2. 计算核心 (Tensor Core / SIMT) 优化

各位同学,今天我们来聊聊AI芯片里最核心的部分——计算核心的优化。说白了,就是Tensor Core和SIMT这两大流派怎么把矩阵乘法和卷积算得飞快。我做了这么多年芯片优化,发现很多人只关注算法层面,却忽略了硬件底层的执行细节。嗯,这里面的门道其实挺多的。

矩阵乘法的硬件加速原理

矩阵乘法是深度学习的基石。全连接层、注意力机制,归根到底都是矩阵乘。传统GPU用SIMT(单指令多线程)来处理,每个线程算一个点积。但Tensor Core不一样,它一次算一个4×4的小矩阵乘。

核心差异:SIMT是「一个线程算一个数」,Tensor Core是「一个指令算一个矩阵」。吞吐量差距可达16倍以上。

我记得在优化BERT模型时,发现矩阵乘的尺寸如果不对齐到4的倍数,Tensor Core根本用不上。那性能直接掉到原来的1/10。所以我的习惯是:所有矩阵维度必须对齐到16或32,具体看硬件支持。

Tensor Core的硬件结构其实很简单:

  • 输入:两个4×4的FP16矩阵A和B
  • 累加:一个4×4的FP32矩阵C
  • 输出:D = A × B + C

你想想看,一次指令完成16个乘加运算,而SIMT需要16条指令。这就是硬件加速的本质——用面积换延迟,用并行换吞吐。

卷积的硬件加速:从im2col到Winograd

卷积的加速比矩阵乘更复杂。为什么?因为卷积有滑动窗口,数据复用模式不一样。我最早做卷积优化时,踩过一个坑:直接用im2col把卷积转成矩阵乘,结果内存占用暴涨了8倍。

常见的卷积加速方法:

方法 原理 适用场景
im2col 将卷积展开为矩阵乘 小卷积核(3×3)
Winograd 用更少的乘法次数 小卷积核,高精度需求
FFT 频域相乘 大卷积核(7×7以上)
直接卷积 硬件定制化计算 特定形状,高利用率

我个人最推荐Winograd。为什么?因为它把乘法次数从O(n²)降到了O(n)。以3×3卷积为例,Winograd F(2,3)只需要4次乘法,而传统方法要9次。省了一半还多。

实战技巧:Winograd的变换矩阵可以提前算好存起来。我在项目中就是这么做,把变换开销摊到每个batch上,整体性能提升了30%。

Warp调度与指令级并行(ILP)

Warp是GPU调度的基本单位,一般是32个线程。硬件一次发射一条指令,32个线程同时执行。但这里有个问题:如果线程之间有数据依赖,流水线就会停顿。

我见过很多新手写的CUDA代码,一个warp里32个线程都在读写同一个地址,结果全部串行化了。性能惨不忍睹。

指令级并行(ILP)的核心思想:让相邻指令之间没有数据依赖。比如:

// 不好的写法:有数据依赖
float a = b * c;
float d = a * e;  // 必须等a算完

// 好的写法:无数据依赖
float a1 = b1 * c1;
float a2 = b2 * c2;
float d1 = a1 * e1;  // 可以和a2并行
float d2 = a2 * e2;

你看,把独立的计算交错开,硬件就能同时发射多条指令。现代GPU每个时钟可以发射2-4条指令,ILP就是让这些发射槽别空着。

注意:ILP不是万能的。寄存器压力过大会导致spill到local memory,反而更慢。我建议每个线程的寄存器使用量控制在32-64个之间。

数据复用与寄存器缓存策略

数据复用是性能优化的重头戏。说白了就是:别让数据在内存和计算单元之间来回倒腾。一次加载,多次使用。

以矩阵乘为例,C = A × B。传统做法是每个线程加载A的一行和B的一列。但这样A的每个元素只被用了一次,B的每个元素也只被用了一次。浪费啊!

更好的策略是分块(tiling):

  • 把A分成16×16的小块
  • 把B分成16×16的小块
  • 加载到共享内存中
  • 每个小块被多个线程复用

我曾经优化过一个矩阵乘kernel,通过调整分块大小从8×8到16×16,性能提升了2.3倍。为什么?因为数据复用率从8倍提升到了16倍。

寄存器缓存是更底层的优化。寄存器是芯片上最快的存储,延迟只有1个时钟周期。而共享内存要20-30个周期,全局内存要400-800个周期。

我的策略是:能放寄存器的绝不放共享内存,能放共享内存的绝不放全局内存。具体做法:

  1. 把频繁使用的循环变量放寄存器
  2. 把中间计算结果放寄存器
  3. 把小的查找表放寄存器(比如sigmoid的近似表)

避坑指南:我曾经把一个大数组全部声明为寄存器变量,结果编译器报寄存器溢出。每个线程的寄存器数量是有限的(一般是255个),超过就会spill到local memory,性能反而下降。所以寄存器不是越多越好,够用就行。

知识体系总览

下面这张图总结了本章的核心逻辑。我建议你把它打印出来贴在工位上,做优化时对照着看。

计算核心优化知识体系 硬件加速原理 • Tensor Core:4×4矩阵指令 • SIMT:32线程并行 • im2col → 矩阵乘 • Winograd → 减少乘法 Warp调度与ILP • Warp = 32线程 • 指令级并行(ILP) • 消除数据依赖 • 寄存器压力控制 数据复用与缓存 • 分块(Tiling)策略 • 共享内存复用 • 寄存器缓存 • 避免spill 优化核心原则 1. 最大化计算密度:让Tensor Core/SIMT始终忙碌 2. 最小化数据搬运:能复用就复用,能缓存就缓存 3. 平衡并行与资源:ILP不是越多越好,寄存器不是越大越好 4. 对齐到硬件边界:维度对齐、Warp对齐、内存对齐 —— 理论与实践的分界线 ——

这张图把三大模块串起来了。硬件加速是基础,Warp调度是手段,数据复用是灵魂。三者缺一不可。

最后说一句:优化没有银弹。每个模型、每个算子、每个硬件都有自己的特点。我的经验是:先profile,再优化,再profile,循环迭代。别一上来就搞花里胡哨的技巧,先把基础打牢。

我的建议:从最简单的矩阵乘开始,用ncu或nsys做性能分析,找到瓶颈再针对性优化。等你把矩阵乘吃透了,卷积、注意力这些自然就通了。