MODEL ARCHITECTURE · 2026-10-20

DeepSeek-V4-Flash 架构:
一个 token 的旅程

近处看细节,远处读压缩记忆;每层只叫醒少量专家。

43Transformer 层
284B总参数
13B每 token 激活参数

← → 逐步讲解 · 拖动进度条回看 · F 全屏

01 / 从文字到预测

文字先变向量,再一层层改写

token ID Embedding · 4096 4 路残差 Block × 43 合并 + 输出头 每个 Block:mHC → Norm → Attention → mHC → Norm → MoE → mHC 残差流的宽度是 4 × 4096;Attention / MoE 的输入仍是 4096 维。
查表token ID 找到一行 embedding。
加工Attention 汇总上下文,MoE 变换当前表示。
预测输出头为词表中的候选 token 打分。
工程细节

每个算子先对齐四个维度

B 是请求数,S 是本次处理的 token 数,d 是隐藏宽度。进入一个 Block 的残差多一条大小为 4 的轴。

位置完整形状单 token 的元素数
残差输入[B, S, 4, 4096]16,384
mHC 汇合后[B, S, 4096]4,096
Q / shared KV[B,S,64,512] / [B,S,512]32,768 / 512
专家中间态[被路由的 token 数, 2048]2,048

decode 时通常 S=1;prefill 的 S 可以很大。B×S 决定 GEMM 行数。Mi 表示 2²⁰:1 MiB=2²⁰ 字节,1 Mi 个参数=2²⁰ 个参数。

工程细节

低秩 Q:两次矩阵乘法接起来

X[B·S,4096] → Qr[B·S,1024] → Q[B·S,64·512]
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)

两块权重共有 4096×1024 + 1024×32768 = 36 Mi 个参数。直接投影需要 128 Mi;低秩节省权重,但多出一次中间读写与归一化。

02 / 层的节奏

每层看多远,由层号决定

SWA L1 SWA L2 CSA L3 HCA L4 CSA L5 HCA L6 … 中间层 CSA L43 SWA:最近 128 token CSA:局部 + 压缩后选读 HCA:局部 + 高压缩全读
开头两层Pro 用 HCA,Flash 用纯 SWA。
从第 3 层开始CSA、HCA 交替,主干最后一层为 CSA。
一直保留局部窗口CSA 和 HCA 都同时看最近 128 个 token。
03 / 四路残差

四条信息通路,共用一个子层

残差流 1 新流 1 残差流 2 新流 2 残差流 3 新流 3 残差流 4 新流 4 Pre-mix Norm + 子层 Post-mix B:4 × 4 残差混合矩阵
Pre-mix把四路加权汇成一个 d 维输入。
子层照常计算Attention 和 MoE 各有一套 mHC 包裹。
Post + residual mix新结果分回四路;B 由 Sinkhorn 约束,行列和约为 1。
工程细节

mHC 真正混合的是什么

z = Σᵢ preᵢ · xᵢ  yⱼ = postⱼ · F(z) + Σᵢ Bᵢⱼ · xᵢ
张量形状如何得到
x / z[B,S,4,4096] / [B,S,4096]4 路残差加权成一个子层输入
pre / post / B4 / 4 / 4×424 个动态系数,经约束后参与混合
生成系数的权重[24, 16384]Attention 与 MoE 各有一套

B 做 20 轮 Sinkhorn 行列归一化,使行列和接近 1;pre 与 post 有自己的门控约束,不需要加和为 1。B 的两个轴分别对应旧流和新流。

逐步演算 · 教学数值

四路残差,算出一个具体输入

x₁ = 2权重 0.1 → 0.2x₂ = 4权重 0.2 → 0.8x₃ = 6权重 0.3 → 1.8x₄ = 8权重 0.4 → 3.2加权和0.00

加权输入是 6。取 F(z)=2z=12、B=I、post=[.25,.5,.75,1],新残差为 [5,10,15,20]。这些教学系数已给定;真实系数由当前残差动态生成。

04 / Shared-KV MQA

很多个问题,共用一本笔记

h ∈ R^4096 64 个 query 头 1 份 shared KV 各头分别读同一份 KV 512 维 = 448 内容 + 64 RoPE 输出投影 → d 维
Query不同头从不同角度询问上下文。
Shared KV同一个 512 维向量同时充当 K 和 V。
位置末尾 64 维用 RoPE 带上位置信息。
工程细节

局部和压缩记忆,共用一次归一化

aⱼ = q·kⱼ / √512  Z = exp(sink) + Σⱼ exp(aⱼ)
o = Σⱼ exp(aⱼ)·kvⱼ / Z

局部条目和选中的压缩条目组成同一个候选集合。每个头还有 learned sink:它增加分母,没有对应的 value 输出。

一维教学例:两个条目的分数为 log2、0,value 为 4、10,sink 分数为 0。

o = (2×4 + 1×10)/(2+1+1) = 4.5

如果删掉 sink,输出变为 6;如果先各自 softmax 再相加,权重也会改变。对齐参考实现时,这两处都要检查。

工程细节

输出投影也分组,并且降低中间维度

[64,512] → [8,4096] → [8,1024] → [4096]
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 个头。第一段共 32 Mi 参数,第二段 32 Mi,共 64 Mi。把所有头直接映射回 d 需要 128 Mi 参数;这里两段合计 64 Mi。

05 / CSA 先压缩

把连续信息,整理成更少的条目

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 压缩条目 1 压缩条目 2 压缩条目 3 压缩条目 4 每新增 4 个 token 产出一条;CSA 的池化窗口还覆盖前一组,形成重叠。
学习怎么汇总用投影和门控加权池化生成条目。
每 4 个产出一条重叠池化照顾组间边界。
只用已到达的信息完整组才可见;未完成组留在压缩状态中。
工程细节

压缩是逐通道的 learned pooling

vₜ = Wkv xₜ  gₜ = Wgate xₜ + apeₜ
cⱼ = Σₜ softmaxₜ(gₜⱼ) · vₜⱼ

softmax 沿 token 轴做;每个通道可以重视不同 token。pooling 后做 RMSNorm,再对末尾 64 维施加 RoPE。

CSA 步长为 4。每个 token 投影成 2×512 维:上一组用前半,当前组用后半,合成最多 8 个 token 的重叠窗口。

HCA 每 128 个 token 一组,无重叠。压缩条目的 RoPE 位置取当前组起点:0、128、256……。

逐步演算 · 教学数值

一个通道,逐项算出压缩结果

v₁ = 2权重 0.1 → 0.2v₂ = 4权重 0.2 → 0.8v₃ = 8权重 0.3 → 2.4v₄ = 10权重 0.4 → 4加权和0.00

结果为 7.4。这里展示四个有效候选;第一组没有前一组,缺失位置的 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 会看到组内未来信息。

06 / CSA 再选读

先用小索引器挑,再做大注意力

当前 query Indexer 打分 c1 0.12 c2 0.83 c3 0.25 c4 0.91 c5 0.14 c6 0.38 c7 0.61 c8 0.08 c9 0.77 c10 0.17 c11 0.32 c12 0.2 Flash:最多 512 条 Pro:最多 1024 条
小计算扫一遍Indexer 看可见的压缩条目,产生相关性分数。
大计算少读一些Flash 的主注意力最多读 512 个压缩条目。
局部细节另保留最近 128 token 一起进入注意力;图中仅示意选 3 / 12。
工程细节

少读 KV 之前,Indexer 仍要给历史打分

scoreⱼ = Σₕ wₕ · ReLU(qₕᵀ kⱼ) → top-k(score)

Indexer 使用 64 个 128 维头,共享一份 128 维压缩索引缓存。w = weights_proj(x) / √128 / √64,可正可负;它不是 softmax 概率。TP 时先 all-reduce 分数,再选条目。

上下文长度压缩候选 N/4Flash 主注意力压缩读数
32,7688,192512
131,07232,768512
1,048,576262,144512

top-k 固定了主注意力读数,Indexer 的候选扫描仍随上下文增长。prefill 参考路径还会构建 query×候选分数,必须把这项计入临时显存。

07 / HCA 高压缩

把远处概括得更短,就能全部读

128 token 1 条压缩 KV 128 token 1 条压缩 KV 128 token 1 条压缩 KV 128 token 1 条压缩 KV 当前 query 读取所有已经完成的压缩组
128 → 1远处信息以更粗的粒度保留。
不再做 top-k 选择主注意力读全部可见的压缩条目。
粗细两条路远处用摘要,最近 128 token 保留细节。
工程细节

长上下文时,先数真正访问的条目

32,768 token 的单层每头访问条目Attention 核心 FLOP / query
普通全注意力32,7684×64×32768×512 ≈ 4.29G
Flash CSA128+512=6404×64×640×512 ≈ 83.9M
Flash HCA128+256=3844×64×384×512 ≈ 50.3M

两个乘加阶段分别是 QK 和加权 value,合计约 4HCd。读数少了,但 Indexer 与压缩器仍需要计算;小 batch 也可能被 kernel 启动和不连续访问拖慢。

N=65,536 时 HCA 的压缩读数达到 512,与 CSA 的 top-k 相同;再往后 HCA 继续增长。

08 / Mixture of Experts

256 个候选专家,每次选择 6 个

专家就是各自拥有权重的一小段前馈网络。图中只画出部分候选。

token 向量 Router E1 E2 E3 E4 E5 E6 E7 E8 E9 E10 E11 E12 共享专家:每 token 都算 加权和 → 输出
前 3 层token ID 查预定专家映射,仍然激活 6 个。
后续层Sqrt(Softplus) 评分,选择 6 个路由专家。
每层都有共享专家专家中间维度 2048;返回结果做加权合并。
工程细节

选谁和给多少权重,是两次计算

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。Flash 的 route_scale=1.5,因此六个路由权重之和是 1.5,共享专家另外相加。

工程细节

一个专家,三块矩阵

gate = W₁x up = W₃x E(x)=W₂[SiLU(gate) ⊙ up]
项目计算参数量
一个专家3×4096×204824 Mi
43 层全部路由专家43×256×3dm277.025 B
每 token 经过的专家参数43×(6+1)×3dm7.575 B

实际先把 up 限制在 [−10,10]、gate 上限设为 10,再做 SiLU 与逐元素乘法;路由权重融合在 W₂ 之前。表中未计 Attention、embedding、路由器、mHC 和 MTP。所有候选专家权重仍需存储。

09 / 词表与 MTP

把理解变成下一步的选择

最后一层:4 路残差 合并 + RMSNorm 词表 logits 采样下个 token MTP 深度 1:额外预测下一步 主干预测下一个 token;MTP 可用于草稿生成,再由主干验证。
主干输出词表中每个候选得到一个分数。
采样选出 token,追加到上下文,继续下一轮。
MTP 辅助多预测一步为投机解码提供草稿。
工程细节

短输入一致,长输入开始偏:检查边界

教学故障:参考输出与自写实现,在 3 token 时一致;4 token 时偏离;第 8 token 偏差再次增大。

检查应当满足最有价值的证据
压缩触发处理 t=3、7 时追加条目逐步打印 visible 和写入索引
重叠组第二组使用前组前半+当前组后半对比 pooling 前的 8×512 输入
prefill / decode同一前缀最后位置输出接近固定权重、禁用采样,比较中间张量

从第一个不一致的张量往前找。若 pooled KV 已不同,调采样温度和路由器都不会修复;若 KV 一致,再检查因果索引与 joint softmax。

10 / 两种尺寸

同一套思路,不同的计算预算

配置 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 使用的权重规模。

练习 1

为一个真实输入写出形状和字节数

B=2、S=16,Flash 的残差、Q、shared KV 均按 BF16。各有多少元素、占多少 KiB?只算这三个逻辑张量。
展开参考解答

残差:2×16×4×4096=524,288 元素,乘 2 B = 1024 KiB。Q:2×16×64×512=1,048,576 元素,占 2048 KiB。

shared KV:2×16×512=16,384 元素,占 32 KiB。头数改变 Q 的大小;shared KV 无须再乘 Q 头数,也没有额外一份 V。

先写出张量形状与单位,再代入数字。

练习 2

给选择加 bias,别给权重加 bias

教学路由器只有 4 个专家、选 2 个。scores=[1,2,3,4],bias=[4,0,0,0],route_scale=1.5。选哪些?最终权重多少?
展开参考解答

选择分数为 [5,2,3,4],所以选 E1 和 E4。归一化仍使用原始分数 [1,4],除以 5,再乘 1.5,得到 [0.3, 1.2]。

若误用 [5,4] 归一化,会得到 [0.8333, 0.6667],改变模型。共享专家没有参加这个 top-k,也不参加这次归一化。

先写出张量形状与单位,再代入数字。

练习 3

定位一个只在整组边界出现的错误

程序在 query 位置 t 使用 compressed_index ≤ ⌊(t+1)/4⌋。t=6、t=7 时分别会放行哪些索引?正确规则是什么?
展开参考解答

t=6 时错误规则放行 0、1,但只有第 0 组已完成。t=7 时放行 0、1、2,但只有前两组已完成。正确规则是严格小于完成组数:index < ⌊(t+1)/4⌋。

decode 中可能读到尚未写入的缓存;prefill 中则可能读到已经并行算出的未来条目。两条路径都错,却不一定以同一种症状出现。

先写出张量形状与单位,再代入数字。

动手实验

把推导变成可以检查的程序

用 NumPy 对比整段与逐 token 的重叠 pooling,检查因果边界、路由 bias 与 mHC 数值。

python3 -m pip install numpy
python3 lab.py
prefill == streaming: True
pooled scalar: 7.400000
routing weights: [0.3, 1.2]
all checks passed

下载 lab.py

修改参数前先预测结果,再运行检查。实验使用小张量和解析预算,在 CPU 上执行。

11 / 参考与延伸阅读

继续沿着实现走

DeepSeek-V4-Flash 官方模型卡Preview 模型的总参数、激活参数与权重精度。
官方 config.json · 推理实现 model.py层数、维度、压缩周期、mHC、路由与 cache 更新。
DeepSeek-V4 技术报告§2 架构,§3.1 专家通信与计算重叠,§4.2 模型配置。
技术报告精读 · Flash 逐卡推理 · V4-Pro 架构

配置核对:2026-10-20。动画中的 token、专家选择、显存块大小是教学示例。