详细拆解了在NVIDIA H100 GPU上从零开始优化矩阵乘法(GEMM)的全过程。通过七个渐进式内核,生动展示了如何从最原始的全局内存访问,逐步运用共享内存分块、寄存器分块、向量化加载、填充避冲突等技巧,并最终驾驭Tensor Core与TMA等Hopper架构黑科技,实现逼近甚至超越cuBLAS的性能。
你写的矩阵乘法,GPU看了都想哭!
想象一下,你辛辛苦苦在纸上算两个大矩阵相乘,手指头都快磨出火星子了,结果隔壁老王掏出一块叫“H100”的黑砖头,一眨眼就干完了,还顺手给你泡了杯咖啡。这不是科幻片,这是2026年一个普通程序员的日常。
为啥差距这么大?因为人家用的是Tensor Core(张量核心),而你还在用小学加减法!
第一步:别当冤种,先搞懂GPU是个啥玩意儿
想让GPU给你打工,总得知道它家里有几口人、几间房吧?
H100这块芯片,可不是一块简单的硅片,它简直就是一个超级城市!这个城市里有8个大区,叫GPC(图形处理集群),每个大区里又塞了18个小工厂,叫SM(流式多处理器)。整个城市总共132个小工厂在同时开工,场面那叫一个壮观。每个小工厂内部更是精妙绝伦,里面有128个专门干浮点运算的工人(FP32 CUDA核心),还有4个肌肉发达、专啃硬骨头的特种兵——第四代Tensor Core(张量核心)。这些特种兵干的活儿不是加加减减,而是一次性吞下一整块64x64的矩阵,咔嚓一下就给你算出结果,效率高到离谱。
更绝的是它的内存系统,就像一个金字塔。塔尖是寄存器(RMEM),那是每个工人自己兜里的小本本,记东西最快,但地方太小。
往下一层是共享内存(SMEM),相当于工厂车间里的公共白板,所有工人都能快速看到上面的内容。
再往下是L2缓存,像是整个城市的中央仓库,东西多但取起来稍微慢一点。
塔底就是显存(GMEM),那是城外的巨型物流中心,容量巨大,但每次去取货都得花500个时钟周期,跟跑一趟郊区差不多。
所以,聪明的做法是什么?当然是把要用的原材料提前搬到车间白板上,让工人们不用老跑郊区,这样才能让他们的小手速拉满!这就是所有高性能计算的第一铁律:减少去显存的次数。
第二步:你的第一版代码,可能还不如小学生
好了,现在你信心满满地写下了第一个矩阵乘法内核。逻辑很简单:一个线程负责算C矩阵里的一个格子,然后循环N次,把A矩阵对应行和B矩阵对应列的数一个个乘起来再加总。听起来没毛病对吧?
但GPU看了只想笑。为啥?因为你让每个工人(线程)都跑显存物流中心去了!而且,他们去的方式还是排着队,一个一个地问:“老板,A[0][0]给我!”、“老板,A[0][1]给我!”……这叫非合并访问,效率低到渣。就算你运气好,让他们按顺序排队领货(合并访问),那也架不住他们一天要跑几百趟。结果就是,GPU的计算单元大部分时间都在摸鱼等数据,算力利用率只有可怜的8.2%。这就好比你请了一群米其林三星大厨,却让他们自己去菜市场一根一根地挑葱,最后做出来的菜能好吃到哪去?
第三步:学会团队合作,把活儿搬到车间白板上
吃一堑长一智,第二版代码必须支棱起来!这次学聪明了,不再让每个工人单打独斗。
一个线程块(block)里的所有工人一起行动,先把A和B矩阵的一小块(tile)从显存物流中心搬回自己工厂的车间白板(shared memory)上。搬完之后,大家再围在白板前,用自己的小本本(寄存器)把需要的数字抄下来,然后埋头苦算。算完一轮,再去搬下一块。
这一招简直是神来之笔!为啥?因为去郊区(显存)的次数大大减少了,大部分时间都在高速的车间白板(共享内存)上操作。性能立马翻了1.7倍,达到了cuBLAS的13.9%。
虽然还是个弟弟,但至少证明路子走对了。这时候GPU的负载均衡也变了,不再是傻乎乎地等数据,而是开始真正干活了,FMA(乘加单元)的活跃度从几乎为零提升到了14.81%。这说明,我们的工人终于有活儿干了!
第四步:一个顶俩,让每个工人多干点活儿
尝到甜头后,野心就大了。既然车间白板这么快,那能不能让每个工人一次多算几个格子呢?当然能!这就是寄存器分块(register tiling)。
以前一个工人只算C矩阵的一个格子,现在让他算一个小方块,比如2x2或者4x4。他可以把这个小方块的部分结果先存在自己的小本本(寄存器)里,反复利用从白板上抄来的数据,最后一次性把结果写回显存。
这样一来,不仅减少了去白板的次数,连最后写回显存的次数也少了。这就好比大厨不再只做一道菜,而是一次性规划好几道菜的流程,中间的备料可以共用,效率自然蹭蹭往上涨。
性能再次飙升,达到了cuBLAS的36.8%。
不过,新问题也来了:车间白板(L1/TEX)成了新的瓶颈。工人们在白板和小本本之间搬运数据太频繁了,导致前端指令发射单元忙得不可开交。看来,光靠人多还不够,还得讲究干活的方式。
第五步:别一个一个搬,学会用推车!
前面我们一直在优化“搬什么”和“怎么算”,但忽略了“怎么搬”。
之前都是让每个工人用手一个一个地从白板上拿数字,这太原始了!现代GPU支持向量化加载,简单说,就是给你一辆小推车(比如float4),一次能拉4个数字回来。
这不仅仅是省力气,更重要的是,它直接减少了需要执行的指令数量。以前拉4个数要发4条指令,现在只要1条。对于一个包含32个工人的小组(warp)来说,这节省的指令量是惊人的。
GPU的前端调度器终于松了口气,不用再被海量的加载指令压垮了。性能直接翻倍,干到了cuBLAS的72%!这感觉,就像从手工作坊升级到了半自动流水线。
不过,新麻烦又出现了:车间白板(shared memory)是有32个窗口(bank)的,如果大家挤在一个窗口领东西,就会排队,造成所谓的“银行冲突”。我们的代码里就出现了高达5路的读取冲突,白白浪费了大量时间。看来,光有推车还不够,还得规划好领货路线。
第六步:规划领货路线,给白板加个缓冲带
解决银行冲突的经典方法,就是在白板的布局上动点手脚——加填充(padding)。
想象一下,白板原本是严丝合缝的32列,现在我们在每行末尾偷偷加4个空位。这样一来,原本会挤在同一个窗口的工人,就被巧妙地分散到了不同的窗口。之前的2.6路存储冲突瞬间消失。虽然读取冲突因为访问模式的原因没完全解决,但整体效率已经非常高了。
这时候,GPU的计算单元(FMA)活跃度冲到了56.73%,调度器也忙得飞起,Stall MIO Throttle(内存输出节流等待)这个指标从0.59降到了几乎为零的0.02。这说明,我们的优化已经非常接近硬件的极限了。接下来,要想再突破,就得玩点更高级的花样。
第七步:召唤特种兵,Tensor Core登场!
前面所有的努力,都是在为CUDA核心服务。
但从Volta架构开始,英伟达就给GPU塞进了真正的王牌——Tensor Core(张量核心)。这玩意儿根本不是按普通思路工作的。它不认单个数字,只认矩阵块。
在Hopper架构的H100上,它甚至进化出了WGMMA(Warp Group Matrix Multiply Accumulate)这种究极形态。
一条WGMMA指令,能让128个线程(一个warp group)协同工作,一次性完成64x64x16的矩阵乘加运算,相当于65536次普通的FMA操作!这已经不是量变,是质变。
为了让这些特种兵能全力输出,英伟达还贴心地配了个后勤部长——TMA(Tensor Memory Accelerator,张量内存加速器)。你只需要告诉TMA:“嘿,把A矩阵那块64x16的砖头搬到SMEM的指定位置”,它就会在后台默默地、高效地、还自带防冲突布局(swizzling)地把活儿干完。
你完全不用操心怎么搬、怎么排,专心指挥特种兵打仗就行。这套组合拳打出来,性能直接原地起飞,轻松超越FP32模式下的cuBLAS,向着混合精度的巅峰发起冲击。
终极奥义:从人肉苦力到指挥千军万马
回顾整个优化历程,就是一个从“人肉苦力”到“运筹帷幄”的蜕变。
最初,我们像原始人一样,一个数字一个数字地搬、一个格子一个格子地算。后来学会了团队合作,把活儿集中到车间白板上。
接着让每个工人身兼数职,提高个人产出。
再后来,我们换上了高效的搬运工具(向量化),并优化了车间布局(padding)以避免拥堵。
最终,我们干脆放弃了亲自下场,转而召唤并指挥一支由Tensor Core组成的特种部队,用TMA这样的自动化后勤系统保障他们的弹药供应。
每一步优化,都不是凭空想象,而是基于对GPU这座超级城市内部运作规则的深刻理解。当你真正摸清了它的脾气,它就会回馈你难以置信的性能。这,就是手搓高性能CUDA内核的魅力所在。
Github源码
https://github.com/HamzaElshafie/h100_gemm
这个仓库是一个 高性能的矩阵乘法实现(GEMM, General Matrix Multiply),目标是在 NVIDIA H100 GPU 上用纯 CUDA 代码逐步优化 出接近或媲美 cuBLAS 性能的矩阵乘法内核。
GEMM 是深度学习和高性能计算中最核心的运算之一(如 Transformer、卷积网络等底层的大规模矩阵乘法)——我们通常使用像 cuBLAS 这样的库来完成。这个项目希望通过手写 CUDA 内核,分析并实现自己的高效实现。
下面是对 HamzaElshafie/h100_gemm GitHub 仓库介绍 的清晰概述,帮助你快速了解这个项目的目的、内容和用法。
核心目标
逐步构建一系列 GEMM 实现,每一版都比前一版更优化、更接近硬件极限。
这些实现包括:
* 基础 / 朴素版本(可正确运行但性能低)
* 共享内存 (Shared Memory) 瓦片版本
* 寄存器瓦片 + 向量化
* Warp 级别瓦片
* Tensor Core + 异步 TMA / WGMMA 优化版本(H100 特性)
你可以在性能对比表中看到不同内核的 TFLOPS 及相对于 cuBLAS 的百分比表现。
性能对比亮点(示例)
| 内核类型 | 性能 (TFLOPS) | 相对 cuBLAS | |
⚠️ 注意:这里显示的是不同精度(如 FP32 / BF16 + FP32 混合)下的性能对比。Tensor Core 优化版本在混合精度下提升巨大,但在完全精度下仍需要继续完善。
如何使用
基本步骤包含:
1. 克隆仓库
bash
git clone https://github.com/HamzaElshafie/h100_gemm.git
cd h100_gemm
2. 配置环境(用 Conda 安装依赖)3. 使用 CMake 构建项目
bash
cmake -B build
cmake --build build
4. 运行示例程序
执行时可以选择不同实现内核和矩阵类型,例如:
bash
./gemm general 0 bf16
其中 general 是实现名称,0 是内核 ID,bf16 指定数据类型。
背后的动机
NVIDIA 的 cuBLAS 是行业标准的库,在 Hopper 系列(如 H100)上经过长时间高度优化,并利用各种底层硬件特性(如 tensor cores、TMA、Heuristics 搜索等)实现极高性能。([NVIDIA Docs][2])
这个项目不是简单调用 cuBLAS,而是 教育性地从零开始实现 GEMM,逐步提升性能,并理解 H100 的特性及如何用手写 CUDA 发挥它们的效率。
适合人群
* 对 GPU 编程、CUDA 内核优化感兴趣的开发者
* 想深入理解矩阵乘法底层性能实现的人
* 深度学习基础运算库开发者
* 想学习如何在 H100 这种高端 GPU 上挖掘性能极限的人