【大模型基础设施工程·特别篇】30:推理算子与内核——Attention/GEMM/采样对比、工程实践与真实项目
内容提要
本文聚焦推理服务的算子层:框架决定请求形状与KV布局,算子决定设备成本。需区分Prefill与Decode,分别关注算力与带宽。对比Attention实现(FA、FlashInfer、cuDNN等)应分层看算法、库与引擎集成,并核对版本支持。GEMM、量化、MoE及采样各有陷阱,如小M需辨权重带宽与launch开销,结构化输出常回退CPU。排障应先查引擎日志中的实际backend,再用profiler定位kernel,最后改配置。
延伸解读
先分阶段,再谈算子选型
文章反复强调,同一 Attention 实现在 Prefill 与 Decode 两端的收益曲线不同:Prefill 更偏算力墙,Decode 更偏带宽墙。因此对比 FA、FlashInfer、cuDNN 等实现时,不能脱离阶段标签下结论。工程上应先确认当前瓶颈落在哪一端,再决定是查 Tensor Core 利用率、paged 布局,还是 split-KV 与 CUDA Graph,避免把两端问题混为一谈。
对比要分层,能力要看版本
文章把 Attention 对象拆成算法/参考实现、Kernel 库、引擎集成三类,指出它们不是同一货架商品。GQA 与 MLA 应分列,能力用“支持/有条件/不适用 + 版本”来读,而不是看吞吐圆点。这意味着上线前必须用具体 GPU 架构、模型结构和引擎版本的 support 表核对,不能把“引擎未启用”误判为“某库能力弱”。
小 M GEMM 与采样慢路径的排查思路
Decode 小 M GEMM 不能直接从“M 小”推出“主因是 launch”,文章建议拆成权重/HBM 带宽墙、launch/同步、shape/kernel 覆盖三个可观测分支分别处理。采样侧同样要拆环:grammar 编译与 bitmask 应用常偏 CPU,结构化输出比例高时容易拖慢 decode。排障应先看引擎日志中的实际 backend 名,再用 profiler 对上 kernel symbol,最后才改配置。
换 backend 前先做正确性回归
文章给出的验收清单包括 greedy 对齐、固定种子随机采样统计、eager 与 CUDA Graph 一致性、paged block 边界、GQA/MLA 各自路径、量化质量门禁以及结构化输出场景。这提示读者:算子替换的收益必须建立在正确性回归之上,微基准赢了但线上尾延迟或显存变差,是文章明确点出的错误示范。
Q&A
推理服务中,框架和算子分别负责什么?为什么只优化框架可能效果不好?
推理框架(如 Scheduler/KV 管理)决定请求如何组成 batch 以及 KV 缓存的内存布局;算子与内核则决定对应路径在设备上的实际计算成本。如果只改一边,比如只调整调度策略而不优化算子,或只替换算子而不匹配布局,往往无法获得预期收益,甚至可能因布局不匹配导致正确性问题。
Prefill 和 Decode 阶段在算子选型上有什么不同侧重点?
Prefill 阶段通常更受算力限制,关注长 prompt 下 Attention 和大 GEMM 能否充分利用 Tensor Core;Decode 阶段通常更受带宽限制(小 batch、长 KV),需关注 paged attention 的 KV 读取带宽、split-KV 以及 CUDA Graph 捕获。同一 Attention 实现在两端的收益曲线不同,选型必须带上阶段标签。
对比 FlashAttention、FlashInfer、cuDNN SDPA 等 Attention 实现时,应该从哪些维度评估?
应分层对比:算法/参考实现(如 FA2/FA3、FlashDecoding)、Kernel 库(如 FlashInfer、cuDNN SDPA)、引擎集成(如 vLLM backend、TRT plugin)。评估维度包括 Prefill 算力利用、Decode 小 q_len 长 KV 表现、是否原生支持 paged 布局、GQA/MLA 支持、CUDA Graph 兼容性以及许可与版本边界。不能把不同层级的对象混为一谈,也不能将引擎未启用误判为库能力弱。
Decode 阶段小 M GEMM 变慢,可能的原因有哪些?如何排查?
小 M GEMM 慢不一定只是 launch 开销,需拆分支排查:可能是权重/HBM 带宽墙(权重反复读、算术强度低),也可能是 launch/同步过多(大量短 kernel 与 CPU/GPU 间隙),还可能是 shape 未被专用 kernel 覆盖(配置了量化但 profiler 无对应 symbol)。对应措施包括量化权重、增大有效 batch、使用 CUDA Graph/persistent kernel、修复路由回退,而非盲目切 tiling。
结构化输出(structured output)导致 decode 变慢,应该先检查什么?
应把链路拆开:grammar 编译/状态机常偏 CPU,需检查是否每步重编译、CPU 占用是否与 GPU 重叠不足;bitmask 应用到 logits 是否回退到逐 token Python;sampling 是否仍走通用排序路径。不要笼统归因于“fused sampler 不够快”,因为 mask 与 fused sample 可能分属不同文件/层,需分别查看日志或 profiler 中的实际符号。
在 vLLM 等引擎中,Attention backend 是如何选择的?排障时应遵循什么顺序?
backend 选择是一棵决策树:根据 GPU 架构/驱动、注意力类型(MHA/GQA/MLA 等)、Prefill 或 Decode、是否 paged、KV dtype、是否需 CUDA Graph 等条件,查引擎版本的 support 表后确定具体 backend,并非全球固定优先级。排障顺序:先读引擎日志/metrics 中的实际 backend 名,再用 Nsight 对 kernel symbol,最后才改调度参数或换库。