@@ -615,6 +615,7 @@ def generate_attention_mask(
615615 previous_chunk : Any = None ,
616616 bidirectional_mask : Any = None ,
617617 compressed_mask : Optional [Array ] = None ,
618+ segment_positions : Array | None = None ,
618619 ) -> Array | None :
619620 """Generates a combined attention mask for Transformer models.
620621
@@ -675,6 +676,9 @@ def generate_attention_mask(
675676 compressed_mask: Optional `Array`. A pre-computed attention mask for
676677 compressed kv blocks (e.g., DeepSeek-V4 compressed attention). If provided, it is
677678 concatenated with the dynamically generated uncompressed mask.
679+ segment_positions: Optional `Array` of shape `[batch_size,
680+ q_sequence_length]`. Identifies original positions for load-balanced
681+ context-parallel inputs.
678682
679683 Returns:
680684 An `Array` representing the attention mask, with shape
@@ -710,20 +714,33 @@ def generate_attention_mask(
710714 elif model_mode == MODEL_MODE_AUTOREGRESSIVE and q_seq_len == 1 :
711715 # In autoregression, the query position is the last position in the KV sequence.
712716 next_pos = kv_seq_len - 1
717+ use_segment_positions = (
718+ segment_positions is not None
719+ and self .config .context_parallel_load_balance
720+ and self .mesh .shape .get (self .config .context_sharding , 1 ) > 1
721+ and previous_chunk is None
722+ and model_mode != MODEL_MODE_AUTOREGRESSIVE
723+ )
724+ if use_segment_positions :
725+ position_row_ids = segment_positions [:, :, None ]
726+ position_col_ids = segment_positions [:, None , :]
713727
714728 causal_mask = None
715729 if model_mode != MODEL_MODE_AUTOREGRESSIVE and self .attention_type not in (
716730 AttentionType .FULL ,
717731 AttentionType .COMPRESSED ,
718732 ):
719- mask_shape = (q_seq_len , kv_seq_len )
720- # row_ids indicates the position of query
721- # col_ids indicates the position of kv
722- row_ids = jax .lax .broadcasted_iota (jnp .int32 , mask_shape , 0 )
723- col_ids = jax .lax .broadcasted_iota (jnp .int32 , mask_shape , 1 )
724- # Attention mask for chunked prefill is generated in the same way
725- # as mentioned in SARATHI - https://arxiv.org/abs/2308.16369
726- causal_mask = (col_ids <= row_ids + next_pos )[None , None , None , :, :]
733+ if use_segment_positions :
734+ causal_mask = (position_col_ids <= position_row_ids )[:, None , None , :, :]
735+ else :
736+ mask_shape = (q_seq_len , kv_seq_len )
737+ # row_ids indicates the position of query
738+ # col_ids indicates the position of kv
739+ row_ids = jax .lax .broadcasted_iota (jnp .int32 , mask_shape , 0 )
740+ col_ids = jax .lax .broadcasted_iota (jnp .int32 , mask_shape , 1 )
741+ # Attention mask for chunked prefill is generated in the same way
742+ # as mentioned in SARATHI - https://arxiv.org/abs/2308.16369
743+ causal_mask = (col_ids <= row_ids + next_pos )[None , None , None , :, :]
727744
728745 output_mask = None
729746 if (mask is not None ) and (causal_mask is not None ):
@@ -737,11 +754,17 @@ def generate_attention_mask(
737754 if self .sliding_window_size is None :
738755 raise ValueError ("Sliding_window_size must be set if Local Sliding attention type" )
739756
740- row_ids_sliding = jax .lax .broadcasted_iota (jnp .int32 , (q_seq_len , 1 ), 0 ) + next_pos
741- col_ids_sliding = jax .lax .broadcasted_iota (jnp .int32 , (1 , kv_seq_len ), 1 )
757+ if use_segment_positions :
758+ row_ids_sliding = position_row_ids
759+ col_ids_sliding = position_col_ids
760+ else :
761+ row_ids_sliding = jax .lax .broadcasted_iota (jnp .int32 , (q_seq_len , 1 ), 0 ) + next_pos
762+ col_ids_sliding = jax .lax .broadcasted_iota (jnp .int32 , (1 , kv_seq_len ), 1 )
742763 sliding_mask = (col_ids_sliding > (row_ids_sliding - self .sliding_window_size )) & (
743764 col_ids_sliding <= row_ids_sliding
744765 )
766+ if use_segment_positions :
767+ sliding_mask = sliding_mask [:, None , None , :, :]
745768 output_mask = sliding_mask * output_mask
746769 elif self .attention_type == AttentionType .COMPRESSED :
747770 if compressed_mask is None :
@@ -772,12 +795,17 @@ def generate_attention_mask(
772795 return jnp .concatenate ([uncompressed_mask , compressed_mask ], axis = - 1 )
773796
774797 elif self .attention_type == AttentionType .CHUNK and output_mask is not None :
775- mask_shape = (q_seq_len , kv_seq_len )
776- chunk_mask = _generate_chunk_attention_mask (
777- mask_shape = (q_seq_len , kv_seq_len ),
778- chunk_size = self .chunk_attn_window_size ,
779- q_offset = next_pos ,
780- )
798+ if use_segment_positions :
799+ same_chunk = (position_row_ids // self .chunk_attn_window_size ) == (
800+ position_col_ids // self .chunk_attn_window_size
801+ )
802+ chunk_mask = (same_chunk & (position_row_ids >= position_col_ids ))[:, None , None , :, :]
803+ else :
804+ chunk_mask = _generate_chunk_attention_mask (
805+ mask_shape = (q_seq_len , kv_seq_len ),
806+ chunk_size = self .chunk_attn_window_size ,
807+ q_offset = next_pos ,
808+ )
781809 output_mask = chunk_mask * output_mask
782810
783811 if bidirectional_mask is not None :
@@ -976,6 +1004,7 @@ def apply_attention(
9761004 decoder_segment_ids ,
9771005 model_mode ,
9781006 previous_chunk ,
1007+ segment_positions = segment_positions ,
9791008 bidirectional_mask = bidirectional_mask ,
9801009 sinks = sinks ,
9811010 indexer_mask = indexer_mask ,
@@ -1301,10 +1330,17 @@ def create_sa_config(config, query, key, attn_logits_soft_cap):
13011330 if self .chunk_attn_window_size is None :
13021331 raise ValueError ("chunk_attn_window_size must be set for chunk attention type" )
13031332
1304- mask &= ChunkedCausalMask (
1305- shape = (query .shape [2 ], key .shape [2 ]),
1306- chunk_size = self .chunk_attn_window_size ,
1307- )
1333+ if use_load_balanced_cp :
1334+ mask &= LoadBalancedChunkedCausalMask (
1335+ shape = (query .shape [2 ], key .shape [2 ]),
1336+ chunk_size = self .chunk_attn_window_size ,
1337+ cp_size = cp_size ,
1338+ )
1339+ else :
1340+ mask &= ChunkedCausalMask (
1341+ shape = (query .shape [2 ], key .shape [2 ]),
1342+ chunk_size = self .chunk_attn_window_size ,
1343+ )
13081344
13091345 max_logit_value = None
13101346 if self .config .use_tokamax_splash :
@@ -1588,6 +1624,8 @@ def cudnn_flash_attention(
15881624 and self .config .context_parallel_strategy == "ring"
15891625 and self .config .context_parallel_load_balance
15901626 )
1627+ if using_context_parallelism and self .attention_type == AttentionType .CHUNK :
1628+ raise ValueError ("Chunk attention is not supported with CUDNN context parallelism." )
15911629
15921630 # Initialize default attention configuration
15931631 sliding_window_size = None
@@ -1815,6 +1853,7 @@ def apply_attention_dot(
18151853 decoder_segment_ids : Array | None ,
18161854 model_mode : str = MODEL_MODE_TRAIN ,
18171855 previous_chunk : Any = None ,
1856+ segment_positions : Array | None = None ,
18181857 bidirectional_mask : Any = None ,
18191858 sinks : Array | None = None ,
18201859 indexer_mask : Array | None = None ,
@@ -1880,6 +1919,7 @@ def apply_attention_dot(
18801919 previous_chunk ,
18811920 bidirectional_mask ,
18821921 compressed_mask = compressed_mask ,
1922+ segment_positions = segment_positions ,
18831923 )
18841924
18851925 if self .config .moba :
@@ -2254,3 +2294,21 @@ def __init__(
22542294 # LocalMask uses shard_count for mask-shard validation. cp_size is the
22552295 # context-parallel size used for the load-balanced query order.
22562296 self .q_sequence = _load_balanced_q_sequence (shape , cp_size )
2297+
2298+
2299+ class LoadBalancedChunkedCausalMask (ChunkedCausalMask ):
2300+ """Lazy chunked mask with load-balanced query positions."""
2301+
2302+ def __init__ (
2303+ self ,
2304+ shape : tuple [int , int ],
2305+ chunk_size : int ,
2306+ cp_size : int ,
2307+ shard_count : int = 1 ,
2308+ ):
2309+ super ().__init__ (
2310+ shape = shape ,
2311+ chunk_size = chunk_size ,
2312+ shard_count = shard_count ,
2313+ )
2314+ self .q_sequence = _load_balanced_q_sequence (shape , cp_size )
0 commit comments