@@ -247,14 +247,15 @@ def _validate_dimensions(self) -> None:
247247 @torch .no_grad ()
248248 def project_drafter_kv (
249249 self , input_ids : torch .Tensor ,
250- ) -> Tuple [torch .Tensor , torch .Tensor ]:
250+ ) -> Tuple [List [ torch .Tensor ], List [ torch .Tensor ] ]:
251251 """Run the drafter forward over input_ids, project K/V through f_θ.
252252
253253 Returns
254254 -------
255- (verifier_k, verifier_v) tensors of shape
256- ``[B, T, verifier_num_layers, verifier_num_kv_heads, verifier_head_dim]``
257- on the f_θ device.
255+ (verifier_k, verifier_v): per-layer lists of length
256+ ``verifier_num_layers``, element ``i`` shaped
257+ ``[B, T, layer_kv_heads[i], verifier_head_dim]`` on the f_θ
258+ device. Per-layer KV-head counts can differ (Gemma 4).
258259
259260 These are the per-position-per-verifier-layer K/V that the
260261 cross-model verifier injects at evicted positions during its
@@ -309,9 +310,9 @@ def forward(
309310 if not evicted_positions :
310311 return self .verifier_model (input_ids = input_ids , use_cache = False )
311312
312- # f_θ projection
313- verifier_k_full , verifier_v_full = self . project_drafter_kv ( input_ids )
314- # verifier_k_full shape: [B, T, L_v, num_kv_heads_v, head_dim_v]
313+ # f_θ projection → per-layer lists (layers can have different
314+ # KV-head counts on Gemma 4).
315+ verifier_k_layers , verifier_v_layers = self . project_drafter_kv ( input_ids )
315316
316317 # Patch verifier attention forwards to inject K/V at evicted
317318 # positions. Restore originals after the forward.
@@ -325,8 +326,8 @@ def forward(
325326 attn ,
326327 layer_idx = layer_idx ,
327328 evicted_positions = evicted_positions ,
328- verifier_k_at_layer = verifier_k_full [:, :, layer_idx ],
329- verifier_v_at_layer = verifier_v_full [:, :, layer_idx ],
329+ verifier_k_at_layer = verifier_k_layers [ layer_idx ],
330+ verifier_v_at_layer = verifier_v_layers [ layer_idx ],
330331 apply_rotary_pos_emb = apply_rotary_pos_emb ,
331332 eager_attention_forward = eager_attention_forward ,
332333 all_attention_functions = all_attention_functions ,
@@ -351,47 +352,72 @@ def _make_patched_forward(
351352 instead of using the verifier's own k_proj / v_proj at those
352353 positions.
353354
354- The patched forward replicates the standard verifier attention
355- layer (Q, K, V projections + RoPE + GQA + softmax) with one
356- change: after K, V are computed at every position, K and V at
357- evicted positions are OVERWRITTEN with the f_θ-projected values
358- (after k_norm + RoPE applied to match the standard pipeline).
355+ Mirrors Gemma 4's ``Gemma4TextAttention.forward`` exactly (RoPE
356+ applied per-tensor on the ``[B, T, H, D]`` layout with
357+ ``unsqueeze_dim=2``; ``q_norm`` / ``k_norm`` / ``v_norm``;
358+ ``v_proj`` is ``None`` on full-attention layers where V = raw K)
359+ with one change: at evicted positions K/V come from the
360+ f_θ-projected values (after k_norm + RoPE for K, after v_norm
361+ for V), so the verifier attends over full context despite
362+ holding only sink+window in its local cache.
359363 """
360364 def _patched_forward (
361365 hidden_states : torch .Tensor ,
362366 position_embeddings : Tuple [torch .Tensor , torch .Tensor ],
363367 attention_mask : Optional [torch .Tensor ] = None ,
368+ shared_kv_states : Any = None ,
364369 past_key_values = None ,
365370 cache_position = None ,
366371 ** kwargs ,
367372 ) -> Tuple [torch .Tensor , Optional [torch .Tensor ]]:
368- B , T , _ = hidden_states .shape
369-
370- input_shape = (B , T )
371- hidden_shape = (* input_shape , - 1 , attn_module .head_dim )
372-
373- query_states = attn_module .q_proj (hidden_states ).view (* hidden_shape ).transpose (1 , 2 )
374- key_states = attn_module .k_proj (hidden_states ).view (* hidden_shape ).transpose (1 , 2 )
375- value_states = attn_module .v_proj (hidden_states ).view (* hidden_shape ).transpose (1 , 2 )
373+ input_shape = hidden_states .shape [:- 1 ] # [B, T]
374+ head_dim = attn_module .head_dim
375+ hidden_shape = (* input_shape , - 1 , head_dim )
376+ cos , sin = position_embeddings
376377
378+ # Query (Gemma 4: norm → RoPE on [B,T,Hq,D] → transpose)
379+ query_states = attn_module .q_proj (hidden_states ).view (hidden_shape )
377380 query_states = attn_module .q_norm (query_states )
378- key_states = attn_module .k_norm (key_states )
381+ query_states = apply_rotary_pos_emb (
382+ query_states , cos , sin , unsqueeze_dim = 2 ,
383+ )
384+ query_states = query_states .transpose (1 , 2 ) # [B, Hq, T, D]
379385
380- cos , sin = position_embeddings
381- query_states , key_states = apply_rotary_pos_emb (
382- query_states , key_states , cos , sin ,
386+ # Key / Value. Full-attention layers have v_proj=None ⇒ the
387+ # value is the raw k_proj output (pre k_norm), per Gemma 4.
388+ k_lin = attn_module .k_proj (hidden_states ).view (hidden_shape )
389+ if getattr (attn_module , "v_proj" , None ) is not None :
390+ v_lin = attn_module .v_proj (hidden_states ).view (hidden_shape )
391+ else :
392+ v_lin = k_lin
393+ key_states = attn_module .k_norm (k_lin )
394+ key_states = apply_rotary_pos_emb (
395+ key_states , cos , sin , unsqueeze_dim = 2 ,
383396 )
397+ key_states = key_states .transpose (1 , 2 ) # [B, Hkv, T, D]
398+ value_states = attn_module .v_norm (v_lin ).transpose (1 , 2 ) # [B, Hkv, T, D]
384399
385400 # Inject f_θ K/V at evicted positions.
386401 # verifier_k_at_layer shape: [B, T, num_kv_heads_v, head_dim_v]
387- # K/V from k_proj also at all T positions; we overwrite the
388- # evicted slice with f_θ output (after k_norm + RoPE).
402+ # (pre-norm pre-RoPE). prepare_restored_attention_kv applies
403+ # k_norm + RoPE to K; V must be v_norm'd here to match the
404+ # local branch (Gemma 4 runs V through v_norm).
389405 if evicted_positions :
406+ idx = torch .tensor (
407+ evicted_positions , device = key_states .device , dtype = torch .long ,
408+ )
409+ cap_k_pre = verifier_k_at_layer .index_select (1 , idx ).to (
410+ device = key_states .device , dtype = key_states .dtype ,
411+ )
412+ cap_v_pre = verifier_v_at_layer .index_select (1 , idx ).to (
413+ device = value_states .device , dtype = value_states .dtype ,
414+ )
415+ cap_v_norm = attn_module .v_norm (cap_v_pre )
390416 key_states , value_states = prepare_restored_attention_kv (
391417 K_local = key_states ,
392418 V_local = value_states ,
393- captured_K_pre_norm = verifier_k_at_layer ,
394- captured_V = verifier_v_at_layer ,
419+ captured_K_pre_norm = cap_k_pre ,
420+ captured_V = cap_v_norm ,
395421 evicted_positions = evicted_positions ,
396422 k_norm = attn_module .k_norm ,
397423 position_embeddings = (cos , sin ),
0 commit comments