INFERENCE WALKTHROUGH · 2026-10-20

DeepSeek-V4-Pro 推理:
一枚 token,8 张卡

权重留在显存,计算把它读进芯片;上下文记忆逐步增长,token 的表示在卡间流动。

8教学划分的 GPU 数
61每步经过的主干层
4 × 7168mHC 残差形状

采用 TP=8 的分片拓扑教学示意:按头、按专家分权重。8×80 GiB 放不下 Pro;后面的容量演算会推到合法的16路划分。

01 / 先摆好模型

每张卡都有自己的权重和上下文记忆

GPU 0 16 个 query 头 48 个本地路由专家 W 分片 + 本地 KV 副本 GPU 1 16 个 query 头 48 个本地路由专家 W 分片 + 本地 KV 副本 GPU 2 16 个 query 头 48 个本地路由专家 W 分片 + 本地 KV 副本 GPU 3 16 个 query 头 48 个本地路由专家 W 分片 + 本地 KV 副本 GPU 4 16 个 query 头 48 个本地路由专家 W 分片 + 本地 KV 副本 GPU 5 16 个 query 头 48 个本地路由专家 W 分片 + 本地 KV 副本 GPU 6 16 个 query 头 48 个本地路由专家 W 分片 + 本地 KV 副本 GPU 7 16 个 query 头 48 个本地路由专家 W 分片 + 本地 KV 副本

每层 384 个路由专家均分;Q / O 按头分片。这组划分中,shared KV、mHC 与共享专家在各卡保留副本。4 路残差是模型维度,与 GPU 数量无关。

工程细节

一张卡负责哪些维度,复制哪些状态

项目Pro 全局TP=8 每张卡
Query 头12816
输出投影组162
Indexer 头648
每层路由专家38448
shared KV / 残差[B,N,512] / [B,S,4,7168]各卡复制;N 是缓存条目数

Q 的后一段投影按输出切分,O 的后一段按输入切分。完整 residual、shared KV、mHC 参数与 shared expert 留在各 rank;不要把这些复制项再除以卡数。

工程细节

FP4 的四个位之外,还要存 scale

P路由 = 61×384×3×7168×3072 = 1547.396 B
bytes/参数 = 4/8 + 1/32 = 0.53125 B

每 32 个 FP4 元素对应 1 个 8-bit scale。主干路由专家裸 FP4 为 720.5625 GiB,含 scale 后为 765.5977 GiB,均分到 8 卡是 95.6997 GiB / 卡。

GiB=2³⁰ 字节。共享专家采用 FP8 权重、每 128×128 块一个 8-bit scale,并在各卡复制。embedding、Attention、mHC、路由表、输出头、MTP、workspace 和 allocator 还要分别记账。

逐步演算 · 教学数值

把每卡确定的三笔开销加起来

路由权重 = 95.6997权重 1 → 95.6997共享专家 = 3.75316权重 1 → 3.753168 请求缓存 = 2.60327权重 1 → 2.60327加权和0.00

单位 GiB;32K 上下文、BF16 参考 cache,主干权重和 scale。合计 102.056 GiB / 卡。已经超过 80 GiB,其他开销还未加入;8 卡只是分片拓扑示意。

02 / Prefill

先把提示词读完,建好每层 KV

图中展示 GPU 0–3;GPU 4–7 同时执行自己的分片。

Prompt:一批 token prefill GPU 0:Q / KV / MoE 本层 KV(按类型保存) GPU 1:Q / KV / MoE 本层 KV(按类型保存) GPU 2:Q / KV / MoE 本层 KV(按类型保存) GPU 3:Q / KV / MoE 本层 KV(按类型保存)
一批 token 一起算矩阵乘法可复用权重,因果 mask 保证只看过去。
每层都有自己的 KV不是只在输入端存一份;61 层各自更新。
读完开始生成最后一个位置的 logits 给出首个输出 token。
工程细节

先把每一层的主 KV 逐项相加

M主 = 1024 × [61×128 + 30⌊N/4⌋ + 31⌊N/128⌋] B
N=32,768,单请求、单卡含义结果
每条 512 维 × BF16一个向量同时作 K/V1,024 B
局部环形区61 层 × 128 条7.625 MiB
CSA + HCA 压缩区30×8192 + 31×256 条247.75 MiB
主 KV 合计所有主干层255.375 MiB

参考代码的量化调用模拟数值精度,缓存张量仍是 BF16;这份预算按实际张量存储计算。生产中的 packed cache 应改用真实格式、scale 和对齐后的大小。

工程细节

还有 Indexer 和未完成压缩组

单请求、单卡额外项公式32K 大小
Indexer cache30×8192×128×260 MiB
每个 CSA 的主+索引暂存2×8×1024×4 + 2×8×256×480 KiB / 层
每个 HCA 的暂存2×128×512×4512 KiB / 层
全部压缩暂存30×80 KiB + 31×512 KiB17.8438 MiB

两份暂存分别保存 value 与 gate,使用 FP32。主 KV、Indexer、暂存合计 333.219 MiB / 请求 / 卡;这些复制项增加卡数也不会自动缩小。

以上不含 RoPE 频率表、MTP 缓存和运行时临时张量;部署账本中给它们单独的行。

工程细节

长 prefill 的峰值,常出现在临时分数

参考 Indexer 先构造每个头的 query×候选分数,再把头求和。设 B=1、S=N=32,768;CSA 候选 C=8,192。

[B,S,Hindex/TP,C] = [1,32768,8,8192]

按 BF16,每个 CSA 层这一项为 4 GiB;头求和后的 [B,S,C] 仍有 512 MiB。最终保留 top-k 并不能消除计算途中的大张量。

优化方向:融合 score 与 top-k、按 query/candidate 分块、减小本轮 prefill token 数;用峰值 allocated 与 memory snapshot 找到真正的大分配。

参考 forward 在 start_pos>0 的路径按逐 token 更新压缩状态。实现 chunked prefill 时必须同时支持块内因果 mask、跨块 remainder 和重叠组,不能仅把输入切片循环调用。

03 / 放大一张 GPU

一次 kernel:读显存,算,写回

各张 GPU 都重复这个过程;这里放大 GPU 0。

GPU 0 · HBM 显存 常驻 W:本层权重 常驻 KV:历史状态 临时:激活 / 通信工作区 SM:计算单元 片上 SRAM / 寄存器 矩阵乘 · 归一化 · 注意力 新激活 h′
读本轮需要的权重块和 KV 块搬到片上。
算SM 用片上数据计算,分块反复推进。
写输出写入激活 / 通信 buffer;原权重和历史 KV 仍在。
工程细节

申请多少缓存,由上限决定

slots = window + (max_seq_len // ratio if ratio else 0)
kv_cache = zeros(max_batch_size, slots, 512)
if ratio == 4:  # CSA 的 Indexer
    index_cache = zeros(max_batch_size, max_seq_len // 4, 128)

这类预分配创建模型时就占显存。实际只输入 32K token,也不会把 max_seq_len=1M 的大张量自动缩小。

配置上限32K × 8 请求1,048,576 × 8 请求
主 KV + Indexer + 暂存2.603 GiB / 卡77.136 GiB / 卡

增大 max_batch_size 与 max_seq_len 之前先算乘积。动态按页分配可以按实际请求增长,但需要相应的 cache 管理与 kernel 支持。

04 / Tensor Parallel

各卡算自己的头,再把输出加起来

图中展示 GPU 0–3;总共 128 个头分到 8 张卡。

GPU 0 · 16 Q 头 读本地 KV → 注意力 O 的部分和 GPU 1 · 16 Q 头 读本地 KV → 注意力 O 的部分和 GPU 2 · 16 Q 头 读本地 KV → 注意力 O 的部分和 GPU 3 · 16 Q 头 读本地 KV → 注意力 O 的部分和 all-reduce
输入可见于各卡mHC 预混合得到 d 维输入,Q 投影按头分工。
KV 共用的含义同卡内多个头读一份 KV;此划分中各卡有 KV 副本。
输出再合起来分片输出投影产生部分和,all-reduce 得到完整结果。
工程细节

all-reduce 的消息大小,先看 dtype

M = B·S·d·4 B  ring 每卡发送量 ≈ 2(G−1)/G × M

参考 O 投影在 all-reduce 前转成 FP32;MoE 的部分和也用 FP32。通信后再转回激活 dtype。

B=32、S=1 的教学 ring 模型算式结果
单个完整输出张量32×7168×4896 KiB
一次 all-reduce 每卡发送2×(8−1)/8×M1568 KiB
理想时间模型2(G−1)α + 2(G−1)M/(Gβ)启动延迟 + 带宽时间

一层至少有 O 与 MoE 两次合并;CSA 还要合并 Indexer 分数。真实 collective 可能选择 tree 或其他算法,先从 trace 中确认消息形状、dtype 和算法。

逐步演算 · 教学数值

同一个输出坐标,把各卡部分和相加

GPU 0 = 2权重 1 → 2GPU 1 = 3权重 1 → 3GPU 2 = -1权重 1 → -1GPU 3 = 4权重 1 → 4加权和0.00

演算 Pro 8 卡中的一个坐标,GPU 4–7 对它的贡献设为 0。合并结果为 8,每张卡都得到这个完整结果;如果少加一张卡,后续 mHC 与路由都会随之改变。这里只画求和语义,ring 实际按块传输。

05 / 按专家分权重

专家待在原卡,token 在那里计算

这组划分中:各卡都有 token 表示,执行本地专家后合并。GPU 4–7 对该示例 token 无路由专家任务。

相同 token 表示 GPU 0 计算:E0、E2 GPU 1 计算:E48 GPU 2 计算:E96、E97 GPU 3 计算:E144 加权部分和归约 再加共享专家
路由一次选 6 个图中专家 ID 是一组示例,可能集中或分散在各卡。
本地权重留在 HBM本轮没选中的专家仍然常驻,只是不参与该 token。
汇合后继续本地加权结果 all-reduce,再加共享专家。
工程细节

batch 变大,读取的专家权重也会变多

E[本轮不同专家数] = E × [1 − (1 − 6/E)ᴮ]

在均匀、各 token 独立选择的教学模型下,B=1 激活 6 个路由专家;B=32 时,Pro 每层预计访问约 152.0 / 384 个专家。

看起来矛盾的现象原因该记录什么
batch 变大、吞吐升高一个 GEMM 处理更多 tokentokens/s、tokens/expert
本轮读权重更多多请求覆盖更多专家不同专家数、HBM bytes
平均负载低但尾延迟高少数热点拖住同步每 rank 的专家计数与耗时

公式只描述均匀路由基线。真实请求常有关联,必须看实际路由分布;不能把“单 token 激活参数×batch”当作实际权重读取字节数。

06 / 高吞吐 Expert Parallel

token 分片时:发输入,收结果

服务系统还可以把 token 分散到各卡,再按需交换激活。这样就得到典型的 EP dispatch / combine。

token 所属卡 专家所在卡 1 专家结果 专家所在卡 2 专家结果 专家所在卡 3 专家结果
Dispatch按专家目的地打包 token 激活,发给对应卡。
本地专家计算先算 gate / up,再做 SwiGLU 和 down 投影。
Combine结果回到 token 所属卡,按路由权重相加。
工程细节

换成 token 分片,通信账本也要换

朴素 dispatch payload = B·S·k·d·bytes

全局 32 个 token、k=6、BF16:输入 payload 为 32×6×7168×2 = 2688 KiB;返回每个专家结果也是这个数量级。

同一 token 的多个专家在同一 rank 时,可以先按目的 rank 合并发送,再在目标卡展开。算网络量时还要扣掉本地专家,加入 IDs、权重与对齐。

参考路径已在各 rank 复制 token,只执行本地专家,然后 all-reduce。token 分片路径要有 dispatch、反向 combine,以及保持 token 顺序的索引。

两种路径可以表达同一个 MoE 结果,但不能把 token 分片的通信量直接套到复制输入的代码上。

07 / Decode cache

每来一个 token,更新局部;满一组,再记长期

最近窗口:环形更新,始终只保留最近 128 个位置 KV 0 KV 1 KV 2 KV 3 KV 4 KV 5 KV 6 KV 7 CSA:新 token 先进入压缩状态,每满 4 个追加一条 t+1 t+2 t+3 t+4 长期压缩 KV:… cₙ cₙ₊₁ HCA 每满 128 个才产出一条;CSA indexer 也维护自己的压缩 key。
窗口更新新 KV 写入当前位置,最老的局部位置被复用。
压缩追加只在组完成后追加条目;未满组保留在状态 buffer。
严格因果每个 query 只读当时已完成、已可见的压缩条目。
工程细节

把整段和逐 token 路径对在一起

full = forward_all(tokens)          # 每层截取检查点
reset_cache()
for t in range(len(tokens)):
    step = forward_one(tokens[t], position=t)
    compare(step, full[t], atol, rtol)

按顺序检查:局部环形区 → 主压缩 KV → 索引压缩 KV → top-k IDs → attention 输出 → MoE 输出。先固定模型权重与 token,暂时移除随机采样。

重点长度:3/4/5、7/8/9、127/128/129;同时检查不同请求的 cache 是否隔离。浮点路径不同可能产生小误差,首次显著偏差的位置最有诊断价值。

08 / Decode 循环

生成一个,再接着生成一个

当前 token 全部 61 层 输出头 采样新 token 每一轮:逐层计算并更新 cache → 采样 → 追加 token → 下一轮
计算要穿过所有层每层都包含 Attention、MoE 和 mHC。
KV 留着复用历史 token 的每层表示无需从头再算。
权重反复被读取模型有 1.6T 参数;每 token 激活约 49B,所选专家随 token 变化。
09 / 计算与内存账

快在哪里,取决于本轮在等什么

Prefill:一批 token

大矩阵乘法更容易占满计算单元;长序列还要读写注意力状态和生成压缩 KV。

Decode:每请求一个 token

小 batch 常受权重 / KV 读取和通信延迟影响。合批增加权重复用;请求也会争用 KV 空间。

每卡显存 = 本地权重 + KV 副本 + 压缩状态 + 激活 / 工作区

估容量用完整权重的实际存储精度、分片与复制方式,再加请求的 KV。49B 激活参数描述每 token 计算规模。

MTP 可先给出草稿,再由主干验证;收益还取决于接受率、验证开销与 batch。

工程细节

先优化端到端关键路径上的最大空档

教学测量:一次 decode 的 attention 6 ms、专家计算 8 ms、未隐藏通信 12 ms、CPU/调度 2 ms,共 28 ms。

把专家计算优化 2 倍,仅节省 4 ms,整轮降到 24 ms(1.17×)。若先减少 8 ms 的暴露通信,整轮变 20 ms(1.40×)。

记录一次完整请求:排队、prefill、每步 decode。区分 TTFT、TPOT、吞吐和 p95/p99;显存利用率不是算力利用率。

GPU kernel 时间之和可包含重叠,CPU 计时又可能只测到异步提交。沿时间线找暴露在关键路径上的部分,再算优化上限。

工程细节

测量前同步,计时区间包住真实计算

torch.cuda.synchronize()
start, end = torch.cuda.Event(enable_timing=True), torch.cuda.Event(enable_timing=True)
start.record()
with torch.profiler.record_function("decode_step"):
    logits = model(tokens, start_pos=position)
end.record()
end.synchronize()
print(start.elapsed_time(end), "ms")

先 warm-up,再重复多轮。CUDA event 只覆盖所在设备/stream 的时间线;多卡用每个 rank 的时间和通信 trace 定位最慢的一张卡,端到端延迟另用 wall clock 记录。

运行这段示意代码需要 PyTorch、CUDA 和已加载模型;配套 lab 使用 CPU 解析模拟。

10 / 通信与计算重叠

切成小波次,让传输和计算接上

Dispatch wave 1 wave 2 wave 3 专家计算 wave 1 wave 2 wave 3 Combine wave 1 wave 2 wave 3 时间 → 下一波在传输入,这一波在计算,上一波在传结果。
分成 waves每波只放一部分专家任务。
就绪就开算当前波收齐输入,立即进入计算。
看剩下的等待性能由关键路径决定;重叠程度取决于负载与互连。
练习 1

8×80 GiB 不够,下一步怎么选

Pro 主干路由专家含 scale 为 765.5977 GiB。暂时只按这项算容量下界:至少几张 80 GiB 卡?再要求 TP 整除输出组16、Q头128、Indexer头64、专家384,最小合法卡数是多少?
展开参考解答

字节下界是 ceil(765.5977/80)=10;10 不整除这些维度。可选 TP 为 1、2、4、8、16,因此最小满足这个下界的是 16。路由权重变为 47.8499 GiB / 卡。

这仍不是完整部署容量证明。再加共享权重、每卡复制缓存、MTP 与峰值 workspace;若硬件/通信拓扑不适合16路TP,还可设计 PP 或不同并行组。

先写出张量形状与单位,再代入数字。

练习 2

上下文翻四倍,为什么缓存不是整整四倍

Pro 单请求 32K 时 cache 为 333.219 MiB。把 N 改为131,072,求主 KV+Indexer+暂存。哪些项不随 N 增长?
展开参考解答

主 KV = 1024×[61×128+30×32768+31×1024] B = 998.625 MiB。Indexer = 30×32768×128×2 B = 240 MiB。

再加固定暂存 17.8438 MiB,总计 1256.47 MiB。局部128窗口和未完成组暂存不随 N 增长,所以总量小于原先的4倍。

先写出张量形状与单位,再代入数字。

练习 3

有一张卡慢,合并就得等它

TP=8、B=32、S=1,O 投影的 FP32 输出大小为 M。算 ring 每卡发送字节数;若另一份实现用 BF16 通信,payload 会变多少?何时延迟却不会减半?
展开参考解答

M=32×7168×4=917,504 B。发送量 2×(8−1)/8×M=1,605,632 B,即 1568 KiB。BF16 payload 减半。

小消息受启动延迟 α 支配;rank 等待、拥塞或计算失衡也不会随 dtype 减半。改变通信精度还要重新检验归约误差。

先写出张量形状与单位,再代入数字。

动手实验

把推导变成可以检查的程序

逐层计算真实形状对应的 cache、FP4 scale 开销和并行容量条件,并模拟环形 cache、归约与调度账本。

python3 lab.py
python3 lab.py --context 131072 --batch 8
cache @ 32768: 333.218750 MiB/request/rank
routed FP4+scale: 765.597656 GiB total
minimum TP for routed weights on 80 GiB: 16
all checks passed

下载 lab.py

先预测上下文翻四倍时哪些项不变,再运行第二条命令。只需 Python 标准库,在 CPU 上检查容量与数据流。

11 / 参考与延伸阅读

从架构走到真正的推理系统

DeepSeek-V4-Pro 官方模型卡Preview 模型的总参数、激活参数与权重精度。
官方 config.json · 推理实现 model.py层数、维度、压缩周期、mHC、路由与 cache 更新。
DeepSeek-V4 技术报告§2 架构,§3.1 专家通信与计算重叠,§4.2 模型配置。
技术报告精读 · Pro 架构 · V4-Flash 推理

配置核对:2026-10-20。动画中的 token、专家选择、显存块大小是教学示例。