@@ -92,6 +92,26 @@ class RouteOutput:
9292 local_group_sizes : Optional [jax .Array ] = None
9393
9494
95+ def _truncate_matrix (all_shards_group_sizes : jax .Array , buffer_size : int ) -> jax .Array :
96+ """Truncates the traffic matrix to fit in buffer_size on receiver side.
97+
98+ When ragged_buffer_factor > 0, the receiver buffer has a fixed capacity
99+ (buffer_size). Due to routing imbalance, some shards might receive more tokens
100+ than this capacity. We use a prefix sum to deterministically truncate the
101+ received tokens on all shards, ensuring we don't write out of bounds.
102+ """
103+ cumsum = jnp .cumsum (all_shards_group_sizes , axis = 0 )
104+ clamped_cumsum = jnp .minimum (cumsum , buffer_size )
105+ clamped_cumsum_extended = jnp .concatenate (
106+ [
107+ jnp .zeros ((1 , all_shards_group_sizes .shape [1 ]), dtype = clamped_cumsum .dtype ),
108+ clamped_cumsum ,
109+ ],
110+ axis = 0 ,
111+ )
112+ return jnp .diff (clamped_cumsum_extended , axis = 0 )
113+
114+
95115def _sort_activations (
96116 inputs : jax .Array ,
97117 sort_indices : jax .Array ,
@@ -985,6 +1005,31 @@ def unpermute(
9851005 )
9861006 return output .reshape (batch_size , sequence_length , - 1 ).astype (self .dtype )
9871007
1008+ @staticmethod
1009+ def _maybe_truncate_local_group_size (
1010+ all_shard_local_sizes : jax .Array ,
1011+ buffer_size : int ,
1012+ ragged_buffer_factor : float ,
1013+ ) -> jax .Array :
1014+ """Optionally truncates the local group sizes if ragged_buffer_factor > 0.0.
1015+
1016+ When ragged_buffer_factor > 0, the receiver buffer has a fixed capacity
1017+ (buffer_size). Due to routing imbalance, some shards might receive more
1018+ tokens
1019+ than this capacity. We use a prefix sum to deterministically truncate the
1020+ received tokens on all shards, ensuring we don't write out of bounds.
1021+ """
1022+ if ragged_buffer_factor > 0.0 :
1023+ flat_sizes = all_shard_local_sizes .reshape (- 1 )
1024+ cumsum = jnp .cumsum (flat_sizes )
1025+ clamped_cumsum = jnp .minimum (cumsum , buffer_size )
1026+ clamped_cumsum_extended = jnp .concatenate ([jnp .zeros ((1 ,), dtype = clamped_cumsum .dtype ), clamped_cumsum ])
1027+ truncated_flat_sizes = jnp .diff (clamped_cumsum_extended )
1028+ truncated_all_shard_local_sizes = truncated_flat_sizes .reshape (all_shard_local_sizes .shape )
1029+ return jnp .sum (truncated_all_shard_local_sizes , axis = 0 )
1030+ else :
1031+ return jnp .sum (all_shard_local_sizes , axis = 0 )
1032+
9881033 @staticmethod
9891034 def local_permute (
9901035 inputs ,
@@ -995,6 +1040,7 @@ def local_permute(
9951040 global_sorted_experts = None ,
9961041 use_custom_sort_vjp = True ,
9971042 use_ragged_sort = False ,
1043+ ragged_buffer_factor = - 1.0 ,
9981044 ):
9991045 """Permutes tokens locally within an expert shard.
10001046
@@ -1047,7 +1093,9 @@ def local_permute(
10471093 # Total count of the local expert IDs is the sum of the counts across all
10481094 # batch shards, since all batch shards will send their contributions to the
10491095 # current expert shard.
1050- local_group_size = jnp .sum (all_shard_local_sizes , axis = 0 )
1096+ local_group_size = RoutedMoE ._maybe_truncate_local_group_size (
1097+ all_shard_local_sizes , inputs .shape [0 ], ragged_buffer_factor
1098+ )
10511099
10521100 # In this case, the data that needs to be processed by the local shard
10531101 # does not start from row 0 but actually starts at
@@ -1093,6 +1141,9 @@ def get_all_to_all_params(
10931141 shard_id ,
10941142 num_expert_parallelism ,
10951143 is_batch_sharded = True ,
1144+ ragged_buffer_factor = - 1.0 ,
1145+ buffer_size = None ,
1146+ is_dispatch = True ,
10961147 ):
10971148 """Generates input offsets, send sizes, output offsets, and receive sizes used for ragged_all_to_all."""
10981149
@@ -1151,30 +1202,99 @@ def transform_array(input_array, shard_id, strategy, is_batch_sharded):
11511202 else :
11521203 raise ValueError (f"Unknown transform array strategy: { strategy } " )
11531204
1154- input_offsets = transform_array (
1155- all_shards_group_sizes ,
1156- shard_id ,
1157- TransformStrategy .INPUT_OFFSET ,
1158- is_batch_sharded ,
1159- )
1160- send_sizes = transform_array (
1161- all_shards_group_sizes ,
1162- shard_id ,
1163- TransformStrategy .SEND_SIZE ,
1164- is_batch_sharded ,
1165- )
1166- output_offsets = transform_array (
1167- all_shards_group_sizes ,
1168- shard_id ,
1169- TransformStrategy .OUTPUT_OFFSET ,
1170- is_batch_sharded ,
1171- )
1172- recv_sizes = transform_array (
1173- all_shards_group_sizes ,
1174- shard_id ,
1175- TransformStrategy .RECV_SIZE ,
1176- is_batch_sharded ,
1177- )
1205+ if ragged_buffer_factor > 0.0 :
1206+ assert buffer_size is not None
1207+ truncated_all_shards_group_sizes = _truncate_matrix (all_shards_group_sizes , buffer_size )
1208+
1209+ if is_dispatch :
1210+ # For input_offsets, we use the untruncated group sizes because the
1211+ # sender's buffer still contains all tokens (including dropped ones).
1212+ input_offsets = transform_array (
1213+ all_shards_group_sizes ,
1214+ shard_id ,
1215+ TransformStrategy .INPUT_OFFSET ,
1216+ is_batch_sharded ,
1217+ )
1218+ # For send/recv sizes and output_offsets, we use truncated group sizes
1219+ # to ensure we don't write out of bounds of the receiver's capacity.
1220+ send_sizes = transform_array (
1221+ truncated_all_shards_group_sizes ,
1222+ shard_id ,
1223+ TransformStrategy .SEND_SIZE ,
1224+ is_batch_sharded ,
1225+ )
1226+ output_offsets = transform_array (
1227+ truncated_all_shards_group_sizes ,
1228+ shard_id ,
1229+ TransformStrategy .OUTPUT_OFFSET ,
1230+ is_batch_sharded ,
1231+ )
1232+ recv_sizes = transform_array (
1233+ truncated_all_shards_group_sizes ,
1234+ shard_id ,
1235+ TransformStrategy .RECV_SIZE ,
1236+ is_batch_sharded ,
1237+ )
1238+ else :
1239+ transposed_all_shards = jnp .transpose (all_shards_group_sizes )
1240+ transposed_truncated = jnp .transpose (truncated_all_shards_group_sizes )
1241+
1242+ # In combine stage, the roles are reversed:
1243+ # input_offsets/sizes and recv_sizes use truncated parameters because
1244+ # the combine sender buffer (dispatch receiver buffer) is packed.
1245+ input_offsets = transform_array (
1246+ transposed_truncated ,
1247+ shard_id ,
1248+ TransformStrategy .INPUT_OFFSET ,
1249+ is_batch_sharded ,
1250+ )
1251+ send_sizes = transform_array (
1252+ transposed_truncated ,
1253+ shard_id ,
1254+ TransformStrategy .SEND_SIZE ,
1255+ is_batch_sharded ,
1256+ )
1257+ # output_offsets use untruncated parameters because we write back
1258+ # to their original untruncated positions.
1259+ output_offsets = transform_array (
1260+ transposed_all_shards ,
1261+ shard_id ,
1262+ TransformStrategy .OUTPUT_OFFSET ,
1263+ is_batch_sharded ,
1264+ )
1265+ recv_sizes = transform_array (
1266+ transposed_truncated ,
1267+ shard_id ,
1268+ TransformStrategy .RECV_SIZE ,
1269+ is_batch_sharded ,
1270+ )
1271+ else :
1272+ matrix = all_shards_group_sizes if is_dispatch else jnp .transpose (all_shards_group_sizes )
1273+ input_offsets = transform_array (
1274+ matrix ,
1275+ shard_id ,
1276+ TransformStrategy .INPUT_OFFSET ,
1277+ is_batch_sharded ,
1278+ )
1279+ send_sizes = transform_array (
1280+ matrix ,
1281+ shard_id ,
1282+ TransformStrategy .SEND_SIZE ,
1283+ is_batch_sharded ,
1284+ )
1285+ output_offsets = transform_array (
1286+ matrix ,
1287+ shard_id ,
1288+ TransformStrategy .OUTPUT_OFFSET ,
1289+ is_batch_sharded ,
1290+ )
1291+ recv_sizes = transform_array (
1292+ matrix ,
1293+ shard_id ,
1294+ TransformStrategy .RECV_SIZE ,
1295+ is_batch_sharded ,
1296+ )
1297+
11781298 return input_offsets , send_sizes , output_offsets , recv_sizes
11791299
11801300 def transform_bias (self , experts_index , * biases ):
@@ -1549,19 +1669,21 @@ def ra2a_and_route(x, logits, pre_bias_logits, num_ep, expert_shard_id, rngs, in
15491669
15501670 if is_batch_sharded_by_expert :
15511671 all_shards_group_sizes = jax .lax .all_gather (reshaped_group_sizes , axis_name = batch_axis )
1552- input_offsets , send_sizes , output_offsets , recv_sizes = RoutedMoE .get_all_to_all_params (
1553- all_shards_group_sizes ,
1554- expert_shard_id ,
1555- num_ep ,
1556- )
1557-
15581672 buffer_size = self .get_ragged_buffer_size (
15591673 jnp .shape (x )[0 ],
15601674 num_ep ,
15611675 self .config .num_experts ,
15621676 self .config .num_experts_per_tok ,
15631677 self .config .ragged_buffer_factor ,
15641678 )
1679+ input_offsets , send_sizes , output_offsets , recv_sizes = RoutedMoE .get_all_to_all_params (
1680+ all_shards_group_sizes ,
1681+ expert_shard_id ,
1682+ num_ep ,
1683+ ragged_buffer_factor = self .config .ragged_buffer_factor ,
1684+ buffer_size = buffer_size ,
1685+ )
1686+
15651687 output_shape = jax .lax .empty ((buffer_size , self .moe_expert_input_dim ), dtype = x .dtype )
15661688
15671689 x = jax .lax .ragged_all_to_all (
@@ -1581,6 +1703,7 @@ def ra2a_and_route(x, logits, pre_bias_logits, num_ep, expert_shard_id, rngs, in
15811703 shard_index = expert_shard_id ,
15821704 use_custom_sort_vjp = self .config .use_custom_sort_vjp ,
15831705 use_ragged_sort = self .config .use_ragged_sort ,
1706+ ragged_buffer_factor = self .config .ragged_buffer_factor ,
15841707 )
15851708 else :
15861709 x , local_sorted_indices , group_sizes , selected_experts = RoutedMoE .local_permute (
@@ -1592,6 +1715,7 @@ def ra2a_and_route(x, logits, pre_bias_logits, num_ep, expert_shard_id, rngs, in
15921715 global_sorted_experts = selected_experts ,
15931716 use_custom_sort_vjp = self .config .use_custom_sort_vjp ,
15941717 use_ragged_sort = self .config .use_ragged_sort ,
1718+ ragged_buffer_factor = self .config .ragged_buffer_factor ,
15951719 )
15961720
15971721 return (
@@ -1779,10 +1903,14 @@ def unsort_output_and_ra2a(intermediate_output, routing, route_metadata, output_
17791903 self .config .use_custom_sort_vjp ,
17801904 )
17811905
1906+ buffer_size = intermediate_output .shape [0 ]
17821907 input_offsets , send_sizes , output_offsets , recv_sizes = RoutedMoE .get_all_to_all_params (
1783- jnp . transpose ( route_metadata .all_shards_group_sizes ) ,
1908+ route_metadata .all_shards_group_sizes ,
17841909 route_metadata .expert_shard_id ,
17851910 self .get_expert_parallelism_size (),
1911+ ragged_buffer_factor = self .config .ragged_buffer_factor ,
1912+ buffer_size = buffer_size ,
1913+ is_dispatch = False ,
17861914 )
17871915 return jax .lax .ragged_all_to_all (
17881916 local_output ,
@@ -1802,6 +1930,7 @@ def unsort_output_and_ra2a(intermediate_output, routing, route_metadata, output_
18021930 route_metadata .expert_shard_id ,
18031931 self .get_expert_parallelism_size (),
18041932 is_batch_sharded = False ,
1933+ is_dispatch = False ,
18051934 )
18061935 return jax .lax .ragged_all_to_all (
18071936 intermediate_output ,
0 commit comments