@@ -60,6 +60,38 @@ def _patch_YarnRotaryEmbedding(config):
6060 delattr (config , attr )
6161
6262
63+ def _apply_mla_rope (t , freqs , * , config , cu_seqlens , cp_group , inverse = False ):
64+ """Apply DSv4's MLA RoPE to a tensor whose frequencies are already expanded per token.
65+
66+ `GPTModel` pre-indexes the rotary table by `position_ids`, so `freqs` is row-aligned with
67+ `t`: row i holds the frequency of token i. That holds for every layout DSv4 supports --
68+ unpacked, packed (thd), and packed CP: swift CP-splits `position_ids` with the same
69+ partition mode as the hidden states, so the pre-indexed frequencies come out rank-local
70+ while still carrying absolute positions. The multiply is therefore purely elementwise and
71+ needs no `cu_seqlens`-based segment alignment, under any `cp_partition_mode`.
72+
73+ Enforcing that invariant here matters: when it does not hold, the generic
74+ `apply_rotary_pos_emb` thd path re-derives positions from `cu_seqlens` assuming a *zigzag*
75+ CP split, which is wrong for DSv4's contiguous split and would corrupt positions silently.
76+ Asserting row alignment turns any future layout change into an immediate, explicit failure.
77+ """
78+ assert freqs .shape [0 ] == t .shape [0 ], (
79+ f'DSv4 MLA RoPE expects per-token frequencies row-aligned with the input, got '
80+ f'freqs.shape[0]={ freqs .shape [0 ]} vs tokens={ t .shape [0 ]} . `GPTModel` must pre-index the '
81+ 'rotary table by `position_ids` (requires `apply_rope_fusion=False`), and under CP the '
82+ '`position_ids` must be split with the same partition mode as the hidden states.' )
83+ return apply_rotary_pos_emb (
84+ t ,
85+ freqs ,
86+ config = config ,
87+ cu_seqlens = cu_seqlens ,
88+ cp_group = cp_group ,
89+ mla_rotary_interleaved = True ,
90+ mla_output_remove_interleaving = True ,
91+ inverse = inverse ,
92+ )
93+
94+
6395class DSv4HybridSelfAttention (McoreDSv4HybridSelfAttention ):
6496
6597 def __init__ (self , config , * args , ** kwargs ):
@@ -153,6 +185,15 @@ def qkv_up_proj_and_rope_apply(q_compressed,
153185 When sequence packing enabled, the input tensors adopt a packed shape of [t, ...];
154186 otherwise, they maintain the unpacked shape [s, b, ...]. In subsequent code comments,
155187 we uniformly use [num_tokens, ...] to denote [s, b, ...] or [t, ...] for two cases.
188+
189+ RoPE frequency layout: `GPTModel` pre-indexes the rotary table by `position_ids`
190+ (see gpt_model.py, the `not apply_rope_fusion` branch), so `rotary_pos_emb` here is
191+ already expanded per token -- row i belongs to token i -- rather than being a
192+ position->frequency lookup table. Every RoPE call below is therefore an elementwise
193+ multiply; see `_apply_mla_rope` for why that invariant is asserted. Under CP the
194+ frequencies arrive rank-local because `position_ids` is split alongside the hidden
195+ states, which is also why the boundary rows carry their own frequencies
196+ (`boundary_rotary_pos_emb`) instead of being re-derived from positions.
156197 """
157198 # q_compressed: [num_tokens, q_lora_rank]
158199 # q: [num_tokens, n * (qk_head_dim + qk_pos_emb_head_dim)]
@@ -166,6 +207,8 @@ def qkv_up_proj_and_rope_apply(q_compressed,
166207 if boundary_kv_compressed is not None :
167208 boundary_rows = boundary_kv_compressed .shape [0 ]
168209 kv_projection_input = torch .cat ([boundary_kv_compressed , kv_compressed ], dim = 0 )
210+ # The boundary rows precede this rank's block, so their frequencies must precede
211+ # too -- keeping kv_rotary_pos_emb row-aligned with kv_projection_input.
169212 kv_rotary_pos_emb = torch .cat ([boundary_rotary_pos_emb , rotary_pos_emb ], dim = 0 )
170213 else :
171214 kv_projection_input = kv_compressed
@@ -182,29 +225,25 @@ def qkv_up_proj_and_rope_apply(q_compressed,
182225
183226 # RoPE and query (shared for wkv and latent)
184227 # q_pos_emb: [num_tokens, n, qk_pos_emb_head_dim]
185- q_pos_emb = apply_rotary_pos_emb (
228+ q_pos_emb = _apply_mla_rope (
186229 q_pos_emb ,
187230 rotary_pos_emb ,
188231 config = self .config ,
189232 cu_seqlens = cu_seqlens_q ,
190233 cp_group = self .pg_collection .cp ,
191- mla_rotary_interleaved = True ,
192- mla_output_remove_interleaving = True ,
193234 )
194235 # query: [num_tokens, n, (qk_head_dim + v_head_dim)]
195236 query = torch .cat ([q_no_pe , q_pos_emb ], dim = - 1 )
196237
197238 kv_no_pe , k_pos_emb = torch .split (kv , [kv .size (- 1 ) - pos_dim , pos_dim ], dim = - 1 )
198239
199240 # k_pos_emb:[num_tokens, 1, qk_pos_emb_head_dim]
200- k_pos_emb = apply_rotary_pos_emb (
241+ k_pos_emb = _apply_mla_rope (
201242 k_pos_emb ,
202243 kv_rotary_pos_emb ,
203244 config = self .config ,
204245 cu_seqlens = cu_seqlens_kv ,
205246 cp_group = self .pg_collection .cp ,
206- mla_rotary_interleaved = True ,
207- mla_output_remove_interleaving = True ,
208247 )
209248
210249 # Single head: key = value = [num_tokens, 1, v_head_dim]
@@ -384,15 +423,13 @@ def forward(
384423 rot_part_in = rot_part .squeeze (1 )
385424 else :
386425 rot_part_in = rot_part
387- rot_part_out = apply_rotary_pos_emb (
426+ rot_part_out = _apply_mla_rope (
388427 rot_part_in ,
389428 rotary_pos_emb ,
390- self .config ,
429+ config = self .config ,
391430 cu_seqlens = cu_seqlens_kv ,
392431 cp_group = self .pg_collection .cp ,
393- mla_rotary_interleaved = True ,
394432 inverse = True ,
395- mla_output_remove_interleaving = True ,
396433 )
397434 if packed_seq :
398435 rot_part = rot_part_out .unsqueeze (1 )
0 commit comments