DeepSeek V4.1 Flash 工程实践(四):长 Prefill 与最后一公里优化
从官方跨层候选筛选、无损索引展开和动态 LDS,到 dense 分块、chunk 与 arena:DeepSeek V4.1 Flash 在八卡 ROCm 上的最终优化路径与性能数据
上一篇用中间张量对齐了图像预处理、视觉塔、aligner 和双偏置路由。至此,DeepSeek V4.1 Flash 的文本、Engram、八卡语言主干和多模态链路都已能够运行。
收官篇讨论最后一个问题:当输入从几千 token 增长到 261K、再到 1M 时,怎样让完整请求继续稳定前进,并把冷 prefill 的时间真正降下来?
这轮优化并不是简单换一个更大的矩阵 kernel。长上下文会同时放大稀疏索引、临时张量、显存碎片、跨 stage 交接和 chunk 调度的成本。某项微基准快了几倍,也可能在整模型里没有收益;某个配置刚好跑到 1600 token/s,也可能只剩几十 MiB 显存,无法成为生产配置。
因此本文只保留三类证据:算子与官方 reference 的对拍、固定输入的完整输出门禁,以及关闭 profiler 后的端到端计时。
先补回官方的跨层稀疏语义
长 prefill 的第一步不是提速,而是修正稀疏选择。
DeepSeek V4.1 Flash 的 DSA 不只是“每层在全部历史里取 Top-K”。第 20 层先按块聚合索引分数,从历史中选择候选块;后续第 24、28、32、36 层再在这批候选中使用各自的分数精选位置。它把稀疏性从单层的历史维度扩展到跨层复用:前一层发布候选范围,后面的消费者只扫描这段受限历史。
早期本地配置关闭了这条候选链,后续层直接在全历史上做 Top-K。短输入时,候选容量足以覆盖全部历史,两条路径看起来相同;超过约 16K 后,候选开始真正排除位置,差异才出现。
zLLM 补回了三部分状态:
- 源层生成的候选块需要跨 stage 保活并随 hidden 一起传递。
- 消费层只为候选槽计算自己的索引分数,再把局部结果映射回全局位置。
- 最新可见块、因果前缀和不足一块的尾部必须遵循官方边界。
真实维度测试覆盖到 1,048,576 个历史位置,候选块和两级 Top-512 集合与官方 PyTorch 结果一致。这个修复也改变了性能问题的形状:后四个消费者的评分范围被封顶为 16,384 个位置,但前面的源索引层仍要遍历随上下文增长的历史。
最新 prefill 路径:不重复解码同一份索引数据
修正候选语义后,profiler 指向前级 FP4 索引评分。朴素实现会在每个评分 tile 中反复解码相同的 query 和 key。上下文越长,这部分重复工作越明显。
最终路径分成四步。
1. 保留数值顺序的无损整数表示
官方 FP4 的尾数与 E8M0 scale 在满足可证明的指数范围时,可以先对齐到小整数,再使用 INT8 输入和 INT32 点积得到精确整数和。随后乘回二的幂,并继续按原来的 head 顺序执行 ReLU、权重与归约。
这不是近似量化。运行时先检查每个 tile 是否满足范围证明;任何条件不满足,就回退原来的 FP32 累加路径。测试覆盖不同 head 数、维度、尾块、因果前缀以及百万历史位置,GPU 评分和 Top-K 都保持逐位一致。
2. query 与 key 只展开一次
只把点积换成整数还不够。如果每个 tile 仍重复解析 FP4 code 和 scale,解码成本仍会随评分次数重复出现。新路径把本批 query 和历史 key 各展开一次,后续评分直接读取已准备的数据。
在独立同形状微测中,128 个 query × 131,072 个历史位置从约 47.6 ms 降到 6.55 ms;32 个 query × 524,288 个历史位置从约 47.6 ms 降到 6.62 ms。它们只是索引评分微测,不能直接当作整模型加速倍数,但明确说明了重复解码值得消除。
3. Dynamic LDS 按真实 head 数分配
早期 kernel 按最多 64 个 head 预留共享内存,而实际前级索引使用 32 个 head。Dynamic LDS 改为按当前 head 数计算共享内存,让一个 CU 可以同时保留更多工作块。
同一份 261,933-token 冷输入中,这一步把 TTFT 从 357.10 秒降到 306.66 秒,输入速度从 733.51 提升到 854.15 token/s。完整 1,024-token 输出与控制组一致。
4. 只重算真正不满足整数条件的 head
如果一个 query 只有一个 head 不满足指数条件,整块退回标量 FP32 会丢掉大部分收益。最终实现为异常 head 建立 warp mask,只对这些点积执行原始顺序重算;异常比例过高时,才整块回退。
经过一次展开、候选评分复用和局部回退后,同一 261K 请求在 chunk=256 时达到 1188.42 token/s,并保持完整输出哈希一致。
Dense 与 MoE:只保留整模型能兑现的改动
索引加速后,瓶颈转移到 routed MoE、attention projection 和 dense 临时张量。
Dense 路径按矩阵形状增加 16、64、128 行的直接 fragment load 分支,同时保持 K 方向每 16 个元素的原累加顺序。它把 261K 冷 prefill 从 1023.58 提升到 1075.85 token/s,约提高 5.1%。
MoE 上测试过扩大 K tile、输入预转换、合并 LDS、改变 route block 和提前读取权重。多数微基准没有在完整模型中兑现,或者只对很小的路由组有效。最终只保留不改变矩阵累加次序、能够通过 CPU/GPU oracle 的路径;没有把局部快 3%–5% 写成端到端收益。
这也是最后一轮优化的基本原则:热点排序会随着前一个瓶颈被消除而变化。 旧 profile 里的第一名,不一定还是新版本最值得优化的部分。
Chunk 不是越大越好,它决定权重复用和显存工作集
当算子与分配器逐步稳定后,我们重新测试 prefill chunk。相同 261,933-token 输入的结果如下:
| Chunk | TTFT | 冷 prefill 速度 | 最低采样显存余量 |
|---|---|---|---|
| 256 | 220.40 秒 | 1188.42 token/s | 约 5.05 GiB |
| 512 | 177.29 秒 | 1477.44 token/s | 约 5.00 GiB |
| 1024 | 173.45 秒 | 1510.12 token/s | 约 2.63 GiB |
| 2048 | 170.67 秒 | 1534.76 token/s | 约 0.26 GiB |
更大的 chunk 能让同一批权重服务更多 token,减少流水线填充和排空次数,但也会扩大 activation、路由结果、RoPE 与索引工作区。窗口从 32 缩到 16 只带来约 0.31% 的变化,也没有恢复显存,因此没有作为优化保留。
不同 chunk 会进入不同的 BF16 行数分派,长生成文本存在分叉;同一 chunk、相同运行件和固定输入的输出可以复现,算子 oracle 也保持一致,但我们没有把跨 batch shape 的全文差异说成已经解决。性能数据表达的是当前执行路径的工程结果,不替代完整质量评测。
Arena 的最后一公里:让归还的大块重新参与切分
长 prefill 中,临时张量的大小不断变化。旧的 L1 复用层可能拿一个很大的已完成 buffer 服务小请求,却把整个块借出去;剩余空间暂时无法回到 arena,于是后续大请求又落到 hipMalloc,还可能触发隐式设备同步。
修正后,如果已完成块来自 arena、容量至少为请求的两倍且余量足够,复用层会在同一把 arena 锁内归还整块,再按实际大小重新切出。余下区域立刻可以合并并服务后续请求。
在 3 GiB arena、chunk=2048 下,最终完整请求达到 1553.91 token/s,TTFT 为 168.56 秒。一次 5 GiB arena 实验达到 1600.04 token/s,但最紧显卡的采样余量只剩约 38 MiB,没有作为稳定方案保留。最终口径选择 3 GiB,而不是追逐刚好跨过 1600 的单次峰值。
最终性能数据
测试机器为双路 AMD EPYC 9334,共 64 个物理核,约 1 TiB 主机内存,8 张 Radeon Pro W7900D,每卡约 48 GiB。模型使用官方 safetensors checkpoint 和 ROCm 后端。以下均为完整 HTTP 请求;加载时间不计入。
| 场景 | 输入 / 输出 | 最终结果 | 说明 |
|---|---|---|---|
| 261K 冷 prefill,恢复基线 | 261,933 / 1,024 | 332.73 token/s,TTFT 787.22 秒 | 修复生产配置后、优化前的同输入基线 |
| 261K 冷 prefill,最终保留 | 261,933 / 1,024 | 1553.91 token/s,TTFT 168.56 秒 | 3 GiB arena,完整 SSE 正常结束 |
| 单路 1M 完整上下文 | 1,000,411 / 1,024 | 732.59 token/s,TTFT 1365.59 秒 | 完整生成结束,输出与前一次 1M 运行一致 |
| 单路 DSpark decode | 约 2K 输出长测 | 25.82 / 26.14 token/s | 两轮无 profiler 结果 |
| 八路 DSpark 聚合 decode | 八路长测 | 107.61 / 109.47 token/s | 当前可靠两轮基线 |
| 八路历史最好区间 | 八路长稳态 | 111.15–114.43 token/s | 展示上限,不作为默认结果 |
从恢复基线到最终 261K 结果,TTFT 缩短约 78.6%,冷输入速度提高到约 4.67 倍。这不是某一个 kernel 的功劳,而是官方稀疏语义、索引表示、共享内存、dense 分块、chunk 和内存生命周期共同作用的结果。
TTFT 从 HTTP 请求发出计到首个非空文本事件,冷 prefill 速度按输入 token 数除以 TTFT。它包含请求处理、八卡 prefill 和首个输出成本,并非纯 GPU kernel 吞吐。显存数据来自每 5 秒一次的整卡采样,可能漏掉瞬时峰值。Prefill 与 decode 来自不同请求,不能相加成同一次运行的总吞吐。
原始汇总数据随文章提供:最终性能 JSON。
这条优化路径最后留下了什么
四篇文章从 Engram 开始,经权重装配、八卡状态传递和视觉张量对拍,最后回到长上下文性能。它们共同说明一件事:模型能力不再只存在于 Transformer 的稠密计算中。
知识可以由 Engram 检索,注意力可以由跨层候选限制历史范围,视觉语义可以作为特殊行进入同一语言主干,推测解码可以减少完整模型的执行轮数。推理引擎需要管理的,已经是计算、检索、状态和资源的组合。
最终有效的优化也遵循同一顺序:先恢复模型真正的语义,再找到重复工作;先通过算子 oracle,再看完整输出;最后才用端到端 TTFT、吞吐和显存决定是否保留。
能够跑到一个峰值,只说明实验成功;能够在正确性、容量和稳定性边界内反复完成请求,才说明工程路径成立。
