DeepSeek V4 源码剖析
第1章 V4 之路:从 V2 / V3 / V3.2 到 V4 的架构演进
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 大小是:
其中 是层数、 是 head 数、 是 head 维度、 是序列长度。以 Llama-2-70B 的几何(80 层、8 个 KV head、head_dim=128、FP16)代进去,32K context 一条序列就要 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 提出两个改造:
- 细粒度专家:把 expert 数量一路加宽——DeepSeekMoE-16B 是 64、V2 是 160、V3 是 256、V4 是 384——并相应减小每个 expert 的 intermediate_size
- 共享专家(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 回头看,能看到三处演进(这是本专栏的解读,官方并没有这样归纳):
- V3.2 是"全长 KV + 稀疏选取"——V4 改成 CSA / HCA 混合:CSA 先 4 倍压缩再做 DSA 式稀疏选取,HCA 做 128 倍压缩后仍是 dense 注意力
- V3.2 没有滑动窗口、上限 128K——V4 给每层加了 128 的滑窗分支,并把上下文推到 1M
- 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 的实战教训——这些是可以验证的,不需要任何内部资料:
-
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 在源码里延续了这条设计。 -
每层独立的
compress_ratio(config.json里compress_ratios是一个长度 62 的数组——主模型 61 层 + MTP 层 1 个)V3.2-Exp 的 DSA 是"全层均匀稀疏"。V4 改成 per-layer 的非均匀配置——
[128, 128, 4, 128, 4, ...]。这个配置只能从工程实验里得来,不是凭直觉拍出来的。 -
滑动窗口 + 稀疏 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 的工程意图就完全暴露了:
- KV LoRA 被抹掉——V4 不再相信"潜在低秩 KV"是最优方案。它选了"head_dim=512 的全维 KV + 极强压缩 + 滑窗 + 稀疏选取"
- O 投影变 grouped low-rank——和 LoRA 微调里的 grouped LoRA 异曲同工,省参省算(比的是 head_dim=512 下的稠密 O 投影,约 470M 对 184M;比 V3 那个 117M 的
wo反而多,见 §2.4) - 稀疏化是层级可配的——
compress_ratios[layer_id]决定本层是 4 倍压缩或 128 倍压缩(ratio=0 仅 MTP 层 1 处,那层连压缩段都不分配) - 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 量无关):
- 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 数差不多要翻倍。
- 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 直接坐实的两条:
- 1M context 下把稀疏注意力与低精度权重同时做进了一套生产权重里——
max_position_embeddings=1048576配上 Indexer +compress_ratios,再配上expert_dtype: "fp4" - 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% 不能记在"去掉 "头上。技术报告第 2.3.4 节的归因是压缩注意力与混合注意力为主,再加上几处精度与参数选择:
- KV 不再是"每 token 一组 latent",而是"每 ratio 个 token 才存一组压缩 KV"——注意力要扫的条目数随之降到约 1/ratio(HCA 层 128 倍、CSA 层 4 倍后再做 top-k)
- lightning indexer 的打分也只对 4 倍压缩后的条目做,并且在 FP4 下计算;attention top-k 从 V3.2 的 2048 降到 1024(首发 04-24 最初为 512)
- 这里的 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 个元素。
V4 每层存的是"滑窗 + 压缩段",条数由该层的 compress_ratio 决定(源码 inference/model.py:473):
代进去(,即按 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: 元素 ≈ 36.8 GB / 序列
- V4:ratio=4 的层每层 ,ratio=128 的层每层 ,加起来 元素 ≈ 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(约 元素),V3.2 的 indexer 按其论文(arXiv:2512.02556)每个 token 存一条 key,维度按其公开配置的 128 计(约 元素;本专栏 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 expertmoe_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 延伸阅读
权威一手资料(按重要性排序):
- DeepSeek-V4 技术报告——
huggingface.co/deepseek-ai/DeepSeek-V4-Pro/blob/main/DeepSeek_V4.pdf—— 这个专栏所有章节都会反复引用,建议至少通读一遍 - DeepSeek-V3 Technical Report(arXiv:2412.19437)—— 理解 V4 必须先理解 V3
- DeepSeek-V2: A Strong, Economical, and Efficient MoE LM(arXiv:2405.04434)—— MLA 和 DeepSeekMoE 的源头
- DeepSeekMoE(arXiv:2401.06066)—— 细粒度专家 + 共享专家的论文起点
- YaRN: Efficient Context Window Extension(arXiv:2309.00071)—— 第 6 章会用到
- FlashMLA 仓库——
github.com/deepseek-ai/FlashMLA,第 5 章主参考 - DeepGEMM 仓库——
github.com/deepseek-ai/DeepGEMM,第 13 章主参考 - 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 为什么消失。