【AI专栏】图解深度学习-AI infra工程师必知必会 - 第 06 章:模型怎么跑在 GPU 上
博主介绍:程序喵大人
- 35 - 资深C/C++/Rust/Android/iOS客户端开发
- 10年大厂工作经验
- 嵌入式/人工智能/自动驾驶/音视频/游戏开发入门级选手
- 《C++20高级编程》《C++23高级编程》等多本书籍著译者
- 更多原创精品文章,首发gzh,见文末
- 👇👇记得订阅专栏,以防走丢👇👇
😉C++基础系列专栏
😃C语言基础系列专栏
🤣C++大佬养成攻略专栏
🤓C++训练营
👉🏻个人网站

咱们前几章把模型结构、算子还有显存账本都盘得差不多了。现在,是时候把目光投向这块深色的控制台了——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到推理引擎的大门了!
码字不易,欢迎大家点赞,关注,评论,谢谢!
更多推荐

所有评论(0)