1. AI训练与GPU计算基础:为什么需要GPU?

大家好,我是你们这趟旅程的向导。咱们直接开门见山——搞AI训练,为什么非得用GPU?CPU不行吗?

说实话,我早年刚入行时也这么想过。那时候我还在用CPU跑一个简单的两层神经网络,训练一次要等一宿。后来换了块入门级GPU,同样的模型,十几分钟就跑完了。你想想看,这差距从哪来的?

核心就一句话:CPU是“通才”,GPU是“专才”。

CPU擅长处理复杂的逻辑分支、串行任务,它把大量晶体管用在了缓存和控制单元上。而GPU呢?它把绝大部分晶体管都堆成了计算单元。说白了,GPU就是为“简单、重复、大规模并行”的计算而生的。

核心观点: AI训练的本质,就是海量的矩阵运算和卷积操作。这些操作天然具备“数据并行”的特性——同一个操作,作用在成千上万个不同的数据上。这正是GPU的强项。

1.1 GPU架构概览:SM、Warp、Memory Hierarchy

要写好GPU算子,你得先摸清它的“脾气”。我习惯把GPU架构拆成三个层次来看:计算单元、调度单元、存储层次。

1.1.1 SM(Streaming Multiprocessor)—— 真正的“干活的人”

一个GPU由多个SM组成。每个SM内部包含若干CUDA Core(或者叫Shader Core),以及共享内存、寄存器文件、调度器等。你可以把SM想象成一个“小工厂”,里面有好几条生产线(CUDA Core)。

我在项目中遇到过一个问题:某个算子性能死活上不去,后来发现是SM利用率太低。说白了,就是没让SM“吃饱”。每个SM能同时驻留的线程块(Thread Block)数量是有限的,你得尽量让每个SM塞满线程块,才能压榨出它的全部性能。

1.1.2 Warp —— GPU调度的基本单位

这个点特别重要,我建议你记牢。GPU并不是一条一条地执行线程,而是32个线程绑在一起,组成一个Warp。这32个线程执行同一条指令,但处理不同的数据。这就是所谓的SIMT(单指令多线程)模型。

避坑指南: 我曾经因为没注意Warp发散(Warp Divergence),导致性能直接腰斩。如果Warp内的32个线程走了不同的分支(比如if-else),那GPU只能串行执行这些分支,其他线程只能干等着。所以,写算子时尽量让同一个Warp内的线程走相同的代码路径。

1.1.3 Memory Hierarchy —— 存储的“金字塔”

GPU的存储结构,说白了就是一个金字塔。越往上,速度越快,容量越小;越往下,速度越慢,容量越大。

存储层级 位置 延迟(大约) 容量 作用域
寄存器 SM内部 1 cycle 几十KB/SM 单个线程
共享内存 SM内部 ~20 cycles 几十~上百KB/SM 线程块内
L1缓存 SM内部 ~30 cycles 与共享内存共用 SM内
L2缓存 芯片全局 ~200 cycles 几MB 全局
全局内存 显存 ~400-800 cycles 几GB~几十GB 全局

嗯,这里要注意:全局内存的延迟是寄存器的几百倍。所以,优化算子的核心思路之一,就是尽量减少对全局内存的访问,把数据尽量往“金字塔”的上层搬。

我个人习惯,在写任何算子之前,先画一张数据流图,标清楚哪些数据该放共享内存,哪些该放寄存器。这一步做好了,后面能省很多事。

1.2 AI训练中的计算模式

AI训练里,来来去去就那么几种计算模式。我把它们总结成三类:矩阵乘法、卷积、归一化。你把这三种吃透了,大部分算子的优化思路就通了。

1.2.1 矩阵乘法 —— 算力的“试金石”

矩阵乘法(GEMM)是深度学习最核心的操作。全连接层、注意力机制里的QKV计算,本质上都是矩阵乘法。

为什么矩阵乘法这么重要?因为它计算密度高,容易把GPU的算力喂饱。一个优化的好的矩阵乘法,可以达到理论峰值算力的80%以上。

我举个例子,一个简单的矩阵乘法 C = A * B,其中A是MxK,B是KxN:

// 伪代码:最朴素的实现
for (int i = 0; i < M; i++) {
    for (int j = 0; j < N; j++) {
        float sum = 0.0f;
        for (int k = 0; k < K; k++) {
            sum += A[i * K + k] * B[k * N + j];
        }
        C[i * N + j] = sum;
    }
}

这个三重循环,直接跑在GPU上,性能会非常差。为什么?因为对B的访问是“列优先”的,而B在内存中是“行优先”存储的,导致缓存命中率极低。

优化技巧: 我常用的方法是“分块”(Tiling)。把大矩阵切成小块,让每个小块能完全塞进共享内存里。这样,计算时从共享内存读数据,速度比从全局内存读快几十倍。具体做法是:把A和B都切成大小为BLOCK_SIZE的小块,每个线程块负责计算一个小块的结果。

1.2.2 卷积 —— 计算机视觉的“心脏”

卷积操作,说白了就是用一个小的“滤波器”(Kernel)在输入图像上滑动,做点积运算。CNN里的卷积层、池化层,都离不开它。

卷积的计算模式,其实可以转化成矩阵乘法来做。这就是著名的im2col(image to column)方法。把输入图像中每个卷积窗口拉成一个列向量,所有列向量拼成一个矩阵,然后跟滤波器矩阵做乘法。

我在项目中遇到过一个问题:im2col虽然能利用高效的矩阵乘法库,但它会引入大量的内存冗余(因为同一个像素可能出现在多个窗口中)。对于大模型,这种内存开销是难以接受的。所以,现在更流行的是直接实现高效的卷积算子,比如使用Winograd算法或FFT。

1.2.3 归一化 —— 训练的“稳定器”

归一化操作,比如Batch Normalization、Layer Normalization,是让训练更稳定的关键。它们的作用是把中间层的输出拉回到一个均值为0、方差为1的分布上。

归一化的计算模式,核心是统计量的计算:求均值、求方差。这些操作需要跨多个维度做规约(Reduction)。

举个例子,Batch Normalization在训练时,需要对每个通道,计算所有样本、所有空间位置的均值和方差:

// 伪代码:Batch Normalization 前向
// 输入 x: [N, C, H, W]
// 输出 y: [N, C, H, W]

// 第一步:对每个通道c,计算均值
for (int c = 0; c < C; c++) {
    float mean = 0.0f;
    for (int n = 0; n < N; n++)
        for (int h = 0; h < H; h++)
            for (int w = 0; w < W; w++)
                mean += x[n][c][h][w];
    mean /= (N * H * W);
    // 第二步:计算方差,类似
    // 第三步:归一化
}

这个规约操作,在GPU上实现时,最常用的方法是“树形规约”(Tree Reduction)。每个线程块内,先把数据两两相加,再四四相加,直到得到一个最终结果。我习惯用共享内存来做这个事,因为共享内存的带宽比全局内存高得多。

总结一下: 矩阵乘法、卷积、归一化,这三种计算模式覆盖了AI训练中90%以上的计算量。理解它们的本质——矩阵乘法是“密集计算”,卷积是“滑动窗口+点积”,归一化是“统计规约”——是写出高性能算子的第一步。

好了,这一章的内容就到这里。记住,GPU不是万能的,但用对了地方,它就是你的“性能加速器”。下一章,我们会深入CUDA编程模型,手把手带你写出第一个高性能算子。

AI训练GPU计算核心知识体系 为什么需要GPU? GPU架构概览 AI训练计算模式 CPU vs GPU:通才 vs 专才 并行计算:数据并行 SM:流多处理器 Warp:32线程一组 Memory Hierarchy 矩阵乘法(GEMM) 卷积(Convolution) 归一化(Normalization) 核心目标:压榨算力,减少访存
可可精品资料库