DeepSeek-V4-Pro 架构:
一个 token 的旅程
近处看细节,远处读压缩记忆;每层只叫醒少量专家。
← → 逐步讲解 · 拖动进度条回看 · F 全屏
文字先变向量,再一层层改写
每个算子先对齐四个维度
B 是请求数,S 是本次处理的 token 数,d 是隐藏宽度。进入一个 Block 的残差多一条大小为 4 的轴。
| 位置 | 完整形状 | 单 token 的元素数 |
|---|---|---|
| 残差输入 | [B, S, 4, 7168] | 28,672 |
| mHC 汇合后 | [B, S, 7168] | 7,168 |
| Q / shared KV | [B,S,128,512] / [B,S,512] | 65,536 / 512 |
| 专家中间态 | [被路由的 token 数, 3072] | 3,072 |
decode 时通常 S=1;prefill 的 S 可以很大。B×S 决定 GEMM 行数。Mi 表示 2²⁰:1 MiB=2²⁰ 字节,1 Mi 个参数=2²⁰ 个参数。
低秩 Q:两次矩阵乘法接起来
qr = rms_norm(X @ Wq_a.T)
q = (qr @ Wq_b.T).reshape(B, S, H, 512)
q = q / sqrt(mean(q*q, axis=-1) + eps)
q[..., -64:] = RoPE(q[..., -64:], position)两块权重共有 7168×1536 + 1536×65536 = 106.5 Mi 个参数。直接投影需要 448 Mi;低秩节省权重,但多出一次中间读写与归一化。
每层看多远,由层号决定
四条信息通路,共用一个子层
mHC 真正混合的是什么
| 张量 | 形状 | 如何得到 |
|---|---|---|
| x / z | [B,S,4,7168] / [B,S,7168] | 4 路残差加权成一个子层输入 |
| pre / post / B | 4 / 4 / 4×4 | 24 个动态系数,经约束后参与混合 |
| 生成系数的权重 | [24, 28672] | Attention 与 MoE 各有一套 |
B 做 20 轮 Sinkhorn 行列归一化,使行列和接近 1;pre 与 post 有自己的门控约束,不需要加和为 1。B 的两个轴分别对应旧流和新流。
四路加权成 5,子层只运行一次
取 F(z)=2z,得到 10。若 B=0.5I+0.125·11ᵀ,旧残差混成 [2.5,3.5,4.5,5.5];post=[.25,.5,.75,1],最终为 [5,8.5,12,15.5]。
很多个问题,共用一本笔记
局部和压缩记忆,共用一次归一化
局部条目和选中的压缩条目组成同一个候选集合。每个头还有 learned sink:它增加分母,没有对应的 value 输出。
一维教学例:两个条目的分数为 log2、0,value 为 4、10,sink 分数为 0。
如果删掉 sink,输出变为 6;如果先各自 softmax 再相加,权重也会改变。对齐参考实现时,这两处都要检查。
输出投影也分组,并且降低中间维度
o = inverse_RoPE(o)
o = o.reshape(B, S, groups, 4096)
r = einsum("bsgd,grd->bsgr", o, Wo_a)
y = r.flatten(-2) @ Wo_b.T每组包含 8 个头。第一段共 64 Mi 参数,第二段 112 Mi,共 176 Mi。Pro 与 Flash 每组输入相同,组数翻倍;更宽的 d 让第二段又增长 3.5 倍。
把连续信息,整理成更少的条目
压缩是逐通道的 learned pooling
softmax 沿 token 轴做;每个通道可以重视不同 token。pooling 后做 RMSNorm,再对末尾 64 维施加 RoPE。
CSA 步长为 4。每个 token 投影成 2×512 维:上一组用前半,当前组用后半,合成最多 8 个 token 的重叠窗口。
HCA 每 128 个 token 一组,无重叠。压缩条目的 RoPE 位置取当前组起点:0、128、256……。
一个通道,逐项算出压缩结果
结果为 3.6。这里展示四个有效候选;第一组没有前一组,缺失位置的 gate 为 −∞、权重为 0。这只是归一化前的一个通道。
压缩条目必须等整组完成后才可见
| 刚处理的位置 t(从 0 开始) | CSA 已完成组数 | 可见的新条目 |
|---|---|---|
| 2 | ⌊3/4⌋ = 0 | 无 |
| 3 | ⌊4/4⌋ = 1 | 第 0 组:token 0…3 |
| 6 | ⌊7/4⌋ = 1 | 仍只有第 0 组 |
| 7 | ⌊8/4⌋ = 2 | 第 1 组:前组+当前组重叠 |
visible = (t + 1) // ratio
valid = compressed_index < visible
if (t + 1) % ratio == 0:
cache[t // ratio] = pool(completed_group)prefill 可以并行算出整段的压缩条目,但每个 query 仍要加自己的因果 mask;否则前面的 token 会看到组内未来信息。
先用小索引器挑,再做大注意力
少读 KV 之前,Indexer 仍要给历史打分
Indexer 使用 64 个 128 维头,共享一份 128 维压缩索引缓存。w = weights_proj(x) / √128 / √64,可正可负;它不是 softmax 概率。TP 时先 all-reduce 分数,再选条目。
| 上下文长度 | 压缩候选 N/4 | Pro 主注意力压缩读数 |
|---|---|---|
| 32,768 | 8,192 | 1024 |
| 131,072 | 32,768 | 1024 |
| 1,048,576 | 262,144 | 1024 |
top-k 固定了主注意力读数,Indexer 的候选扫描仍随上下文增长。prefill 参考路径还会构建 query×候选分数,必须把这项计入临时显存。
把远处概括得更短,就能全部读
Pro 的 CSA 和 HCA,成本交点在哪里
| 上下文 N | CSA 每头读数 | HCA 每头读数 |
|---|---|---|
| 32,768 | 1,152 | 384 |
| 131,072 | 1,152 | 1,152 |
| 1,048,576 | 1,152 | 8,320 |
只看注意力点积与 value 聚合,约需 4×头数×读数×512 FLOP。32K 时,Pro 的 CSA 单 query 约 302 MFLOP;HCA 约 101 MFLOP。
这份账本不含投影、Indexer、压缩器和 kernel 调度。交点只是读数相等,不代表完整 kernel 延迟相等。
384 个候选专家,每次选择 6 个
专家就是各自拥有权重的一小段前馈网络。图中只画出部分候选。
选谁和给多少权重,是两次计算
scores = sqrt(softplus(x @ Wgate.T))
if layer_id < 3:
ids = tid2eid[token_id] # 固定的 6 个专家 ID
else:
ids = topk(scores + bias, k=6)
w = scores[ids] / scores[ids].sum()
w *= route_scale
y = sum(w[j] * expert[ids[j]](x) for j in range(6))
y += shared_expert(x)专家选择可受 bias 影响,归一化权重取未加 bias 的 scores。Pro 的 route_scale=2.5,因此六个路由权重之和是 2.5,共享专家另外相加。
一个专家,三块矩阵
| 项目 | 计算 | 参数量 |
|---|---|---|
| 一个专家 | 3×7168×3072 | 63 Mi |
| 61 层全部路由专家 | 61×384×3dm | 1547.396 B |
| 每 token 经过的专家参数 | 61×(6+1)×3dm | 28.208 B |
实际先把 up 限制在 [−10,10]、gate 上限设为 10,再做 SiLU 与逐元素乘法;路由权重融合在 W₂ 之前。表中未计 Attention、embedding、路由器、mHC 和 MTP。所有候选专家权重仍需存储。
把理解变成下一步的选择
同样是六个专家,为什么 Pro 更难喂满
教学观测:一次 decode 有 128 个 token,路由数固定为 6。Pro 每层 384 个专家,总计 768 次 token→expert 分配。
若负载均匀,每个专家 GEMM 只有约 2 行。中间维度 3072 再大,也不能保证 GPU 高利用率。
先看 tokens/expert 的直方图,再区分大量小 GEMM 和少数热点。可测更大 batch、grouped GEMM、专家复制和通信重叠。
Flash 同样 batch 的平均值为 3。Pro 的专家矩阵更大、专家更多,两者一起改变调度效率;只比较激活参数会漏掉这一层。
同一套思路,不同的计算预算
| 配置 | V4-Flash | V4-Pro |
|---|---|---|
| 主干层数 / 宽度 | 43 / 4096 | 61 / 7168 |
| 总参数 / 每 token 激活 | 284B / 13B | 1.6T / 49B |
| 开头两层 | 纯 SWA | HCA + 局部窗口 |
| Query 头 / 低秩维度 | 64 / 1024 | 128 / 1536 |
| CSA top-k | 512 | 1024 |
| 路由专家 / 中间维度 | 256 / 2048 | 384 / 3072 |
| 输出投影分组 | 8 | 16 |
共同配置:4 路残差、512 维 shared KV、128 token 局部窗口、4 / 128 压缩比、6 个路由专家 + 1 共享专家、MTP 深度 1。总参数决定常驻权重规模,激活参数描述每 token 使用的权重规模。
为一个真实输入写出形状和字节数
展开参考解答
残差:2×16×4×7168=917,504 元素,乘 2 B = 1792 KiB。Q:2×16×128×512=2,097,152 元素,占 4096 KiB。
shared KV:2×16×512=16,384 元素,占 32 KiB。头数改变 Q 的大小;shared KV 无须再乘 Q 头数,也没有额外一份 V。
先写出张量形状与单位,再代入数字。
给选择加 bias,别给权重加 bias
展开参考解答
选择分数为 [5,2,3,4],所以选 E1 和 E4。归一化仍使用原始分数 [1,4],除以 5,再乘 2.5,得到 [0.5, 2]。
若误用 [5,4] 归一化,会得到 [1.3889, 1.1111],改变模型。共享专家没有参加这个 top-k,也不参加这次归一化。
先写出张量形状与单位,再代入数字。
定位一个只在整组边界出现的错误
展开参考解答
t=6 时错误规则放行 0、1,但只有第 0 组已完成。t=7 时放行 0、1、2,但只有前两组已完成。正确规则是严格小于完成组数:index < ⌊(t+1)/4⌋。
decode 中可能读到尚未写入的缓存;prefill 中则可能读到已经并行算出的未来条目。两条路径都错,却不一定以同一种症状出现。
先写出张量形状与单位,再代入数字。
把推导变成可以检查的程序
用 NumPy 检查分组输出投影、Sinkhorn 行列和、路由 bias 与专家参数量。
python3 -m pip install numpy
python3 lab.pygrouped projection: equal
Sinkhorn max error < 1e-10
routing weights: [0.5, 2.0]
all checks passed修改参数前先预测结果,再运行检查。实验使用小张量和解析预算,在 CPU 上执行。
继续沿着实现走
配置核对:2026-10-20。动画中的 token、专家选择、显存块大小是教学示例。