DeepSeek 从 2024 年初的 V1 到 2026 年 9 月的 V4.1-Flash,注意力结构改了六轮:MHA → GQA → MLA → DSA → CSA / HCA → CSA2。每一轮都有明确的目标——砍掉 KV cache 或注意力计算里的某一个量。这篇把七代结构放在一起对比,每种结构配一张小算子级的张量流图(单个算子怎么进怎么出,不涉及 kernel 融合),最后给总表。
口径说明:所有结构与数字以 HuggingFace 上 deepseek-ai 各 repo 的官方推理代码和 config.json 为准——V1 两个 repo 不带代码、model_type 是 llama,以其声明的 transformers 4.33.1 的 LlamaAttention 为准;V2/V3 以 repo 自带的 modeling_deepseek.py 为准;V3.2 与 V4.1 以 repo 的 inference/model.py 为准;V4 以 inference/ 目录与 modeling_deepseek_v4.py 为准。论文没写、只在代码里能看到的细节会单独标出。CSA / HCA 的完整推导在本站 DeepSeek-V4 连载的第 1–3 篇,这篇不重复展开,只做对比。
注意力的账挂在五个量上
先想清楚 decode 时一个 token 的注意力要花多少钱。每层每 token 要把前文所有缓存的 K、V 读一遍,和当前 query 算内积、softmax、再加权求和。所以
KV cache / token=2⋅nkv⋅c⋅L⋅b,注意力 FLOPs / token≈4⋅nh⋅c⋅n看⋅L.
nkv 是 KV 头数,c 是每条缓存条目的维度,L 是层数,b 是每个元素的字节数,n看 是每个 query 实际要看的条目数,nh 是 query 头数。能拧的旋钮就这么几个:头数、条目维度、存的条目数、看的条目数、层数与精度。七种结构各自拧了不同的旋钮——这就是它们全部的区别。
思维导图:每一代改了什么、怎么改的
总览:每一代结构的目标是什么、靠什么手段达到。账摆在那儿——KV cache 正比于头数 × 条目维度 × 条目数 × 层数 × 每数字节,注意力 FLOPs 正比于头数 × 维度 × 看的条目数;MHA → GQA → MLA → DSA → CSA/HCA → CSA2 每一代挑其中一个或几个因子砍。
读法:中间一列是被砍的量,右边是动手的结构与手段。注意最后一代 CSA2 砍的「层数」是前几代都没碰过的维度——它问的是「40 层每层都自己压一份 KV、自己跑一遍索引,真的有必要吗」。
MHA(DeepSeek-V1-7B / DeepSeekMoE-16B):基线
目标:基线结构,表达力满配——每个 query 头都有自己独立的 K、V。DeepSeek 的第一代模型没有任何自研注意力:deepseek-llm-7B 和 DeepSeekMoE-16B 都是标准 MHA(分别是 32/32 头、16/16 头),而且 deepseek-llm 的 repo 里连模型代码都没有,直接复用 transformers 的 Llama 实现(config 里 model_type: llama、transformers_version: 4.33.1)。
V(不过 RoPE)(当前 token)4096q_proj4096 → 4096k_proj4096 → 4096v_proj4096 → 4096view 分头[32, 128]view 分头[32, 128]view 分头[32, 128]RoPE(只对 Q / K)full:全部 128 维KV cache16 KiB / token / 层scores[32, 1, t]+ causal mask上三角softmax(fp32)→[32, 1, t][32, 128]concat 32 头→ 4096o_proj4096 → 4096out4096DeepSeek-V1-7B 的 MHA(decode 一步,t = cache 里已有的 token 数)。32 个头各有自己的 K、V,KV cache 每 token 每层 16 KiB;RoPE 是 full 的(全部 128 维,θ = 10000),只加在 Q/K 上。这是后续所有结构的基线形态。
小算子流水(decode 一个 token,t 条缓存):q/k/v_proj 各自把 4096 维投到 4096 维 → 分成 32 头 × 128 维 → Q、K 加 full RoPE(θ=104,无长度外推)→ K、V 追加进 cache → softmax(QK⊤/128+causal mask) → 乘 V → 拼回 4096 维 → o_proj。
账:每 token 每层 2×32×128×2B=16 KiB,30 层共 480 KiB;4096 token 的上下文约 1.9 GiB。代价:cache 和访存都正比于头数,模型越大越贵——67B 若按 MHA 算是每 token 3 MiB,这就是下一代要解决的问题。
GQA(DeepSeek-V1-67B):第一刀砍在 KV 头数上
目标:decode 是显存带宽瓶颈,KV cache 的大小和每步要读的字节数都正比于 nkv。GQA(Google 2023 年提出,DeepSeek 在 67B 上采用)把 K、V 砍到 8 组,64 个 query 头每 8 个共享一组。
V(不过 RoPE)cache 在复制之前[64](当前 token)8192q_proj(64 头)8192 → 8192k_proj(8 组)8192 → 1024v_proj(8 组)8192 → 1024view 分头[64, 128]view 分头[8, 128]view 分头[8, 128]RoPE(只对 Q / K)full:全部 128 维KV cache4 KiB / token / 层repeat_kv × 8expand 逻辑复制,不占 cache→scores[64, 1, t]+ causal mask上三角softmax(fp32)→[64, 1, t][64, 128]concat 64 头→ 8192o_proj8192 → 8192out8192DeepSeek-V1-67B 的 GQA(decode 一步,t = cache 里已有的 token 数)。64 个 query 头共享 8 组 K/V;注意 KV cache 画在 repeat_kv 之前——cache 只存 8 组(4 KiB / token / 层,若用 MHA 则是 32 KiB),repeat_kv 的 8 倍复制只是算分时的逻辑 expand,不占显存。RoPE 是 full 的(全部 128 维,θ = 10000),只加在 Q/K 上。
与 MHA 的唯一差别是 repeat_kv:读出 cache 后把 [t, 8, 128] 在第 1 维上 expand 成 [t, 64, 128] 再算分,注意力公式本身不变。
账:每 token 每层 2×8×128×2B=4 KiB,95 层共 380 KiB——比小它十倍的 7B(480 KiB)还少,是假想 MHA 版 67B 的 1/8。代价:KV 侧的表达容量降到 8 组,质量微降但远小于 8 倍的账(这也是 GQA 论文的消融结论);另外 cache 不因为 repeat 变大,这是个常见误解。
MLA(DeepSeek-V2 / V3):砍每个条目的维度
动机:GQA 还在按「组」存完整的 K 和 V,组数再少每组还是 2c 个数。V2 问了一个更激进的问题:能不能把 K、V 联合压成一个低秩 latent,只缓存 latent?这就是 MLA(Multi-head Latent Attention),公式是低秩联合压缩:
ctKV=WDKVht,ktC=WUKctKV,vtC=WUVctKV,
ctKV 只有 512 维。由矩阵乘法结合律,WUK 可以吸收进 WQ、WUV 可以吸收进 WO,于是推理时直接在 latent 空间做注意力、128 个 query 头共享同一条 latent——训练时是满配多头,推理时折叠成 MQA 形态。但 RoPE 和吸收是冲突的:位置矩阵会夹在 WQ 与 WUK 之间让吸收不可行,所以 V2 把位置从内容里解耦出来——单独造一条 64 维、所有头共享、带 RoPE 的 key ktR 拼在每个头的 key 后面。最终每 token 每层只缓存 512+64=576 个数。
RoPE 后拼回(共享,只存一份)[128,128]key [128,192]读全量 K、V替代query [128,192]7168q_a_proj7168 → 1536RMSNorm1536q_b_proj1536 → 24576view → [128 头, 192]split:[128,128][128,64]concatquery [128 头, 192]RoPE:只加 64 维 rope 分量,YaRN factor 40kv_a_proj_with_mqa7168 → 576split[512][64](全头共享一份)RMSNorm512kv_b_proj512 → 32768view → [128 头, 256]split:[128,128][128,128]concat:key [128 头, 192]KV cache —— HF repo modeling 代码(naive)存展开的 K [128,192] + V [128,128]40,960 数 / token / 层官方推理代码(absorb,默认)只存 latent 512 + rope 64 = 576 数两条路径差 71×核心注意力(128 头)scalesoftmax(fp32)→输出 [128, 128]reshape → 16384o_proj16384 → 7168out7168MLA(V3 数字,naive 展开路径):K、V 由 512 维 latent 现场展开,RoPE 只加在 64 维共享 rope 分量上,所以推理时可以把上投影吸收掉、直接缓存 latent。两个 cache 框是本图重点:HF 参考实现(modeling_deepseek.py)只有 naive 路径,实际存展开的 K/V(40,960 数 / token / 层);官方推理代码默认 absorb,只存 512 + 64 = 576 数——两条路径差 71×。
账:latent 口径下 V3 全 61 层每 token 约 68.6 KiB(BF16),是同头数 MHA 的 1/57、GQA-8 的 28%,论文的口径是「相当于 GQA 只剩 2.25 组」。注意两个容易搞错的点:MLA 不省 FLOPs(naive 路径还多一次 WUK/WUV 展开,absorb 路径下内积维数反而从 192 涨到 576),它省的是显存和 decode 访存;query 侧的低秩压缩(ctQ,1536 维)也不省 KV cache,省的是训练激活。另外 softmax 的 scale 按拼接后的头维算是 192−1/2,开了 YaRN 长上下文还要乘 mscale2(V3 ≈ 0.1352),这是只读代码才能看到的细节。
DSA(DeepSeek-V3.2):砍每个 query 看的条目数
动机:MLA 解决了「存」,但没解决「算」——1M 上下文下每个 query 仍要对全部 1M 条 latent 打分,每层约 82 GFLOP/token。DSA(DeepSeek Sparse Attention)的思路是:大部分 token 对当前 query 根本不重要,能不能只看 top-k 个?
lightning indexer(只决定看哪 2048 个 token;打分仍是 O(L),但常数小、FP8)主注意力(MLA,与 V3 同骨架)共享[64]FP8(FP8)indices [2048]cache 一字节没省[128 头, 192]7168wq_a(共享)7168 → 1536q_normRMSNorm[1536]主路 / indexer 共享weights_proj(fp32)7168 → 64indexer wq_b1536 → 8192= 64 头 × 128RoPE 前 64 维非交错(≠ MLA 交错),YaRN ×40Hadamard 旋转+ FP8 量化(e4m3)wk7168 → 128,单头LayerNorm128RoPE 非交错 + Hadamard+ FP8 量化indexer k_cache(FP8)+ scale128 B + 4 B / tokenfp8_index kernel[L] fp32≈ 16.4 GFLOP / token @ 1M+ causal mask → top-2048indices [2048]< 2048 时等价稠密wkv_a7168 → 576splitlatent 512(RMSNorm)+64(RoPE 交错)KV cache(主)576 维 / token / 层与 V3 相同,一字节没省主注意力 wq_b1536 → 128 × 192按 indices gather2048 条 × 576 维参考实现用mask 模拟生产:FlashMLA 真 gather稀疏主注意力(MQA 模式)128 头 × 2048 条scale 同 V3 ≈ 0.1352≈ 168 MFLOP @1M(dense 82 GFLOP 的 1/488)o_proj16384 → 7168DSA 回答「省的是计算不是显存」:indexer 给每个 query 从 L 条里选出 top-2048(ReLU 打分、FP8,≈16.4 GFLOP/token @1M),主注意力只对这 2048 条算(≈168 MFLOP,是 dense 82 GFLOP 的 1/488);KV cache 仍是每 token 每层 576 维,和 V3 一字节不差(还多了 indexer 自己的 128 B FP8 键)。序列短于 2048 时等价稠密。
indexer 是个刻意做小做快的小网络:query 复用 MLA 的 ctQ(共享 wq_a + RMSNorm,只加自己的 wq_b 投出 64 头 × 128 维),key 只有一条 128 维(64 个 query 头共享,MQA 式),打分是
It,s=j=1∑64wt,j⋅ReLU(qt,jI⋅ksI),
选 ReLU 而不归一化是为了吞吐(论文原话 for throughput consideration),全程 FP8、打分前先 Hadamard 旋转。因果 mask 加在打分之后、top-k 之前,保证选不出未来 token;min(2048, t) 意味着序列短于 2048 时 DSA 等价于稠密。
账:核心注意力从 82 GFLOP/token/层 降到 168 MFLOP(约 1/488),代价是 indexer 自己的 16.4 GFLOP(仍是 O(n2),但常数小得多且 FP8)。省的是计算,不是显存——这是关于 DSA 最常见的误解。
CSA(DeepSeek-V4):先压缩,再在压缩块上检索
动机:DSA 的 cache 还是每个 token 一条 576 维,1M × 61 层约 46 GiB;而且 indexer 的打分随 n 线性涨。V4 把刀伸向存的条目数:每 m=4 个 token 压缩成一条 512 维条目,条目数除以 4,检索也在块级做。
压缩分支(写入一次,所有 query 共用)lightning indexer(只决定看哪 1024 个块)同一个:128 头 × 512128 条被选中的 1024 条块编号71687168 → 1536RMSNorm15361536 → 128 × 512逐头 RMSNorm+ 后 64 维 RoPE7168 → 512(单头)RMSNorm + RoPE(64)非 rope 维存 FP8滑窗 cache最近 128 条 × 512重叠压缩 4 → 1:7168 → 2 × 512RMSNorm + RoPE(块首位置)θ = 160000,YaRN ×16压缩 cache条 × 512indexer query1536 → 64 头 × 128,RoPE,FP4indexer 压缩每 4 token 一条 × 128,FP4top-1024 个块MQA 核心注意力128 头 × (128 + 1024) 条K = V,每头一个 sink输出后 64 维反旋转 RoPE(−t)让贡献只依赖相对距离分组投影16 组,每组 4096 → 102416384 → 7168一层 CSA(Pro 维度)。只有一条 512 维的 KV 向量,既当 K 又当 V,所有 128 个 query 头共用;推理时真正缓存的是右侧两个 cache 框加 indexer 自己的压缩键。核心注意力的每个 query 看 128 条滑窗 + 1024 条被选中的压缩条目。
机制骨架(详细推导见连载第 1 篇):压缩不是平均池化,而是逐通道各学一套 softmax 权重的加权求和,相邻条目重叠 2m=8 个 token 防止块边界信息断裂;压完的条目 K = V——同一条 512 维向量既做内积又做加权和,所有 128 个 query 头共享(MLA 的 MQA 模式干脆连 WUK/WUV 都不要了)。lightning indexer 升级成块级(选 top-1024 块,覆盖 4096 个 token);最近 128 个 token 的未压缩 KV 走滑窗分支,和压缩条目拼进同一个 softmax。K = V 会让输出带上绝对位置,所以输出最后 64 维要乘 R−t 反旋转回相对位置;每个头还有一个可学习的 attention sink logit 只加在 softmax 分母上(这两件事的推导见第 3 篇)。
账:一条压缩条目存 576 B(64 维 rope 用 BF16 + 448 维 FP8),每 4 个 token 摊一条,CSA 层每 token 每层约 144 B;Pro 全 61 层在 1M 上下文下约 5.3 GiB,是同规模 GQA-8 BF16 基线的约 2%。
HCA(DeepSeek-V4):压到不用检索为止
动机:同一个 V4 里还藏着另一种更极端的选择——如果把压缩率拉到 m′=128,1M 上下文也只产生 8192 条,密集全看都算得起,那 indexer 整台机器都可以扔掉。
压缩分支:1M token 只产生 8192 条,所以不用选128 条全部压缩条目71687168 → 1536RMSNorm1536 → 128 × 512逐头 RMSNorm+ 后 64 维 RoPE7168 → 512RMSNorm + RoPE(64)滑窗 cache最近 128 条压缩 128 → 1,不重叠:7168 → 512RMSNorm + RoPE(块首)softmax 在 128 个槽上压缩 cache条 × 512MQA 核心注意力128 头 × (128 +) 条K = V,每头一个 sink输出反旋转 RoPE(−t)分组投影 16 × (4096 → 1024)再 16384 → 7168一层 HCA(Pro 维度)。和 CSA 相比只有两处不同:压缩率从 4 变成 128 且不重叠;没有 indexer,query 看全部压缩条目。
和 CSA 的差异就三处:压缩率 4 → 128、不重叠、没有 indexer(可见集合由规则生成:全部已闭合条目 + 因果边界)。query 看 128 条滑窗 + 全部 n/128 条压缩条目(1M 时 8320 条)。每 token 每层摊到 576/128=4.5 B,几乎免费;代价是每条的「分辨率」低——所以 V4 让 CSA(精读、稀疏检索)和 HCA(粗读、全看)按层 1
交错,两种读法互补(层排布与动机见
第 3 篇)。
CSA2(DeepSeek-V4.1-Flash):砍层数维度和每个字节的精度
动机:V4 的 40+ 层每层都自己压缩一份 KV、自己跑一遍 indexer,但这些层看到的上下文是同一段——跨层的重复劳动。V4.1-Flash(2026.09,论文 2609.19969)问:能不能把 KV cache 沿层这个维度也共享掉?同时把每条的字节数从 FP8 压到 FP4。
Full:层 2/8/14(m=2)、层 20(m=1,无 wgate)Reindex:层 24/28/32/36Reuse:30 层跨层共享池 SharedAttentionRuntime(356 B/条 × 2.5 条/token = 890 B/token)[64,512]RoPE 前 latentindex_ktop-512写 compress_kv写 index_k写 topk_idxs层 20:写 candidates读 compress_kv、新 top-512读 index_k读 candidates写回 topk_idxs(覆盖)读 compress_kv读 compress_kv读 topk_idxs(最新)[B, N, 5120]mHC + norm 后Q 低秩(每层自有)wq_a 5120→1280+ q_norm →wq_b 1280→64×512RoPE 末 64 维[64, 512]indexer Q(每层自有)wq_b 1280→32×128weights_proj 5120→32FP4SWA 路径(每层自有)wkv 5120→512 + kv_normRoPE 末 64 → FP8ring cache 128 槽sparse_attn(每层自有)SWA 128 + 全局 top-512+ sink → 反旋转 RoPECompressorwkv、wgate 5120→512softmax(gate)⊙kv按 m 求和(不重叠)m=1 无 wgate(层 20)latent [N/m, 512]RoPE 末 64 维 → FP4E2M1 + E4M3 scale/16indexer Kwk 512→128 + k_normRoPE θ=160000 → FP4E8M0/32,无 Hadamard打分因果掩码 → top-512按位置 sort层 20 另产 candidates(2048 块 × 8 = 16384)输出投影(每层自有)wo_a 8 组 ×(4096→1024)wo_b 8192→5120Q 低秩(每层自有)wq_a 5120→1280+ q_norm →wq_b 1280→64×512RoPE 末 64 维[64, 512]重打分读共享 index_kcandidates 掩码→ top-512,sort写回 topk_idxs(覆盖)sparse_attn(每层自有)SWA 128 + 全局 top-512+ sink → 反旋转 RoPESWA 路径(每层自有)wkv 5120→512 + kv_normRoPE 末 64 → FP8ring cache 128 槽indexer Q(每层自有)wq_b 1280→32×128weights_proj 5120→32FP4;K 从共享池读本层不产 KV / index_k无 Compressor无 wk / k_norm输出投影(每层自有)wo_a 8 组 ×(4096→1024)wo_b 8192→5120Q 低秩(每层自有)wq_a 5120→1280+ q_norm →wq_b 1280→64×512RoPE 末 64 维[64, 512]无 indexer、无 Compressor注意力参数清单与纯 SWA 层相同SWA 路径(每层自有)wkv 5120→512 + kv_normRoPE 末 64 → FP8ring cache 128 槽sparse_attn(每层自有)读共享 compress_kv+ 共享 topk_idxs+ SWA 128 + sink → 反旋转输出投影(每层自有)wo_a 8 组 ×(4096→1024)wo_b 8192→5120compress_kv主 KV latent,FP4(288 B/条)index_kindexer K,FP4(68 B/条)topk_idxs最新 top-512 索引candidates(仅 decoder)≤ 2048 块 × 8 = 16384层 20 产出三种模式各自算什么、复用什么:Full 自己压 main KV(softmax(gate)⊙kv 按 m 个不重叠 token 求和、无 APE)、从 latent 投出 indexer K(无 Hadamard)、跑 indexer 产 top-512(层 20 另产 candidates 候选池),统统写进跨层共享池;Reindex 只带自己的 indexer Q,读共享 index_k 重打分(先经 candidates 掩码),把新 topk_idxs 写回;Reuse 只有自己的 Q / SWA / O,直接读共享 compress_kv + topk_idxs 进 sparse_attn。实线是层内数据流与写池,虚线是从池里读。
每个 CSA2 层被静态指派三种模式之一(都保留自己的 query 和自己的滑窗 KV):
- Full(4 层:2、8、14、20):完整跑一遍——自己压缩主 KV、从主 KV 投影出 indexer K、打分选 top-512,结果写进跨层共享池;
- Reindex(4 层:24、28、32、36):复用共享的主 KV 和 indexer K,只用自己的 indexer query 重新打分、选新的 top-k——选择可以逐层变,缓存不变;
- Reuse(30 层):连 top-k 索引都复用最近一次算出来的,直接做稀疏注意力,全层没有任何压缩/索引参数。
decoder 里还有一级 hierarchical indexer:第一个 Full 层(第 20 层)全量打分后额外选出 ≤2048 个高分块(×8 = 16384 个候选位置)组成候选池,后面的 Reindex 层只在池内打分,把深层索引的成本从 O(n) 砍成常数。相对 V4 的 CSA,压缩算子也简化了:不重叠、去掉了块内位置偏置;indexer K 从主 KV 投影而来,不再单独从 hidden states 压一路。
第 0 层:纯 SWA(window 128)第 1 层:纯 SWA(window 128)第 2 层:Full(m=2):自产 main KV + indexer K + top-512第 3 层:Reuse:复用共享 compress_kv + topk_idxs第 4 层:Reuse:复用共享 compress_kv + topk_idxs第 5 层:Reuse:复用共享 compress_kv + topk_idxs第 6 层:Reuse:复用共享 compress_kv + topk_idxs第 7 层:Reuse:复用共享 compress_kv + topk_idxs第 8 层:Full(m=2):自产 main KV + indexer K + top-512第 9 层:Reuse:复用共享 compress_kv + topk_idxs第 10 层:Reuse:复用共享 compress_kv + topk_idxs第 11 层:Reuse:复用共享 compress_kv + topk_idxs第 12 层:Reuse:复用共享 compress_kv + topk_idxs第 13 层:Reuse:复用共享 compress_kv + topk_idxs第 14 层:Full(m=2):自产 main KV + indexer K + top-512第 15 层:Reuse:复用共享 compress_kv + topk_idxs第 16 层:Reuse:复用共享 compress_kv + topk_idxs第 17 层:Reuse:复用共享 compress_kv + topk_idxs第 18 层:Reuse:复用共享 compress_kv + topk_idxs第 19 层:Reuse:复用共享 compress_kv + topk_idxs第 20 层:Full(m=1):自产 main KV + indexer K + top-512,另产 candidates;输入 = encoder 末态 H_20第 21 层:Reuse:复用共享 compress_kv + topk_idxs第 22 层:Reuse:复用共享 compress_kv + topk_idxs第 23 层:Reuse:复用共享 compress_kv + topk_idxs第 24 层:Reindex:读共享 index_k 重打分,写回 topk_idxs第 25 层:Reuse:复用共享 compress_kv + topk_idxs第 26 层:Reuse:复用共享 compress_kv + topk_idxs第 27 层:Reuse:复用共享 compress_kv + topk_idxs第 28 层:Reindex:读共享 index_k 重打分,写回 topk_idxs第 29 层:Reuse:复用共享 compress_kv + topk_idxs第 30 层:Reuse:复用共享 compress_kv + topk_idxs第 31 层:Reuse:复用共享 compress_kv + topk_idxs第 32 层:Reindex:读共享 index_k 重打分,写回 topk_idxs第 33 层:Reuse:复用共享 compress_kv + topk_idxs第 34 层:Reuse:复用共享 compress_kv + topk_idxs第 35 层:Reuse:复用共享 compress_kv + topk_idxs第 36 层:Reindex:读共享 index_k 重打分,写回 topk_idxs第 37 层:Reuse:复用共享 compress_kv + topk_idxs第 38 层:Reuse:复用共享 compress_kv + topk_idxs第 39 层:Reuse:复用共享 compress_kv + topk_idxs第 40 层:DSpark 草稿层:纯 SWA(window 128)第 41 层:DSpark 草稿层:纯 SWA(window 128)第 42 层:DSpark 草稿层:纯 SWA(window 128)02814202428323639encoder(0–19)decoder(20–39)decoder 全局 KV 由(encoder 末态)逐层投影(式 1)40–42:DSpark 草稿层(纯 SWA)Full ×4(层 2/8/14/20)Reindex ×4(24/28/32/36)Reuse ×30纯 SWA ×2(层 0/1)Full = 4、Reindex = 4、Reuse = 30;SWA 仍逐层自产(window 128,FP8),CED 只改全局 KV 的来源V4.1-Flash 层排布(0 基,43 格 = 40 主干 + 3 DSpark 草稿层)与 CED 的数据来源:分界线以下 20 层是 encoder、以上 20 层是 decoder;decoder 每一层的全局 KV 都不从自己层的隐藏态产生,而是由 encoder 末态 H20 逐层投影(论文式 1),所以 prefill 只需算前半层。层 20 是 decoder 唯一的 Full 层(m=1),它的输入就是 H20。
配合 CED(Causal Encoder-Decoder):下 20 层是 encoder、上 20 层是 decoder,decoder 的全局 KV 一律从 encoder 末态投影(SWA 仍每层自产),prefill 只需完整跑前半段——所以 552B 参数的模型 prefill 每 token 只激活 8B、decode 激活 16B。
账:主 KV 整条 512 维 FP4(E2M1 + 每 16 通道一个 E4M3 scale)= 288 B/条,indexer K 68 B/条,合计 356 B;每个 token 摊到 2.5 条(encoder 3 个 Full 层 m=2 → 1.5 条 + decoder 1 个 Full 层 m=1 → 1 条),乘起来正好是官方宣称的 890 B/token——V4-Flash 的约 1/4、V1 的 1/437。
总结:七代结构一张表
| 结构 | 首次出现 | 砍的量 | 每 token KV cache | 每个 query 看多少 | 注意力形态 | 主要代价 |
|---|
| MHA | V1-7B(2024.01) | —(基线) | 480 KiB(30 层) | 全部 n | 32 头各自 KV | cache ∝ 头数 |
| GQA | V1-67B(2024.01) | KV 头数 64→8 | 380 KiB(95 层) | 全部 n | 64 Q 头共享 8 组 KV | KV 容量降 8× |
| MLA | V2 / V3(2024.05/12) | 条目维度 →576 维 | 68.6 KiB(61 层 BF16) | 全部 n | 推理时折叠成 MQA | 不省 FLOPs |
| DSA | V3.2(2025) | 看的条目数 →2048 | 不变(+132 B 索引键) | top-2048 token | MLA + indexer | indexer 仍 O(n2) |
| CSA | V4(2026.06) | 存的条目数 ÷4 | ~176 B/层(含 indexer 键) | top-1024 块 + 128 滑窗 | K=V 单头 MQA | 压缩丢细节 |
| HCA | V4(2026.06) | 存的条目数 ÷128 | 4.5 B/层 | 全部 n/128 条 + 128 滑窗 | 同上,无 indexer | 分辨率低,只能粗读 |
| CSA2 | V4.1-Flash(2026.09) | 层数 × 精度(FP4) | 890 B(全部层) | top-512 块 + 128 滑窗 | 跨层共享 KV/索引 | 30/40 层没有自己的全局 KV |
V1 → V4.1:437×100 B1 KB10 KB100 KBDeepSeek-V1-67B(GQA-8,BF16)2 × 8 × 128 × 2 B × 95 层389,120 BDeepSeek-V3(MLA latent,BF16)576 × 2 B × 61 层70,272 BDeepSeek-V3.2(+ DSA)V4.1 论文 Figure 1b(FP8 口径)48,068 BDeepSeek-V4-Pro本站手算口径(attn-csa-hca.md §4)5,424 BDeepSeek-V4-FlashV4.1 论文 Figure 1b3,514 BDeepSeek-V4.1-Flash(CSA2 + FP4)356 B/条 × 2.5 条(Figure 1b)890 B对数轴(log10):相邻刻度 10×;V4.1 的 890 B 只计 HBM 常驻的全局 KV,SWA ring buffer 与长度无关、不计入每 token 全局 KV cache 字节数。口径差异:V3.2 / V4-Flash / V4.1 三行取自 V4.1 论文 Figure 1b;V4-Pro 一行是本站按 config 手算(≈5.4 KB,含 CSA + HCA + indexer + 滑窗),与论文口径的差别在量化 scale 与滑窗的记法。前两行(V1 GQA、V3 MLA)由结构参数直接算出。
最后收几个最常见的误解:「DeepSeek 从 V1 就用 MLA」——V1 是 MHA/GQA,MLA 首现于 V2;「MLA 缓存展开后的 K/V」——只有 HF 参考实现这么干,官方推理代码缓存的是 576 维 latent;「MLA/DSA 省 FLOPs/省 cache」——MLA 省显存不省算力,DSA 省算力不省显存,两者正好互补;「CSA 的 K、V 是分开的两条」——K = V 同一条 512 维;「CSA2 是 CSA 的小改版」——它动了层间共享、CED 和存储精度三个维度,是架构级改动。
从趋势看,这条线的演化方向很清晰:先在注意力内部省(头数、维度),再在序列上省(看的、存的条目数),最后跳出单层——跨层共享、编码器/解码器分工、按 bit 抠存储。注意力层越来越不像一个矩阵乘法,越来越像一个带索引、带缓存层级、带精度分级的检索系统。
评论