DeepSeek-V4-Flash 推理:
一枚 token,4 张卡
权重留在显存,计算把它读进芯片;上下文记忆逐步增长,token 的表示在卡间流动。
采用 TP=4 的分片拓扑教学示意:按头、按专家分权重。能否部署还取决于完整权重、复制缓存与临时内存的实际峰值。
每张卡都有自己的权重和上下文记忆
每层 256 个路由专家均分;Q / O 按头分片。这组划分中,shared KV、mHC 与共享专家在各卡保留副本。4 路残差是模型维度,与 GPU 数量无关。
一张卡负责哪些维度,复制哪些状态
| 项目 | Flash 全局 | TP=4 每张卡 |
|---|---|---|
| Query 头 | 64 | 16 |
| 输出投影组 | 8 | 2 |
| Indexer 头 | 64 | 16 |
| 每层路由专家 | 256 | 64 |
| shared KV / 残差 | [B,N,512] / [B,S,4,4096] | 各卡复制;N 是缓存条目数 |
Q 的后一段投影按输出切分,O 的后一段按输入切分。完整 residual、shared KV、mHC 参数与 shared expert 留在各 rank;不要把这些复制项再除以卡数。
FP4 的四个位之外,还要存 scale
每 32 个 FP4 元素对应 1 个 8-bit scale。主干路由专家裸 FP4 为 129.0000 GiB,含 scale 后为 137.0625 GiB,均分到 4 卡是 34.2656 GiB / 卡。
GiB=2³⁰ 字节。共享专家采用 FP8 权重、每 128×128 块一个 8-bit scale,并在各卡复制。embedding、Attention、mHC、路由表、输出头、MTP、workspace 和 allocator 还要分别记账。
把每卡确定的三笔开销加起来
单位 GiB;32K 上下文、BF16 参考 cache,主干权重和 scale。合计 37.086 GiB / 卡。80 GiB 卡还剩约 42.91 GiB 给其他权重和临时内存;最终要以实际分配验证。
先把提示词读完,建好每层 KV
图中展示 GPU 0–3;四张卡协作完成每一层。
先把每一层的主 KV 逐项相加
| N=32,768,单请求、单卡 | 含义 | 结果 |
|---|---|---|
| 每条 512 维 × BF16 | 一个向量同时作 K/V | 1,024 B |
| 局部环形区 | 43 层 × 128 条 | 5.375 MiB |
| CSA + HCA 压缩区 | 21×8192 + 20×256 条 | 173 MiB |
| 主 KV 合计 | 所有主干层 | 178.375 MiB |
参考代码的量化调用模拟数值精度,缓存张量仍是 BF16;这份预算按实际张量存储计算。生产中的 packed cache 应改用真实格式、scale 和对齐后的大小。
还有 Indexer 和未完成压缩组
| 单请求、单卡额外项 | 公式 | 32K 大小 |
|---|---|---|
| Indexer cache | 21×8192×128×2 | 42 MiB |
| 每个 CSA 的主+索引暂存 | 2×8×1024×4 + 2×8×256×4 | 80 KiB / 层 |
| 每个 HCA 的暂存 | 2×128×512×4 | 512 KiB / 层 |
| 全部压缩暂存 | 21×80 KiB + 20×512 KiB | 11.6406 MiB |
两份暂存分别保存 value 与 gate,使用 FP32。主 KV、Indexer、暂存合计 232.016 MiB / 请求 / 卡;这些复制项增加卡数也不会自动缩小。
以上不含 RoPE 频率表、MTP 缓存和运行时临时张量;部署账本中给它们单独的行。
长 prefill 的峰值,常出现在临时分数
参考 Indexer 先构造每个头的 query×候选分数,再把头求和。设 B=1、S=N=32,768;CSA 候选 C=8,192。
按 BF16,每个 CSA 层这一项为 8 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 和重叠组,不能仅把输入切片循环调用。
一次 kernel:读显存,算,写回
各张 GPU 都重复这个过程;这里放大 GPU 0。
申请多少缓存,由上限决定
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 + 暂存 | 1.813 GiB / 卡 | 53.883 GiB / 卡 |
增大 max_batch_size 与 max_seq_len 之前先算乘积。动态按页分配可以按实际请求增长,但需要相应的 cache 管理与 kernel 支持。
各卡算自己的头,再把输出加起来
图中展示 GPU 0–3;总共 64 个头分到 4 张卡。
all-reduce 的消息大小,先看 dtype
参考 O 投影在 all-reduce 前转成 FP32;MoE 的部分和也用 FP32。通信后再转回激活 dtype。
| B=32、S=1 的教学 ring 模型 | 算式 | 结果 |
|---|---|---|
| 单个完整输出张量 | 32×4096×4 | 512 KiB |
| 一次 all-reduce 每卡发送 | 2×(4−1)/4×M | 768 KiB |
| 理想时间模型 | 2(G−1)α + 2(G−1)M/(Gβ) | 启动延迟 + 带宽时间 |
一层至少有 O 与 MoE 两次合并;CSA 还要合并 Indexer 分数。真实 collective 可能选择 tree 或其他算法,先从 trace 中确认消息形状、dtype 和算法。
同一个输出坐标,把各卡部分和相加
演算 Flash 4 卡中的一个坐标。合并结果为 8,每张卡都得到这个完整结果;如果少加一张卡,后续 mHC 与路由都会随之改变。这里只画求和语义,ring 实际按块传输。
专家待在原卡,token 在那里计算
这组划分中:各卡都有 token 表示,执行本地专家后合并。
batch 变大,读取的专家权重也会变多
在均匀、各 token 独立选择的教学模型下,B=1 激活 6 个路由专家;B=32 时,Flash 每层预计访问约 136.1 / 256 个专家。
| 看起来矛盾的现象 | 原因 | 该记录什么 |
|---|---|---|
| batch 变大、吞吐升高 | 一个 GEMM 处理更多 token | tokens/s、tokens/expert |
| 本轮读权重更多 | 多请求覆盖更多专家 | 不同专家数、HBM bytes |
| 平均负载低但尾延迟高 | 少数热点拖住同步 | 每 rank 的专家计数与耗时 |
公式只描述均匀路由基线。真实请求常有关联,必须看实际路由分布;不能把“单 token 激活参数×batch”当作实际权重读取字节数。
token 分片时:发输入,收结果
服务系统还可以把 token 分散到各卡,再按需交换激活。这样就得到典型的 EP dispatch / combine。
换成 token 分片,通信账本也要换
全局 32 个 token、k=6、BF16:输入 payload 为 32×6×4096×2 = 1536 KiB;返回每个专家结果也是这个数量级。
同一 token 的多个专家在同一 rank 时,可以先按目的 rank 合并发送,再在目标卡展开。算网络量时还要扣掉本地专家,加入 IDs、权重与对齐。
参考路径已在各 rank 复制 token,只执行本地专家,然后 all-reduce。token 分片路径要有 dispatch、反向 combine,以及保持 token 顺序的索引。
两种路径可以表达同一个 MoE 结果,但不能把 token 分片的通信量直接套到复制输入的代码上。
每来一个 token,更新局部;满一组,再记长期
把整段和逐 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 是否隔离。浮点路径不同可能产生小误差,首次显著偏差的位置最有诊断价值。
生成一个,再接着生成一个
快在哪里,取决于本轮在等什么
Prefill:一批 token
大矩阵乘法更容易占满计算单元;长序列还要读写注意力状态和生成压缩 KV。
Decode:每请求一个 token
小 batch 常受权重 / KV 读取和通信延迟影响。合批增加权重复用;请求也会争用 KV 空间。
估容量用完整权重的实际存储精度、分片与复制方式,再加请求的 KV。13B 激活参数描述每 token 计算规模。
MTP 可先给出草稿,再由主干验证;收益还取决于接受率、验证开销与 batch。
先优化端到端关键路径上的最大空档
教学测量:一次 decode 的 attention 3 ms、专家计算 5 ms、未隐藏通信 2 ms、CPU/调度 6 ms,共 16 ms。
把专家计算优化 2 倍,仅节省 2.5 ms,整轮降到 13.5 ms(1.19×)。若 CPU 开销降到 1 ms,整轮变 11 ms(1.45×)。
记录一次完整请求:排队、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 解析模拟。
切成小波次,让传输和计算接上
80 GiB 卡还剩多少空间
展开参考解答
缓存 = 8×232.015625/1024 = 1.812622 GiB;三项合计 34.265625+1.007874+1.812622 = 37.086121 GiB。80 GiB 还剩 42.913879 GiB。
剩余空间还要放其余权重、MTP、RoPE 表、workspace 与 allocator 余量。算式给出容量账本的一部分,不等同于实际测得峰值。
先写出张量形状与单位,再代入数字。
上下文翻四倍,为什么缓存不是整整四倍
展开参考解答
主 KV = 1024×[43×128+21×32768+20×1024] B = 697.375 MiB。Indexer = 21×32768×128×2 B = 168 MiB。
再加固定暂存 11.6406 MiB,总计 877.016 MiB。局部128窗口和未完成组暂存不随 N 增长,所以总量小于原先的4倍。
先写出张量形状与单位,再代入数字。
有一张卡慢,合并就得等它
展开参考解答
M=32×4096×4=524,288 B。发送量 2×(4−1)/4×M=786,432 B,即 768 KiB。BF16 payload 减半。
小消息受启动延迟 α 支配;rank 等待、拥塞或计算失衡也不会随 dtype 减半。改变通信精度还要重新检验归约误差。
先写出张量形状与单位,再代入数字。
把推导变成可以检查的程序
逐层计算真实形状对应的 cache、FP4 scale 开销和并行容量条件,并模拟环形 cache、归约与调度账本。
python3 lab.py
python3 lab.py --context 131072 --batch 8cache @ 32768: 232.015625 MiB/request/rank
routed FP4+scale: 137.062500 GiB total
minimum TP for routed weights on 80 GiB: 2
all checks passed先预测上下文翻四倍时哪些项不变,再运行第二条命令。只需 Python 标准库,在 CPU 上检查容量与数据流。
从架构走到真正的推理系统
配置核对:2026-10-20。动画中的 token、专家选择、显存块大小是教学示例。