Skip to content

Commit 9f9a6b2

Browse files
abhinavgoel95NuojCheng
authored andcommitted
Add fused MoE FFN1: fuse wi_0 and wi_1 into one grouped GEMM
When prefuse_moe_weights=True (requires sparse_matmul=True), the two FFN1 expert weight matrices [G,K,N] are concatenated into [G,K,2N] and dispatched as a single grouped GEMM, then split. This halves FFN1 kernel launches and reads input activations from HBM once instead of twice. Backend-agnostic: works with Megablox, Tokamax, and jax.lax.ragged_dot. When attention=vllm_rpa the fused tensor is passed directly to the vLLM-TPU serving kernel. correct sort kernel convergence
1 parent f2917ee commit 9f9a6b2

3 files changed

Lines changed: 21 additions & 8 deletions

File tree

src/maxtext/kernels/ragged/ragged_gather_reduce.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ def _preprocess(
385385
reduce_group_size: int,
386386
num_row_partitions: int,
387387
num_simd_lanes: int,
388-
) -> tuple[jax.Array, jax.Array, jax.Array, jax.Array]:
388+
) -> tuple[jax.Array, jax.Array, jax.Array, jax.Array, jax.Array]:
389389
"""Preprocesses indices for ragged gather reduce."""
390390
assert indices.ndim == 1, "Ragged scatter only supports 1d indices."
391391

@@ -416,12 +416,16 @@ def _preprocess(
416416
num_src_rows_per_row_partition.astype(jnp.int32),
417417
(0, num_simd_lanes - num_row_partitions),
418418
)
419+
# If there is no valid source row in a reduce group, we set the mask to
420+
# False, so that the output for that group is set to zero.
421+
mask = jnp.any(valid_rows_mask.reshape(-1, reduce_group_size), axis=-1)
419422

420423
return (
421424
src_indices,
422425
dst_indices,
423426
topk_weights,
424427
num_src_rows_per_row_partition,
428+
mask,
425429
)
426430

427431

@@ -481,11 +485,6 @@ def ragged_gather_reduce(
481485
# Heuristic threshold on whether to fallback for small inputs.
482486
dtype = x.dtype
483487
dtype_bytes = jax.dtypes.itemsize_bits(dtype) // 8
484-
if jnp.size(x) * dtype_bytes * 2 < pltpu.get_tpu_info().vmem_capacity_bytes * 0.6:
485-
# For small {input + output}, it's likely that both can be put in TC VMEM,
486-
# so it's likely faster to run TC-based implementation on it than going
487-
# through SC, without data movement to/from HBM.
488-
return _fallback_implementation(x, indices, topk_weights, valid_rows_mask, reduce_group_size)
489488

490489
hidden_size = x.shape[-1]
491490
input_size = indices.size
@@ -533,6 +532,7 @@ def ragged_gather_reduce(
533532
dst_indices,
534533
topk_weights,
535534
num_src_rows_per_row_partition,
535+
mask,
536536
) = _preprocess(
537537
indices,
538538
topk_weights,
@@ -588,4 +588,10 @@ def ragged_gather_reduce(
588588
},
589589
)(num_src_rows_per_row_partition, x, src_indices, dst_indices, topk_weights)
590590

591-
return out.astype(x.dtype)
591+
# If there is no valid source row in a reduce group, set that group's output
592+
# to zero.
593+
return jnp.where(
594+
mask[:, None],
595+
out.astype(x.dtype),
596+
jnp.zeros_like(out, dtype=x.dtype),
597+
)[: (input_size // reduce_group_size), :hidden_size]

src/maxtext/kernels/ragged/ragged_sort.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,8 @@ def _ring_ragged_unsort_fwd(sorted_tokens_local, group_sizes_local, topk_argsort
303303
valid_rows_mask=valid_rows_mask,
304304
reduce_group_size=topk,
305305
enforce_fallback=enforce_gather_reduce_fallback,
306+
flops_override=gather_reduce_flops_override,
307+
bytes_accessed_override=gather_reduce_bytes_accessed_override,
306308
)
307309
else:
308310
# Shift indices so they map to the packed local buffer [0, local_num_tokens).
@@ -319,6 +321,8 @@ def _ring_ragged_unsort_fwd(sorted_tokens_local, group_sizes_local, topk_argsort
319321
valid_rows_mask=valid_rows_mask,
320322
reduce_group_size=topk,
321323
enforce_fallback=enforce_gather_reduce_fallback,
324+
flops_override=gather_reduce_flops_override,
325+
bytes_accessed_override=gather_reduce_bytes_accessed_override,
322326
)
323327

324328
res = (
@@ -375,6 +379,8 @@ def _ring_ragged_unsort_bwd(res, g_out):
375379
weights=weight_for_sorted,
376380
has_weights=True,
377381
enforce_fallback=enforce_gather_fallback,
382+
flops_override=gather_flops_override,
383+
bytes_accessed_override=gather_bytes_accessed_override,
378384
)
379385
else:
380386
# Slice the inverse permutation to match the packed local buffer.
@@ -392,6 +398,8 @@ def _ring_ragged_unsort_bwd(res, g_out):
392398
weights=sliced_weights,
393399
has_weights=True,
394400
enforce_fallback=enforce_gather_fallback,
401+
flops_override=gather_flops_override,
402+
bytes_accessed_override=gather_bytes_accessed_override,
395403
)
396404
return grad_sorted_tokens, None, None, None
397405

src/maxtext/layers/moe.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -918,7 +918,6 @@ def permute(
918918
repeats=group_size,
919919
total_repeat_length=math.prod(selected_experts.shape),
920920
)
921-
922921
return (
923922
sorted_inputs,
924923
sorted_selected_experts,

0 commit comments

Comments
 (0)