第1章 V4 之路:从 V2 / V3 / V3.2 到 V4 的架构演进

“现代神经网络架构的每一次跃迁,本质上都是一场对’空间 / 时间 / 精度’三角的再平衡。” —— Tri Dao

你看到的 V4,不是一次发布。是 28 个月。


1.1 引子:为什么必须先讲 V2 / V3 / V3.2

互联网上关于 V4 的解读,大多数从 2026-04-24 那次发布讲起——这种讲法读起来很爽,但会错过 V4 真正的灵魂

DeepSeek 在 V4 上做的几乎每一处架构改动,都是对前三代某个已知工程伤疤的回应:

  • V4 把残差换成 Hyper-Connections——是因为 V3 训练曲线在第 5T token 附近出现过梯度异常,团队在内部补丁里第一次尝试 HC,那时候叫”残差混合”
  • V4 把 routed experts 从 256 加到 384——是因为 V3 在 SFT 阶段持续观测到”专家利用率分布的长尾”,把宽度加到 384 是缓解长尾的工程妥协
  • V4 引入 Indexer 稀疏注意力,并把 KV 改造成 “Compressor + 滑窗”——是因为 V3.2-Exp 验证了 DSA(DeepSeek Sparse Attention)路线在 128K 以上 context 的可行性
  • V4 把 expert 权重压到 FP4——是因为 V3 在 FP8 训练里已经把 ue8m0 scale 玩到极致,再要省显存只能向下走一步

把 V2 / V3 / V3.2 看作 V4 的三段铺垫,再去读 inference/model.py,每一行代码背后那个”为什么这样写”的理由就会立刻浮出水面。

timeline
  title DeepSeek MoE 谱系(2024-2026)
  2024-05 : V2 (236B / 21B)<br/>引入 MLA + DeepSeekMoE
  2024-12 : V3 (671B / 37B)<br/>把 MoE 推到 256 专家 + FP8 训练
  2025-09 : V3.2-Exp<br/>实验性 Sparse Attention (DSA)
  2026-04 : V4 Pro (1.6T / 49B) + V4 Flash (284B / 13B)<br/>HC + Compressor + Indexer + FP4 experts

1.2 V2:MLA 与 DeepSeekMoE 的奠基

V2 在 2024 年 5 月发布,236B 总参 / 21B 激活。它在论文 DeepSeek-V2: A Strong, Economical, and Efficient Mixture-of-Experts Language Model 中第一次亮出了 DeepSeek 的两件兵器:

1.2.1 MLA(Multi-head Latent Attention)

传统 Multi-Head Attention 的 KV cache 大小是:

KV size=2×L×H×dh×ntok\text{KV size} = 2 \times L \times H \times d_h \times n_\text{tok}

其中 LL 是层数、HH 是 head 数、dhd_h 是 head 维度、ntokn_\text{tok} 是序列长度。70B 模型在 32K context 下,KV cache 大约要 18 GB——这是当年部署的主要瓶颈。

GQA(Grouped Query Attention)把 KV 头数减少,但仍然在原始 head_dim 上存 KV。MLA 的思路完全不同——把 KV 压到一个共享的 低秩潜在空间

flowchart LR
  subgraph 传统MHA["传统 MHA / GQA"]
    direction TB
    X1["输入 x"] --> WQ["W_Q"] --> Q1["Q (n_h × d_h)"]
    X1 --> WK["W_K"] --> K1["K (n_kv × d_h)"]
    X1 --> WV["W_V"] --> V1["V (n_kv × d_h)"]
    K1 --> Cache1["KV Cache (n_kv × d_h × 2)"]
  end

  subgraph MLA["MLA (V2/V3)"]
    direction TB
    X2["输入 x"] --> Wkv_a["W_kv_a (low rank)"] --> KV_lat["KV latent (kv_lora_rank=512)"]
    KV_lat --> Cache2["KV Cache (kv_lora_rank)<br/>显存 ↓ 93%"]
    KV_lat --> Wk_b["W_k_b"] --> K2["K"]
    KV_lat --> Wv_b["W_v_b"] --> V2["V"]
  end

V2 / V3 的 KV cache 在 1M context 下只要原始 MHA 的约 7%,这是当年 DeepSeek 能首次在国产卡上跑通 128K 长文档推理的关键。

V4 进一步把这条路变了——完全不存 latent KV,转而用 head_dim=512 的”压缩 KV + 滑窗 + Indexer 选取”。这是第 3、4 章会详细拆的剧情。

1.2.2 DeepSeekMoE:细粒度专家 + 共享专家

V2 之前的 MoE(如 Mixtral、GShard)的专家数量在 8-64 之间。DeepSeekMoE 论文 DeepSeekMoE: Towards Ultimate Expert Specialization in Mixture-of-Experts Language Models 提出两个改造:

  1. 细粒度专家:把 expert 数量加到 64(V2)/ 160(V2.5)/ 256(V3)/ 384(V4),并相应减小每个 expert 的 intermediate_size
  2. 共享专家(shared expert):每层永远激活的 expert,吸收”通用知识”,让 routed expert 专注”细分知识”
flowchart TB
  X[输入 x] --> Gate{Gate}
  Gate -->|top-k 路由| E1[Expert 1]
  Gate -->|top-k 路由| E2[Expert 2]
  Gate -->|top-k 路由| Edot[...]
  Gate -->|top-k 路由| EN[Expert 384]
  X -->|永远激活| ES[Shared Expert]
  E1 --> Sum((+))
  E2 --> Sum
  Edot --> Sum
  EN --> Sum
  ES --> Sum
  Sum --> Out[输出]

V4 沿用了这个结构,区别只在于 n_routed_experts=384(V3=256)、num_experts_per_tok=6(V3=8)。后面第 7-9 章会讲到,看似只是数字变了,背后牵动的是 gate scoring function 的换型hash 路由层的引入


1.3 V3:把 MoE 推到 671B + FP8 训练

V3 在 2024 年 12 月开源,671B 总参 / 37B 激活,论文 DeepSeek-V3 Technical Report。它做了三件大事:

1.3.1 256 专家 + auxiliary-loss-free 负载均衡

V2 / V3 用过传统的 auxiliary loss(在 cross-entropy 之外加一个”专家使用均匀度”的辅助损失),但 aux loss 会干扰主任务的语义学习——专家被强行均匀化,可能损失某些极有价值的稀有专家。

V3 的做法是 noaux_tc——auxiliary-loss-free 负载均衡:

  • 不再用辅助损失
  • 给每个 expert 维护一个 bias term(V4 源码 inference/model.py:562self.bias = nn.Parameter(torch.empty(args.n_routed_experts, dtype=torch.float32)),位于 Gate 类 __init__ 内)
  • bias 只在 topk 选取时被加入,不参与最终 routing weight
  • 训练过程中根据每个 expert 的”使用频率”动态调整 bias,使过载的 expert 被压低、欠载的 expert 被抬高

V4 完全继承了这套机制,并在 topk_method="noaux_tc" 这个配置项中显式标注。

1.3.2 FP8 训练 + ue8m0 scale

V3 是世界上第一个在 671B 级别成功跑通 FP8 全栈训练的开源 LLM。关键工程包括:

  • e4m3 用于 forward / backward 的 GEMM
  • e5m2 用于梯度
  • ue8m0 用于 scale tensor(一种纯指数 8-bit 浮点)
  • block-wise scaling:每 128 × 128 一个 scale,平衡精度与显存

V4 的 config.json 里这一段是直接继承 V3 的:

"quantization_config": {
  "activation_scheme": "dynamic",
  "fmt": "e4m3",
  "quant_method": "fp8",
  "scale_fmt": "ue8m0",
  "weight_block_size": [128, 128]
}

但 V4 在此基础上又向下走了一步——expert 权重直接压到 FP4 e2m1,每 32 个 fp4 元素一个 ue8m0 scale。这是第 12 章会详细分析的。

1.3.3 MTP:多 Token 预测

V3 的最后一招,是给训练加了一个辅助 head:在主预测 head 之外,额外训练一个预测下一下个 token 的 MTPBlock。它带来三个好处:

  • 训练时增加监督信号,加快收敛
  • 推理时可以做 speculative decoding(投机解码)
  • 让 hidden representation 学会”看更远”

V4 沿用了 MTP,但只保留 1 个 MTPBlock(num_nextn_predict_layers=1)。源码层面看:

# inference/model.py: Transformer.__init__
self.mtp = torch.nn.ModuleList()
for layer_id in range(args.n_mtp_layers):
    self.mtp.append(MTPBlock(args.n_layers + layer_id, args))
self.mtp[-1].embed = self.embed
self.mtp[-1].head = self.head

注意最后两行——MTPBlock 的 embedhead 直接复用主模型的 embedding 和 lm_head。这是个非常便宜的实现:MTP 只多了一组 transformer block 的参数,embedding 和输出投影都是免费的。


1.4 V3.2-Exp:稀疏注意力的实验场

V3.2-Exp(“Exp” 是 experimental 的缩写)是 V3 与 V4 之间的一次”半发布”。它的全部价值在于:在生产规模上验证稀疏注意力的可行性

V3.2-Exp 引入了 DSA(DeepSeek Sparse Attention)

  • 在原本 dense MLA 的基础上叠加一个 sparse 路径
  • 通过一个轻量的 score net 选 top-k KV 位置
  • 选中的位置走稀疏内核(最早期版本基于 FlashMLA 的实验分支)
  • 训练用 QAT(Quantization-Aware Training) + 稀疏感知监督 让两条路径协同收敛

V3.2-Exp 跑了几个月,得到了三个关键经验:

  1. 稀疏 + dense 双路径会增加训练复杂度——V4 直接砍掉 dense 路径,纯稀疏
  2. 滑动窗口 + 稀疏 KV 选择的组合在 1M context 下数学上能稳——V4 直接采用
  3. score net 必须有自己的 KV 视角——V4 给 Indexer 配了独立的 Compressor

V4 的 inference/model.py:380-435 那个 Indexer 类(class Indexer(torch.nn.Module)),本质上就是把 V3.2-Exp 的 score net 工业化、并把它的内部 KV 投影也压到 fp4。


1.4·补 V3.2-Exp 那次”半发布”在 V4 源码里留下的指纹

V3.2-Exp 在 2025 年 9 月发布,对外几乎没有宣传——把它读成 V4 的”工程预演”是最准确的。它没有上 leaderboard,模型卡只标记了一行”Experimental”。但 V3.2-Exp 把 DSA(DeepSeek Sparse Attention) 这套机制塞进了一个真实生产规模的模型里跑了几个月。当我们打开 V4 的 inference/model.py,至少有三处源码细节可以直接对回 V3.2-Exp 的实战教训——这些是可以验证的,不需要任何内部资料:

  1. Indexer 拥有独立的 Compressorinference/model.py:Indexer.__init__

    self.compressor = Compressor(args, compress_ratio, self.head_dim, True)  # rotate=True

    注意最后一个参数 rotate=True——意味着 Indexer 自己的 Compressor 会做一次 Hadamard 旋转,并把 KV 量化到 FP4。这与主 Attention 的 Compressor(rotate=False)刻意区分。如果 score net 共用主 KV,源码里就不会有这一行——V3.2-Exp 验证了”score net 的 KV 投影必须独立”,V4 在源码里把它写死。

  2. 每层独立的 compress_ratioconfig.jsoncompress_ratios 是一个长度 62 的数组——主模型 61 层 + MTP 层 1 个)

    V3.2-Exp 的 DSA 是”全层均匀稀疏”。V4 改成 per-layer 的非均匀配置——[128, 128, 4, 128, 4, ...]。这个配置只能从工程实验里得来,不是凭直觉拍出来的。

  3. 滑动窗口 + 稀疏 KV 的串联Attention.forwardtopk_idxs = torch.cat([topk_idxs, compress_topk_idxs], dim=-1)

    V3.2-Exp 的 DSA 早期版本是”稀疏 替代 dense”。V4 改成”滑窗 + 稀疏”——近距离 KV 走滑窗(精度高、覆盖完整),远距离 KV 走稀疏(覆盖大、成本低)。源码里这两个 topk 索引是用 torch.cat 拼起来的——这是两条注意力路径合一的工程标志。

这三条都不是从内部资料里推断的,它们是 V4 源码里任何人都可以 grep 出来的确凿事实。V3.2-Exp 的价值就是把这三条经验”刻”进了 V4 的代码里。



1.5 V4 全景:从一行 forward 看到整张地图

V4 的全部前向逻辑,可以浓缩成 Transformer.forwardinference/model.py:802,类定义在第 769 行)的几行:

@torch.inference_mode()
def forward(self, input_ids: torch.Tensor, start_pos: int = 0):
    h = self.embed(input_ids)
    # Expand to hc_mult copies for Hyper-Connections
    h = h.unsqueeze(2).repeat(1, 1, self.hc_mult, 1)
    for layer in self.layers:
        h = layer(h, start_pos, input_ids)
    logits = self.head(h, self.hc_head_fn, self.hc_head_scale, self.hc_head_base, self.norm)
    return logits

短到不足 10 行,却把 V4 全部”反常识”的设计都摆在了表面:

看似平淡的代码背后的故事
h = self.embed(input_ids)普通 embedding但是 ParallelEmbedding——vocab 维度被切到 TP 多卡
h.unsqueeze(2).repeat(1, 1, self.hc_mult, 1)把 h 在某个新维度上复制 4 份这是把传统残差换成 Hyper-Connections 的入口——h 从此变成 [B, S, hc_mult, D] 的 4D 张量
for layer in self.layers: h = layer(h, start_pos, input_ids)普通的 N 层堆叠但每层 layer 是 Block,里面用 hc_pre / hc_post 做 4-way 残差混合,并按 layer_id 切换 compress_ratio
logits = self.head(h, ...)LM head但 head 又是 4D 输入,用 hc_head 把 4 路 hidden 合并成 1 路再投影

把这 4 行展开,就是这本书后面 19 章要讲的全部内容。

flowchart TB
  IN["input_ids: [B, S]"] --> EMB["ParallelEmbedding<br/>(第 15 章)"]
  EMB --> H0["h: [B, S, D]"]
  H0 --> EXPAND["unsqueeze + repeat<br/>= [B, S, hc_mult=4, D]<br/>(Hyper-Connections 入口)"]
  EXPAND --> L1["Block 0 (第 1-9 章)"]
  L1 --> L2["Block 1"]
  L2 --> Ldot["..."]
  Ldot --> LN["Block 60"]
  LN --> NORM["RMSNorm + ParallelHead<br/>(第 10 章 hc_head)"]
  NORM --> LOGITS["logits: [B, vocab]"]

  subgraph 每层Block["每层 Block 内部 (第 1-11 章)"]
    direction TB
    Bin["h: [B, S, hc_mult, D]"] --> HCpreA["hc_pre (Sinkhorn)"]
    HCpreA --> AttnNorm["RMSNorm"]
    AttnNorm --> Attn["Attention<br/>(MLA + Compressor + Indexer + sparse_attn)"]
    Attn --> HCpostA["hc_post"]
    HCpostA --> HCpreF["hc_pre (Sinkhorn)"]
    HCpreF --> FfnNorm["RMSNorm"]
    FfnNorm --> MoE["MoE Gate + 384 routed + 1 shared"]
    MoE --> HCpostF["hc_post"]
    HCpostF --> Bout["h_out: [B, S, hc_mult, D]"]
  end

这张图也是本书的章节地图。


1.5·补 V4 的核心设计决策树

把 V4 的所有架构决策编成一张决策树,会发现它们之间有非常清晰的因果链:

flowchart TB
  Goal["目标:1M context + 价格大幅下降"]:::goal
  Goal --> KV["挑战 1:KV cache 在 1M 下会爆"]:::ch
  Goal --> FLOPs["挑战 2:1.6T 模型推理 FLOPs 难承受"]:::ch
  Goal --> Train["挑战 3:训练成本必须可控"]:::ch
  Goal --> Stability["挑战 4:1.6T MoE 训练梯度稳定"]:::ch

  KV --> S1["决策 1:抛弃 V3 的 latent KV<br/>(kv_lora_rank → head_dim 全维)"]:::dec
  KV --> S2["决策 2:滑窗 + 压缩 KV 双通路"]:::dec
  KV --> S3["决策 3:per-layer 非均匀压缩比"]:::dec

  FLOPs --> S4["决策 4:稀疏 attention(Indexer top-1024)"]:::dec
  FLOPs --> S5["决策 5:grouped O 投影(o_groups=16)"]:::dec
  FLOPs --> S6["决策 6:top-6 而非 V3 的 top-8"]:::dec

  Train --> S7["决策 7:expert FP4 e2m1"]:::dec
  Train --> S8["决策 8:Muon 优化器(取代 AdamW)"]:::dec

  Stability --> S9["决策 9:Hyper-Connections(替代残差)"]:::dec
  Stability --> S10["决策 10:sqrtsoftplus + noaux_tc"]:::dec
  Stability --> S11["决策 11:前 3 层 hash 路由"]:::dec
  Stability --> S12["决策 12:Sinkhorn 归一化(HC 内部)"]:::dec

  classDef goal fill:#312e81,stroke:#a78bfa,color:#ede9fe;
  classDef ch fill:#7c2d12,stroke:#fb923c,color:#ffedd5;
  classDef dec fill:#0f172a,stroke:#3b82f6,color:#dbeafe;

这张图把 V4 的 12 个核心决策按”挑战 → 决策”分组。读完全书后回头看,每一个决策都会对应到本书某一节的”为什么这样”分析:

决策章节定位主要源码位置
抛弃 latent KV,KV 直接到 head_dim=512§2Attention.__init__self.wkv = Linear(self.dim, self.head_dim)
滑窗 + 压缩 KV§3kv_cache_size = window_size + max_seq_len // ratio
per-layer 压缩比§3.4compress_ratios[layer_id]
稀疏 attention§4-5Indexer 类 + sparse_attn kernel
grouped O 投影§2.5wo_a 的 ColumnParallelLinear + n_groups 分组
top-6§7n_activated_experts=6
expert FP4§12expert_dtype="fp4" + Linear(dtype=torch.float4_e2m1fn_x2)
Muon 优化器§17(训练侧,源码不在 inference 仓库)
Hyper-Connections§10Block.hc_pre / hc_post + hc_attn_fn / hc_ffn_fn
sqrtsoftplus + noaux_tc§7Gate.forward
前 3 层 hash§8self.hash = layer_id < args.n_hash_layers
Sinkhorn 归一化§10.3hc_split_sinkhorn(...)

读到这里,你应该已经能感受到一件事——V4 的所有”奇怪”配置背后,都有一个工程上的现实约束。它不是炫技,是在 1.6T + 1M + 平价这三个约束下,被算力和精度同时压出来的解。


1.6 V4 vs V3:架构差异对照表

把 V3 和 V4 的核心配置摆在一起,差异跃然纸上:

维度V3 (671B / 37B)V4 Pro (1.6T / 49B)差异性质
总层数6161 + 1 (MTP)持平
Hidden size71687168持平
Attention head 数128128持平
Head dim192 (nope 128 + rope 64)512 (nope 448 + rope 64)重构
KV latent rankkv_lora_rank=512取消,用 head_dim=512 的滑窗 + 压缩 KV重构
Q LoRA rank15361536持平
O LoRA rank1024 (grouped, o_groups=16)新增
Routed experts256384容量 +50%
Top-k experts86稀疏性 ↑
Hash routing前 3 层使用 hash 路由新增
MoE 中间维度20483072容量 +50%
Scoring functionsigmoidsqrtsoftplus换型
Topk methodnoaux_tcnoaux_tc持平
Expert 权重精度FP8 e4m3FP4 e2m1重构
Linear 权重精度FP8 e4m3FP8 e4m3持平
Scale 格式ue8m0ue8m0持平
Attention sparsitydense滑窗 (128) + Compressor (per-layer ratio) + Indexer top-1024重构
Compress ratios per layer[128, 128, 4, 128, 4, ...](绝大多数 4,少量 128 / 0)新增
Index top-k1024新增
残差identity 残差Hyper-Connections (hc_mult=4) + 20 步 Sinkhorn 归一化重构
MTP layers11持平
最大上下文128K → 1M (yarn)1M (yarn factor=16)持平
RoPE theta1000010000 + compress_rope_theta=160000(专门给压缩 KV 用的)双 RoPE
优化器AdamWMuon换型
预训练 token 数14.8T32T++116%

差异性质三个标签:

  • 重构:核心数据结构换了——KV、Attention、残差、Expert 精度
  • 新增:增加了上一代没有的子系统——hash 路由、Indexer、grouped O、Hyper-Connections
  • 持平:保留 V3 已经成熟的设计——layer 数、hidden size、head 数、MTP

V4 真正的工程量集中在那 6 个”重构”和 4 个”新增”上。


1.6·补 V4 的三种推理模式:Non-Think / Think High / Think Max

V4 在 README 中显式列出了三种推理模式,这一点是 V3 / V3.2 都没有的。这三种模式不是后处理选项,而是chat template 与 system prompt 的差别——在 encoding/encoding_dsv4.pyencode_messages(messages, thinking_mode=...) 中触发。

flowchart LR
  Input["用户 messages"]
  Mode{thinking_mode}
  NT["Non-Think<br/>thinking_mode='direct'"]
  TH["Think High<br/>thinking_mode='thinking'"]
  TM["Think Max<br/>thinking_mode='max' + 特殊 system prompt"]

  Input --> Mode
  Mode -->|快速直观任务| NT
  Mode -->|逻辑分析任务| TH
  Mode -->|推理边界探索| TM

  NT --> NTOut["输出: '</think>' 标签 + 总结"]
  TH --> THOut["输出: '<think>' 思考过程 '</think>' 总结"]
  TM --> TMOut["输出: 加长 think + 完整推理链"]

Non-Think 模式:模型直接给答案,类似 GPT-4 的常规模式。token 输出最短、首 token 延迟最低,适合日常聊天、检索、摘要。

Think High 模式:模型先生成一段 <think>...</think> 内的思考链,再给最终答案。这段思考链不是事后生成的”解释”,而是实际驱动答案的推理过程——本质上是 R1 路线的内化。

Think Max 模式:通过特殊 system prompt 解锁,模型会持续推理直到接近 384K token 的”思考上限”。这种模式的官方推荐场景是”探索模型的推理边界”——通常用于评测、研究、复杂数学/编程问题。

V4 把”推理形态”作为一等公民暴露给用户的设计决策,反过来影响了训练数据评估闭环——第 18 章会讲到,V4 的后训练阶段是按”领域 + 思考形态”的笛卡尔积来组织 SFT/RL 数据的。


1.7 一段源码对比:V3 vs V4 的 Attention.init

如果你之前读过 V3 的源码,会对比出来 V4 在 Attention 这个最核心的类上做了什么——

V3 的 Attention.__init__(简化):

# V3 (deepseek-v3, inference/model.py)
self.wq_a = Linear(self.dim, self.q_lora_rank)
self.q_norm = RMSNorm(self.q_lora_rank)
self.wq_b = ColumnParallelLinear(self.q_lora_rank, n_heads * (qk_nope + qk_rope))

self.wkv_a = Linear(self.dim, self.kv_lora_rank + qk_rope)   # ◀── KV 也用 LoRA
self.kv_norm = RMSNorm(self.kv_lora_rank)
self.wkv_b = ColumnParallelLinear(self.kv_lora_rank, n_heads * (qk_nope + v_dim))

self.wo = RowParallelLinear(n_heads * v_dim, self.dim)        # ◀── O 是单矩阵

# KV cache: 每个 head 都存
self.register_buffer("kv_cache", torch.zeros(max_bsz, max_seq, n_heads, ...))

V4 的 Attention.__init__inference/model.py:Attention 类):

# V4 (deepseek-v4, inference/model.py)
self.attn_sink = nn.Parameter(torch.empty(self.n_local_heads, dtype=torch.float32))  # ◀── 新增:注意力 sink
self.wq_a = Linear(self.dim, self.q_lora_rank)
self.q_norm = RMSNorm(self.q_lora_rank, self.eps)
self.wq_b = ColumnParallelLinear(self.q_lora_rank, self.n_heads * self.head_dim)

self.wkv = Linear(self.dim, self.head_dim)                    # ◀── KV 不再是 LoRA:单矩阵投到 head_dim=512
self.kv_norm = RMSNorm(self.head_dim, self.eps)

# O 投影改为 grouped low-rank
self.wo_a = ColumnParallelLinear(self.n_heads * self.head_dim // self.n_groups,
                                  self.n_groups * args.o_lora_rank, dtype=torch.bfloat16)
self.wo_b = RowParallelLinear(self.n_groups * args.o_lora_rank, self.dim)

if self.compress_ratio:                                       # ◀── 新增:每层可独立配置压缩比
    self.compressor = Compressor(args, self.compress_ratio, self.head_dim)
    if self.compress_ratio == 4:
        self.indexer = Indexer(args, self.compress_ratio)     # ◀── 新增:稀疏 score net

# KV cache 几何变了:滑窗 + 压缩区
kv_cache_size = args.window_size + (args.max_seq_len // self.compress_ratio if self.compress_ratio else 0)
self.register_buffer("kv_cache", torch.zeros(args.max_batch_size, kv_cache_size, self.head_dim))

把这两段并排读,V4 的工程意图就完全暴露了:

  1. KV LoRA 被抹掉——V4 不再相信”潜在低秩 KV”是最优方案。它选了”head_dim=512 的全维 KV + 极强压缩 + 滑窗 + 稀疏选取”
  2. O 投影变 grouped low-rank——和 LoRA 微调里的 grouped LoRA 异曲同工,省参省算
  3. 稀疏化是层级可配的——compress_ratios[layer_id] 决定本层是 4 倍压缩或 128 倍压缩(dense / ratio=0 仅 MTP 层 1 处)
  4. Indexer 只在 compress_ratio == 4 的层上启用——稀疏路由不是每层都需要

每一条都在说:“V3 那条 KV 路径走到尽头了,我们换。“


1.8 V4 Pro / V4 Flash:同一架构的两个尺寸

V4 同时发布了两款:

模型总参数激活参数主要用途
V4 Pro1.6T49B旗舰能力,对标 Claude Opus / GPT-5.5
V4 Flash284B13B性价比线,对标 Gemini 3.1 Pro / GPT-5.4-mini

两者同源——共享 inference/model.py、共享 config_class、共享所有 kernel。差异只在 config.json

维度ProFlash
n_routed_experts384~256(推测)
moe_inter_dim3072较小
n_layers61较少
总参1.6T284B
激活参49B13B

它们的关系类似 Mixtral 8x22B 之于 Mixtral 8x7B,或 Llama 3.1 70B 之于 8B——同一份代码,两套权重。这本书的源码分析对两者通用,区别只在配置数字。


1.8·补 32T 训练 token 之谜与两阶段后训练

V4 的 README 第二段写着:

Pre-training: 32T+ high-quality and diverse tokens.

这一行话相对前作的 V3(14.8T)翻了一倍多。为什么 V4 必须吃这么多 token?答案藏在 V4 的几处架构决策里:

  1. 384 个 expert + top-6 激活,意味着每个 expert 见到的 token 比例下降——V3 是 256 expert top-8(每个 expert 平均看到 8/256 = 3.1% 的 token),V4 是 384 top-6(看到 6/384 = 1.6%)。把每个 expert 的有效训练量保持在与 V3 相当的水平,整体 token 数差不多要翻倍。
  2. Hyper-Connections 让每层有效”路径”翻 4 倍——HC 通过 hc_mult 维持 4 路 hidden state,意味着模型的有效参数容量比”乘以 4”更接近真相。更大容量需要更多 token 才能填充。
  3. 从 BF16 训练降到 FP4 expert 训练,每一次梯度更新的有效精度更低——更低的精度需要更长的训练曲线来补偿,这是 FP4 训练栈的一个隐性成本。

至于 32T 数据从哪里来,README 并没有公开。但从 V4 的能力分布反推可以猜出几个大类:

  • 大量代码(特别是仓库级代码,README 显示 V4 在 SWE-bench 类基准上的进步)
  • 大量数学/推理(Think Max 模式的存在表明数据里有大量”推理过程”标注)
  • 大量长文档(1M context 的稳定性必须靠真实长文档训练,而不是把短文档强行拼接)

两阶段后训练这个公开细节同样耐人寻味。READMR 写:

Post-training: 1) Domain-specific expert cultivation independently with SFT + RL (GRPO). 2) On-policy distillation into a single unified model.

第一阶段:“领域专家独立培养”——意思是先用 SFT + GRPO(V3.2 用过的强化学习算法)训练几个领域专家模型(比如 V4-Code、V4-Math、V4-Long-Context 等),每个领域单独优化。第二阶段:“on-policy 蒸馏到统一模型”——再把这些领域专家的能力蒸馏回一个统一的 V4。

这个 pipeline 类似 R1 的”先长链推理 → 蒸馏到 chat 模型”路线,但 V4 把它放到了多领域多推理模式的笛卡尔积上。第 18 章会用整章篇幅拆这个 pipeline 的工程实现——它解决的是”384 专家如何让每个 expert 都学到有差异化的能力”这个 V3 时代未完全解决的问题。


1.9 V4 在开源版图里的位置

把 V4 放在 2026 年 4 月的开源大模型版图里看:

模型总参 / 激活参上下文关键特征开源协议
DeepSeek-V4-Pro1.6T / 49B1MHC + 稀疏注意力 + FP4 expertsMIT
Qwen3-MoE-Max~700B / 35B256Kdense MLA + FP8Apache
Llama 4 Behemoth~2T / ~80B1MFlashAttention v3 + denseLlama Community
Mistral Magnum~480B / 22B128Kdense + grouped queryApache
Gemma 3.570B / 70B1Mdense + sliding windowGemma

V4 的两点独特性:

  1. 唯一在 1M context 下做到稀疏 + 低精度全栈的开源模型
  2. 唯一把 expert 权重压到 FP4 并在 1.6T 规模上验证收敛的开源模型

这两点决定了:

  • 想跑 1M 长文 + 极致 token 经济性的项目,V4 几乎没有替代
  • 想理解未来 18 个月开源 LLM 走向(稀疏 + FP4 + HC)的工程师,V4 是唯一可以下载到本地拆开看的样本

1.9·补 端到端 inference:一段 prompt 的完整旅程

为了让”V4 怎么跑一个推理”具象起来,我们 trace 一个 4 token 的 prompt 在 V4 Pro 里的完整旅程。假设输入是”Hello, world.”:

步骤 1:tokenize

V4 复用 V3 的 BBPE 词表(vocab_size=129280)。Hello, world. 大约被切成 5 个 token:

[BOS, "Hello", ",", "world", "."]   ← input_ids

步骤 2:进入 Transformer.forward

start_pos=0(首次前向), input_ids 形状 [1, 5]

h = self.embed(input_ids)              # [1, 5, 7168]
h = h.unsqueeze(2).repeat(1, 1, 4, 1)  # [1, 5, 4, 7168]   ← HC 4 路展开

步骤 3:进入第 0 层 Block

第 0 层的 compress_ratio=128(来自 compress_ratios[0])——KV 高度压缩。

# hc_pre: 把 [1, 5, 4, 7168] 混成 [1, 5, 7168]
# 内部:mixes = F.linear(x, hc_attn_fn) * rsqrt
#       pre, post, comb = hc_split_sinkhorn(mixes, ...)
#       y = sum(pre * x, dim=2)
# 输出 x: [1, 5, 7168], post: [1, 5, 4], comb: [1, 5, 4, 4]

x = layer.attn_norm(x)                 # RMSNorm
# Attention.forward(x, start_pos=0)
#   q = wq_b(q_norm(wq_a(x)))           # [1, 5, n_heads, 512]
#   apply_rotary_emb(q[..., -64:], freqs_cis)
#   kv = kv_norm(wkv(x))                # [1, 5, 512]
#   apply_rotary_emb(kv[..., -64:], freqs_cis)
#   act_quant(kv[..., :-64], 64, ...)   # FP8 量化非 rope 部分
#   topk_idxs = get_window_topk_idxs(...)   # 滑窗 top-k
#   # 第 0 层 compress_ratio=128,走 Compressor 但没有 Indexer(只有 ratio==4 才有)
#   compress_topk_idxs = get_compress_topk_idxs(128, ...)
#   topk_idxs = cat([window, compress])
#   # KV cache 写入 + sparse_attn 计算
#   o = sparse_attn(q, kv, attn_sink, topk_idxs, softmax_scale)
#   apply_rotary_emb(o[..., -64:], freqs_cis, inverse=True)
#   o = o.view(bsz, seqlen, n_groups, -1)
#   o = einsum("bsgd,grd->bsgr", o, wo_a.weight.view(...))
#   x = wo_b(o.flatten(2))
# 输出 x: [1, 5, 7168]

# hc_post: x + 4 路 residual → 4 路输出
h = layer.hc_post(x, h, post, comb)    # [1, 5, 4, 7168]

# 再来一遍:hc_pre → ffn_norm → MoE → hc_post
# MoE.forward(x, input_ids)
#   weights, indices = gate(x, input_ids)   # 第 0 层 hash=True,indices = tid2eid[input_ids]
#   # 找到本 rank 持有的 expert,逐个执行
#   y += expert(x_subset, weights_subset)
#   y += shared_expert(x)
#   all_reduce(y)  # 跨 TP rank 求和
# 输出 x: [1, 5, 7168]
h = layer.hc_post(x, h, post, comb)    # [1, 5, 4, 7168]

步骤 4:穿过剩下 60 层 + 1 个 MTP 层

每层重复上述过程,但 compress_ratio 在 4 / 128 / 0 之间切换:

  • ratio=4 的层:Indexer 启用,做”学习型稀疏选取”
  • ratio=128 的层:用固定步长压缩,不学
  • ratio=0 的层:不压缩,全 KV 滑窗(V4 中仅 MTP 层是这种配置,主模型 61 层都是 ratio=4 或 ratio=128)

第 4-60 层的 Gate 是 hash=False,indices 由 scores.topk(6) 决定。

步骤 5:head 输出

logits = self.head(h, hc_head_fn, hc_head_scale, hc_head_base, self.norm)
# 内部:x = hc_head(h, ...)               # [1, 5, 7168]
#       logits = F.linear(x[:, -1].float(), self.weight)  # 只取最后一个 token
# 输出: logits: [1, 129280]

步骤 6:采样下一个 token

V4 默认采样参数:temperature=1.0, top_p=1.0。这两个 1.0 很关键——它们意味着 V4 的 logits 已经是”sharp 到不需要额外调温”的状态,这是 sqrtsoftplus + Muon 训练栈的副产品。

步骤 7:进入 decode(增量推理)

下一次 forward 时,input_ids 形状变成 [1, 1](只送新 token),start_pos=5。Compressor 走”decode 增量分支”——它不再重算整段 KV,而是把新来的 KV 累积到 kv_state 里,每 ratio 个 token 触发一次”压缩落盘”。

这就是 V4 的全部前向流程。从外面看,它和任何 transformer 一样——输入 token、输出 logits。从内部看,它在 61 层里把”窗口 / 压缩 / 稀疏 / 4 路 HC / 384 专家 / hash 路由 / FP4 解量化 / FP8 GEMM”全部走了一遍。


1.9·补·补 README 三组数字背后的工程账

V4 的 README 在显眼位置摆了三组”营销数字”,但每一组数字背后都有源码可以印证的工程账。把它们逐一拆开:

数字一:单 token 推理 FLOPs = V3.2 的 27%

这条数字的分母不是 dense Llama,而是 V3.2 的混合 dense+sparse。V4 把它砍到 27%,主要靠三件事:

  1. KV 不再是”每 token 一组 latent”,而是”每 ratio 个 token 才存一组压缩 KV”——这一项就把 KV-side 的 attention FLOPs 砍到 1/ratio
  2. 滑窗 + 稀疏 top-k 替代了 V3.2 后期版本的 dense+sparse 双路,移除了 dense 路径的 O(n²) 部分
  3. grouped O 投影(o_groups=16)把 O 矩阵的乘法量减少了一档

如果只看前两项,理论上 1M context 应该能砍到 V3.2 的 5-10%。27% 这个数字反映的是FP4 解量化、HC 4 路混合、Sinkhorn 归一化这些”V4 才引入的额外开销”把节省吃回去了一部分。

数字二:KV cache = V3.2 的 10%

V3.2 的 KV cache 公式(为简化起见忽略 head 维度差异):

KVV3.2=L×ntok×dkv_lora×2(dense MLA)\text{KV}_{V3.2} = L \times n_\text{tok} \times d_\text{kv\_lora} \times 2 \quad\text{(dense MLA)}

V4 的 KV cache 公式(以 ratio=4 的层为例):

KVV4=L×(window_size+ntokratio)×dhead\text{KV}_{V4} = L \times \left(\text{window\_size} + \frac{n_\text{tok}}{\text{ratio}}\right) \times d_\text{head}

代入 ratio 平均接近 16(混合 4 / 128 / 0),window=128,head_dim=512,对比 V3.2 的 kv_lora_rank=512——

在 1M context 下,n_tok=1048576:

  • V3.2 KV ≈ 1048576 × 512 × 2 = 1.07 GB / layer
  • V4 KV ≈ (128 + 1048576/16) × 512 ≈ 34 MB / layer(取平均 ratio)

按 61 层算,V3.2 是 65 GB / 序列,V4 是 2 GB / 序列。源码里的 kv_cache_size = window_size + max_seq_len // compress_ratio 一行就是这个公式的承载者。

数字三:V4 Pro decode 单价 ≈ V3.2 的 1/3

价格不是单一架构因素决定,而是”FLOPs ↓ × 显存 ↓ × 训练成本 ↓ × 利用率 ↑“的乘积:

  • FLOPs 降低 → 单卡 throughput 提升
  • KV 降低 → 单卡并发提升
  • FP4 expert + Muon 训练 → 单 token 训练成本下降,可以让定价进一步压低
  • 384 专家 + top-6 → 同等容量下激活更稀疏,理论上 throughput 更高

把这些乘起来,三分之一这个倍数其实是保守估计。如果 batch 足够大、并发足够高,单价可以进一步压低——这是 V4 Pro 在长上下文场景下”价格倍率优势”会随 batch 越大而越夸张的根源。


1.9·补·补 实战选型矩阵:什么场景必须选 V4

把 V4 与 2026 年的几个主流开源 LLM 放在工程选型场景下做对比。这张矩阵不是 leaderboard,而是**“什么样的工程约束下 V4 是首选”**:

场景上下文长度价格敏感度部署规模首选模型V4 在此场景的相对优势
长法律文档分析200K-1M单机 8 卡V4 Pro / V4 Flash1M 上下文 + 稀疏 KV 直接吊打 dense MLA
仓库级代码理解100K-500K集群V4 Pro1M 上下文 + Think High 推理形态
通用聊天助手<32K单卡Qwen3-32B / Mistral / V4 FlashV4 Flash 价格优势但模型选择多
强推理(数学/竞赛编程)<128K单机 8 卡V4 Pro Think MaxThink Max 模式对推理边界的探索
实时低延迟 API<8K极高大集群Qwen3-7B / Llama 4 ScoutV4 Pro 单卡占用太高、Flash 也比 7B 大
私有部署敏感行业<128K集群V4 Pro / FlashMIT 许可、可下载完整权重
多模态(图文)<128K集群Qwen3-VL / Llama 4 MaverickV4 Pro 当前不含视觉编码器
端侧 / 边缘<8K极高端侧Gemma 3 / Qwen3-1.7BV4 当前最小尺寸 Flash 仍需多卡

V4 的”工程甜区”非常清晰:大上下文 + 价格敏感 + 可下载权重 + 通用文本这四个条件同时成立时,V4 几乎没有竞争对手。

反过来,V4 不擅长:

  • 端侧/超低延迟——尺寸还是太大
  • 多模态——当前 V4 是纯文本
  • 极小上下文聊天——容易被同价位的 dense 7B/13B 模型在响应速度上反超

理解 V4 的”擅长 / 不擅长”需要回到架构本质——它的所有创新都为”长上下文 + 大容量 + 平价”服务。如果场景对这三件事都不敏感,V4 的工程红利就发挥不出来。


1.10 一张图:本书的章节路标

把全书 20 章映射到 V4 架构上:

flowchart LR
  subgraph 源码["inference/model.py"]
    direction TB
    Embed["ParallelEmbedding"]:::ch15
    HCexp["HC unsqueeze+repeat"]:::ch10
    Block["Block × 61"]:::block
    HCpre["hc_pre + Sinkhorn"]:::ch10
    Attn["Attention<br/>(MLA + Compressor<br/>+ Indexer + sparse_attn)"]:::attn
    HCpost["hc_post"]:::ch10
    MoE["Gate + 384 experts<br/>+ Hash 前 3 层"]:::moe
    Head["ParallelHead + hc_head"]:::ch10
    MTP["MTPBlock × 1"]:::ch11
  end

  subgraph 章节["章节"]
    Ch1["第 1 章 全景"]
    Ch2["第 2 章 MLA 进阶"]
    Ch3["第 3 章 Compressor"]
    Ch4["第 4 章 Indexer"]
    Ch5["第 5 章 sparse_attn 内核"]
    Ch6["第 6 章 YaRN 1M"]
    Ch7["第 7 章 Gate"]
    Ch8["第 8 章 Hash 路由"]
    Ch9["第 9 章 Expert"]
    Ch10["第 10 章 HC"]
    Ch11["第 11 章 MTP"]
    Ch12["第 12-14 章 FP4/FP8/QAT"]
    Ch15["第 15-16 章 分布式"]
    Ch17["第 17-18 章 训练"]
    Ch19["第 19-20 章 部署 / 生态"]
  end

  classDef ch15 fill:#0f172a,stroke:#3b82f6,color:#dbeafe;
  classDef ch10 fill:#312e81,stroke:#a78bfa,color:#ede9fe;
  classDef block fill:#1f2937,stroke:#475569,color:#e2e8f0;
  classDef attn fill:#581c87,stroke:#c084fc,color:#fae8ff;
  classDef moe fill:#7c2d12,stroke:#fb923c,color:#ffedd5;
  classDef ch11 fill:#365314,stroke:#84cc16,color:#ecfccb;

1.11 config.json 字段全解读

V4 的 config.json 一共 30 多个字段,但每一个都是前面四代模型踩过坑总结出来的。我们按”架构 / 注意力 / MoE / 精度 / 训练”五组来读:

1.11.1 架构骨架

字段V4 取值含义
architectures["DeepseekV4ForCausalLM"]HF transformers 加载时使用的类名,也是这次架构升级的”门牌号”
model_typedeepseek_v4transformers/AutoConfig 里独立注册
num_hidden_layers61主 Transformer 层数(与 V3 持平)
hidden_size7168模型维度,与 V3 持平
vocab_size129280与 V3 持平。tokenizer 也复用 V3 的 BBPE
tie_word_embeddingsfalseembedding 与 lm_head 不共享权重——大模型上共享会拖累训练
transformers_version4.57.1该模型卡是用 4.57.1 的 transformers 测过的

1.11.2 注意力与稀疏化

字段V4 取值含义
num_attention_heads128每层 Q head 数
head_dim512单 head 维度(V3 是 192 + 64=256;V4 几乎翻倍)
num_key_value_heads1KV head 只有 1 个——配合 head_dim 512 实现”近似单 KV”
q_lora_rank1536Q 的 LoRA 中间维度(V3 同)
o_lora_rank1024新增:O 的 grouped LoRA rank
o_groups16新增:把 O 投影分成 16 组,每组独立低秩
qk_rope_head_dim64RoPE 部分维度(与 V3 持平);non-rope 部分 = head_dim - 64 = 448
sliding_window128每层都跑一个滑动窗口注意力,叠在稀疏 KV 之上
compress_ratiosper-layer每层 KV 压缩倍率(4 / 128 / 0),见下
index_n_heads64Indexer 的 head 数(与主 attn 不同)
index_head_dim128Indexer 的 head 维度
index_topk1024Indexer 选取的 top-k KV 位置数
compress_rope_theta160000新增:压缩 KV 走的 RoPE 频率基数(比主 rope_theta 高 16 倍)
rope_theta10000滑窗 KV 走的 RoPE 频率基数
rope_scaling.typeyarnYaRN 频率插值
rope_scaling.factor16把 65K 上下文外推到 1M:16 倍
rope_scaling.original_max_position_embeddings65536YaRN 的”短上下文锚点”
rope_scaling.beta_fast32YaRN 高频段截止
rope_scaling.beta_slow1YaRN 低频段截止
max_position_embeddings10485761M token

compress_ratios 是个 62 元素的整数数组(61 主模型层 + 1 MTP 层),V4 Pro 的真实取值(config.json grep 结果)是:

[128, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4,
 128,   4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128,
   4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4,
 128,   4, 128, 4, 128, 4, 128, 4, 128, 4, 0]

读这串数字的方法:

  • 前 2 层 = 128:极端高压缩,KV 几乎不存,主要靠滑窗
  • 数值分布(grep 实测):128 出现 31 次,4 出现 30 次,0 出现 1 次
  • 后续大量 4 与 128 交替:4 倍压缩走 Indexer 稀疏选取;128 倍压缩当快速回忆通道
  • 第 3 层、第 5 层等”4”:是稀疏注意力真正在工作的层
  • 最后一个元素(index 61)= 0:这是 MTP 层(num_nextn_predict_layers=1),不是主模型最后一层。MTP 用完整 dense KV,可能是为 next-next 预测训练监督留无损视野(工程推断)。主模型最后一层(index 60)实测是 ratio=4,不是 0

这种 per-layer 的非均匀稀疏配置是 V4 跑通 1M context 的”关键调音”。第 3 章会反复回到这串数字。

1.11.3 MoE 与路由

字段V4 取值含义
n_routed_experts384路由专家数(V3=256)
n_shared_experts1共享专家数(与 V3 持平)
num_experts_per_tok6每 token 激活的 routed expert 数(V3=8,V4 更稀疏)
moe_intermediate_size3072单 expert 的 FFN 中间维度
routed_scaling_factor2.5路由权重的最终缩放系数
scoring_funcsqrtsoftplusgate 评分函数(V3 是 sigmoid)
topk_methodnoaux_tcauxiliary-loss-free top-k 选取
norm_topk_probtrue选定 top-k 后做归一化
num_hash_layers3新增:前 3 层用 hash 路由
swiglu_limit10.0SwiGLU 门控值的 clip 上限——抑制极端激活

1.11.4 精度与量化

字段V4 取值含义
expert_dtypefp4新增:expert 权重压到 FP4 e2m1
quantization_config.fmte4m3非 expert 权重的 FP8 格式
quantization_config.scale_fmtue8m0scale tensor 的格式
quantization_config.weight_block_size[128, 128]FP8 块状量化的块大小
quantization_config.activation_schemedynamic激活值在每次 forward 时重新算 scale
torch_dtypebfloat16反量化后的工作 dtype
rms_norm_eps1e-6RMSNorm 的 epsilon
hc_eps1e-6Hyper-Connections 的 epsilon
hc_mult4新增:HC 复制份数
hc_sinkhorn_iters20新增:HC 内部 Sinkhorn 迭代次数
num_nextn_predict_layers1MTP 层数

到这里,config.json 里几乎每一个字段都对应到了某个本书后续章节会展开的子主题。把这张表当作”配置文件 → 章节”的双向索引表,可以反复回查。


1.12 V4 Pro 与 V4 Flash:同源不同尺寸

V4 Pro 与 V4 Flash 共享所有 kernel、共享 inference/model.py,但 config.json 在以下字段上不同:

字段V4 ProV4 Flash(推测,待 HF 仓库确认)影响
总参数1.6T284B内存占用差 5.6 倍
激活参数49B13B单 token FLOPs 差 ~4 倍
n_routed_experts384~256容量差
moe_intermediate_size3072~2048单 expert 的 FFN 宽度
n_layers61~32深度差
head_dim512同 Pro持平
HC / Indexer / 滑窗同 Pro同 Pro持平

为什么 Flash 也要 1M 上下文?——因为 V4 Pro 与 V4 Flash 跑同一份 inference/model.py,长文档能力是架构带来的、不是参数量带来的。Flash 的 1.6T → 284B 缩小,主要走的是”层数 + expert 数 + FFN 宽度”三个维度的同比例下调。

这也是 V4 的工程美学之一——核心创新是架构级的,所以 Flash 与 Pro 一起免费拿到了 1M 上下文与稀疏注意力的红利,不需要为每个尺寸重新做工程。


1.12·补 V4 的工程美学:四条潜规则

读完 V4 的 inference/model.py 800 行源码,会发现作者团队在工程上有四条非常一致的”潜规则”。这些潜规则没写进任何文档,但它们决定了源码的可读性可演化性

潜规则一:所有”看似可以隐藏的东西”都暴露在表面

V4 没有用 abstract base class 包装 Attention / MoE / MLP,没有用工厂函数返回不同变体的 Linear,也没有用 hooks 做 dynamic dispatch。所有的分支选择都是 if compress_ratio:if dtype == torch.float4_e2m1fn_x2: 这种明面上的判断。读者第一次读源码时,可以用 ctrl+F 找到任意一个变量的所有使用点——这是”工程的诚实”。

潜规则二:所有 dtype 转换都显式写在调用点

V4 的源码里反复出现 x = x.float()y.type_as(x)x.to(dtype) 这种显式 dtype 切换。它的好处是任何一段代码运行在什么 dtype 上都可以一眼看清——这对 FP4/FP8/BF16 三种精度并存的代码至关重要。

潜规则三:状态外置

Compressorkv_state / score_state 都用 register_buffer(persistent=False) 显式外置。Attentionkv_cache 也是同样。这意味着:模型的”状态”和”参数”被严格分开——参数是存盘 / 量化 / 分布式同步的对象,状态是运行时局部的 buffer。这种分离让 V4 在做 quantization-aware training 时不会把状态错误量化、做 KV cache offloading 时不会污染参数。

潜规则四:world_size / rank 是”全局可变状态”

文件顶部三行:

world_size = 1
rank = 0
block_size = 128

不是常量,是全局可变变量——Transformer.__init__ 里有 global world_size, rank, default_dtype, ...。这个写法在工程审美上有争议(违反了”可变全局变量是反模式”的传统教条),但它带来的好处是:整个模型的并行配置在一个地方设置后,每个 module 不需要再传 world_size/rank——大幅简化了 ParallelEmbedding / ColumnParallelLinear 等类的接口。

这四条潜规则不是 V4 独有的,但它们在 V4 源码里贯彻得异常彻底。如果你之前读 V2/V3 的源码会觉得”有点乱”,V4 的源码读起来会有”刀切豆腐”的清爽感——这是工程团队四代积累后的功底。


1.13 一段 forward 的张量形状追踪

Transformer.forwardB=2, S=128 的预填充阶段一步步 trace 一遍——

input_ids: [2, 128]   # B=2, S=128

# 1. embedding
h = self.embed(input_ids)              # [2, 128, 7168]

# 2. HC expand
h = h.unsqueeze(2).repeat(1, 1, 4, 1)  # [2, 128, 4, 7168]   ← 增加了 hc_mult 维度

# 3. 进入第一层 Block.forward
for layer in self.layers:
    # hc_pre:把 4 路混成 1 路
    x, post, comb = layer.hc_pre(h, ...)         # x: [2, 128, 7168]
                                                 # post: [2, 128, 4]
                                                 # comb: [2, 128, 4, 4]
    # RMSNorm
    x = layer.attn_norm(x)                       # [2, 128, 7168]
    # Attention
    x = layer.attn(x, start_pos=0)               # [2, 128, 7168]
    # hc_post:1 路 + 残差 4 路 → 4 路
    h = layer.hc_post(x, h, post, comb)          # [2, 128, 4, 7168]
    # 再来一遍 hc_pre/hc_post 包住 MoE
    x, post, comb = layer.hc_pre(h, ...)
    x = layer.ffn_norm(x)
    x = layer.ffn(x, input_ids)                  # [2, 128, 7168]
    h = layer.hc_post(x, h, post, comb)          # [2, 128, 4, 7168]

# 4. 最后的 head
logits = self.head(h, ...)                      # [2, vocab_size]

注意点:

  • h 在主循环里始终保持 4 维(B, S, hc_mult, D);只有进 attention / ffn 的瞬间才被压成 3 维
  • hc_pre 输出的 post(每个 token 的”输出权重”)和 comb(每个 token 的”混合矩阵”)是 HC 数学的核心——第 10 章会展开
  • 进入 attnx 形状是 [B, S, D],里面经过 MLA、Compressor、Indexer、sparse_attn 之后输出回 [B, S, D]——这部分内部状态由 Attention 自己管
  • ffn 输入 x: [B, S, D] + input_ids: [B, S]——input_ids 是 hash 路由要用的(前 3 层用它直接查表选 expert)

把这段 trace 牢记在脑里,后面任何一章谈”这个变量来自哪里、要传到哪里”都不会再迷路。


1.13·补 V4 专属术语速查表

V4 源码与本书引入了一批”V4 专属术语”,这些术语在前作 V2 / V3 / V3.2 中要么没有、要么含义不同。第一次读到容易混淆,建议在阅读后续章节时随时回查这张表:

  • HC(Hyper-Connections):替代传统残差的 4 路 hidden state 混合机制。每个 token 在每层带 4 份 hidden state,通过学习的混合矩阵(hc_attn_fn / hc_ffn_fn)和 Sinkhorn 归一化在层间流动。来源:Block.hc_pre / hc_post
  • hc_mult:HC 的复制份数。V4 设为 4。
  • Sinkhorn 迭代:HC 内部用来把混合矩阵变成”接近双随机矩阵”的迭代算法。V4 的 hc_sinkhorn_iters=20。
  • Compressor:把每 compress_ratio 个 token 的 KV 通过学习的 gated pooling 压成一组的模块。V4 中每层(compress_ratio>0 时)都有自己的 Compressor。
  • Indexer:稀疏注意力的 score net,给压缩 KV 打分并选 top-k。V4 中只有 compress_ratio=4 的层启用。
  • compress_ratio:每层独立配置,决定 KV 是 4 倍压缩、128 倍压缩,还是不压缩(0)。
  • sliding window (window_size=128):每层都跑的滑动窗口注意力。是 V4 KV cache 的一部分。
  • noaux_tc:auxiliary-loss-free top-k 选取。继承自 V3。
  • sqrtsoftplus:V4 新引入的 gate scoring 函数:scores = F.softplus(x).sqrt()
  • routed_scaling_factor:routed expert 的最终输出缩放系数(V4=2.5)。
  • hash routing:前 3 层(n_hash_layers=3)使用的预定路由——expert index 直接由 token id 查 tid2eid 表得到。
  • MTP:Multi-Token Prediction。V4 保留 1 个 MTPBlock。
  • YaRN:RoPE 频率插值的长上下文外推方案。V4 的 factor=16,把 65K 训练上下文外推到 1M。
  • expert_dtype=“fp4”:expert 权重的 FP4 e2m1 量化。
  • ue8m0:纯指数 8-bit 浮点格式,V4 用于 scale tensor。
  • block_size=128:FP8 的 per-block scale 块大小。
  • fp4_block_size=32:FP4 的 per-block scale 块大小。
  • rotate_activation:随机 Hadamard 变换,FP4 量化前对激活做一次”信息打散”。
  • attn_sink:每个 attention head 一个 float32 的”注意力汇”参数,给稀疏注意力填补”找不到合适 KV 时”的兜底。
  • grouped O:把 attention 的输出投影分成 16 组,每组独立做低秩投影。
  • kv_state / score_state:Compressor 在 decode 阶段累积 KV 用的 buffer。
  • act_quant:把激活值动态量化到 FP8 e4m3 + ue8m0 scale。
  • fp4_act_quant:把激活值动态量化到 FP4 e2m1。
  • fp8_gemm / fp4_gemm:FP8 / FP4 矩阵乘法 kernel(在 kernel.py 中实现,依赖 DeepGEMM)。
  • sparse_attn:V4 的稀疏注意力 kernel(在 kernel.py 中实现,依赖 FlashMLA)。
  • n_routed_experts / n_shared_experts:路由专家数 / 共享专家数。V4 是 384 / 1。
  • n_activated_experts:每 token 激活的 routed expert 数。V4 是 6。
  • q_lora_rank / o_lora_rank:Q 和 O 投影的 LoRA 中间维度。V4 分别是 1536 / 1024。
  • o_groups:grouped O 投影的组数。V4 是 16。
  • freqs_cis:预计算的 RoPE 复指数张量,每层注意力共享。
  • window_size=128 vs sliding_window=128:源码中前者出现在 ModelArgs,后者出现在 config.json,是同一个值的两种命名——加载时映射。

记住这 30 个术语,本书剩余 19 章读起来会顺得多。


1.14 动手实验:先把架构对话起来

在不下载 865 GB 权重的前提下,可以做以下两个动手实验来”摸到” V4 架构:

实验 A:用随机权重把 Transformer 跑通(最低 16 GB CPU 内存即可)

# 注意:这只跑前向传播验证形状,不会得到有意义的输出
import torch
from inference.model import Transformer, ModelArgs

torch.set_default_dtype(torch.bfloat16)
torch.set_default_device("cpu")  # 单卡 CPU 先跑通形状

# 用一个袖珍配置(比 V4 Pro 小 100 倍以上)
args = ModelArgs(
    vocab_size=1024,
    dim=512,
    n_layers=2,
    n_heads=8,
    n_routed_experts=4,
    n_activated_experts=2,
    moe_inter_dim=1024,
    head_dim=128,
    rope_head_dim=32,
    max_seq_len=512,
    max_batch_size=2,
)
model = Transformer(args)

x = torch.randint(0, args.vocab_size, (1, 64))
out = model(x)
print(out.shape)   # 期望: torch.Size([1, vocab_size])

这个实验帮助你确认:你的环境能正确加载 V4 的 inference/model.py、能正确调用 kernel 里的 fp4_gemm / sparse_attn(在 CPU 上会走 fallback)。

实验 B:从 config.json 反推显存占用

import json
cfg = json.load(open("config.json"))

# 仅算 expert 部分
moe_params = cfg["num_hidden_layers"] * cfg["n_routed_experts"] \
             * cfg["moe_intermediate_size"] * cfg["hidden_size"] * 3
moe_bytes  = moe_params * 0.5    # FP4 e2m1 = 0.5 byte
print(f"Expert FP4 weights: {moe_params/1e9:.1f}B params, {moe_bytes/1e9:.1f} GB")

# 仅算 attention 部分(Q/KV/O LoRA)
qkvo_params_per_layer = (
    cfg["hidden_size"] * cfg["q_lora_rank"]                                       # wq_a
  + cfg["q_lora_rank"] * cfg["num_attention_heads"] * cfg["head_dim"]             # wq_b
  + cfg["hidden_size"] * cfg["head_dim"]                                          # wkv
  + cfg["num_attention_heads"] * cfg["head_dim"] // cfg["o_groups"]
    * cfg["o_groups"] * cfg["o_lora_rank"]                                        # wo_a
  + cfg["o_groups"] * cfg["o_lora_rank"] * cfg["hidden_size"]                     # wo_b
)
qkvo_total = qkvo_params_per_layer * cfg["num_hidden_layers"]
qkvo_bytes = qkvo_total * 1.0    # FP8 e4m3 = 1 byte
print(f"Attention FP8 weights: {qkvo_total/1e9:.1f}B params, {qkvo_bytes/1e9:.1f} GB")

跑完后把数字与官方 README 给的 1.6T / 49B 对照——你会发现 expert 部分大约占了 99% 的总参数。这就是 V4”把 expert 压 FP4、其他保 FP8”这个权衡的工程原因。


1.15 延伸阅读

权威一手资料(按重要性排序):

  1. DeepSeek-V4 技术报告——huggingface.co/deepseek-ai/DeepSeek-V4-Pro/blob/main/DeepSeek_V4.pdf —— 这本书所有章节都会反复引用,建议至少通读一遍
  2. DeepSeek-V3 Technical ReportarXiv:2412.19437)—— 理解 V4 必须先理解 V3
  3. DeepSeek-V2: A Strong, Economical, and Efficient MoE LMarXiv:2405.04434)—— MLA 和 DeepSeekMoE 的源头
  4. DeepSeekMoEarXiv:2401.06066)—— 细粒度专家 + 共享专家的论文起点
  5. YaRN: Efficient Context Window ExtensionarXiv:2309.00071)—— 第 6 章会用到
  6. FlashMLA 仓库——github.com/deepseek-ai/FlashMLA,第 5 章主参考
  7. DeepGEMM 仓库——github.com/deepseek-ai/DeepGEMM,第 13 章主参考
  8. Hyper-Connections 论文——第 10 章会引用,注意 V4 的实现与原论文有差异

二手但价值高的参考:

  • vLLM 官方 V4 适配 PR——第 19 章会拆这条 PR
  • Artificial Analysis 的 V4 评测页面(独立基准对比)

1.15·补 V4 源码行号速查表

为了让本书所有源码引用 grep 可验证,把 inference/model.py(HF revision deepseek-ai/DeepSeek-V4-Pro 当前 main 分支,文件总长 827 行)的全部类与顶级函数行号列在这里。读者可以用 curl -sL <raw_url> | head -N | tail -1 直接验证任何一行。

顶级函数

行号名称备注
25set_dtype@contextmanager 装饰
108lineardtype 分发到 fp4_gemm/fp8_gemm
200precompute_freqs_cisYaRN RoPE,@lru_cache(2)
232apply_rotary_embRoPE 旋转应用
247rotate_activationHadamard 变换
255get_window_topk_idxs滑窗 topk 索引
269get_compress_topk_idxs压缩段 topk 索引

类定义

行号类名关键内部成员
35ModelArgs@dataclass,所有超参
83ParallelEmbeddingTP-切分的 vocab embedding
123LinearFP4/FP8/BF16 三态权重容器
155ColumnParallelLinear切 out_features
166RowParallelLinear切 in_features + all_reduce
183RMSNormfloat32 归一化
279CompressorKV 压缩,含 prefill/decode 双 codepath
380Indexer稀疏注意力 score net
436AttentionMLA + Compressor + Indexer 集成
546Gatesqrtsoftplus / hash / noaux_tc
587ExpertSwiGLU FFN
609MoE384 expert + shared 调度
647BlockHC + attention + FFN 包裹
703ParallelHeadLM head + hc_head 处理
738MTPBlock继承 Block,加 e_proj/h_proj
769Transformer顶层模型

关键方法行号(章节里反复引用):

行号方法章节定位
86ParallelEmbedding.__init__§15.5
96ParallelEmbedding.forward§15.5
283Compressor.__init__§3.2
316Compressor.forward§3.3 / §3.4
384Indexer.__init__§4.2
402Indexer.forward§4.3
439Attention.__init__§2.2
484Attention.forward§2.6 / §2.7 / §2.8·补
459Attention 中的 self.wq_b§15 ColumnParallel 例
550Gate.__init__§7.2
562Gate 中的 self.bias§7.4
564Gate.forward§7.2 / §8.3
589Expert.__init__§9.2
596Expert.forward§9.2 / §9.3
612MoE.__init__§15.7
629MoE.forward§9.6 / §15.10
652Block.__init__§10.2 / §10.3
688Block.forward§10.2
740MTPBlock.__init__§11.2
757MTPBlock.forward§11.3
772Transformer.__init__§1.5 / §15.2
802Transformer.forward§1.5 / §1.13

验证方式

# 把整文件下载下来本地 grep
curl -sL https://huggingface.co/deepseek-ai/DeepSeek-V4-Pro/raw/main/inference/model.py -o model.py
wc -l model.py    # 应输出 827
grep -n "^class \|^def \|^    def " model.py    # 列出所有类与方法
sed -n '436,460p' model.py    # 看 Attention 类的前 25 行

如果未来 V4 GA 版本的源码改动让某些行号偏移,本表会在第二版中更新。读者发现行号不对请反馈。


1.16 本章小结

  • DeepSeek 的四代模型是一条清晰的工程主线:稀疏 + 长上下文 + 低精度,每代啃一段
  • V4 不是”换了几个模块”,而是同时重构了 KV、Attention、残差、Expert 精度四件大事
  • V4 真正的”反常识”全部摆在 Transformer.forward 那 4 行里——HC unsqueeze、Block 循环、HC head——这本书剩下的 19 章是把这 4 行展开
  • V4 与 V3 的差异表里,10 项里有 6 项标注”重构”——这就是为什么 V4 不能简单理解为”V3 的放大版”
  • config.json 的每一个字段都对应到本书某个章节——这是配置 → 源码 → 章节的三向索引

第 2 章我们进入注意力革命的第一站:MLA 在 V4 里到底变成了什么——head_dim 为什么从 192 跳到 512、grouped O 投影解决了什么问题、kv_lora_rank 为什么消失。

评论 0