@@ -413,6 +413,44 @@ def _build_padding_segment_ids(
413413 return segment_ids_cls (q = q_segment_ids , kv = kv_segment_ids )
414414
415415
416+ def _ulysses_head_chunk_ranges (num_heads : int , ulysses_shards : int , num_chunks : int ):
417+ """Build head-axis ranges for chunked Ulysses all-to-all.
418+
419+ The Ulysses all-to-all splits each local chunk's head axis over
420+ `ulysses_shards`, so every returned range length is a multiple of
421+ `ulysses_shards`. When `num_chunks` does not evenly divide the number of
422+ Ulysses-sized head groups, earlier chunks get the floor-sized range and the
423+ final chunk carries the remainder.
424+
425+ Returns:
426+ A list of `(start, end)` half-open ranges over the head axis. Concatenating
427+ tensors sliced with these ranges along the head axis restores the original
428+ head layout. For `num_chunks <= 1`, returns `[(0, num_heads)]`, which is the
429+ unchunked all-to-all path.
430+ """
431+ if num_chunks <= 1 :
432+ return [(0 , num_heads )]
433+ if num_heads % ulysses_shards != 0 :
434+ raise ValueError (
435+ "Ulysses attention requires the number of heads to be divisible by the Ulysses shard count, "
436+ f"got heads={ num_heads } and ulysses_shards={ ulysses_shards } ."
437+ )
438+
439+ head_groups = num_heads // ulysses_shards
440+ num_chunks = min (num_chunks , head_groups )
441+ regular_groups_per_chunk = max (1 , head_groups // num_chunks )
442+
443+ ranges = []
444+ start_group = 0
445+ for chunk_idx in range (num_chunks ):
446+ end_group = head_groups if chunk_idx == num_chunks - 1 else min (start_group + regular_groups_per_chunk , head_groups )
447+ if start_group >= end_group :
448+ break
449+ ranges .append ((start_group * ulysses_shards , end_group * ulysses_shards ))
450+ start_group = end_group
451+ return ranges
452+
453+
416454def _tpu_flash_attention (
417455 query : jax .Array ,
418456 key : jax .Array ,
@@ -640,6 +678,7 @@ def _ulysses_attention(
640678 use_custom_kernel : bool = False ,
641679 use_base2_exp : bool = True ,
642680 use_experimental_scheduler : bool = False ,
681+ ulysses_attention_chunks : int = 1 ,
643682) -> jax .Array :
644683 """Ulysses sequence-parallel attention.
645684
@@ -662,6 +701,7 @@ def _ulysses_attention(
662701 "Ulysses attention requires the number of heads to be divisible by the context shard count, "
663702 f"got heads={ num_heads } and context_shards={ num_shards } ."
664703 )
704+
665705 if not use_custom_kernel :
666706 block_sizes = _select_flash_block_sizes (query , key , flash_block_sizes , dtype , "flash" )
667707
@@ -762,7 +802,21 @@ def wrap_ulysses_attention(query, key, value):
762802 "Warning, batch dimension should be shardable among the devices in data and fsdp"
763803 f" axis, batch dimension: { query .shape [0 ]} , devices_in_batch_sharding: { devices_in_batch_sharding } "
764804 )
765- x = wrap_ulysses_attention (query , key , value )
805+ head_chunk_ranges = _ulysses_head_chunk_ranges (num_heads , num_shards , ulysses_attention_chunks )
806+ if len (head_chunk_ranges ) > 1 :
807+ # Run Ulysses all-to-all per head group so XLA can overlap one group's
808+ # collective with another group's head-parallel local attention compute.
809+ chunk_outputs = [
810+ wrap_ulysses_attention (
811+ query [:, start :end ],
812+ key [:, start :end ],
813+ value [:, start :end ],
814+ )
815+ for start , end in head_chunk_ranges
816+ ]
817+ x = jnp .concatenate (chunk_outputs , axis = 1 )
818+ else :
819+ x = wrap_ulysses_attention (query , key , value )
766820 x = x [:, :, :orig_q_seq_len , :]
767821 x = _reshape_heads_to_head_dim (x )
768822
@@ -787,6 +841,7 @@ def _ulysses_ring_attention(
787841 use_base2_exp : bool = False ,
788842 use_experimental_scheduler : bool = False ,
789843 ulysses_shards : int = - 1 ,
844+ ulysses_attention_chunks : int = 1 ,
790845) -> jax .Array :
791846 """2D context-parallel attention using a private Ulysses x ring mesh.
792847
@@ -916,7 +971,21 @@ def wrap_ulysses_ring_attention(query, key, value):
916971 "Warning, batch dimension should be shardable among the devices in data and fsdp"
917972 f" axis, batch dimension: { query .shape [0 ]} , devices_in_batch_sharding: { devices_in_batch_sharding } "
918973 )
919- x = wrap_ulysses_ring_attention (query , key , value )
974+ head_chunk_ranges = _ulysses_head_chunk_ranges (num_heads , num_ulysses_shards , ulysses_attention_chunks )
975+ if len (head_chunk_ranges ) > 1 :
976+ # Run the Ulysses phase per head group before the ring phase so XLA can
977+ # overlap all-to-all collectives with head-parallel local attention compute.
978+ chunk_outputs = [
979+ wrap_ulysses_ring_attention (
980+ query [:, start :end ],
981+ key [:, start :end ],
982+ value [:, start :end ],
983+ )
984+ for start , end in head_chunk_ranges
985+ ]
986+ x = jnp .concatenate (chunk_outputs , axis = 1 )
987+ else :
988+ x = wrap_ulysses_ring_attention (query , key , value )
920989 x = jax .lax .with_sharding_constraint (x , q_axis_names )
921990 x = x [:, :, :orig_q_seq_len , :]
922991 x = _reshape_heads_to_head_dim (x )
@@ -941,6 +1010,7 @@ def _ulysses_ring_custom_attention(
9411010 use_base2_exp : bool = True ,
9421011 use_experimental_scheduler : bool = False ,
9431012 bidirectional : bool = False ,
1013+ ulysses_attention_chunks : int = 1 ,
9441014) -> jax .Array :
9451015 """Hybrid Ulysses + Ring (USP) with the CUSTOM splash kernel on main's mesh.
9461016
@@ -1057,7 +1127,21 @@ def wrap_ulysses_ring_attention(query, key, value):
10571127 attention_output = a2a (attention_output , split_axis = 2 , concat_axis = 1 )
10581128 return attention_output
10591129
1060- x = wrap_ulysses_ring_attention (query , key , value )
1130+ head_chunk_ranges = _ulysses_head_chunk_ranges (num_heads , num_ulysses_shards , ulysses_attention_chunks )
1131+ if len (head_chunk_ranges ) > 1 :
1132+ # Run the custom Ulysses phase per head group before the custom ring phase so
1133+ # XLA can overlap all-to-all collectives with head-parallel attention compute.
1134+ chunk_outputs = [
1135+ wrap_ulysses_ring_attention (
1136+ query [:, start :end ],
1137+ key [:, start :end ],
1138+ value [:, start :end ],
1139+ )
1140+ for start , end in head_chunk_ranges
1141+ ]
1142+ x = jnp .concatenate (chunk_outputs , axis = 1 )
1143+ else :
1144+ x = wrap_ulysses_ring_attention (query , key , value )
10611145 x = jax .lax .with_sharding_constraint (x , q_axis_names )
10621146 x = x [:, :, :orig_q_seq_len , :]
10631147 x = _reshape_heads_to_head_dim (x )
@@ -1207,6 +1291,7 @@ def ulysses_custom_kernel(q, k, v, context):
12071291 use_custom_kernel = True ,
12081292 use_base2_exp = context .get ("use_base2_exp" , True ),
12091293 use_experimental_scheduler = context .get ("use_experimental_scheduler" , False ),
1294+ ulysses_attention_chunks = context ["ulysses_attention_chunks" ],
12101295 )
12111296
12121297
@@ -1228,6 +1313,7 @@ def ulysses_ring_custom_kernel(q, k, v, context):
12281313 ulysses_shards = context ["ulysses_shards" ],
12291314 use_base2_exp = context .get ("use_base2_exp" , True ),
12301315 use_experimental_scheduler = context .get ("use_experimental_scheduler" , False ),
1316+ ulysses_attention_chunks = context ["ulysses_attention_chunks" ],
12311317 )
12321318
12331319
@@ -1253,6 +1339,7 @@ def ulysses_ring_custom_bidir_kernel(q, k, v, context):
12531339 use_base2_exp = context .get ("use_base2_exp" , True ),
12541340 use_experimental_scheduler = context .get ("use_experimental_scheduler" , False ),
12551341 bidirectional = True ,
1342+ ulysses_attention_chunks = context ["ulysses_attention_chunks" ],
12561343 )
12571344
12581345
@@ -1271,6 +1358,7 @@ def ulysses_kernel(q, k, v, context):
12711358 mask_padding_tokens = context ["mask_padding_tokens" ],
12721359 residual_checkpoint_name = context ["residual_checkpoint_name" ],
12731360 attention_mask = context ["attention_mask" ],
1361+ ulysses_attention_chunks = context ["ulysses_attention_chunks" ],
12741362 )
12751363
12761364
@@ -1292,6 +1380,7 @@ def ulysses_ring_kernel(q, k, v, context):
12921380 use_base2_exp = context ["use_base2_exp" ],
12931381 use_experimental_scheduler = context ["use_experimental_scheduler" ],
12941382 ulysses_shards = context ["ulysses_shards" ],
1383+ ulysses_attention_chunks = context ["ulysses_attention_chunks" ],
12951384 )
12961385
12971386
@@ -1404,6 +1493,7 @@ def _apply_attention(
14041493 use_base2_exp : bool = False ,
14051494 use_experimental_scheduler : bool = False ,
14061495 ulysses_shards : int = - 1 ,
1496+ ulysses_attention_chunks : int = 1 ,
14071497):
14081498 """Routes to different attention kernels using a module-level registry."""
14091499
@@ -1435,6 +1525,7 @@ def _apply_attention(
14351525 "use_base2_exp" : use_base2_exp ,
14361526 "use_experimental_scheduler" : use_experimental_scheduler ,
14371527 "ulysses_shards" : ulysses_shards ,
1528+ "ulysses_attention_chunks" : ulysses_attention_chunks ,
14381529 "dim_head" : dim_head ,
14391530 "split_head_dim" : split_head_dim ,
14401531 "float32_qk_product" : float32_qk_product ,
@@ -1648,11 +1739,13 @@ def __init__(
16481739 use_base2_exp : bool = False ,
16491740 use_experimental_scheduler : bool = False ,
16501741 ulysses_shards : int = - 1 ,
1742+ ulysses_attention_chunks : int = 1 ,
16511743 ):
16521744 self .dpa_layer = None
16531745 self .use_base2_exp = use_base2_exp
16541746 self .use_experimental_scheduler = use_experimental_scheduler
16551747 self .ulysses_shards = ulysses_shards
1748+ self .ulysses_attention_chunks = ulysses_attention_chunks
16561749 if attention_kernel == "cudnn_flash_te" :
16571750 from transformer_engine .jax .flax .transformer import DotProductAttention # pytype: disable=import-error
16581751
@@ -1716,6 +1809,7 @@ def apply_attention(self, query: Array, key: Array, value: Array, attention_mask
17161809 use_base2_exp = self .use_base2_exp if hasattr (self , "use_base2_exp" ) else False ,
17171810 use_experimental_scheduler = self .use_experimental_scheduler if hasattr (self , "use_experimental_scheduler" ) else False ,
17181811 ulysses_shards = (self .ulysses_shards if hasattr (self , "ulysses_shards" ) else - 1 ),
1812+ ulysses_attention_chunks = (self .ulysses_attention_chunks if hasattr (self , "ulysses_attention_chunks" ) else 1 ),
17191813 )
17201814
17211815
@@ -1737,6 +1831,7 @@ class AttentionOp(nn.Module):
17371831 use_base2_exp : bool = False
17381832 use_experimental_scheduler : bool = False
17391833 ulysses_shards : int = - 1
1834+ ulysses_attention_chunks : int = 1
17401835
17411836 def setup (self ):
17421837 self .dpa_layer = None
@@ -1785,6 +1880,7 @@ def apply_attention(self, query: Array, key: Array, value: Array, attention_mask
17851880 use_base2_exp = self .use_base2_exp ,
17861881 use_experimental_scheduler = self .use_experimental_scheduler ,
17871882 ulysses_shards = self .ulysses_shards ,
1883+ ulysses_attention_chunks = self .ulysses_attention_chunks ,
17881884 )
17891885
17901886
@@ -1827,6 +1923,7 @@ def __init__(
18271923 "use_base2_exp" : False ,
18281924 "use_experimental_scheduler" : False ,
18291925 "ulysses_shards" : - 1 ,
1926+ "ulysses_attention_chunks" : 1 ,
18301927 ** (attention_config or {}),
18311928 }
18321929
@@ -1878,6 +1975,7 @@ def __init__(
18781975 use_base2_exp = attention_config ["use_base2_exp" ],
18791976 use_experimental_scheduler = attention_config ["use_experimental_scheduler" ],
18801977 ulysses_shards = attention_config ["ulysses_shards" ],
1978+ ulysses_attention_chunks = attention_config ["ulysses_attention_chunks" ],
18811979 )
18821980 # None axes corresponds to the stacked weights across all blocks
18831981 # because of the use of nnx.vmap and nnx.scan.
0 commit comments