博主介绍:程序喵大人

在这里插入图片描述

咱们前几章把模型结构、算子还有显存账本都盘得差不多了。现在,是时候把目光投向这块深色的控制台了——GPU 硬件本身。

作为这套前传的最后一章,咱们的画风也从之前的车间和白板,正式切入到了底层的硬件视角。你看这块 GPU 板卡,它的核心其实就两样东西:外围提供巨大存储空间的高带宽显存(HBM),以及中央密密麻麻负责干活的流式多处理器(SM)。

我们在 PyTorch 里用 Python 调用的那一个个高层算子,最终都会通过 PyTorch 的分发机制转到 C++ 底层,并启动对应的 CUDA Kernel。这些 Kernel 才是直接驱使 GPU 芯片上数以千计的流式多处理器(SM)做并行计算的真正载体。读懂了这个过程,你以后去看 Nsight Profiler 的性能报告,或者听大佬们聊算子融合(Kernel Fusion)、Flash Attention,就不会觉得是在听天书了。

一、CPU vs GPU:一个核 vs 一万个核

在这里插入图片描述

一提到跑大模型,为什么大家非得用 GPU,CPU 就不行吗?其实只要对比一下它们的核心构成,答案就不言而喻了。

你看左边的 CPU,它就像是几个超级顶尖的特种兵(比如 8 到 16 个核心)。每个人的装备都极其豪华,主频跑得飞快(能到 5GHz),特别擅长处理那种逻辑复杂、拐弯抹角、还得排队执行的任务。

再看右边的 GPU,拿 A100 来说,它里面直接塞了将近 7000 个 CUDA 核心!这些核心单拎出来看,可能反应还有点慢(只有 1-2 GHz),甚至只会做最简单的乘法和加法。但它强就强在“万人齐发”——面对像深度学习这种全是独立数据运算的任务,这几千人同时开工,瞬间就能把 CPU 按在地上摩擦。

二、GPU 的内存层级:HBM / SRAM / 寄存器

在这里插入图片描述

既然 GPU 算得这么快,那数据喂得跟不上怎么办?这就逼着 GPU 搞出了极其严苛的“内存层级金字塔”。

你看最底下的 HBM(高带宽显存),它就是咱们常说的“80GB 显存”。所有的权重、激活值平时都老老实实地待在这里。它容量虽然大,但离计算核心相对较远,存取起来其实是有些拖沓的。

往上一层,是每个 SM 专属的 Shared Memory(类似 SRAM)。它的容量极其可怜(只有几十 MB 甚至更少),但速度快得惊人。算子优化的核心战役,往往就在这层打响。比如大名鼎鼎的 Flash Attention,说白了就是想尽办法在这一层把活干完,死活不愿意去底层慢吞吞的 HBM 里来回搬数据。

到了最顶尖的寄存器(Registers),那就更小更猛了,它是直接和计算单元绑定的,每个线程独享,速度快到飞起。理解了这个金字塔,你就抓住了 GPU 优化的七寸。

三、Kernel 是什么:一段在 GPU 上并行跑的代码

在这里插入图片描述

说了半天硬件,咱们来看看让硬件动起来的指令——Kernel。

你可以把 Kernel 简单理解为一段写成 C 或者 CUDA 的函数。但它的牛逼之处在于它的执行方式:你看右边这张网格图。当 CPU 下达一条 Kernel 启动指令时,GPU 会瞬间召唤出成百上千个线程(Thread)。

这些线程被高度组织化、模块化地编排。其中最基本的调度单位是 Warp(线程束),每个 Warp 包含固定的 32 个线程。在硬件层面,这 32 个线程共享同一个指令发射器,在同一时刻跑同一条指令。这种物理层面的同步执行机制被称为步调一致(Lockstep)。

多个 Warp 组合在一起构成 Block(线程块)。同一个 Block 内部的线程由于物理距离极近,可以使用 Shared Memory 进行极高带宽的组内数据共享与互助,而所有 Block 集合起来,就组成了最外层的 Grid(线程网格)。

当 Kernel 运行的时候,这上万个线程同时执行一模一样的代码指令。每个线程利用自己独一无二的线程 ID(Thread ID),去定位和计算对应的输入数据。这就是传说中的单指令多线程(SIMT,Single Instruction Multiple Threads)模式。

正因为 Warp 中的 32 个线程绑定在一起执行,当代码中出现复杂的 if-else 分支时,不同线程走不同的路径会导致分支分化(Warp Divergence)。硬件在此时会采取串行化的方式来依次执行各分支路径,从而使得计算资源出现闲置。编写极致性能的 CUDA Kernel 往往需要保持 Warp 内部执行路径的高度一致。这种暴力的并行分发模式是大模型得以流畅运转的物理基石。

四、算子怎么变成 Kernel:框架在背后做的事

在这里插入图片描述

那我们在写 PyTorch 的时候,又是怎么触发这些 Kernel 的呢?其实框架在背后帮我们把脏活累活全干了。

你看这条分层路径:最左边是你写的一行极简的 Python 代码(比如 torch.matmul)。一敲回车,PyTorch 内部的分发系统就会迅速接管,它会偷偷看看你的数据是在 CPU 还是 GPU 上,用的是 FP16 还是 BF16。

确认完身份后,它会去仓库里翻出一段早就写好的、极致优化过的 CUDA Kernel(可能是 NVIDIA 官方的 cuBLAS,也可能是别的库)。最后,这段代码才会被真正扔到硬件层面去并行执行。我们平时讨论的底层优化(比如用 Triton 写算子),其实都是在跳过表层的 Python,直接在 Kernel 这一层抠细节。

五、矩阵乘法为什么是 GPU 的爱

在这里插入图片描述

回过头来看,为什么深度学习里的算子能如此完美地契合 GPU 这套并行逻辑?关键就在于——深度学习的大部分计算,全都是矩阵乘法(Linear、Attention 基本上全是它)。

你看中间这个拆解图:算一个输出矩阵 C,它里面的每一个小格子,只和 A 的某一行以及 B 的某一列有关,和其他格子完全独立。

这意味着什么?意味着这几万个输出格子,可以直接分配给 GPU 里的几万个线程,大家不用互相商量,不用等对方算完,只管闷头算自己的那一块就行。这种天然零依赖的数据结构,简直就是为 GPU 量身定制的“甜点”。

六、Memory-bound vs Compute-bound:瓶颈在哪

在这里插入图片描述

既然跑在硬件上,总会遇到天花板。在 AI Infra 领域,咱们通常把算子分为两大类瓶颈,也就是大名鼎鼎的 Roofline 模型。

  • 一类叫 Compute-bound(算力瓶颈),比如巨大无比的矩阵乘法。这种算是“幸福的烦恼”,因为这意味着你的显存带宽跟得上,GPU 的算力被你确确实实地压榨到了极限。
  • 另一类叫 Memory-bound(显存带宽瓶颈),比如之前讲的 LayerNorm、激活函数。它们的特点是计算量极小,但数据搬运量很大。GPU 算得太快,结果全卡在等数据从 HBM 传过来的路上了。这种时候,计算核心其实是在无奈地摸鱼。遇到这类算子,我们的终极优化思路就是——合并它们,减少读写 HBM 的次数。

七、一次 Forward 在 GPU 上的时间轴

在这里插入图片描述

把上面所有的理论串起来,咱们最后来看看,一次真正的 Forward Pass,在时间轴上到底长什么样。

你看这张像心电图一样的时间线。从左到右,一个个 Kernel 排着队在 GPU 上轰炸。你能明显看到,Linear 和 Attention 这类 Compute-bound 算子,霸占了最宽、最长的时间条;而那些 Norm 和 Softmax,只占了零星的一点点绿色短条。

但别小看这些短条,每次启动一个 Kernel 都是有隐形开销的。如果让这些小算子频繁起停,时间全浪费在跑腿上了。所以现在的工程大神们,天天在做的就是把这些零碎的小 Kernel 揉成一个大 Kernel(Kernel Fusion),让这根时间轴变得越紧凑越好。

八、总结:你现在可以读 Transformer 系列了

在这里插入图片描述

好了,到这里,咱们这套【AI专栏】图解深度学习-AI infra工程师必知必会的前传就正式完结了。

咱们退后一步看看你现在手里的这套“心智模型”:

  • 第一步,你知道了模型就是权重加结构;
  • 第二步,你看懂了流淌在里面的 Tensor 和 Shape;
  • 第三步,你认识了各个算子工位的成本;
  • 第四步,你理清了训练和推理那两笔截然不同的显存账本;
  • 最后,咱们又一起钻进了 GPU,搞懂了 Kernel 是怎么一回事。

有了这套严丝合缝的基础认知,接下来你再去看那些动辄谈论 KV Cache 管理、多卡分布式并行、甚至是手写算子的硬核文章,绝对不会再有一头雾水的感觉了。

现在,带上这套知识地图,你可以自信地跨入正传【AI专栏】图解Transformer:从token到推理引擎的大门了!

码字不易,欢迎大家点赞,关注,评论,谢谢!

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐