Skip to content

Commit afcb5ff

Browse files
committed
Overlap moe comms with collective matmul
Implements Overlapped All-Gather and GMM Computation (Collective Matmul) for Ring of Experts (RoE) in MoE layers to hide communication overhead and token sorting latency. The changes include: - Chunks input activations along the contracting embedding dimension (embed_dim // num_moe_emb_chunks). While chunk i computes GMM up-projections, chunk i+1 concurrently executes Ring of Experts All-Gather and routing across expert parallel shards. - Update kernel to use in-place Partial Sum Accumulation to avoid unnecessary copies using input_output_aliases. Testing - Layer Tests (tests/unit/moe_test.py): Verified chunked GMM v2 routing, partial sum accumulation, and MLP bias addition (test_moe_emb_chunking_gmm_v2 with mlp_bias=True). - AOT Compilation Tests (tests/unit/train_compile_test.py): Verified ahead-of-time HLO compilation and sharding on multi-slice topologies for chunked MoE (test_moe_emb_chunking and test_moe_emb_chunking_with_mlp_bias). - Low-Level Pallas Kernel Tests (tests/unit/pallas_mosaic_tpu_v2_kernel_test.py): Verified numerical correctness and in-place buffer aliasing for partial sum accumulation across GMM (test_gmm_partial_sum0/1) and TGMM with zero-initialized empty expert groups (test_tgmm_empty_group_with_partial_sum0-3). - End-to-End Execution & Profiling: Verified E2E training execution on v6e-8 TPU VM (deepseek3-tiny with num_moe_emb_chunks=4) and generated XPlane / XProf traces confirming communication-compute overlap.
1 parent 2ee9fe1 commit afcb5ff

9 files changed

Lines changed: 493 additions & 136 deletions

File tree

src/maxtext/configs/base.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@ load_balance_loss_weight: 0.0 # weight for the load balance loss
220220
use_random_routing: false # whether to use random routing for debug/test purpose
221221
use_custom_sort_vjp: true # whether to use a custom VJP sort for efficient backward pass processing in sparse matmul
222222
use_ring_of_experts: false # whether to use ring of experts for sparse matmul expert parallelism
223+
num_moe_emb_chunks: 0 # number of chunks for overlapping token all-gather and GMM computation along embedding dimension
223224
# If true, peel the 'expert' mesh axis off the MoE dispatch/MLP batch dim so the expert GEMM
224225
# stays expert-parallel (AllToAll); false keeps 'expert' on the batch dim (activation_batch_moe).
225226
# Only affects the dense (dense_matmul) MoE path; the sparse (shard_map) path is unaffected.

src/maxtext/configs/types.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,9 @@ class Attention(BaseModel):
623623
" Requires use_tokamax_gmm: true. Currently incompatible with quantization."
624624
),
625625
)
626+
num_moe_emb_chunks: int = Field(
627+
0, description="Number of chunks for overlapping token all-gather and GMM computation along embedding dimension."
628+
)
626629
ragged_block_size: int = Field(256, description="Block size for ragged attention.")
627630
enable_padding_causal_mask: bool = Field(True, description="Temporary flag for TE padding.")
628631
use_tokamax_splash: bool = Field(False, description="Whether to use tokamax splash attention.")
@@ -2591,6 +2594,17 @@ def _validate_use_te_comm_gemm_overlap(self):
25912594
"TE Collective GEMM operations are only supported for TE quantization recipes (i.e. starting with 'te_')."
25922595
)
25932596

2597+
def validate_num_moe_emb_chunks(self):
2598+
"""
2599+
Validates that num_moe_emb_chunks is used with supported settings.
2600+
"""
2601+
if self.num_moe_emb_chunks > 0:
2602+
if not self.use_gmm_v2 or not self.use_ring_of_experts:
2603+
raise ValueError(
2604+
f"num_moe_emb_chunks > 0 requires use_gmm_v2=True and use_ring_of_experts=True. "
2605+
f"Got use_gmm_v2={self.use_gmm_v2}, use_ring_of_experts={self.use_ring_of_experts}."
2606+
)
2607+
25942608
@model_validator(mode="after")
25952609
def set_derived_and_validate_values(self) -> "MaxTextConfig":
25962610
"""
@@ -3200,6 +3214,7 @@ def calculate_global_batch_sizes(per_device_batch_size, expansion_factor, num_de
32003214
if self.model_name.startswith("deepseek4") and self.first_num_hash_layers > 0 and self.use_ring_of_experts:
32013215
raise ValueError("DeepSeek V4 hash routing is currently not supported with ring of experts.")
32023216
self.validate_ragged_buffer_factor()
3217+
self.validate_num_moe_emb_chunks()
32033218

32043219
# Gemma 4 small (E2B / E4B) uses per-layer KV sharing, which is incompatible with nn.scan.
32053220
if self.model_name in ("gemma4-e2b", "gemma4-e4b") and self.scan_layers:

src/maxtext/kernels/megablox/ops.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ def gmm(
7474
qwix_rule: qwix.QtRule | None = None,
7575
use_manual_quantization: bool = False, # used in batchsplit
7676
use_gmm_v2: bool = False,
77+
partial_sum: jnp.ndarray | None = None,
7778
):
7879
"""Grouped matrix multiplication operation."""
7980
quantization_rule = None
@@ -112,6 +113,7 @@ def gmm(
112113
lhs_vma_axes,
113114
rhs_vma_axes,
114115
use_gmm_v2,
116+
partial_sum,
115117
)
116118

117119

@@ -142,6 +144,7 @@ def _gmm_fwd(
142144
lhs_vma_axes: tuple = tuple(),
143145
rhs_vma_axes: tuple = tuple(),
144146
use_gmm_v2: bool = False,
147+
partial_sum: jnp.ndarray | None = None,
145148
) -> tuple[
146149
jnp.ndarray,
147150
tuple[
@@ -200,6 +203,7 @@ def _gmm_fwd(
200203
tile_n=tiling[2],
201204
),
202205
preferred_element_type=preferred_element_type,
206+
partial_sum=partial_sum,
203207
)
204208
elif use_tokamax_backend:
205209
# manual_axis_type is for gmm with shard_map check_vma=True, needs tokamax > 0.0.12
@@ -234,7 +238,7 @@ def _gmm_fwd(
234238
for axis in lhs_vma_axes:
235239
out = jax.lax.pcast(out, axis_name=axis, to="varying")
236240

237-
return out, (lhs, rhs, group_sizes, group_offset)
241+
return out, (lhs, rhs, group_sizes, group_offset, partial_sum)
238242

239243

240244
def _gmm_bwd(
@@ -256,12 +260,13 @@ def _gmm_bwd(
256260
jnp.ndarray | qpl.QArray,
257261
jnp.ndarray,
258262
jnp.ndarray | None,
263+
jnp.ndarray | None,
259264
],
260265
grad: jnp.ndarray,
261-
) -> tuple[jnp.ndarray, jnp.ndarray, None, None, jnp.ndarray]:
266+
) -> tuple[jnp.ndarray, jnp.ndarray, None, None, jnp.ndarray, jnp.ndarray | None]:
262267
"""Backward function for throughput GMM VJP."""
263268
del preferred_element_type
264-
lhs, rhs, group_sizes, group_offset = residual
269+
lhs, rhs, group_sizes, group_offset, partial_sum_fwd = residual
265270
num_actual_groups = rhs.shape[0]
266271

267272
# Jargon used here:
@@ -406,4 +411,5 @@ def _gmm_bwd(
406411
#
407412
# TODO(tgale, enriqueps, apaske): Fuse this transposition into the tgmm.
408413
drhs = drhs.swapaxes(1, 2) if transpose_rhs else drhs
409-
return dlhs, drhs, None, None, grad
414+
dpartial_sum = grad if partial_sum_fwd is not None else None
415+
return dlhs, drhs, None, None, grad, dpartial_sum

src/maxtext/kernels/megablox/pallas_mosaic_tpu_v2_gmm_kernel.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -909,7 +909,9 @@ def calculate_tiling(
909909
tile_n_limit //= fuse_act_factor
910910

911911
def _is_tile_k_quant_block_compatible(tk: int) -> bool:
912-
if tk % rhs_cfgs.quant_block_size != 0 and rhs_cfgs.quant_block_size % tk != 0: # pyrefly: ignore[unsupported-operation]
912+
if (
913+
tk % rhs_cfgs.quant_block_size != 0 and rhs_cfgs.quant_block_size % tk != 0
914+
): # pyrefly: ignore[unsupported-operation]
913915
return False
914916
return True
915917

@@ -999,7 +1001,9 @@ def validate_inputs(
9991001
if rhs_bias is not None:
10001002
assert rhs_bias.shape == (size_group, 1, size_n)
10011003
if partial_sum is not None:
1002-
assert partial_sum.shape == (size_m, size_n)
1004+
assert partial_sum.shape[-1] == size_n
1005+
# lhs's m dimension can sometimes be padded to wi_tile_fwd_batch_seq
1006+
assert partial_sum.shape[0] <= size_m
10031007
if rhs_scale is not None:
10041008
num_quant_blocks = rhs_scale.shape[1]
10051009
assert rhs_scale.shape == (size_group, num_quant_blocks, 1, size_n)
@@ -1341,6 +1345,13 @@ def gmm_v2(
13411345
partial_sum_spec, # partial_sum
13421346
]
13431347

1348+
input_output_aliases = {}
1349+
if partial_sum is not None:
1350+
flat_args_preceding = (group_sizes, group_offset, lhs, rhs_weights)
1351+
leaves = jax.tree_util.tree_leaves(flat_args_preceding)
1352+
partial_sum_idx = sum(1 for x in leaves if x is not None)
1353+
input_output_aliases = {partial_sum_idx: 0}
1354+
13441355
return pl.pallas_call(
13451356
functools.partial(kernel_main, cfgs=cfgs),
13461357
out_shape=out_init,
@@ -1357,4 +1368,5 @@ def gmm_v2(
13571368
name=get_scope_name(cfgs),
13581369
cost_estimate=get_cost_estimate(cfgs),
13591370
metadata=get_metadata(cfgs),
1371+
input_output_aliases=input_output_aliases,
13601372
)(group_sizes, group_offset, lhs, rhs_weights, partial_sum)[:, : cfgs.out_size_n]

src/maxtext/kernels/megablox/pallas_mosaic_tpu_v2_tgmm_kernel.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -763,6 +763,13 @@ def tgmm_v2(
763763
partial_sum_spec,
764764
]
765765

766+
input_output_aliases = {}
767+
if partial_sum is not None:
768+
flat_args_preceding = (group_sizes, group_offset, lhs, rhs)
769+
leaves = jax.tree_util.tree_leaves(flat_args_preceding)
770+
partial_sum_idx = sum(1 for x in leaves if x is not None)
771+
input_output_aliases = {partial_sum_idx: 0}
772+
766773
raw_out = pl.pallas_call(
767774
functools.partial(tgmm_kernel_main, cfgs=cfgs),
768775
out_shape=out_init,
@@ -781,6 +788,7 @@ def tgmm_v2(
781788
# the metadata here is for profiling, debugging, and cost modeling.
782789
# It does not affect the kernel's computation.
783790
metadata=gmm_v2.get_metadata(cfgs),
791+
input_output_aliases=input_output_aliases,
784792
)(group_sizes, group_offset, lhs, rhs, partial_sum)[:, : dims.size_k, : dims.size_n]
785793

786794
if partial_sum is not None:

0 commit comments

Comments
 (0)