Skip to content

MLA FLOPs、RoPE、Query Compression 与 MTP 笔记 ​

本文关心的是 DeepSeek-V2 MLA 里一个很关键的问题:MLA 到底省了什么,FLOPs 又被转移到了哪里,RoPE等其他部分在MLA中的计算方式会有什么变化?本文会先比较 MHA 和 MLA 在 prefill/training/decode 中的 FLOPs 差异,代入论文中真实参数分析FLOPs计算的具体细节,再顺着同一套记号讨论 RoPE、query compression 和 MTP。这样做可以让很多看起来像“架构设计”的问题,其实可以直接从矩阵乘顺序和 cache format 中看出来。

基本上,全文可以分成四部分:

  • MHA 和 MLA 在 prefill/training/decode 中的 FLOPs 对比。
  • 为什么 RoPE 会阻碍 MLA absorption。
  • 为什么 MLA 里还要压缩 Q。
  • 为什么 MTP 会削弱 MLA 的加速收益。

MLA sketch

Notation ​

为方便推导,本文把一次 multiply-add 记为 2 FLOPs, 并且只统计矩阵乘的 FLOPs,暂时忽略 softmax、mask、normalization、RoPE elementwise rotation 和 bias term。MLA 推导里用到的矩阵的主要维度如下:

SymbolMeaningShape / role
Tprefill sequence lengthnumber of tokens in prefill or training
tdecode KV-cache lengthnumber of cached historical tokens
dhidden sizexi∈Rd
hnumber of attention headshead index s=1,…,h
dkper-head query/key dimensionqis,kis∈Rdk
dvper-head value dimensionvis,ois∈Rdv
dcMLA KV latent dimensionciKV∈Rdc
dqcMLA query latent dimensionciQ∈Rdqc

为了避免 head index 掩盖 shared latent,下面更有用的 tensor 视角是:

WQ,WK:(h,d,dk),WV:(h,d,dv),Wo:(h,dv,d),WDKV:(d,dc),WUK:(h,dc,dk),WUV:(h,dc,dv),WDQ:(d,dqc),WUQ:(h,dqc,dk).

因此 CKV:(T,dc) 和 CQ:(T,dqc) 是所有 heads 共享的 latent,而 Q,K,V 位于 head space,例如 Q:(T,h,dk),V:(T,h,dv)。下面有些公式仍然保留单 head slice 来方便阅读,但真正的 storage/computation convention 应该按这里的 tensor shape 理解。

Baseline FLOPs ​

先从 baseline multi-head attention (MHA) 开始, 推导每一步的矩阵乘和 FLOPs。便于与MLA对比,理解MLA的优化到底在哪里。

MHA Prefill ​

首先从输入 X 计算 Q,K,V projection:Q=XWQ,K=XWK,V=XWV.

对于 MHA prefill,完整 score 表达式 S ,attention output O ,output projection Y 计算公式如下,其中 P 是 softmax 之后的 attention matrix:

S=QK⊤=X(T,d)WQ(h,d,dk)WK⊤(h,dk,d)X⊤(d,T).O=PV=P(h,T,T)X(T,d)WV(h,d,dv).Y=O(T,h,dv)Wo(h,dv,d).

对于attention score S 的计算,从 FLOPs 角度看,关键是让 dominating 的 T2 项系数尽可能小。由于 dk<d,合理的计算顺序是:

X(T,d)WQ(h,d,dk)→Q(T,h,dk),FLOPs=2Thddk.X(T,d)WK(h,d,dk)→K(T,h,dk),FLOPs=2Thddk.Q(T,h,dk)K⊤(h,dk,T)→S(h,T,T),FLOPs=2hT2dk,

然后计算 O,Y 的 FLOPs,这里可以把 value/output path 合在一起看。按 tensor shape 写,可以理解为 head-wise 的四矩阵乘法:

P(h,T,T)X(T,d)WV(h,d,dv)Wo(h,dv,d)→Y(T,d).

最优顺序是先把 X 投影到 dv,再乘 P。对所有 heads 来说,标准顺序是:

XWV→V,PV→O,OWo→Y,F=2Thddv+2hT2dv+2Thdvd.

如果先算 PX,那么 cost 变成 2hT2d+2Thddv+2Thdvd。如果先合并 WVWo,后面的 attention 乘法二次项系数也会变成 d 而不是 dv。由于通常 dv≪d,标准顺序更优,因为 quadratic term 是 2hT2dv,而不是 2hT2d。

但对于 causal prefill/training,实现上只需要计算 lower triangle。可见的 query-key pair 数量是: T(T+1)2. 所以 causal-aware attention FLOPs 是: Fattn,causal=hT(T+1)(dk+dv). 因此, causal-aware prefill FLOPs 是:

FMHA,prefill=2Tdh(2dk+dv)+2hT2(dk+dv)+2Thdvd.

如果实现中显式考虑 causal mask,把中间项替换为 hT(T+1)(dk+dv)。

MLA Prefill ​

对于 MLA prefill,首先计算共享的 KV latent:CKV=XWDKV。这里的 CKV 是所有 heads 共用的 latent 表示。

Without Query Compression.

对于不使用 query compression 的 MLA,完整 score 表达式 S 和 attention output O 如下:

S=QK⊤=X(T,d)WQ(h,d,dk)WUK⊤(h,dk,dc)WDKV⊤(dc,d)X⊤(d,T).O=PV=P(h,T,T)X(T,d)WDKV(d,dc)WUV(h,dc,dv).

从 FLOPs 角度看,仍然要盯住 T2 项的系数。通常 dk<dc,所以 prefill 里不应该先算 Qs(WUKs)⊤ 再乘 (CKV)⊤;这样会把 dominating 的 QK⊤ 项变成 2T2dc。

key side 还有另一个计算顺序问题。按 tensor shape 看,score 里的 key side 是:

X(T,d)WDKV(d,dc)WUK(h,dc,dk)→K(T,h,dk).

shared-latent 顺序是:

XWDKV→CKV,CKVWUK→K,F=2Tddc+2Thdcdk.

如果先形成 effective per-head key projection WDKVWUK,再和 X 相乘,那么 activation-side cost 是:

X(WDKVWUK)→K,F=2Thddk,

这里还没有算额外的一次性 weight precomposition cost 2hddcdk。代入 DeepSeek-V2 参数 d=5120, dc=512, dk=128, h=128,shared-latent 顺序约为 22.0MT FLOPs,而 effective-WK 顺序是 167.8MT FLOPs。也就是说,便宜的路径是先算 XWDKV,先利用共享的 dc bottleneck,再扩展到各个 heads。

因此计算顺序是:

X(T,d)WDKV(d,dc)→CKV(T,dc),FLOPs=2Tddcshared across heads.X(T,d)WQ(h,d,dk)→Q(T,h,dk),FLOPs=2Thddk.CKV(T,dc)WUK(h,dc,dk)→K(T,h,dk),FLOPs=2Thdcdk.Q(T,h,dk)K⊤(h,dk,T)→S(h,T,T),FLOPs=2hT2dk.

然后计算 O 的 FLOPs,其中 O=PV,V=CKVWUV:

CKV(T,dc)WUV(h,dc,dv)→V(T,h,dv),FLOPs=2Thdcdv.P(h,T,T)V(T,h,dv)→O(T,h,dv),FLOPs=2hT2dv.

这个顺序在 kernel 层面是可实现的。最直接的做法是先 materialize K,V,然后调用标准 FlashAttention kernel;更节省内存的做法则是在每个 attention tile 内从 CKV 计算 K,V。

不使用 query compression 时,causal-aware MLA prefill FLOPs 是:

FMLA,prefill,noQ=2Tddc+2Thddk+2Thdc(dk+dv)+2hT2(dk+dv)+2Thdvd.

如果实现中显式考虑 causal mask,把中间的 attention 项替换为 hT(T+1)(dk+dv)。

With Query Compression.

使用 query compression 后,完整 score 表达式变成:

S=QK⊤=X(T,d)WDQ(d,dqc)WUQ(h,dqc,dk)WUK⊤(h,dk,dc)WDKV⊤(dc,d)X⊤(d,T).

attention output O 保持不变:

O=PV=P(h,T,T)X(T,d)WDKV(d,dc)WUV(h,dc,dv).

最优计算顺序是:

X(T,d)WDQ(d,dqc)→CQ(T,dqc),FLOPs=2Tddqcshared across heads.CQ(T,dqc)WUQ(h,dqc,dk)→Q(T,h,dk),FLOPs=2Thdqcdk.

CKV,K,V,S,O 路径和 no-Q-compression 情况相同。使用 query compression 时,non-causal-aware MLA prefill 总 FLOPs 是:

FMLA,prefill,Qcomp=2Tddc+2Tddqc+2Thdqcdk+2Thdc(dk+dv)+2hT2(dk+dv)+2Thdvd.

如果实现中显式考虑 causal mask,把中间的 attention 项替换为 hT(T+1)(dk+dv)。

Prefill Comparison ​

从上面的公式可以看到,如果二者都显式计算同样形状的 QK⊤ 和 PV,那么 MHA 和 MLA 的 quadratic attention FLOPs 是相同的;prefill 阶段真正的差异主要来自 linear projection part。

MHA 的 linear projection FLOPs 是 2Tdh(2dk+dv)。MLA without query compression 是 2Tddc+2Thddk+2Thdc(dk+dv)。MLA with query compression 是 2Tddc+2Tddqc+2Thdqcdk+2Thdc(dk+dv)。

代入 DeepSeek-V2 attention 配置 d=5120, h=128, dk=dv=128, dc=512, dqc=1536,MHA linear projection cost 是 503.3MT,MLA without query compression 是 206.6MT,MLA with query compression 是 104.9MT。也就是说,MLA no-Q 约为 MHA 的 41.1%,MLA Q-compression 约为 MHA 的 20.8%。

MLA prefill 不会降低 quadratic attention FLOPs,但会显著降低 linear projection FLOPs;with query compression 时这一点尤其明显。在 DeepSeek-V2 配置下,MLA with query compression 的 projection FLOPs 只有 MHA 的约 20.8%。

MHA Decode ​

有 KV cache 时,历史的 K≤t,V≤t 已经在之前的 decode steps 中算好。当前 step 只需要计算并 append 新的 q,k,v:

x(1,d)WQ(h,d,dk)→q(1,h,dk),x(1,d)WK(h,d,dk)→k(1,h,dk),x(1,d)WV(h,d,dv)→v(1,h,dv).

对所有 heads 来说,当前 token projection cost 是:Fqkv=2dh(2dk+dv).

x(1,d)WQ,WK(h,d,dk)→q,k(1,h,dk),x(1,d)WV(h,d,dv)→v(1,h,dv).

这里的计算顺序实际上由 KV cache 决定:先从当前 token 的 x 生成 q,k,v,其中 k,v 被 append 到 cache;然后只用当前 q 去读历史 K≤t 计算 scores。这样 decode-length 相关的乘法发生在 dk,dv 维度上,而不是重新从 hidden dimension d 处理整段 X≤t。然后 attention 读取 cached K,V:

score=qK≤t⊤,o=pV≤t.q(1,h,dk)K≤t⊤(h,dk,t)→score(1,h,t),FLOPs=2htdk.p(1,h,t)V≤t(t,h,dv)→o(1,h,dv),FLOPs=2htdv.o(1,h,dv)Wo(h,dv,d)→y(1,d),FLOPs=2hdvd.

所以 cached decode FLOPs 是:FMHA,decode,cached=2dh(2dk+dv)+2ht(dk+dv)+2hdvd.

MLA Decode ​

对于 MLA decode,cache 中存的不是 full per-head K,V,而是共享的 latent cache。在当前 step,我们 append 一个新的 latent vector:

C≤tKV(t,dc),x(1,d)WDKV(d,dc)→cKV(1,dc),FLOPs=2ddc.

因此当前 token 的 linear projection cost 是明确包含在 decode 里的。没有 query compression 时:

FMLA,current,noQ=2ddc+2hddk.

有 query compression 时:

FMLA,current,Qcomp=2ddc+2ddqc+2hdqcdk.

MLA decode 不需要额外 append full k,v;真正写入 cache 的是 shared cKV latent,K,V 只在 attention 需要时 reconstruct 或 absorb。

完整的 score 和 value 表达式如下。这里保留两个变体:MLA without query compression 和 MLA with query compression。它们的区别主要在 current-token query path,而 latent-cache attention path 是共享的。

scorenoQ=x(1,d)WQ(h,d,dk)WUK⊤(h,dk,dc)(C≤tKV)⊤(dc,t),scoreQcomp=x(1,d)WDQ(d,dqc)WUQ(h,dqc,dk)WUK⊤(h,dk,dc)(C≤tKV)⊤(dc,t),o=p(1,h,t)C≤tKV(t,dc)WUV(h,dc,dv).

对于输入 query x,cached execution 首先构造当前 query。没有 query compression 时:

x(1,d)WQ(h,d,dk)→q(1,h,dk),FLOPs=2hddk.

有 query compression 时,q 的计算被 factorize 为:

x(1,d)WDQ(d,dqc)→cQ(1,dqc),FLOPs=2ddqcshared across heads,cQ(1,dqc)WUQ(h,dqc,dk)→q(1,h,dk),FLOPs=2hdqcdk.

之后两个变体使用相同的 cached attention path。score side 的顺序是先把 WUK⊤ 吸收到 current query 上,得到 qabs=qWUK⊤,再和 latent cache 相乘;这样 WUK 只作用在长度为 1 的 current token 上,而不是作用在长度为 t 的整个 cache 上。value side 也是同样的原则:先算 z=pC≤tKV 得到 latent weighted sum,再乘 WUV,而不是先为所有 cached tokens reconstruct full V。

q(1,h,dk)WUK⊤(h,dk,dc)→qabs(1,h,dc),FLOPs=2hdkdc,qabs(1,h,dc)(C≤tKV)⊤(dc,t)→score(1,h,t),FLOPs=2htdc,p(1,h,t)C≤tKV(t,dc)WUV(h,dc,dv)→o(1,h,dv),FLOPs=2htdc+2hdcdv.

output projection 与 MHA 相同:

o(1,h,dv)Wo(h,dv,d)→y(1,d),FLOPs=2hdvd.

所以 cached all-head decode attention FLOPs 是:FMLA,decode,attn=2hdkdc+4htdc+2hdcdv.

对于整个 MLA decode layer:

FMLA,decode,cached,noQ=2ddc+2hddk+2hdkdc+4htdc+2hdcdv+2hdvd,FMLA,decode,cached,Qcomp=2ddc+2ddqc+2hdqcdk+2hdkdc+4htdc+2hdcdv+2hdvd.

其中 4htdc 是 decode-length part:一个 2htdc 用来读取 latent cache 计算 scores,另一个 2htdc 用来再次读取 latent cache 计算 weighted latent value。这个顺序在 kernel 层面可实现:一个 fused MLA decode kernel 可以计算 qabs,stream C≤tKV 的 cache blocks,做 online softmax,累积 zs=psC≤tKV,最后再乘 WUVs。

Decode Comparison ​

把 MHA 和 MLA 的 decode 公式放在一起看,MHA cached decode 是:

FMHA,decode,cached=2dh(2dk+dv)+2ht(dk+dv)+2hdvd.

MLA without query compression 是:

FMLA,decode,cached,noQ=2ddc+2hddk+2hdkdc+4htdc+2hdcdv+2hdvd.

如果只看 FLOPs,MLA decode 不一定比 MHA 更小。比如 decode-length 相关项里,MHA 是 2ht(dk+dv),而 MLA latent path 里是 4htdc。在 DeepSeek-V2 的维度 dk=dv=128, dc=512 下,后者的算术量并不小。但 decode 阶段通常是一个 memory-bound 的问题,MHA 每个 cached token 要存和读 full per-head cache:K≤t:(t,h,dk),V≤t:(t,h,dv), 所以每个 token 的 cache 规模大约是 h(dk+dv)。MLA 存的是 shared latent:C≤tKV:(t,dc), 再加上较小的 decoupled RoPE cache。代入 h=128, dk=dv=128, dc=512,MHA 的 full KV cache 每个 token 是 32768 维,而 MLA 的 latent cache 是 512 维。即使 MLA 在 kernel 内多做一些 qabs、latent score 和 latent value 的计算,它避免了反复从显存读取 full per-head K,V。

所以 MLA decode 的加速主要来自 cache 的变化:把长上下文 decode 中最重的 full KV 读写,换成了 shared latent cache 的读写。FLOPs 被转移到一些更小、更适合 fused kernel 的计算上;MLA 在这部分的本质还是用计算来换显存。

Why RoPE Gets in the Way ​

MLA 的 absorption trick 成立,有一个隐含前提:从 latent cache 到 key 的映射在不同 position 上是固定的。不考虑 RoPE 时,对于 cached token i:

kis=ciKVWUKs,scoreis=qs(kis)⊤=(qs(WUKs)⊤)(ciKV)⊤.

于是可以先计算一个 current-token vector qabss=qs(WUKs)⊤,再对所有 cached positions 复用它。这就是 latent-cache decode 背后最干净的代数结构。

问题在于,RoPE 会在 key path 中插入 position-dependent rotation。令 Rt 表示当前 position 的 query rotation,Ri 表示 cached position i 的 key rotation:

scoreis=(Rtqs)(Rikis)⊤=qsRt⊤Ri(WUKs)⊤(ciKV)⊤.

现在矩阵 Rt⊤Ri(WUKs)⊤ 依赖于 i。这意味着我们不能只形成一个 qabss,然后对所有 cached tokens 复用它。重构并缓存所有 rotated per-head keys 当然可行,但这样基本又回到了普通 KV cache 的方向。

实际做法是 decouple content channel 和 positional channel:

scoreis=qcontent,abss(ciKV)⊤+(Rtqropes)(Riki,ropes)⊤.

content channel 仍然走 MLA latent-cache path;RoPE channel 则单独存,并且维度保持较小。所以 RoPE 并不会让 MLA 不可能实现,它只是阻止了“整个 key 都隐藏在一个 shared latent cache 后面”的最干净版本。

Why Compress Q Too ​

KV compression 和 Q compression 解决的是系统里的不同问题。KV compression 改变 decode 时存什么、读什么;Q compression 则 factorize query projection:

CQ=XWDQ,Qs=CQWUQs.
  • Activation memory. 没有 query compression 时,query activation 规模是 Thdk;有 query compression 时,compressed query state 规模是 Tdqc。关键条件很简单:

    dqc<hdk.

    对于 DeepSeek-V2,h=128,dk=128,dqc=1536,所以 hdk=16384。这会显著减少需要保存的 query activations,在 long-context prefill/training 中尤其重要。

  • Prefill/training FLOPs. Q compression 也可能带来 compute win。query projection 从:

    FQ=2Tdhdk⟹FQ,comp=2Tddqc+2Thdqcdk.

    当 dqc(d+hdk)<hddk 时,Q compression 会降低 query-projection FLOPs。在 DeepSeek-V2 维度下,FQ=167.8MT,而 FQ,comp=66.1MT。这也解释了为什么前面的 prefill comparison 中,MLA no-Q 和 MLA Q-compression 的差距很明显。

  • Decode. Q compression 只影响 current-token query path;它本身不会降低 latent cache-length term。score path 是:

    x(1,d)WDQ(d,dqc)WUQ(h,dqc,dk)WUK⊤(h,dk,dc)(C≤tKV)⊤(dc,t).

    实际实现中通常仍然经过较小的 per-head bottleneck dk。score-side cost 是:

    2dqcdk+2dkdc+2tdcinstead of2dqcdc+2tdc.

    因为 dk 远小于 dc 和 dqc,所以 two-step path 通常更便宜。

Q compression 会节省 activation memory,也可能节省 prefill projection FLOPs,但对 decode compute 的改变相对有限;KV compression 仍然是主要的 decode-cache bandwidth win。

Why MTP Weakens MLA's Decode Acceleration Gain ​

MTP

DeepSeek-V3 Technical Report 中的 MTP 是额外 training objective。对于 prediction depth k,MTP module 会把上一 depth 的 representation 和 shifted token embedding 拼接起来:

hi′k=Mk[RMSNorm(hik−1);RMSNorm(Emb(ti+k))],h1:T−kk=TRMk(h1:T−k′k).

然后 shared output head 预测第 k 个额外 future token:

Pi+k+1k=OutHead(hik).

正常 inference 时,DeepSeek-V3 可以直接丢弃这些 MTP modules,此时 MLA 的 decode path 不受 MTP 影响。真正需要讨论的是把 MTP modules 拿来做 speculative decoding:MTP path 先 draft 多个 future tokens,再由 main model verify。

先看 plain MLA 的收益。前面推导的 MLA decode 加速,主要来自 main decode path 的 cache format 改变:普通 MHA 每个 token 大约读取 h(dk+dv) 维 cached K,V,而 MLA 主要读取 dc 维 latent cache,再加上较小的 decoupled RoPE 部分。以 DeepSeek-V2/V3 风格的参数为例,h(dk+dv)=128(128+128)=32768,而 dc=512。也就是说,MLA 的优势可以粗略理解为:

GMLA=FMHA,decode−FMLA,decode.

引入 MTP 做 speculative decoding 后,实际每生成一组 accepted tokens 的成本不再只是 FMLA,decode,而是 draft 加 verify 的成本。假设一次 draft D 个 tokens,最终接受 A 个 tokens,那么平均到每个 accepted token 上,大致是:

F¯MTP≈Fdraft(1:D)+Fverify(D)A.

如果要让 MTP 不吃掉 MLA 原本的加速收益,就需要这个平均成本不要比 plain MLA decode 高太多;更强一点,如果希望 MTP 在 MLA 上继续带来 decode 加速,则需要:

Fdraft(1:D)+Fverify(D)<A⋅FMLA,decode.

问题在于,MTP draft path 并不是免费的。第 k 个 draft depth 至少包含 fusion projection、一个额外的 TRMk block,以及 output head:

Fdraft(k)≈4d2+FTRMk,decodeMLA+2dV.

其中 4d2 来自把 [hk−1;Emb] 从 2d 投影回 d,FTRMk,decodeMLA 是 MTP Transformer block 的额外 decode cost,2dV 是计算完整 logits 时 shared output head 的 cost。

关键点很简单:TRMk 跑在 shifted hidden stream hk 上,和 main model 的 hidden states、token alignment 都不同,所以不能直接复用 main CKV cache。如果 MTP block 内部也使用 MLA,就要为这条 stream 维护一份自己的 latent cache。

因此,MTP 会削弱 MLA 在 decode 阶段的加速收益。MLA 把 main decode path 的 cache 读写压低了,但 MTP 又多出一条 draft stream、verify cost,以及一份不能直接复用 main cache 的 shifted latent states。从 FLOPs/cache 角度看,这些额外成本会吃掉一部分 MLA 的收益。