1717from typing import Any
1818import jax
1919import jax .numpy as jnp
20+ from jax .sharding import Mesh
2021from flax import nnx
2122from maxtext .layers .embeddings import DeepSeekV4RotaryEmbedding , apply_rotary_pos_emb
2223from maxtext .layers .normalizations import DeepSeekV4RMSNorm , DeepSeekV4UnweightedRMSNorm
2324from maxtext .layers .linears import DeepSeekGroupedLinear
25+ from maxtext .layers .attention_op import AttentionOp
26+ from maxtext .common .common_types import MODEL_MODE_TRAIN , AttentionType
2427
2528
2629class HCACompressor (nnx .Module ):
@@ -105,7 +108,7 @@ def __init__(
105108 # Interleaved rotary embeddings applied to the trailing slice
106109 self .rotary_emb = DeepSeekV4RotaryEmbedding (
107110 head_dim = head_dim ,
108- partial_rotary_factor = 64.0 / 512.0 ,
111+ partial_rotary_factor = config . qk_rope_head_dim / config . head_dim ,
109112 rope_theta = rope_theta ,
110113 )
111114
@@ -328,7 +331,7 @@ def __init__(
328331 # Interleaved rotary embedding aligning query/key pos representations
329332 self .rotary_emb = DeepSeekV4RotaryEmbedding (
330333 head_dim = self .head_dim ,
331- partial_rotary_factor = ( config .head_dim * ( 64.0 / 512.0 )) / self .head_dim ,
334+ partial_rotary_factor = config .qk_rope_head_dim / self .head_dim ,
332335 rope_theta = rope_theta ,
333336 )
334337
@@ -582,7 +585,7 @@ def __init__(
582585 # Interleaved rotary embeddings for compressed sequences
583586 self .rotary_emb = DeepSeekV4RotaryEmbedding (
584587 head_dim = head_dim ,
585- partial_rotary_factor = 64.0 / 512.0 ,
588+ partial_rotary_factor = config . qk_rope_head_dim / config . head_dim ,
586589 rope_theta = rope_theta ,
587590 )
588591
@@ -761,9 +764,11 @@ def __init__(
761764 num_heads : int ,
762765 config : Any ,
763766 layer_idx : int ,
767+ mesh : Mesh | None = None ,
764768 eps : float = 1e-6 ,
765769 weight_dtype : Any = jnp .float32 ,
766770 dtype : Any = jnp .float32 ,
771+ attention_type : str = "compressed_sparse_attention" ,
767772 * ,
768773 rngs : nnx .Rngs ,
769774 ):
@@ -779,12 +784,13 @@ def __init__(
779784 eps: Tiny additive variance limit for RMS normalization stability.
780785 weight_dtype: The parameter weights numerical data type.
781786 dtype: The mathematical execution numerical data type.
787+ attention_type: The type of compressed attention being instantiated.
782788 rngs: The Flax NNX random number generator collection.
783789 """
784790 super ().__init__ ()
785791 self .config = config
786792 self .layer_idx = layer_idx
787- self .layer_type = config . layer_types [ layer_idx ]
793+ self .attention_type = attention_type
788794 self .num_heads = num_heads
789795 self .head_dim = head_dim
790796 self .sliding_window = config .sliding_window
@@ -858,7 +864,7 @@ def __init__(
858864 self .sinks = nnx .Param (jax .nn .initializers .zeros (rngs .params (), (num_heads ,), weight_dtype ))
859865
860866 # Layer specific compressor allocation
861- if self .layer_type == "heavily_compressed_attention" :
867+ if self .attention_type == "heavily_compressed_attention" :
862868 self .compressor = HCACompressor (
863869 hidden_size = hidden_size ,
864870 head_dim = head_dim ,
@@ -869,7 +875,7 @@ def __init__(
869875 dtype = dtype ,
870876 rngs = rngs ,
871877 )
872- elif self .layer_type == "compressed_sparse_attention" :
878+ elif self .attention_type == "compressed_sparse_attention" :
873879 self .compressor = CSACompressor (
874880 hidden_size = hidden_size ,
875881 q_lora_rank = q_lora_rank ,
@@ -884,117 +890,193 @@ def __init__(
884890 else :
885891 self .compressor = None
886892
893+ # Compute partial rotary factor dynamically from config to prevent dimension mismatches.
894+ # DeepSeek-V4 pairs consecutive channels to apply partial RoPE on qk_rope_head_dim channels,
895+ # requiring dynamic scaling: partial_rotary_factor = qk_rope_head_dim / head_dim.
896+ self .partial_rotary_factor = self .config .qk_rope_head_dim / self .config .head_dim
897+
898+ self .rope_theta = (
899+ self .config .rope_max_timescale if self .attention_type == "sliding_attention" else self .config .compress_rope_theta
900+ )
901+
902+ # Local rotary embedding block matching standard MaxText (Gemma/Llama2) paradigms.
903+ self .rotary_embedding = DeepSeekV4RotaryEmbedding (
904+ head_dim = self .head_dim ,
905+ partial_rotary_factor = self .partial_rotary_factor ,
906+ rope_theta = self .rope_theta ,
907+ )
908+
909+ # Scaling factor applied to query representations to match standard MaxText attention scaling.
910+ # MaxText's AttentionOp core expects queries to be pre-scaled by 1 / sqrt(head_dim).
911+ self .scaling = self .head_dim ** - 0.5
912+
913+ self .attention_op = AttentionOp (
914+ config = self .config ,
915+ mesh = mesh ,
916+ attention_kernel = self .config .attention ,
917+ max_target_length = self .config .max_target_length ,
918+ num_query_heads = self .num_heads ,
919+ num_kv_heads = 1 ,
920+ dtype = self .dtype ,
921+ compute_axis_order = (0 , 1 , 2 , 3 ),
922+ attention_type = AttentionType .FULL ,
923+ rngs = rngs ,
924+ )
925+
887926 def __call__ (
888927 self ,
889- hidden_states : jnp .ndarray ,
890- cos : jnp .ndarray ,
891- sin : jnp .ndarray ,
892- position_ids : jnp .ndarray ,
928+ hidden_states : jnp .ndarray | None = None ,
929+ position_ids : jnp .ndarray | None = None ,
893930 attention_mask : jnp .ndarray | None = None ,
931+ inputs_q : jnp .ndarray | None = None ,
932+ inputs_kv : jnp .ndarray | None = None ,
933+ ** kwargs ,
894934 ) -> tuple [jnp .ndarray , jnp .ndarray ]:
895- """Executes DeepSeek-V4 compressed multi-head attention.
935+ """Executes the main coordination attention pass over sequence inputs .
896936
897- This method projects input states to query representations, applies low-rank
898- LoRA, normalizes and applies positional RoPE, creates shared multi-query key-value
899- configurations, expands local context windows using structural compressors (if present),
900- calculates multi-head attention logits, applies structural masks and learnable
901- sinks, computes stable attention probabilities, and mixes final attention outputs
902- using parallel block-diagonal grouped projections.
937+ This method coordinates multi-head attention augmented with query-compression LoRA
938+ projections, unweighted key/value normalizations, long-range context compressor
939+ integrations, learnable attention sinks, and parallelized grouped output mixing.
903940
904941 Args:
905- hidden_states: The input hidden representation sequence of shape [B, S, D_model].
906- cos: Positional RoPE cosine frequencies array of shape [B, S, D_rope].
907- sin: Positional RoPE sine frequencies array of shape [B, S, D_rope].
908- position_ids: Absolute sequence position identifiers of shape [B, S].
909- attention_mask: Optional attention mask of shape [B, 1, S, S_kv].
942+ hidden_states: Input sequence representations of shape [B, S, D_model].
943+ position_ids: Sequence absolute position identifiers of shape [B, S].
944+ attention_mask: Optional attention mask preventing invalid token attendance.
945+ inputs_q: Optional query input override for decoupled execution.
946+ inputs_kv: Optional key/value input override for decoupled execution.
947+ **kwargs: Additional runtime execution configurations (e.g., decoder_segment_ids).
910948
911949 Returns:
912- A tuple containing:
913- - The projected mixed output sequence tensor of shape [B, S, D_model].
914- - The final multi-head attention weights of shape [B, H, S, S_kv].
950+ Tuple containing the projected output representations of shape [B, S, D_model]
951+ and an empty caching intermediate placeholder.
915952 """
916- # hidden_states shape: [B, S, D_model]
917- # cos, sin shape : [B, S, D_rope ]
918- # position_ids shape: [B, S]
919- # attention_mask shape: [B, 1, S_q, S_kv]
953+ # Resolve input representations from standard hidden states or override inputs.
954+ # hidden_states : [B, S, D_model ]
955+ if hidden_states is None :
956+ hidden_states = inputs_q
920957 batch , seq_len , _ = hidden_states .shape
921- h_shape = (batch , seq_len , self .num_heads , self .head_dim )
922958
923- # Project inputs to query representations
924- # q_residual: [B, S, D_rank]
959+ # Generate absolute position identifiers if not provided at runtime.
960+ # position_ids: [B, S]
961+ if position_ids is None :
962+ position_ids = jnp .broadcast_to (jnp .arange (seq_len , dtype = jnp .int32 )[None ], (batch , seq_len ))
963+
964+ # Resolve rotary position embedding sinusoids from runtime keyword arguments or compute local sinusoids.
965+ # Utilizing pre-computed sinusoids avoids redundant computation across decoder layers during forward passes.
966+ # cos: [B, S, D_rope/2]
967+ # sin: [B, S, D_rope/2]
968+ cos = kwargs .get ("cos" , None )
969+ sin = kwargs .get ("sin" , None )
970+ if cos is None or sin is None :
971+ cos , sin = self .rotary_embedding (hidden_states , position_ids )
972+
973+ # Project input features to low-rank query residuals and apply RMS normalization.
974+ # # [B, S, D_model] -> [B, S, D_rank]
925975 q_residual = self .q_a_norm (self .q_a_proj (hidden_states ))
926- # q: [B, H, S, D_head]
927- q = self .q_b_proj (q_residual ).reshape (h_shape ).transpose (0 , 2 , 1 , 3 )
928- q = self .q_b_norm (q )
929- # q: [B, H, S, D_head]
930- q = apply_rotary_pos_emb (q , cos , sin , unsqueeze_dim = 1 )
931-
932- # Project inputs to key/value representation
933- # kv: [B, 1, S, D_head]
934- kv = self .kv_norm (self .kv_proj (hidden_states )).reshape (batch , seq_len , 1 , self .head_dim ).transpose (0 , 2 , 1 , 3 )
935- # kv: [B, 1, S, D_head]
936- kv = apply_rotary_pos_emb (kv , cos , sin , unsqueeze_dim = 1 )
937976
977+ # Project low-rank residuals to multi-head query dimensions and reshape.
978+ # # [B, S, D_rank] -> [B, S, H, D_head]
979+ q = self .q_b_proj (q_residual ).reshape (batch , seq_len , self .num_heads , self .head_dim )
980+
981+ # Apply scale-free unweighted RMS normalization across multi-head queries and scale by attention scaling factor.
982+ # Unweighted normalization stabilizes query variance without introducing learnable scaling parameters.
983+ # MaxText's AttentionOp core assumes pre-scaled query tensors, requiring explicit scaling here.
984+ # # [B, S, H, D_head] -> [B, S, H, D_head]
985+ q = self .q_b_norm (q ) * self .scaling
986+
987+ # Apply Rotary Position Embedding (RoPE) to query representations.
988+ # # [B, S, H, D_head] -> [B, S, H, D_head]
989+ q = apply_rotary_pos_emb (q , cos , sin , unsqueeze_dim = 2 )
990+
991+ # Project input representations to shared key/value features and apply RMS normalization.
992+ # # [B, S, D_model] -> [B, S, 1, D_head]
993+ kv = self .kv_norm (self .kv_proj (hidden_states )).reshape (batch , seq_len , 1 , self .head_dim )
994+
995+ # Apply Rotary Position Embedding (RoPE) to shared key/value representations.
996+ # # [B, S, 1, D_head] -> [B, S, 1, D_head]
997+ kv = apply_rotary_pos_emb (kv , cos , sin , unsqueeze_dim = 2 )
998+
999+ # Integrate long-range context compressor representations if configured.
9381000 block_bias = None
939- # Apply compressed attention key-value generation path
9401001 if self .compressor is not None :
941- # compressed_kv: [B, 1, W, D_head] or [B, 1, S*k, D_head]
942- # block_bias: [B, 1, S, W] or [B, 1, S, S*k]
1002+ # Execute compressor pass to extract compressed key/value blocks and structural block bias masks.
1003+ # compressed_kv: [B, 1, W, D_head] or [B, W, 1, D_head]
1004+ # block_bias: [B, 1, S, W] or [B, S, W]
9431005 compressed_kv , block_bias = self .compressor (hidden_states , q_residual , position_ids )
944- # kv: [B, 1, S + W, D_head] or [B, 1, S + S*k, D_head]
945- kv = jnp .concatenate ([kv , compressed_kv ], axis = 2 )
9461006
947- # Broadcast key/value configurations to all heads
948- # k: [B, H, S_kv, D_head]
949- k = jnp .repeat (kv , self .num_heads , axis = 1 )
950- # v: [B, H, S_kv, D_head]
951- v = jnp .repeat (kv , self .num_heads , axis = 1 )
1007+ # Standardize compressed key/value layout to match multi-head sequence formats.
1008+ # # [B, 1, W, D_head] -> [B, W, 1, D_head]
1009+ if compressed_kv .shape [1 ] == 1 :
1010+ compressed_kv = compressed_kv .transpose (0 , 2 , 1 , 3 )
9521011
953- # Compute attention logits
954- # logits: [B, H, S, S_kv ]
955- logits = jnp .einsum ( "bhsd, bhkd -> bhsk" , q , k , precision = self . config . matmul_precision ) * self . scaling
1012+ # Concatenate local sequence keys with compressed long-range cache blocks along sequence dimension.
1013+ # # [B, S, 1, D_head] + [B, W, 1, D_head] -> [B, S + W, 1, D_head ]
1014+ kv = jnp .concatenate ([ kv , compressed_kv ], axis = 1 )
9561015
957- # Apply attention mask addition and block bias concatenation
1016+ # Reconcile structural block bias masks with runtime attention masks.
9581017 if attention_mask is not None :
9591018 if block_bias is not None :
1019+ # Concatenate block bias mask to attention mask along trailing sequence dimension.
1020+ # # [B, 1, S, S] + [B, 1, S, W] -> [B, 1, S, S + W]
9601021 attention_mask = jnp .concatenate ([attention_mask , block_bias .astype (attention_mask .dtype )], axis = - 1 )
961- elif kv .shape [2 ] > attention_mask .shape [- 1 ]:
962- pad_width = kv .shape [2 ] - attention_mask .shape [- 1 ]
1022+ elif kv .shape [1 ] > attention_mask .shape [- 1 ]:
1023+ # Pad attention mask with zero-value allowed elements to match extended key/value sequence length.
1024+ # # [B, 1, S, S] -> [B, 1, S, S + W]
1025+ pad_width = kv .shape [1 ] - attention_mask .shape [- 1 ]
9631026 attention_mask = jnp .pad (attention_mask , ((0 , 0 ), (0 , 0 ), (0 , 0 ), (0 , pad_width )), constant_values = 0.0 )
964- logits = logits + attention_mask
965-
966- # Concatenate learnable attention sinks
967- # sinks: [1, H, 1, 1] -> [B, H, S, 1]
968- sinks = self .sinks .value .reshape (1 , - 1 , 1 , 1 )
969- sinks = jnp .broadcast_to (sinks , (batch , self .num_heads , seq_len , 1 ))
970- # combined_logits: [B, H, S, S_kv + 1]
971- combined_logits = jnp .concatenate ([logits , sinks ], axis = - 1 )
972-
973- # Stable Softmax projection
974- combined_logits = combined_logits - jnp .max (combined_logits , axis = - 1 , keepdims = True )
975- probs = jax .nn .softmax (combined_logits , axis = - 1 )
976- # Drop sinks representation column
977- # attn_weights: [B, H, S, S_kv]
978- attn_weights = probs [..., :- 1 ]
979-
980- # Project attention weights onto values
981- # attn_output: [B, H, S, D_head]
982- attn_output = jnp .einsum ("bhsk, bhkd -> bhsd" , attn_weights , v , precision = self .config .matmul_precision )
983-
984- # Apply conjugate RoPE transformation to restore position invariants
985- attn_output = apply_rotary_pos_emb (attn_output , cos , - sin , unsqueeze_dim = 1 )
986-
987- # Map outputs to grouped linear configuration
988- # grouped: [B, S, o_groups, (H / o_groups) * D_head]
989- grouped = attn_output .transpose (0 , 2 , 1 , 3 ).reshape (batch , seq_len , self .config .o_groups , - 1 )
990- # grouped: [B, S, o_groups, o_lora_rank]
1027+ # Ensure key/value sequence length is perfectly divisible by the Splash attention block size (sa_block_kv).
1028+ # Hardware Matrix Multiply Units (MXUs) and XLA Pallas kernels enforce strict memory layout alignment grids.
1029+ # When Splash Flash Attention is active, the runtime key/value sequence dimension must perfectly divide by sa_block_kv.
1030+ # Because long-range cache compressors append dynamic auxiliary tokens (e.g. +32 tokens), the resulting combined length
1031+ # may break hardware divisibility constraints (e.g. 4128 % 512 != 0).
1032+ # This dynamic padding forces exact MXU grid alignment.
1033+ # # [B, S + W, 1, D_head] -> [B, align(S + W, sa_block_kv), 1, D_head]
1034+ if self .config .sa_block_kv > 0 and kv .shape [1 ] % self .config .sa_block_kv != 0 :
1035+ pad_len = self .config .sa_block_kv - (kv .shape [1 ] % self .config .sa_block_kv )
1036+ kv = jnp .pad (kv , ((0 , 0 ), (0 , pad_len ), (0 , 0 ), (0 , 0 )), constant_values = 0.0 )
1037+ if attention_mask is not None :
1038+ # Pad 4D attention mask along trailing key/value sequence axis.
1039+ # # [B, 1, Q, S + W] -> [B, 1, Q, align(S + W, sa_block_kv)]
1040+ attention_mask = jnp .pad (attention_mask , ((0 , 0 ), (0 , 0 ), (0 , 0 ), (0 , pad_len )), constant_values = 0.0 )
1041+
1042+ # Squeeze redundant head dimension from 4D attention masks to ensure compatibility with AttentionOp core.
1043+ # # [B, 1, S, S + W] -> [B, S, S + W]
1044+ unified_mask = (
1045+ jnp .squeeze (attention_mask , axis = 1 ) if attention_mask is not None and attention_mask .ndim == 4 else attention_mask
1046+ )
1047+
1048+ # Execute core attention operator pass over query and concatenated key/value sequences.
1049+ # # q: [B, S, H, D_head], kv: [B, S + W, 1, D_head] -> [B, S, H, D_head]
1050+ attn_output = self .attention_op (
1051+ query = q ,
1052+ key = kv ,
1053+ value = kv ,
1054+ decoder_segment_ids = kwargs .get ("decoder_segment_ids" , None ),
1055+ inputs_positions = position_ids ,
1056+ model_mode = kwargs .get ("model_mode" , MODEL_MODE_TRAIN ),
1057+ indexer_mask = unified_mask ,
1058+ sinks = self .sinks ,
1059+ )
1060+
1061+ # Apply conjugate RoPE rotation (-sin) to attention outputs to un-rotate representations.
1062+ # Un-rotating aligns output feature spaces prior to multi-head mixing projections.
1063+ # # [B, S, H, D_head] -> [B, S, H, D_head]
1064+ attn_output = apply_rotary_pos_emb (attn_output , cos , - sin , unsqueeze_dim = 2 )
1065+
1066+ # Reshape attention outputs into block-diagonal output groups.
1067+ # # [B, S, H, D_head] -> [B, S, o_groups, H * D_head / o_groups]
1068+ grouped = attn_output .reshape (batch , seq_len , self .config .o_groups , - 1 )
1069+
1070+ # Apply block-diagonal grouped linear projections to mix intra-group features.
1071+ # # [B, S, o_groups, H * D_head / o_groups] -> [B, S, o_groups, o_lora_rank]
9911072 grouped = self .o_a_proj (grouped )
992- # Flatten back to grouped rank representations
993- # grouped_flat: [B, S, o_groups * o_lora_rank]
1073+
1074+ # Flatten grouped representations into a unified feature vector per sequence position.
1075+ # # [B, S, o_groups, o_lora_rank] -> [B, S, o_groups * o_lora_rank]
9941076 grouped_flat = grouped .reshape (batch , seq_len , - 1 )
9951077
996- # Final linear projection to hidden dimension space
997- # output: [B, S, D_model]
1078+ # Project mixed representations back to global model hidden dimension.
1079+ # # [B, S, o_groups * o_lora_rank] -> [B, S, D_model]
9981080 output = self .o_b_proj (grouped_flat )
9991081
1000- return output , attn_weights
1082+ return output , None
0 commit comments