GEMM

从数据复用到矩阵内核

核心解释

GEMM 是通用矩阵乘法。它把乘法、缩放与已有结果的累加组合起来,是线性层等计算的基础。先看不含缩放与偏置的情形:C=AB⊤C=AB^{\top}。A 的每一行与 B 的每一行做点积,得到 C 的一个元素。

A∈RM×K,B∈RN×K,C∈RM×NCij=∑k=1KAikBjk\begin{gathered} A\in\mathbb{R}^{M\times K},\quad B\in\mathbb{R}^{N\times K},\quad C\in\mathbb{R}^{M\times N}\\ C_{ij}=\sum_{k=1}^{K} A_{ik}B_{jk}\end{gathered}

GPU 将输出分成 tile。一个 CTA 通常负责一块输出,沿 K 维逐段读入输入并累加。不同输出会使用相同的输入,分块让这些数据在缓存、shared memory 和寄存器中重复使用。[1]

流水线把搬运下一块数据与计算当前块重叠起来。Hopper 上常用 TMA 搬运输入、WGMMA 执行矩阵乘加,并用多个缓冲区维持生产与消费。等待数据就绪和等待缓冲区可复用,是两个不同的依赖。[2]

单个 CTA 内的效率之外,还要把工作均匀分给整张 GPU。输出 tile 太少、边缘 tile 不完整,或最后一波任务没有填满,都可能降低吞吐。Persistent kernel、Split-K 和 Stream-K 从不同角度调整工作分配。[3]

训练、prefill 与 decode 会产生不同的矩阵形状。较大的 M 往往有更多权重复用;小 M 更容易暴露带宽和并行工作不足的问题。低精度再加入 scale、打包与误差控制,因此需要同时检查 kernel 耗时和完整调用的成本。[4] [5]

资料

QA

公式里的转置,是否必须对应一次数据拷贝?

对 A∈RM×KA\in\mathbb{R}^{M\times K} 和 B∈RN×KB\in\mathbb{R}^{N\times K},计算 C=AB⊤C=AB^{\top} 得到 M×NM\times N 的输出。这里的转置描述数学运算。

实际 kernel 根据 stride、描述符和指令要求读取数据。支持现有布局时,可以直接计算;需要重新打包、对齐或改布局时,才产生额外的数据搬运。

同一个矩阵乘法,为什么大 batch 快,小 batch 吃不满算力?

忽略缩放与偏置,乘加按两次浮点操作计数,工作量约为 2MNK2MNK。若 A、B 各从显存读一次、输出写一次,元素字节数分别为 bA,bB,bCb_A,b_B,b_C,下面给出理想化的显存算术强度。实际重复读取和附加操作会改变它。

当 MM 很小时,读取 B 的成本可能主导。增大 M 让同一份权重参与更多计算,通常提高复用,但最终还要看缓存、调度和计算吞吐。

Iideal=2MNKbAMK+bBNK+bCMNI_{\mathrm{ideal}}=\frac{2MNK}{b_A MK+b_B NK+b_C MN}

Tile 做得越大,复用越多,为什么仍可能变慢?

增大输出 tile 可以提高输入复用,也会增加累加器和缓冲区占用。资源压力可能降低常驻 block 数,甚至造成寄存器溢出。

大 tile 还会减少可分配的任务数量。矩阵边缘的无效工作与最后一波没有填满的 SM,可能抵消复用带来的收益。

增大 K tile 和增加流水线 stage,是同一件事吗?

K tile 决定每轮沿归约维处理多少数据。stage 数决定同时在流水线中保留多少组输入缓冲区。两者都影响 shared memory 用量,但解决的问题不同。

增加 stage 可以让搬运更早开始;增大 K tile 会改变每轮的搬运和计算工作量。调优时需要一起检查资源占用和等待时间。

TMA 拷贝完成以后,这块 shared memory 就能覆盖了吗?

拷贝完成表示输入可以交给消费者。消费者发出的异步矩阵指令,还可能继续从这块 shared memory 读取数据。

复用缓冲区前,要等待相关读取结束,再把空闲阶段交还给生产者。否则下一轮拷贝可能覆盖正在计算的输入。

按分组顺序编号 tile,就能保证 GPU 按这个顺序执行吗?

Triton 教程把 program ID 映射到分组后的输出 tile,以改善相邻工作之间的数据复用。这个映射改变谁处理哪块数据。

普通 grid 中的 block 调度顺序没有因此获得保证。需要显式控制工作分配时,可以研究 persistent kernel 的 tile scheduler;它也需要正确处理跨 CTA 的依赖。

输出 tile 不够分时,Split-K 和 Stream-K 为什么有用?

当 M/N 较小而 K 较大时,可以把一个输出 tile 的归约工作分给多个 CTA。这样增加并行工作,但每个 CTA 只得到部分和。

Split-K 按设定的份数切分 K。Stream-K 从整体的内循环工作量出发分配任务,缓解尾波负载不均。切开的输出仍需要归约,收益要覆盖额外的同步和数据搬运。

用了 FP8 或 FP4,累加与缩放也都用同样精度吗?

输入存储、scale、乘法与累加结果可以使用不同格式。低精度 GEMM 的接口还需要说明缩放粒度、scale 布局以及输出类型。

同样叫 FP4,也可能采用不同的 block scaling 格式。比较性能前,先对齐数值路径与误差要求,再把量化、打包和 scale 处理的成本算进实际调用。

Grouped GEMM 为什么适合专家计算?

路由后,各专家收到的 token 数可能不同,也就产生不同 M 的矩阵乘法。Grouped GEMM 把这些问题交给同一个调度体系,减少逐个启动小 kernel 的成本。

它仍要面对不均匀的工作量。连续布局与 masked 布局适合不同的输入组织方式,不能只按总 FLOPs 判断哪一个更快。

自己的 kernel 比 cuBLAS 快,怎样判断这个结论成立?

先对齐 shape、布局、输入和累加精度、输出操作与误差检查。把编译、初始化和预热放在稳态计时之外,用 GPU 计时记录多次运行。

明确测试热缓存还是冷缓存,并保持相同设置。记录 GPU、软件版本、功耗和频率条件。若比较应用收益,再把量化、转置、分配与后处理等实际开销计入。