MediumPlusDeepSeek-V3

Decoupled RoPE

DeepSeek-V3

Medium · 🔒 Plus required

Separate RoPE for compressed keys


Independent study note. Written from the public paper and official code. This is not TensorTonic Plus and does not reproduce their exercises, starter code, or tests. For the official version, subscribe on TensorTonic.

Overview

MLA stores a low-rank latent (\mathbf{c}{t}^{KV}) instead of per-head keys. Rotary position embeddings (RoPE) are a position-dependent orthogonal transform. If you rotate the reconstructed full key (\mathbf{k}{t}^{C}=W^{UK}\mathbf{c}{t}^{KV}), that rotation cannot be folded into a position-independent matrix, so you lose the main inference trick of MLA: absorbing (W^{UK}) into the query. DeepSeek-V2/V3 therefore split each query and key into a content half that never sees RoPE and a short rotary half that does. Only the rotary key (\mathbf{k}{t}^{R}) is cached alongside (\mathbf{c}_{t}^{KV}). The paper applies YaRN exclusively to this shared rotary key when extending context from 4K to 128K.

How it works

After the content key is up-projected, a second linear map builds a rotary key from the uncompressed residual (not from (\mathbf{c}_{t}^{KV})):

\mathbf{k}_{t}^{R}=\operatorname{RoPE}(W^{KR}\mathbf{h}_{t})\in\mathbb{R}^{d_{h}^{R}},\qquad \mathbf{k}_{t,i}=[\mathbf{k}_{t,i}^{C};\mathbf{k}_{t}^{R}].

(W^{KR}\in\mathbb{R}^{d_{h}^{R}\times d}) with (d_{h}^{R}=64). The same (\mathbf{k}_{t}^{R}) is concatenated onto every head (i). Queries are compressed first, then split:

\mathbf{q}_{t}^{C}=W^{UQ}\mathbf{c}_{t}^{Q},\qquad \mathbf{q}_{t}^{R}=\operatorname{RoPE}(W^{QR}\mathbf{c}_{t}^{Q}),\qquad \mathbf{q}_{t,i}=[\mathbf{q}_{t,i}^{C};\mathbf{q}_{t,i}^{R}].

(W^{QR}\in\mathbb{R}^{n_{h}d_{h}^{R}\times d_{c}'}) produces a per-head rotary query. Attention (paper eq. 10) dots the concatenated vectors and scales by the sum of the two widths:

\mathbf{o}_{t,i}=\sum_{j=1}^{t}\operatorname{Softmax}_{j}\!\left(\frac{\mathbf{q}_{t,i}^{\top}\mathbf{k}_{j,i}}{\sqrt{d_{h}+d_{h}^{R}}}\right)\mathbf{v}_{j,i}^{C}.

Because the rotary inner product (\mathbf{q}{t,i}^{R\top}\mathbf{k}{j}^{R}) does not involve (W^{UK}), the content scores (\mathbf{q}{t,i}^{C\top}W^{UK}\mathbf{c}{j}^{KV}) can still absorb (W^{UK}) into the query. The official “absorb” path therefore caches only (\mathbf{c}^{KV}) and (\mathbf{k}^{R}).

RoPE itself is the usual complex multiply. With (\boldsymbol{\omega}{m}=\theta^{-2m/d{h}^{R}}) and YaRN-adjusted frequencies when (T>\texttt{original_seq_len}),

\operatorname{RoPE}(\mathbf{x})_{t}=\mathbf{x}_{t}\odot e^{i\,t\boldsymbol{\omega}}.

The 671B config uses (\theta=10^{4}), YaRN scale (s=40), and softmax mscale ((0.1\ln s+1)^{2}) once the sequence exceeds the pretrain length of 4096. Values are never rotated.

Official code

In inference/model.py:

Watch-outs

Sources