64 张 V100 上跑 DeepSeek V4.1 Flash:FP8 激活、DSpark 和长上下文缓存

保留 FP8 激活与 FP4/FP8 权重,在 8 台 V100 节点上实现文本推理服务。记录 SM70 兼容计算、推测解码、CUDA Graph、前缀缓存,以及单请求约 18–24 tok/s 的实测结果。

现状

这次的目标是在现有 V100 集群上运行 DeepSeek V4.1 Flash,保留 FP8 activation,接入已有的 Harness,支持思考输出、tool calling 和较长的对话上下文。先把服务维持在现在的可用版本,下面整理这几天做过的改动。

项目当前配置
推理节点8 台,每台 8 张 V100-SXM2 16 GB,共 64 张
文本模型40 层 backbone,3 层 DSpark,draft block size 为 5
激活与权重FP8 E4M3 激活;保留已有 FP4/FP8 权重格式
并行配置attention 使用节点内 TP8;部署按 TP8 × EP8 组织,routed experts 分片到 64 个 rank
执行后端自维护的 SM70 兼容运行时,PyTorch/CUDA/NCCL
客户端接口OpenAI-compatible Chat Completions,SSE、reasoning、tools
空闲时串行单请求速度英文约 18、中文约 19、Python 约 24 tok/s
配置上限总上下文 262144 tokens;最大输出 32768,默认输出 4096
DSpark 快速路径满足条件的单请求,prompt 最长 32768 tokens

表中的上下文和输出数字是配置上限。已经单独做过 20K、32K 输入及缓存验证;完整 262K 输入和连续 32768-token 输出的压力测试还没有完成。

FP8 在 V100 上怎么计算

V100 的 Tensor Core 使用 FP16 矩阵输入,支持 FP32 accumulation;这决定了兼容层的实现方式。FP8 activation 和 packed FP4 weight 保留压缩格式,kernel 读取后把当前计算块展开为 FP16,执行 WMMA,再做 FP32 scaling 和 accumulation。硬件接口见 Volta Tuning Guide。

FP8 activation + packed FP4 / FP8 weight
                 ↓ 按块解码
             FP16 WMMA
                 ↓
         FP32 scaling / accumulation
                 ↓
       原有 BF16 边界或 FP32 collective

权重没有整份转换成常驻 FP16,activation quantization 也继续执行。V100 上付出的额外成本包括解码、scale 处理,以及较碎的 kernel dispatch。

这一轮优化以已验证的兼容实现为数值基线,保留它的 FP32 reduction tree 和 BF16 边界。与原生 FP8/FP4 硬件实现的逐位一致性,没有从这些测试中得到证明。

vLLM、SGLang 仍然是接口和 serving 行为的参照。这次运行的是自维护后端,本文没有测到同配置的 vLLM/SGLang 原生后端结果,因此也没有做框架速度排名。迁移执行后端还要核验这组模型算子、SM70、quantization、KV 状态和 DSpark 的组合;只把 HTTP 层换成另一个 server,底层计算成本会继续存在。

多节点计算与通信

attention 在每台机器的 8 张卡之间做 TP。routed expert 参数按 rank 分片,FFN 输出先在节点内归约,再由节点 leader 做跨节点归约,最后广播回节点内。跨节点路径使用 NCCL;原有 FP32 reduction 边界保留。

shared expert 的计算放到独立 CUDA stream,与有序的 expert collective 重叠。两条分支在 postcompute 前汇合,后续 GEMM 才能复用工作区。运行时为 main stream 和 auxiliary stream 分配独立 graph pool,避免一条 stream 还在读另一条已经复写的 buffer。

测过将两次 TP reduction 打包成一次,也测过调整 shared TP 与 leader EP 的提交顺序。在 16-rank、实际 FFN 权重的实验中,两种方案的整体时间比都低于 1,因此保留原来的通信顺序。减少一次 collective 会同时改变 payload、等待点和 overlap,收益需要放到完整 FFN 中测量。

当时的 40 层 profile 中,target decode 的 attention 约占 43%–48%,FFN 约占 38%–44%。这些 CUDA event 区间还包含 peer readiness 和 host enqueue gap。仅凭 collective 区间较长,还不足以确定 NIC 是主要限制;本轮也没有据此修改 MTU 或追加节点。

DSpark 与缓存事务

逐 token 的 target-only 解码,每生成一个 token 都要经过完整 backbone。DSpark 用 3 个 draft stage 一次提出最多 5 个候选,再由 target 一起验证。当前配置使用 0.2 的 confidence threshold,低置信候选后面的部分不送入本轮验证。

验证会暂时写入超过最终接受位置的 cache。接受了前 k 个候选后,后面的写入必须撤回,同时保留已接受前缀。这里除了普通 KV,还有压缩 KV、index cache、共享 candidate 状态和 draft 的 window ring。只裁剪最终输出 token 列表,会把错误状态留给下一轮。

当前实现先保证 cache capacity,再保存本轮可能改写的区域,验证结束后提交接受的前缀。全局 rank 0 统一 token 和 proposal 决策,其他 rank 接收同一个结果,避免不同 EP replica 的数值差异把后续 collective 带到不同的 shape。

这条路径曾暴露 ring overwrite race 和不同验证宽度的数值敏感性。修复时分别核对 cache transaction、输出 token hash 和 64-rank 一致性。已经观察到 target-only 与 DSpark 的自由生成文本不同,因此这里不把两条执行路径描述成逐位等价。冷缓存和热缓存之间、同一个 kernel 改动前后,则分别用固定的同路径对照验证。

FFN kernel 和 CUDA Graph

routed FFN 的 FP4 kernel 做了两层修改。

第一层是成对读取 FP8/FP4 数据,使用整数操作把两个相邻值解码为 half bits,减少分散的读写。第二层是把原有 reduction 拆分组织成 16 组 FP32 partial,保留每个 32-wide WMMA partial、两个 scaling multiply 和后续加法顺序。

64-register 编译版本在局部实验中表现较好。接到完整服务,再加上 shared expert overlap 和私有计算图后,五轮交替测试得到下面的结果:

用例原 FFN,tok/scombined FFN,tok/s
英文,256 输出 token13.17813.799
中文,128 输出 token14.21314.822
Python,128 输出 token17.79718.821

这次完整 HTTP 请求的几何平均收益为 4.91%。同一阶段局部 routed kernel 的收益接近 2 倍,完整模型还包含 attention、shared expert、通信、draft 和 prefill。

后面又给 backbone attention 增加了三段私有计算图:

  1. HC preparation 与 Q/QR/KV projection;
  2. output projection;
  3. HC finish。

window、compressed KV、index 的状态更新与 FP32 TP collective 留在原有 eager 顺序中。每个 block/shape 拥有稳定 buffer;整次事务结束时,返回给调用者的结果单独复制。启动阶段预构建常用 shape,运行时遇到未覆盖的 layout 或调试模式就使用原路径。

这一轮 38 个完整模型请求通过后,attention 改动的整体几何平均收益为 5.80%。局部 attention sequence 的时间比是 1.261;文章中的模型收益采用前一个数字。

CUDA Graph 的 capture、replay 与跨 rank 协调有额外约束。NCCL 的图执行文档说明了 collective capture/replay 的一致性要求。当前选定的私有图仅包含计算,collective 继续有序提交。

短上下文的 index shortcut

某些 indexer 会对 N 个已有位置做 top-k,再按位置排序。当 k = N 时,最后的排序结果覆盖完整位置轴。对于符合条件的非 candidate-source indexer,可以直接生成完整 indices,省去 query projection、打分、score reduction 和 top-k。

key-owner 的 projection、normalization、RoPE、量化以及 cache publication 仍然执行,它们会影响后续较长上下文。发布共享 candidate mask 的特殊层继续走原路径。超过 index_topk=512 或形状不满足条件时,也使用原逻辑。

验证覆盖了 512→513 的边界、压缩 ratio、source/borrower cache、batch、prefill 和 speculative rollback。这个分支在它自己的完整模型 A/B 中带来约 5.28% 的几何平均收益。各阶段使用的 baseline 不同,文中的百分比没有直接相加。

长上下文与 prefix cache

早期「已经有 warmup,为何还要等一分钟」的请求,Harness 实际发来了接近 10K tokens。启动 warmup 可以预先建立 graph、通信和编译缓存,每条新 prompt 的 prefill 仍需执行。

长 prompt 按 chunk 处理,每个 chunk 同时推进 target cache 和 3 个 draft stage 的 prefix。draft 使用有界的 128-position ring;chunk 的 main hidden state 完成播种后释放。

prefix cache 保存精确 token 前缀对应的模型状态,key 同时约束 operator identity。每个 rank 最多保留 4 个 entry、128 MiB,所有 rank 一起决定是否接受 snapshot。缓存保存已计算的前缀状态;请求尾部变化时,新尾部和输出继续计算。

这一阶段的 20K 输入测试输出了实际 256 个 token:

指标原 target-only 路径DSpark 冷缓存DSpark 热缓存
完整请求,秒148.52133.0621.81
第一个输出,秒111.99113.793.05
输出速度,tok/s1.721.9211.74

热请求复用了 19968 个输入 token。32K 输入的另一组测试,首输出从 184.43 秒降至 1.20 秒,完整请求从 205.07 秒降至 20.50 秒。后者最后重算 64-token chunk,20K 用例最后重算 512 tokens,因此两组热请求的首输出时间有明显差异。

随后在实际安装的 Harness adapter 上,用多主题 20K fixture 再测了一次:首输出由 114.47 秒降至 1.42 秒,thinking 和最终文本一致。这里的改善主要影响复用长前缀的交互;全新长文档的冷 prefill 延迟仍然较高。

配置中还有两个独立限制:总 context window 为 262144,DSpark prompt admission 为 32768。超过 DSpark admission 的请求使用原有 target-only 路径。把总窗口填大,不会自动扩大快速路径或降低 prefill 成本。

API、思考输出与 tool calling

接口保留 Chat Completions 的 message、SSE 和 tool-call 结构。适配过程中处理了 developer role、tool result 的文本 content block,以及流式 tool_calls 的 index、id、arguments 和 finish reason。验收同时覆盖服务端请求和实际安装的 Harness adapter。

当前这条链路把思考增量放在 delta.reasoning_content,正文放在 delta.content。这个拆分与 SGLang 的 reasoning parser 文档相符。接其他客户端时要检查字段版本:当前 vLLM 文档已把原来的 reasoning_content 政名为 reasoning,旧客户端如果只读取前者,可能显示为空。

一次短算术请求的最新测试收到 94 个非空 reasoning frame:第一个在 0.97 秒,最后一个在 14.55 秒,正文在 14.74 秒开始。独立的 Harness adapter 请求收到 125 个 thinking delta,也在正文之前持续到达。

浏览器显示还有 disclosure 状态:原生 thinking row 默认折叠,展开后才显示完整正文。此前实际浏览器测试记录过生成过程中展开内容持续增长;最新一次网页复测遇到诊断会话的 HTTP 401,因此本轮新证据限于 HTTP SSE 和 adapter,网页认证需要单独处理。

当前速度与测试口径

最后选定 attention 改动后,9 个公开接口 SSE 请求的中位数如下。每个用例测 3 次,固定 prompt 和 temperature,使用实际生成数量除以完整 HTTP 时间,cached input 为零。

用例实际输出 tokenstok/s
英文解释潮汐25618.133
中文解释缓存局部性12819.025
Python merge sort12823.962

这些提示词长度较短,测量包含各自的 prefill。上面的长上下文热缓存结果单列,输入缓存命中数不计入输出吞吐。英文和中文仍未普遍达到 30 tok/s;512 输出预算下出现过约 30 tok/s 的 Python 用例,目前保留为单独的测量。

交付前又在实际服务上做了三个相同请求。当时还有长上下文请求运行,英文请求耗时 71.08 秒,中文耗时 59.19 秒;中文的服务端排队时间为 52.48 秒,模型 batch 执行约 6.69 秒。没有明显排队的 Python 请求耗时 5.33 秒,实际输出 128 tokens,约 24.00 tok/s。三次输出均与固定基准一致。当前 DSpark 单请求路径按队列串行执行,交互延迟还会受到前面请求长度的影响。

没有选用的改动

尝试结果与处理
GPU-local CPU affinity三轮交替测试,中文略有改善,英文和 Python 没有一致收益;恢复原 mask
TP collective 打包与 shared TP overlap实际分布式 FFN 测试整体变慢;保留原顺序
FP32 sparse-attention 新 kernel局部速度改善,但部分输出发生小数值变化;完整模型整体约 1.92% 收益,没有选用
内部 activation buffer 借用完整模型约 1.05% 收益;没有部署
扩大 sparse-attention graph 覆盖2278 个额外 graph entry,52 个完整模型请求输出一致;整体只提高 0.82%,没有部署

最后一项在新 shape 的 microbenchmark 上接近 3.90 倍,普通请求的大部分时间却没有经过这些新 shape。线上继续使用此前选定的版本。

还没做的

  • 完整 262K 输入及连续 32768-token 输出压力测试。
  • 更广的多并发、排队、公平性和长期稳定性测试。
  • 与同一数值基线匹配的通用 serving 后端迁移验收。
  • 统一 target-only/DSpark 不同验证宽度的数值审计。

参考文档

硬件与执行

接口