为什么大模型 Decode 会让 DCU“算力闲置”:从 Prefill、GEMV 到 HBM 带宽的实测解释

📅 2026/7/19 6:07:00
为什么大模型 Decode 会让 DCU“算力闲置”:从 Prefill、GEMV 到 HBM 带宽的实测解释
适用环境海光 DCU gfx936、DTK/ROCm/HIP、BF16 大模型推理。本文回答为什么监控中 DCU 一直有 kernel 在运行生成 token 仍然不够快为什么 Prefill 和 Decode 的优化方向不同以及怎样用一组简单实测判断双缓冲、调高并行度是否值得做。结论先行在单请求、单 token Decode 中Qwen3.5-27B 的大量线性层会退化为 GEMV时间主要消耗在从 HBM 流式读取权重而不是 BF16 矩阵单元的乘加。若实测带宽已接近峰值继续增加双缓冲或并行 wave 通常无效应转而减少额外访存、优化实际 shape 的 kernel或承认已经接近硬件边界。1. 一个常见误判DCU 忙不代表算力用满在海光 DCU 上部署大模型时常能看到hy-smi显示设备并不空闲profiler 时间线也几乎没有空洞。直觉上这很容易被理解为“计算资源已经吃满没什么可优化的了”。这两个观察只能说明 kernel 在持续执行不能说明矩阵计算单元在高效做乘加。GPU/DCU 执行一个访存受限 kernel 时大量 wave 仍处于活跃状态它们发起 HBM load、等待数据返回、再被调度器切换出去。时间线上 kernel 是连续的但计算单元可能绝大部分时间都在等数据。判断是否还有优化空间关键不是“设备忙不忙”而是下面两个问题这个 kernel 每搬运 1 byte 数据做了多少浮点运算实测带宽或算力距离当前测试环境的硬件上限还有多远前者是算术强度后者决定应该优化计算、访存还是停止在错误方向上继续投入。2. Decode 和 Prefill 不是同一种负载大模型的自回归推理可粗略分为两个阶段。阶段一次处理多少 token典型矩阵形状主要影响的用户指标常见优化方向Prefill一段 prompt 中的多个 token较大的 GEMMTTFT首 token 时延Matrix Core、GEMM tile、FlashAttention、批处理Decode每步 1 个新 tokenN1的 GEMV/skinny GEMMTPOT每输出 token 时延HBM 有效带宽、细长 GEMV、减少中间张量访问、kernel launch以一层线性投影为例Prefill 计算多个 token 时一份权重可被多个输入向量复用矩阵足够大运算量相对权重读取量更高。Decode 则不同每一步只来了一个新 token但仍要读取这一层的大部分权重。矩阵乘法在形状上退化为矩阵向量乘法Prefill: [S, K] x [K, M] - [S, M] S 通常较大 Decode: [1, K] x [K, M] - [1, M] S 1这不是“算得少所以一定快”。对 Decode 而言乘加减少了但权重读取量并没有按同样比例减少。于是 HBM 中的权重流成为主要成本。3. 用单张海光 DCUgfx936的 Roofline 给直觉一个量纲性能上界可以用 Roofline 的简化形式表达P_{\text{achievable}} \min(P_{\text{peak}},\ B_{\text{HBM}} \times I)其中P_{\text{peak}} 是设备可达到的计算峰值B_{\text{HBM}} 是显存带宽I 是算术强度即 FLOPs/Byte。在单张海光 DCUgfx936的环境中通过单卡微基准得到的参考值如下指标微基准结果用途HBM 可达带宽1206 GB/s判断流式读权重的带宽利用率BF16 可达算力约395 TFLOP/s判断大 GEMM 是否接近算力上限Roofline 拐点约327 FLOP/byte低于该值时更可能受 HBM 限制这些数字的作用是建立这套环境的参照系一个 kernel 如果已经接近1206 GB/s就不应再把“提高计算吞吐”作为首要假设。用字符图表示 Roofline性能 ^ 计算上限 P_peak | ---------------------- | / | / 带宽上限: B_HBM x I | / -------------------------------------------- 算术强度 I 约 327 FLOP/byte可以直接理解为“最终速度取两道上限中较低的那一道”一条上限由 HBM 每秒能搬多少字节决定另一条由计算单元每秒能做多少 FLOPs 决定。例如假设某个 kernel 每读取 1 byte 只完成 10 FLOP。即使 HBM 已满速按1206 GB/s × 10 FLOP/byte也只能提供约12.06 TFLOP/s的计算数据远低于395 TFLOP/s的 BF16 计算能力此时加大计算 tile 或提高 Matrix Core 利用率并不能让数据更快到达。反过来若每读取 1 byte 可完成 500 FLOP则带宽侧允许的计算速率约为603 TFLOP/s高于395 TFLOP/s这时才是计算能力先成为上限。因此这类 kernel 的速度首先由搬数据决定应优先减少无效读写或提高有效带宽高于这个值时才更值得检查 Matrix Core 利用率、GEMM tile 和计算指令效率。4. 把这个判断代入 Qwen3.5-27B 的 Decode下面用 Qwen3.5-27B 做一个数量级估算。BF16 的 27B 参数约为 54 GB。单 token Decode 会依次经过大量线性层因此可以把“读取主要权重”近似看成一次约 54 GB 的 HBM 数据流。它不是某一个 kernel 的耗时也不是完整服务的 TPOT只是为了和纯乘加成本作对照。先问第一个问题如果 HBM 持续以 1206 GB/s 读取这 54 GB光搬完这些字节至少需要多久然后问第二个问题假设所有数据都已经在计算单元旁边只计算这一个 token 的乘加理论上要多久单 token 前向约有 2 \times 27\text{B}54\text{ GFLOP} 的乘加量若按 395 TFLOP/s 计算纯计算下界为45 ms 与 0.14 ms 的巨大差距说明对单 token Decode把权重从 HBM 搬到计算单元比完成乘加更可能是主成本。真实 TPOT 还包括 KV-cache attention、norm、采样、kernel launch、同步和框架开销且权重访问不一定始终达到峰值带宽。因此这两个数只能看作理论下界不能直接作为服务性能指标它们的作用是避免一开始就把问题误判成“BF16 算力不够”。长上下文还会引入另一条 HBM 数据流KV-cache。权重读取量在每个 Decode step 中基本固定而 attention 读取的 K/V 长度随上下文增长。因此不能只把 Decode 简化成“读权重”而应分开观察单 token Decode ├── 线性层: 读取固定规模的模型权重常见为 skinny GEMV ├── Attention: 读取随上下文增长的 KV-cache └── 其他: norm、rope、采样、调度和 kernel launch这也解释了为什么 4K、16K、32K 上下文不应盲目使用同一个优化参数。5. DCU 上的一个反例双缓冲并不自动带来提速双缓冲、软件流水和更高 occupancy 常被当作优化访存 kernel 的通用答案但它们解决的是访存延迟没有被隐藏的问题而不是 HBM 总带宽已经饱和的问题。在 gfx936 上对 Qwen3.5 类 GEMV 形状的对照微测中无双缓冲版本已经达到 HBM 峰值的92%--101%加入双缓冲后多个形状的差异在±1.5%内。这个结果不是说双缓冲永远无用而是说明在该 kernel、该 shape 和该硬件状态下瓶颈已经是带宽管道本身。为什么 wave64 的 DCU 在这里能自然隐藏大部分 load latency原因在于资源和并发度gfx936 的 wavefront 为 64 线程该 GEMV 示例中VGPR 使用量约为 58、LDS 使用量约为 32资源占用不高一个34816输出通道的 gate/up 投影可以产生大量独立 wave。当一个 wave 等待 HBM 数据时硬件有足够多的其他常驻 wave 可以切换执行load latency 已被 wave 调度隐藏。此时手写双缓冲做的是同一件事的重复劳动它不能把 HBM 管道变宽。可以用下面的判据快速筛选方向观察到的现象更可能的瓶颈优先尝试MemUnitBusy高、带宽接近峰值、计算吞吐低带宽受限减少字节、减少额外读写、改善访问连续性带宽未满、memory stall 高、可驻留 wave 少延迟/occupancy 受限增加并行度、调整寄存器/LDS、预取或双缓冲计算吞吐高且接近 BF16 上限算力受限GEMM tile、Matrix Core、算法或融合在 ROCm/HIP 环境中可以用rocprof或omniperf抓取 kernel 指标。重点不是记住某一个百分比阈值而是用实测回答“HBM 是否已经被喂满”和“是否还有足够 wave 隐藏延迟”。6. 从真实 shape 到上下文长度Decode kernel 的并行参数如何选择前文说明了单 token Decode 同时存在权重流和 KV-cache 流。即使它们都偏访存受限具体优化参数也不能共用线性层主要面对固定权重矩阵attention 还要面对随上下文长度增长的 KV-cache。前者的可用并行划分由矩阵 shape 决定后者的分段收益则会随上下文长度变化。6.1 线性层shape 决定 skinny GEMV 的可用范围以线性层 Y XW^T 为例常用m、k、n描述计算X: [n, k] 输入 token 数 x 输入特征维度 W: [m, k] 输出特征维度 x 输入特征维度 Y: [n, m] 输出 token 数 x 输出特征维度因此Qwen3.5 gate/up 的 Decodeshape为m34816,k5120,n1一个长度为 5120 的输入向量得到 34816 个输出值。这里的 shape 指张量各维度的大小在 kernel dispatch 中BF16 dtype、是否有 bias、内存布局等也必须一并检查才能保证走到正确路径。这个 shape 在 Decode 中表现为 skinny GEMV。行块参数决定一个 block 同时处理多少个输出行从而影响 wave 的工作划分、权重读取和寄存器归约。Qwen3.5 的m34816,k5120,n1是一个真实例子。LLMM1 对rows_per_block的扫描结果如下rows_per_block耗时20.402 ms40.363 ms80.350 ms160.827 ms8 行最优16 行反而变慢。它说明 tile 不是越大越好必须对真实 shape 测量。实际服务只让这一精确 shape 走 8 行路径qkv、o 等其他 shape 回退原始F.linear。6.2 Attention上下文长度决定 softmax 分段的收益与静态权重不同KV-cache 是按请求逐 token 增长的运行时状态。对一个 Q head当前 query 是Q:[1,D]历史 key/value 是K:[L,D]与V:[L,D]其中L是上下文长度。vLLM 将 K/V 物理保存为分页的 KV-cache block但 attention 的主要工作仍沿L展开。16 段或 32 段指的是把这条 KV 序列按 token 位置切成 16 或 32 个连续区间16 段: KV token [0 ................................ L) |---- segment 0 ----| ... |---- segment 15 ----| 32 段: KV token [0 ................................ L) |- seg 0 -| ... |---------- seg 31 ----------|每一段都可以独立完成本段的Q K^T、局部最大值、局部 softmax 分母和局部prob V。但各段不能直接把输出相加softmax 的分母必须覆盖全部L个位置。因此 kernel 还需要根据各段的局部最大值和局部和做一次数值稳定的全局合并。这就是这里的softmax reduction。分段同样不改变总 FLOPQK 点积和加权 V 的运算量没有少。它的目的是在长序列中暴露更多独立的 KV 读取和局部 reduction 工作代价是需要更多中间缓冲和一次更大的最终合并。因此attention 不能只问“能不能分段”还要问“在当前L下更多分段带来的并行收益是否覆盖了合并成本”。6.3 实测分段数量必须随上下文长度切换分段太少时16K/32K 的单段工作过长并行度不足分段太多时局部统计量和最终合并的开销反而成为负担。下面的实测给出了该取舍在当前 DCU 环境中的结果。在 Qwen3.5 的真实 attention 头形状16 个 Q heads、4 个 KV heads、head dim 256上BF16、page size 784 的微测结果如下上下文长度16 段 parallel softmax32 段 parallel softmax结论8K0.397 ms0.404 ms序列较短32 段的合并成本超过并行收益16K0.480 ms0.432 ms32 段子核约快10%32K0.730 ms0.573 ms32 段子核约快21%因此合理实现不是“永远用 32 段”而是只在单 token Decode、上述 Qwen3.5 头形状、且最大序列长度不低于 12,288 时选择 32 段Prefill、短上下文与其他模型保留原有 16 段路径。与 16 段对照时32 段输出最大绝对误差不超过2.44e-4、平均绝对误差不超过1.58e-5均为有限值差异来自浮点归并顺序变化。这部分数据是 attention子核测量不能直接相加到端到端吞吐。真实服务还要经过数值回归、health check、短请求 smoke以及统一 benchmark 的 TPOT/TTFT 验证。7. 一套适合 DCU Decode 的最小诊断流程面对一个“生成慢”的服务可以按以下顺序排查避免一开始就大改框架1. 固定真实模型、上下文长度、batch 和输出长度抓取服务级 TPOT/TTFT。 2. 用 profiler 按线性层、attention、其他 kernel 聚合耗时。 3. 对热点 kernel 测字节量/耗时计算有效带宽同时查看 MemUnitBusy、计算吞吐、VGPR/LDS。 4. 若带宽接近当前测试环境的上限 - 不再优先投入双缓冲或单纯增大并行度 - 检查是否有多余中间张量读写、是否存在可验证的真实 shape 特化。 5. 若带宽未满且 wave 不足 - 再评估 tile、LDS、VGPR、预取或双缓冲。 6. 每个改动分别完成真实 shape 微测 - 数值对照 - 服务 smoke - 同口径端到端测试。这里最重要的是第 6 步。微基准帮助解释硬件机制但只有服务级验证才能确认改动没有被框架开销抵消也没有破坏请求完成率或时延尾部。8. 总结在单请求大模型 Decode 中DCU 的挑战往往不是“如何让更多计算单元同时做乘加”而是“如何让 HBM 中的权重流和 KV-cache 流少走冤枉路”。对 gfx936 而言wave64、VGPR/LDS、可达 HBM 带宽和真实模型 shape 共同决定一个 kernel 是否值得改。实测已经接近 HBM 上限时双缓冲和更高并行度未必有效针对 Qwen3.5 的精确 skinny GEMV shape 扫描行块或根据上下文长度调整 attention reduction才是更可证伪、也更稳健的优化路径。一句话概括本文的方法先用 DCU 指标确认自己撞的是哪堵墙再为真实 shape 写受保护的优化路径最后用数值与服务级测试决定是否保留。参考与说明vLLM本文服务框架及部分执行路径参考。ROCm DocumentationHIP、rocprof 与性能分析工具资料。本文实验使用前文列出的单张 gfx936 环境型号、驱动、DTK/ROCm 版本、时钟状态和测试 shape 均会影响结果请以自己的设备实测为准。