DeepSeek V4 源码剖析

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

作者 杨艺韬 · 13,886 字 · 发布于 · 更新于

V2 (2024-05) → V3 (2024-12) → V3.2-Exp (2025-09) / V3.2 (2025-12) → V4 (2026-04)

你看到的 V4,不是一次发布。是四代模型、近两年时间,把"稀疏 + 长上下文 + 低精度"这条路一寸寸磨出来的结果。


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

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

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

  • V4 把残差换成 Hyper-Connections——README 把它列为三大升级之一,理由是"增强跨层信号传播的稳定性,同时保住模型表达力",指向的正是深层大模型残差路径上的老问题
  • V4 把 routed experts 从 256 加到 384、把 top-k 从 8 降到 6——沿着 DeepSeekMoE 论文"细粒度专家 + 共享专家"的那条线继续往更细的粒度推
  • V4 引入 Indexer 稀疏注意力,并把 KV 改造成 "Compressor + 滑窗"——是因为 V3.2-Exp 验证了 DSA(DeepSeek Sparse Attention)路线在 128K context 下的可行性
  • V4 把 expert 权重压到 FP4——是因为 V3 已在 671B 规模上跑通 FP8 混合精度训练,再要省显存只能向下走一步

把 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)
  2025-12 : V3.2 正式版<br/>DSA 转正(V4 技术报告的对比基线)
  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} 是序列长度。以 Llama-2-70B 的几何(80 层、8 个 KV head、head_dim=128、FP16)代进去,32K context 一条序列就要 2×80×8×128×32768×2 B≈10.72\times80\times8\times128\times32768\times2\ \text{B}\approx 10.7 GB——而如果不用 GQA、64 个 KV head 全存,同样的 32K 要 86 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/>V2 对比 DeepSeek 67B ↓ 93.3%"]
    KV_lat --> Wk_b["W_k_b"] --> K2["K"]
    KV_lat --> Wv_b["W_v_b"] --> V2["V"]
  end

DeepSeek-V2 论文给出的口径是:与上一代 dense 模型 DeepSeek 67B 相比,V2 的 KV cache 减少 93.3%;若按 V3 的几何与同构 MHA 比(每 token 每层 128 头 ×(K 192 维 + V 128 维)= 40960 个元素,对 MLA 的 576 个),约为 1/70。这是 V2 / V3 能以可接受的显存跑长文档推理的关键。

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

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

V2 之前常见的 MoE(如 Mixtral)每层只有 8 个粗粒度专家。DeepSeekMoE 论文 DeepSeekMoE: Towards Ultimate Expert Specialization in Mixture-of-Experts Language Models 提出两个改造:

  1. 细粒度专家:把 expert 数量一路加宽——DeepSeekMoE-16B 是 64、V2 是 160、V3 是 256、V4 是 384——并相应减小每个 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 及更早的 MoE 用的是传统 auxiliary loss(在 cross-entropy 之外加一个"专家使用均匀度"的辅助损失),但 aux loss 会干扰主任务的语义学习——专家被强行均匀化,可能损失某些极有价值的稀有专家。

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

  • 不再靠辅助损失做主要的负载均衡(只保留一个 α=0.0001 的极小序列级均衡损失,防止单条序列内极端失衡)
  • 给每个 expert 维护一个 bias term(V4 源码 inference/model.py:562 的 self.bias = nn.Parameter(torch.empty(args.n_routed_experts, dtype=torch.float32)),位于 Gate 类 __init__ 内)
  • bias 只在 topk 选取时被加入,不参与最终 routing weight
  • 训练过程中根据每个 expert 的"使用频率"动态调整 bias,使过载的 expert 被压低,欠载的 expert 被抬高

V4 继承了这套机制(连同那个 0.0001 的序列级均衡损失,见技术报告第 2.1、4.2.2 节),并在 topk_method="noaux_tc" 这个配置项中显式标注。

1.3.2 FP8 训练 + ue8m0 scale

V3 报告称自己"首次在超大规模模型上验证了 FP8 混合精度训练的有效性"(embedding、输出 head、MoE gate、归一化与注意力等敏感算子仍保留 BF16/FP32)。关键工程包括:

  • e4m3 用于全部 GEMM 张量——V3 报告特意点明:与"forward 用 e4m3、backward 用 e5m2"的常规做法不同,V3 在所有张量上统一用 e4m3,靠细粒度 scaling 而不是靠更大的指数位来扛动态范围
  • 2 的整数次幂 scale:V3 只对部分激活(注意力后 Linear 的输入、MoE dispatch 前的激活等)要求 scale 取 2 的整数次幂;V4 配置里的 scale_fmt: ue8m0(一种纯指数 8-bit 格式)把这一点变成了 scale 的存储格式
  • 细粒度 scaling:权重每 128 × 128 一块、激活每 1 × 128 一段各配一个 scale,平衡精度与显存

V4 的 config.json 里这一段,除 scale_fmt 外四个字段与 V3 的 config.json 完全一致(V3 的 quantization_config 没有 scale_fmt 字段):

"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 的 MTP 模块(V4 源码里叫 MTPBlock)。它带来三个好处:

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

V4 原样沿用了 V3 的 MTP 配置,同样只有 1 个 MTPBlock(num_nextn_predict_layers=1,V3 的 MTP 深度也是 1)。源码层面看:

# inference/model.py:789: 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 的 embed 和 head 直接复用主模型的 embedding 和 lm_head。这是个非常便宜的实现:MTP 只多了一组 transformer block 的参数(外加 e_proj / h_proj 两个投影、几个 RMSNorm 和自己的 hc_head 参数),embedding 和输出投影都是免费的。


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

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

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

  • 建在 MLA 的 MQA 模式上(每个 latent KV 被全部 query head 共享),KV cache 仍是每 token 一份
  • 通过一个轻量的 score net(lightning indexer)给每个 query 从全部前文里选 top-2048 个 KV
  • 稀疏 kernel 由 FlashMLA 提供,indexer 的 logits kernel 在 DeepGEMM 里,另有 TileLang 参考实现
  • 训练分两步:先冻结主模型、只训 indexer,用 KL 损失让它对齐主注意力的分布(dense warm-up),再放开全部参数做稀疏训练;上下文上限 128K

对照 V4 回头看,能看到三处演进(这是本专栏的解读,官方并没有这样归纳):

  1. V3.2 是"全长 KV + 稀疏选取"——V4 改成 CSA / HCA 混合:CSA 先 4 倍压缩再做 DSA 式稀疏选取,HCA 做 128 倍压缩后仍是 dense 注意力
  2. V3.2 没有滑动窗口、上限 128K——V4 给每层加了 128 的滑窗分支,并把上下文推到 1M
  3. V3.2 的 indexer 已有自己的 key 投影——V4 延续这一点,给 Indexer 配了独立的 Compressor

V4 的 inference/model.py:380-433 那个 Indexer 类(class Indexer(torch.nn.Module)),本质上就是把 V3.2-Exp 的 score net 搬到压缩 KV 上,并把它的 QK 路径压到 fp4。


1.5 V3.2-Exp 那次"半发布"在 V4 源码里留下的指纹

V3.2-Exp 在 2025 年 9 月发布,模型卡称它是"an experimental version"——把它读成 V4 的"工程预演"并不为过。V3.2-Exp 把 DSA(DeepSeek Sparse Attention) 这套机制塞进了一个真实生产规模的模型里跑了几个月。当我们打开 V4 的 inference/model.py,至少有三处源码细节可以直接对回 V3.2-Exp 的实战教训——这些是可以验证的,不需要任何内部资料:

  1. Indexer 拥有独立的 Compressor(inference/model.py:398,在 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 的 indexer 已经有独立的 key 投影,V4 在源码里延续了这条设计。

  2. 每层独立的 compress_ratio(config.json 里 compress_ratios 是一个长度 62 的数组——主模型 61 层 + MTP 层 1 个)

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

  3. 滑动窗口 + 稀疏 KV 的串联(inference/model.py:514:topk_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,则是本专栏的解读。


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

V4 的全部前向逻辑,可以浓缩成 Transformer.forward(inference/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.7 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;
  classDef ch fill:#7c2d12,stroke:#fb923c;
  classDef dec fill:#0f172a,stroke:#3b82f6;

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

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

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


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

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

维度 V3 (671B / 37B) V4 Pro (1.6T / 49B) 差异性质
总层数 61 + 1 (MTP) 61 + 1 (MTP) 持平
Hidden size 7168 7168 持平
Attention head 数 128 128 持平
Head dim 192 (nope 128 + rope 64) 512 (nope 448 + rope 64) 重构
KV latent rank kv_lora_rank=512 取消,用 head_dim=512 的滑窗 + 压缩 KV 重构
Q LoRA rank 1536 1536 持平
O LoRA rank 无 1024 (grouped, o_groups=16) 新增
Routed experts 256 384 容量 +50%
Top-k experts 8 6 稀疏性 ↑
Hash routing 无(前 3 层是 dense FFN) 全部 MoE,前 3 层使用 hash 路由 新增
MoE 中间维度 2048 3072 容量 +50%
Scoring function sigmoid sqrtsoftplus 换型
Topk method noaux_tc noaux_tc 持平
节点受限路由 有(每 token 至多发往 4 个节点) 取消(技术报告第 2.1 节;源码无 n_group / topk_group,并行策略相应重设计) 简化
Expert 权重精度 FP8 e4m3 FP4 e2m1 重构
Linear 权重精度 FP8 e4m3 FP8 e4m3 持平
Scale 格式 未声明(config 无 scale_fmt) ue8m0 显式化
Attention sparsity dense 滑窗 (128) + Compressor (per-layer ratio) + Indexer top-1024 重构
Compress ratios per layer 无 [128, 128, 4, 128, 4, ...](绝大多数 4,少量 128 / 0) 新增
Index top-k 无(V3.2 为 2048) 1024(首发 04-24 最初为 512,当日即改为 1024) 新增
残差 identity 残差 Hyper-Connections (hc_mult=4) + 20 步 Sinkhorn 归一化 重构
MTP layers 1 1 持平
最大上下文 128K(yarn factor=40) 1M(yarn factor=16,原始窗口 65536) 扩展 8 倍
RoPE theta 10000 10000(纯滑窗层,不开 YaRN)+ compress_rope_theta=160000(有压缩段的层整层使用,叠加 YaRN) 双 RoPE
优化器 AdamW Muon(embedding、预测 head 与 RMSNorm 权重仍用 AdamW) 换型
预训练 token 数 14.8T 33T(技术报告;README 称 32T+,32T 是 Flash 的量) +123%

差异性质三个标签:

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

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


1.9 V4 的三种推理模式:Non-Think / Think High / Think Max

V4 在 README 中显式列出了三种推理模式:V3.1 / V3.2 已有 thinking / non-thinking 两档,V4 多出了 Think Max 这一档。在推理侧,三种模式体现为chat template 与 system prompt 的差别——由 encoding/encoding_dsv4.py 的 encode_messages(messages, thinking_mode=..., reasoning_effort=...) 触发;在训练侧,技术报告说它们来自不同 RL 配置(不同的长度惩罚与上下文窗口)。

flowchart LR
  Input["用户 messages"]
  Mode{thinking_mode}
  NT["Non-Think<br/>thinking_mode='chat'"]
  TH["Think High<br/>thinking_mode='thinking'"]
  TM["Think Max<br/>thinking_mode='thinking'<br/>+ reasoning_effort='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 开头注入一段特殊指令来解锁,思考链比 Think High 更长;README 建议这个模式把上下文窗口至少设到 384K token(技术报告评测时三种模式的上下文窗口分别是 8K / 128K / 384K)。这种模式的官方推荐场景是"探索模型的推理边界"——通常用于评测、研究、复杂数学/编程问题。

V4 把"推理形态"作为一等公民暴露给用户的设计决策,反过来影响了训练数据和评估闭环——第 18 章会讲到,三种思考形态混在一个统一模型里训练会带来什么问题。


1.10 一段源码对比: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: absorb 路径只存 latent + rope 两段,不按 head 展开
self.register_buffer("kv_cache", torch.zeros(max_bsz, max_seq, self.kv_lora_rank))
self.register_buffer("pe_cache", torch.zeros(max_bsz, max_seq, qk_rope))

V4 的 Attention.__init__(inference/model.py:439 起,节选并调整了顺序):

# 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 异曲同工,省参省算(比的是 head_dim=512 下的稠密 O 投影,约 470M 对 184M;比 V3 那个 117M 的 wo 反而多,见 §2.4)
  3. 稀疏化是层级可配的——compress_ratios[layer_id] 决定本层是 4 倍压缩或 128 倍压缩(ratio=0 仅 MTP 层 1 处,那层连压缩段都不分配)
  4. Indexer 只在 compress_ratio == 4 的层上启用——稀疏路由不是每层都需要

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


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

V4 同时发布了两款:

模型 总参数 激活参数 主要用途
V4 Pro 1.6T 49B 旗舰能力;README 的对照组是 Opus-4.6 / GPT-5.4 / Gemini-3.1-Pro
V4 Flash 284B 13B 性价比线;README 称给足思考预算后推理能力接近 Pro,知识类与最复杂 agentic 任务略逊

两者同源——技术报告第 4.2.1 节给出的两套配置结构相同、只是数字不同,推理代码也是同一套写法。差异体现在 config.json 上(Flash 一列取自技术报告):

维度 Pro Flash
n_layers 61 43
hidden_size 7168 4096
n_heads 128 64
n_routed_experts 384 256
moe_inter_dim 3072 2048
总参 1.6T 284B
激活参 49B 13B

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


1.12 33T 训练 token 之谜与两阶段后训练

V4 的 README 写着:

We pre-train both models on more than 32T diverse and high-quality tokens.

技术报告给出的具体口径是 Flash 32T、Pro 33T,相对前作的 V3(14.8T)翻了一倍多。为什么 V4 要吃这么多 token?报告没有给出归因,下面两条是本专栏的推测(FP4 量化感知训练只在后训练引入,技术报告第 3.4 节,与预训练 token 量无关):

  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,拓宽的是层间残差通道而不是层内参数(技术报告强调它的计算开销很小);残差通道更宽,可能也需要更多 token 才能把它用满。

至于数据从哪里来,技术报告第 4.1 节只给了大类:数学、代码、网页、长文档等(长文档优先科研论文与技术报告,mid-training 阶段还加入了 agentic 数据以增强代码能力),没有给具体配比。

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

The post-training features a two-stage paradigm: independent cultivation of domain-specific experts (through SFT and RL with GRPO), followed by unified model consolidation via on-policy distillation.

第一阶段:"领域专家独立培养"——意思是先用 SFT + GRPO(DeepSeek 自 DeepSeekMath / R1 起沿用的强化学习算法)为每个目标领域单独训练一个专家模型(技术报告举的例子是数学、代码、agent、指令遵循),每个领域单独优化。第二阶段:"on-policy 蒸馏到统一模型"——再把这些领域专家的能力蒸馏回一个统一的 V4。

技术报告第 5.1 节说后训练流程大体沿用 V3.2,关键改动是把混合 RL 阶段整个换成了 on-policy 蒸馏。拿 R1 的"先长链推理 → 蒸馏到 chat 模型"路线作类比(这只是本专栏的类比),V4 把它放到了多领域和多推理模式的笛卡尔积上。第 18 章会用整章篇幅拆这个 pipeline。注意这里的"领域专家"是一个个独立训练的完整模型,和 MoE 层里的 384 个 routed expert 不是一回事。


1.13 V4 在开源版图里的位置

同期各家模型的规格对照表放在第 20 章 §20.1,那里连同"哪些数字能一手核到、哪些是按公开材料整理的近似值"一起标注了可信度分层。这里只说 V4 自己那一格里能从 config.json 与 README 直接坐实的两条:

  1. 1M context 下把稀疏注意力与低精度权重同时做进了一套生产权重里——max_position_embeddings=1048576 配上 Indexer + compress_ratios,再配上 expert_dtype: "fp4"
  2. expert 权重压到 FP4 并在 1.6T 规模上给出了可下载的收敛结果——expert_dtype: "fp4" 与 quantization_config 是配置里写死的,权重本身是 MIT 许可、任何人可以下载验证

至于"是不是唯一"——开源版图变化太快,本专栏不下这种断言(§20.1 同样的立场)。能确定的是这两条的组合决定了 V4 的适用面:

  • 想跑 1M 长文 + 极致 token 经济性的项目,V4 是当下少数能直接下载权重就用的选择
  • 想理解稀疏 + FP4 + HC 这条工程路线,V4 是可以下载到本地拆开看的样本

1.14 端到端 inference:一段 prompt 的完整旅程

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

步骤 1:tokenize

V4 复用 V3 的 BBPE 词表(vocab_size=129280;技术报告说只在 V3 tokenizer 上新增了少量用于构造上下文的特殊 token)。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 部分(pin 版的细节见 §14.6)
#   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[idx] += expert(x[idx], weights[idx, top, None])
#   all_reduce(y)          # 先跨 rank 求和 routed 部分
#   y += shared_experts(x) # 再加共享专家(每个 rank 都完整持有,不参与 all_reduce)
# 输出 x: [1, 5, 7168]
h = layer.hc_post(x, h, post, comb)    # [1, 5, 4, 7168]

步骤 4:穿过剩下 60 层

每层重复上述过程,但 compress_ratio 在 4 / 128 之间切换(MTP 层不在 Transformer.forward 里跑,它由 self.mtp 单独持有):

  • ratio=4 的层:Indexer 启用,做"学习型稀疏选取"
  • ratio=128 的层:Compressor 照样是学习的门控池化,但不建 Indexer,已完成的压缩条目全部参与注意力(get_compress_topk_idxs 直接把它们全列进 topk)
  • ratio=0 的层:没有压缩段——kv_cache_size 只剩 window_size=128 个槽位,Compressor 与 Indexer 都不构造,是纯滑窗注意力(V4 Pro 中仅 MTP 层是这种配置,主模型 61 层都是 ratio=4 或 ratio=128)

第 3-60 层(layer_id >= n_hash_layers=3)的 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

README 推荐的采样参数:temperature=1.0, top_p=1.0。这两个 1.0 是 README 在 "How to Run Locally" 一节给出的官方推荐值,采样端不做二次干预(注意 inference/generate.py 命令行的 --temperature 默认值是 0.6,照 README 跑要显式传 1.0)。

步骤 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.15 README 三组数字背后的工程账

V4 的 README 在显眼位置摆了两组效率数字("1M 上下文下相比 DeepSeek-V3.2 只需 27% 的单 token 推理 FLOPs 与 10% 的 KV cache")加一组参数规模数字。它们看起来像营销话术,但每一组背后都有源码可以印证的工程账。把它们逐一拆开:

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

这条数字的分母不是 dense Llama,而是 V3.2——它本身已经是 top-k 稀疏注意力(每个 query 只选 2048 个 KV),所以这 27% 不能记在"去掉 O(n2)O(n^2)"头上。技术报告第 2.3.4 节的归因是压缩注意力与混合注意力为主,再加上几处精度与参数选择:

  1. KV 不再是"每 token 一组 latent",而是"每 ratio 个 token 才存一组压缩 KV"——注意力要扫的条目数随之降到约 1/ratio(HCA 层 128 倍、CSA 层 4 倍后再做 top-k)
  2. lightning indexer 的打分也只对 4 倍压缩后的条目做,并且在 FP4 下计算;attention top-k 从 V3.2 的 2048 降到 1024(首发 04-24 最初为 512)
  3. 这里的 FLOPs 按"等效 FP8 FLOPs"计(报告原文 measured in equivalent FP8 FLOPs)

注意 27% 是单 token 全模型的 FLOPs 比,不只是注意力那一段:激活参数 49B 对 V3.2 的 37B,线性层这半边反而是涨的,注意力这半边省下来的量要先把这块涨幅填平,剩下的才体现在最终比值里。

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

这条数字可以用 config.json 直接算出来,不需要任何额外假设。

V3.2 的 DSA 只让计算变稀疏,KV cache 仍与 V3 一样,每个 token 每层存一份 latent:kv_lora_rank=512 加上 qk_rope_head_dim=64,共 576 个元素。

KVV3.2=L×ntok×(dkv_lora+drope)\text{KV}_{V3.2} = L \times n_\text{tok} \times (d_\text{kv\_lora} + d_\text{rope})

V4 每层存的是"滑窗 + 压缩段",条数由该层的 compress_ratio 决定(源码 inference/model.py:473):

KVV4=∑l=0L−1(window_size+ntokratiol)×dhead\text{KV}_{V4} = \sum_{l=0}^{L-1} \left(\text{window\_size} + \frac{n_\text{tok}}{\text{ratio}_l}\right) \times d_\text{head}

代进去(ntok=1048576n_\text{tok}=1048576,即按 max_seq_len=1048576 的 1M 部署设定——参考实现 ModelArgs 默认只有 4096;window=128,head_dim=512,61 个主模型层里 30 层 ratio=4、31 层 ratio=128,两边都按 1 字节/元素折算;参考实现实际把 KV 存成 bf16,两边字节数同步翻倍、比值不变,第 3 章 §3.10 按 bf16 重算过同一笔账):

  • V3.2:61×1048576×576=3.68×101061 \times 1048576 \times 576 = 3.68\times10^{10} 元素 ≈ 36.8 GB / 序列
  • V4:ratio=4 的层每层 (128+262144)×512≈1.34×108(128 + 262144)\times512 \approx 1.34\times10^8,ratio=128 的层每层 (128+8192)×512≈4.3×106(128+8192)\times512\approx4.3\times10^6,加起来 4.16×1094.16\times10^9 元素 ≈ 4.16 GB / 序列(按 bf16 存约 8.3 GB;按技术报告第 2.3.4 节的混合格式——RoPE 64 维 BF16、其余 448 维 FP8——约 4.7 GB)

比值 11.3%——与 README 那个"10%"量级一致(口径不完全相同:技术报告 Figure 1 的 10% 是估算的累计 KV cache,V4 一侧按混合格式存,报告没有逐项写明是否含 indexer 缓存;这里是两边同一字节宽度的粗算,第 3 章 §3.10 的说明相同)。折算成"等效压缩比":61 层平均每层只留 13.3 万条 KV,相当于把 1M 序列压到约 1/7.9,再乘上"V4 每条 KV 是 512 维、V3.2 每条是 576 维"这个 0.89 的系数,就是最终那个十分之一。这笔账两边都没算 indexer 自带的缓存:V4 在 30 个 ratio=4 层上另有每 4 token 一条、128 维的 indexer KV(约 1.0×1091.0\times10^9 元素),V3.2 的 indexer 按其论文(arXiv:2512.02556)每个 token 存一条 key,维度按其公开配置的 128 计(约 8.2×1098.2\times10^9 元素;本专栏 pin 树不含 V3.2 源码,这一项是估算);都算进去,比值约 11.5%,结论不变(V4 这份 indexer KV 按 bf16 约 1920 MiB,第 3 章 §3.10 把它并进总账后约 9.6 GiB)。

源码里的 kv_cache_size = args.window_size + (args.max_seq_len // self.compress_ratio if self.compress_ratio else 0) 一行,就是这个公式的承载者。

数字三:1.6T 总参 / 49B 激活

这两个数字之间的比值是 3.1%——比 V3 的 37B/671B = 5.5% 又稀疏了一档。它由三个配置项共同决定:

  • n_routed_experts=384、num_experts_per_tok=6:每个 token 只碰 1.6% 的 routed expert
  • moe_intermediate_size=3072:单个 expert 比 V3 的 2048 更宽,所以"专家数 ×1.5、宽度 ×1.5"叠出来的总容量增幅远大于激活量增幅
  • expert_dtype="fp4":每个 expert 参数只占半字节,总参往上翻的显存代价被砍掉一半

反过来说,这也是 V4 的成本结构:容量买在总参上,成本付在激活参上。第 9 章 §9.8 会沿着 MoE.forward 的实际调度路径,拆开这 49B 激活参数在一次前向里是怎么被用到的。


1.16 实战选型矩阵:什么场景必须选 V4

把 V4 与 2026 年的几个主流开源 LLM 放在工程选型场景下做对比(对照模型与第 20 章 §20.1 的表一致,规格以那里标注的官方模型卡为准)。这张矩阵不是 leaderboard,而是"什么样的工程约束下 V4 是首选":

场景 上下文长度 价格敏感度 部署规模 首选模型 V4 在此场景的相对优势
长法律文档分析 200K-1M 高 单机 8 卡 V4 Pro / V4 Flash 1M 上下文 + 稀疏 KV,显存与延迟均优于 dense MLA
仓库级代码理解 100K-500K 中 集群 V4 Pro 1M 上下文 + Think High 推理形态
通用聊天助手 <32K 高 单卡 / 单机 Gemma 4 31B / Qwen3.5 小尺寸 / V4 Flash V4 Flash 价格优势但模型选择多
强推理(数学/竞赛编程) <128K 低 单机 8 卡 V4 Pro Think Max Think Max 模式对推理边界的探索
实时低延迟 API <8K 极高 大集群 Qwen3.5 小尺寸 / Gemma 4 V4 Pro 单卡装不下、Flash 也远大于小尺寸模型
私有部署敏感行业 <128K 中 集群 V4 Pro / Flash MIT 许可、可下载完整权重
多模态(图文) <128K 中 集群 Qwen3.5-397B-A17B / Mistral Large 3 V4 Pro 当前不含视觉编码器
端侧 / 边缘 <8K 极高 端侧 Gemma 4 E2B / E4B、Qwen3.5 小尺寸 V4 当前最小尺寸 Flash 仍需多卡

V4 的"工程甜区"非常清晰:大上下文 + 价格敏感 + 可下载权重 + 通用文本这四个条件同时成立时,V4 是非常有力的候选。

反过来,V4 不擅长:

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

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


1.17 一张图:本专栏的章节路标

把全专栏 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;
  classDef ch10 fill:#312e81,stroke:#a78bfa;
  classDef block fill:#1f2937,stroke:#475569;
  classDef attn fill:#581c87,stroke:#c084fc;
  classDef moe fill:#7c2d12,stroke:#fb923c;
  classDef ch11 fill:#365314,stroke:#84cc16;

1.18 config.json 字段全解读

V4 的 config.json 一共 48 个顶层字段,其中不少都能对回前几代模型的经验。我们挑主要字段,按"架构 / 注意力 / MoE / 精度"四组来读(推理代码实际读取的是字段名不同的 inference/config.json,见 §1.22 末条):

1.18.1 架构骨架

字段 V4 取值 含义
architectures ["DeepseekV4ForCausalLM"] HF transformers 加载时使用的类名,也是这次架构升级的"门牌号"
model_type deepseek_v4 HF 的模型类型标识,与 V3 的 deepseek_v3 区分开
num_hidden_layers 61 主 Transformer 层数(与 V3 持平)
hidden_size 7168 模型维度,与 V3 持平
vocab_size 129280 与 V3 持平。tokenizer 复用 V3 的 BBPE,只新增少量特殊 token
tie_word_embeddings false embedding 与 lm_head 不共享权重
transformers_version 4.57.1 生成这份 config 时的 transformers 版本(inference/requirements.txt 要求 transformers>=5.0.0)

1.18.2 注意力与稀疏化

字段 V4 取值 含义
num_attention_heads 128 每层 Q head 数
head_dim 512 单 head 维度(V3 的 qk head dim 是 192 = nope 128 + rope 64;V4 到 512)
num_key_value_heads 1 KV head 只有 1 个——所有 Q head 共享同一组 512 维 KV(MQA)
q_lora_rank 1536 Q 的 LoRA 中间维度(V3 同)
o_lora_rank 1024 新增:O 的 grouped LoRA rank
o_groups 16 新增:把 O 投影分成 16 组,每组独立低秩
qk_rope_head_dim 64 RoPE 部分维度(与 V3 持平);non-rope 部分 = head_dim - 64 = 448
sliding_window 128 每层都跑一个滑动窗口注意力,叠在稀疏 KV 之上
compress_ratios per-layer 每层 KV 压缩倍率(4 / 128 / 0),见下
index_n_heads 64 Indexer 的 head 数(与主 attn 不同)
index_head_dim 128 Indexer 的 head 维度
index_topk 1024 Indexer 选取的 top-k KV 位置数(首发 04-24 最初为 512,当日即改为 1024)
compress_rope_theta 160000 新增:有压缩段(ratio>0)的层整层使用的 RoPE 频率基数,叠加 YaRN(比 rope_theta 高 16 倍)
rope_theta 10000 纯滑窗层(ratio=0)使用的 RoPE 频率基数,不开 YaRN
rope_scaling.type yarn YaRN 频率插值
rope_scaling.factor 16 频率缩放倍数:65536 × 16 = 1M
rope_scaling.original_max_position_embeddings 65536 YaRN 的"短上下文锚点"
rope_scaling.beta_fast 32 YaRN 高频段截止
rope_scaling.beta_slow 1 YaRN 低频段截止
max_position_embeddings 1048576 1M 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"(layer_id 2、4……):是稀疏注意力真正在工作的层(inference/model.py:468 的 if self.compress_ratio == 4: 是 Indexer 的唯一开关)
  • 最后一个元素(index 61)= 0:这是 MTP 层(num_nextn_predict_layers=1),不是主模型最后一层。注意 0 在这里是最省而不是最全——inference/model.py:473 的 kv_cache_size = window_size + (max_seq_len // compress_ratio if compress_ratio else 0) 代入 0 只剩 128 个滑窗槽位,inference/model.py:466 的 if self.compress_ratio: 也不成立,Compressor / Indexer 一个都不建。MTP 层是纯滑窗注意力,源码注释原话是 pure sliding-window attention(inference/model.py:478)。主模型最后一层(index 60)实测是 ratio=4,不是 0

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

1.18.3 MoE 与路由

字段 V4 取值 含义
n_routed_experts 384 路由专家数(V3=256)
n_shared_experts 1 共享专家数(与 V3 持平)
num_experts_per_tok 6 每 token 激活的 routed expert 数(V3=8,V4 更稀疏)
moe_intermediate_size 3072 单 expert 的 FFN 中间维度
routed_scaling_factor 2.5 路由权重的最终缩放系数
scoring_func sqrtsoftplus gate 评分函数(V3 是 sigmoid)
topk_method noaux_tc auxiliary-loss-free top-k 选取
norm_topk_prob true 选定 top-k 后做归一化
num_hash_layers 3 新增:前 3 层用 hash 路由
swiglu_limit 10.0 SwiGLU 的 clip 阈值:up 截到 ±10、gate 只截上限——技术报告第 4.2.3 节说这是为压制 MoE 离群值、稳定训练而加,Flash / Pro 全程训练开启;04-27 起 shared expert 也生效(首发 04-24 只作用于 routed expert)

1.18.4 精度与量化

字段 V4 取值 含义
expert_dtype fp4 新增:expert 权重压到 FP4 e2m1(04-27 加入;首发 04-24 的 config.json 无此字段)
quantization_config.fmt e4m3 非 expert 权重的 FP8 格式
quantization_config.scale_fmt ue8m0 scale tensor 的格式
quantization_config.weight_block_size [128, 128] FP8 块状量化的块大小
quantization_config.activation_scheme dynamic 激活值在每次 forward 时重新算 scale
torch_dtype bfloat16 反量化后的工作 dtype
rms_norm_eps 1e-6 RMSNorm 的 epsilon
hc_eps 1e-6 Hyper-Connections 的 epsilon
hc_mult 4 新增:HC 复制份数
hc_sinkhorn_iters 20 新增:HC 内部 Sinkhorn 迭代次数
num_nextn_predict_layers 1 MTP 层数

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


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

V4 Pro 与 V4 Flash 架构相同,差异落在配置数字上。下表 Flash 一列取自技术报告第 4.2.1 节(本专栏未核对 Flash 仓库的 config.json):

字段 V4 Pro V4 Flash(技术报告) 影响
总参数 1.6T 284B 内存占用差 5.6 倍
激活参数 49B 13B 激活参数差约 3.8 倍
num_hidden_layers 61 43 深度差
hidden_size 7168 4096 宽度差
num_attention_heads 128 64 注意力宽度差
q_lora_rank 1536 1024 Q 压缩维度
o_groups 16 8 grouped O 组数
n_routed_experts 384 256 容量差
moe_intermediate_size 3072 2048 单 expert 的 FFN 宽度
index_topk 1024 512 稀疏选取条数
前两层注意力 HCA(ratio=128) 纯滑窗 浅层配置不同
head_dim / 滑窗 / HC / hash 层 / MTP 512 / 128 / 4 / 3 / 1 同 Pro 持平

为什么 Flash 也要 1M 上下文?——因为 V4 Pro 与 V4 Flash 用的是同一套架构,长文档能力主要是架构带来的、不是参数量带来的。Flash 的 1.6T → 284B 缩小,是在层数、hidden size、注意力头数、expert 数、FFN 宽度几个维度上同时下调的结果。

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


1.20 V4 的工程美学:四条潜规则

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

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

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 三种精度并存的代码至关重要。

潜规则三:状态外置

Compressor 的 kv_state / score_state 都用 register_buffer(persistent=False) 显式外置。Attention 的 kv_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 源码里贯彻得异常彻底,读起来有"刀切豆腐"的清爽感。


1.21 一段 forward 的张量形状追踪

把 Transformer.forward 在 B=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 章会展开
  • 进入 attn 的 x 形状是 [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)

1.22 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,以 65536 为原始长度缩放到 1M(技术报告称预训练序列长度逐步扩到了 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 变换(调用 fast_hadamard_transform),FP4 量化前对激活做一次"信息打散"。
  • attn_sink:每个 attention head 一个 float32 的"注意力汇"参数,其 exp 值加进 softmax 分母,让该 head 的注意力总和可以小于 1——"找不到合适 KV 时"的兜底(技术报告第 2.3.3 节)。
  • 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。官方推理仓库里的实现在 inference/kernel.py,用 TileLang 写(@tilelang.jit),不依赖 DeepGEMM——DeepGEMM 是生产部署时的高性能替代品,见第 13 章。
  • sparse_attn:V4 的稀疏注意力 kernel。官方推理仓库里的实现同样在 inference/kernel.py,是 TileLang 写的 sparse_attn_kernel(gather top-k 索引 + online softmax + attn_sink),不依赖 FlashMLA,见第 5 章。
  • 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 复指数张量,每个 Attention 各自注册为 buffer;precompute_freqs_cis 带 @lru_cache(2),同参数的层拿到的是同一张表——有压缩段的层(compress_rope_theta + YaRN)和纯滑窗层(rope_theta、无 YaRN)是两张不同的表。
  • window_size=128 vs sliding_window=128:前者是 ModelArgs 与 inference/config.json 里的字段名,后者是顶层 config.json(HF 风格)里的字段名,是同一个值的两种命名。inference/generate.py 直接 ModelArgs(**json.load(f)) 读取 inference/config.json,两份文件之间没有自动映射。

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

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

实验 A:用随机权重把 Transformer 跑通(需要一张 CUDA 卡,袖珍配置占用很小)

# 注意:这只跑前向传播验证形状,不会得到有意义的输出
# 前置:pip install tilelang fast-hadamard-transform;需要一张 CUDA 卡
#      并在 inference/ 目录下运行(model.py 用的是 `from kernel import ...` 这种平级导入)
import torch
from model import Transformer, ModelArgs

torch.set_default_dtype(torch.bfloat16)
torch.set_default_device("cuda")

# 用一个袖珍配置(比 V4 Pro 小 100 倍以上)
args = ModelArgs(
    vocab_size=1024,
    dim=512,
    n_layers=2,
    n_heads=8,
    dtype="bf16",          # 跳过 FP8 权重容器,纯 bf16 跑形状
    expert_dtype=None,
    n_routed_experts=4,
    n_activated_experts=2,
    moe_inter_dim=1024,
    head_dim=192,          # 注意:head_dim - rope_head_dim 必须是 64 的倍数,
    rope_head_dim=64,      # 否则 Compressor 里的 act_quant(kv[..., :-rd], 64, ...) 会断言失败
    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,并让 TileLang 把 kernel.py 里的 sparse_attn_kernel / act_quant_kernel 真正 JIT 编译出来。没有 CUDA 就跑不起来——kernel.py 里所有算子都是 @tilelang.jit 的 GPU kernel,源码里没有任何 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 对照:routed expert 大约 1.55T,占 1.6T 总参的 97%;attention 侧(wq_a/wq_b/wkv/wo_a/wo_b)加起来只有约 18B。这就是 V4"把 expert 压 FP4、其他保 FP8"这个权衡的工程原因。


1.24 V4 源码行号速查表

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

顶级函数:

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

类定义:

行号 类名 关键内部成员
35 ModelArgs @dataclass,所有超参
83 ParallelEmbedding TP-切分的 vocab embedding
123 Linear FP4/FP8/BF16 三态权重容器
155 ColumnParallelLinear 切 out_features
166 RowParallelLinear 切 in_features + all_reduce
183 RMSNorm float32 归一化
279 Compressor KV 压缩,含 prefill/decode 双 codepath
380 Indexer 稀疏注意力 score net
436 Attention MLA + Compressor + Indexer 集成
546 Gate sqrtsoftplus / hash / noaux_tc
587 Expert SwiGLU FFN
609 MoE 384 expert + shared 调度
647 Block HC + attention + FFN 包裹
703 ParallelHead LM head + hc_head 处理
738 MTPBlock 继承 Block,加 e_proj/h_proj
769 Transformer 顶层模型

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

行号 方法 章节定位
86 ParallelEmbedding.__init__ §15.6
96 ParallelEmbedding.forward §15.6
283 Compressor.__init__ §3.2
316 Compressor.forward §3.3 / §3.4
384 Indexer.__init__ §4.2
402 Indexer.forward §4.3
439 Attention.__init__ §2.2
484 Attention.forward §2.9 / §2.10 / §2.13
459 Attention 中的 self.wq_b §15 ColumnParallel 例
550 Gate.__init__ §7.2
562 Gate 中的 self.bias §7.5
564 Gate.forward §7.2 / §8.4
589 Expert.__init__ §9.2
596 Expert.forward §9.2 / §9.4
612 MoE.__init__ §15.8
629 MoE.forward §9.8 / §15.8
652 Block.__init__ §10.2 / §10.3
688 Block.forward §10.2
740 MTPBlock.__init__ §11.2
757 MTPBlock.forward §11.3
772 Transformer.__init__ §1.6 / §15.2
802 Transformer.forward §1.6 / §1.21

验证方式:

# 把整文件下载下来本地 grep
# main 分支此后可能已更新;要与本表逐行对上,请按专栏首页的方法 clone 后 git checkout 4504094
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.25 延伸阅读

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

  1. DeepSeek-V4 技术报告——huggingface.co/deepseek-ai/DeepSeek-V4-Pro/blob/main/DeepSeek_V4.pdf —— 这个专栏所有章节都会反复引用,建议至少通读一遍
  2. DeepSeek-V3 Technical Report(arXiv:2412.19437)—— 理解 V4 必须先理解 V3
  3. DeepSeek-V2: A Strong, Economical, and Efficient MoE LM(arXiv:2405.04434)—— MLA 和 DeepSeekMoE 的源头
  4. DeepSeekMoE(arXiv:2401.06066)—— 细粒度专家 + 共享专家的论文起点
  5. YaRN: Efficient Context Window Extension(arXiv:2309.00071)—— 第 6 章会用到
  6. FlashMLA 仓库——github.com/deepseek-ai/FlashMLA,第 5 章主参考
  7. DeepGEMM 仓库——github.com/deepseek-ai/DeepGEMM,第 13 章主参考
  8. Hyper-Connections 论文——第 10 章会引用,注意 V4 用的是它的约束版 mHC(Manifold-Constrained Hyper-Connections,技术报告引作 Xie et al., 2026),与原论文有差异

二手但价值高的参考:

  • vLLM 的 V4 适配——第 19 章 §19.3 给出"该在哪些地方动刀"的推断地图
  • Artificial Analysis 的 V4 评测页面(独立基准对比)

1.26 本章小结

  • DeepSeek 的四代模型是一条清晰的工程主线:稀疏 + 长上下文 + 低精度,每代啃一段
  • V4 不是"换了几个模块",而是同时重构了 KV、Attention、残差、Expert 精度四件大事
  • V4 真正的"反常识"全部摆在 Transformer.forward 那 4 行里——HC unsqueeze、Block 循环、HC head——这个专栏剩下的 19 章是把这 4 行展开
  • V4 与 V3 的差异表里,5 处标注"重构"(head_dim、KV latent、expert 精度、attention 稀疏化、残差)、4 处标注"新增"(O LoRA、hash 路由、per-layer 压缩比、Indexer top-k)——这就是为什么 V4 不能简单理解为"V3 的放大版"
  • config.json 的每一个字段都对应到本专栏某个章节——这是配置 → 源码 → 章节的三向索引

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