Performance report性能报告

Performance Report A: Sparse Attention on Tensor Cores性能分析报告 A:稀疏注意力改用 tensor core

Real V4.1 4-GPU prefill goes from 1,085 to 2,572 tok/s/GPU (2.37×) after the sparse-attention kernel moves from CUDA cores to tensor cores.稀疏注意力 kernel 从 CUDA core 改用 tensor core 后,真实 V4.1 四卡 prefill 吞吐从 1,085 提高到 2,572 tok/s/GPU(2.37 倍)。

2026-10-22 · PR #38 · code 804f4b0 (before the change: eac0347)

Conclusion

Real V4.1 4-GPU prefill throughput rises from 1,085 to 2,572 tok/s/GPU (2.37×), and the step time falls from 3,776 ms to 1,593 ms. The gap to the official engine shrinks from 33.3× to 14.1×; mini reaches 7.1% of the official engine. The sparse-attention kernel drops from 2,255 ms/step to 98.8 ms/step (22.8×) and is no longer the bottleneck. The two largest items now are MoE (726 ms) and the indexer (461 ms).

Setup

ItemConfiguration
ModelReal DeepSeek-V4.1-Flash, revision dba1be0
Hardware / topology1 node, 4 × B200, EP4 / DP4 / CP1
Workloadworkload.json.gz (SHA-256 adf09ec9…), 131,072 context, 126,976 cached, 4,096 new tokens per step per rank
Timing10 complete steady-state steps on rank 0; plus a 2-step torch profile
JobsBefore the change and official: 9297; after the change: 9329

End-to-end results

MetricBeforeAfterOfficialChange
Input throughput (tok/s/GPU)1,084.82,571.736,170.9×2.37
Mean step (ms)3,775.91,592.7113.2−57.8%
Step variation (ms)3,774.9–3,777.01,591.5–1,596.8111.5–116.3Stable on both sides
Share of official3.0%7.1%100%
Gap to official33.3×14.1×—
Peak memory (GiB)147.6147.6—Unchanged (scratch is 42 MB per query block)

Per-stage time (profile, ms/step)

StageBeforeAfterOfficialCurrent gap
Sparse attention core2,254.998.814.96.6×
Indexer460.7460.87.958×
Other attention (projections, compression, gather, fake quant)253254~1913×
MoE (gate + experts + communication)74672652.814×
HC, normalization and other123122~1012×
Total (step under profile)3,8381,662110.615×

Only sparse attention changed; every other stage is within measurement error, which shows the change has no side effects. The step under the profiler is about 4% longer than the timing window; that is the profiler’s own overhead.

Kernel analysis

Shape (T, H, K, D)Old (ms)New (ms)SpeedupNew TFLOP/s
256, 64, 640, 512 (real compressed layer)3.6720.16622.1×129
256, 64, 128, 512 (real uncompressed layer)0.6860.0699.9×62
256, 4, 640, 512 (tiny model)0.2840.1052.7×13
  • Why it is faster: the 64 heads of one query share the same 640 keys. The old kernel ran one program per (query, head) and summed with tl.sum on CUDA cores, reading the same key set 64 × 2 times. The new kernel stacks 32 heads into the M dimension of tl.dot, runs on tensor cores, and reads each key set once for QK and once for PV per head block.
  • How far from peak: each step’s attention is about 13.2 TFLOP, so 98.8 ms is 134 TFLOP/s, roughly 6% of B200’s dense bf16 peak. The official 14.9 ms is about 0.89 PFLOP/s.
  • Why a gap remains:
    1. M is only 32, so the tensor-core tile is small. With HEADS=64 the fp32 accumulator [64, 512] does not fit in registers and spills; the parameter sweep confirms it.
    2. The two-pass algorithm writes and reads the scores scratch once more.
    3. About 180 ms of gather/copy outside the kernel assembles the keys into [T, 640, 512].
  • The tiny model gains little: it has only 4 heads, which cannot fill an M=16 tile, so sharing keys helps less.

Accuracy

  • The maximum error against the fp32 CPU reference is the same order as the old kernel (≤ 4.9e-4). Tensor-core accumulation order differs, so results are not bit-identical to the old kernel.
  • The bf16 probability-rounding regression fixture still passes; it fails if the bf16 rounding is removed or the sink is folded into the max.
  • All end-to-end reference tests pass (golden tokens, per-position logits along the reference trajectory, chunked prefill, preemption). Full suite: 163 passed.

Tiny-model goodput (1 GPU, bench.py defaults, median of 3)

Throughput (tok/s)TTFT p50 (ms)TPOT p50/p99 (ms)goodput (req/s)
Before6,7912944.8 / 16.149.36
After7,202 (+6%)2744.6 / 15.052.36 (+6%)

Next

Ordered by remaining time: MoE 726 ms, indexer 461 ms, other attention 254 ms.

  1. B1: fuse indexer scoring. The micro-benchmark already drops from 61.8 ms to 4.9 ms per layer (904 TFLOP/s); this item should go from about 461 to 90 ms, for about 3,200 tok/s/GPU.
  2. B2: score only the candidate blocks in layers 24–36.
  3. C: run MoE grouped GEMM directly on FP4 weights.

Raw data: profile-2026-10-22/stage-a/; before the change: profile-2026-10-22/.

2026-10-22 · PR #38 · 代码 804f4b0(改动前 eac0347)

结论

真实 V4.1 四卡 prefill 吞吐从 1,085 提高到 2,572 tok/s/GPU(2.37 倍),每步从 3,776 ms 降到 1,593 ms。 到官方的差距从 33.3 倍缩小到 14.1 倍,mini 达到官方的 7.1%。稀疏注意力 kernel 从 2,255 ms/步降到 98.8 ms/步(22.8 倍),不再是瓶颈。现在最大的两项是 MoE(726 ms)和 indexer(461 ms)。

实验设置

项配置
模型真实 DeepSeek-V4.1-Flash,revision dba1be0
硬件 / 拓扑单机 4 × B200,EP4 / DP4 / CP1
负载workload.json.gz(SHA-256 adf09ec9…),131,072 上下文,126,976 已缓存,每步每 rank 4,096 新 token
计时10 个完整稳态步,rank 0;另抓 2 步 torch profile
作业改动前与官方:9297;改动后:9329

端到端结果

指标改动前改动后官方变化
输入吞吐 (tok/s/GPU)1,084.82,571.736,170.9×2.37
平均每步 (ms)3,775.91,592.7113.2−57.8%
每步波动 (ms)3,774.9–3,777.01,591.5–1,596.8111.5–116.3两边都稳定
占官方3.0%7.1%100%
与官方差距33.3×14.1×—
峰值显存 (GiB)147.6147.6—不变(scratch 为每块 query 42 MB)

分环节耗时(profile,ms/步)

环节改动前改动后官方当前差距
稀疏注意力核心2,254.998.814.96.6×
Indexer460.7460.87.958×
注意力其余(投影、压缩、gather、伪量化)253254约 1913×
MoE(gate + 专家 + 通信)74672652.814×
HC、归一化及其他123122约 1012×
合计(profile 下的步长)3,8381,662110.615×

只有稀疏注意力一项发生了变化,其他环节都在测量误差以内,说明这次改动没有副作用。profile 下的步长比计时窗口长约 4%,这是 profiler 自身的开销。

Kernel 分析

形状 (T, H, K, D)旧 (ms)新 (ms)加速新 TFLOP/s
256, 64, 640, 512(真实压缩层)3.6720.16622.1×129
256, 64, 128, 512(真实无压缩层)0.6860.0699.9×62
256, 4, 640, 512(tiny 模型)0.2840.1052.7×13
  • 为什么快: 一个 query 的 64 个 head 共享同一组 640 个 key。旧 kernel 每个 (query, head) 是一个 program,用 tl.sum 在 CUDA core 上算,同一组 key 被读了 64 × 2 次。新 kernel 把 32 个 head 拼成 tl.dot 的 M 维,走 tensor core,每组 key 每个 head 块只读一遍 QK,再读一遍 PV。
  • 离峰值还有多远: 每步注意力约 13.2 TFLOP,98.8 ms 相当于 134 TFLOP/s,约为 B200 稠密 bf16 峰值的 6%。官方 14.9 ms 约 0.89 PFLOP/s。
  • 剩余差距的原因:
    1. M 只有 32,tensor core tile 偏小。HEADS=64 时 fp32 累加器 [64, 512] 放不进寄存器,会溢出,扫参也证实了这点。
    2. 两遍算法要多写读一次 scores scratch。
    3. kernel 外面还有约 180 ms 的 gather/拷贝,用来把 key 拼成 [T, 640, 512]。
  • tiny 模型收益小: 它只有 4 个 head,凑不满 M=16 的 tile,共享 key 带来的收益有限。

精度

  • 与 fp32 CPU 参考的最大误差和旧 kernel 同一量级(≤ 4.9e-4)。tensor core 的累加顺序不同,所以和旧 kernel 不是 逐位相等。
  • bf16 概率舍入回归 fixture 仍然通过:去掉 bf16 舍入,或把 sink 算进最大值,这个 fixture 都会失败。
  • 端到端参考测试全部通过(golden token、参考轨迹逐位置 logits、chunked prefill、抢占)。全量 163 passed。

tiny 模型 goodput(单卡,bench.py 默认参数,3 次取中位数)

吞吐 (tok/s)TTFT p50 (ms)TPOT p50/p99 (ms)goodput (req/s)
改动前6,7912944.8 / 16.149.36
改动后7,202(+6%)2744.6 / 15.052.36(+6%)

下一步

按剩余耗时排序:MoE 726 ms,indexer 461 ms,注意力其余 254 ms。

  1. B1: 融合 indexer 打分。微基准已经从 61.8 ms 降到 4.9 ms/层(904 TFLOP/s),预计这一项约 461 → 90 ms, 吞吐约 3,200 tok/s/GPU。
  2. B2: 第 24–36 层只对候选块打分。
  3. C: MoE 直接用 FP4 权重做 grouped GEMM。

原始数据:profile-2026-10-22/stage-a/,改动前: profile-2026-10-22/。

mini-DeepInfer · Next in this project接着读Performance Report C1: MoE Combine Over Local Rows, In-Place Dequantization性能分析报告 C1:MoE 只合并本地行,原地反量化 →