跳转到主要内容

Prefill Trace:Worker 供给、DSA/MLA 与 Chunked Prefill

· 约 8 分钟阅读

覆盖范围与核心结论

本文提供一组可复用的 prefill trace 解读口径:看到 MessageQueue.dequeue/acquire_read、模型 forward、MLA / sparse attention、KV gather / upconvert、copy 和 NCCL 同时出现时,应该怎样把它们拆成 worker 供给、模型计算、KV 数据路径与通信同步四层。

核心结论先写在前面:

  1. MessageQueue 侧的长等待,不等于模型正在计算;它更像 worker 等待 scheduler / engine 供给下一批可执行任务。
  2. DSA/MLA 在这个模型路径里不是 decode 专属概念,prefill 侧也会进入对应的数据路径。
  3. MLA 对 decode 的 KV-cache 带宽和容量收益通常最直观;prefill 侧也可能受益,但收益更依赖上下文长度、prefix hit、chunk shape 和内核实现。
  4. Queue Length ≈ 22.94 不是 batch size;它是一个窗口聚合出来的队列积压量,所以会是小数。
  5. prefix cache 和 chunked prefill 的关系可以按“先确定哪些 prefix 已经可复用,再对剩余未计算 token 切 chunk”来理解。

1. Prefill Trace 的一条执行链

一个 P 侧 profile 里常见的链路可以粗略分成四段:

请求进入 engine / scheduler
  -> worker 从 MessageQueue 取本轮工作
  -> GPUModelRunner.execute_model
  -> model.forward / attention / MoE / KV / 通信 / 同步

所以,当 trace 里看到:

  • MessageQueue.dequeueacquire_read 很长;
  • GPUModelRunner.execute_model 也有明显耗时;
  • model forward 下面出现 DSA/MLA、KV gather、copy、NCCL、stream sync;

不能把这些都混成“模型计算慢”。它们对应的是不同层的问题:

trace 信号更接近的含义
MessageQueue.dequeue/acquire_readworker 等任务、等调度结果、等队列供给
GPUModelRunner.execute_model本轮真正进入模型执行的总包络
GlmMoeDsaForCausalLM.forwardGLM-5.2 模型 forward 本体
MLA / sparse attentionattention/KV 数据路径
KV gather / upconvert / copyKV cache 布局、压缩表示、搬运和重组成本
NCCL / stream sync多卡通信、同步和依赖等待

这也是为什么一句“P trace 的主要信号集中在 Python 侧 worker 供给等待和 GPU 侧 DSA/MLA 数据路径”要拆开看:前半句说的是 worker 有没有持续拿到活干,后半句说的是拿到活之后,GPU 上的模型路径把时间花在哪里。

2. DSA/MLA 在 Prefill 和 Decode 里都有吗?

在这个 GLM-5.2 trace 对应的模型路径里,可以按“都有,但作用和收益侧重点不同”理解。

MLA 不是一种新的 decode-only 算法。它更像 attention 下方的 KV 表示和访问方式变化:attention 语义仍然是“当前 query 看历史 key/value”,但 KV 不再按 full attention 的完整 K/V 形式直接存放和读取,而是使用压缩或 latent 表示,再配合专门的 kernel 和 cache layout。

因此:

阶段MLA 关注点
Prefill一段 prompt/chunk 的 token 需要建立 KV、访问已有 prefix/context,并完成 attention/FFN/MoE
Decode每个 step 通常只来少量新 token,但要反复读很长的历史 KV

decode 阶段最容易看出 MLA 的收益,因为 decode 经常被 KV cache 读取带宽、cache 容量和长上下文访问拖住。KV 表示变小之后,每个 step 要搬、要读、要维护的 cache 压力更低。

prefill 阶段也可能有收益,尤其在长上下文、prefix cache hit、chunked prefill、上下文很长但本轮 Q_len 较短时。此时虽然本轮只计算一段 chunk,但 attention 仍可能要面对很长的 K_len。MLA 改变 KV 表示后,缓存 payload、访问路径和某些 attention kernel 的代价都会变化。

但 prefill 的收益不能简单等同于 decode。prefill 里还会有大矩阵计算、MoE dispatch/grouped GEMM、activation、KV 写入、gather/upconvert/copy 等成本。MLA/DSA 本身也可能带来额外的数据重组和 kernel 调度开销。所以更准确的说法是:

MLA 在 prefill 侧有潜在优势,但不是所有 prefill shape 都一定更快;decode 侧的 KV-cache 优势通常更稳定、更直接。

DSA 可以理解为另一层稀疏化或选择机制:它决定哪些历史 token / block 更值得参与当前计算。它可以和 MLA 同时存在:MLA 改 KV 表示,DSA 改访问范围或访问路径。prefill 侧如果上下文很长,DSA 有机会减少 dense full attention 式的全量历史扫描;decode 侧则有机会降低每步读取和计算的历史范围。

3. 它们相比 Full Attention / Causal Attention 的优势在哪里?

先区分两个层次:

  1. causal attention 是语义约束:当前位置只能看自己和之前的 token,不能看未来 token。
  2. full attention 通常指实现上对历史 KV 做完整 softmax attention,不做 MLA 压缩或 DSA 稀疏选择。

MLA 不一定改变 causal 语义,它主要改变 KV cache 的表示和读取方式。DSA 也不一定改变“不能看未来”的约束,它主要改变“历史里具体看哪些、怎么高效看”。

所以在 prefill 侧,它们相比普通 full causal attention 的优势主要来自:

优势来源对 prefill 的意义
KV 表示更小长上下文或 prefix cache 下,历史 KV 的读写和搬运压力下降
专门 attention kernel适配 latent KV、chunked context 或 sparse path,减少无效工作
稀疏选择不必每次都按 dense full attention 路径扫描全部历史
cache layout 优化prefix reuse、paged/block cache、跨卡 KV 传输更可控

但这不是免费午餐。trace 里如果看到大量 gatherupconvertcopy、metadata 构造、NCCL 或同步,说明模型为了获得 KV 压缩和稀疏访问收益,也付出了数据路径复杂化的成本。

4. Queue Length ≈ 22.94 是 Batch 数量吗?

不是。

Queue Length ≈ 22.94 更应该理解为监控窗口里的队列积压量,通常是请求或序列等待量的时间平均值。它是窗口聚合指标,所以可以是小数。

batch size 是另一件事:它描述某一轮模型执行里真正被 scheduler 放进 batch 的请求数、序列数或 token 数。不同系统里会分别展示:

指标含义
Queue Length队列中等待调度或等待执行的积压量,常是窗口平均
Running Requests / Active Sequences当前正在服务中的请求或序列
Batch Size某轮执行合批后的请求/序列规模
Batched Tokens某轮执行实际 scheduled token 数
Prefill Batch Sizeprefill 类型任务的合批规模

因此,Queue Length ≈ 22.94 不能读成“batch 里有 22.94 个请求”。它更像“这段时间队列里平均压着约 23 个待处理单位”。如果同时看到 batch size 很小而 queue length 很大,问题通常不是“模型一次吃了太大 batch”,而是 scheduler/worker/资源配额没有把积压及时消化掉。

5. Chunked Prefill 是先切块还是先看 Prefix?

更稳妥的理解是:

原始 prompt token
  -> prefix cache / computed blocks 命中判断
  -> 得到本次还需要计算的 effective prefill tokens
  -> scheduler 按 token budget 切 chunk
  -> chunk 进入 prefill forward

也就是说,概念上是先看 prefix,再对未命中的部分切 chunk。

原因很简单:如果前 8K token 已经命中 prefix cache,就不应该再把这 8K token 切成 chunk 重新跑一遍 prefill。真正需要 chunked prefill 处理的是“还没有算过、需要补进 KV cache 的 token”。

不过有一个容易误解的点:prefix cache 命中后,命中的 prefix 不需要重新计算 hidden states / FFN / attention 输出,但它仍然是后续 token 的上下文。对于 suffix chunk 来说:

  • 本轮 Q_len 是当前要计算的 chunk token 数;
  • K_len 可能包含已经命中的 prefix KV,加上已经计算过的前序 chunk KV,再加上当前 chunk 内的 KV;
  • 所以 prefix hit 会减少 prefill 计算 token 数,但不代表 attention 上下文长度变成零。

工程实现里,prefix cache 检查、block bookkeeping、调度切分可能交织在一起;但调参和看 trace 时,用“先确定可复用 prefix,再切剩余 token”这个模型最不容易误判。

6. 回到 GLM-5.2 P Trace 怎么读

如果某个 P trace 同时有这些信号:

  • Python 侧 MessageQueue.dequeue/acquire_read 出现秒级等待;
  • GPU 侧 GPUModelRunner.execute_modelGlmMoeDsaForCausalLM.forward 出现数百毫秒级包络;
  • DSA/MLA、sparse attention、KV gather/upconvert/copy、NCCL、stream sync 都有明显占比;
  • input_ids 分布里既有 256 token 左右的小 tail block,也有几千到 16K 的大块;

那么比较合理的拆法是:

  1. 先看 worker 供给:是不是 queue/backlog 很大,但 worker 拿到任务不连续?
  2. 再看 effective prefill tokens:原始 prompt 多长不够,prefix hit 后剩下多少 token 更关键。
  3. 再看 chunk shape:小 tail chunk 会让 kernel launch、copy、metadata、MoE expert token group 更碎。
  4. 再看 MLA/DSA 数据路径:压缩 KV 和稀疏访问是否被 gather/upconvert/copy/NCCL 抵消了一部分。
  5. 最后看同步:stream sync 或跨卡通信是否把本来可并行的工作串住。

这比单独盯 input_ids 更可靠。input_ids 告诉我们本轮喂给模型的 token 规模,但它不是唯一解释变量。对 prefill 来说,真正决定耗时的是:

effective uncached tokens
+ attention 的 Q_len / K_len
+ MoE 每个 expert 的 token 分布
+ KV cache layout 和 prefix hit
+ 跨卡通信 / KV 传输
+ kernel shape 和同步等待

7. 证据边界与后续验证

本文给出的是跨 trace 可复用的分析框架。对某一次具体 GLM-5.2 profile 做定量归因时,还需要补三类逐 step 证据:

  1. 从 profile 里导出每个 prefill forward 的 input_ids、duration、Q_len/K_len 或等价 shape。
  2. 把 prefix hit 后的 effective prefill tokens 和原始 prompt length 分开统计。
  3. 分别画 scheduled tokens -> forward durationK_len -> attention durationexpert token count -> MoE duration,避免把所有耗时都归因到 input_ids

在这些字段缺失时,可以判断“等待、计算、搬运、通信分别出现在哪里”,但不应给出某个机制贡献了多少百分比的精确结论。

相关页面

修改历史2 次提交