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 后的端到端计时。

261K 冷 Prefill 从恢复基线到最终保留路径的性能演进

先补回官方的跨层稀疏语义

长 prefill 的第一步不是提速,而是修正稀疏选择。

DeepSeek V4.1 Flash 的 DSA 不只是“每层在全部历史里取 Top-K”。第 20 层先按块聚合索引分数,从历史中选择候选块;后续第 24、28、32、36 层再在这批候选中使用各自的分数精选位置。它把稀疏性从单层的历史维度扩展到跨层复用:前一层发布候选范围,后面的消费者只扫描这段受限历史。

早期本地配置关闭了这条候选链,后续层直接在全历史上做 Top-K。短输入时,候选容量足以覆盖全部历史,两条路径看起来相同;超过约 16K 后,候选开始真正排除位置,差异才出现。

zLLM 补回了三部分状态:

  1. 源层生成的候选块需要跨 stage 保活并随 hidden 一起传递。
  2. 消费层只为候选槽计算自己的索引分数,再把局部结果映射回全局位置。
  3. 最新可见块、因果前缀和不足一块的尾部必须遵循官方边界。

真实维度测试覆盖到 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 输入的结果如下:

ChunkTTFT冷 prefill 速度最低采样显存余量
256220.40 秒1188.42 token/s约 5.05 GiB
512177.29 秒1477.44 token/s约 5.00 GiB
1024173.45 秒1510.12 token/s约 2.63 GiB
2048170.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,024332.73 token/s,TTFT 787.22 秒修复生产配置后、优化前的同输入基线
261K 冷 prefill,最终保留261,933 / 1,0241553.91 token/s,TTFT 168.56 秒3 GiB arena,完整 SSE 正常结束
单路 1M 完整上下文1,000,411 / 1,024732.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、吞吐和显存决定是否保留。

能够跑到一个峰值,只说明实验成功;能够在正确性、容量和稳定性边界内反复完成请求,才说明工程路径成立。

← 回到所有文章