Skip to content

Commit dd9c267

Browse files
Merge pull request #4187 from AI-Hypercomputer:chengnuojin-ragged-guard
PiperOrigin-RevId: 936272634
2 parents bf22ce9 + c32e756 commit dd9c267

7 files changed

Lines changed: 110 additions & 38 deletions

File tree

src/maxtext/configs/base.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,10 @@ use_ragged_sort: false # whether to use the Pallas ragged-sort kernels in the Mo
222222
# without `use_ring_of_experts` (with EP > 1). When `use_ring_of_experts=True` the kernels run
223223
# inside `permute`/`unpermute`; otherwise they run inside `local_permute`/local-unpermute.
224224
use_gather_mosaic_kernel: false # whether to use a custom mosaic kernel for token gather ops
225+
ragged_gather_fallback: false # when true, unconditionally use the JAX reference implementation instead of the
226+
# ragged gather SparseCore kernel. When false (default), use the SparseCore kernel.
227+
ragged_gather_reduce_fallback: false # when true, unconditionally use the JAX reference implementation instead of the
228+
# ragged gather reduce SparseCore kernel. When false (default), use the SparseCore kernel.
225229
# tunable tiling dimensions used for mlp gmm
226230
# megablox/jax ragged dot - supports forward pass only (6 configs: `wi_tile_fwd...` and `wo_tile_fwd_...`)
227231
# tokamax ragged dot - supports all 18 configs

src/maxtext/configs/types.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -750,6 +750,16 @@ class MoEGeneral(BaseModel):
750750
False,
751751
description="Whether to use a custom mosaic kernel for token gather ops.",
752752
)
753+
ragged_gather_fallback: bool = Field(
754+
False,
755+
description="When true, unconditionally use the JAX reference implementation instead of the ragged gather "
756+
"SparseCore kernel. When false (default), use the SparseCore kernel.",
757+
)
758+
ragged_gather_reduce_fallback: bool = Field(
759+
False,
760+
description="When true, unconditionally use the JAX reference implementation instead of the ragged gather "
761+
"reduce SparseCore kernel. When false (default), use the SparseCore kernel.",
762+
)
753763
use_random_routing: bool = Field(False, description="Whether to use random routing for debugging.")
754764
interleave_moe_layer_step: int = Field(1, description="Frequency of MoE layers, e.g., 2 means every 2nd layer is MoE.")
755765
moe_fsdp_use_two_stage_all_gather: bool = Field(

src/maxtext/kernels/ragged/ragged_gather.py

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,30 @@
1616
# Source from https://github.com/vllm-project/tpu-inference/blob/main/tpu_inference/kernels/sparse_core/ragged_gather.py
1717

1818
import functools
19-
2019
import jax
2120
import jax.numpy as jnp
2221
from jax.experimental import pallas as pl
2322
from jax.experimental.pallas import tpu as pltpu
2423
from jax.experimental.pallas import tpu_sc as plsc
2524
from packaging.version import Version
2625

27-
2826
# JAX <= 0.10.0 used `out_shape`/`scratch_shapes` kwargs for `pl.kernel`; later
2927
# versions renamed them to `out_type`/`scratch_types`.
3028
if Version(jax.__version__) <= Version("0.10.0"):
3129
_OUT_KW = "out_shape"
3230
_SCRATCH_KW = "scratch_shapes"
31+
_COMPILER_PARAMS = {
32+
"use_tc_tiling_on_sc": True,
33+
"disable_bounds_checks": True,
34+
}
3335
else:
3436
_OUT_KW = "out_type"
3537
_SCRATCH_KW = "scratch_types"
38+
_COMPILER_PARAMS = {
39+
"use_tc_tiling_on_sc": True,
40+
"disable_bounds_checks": True,
41+
"needs_layout_passes": False,
42+
}
3643

3744

3845
def main_kernel(
@@ -264,6 +271,19 @@ def dma_write_loop(col_vmem_start):
264271
inner_kernel()
265272

266273

274+
def _fallback_implementation(
275+
x: jax.Array,
276+
indices: jax.Array,
277+
weights: jax.Array | None = None,
278+
has_weights: bool = False,
279+
) -> jax.Array:
280+
"""Fallback to (non-ragged) JAX implementation for ragged gather."""
281+
out = x[indices]
282+
if has_weights:
283+
out = out * weights[:, None]
284+
return out
285+
286+
267287
def calculate_col_size(hidden_size: int) -> int:
268288
"""Calculate col size for ragged gather kernel."""
269289
tpu_info = pltpu.get_tpu_info()
@@ -288,14 +308,15 @@ def calculate_col_size(hidden_size: int) -> int:
288308
return pl.cdiv(hidden_size, (num_cols * num_lanes)) * num_lanes
289309

290310

291-
@functools.partial(jax.jit, static_argnames=("has_weights",))
311+
@functools.partial(jax.jit, static_argnames=("has_weights", "enforce_fallback"))
292312
def ragged_gather(
293313
x: jax.Array,
294314
indices: jax.Array,
295315
start: jax.Array,
296316
end: jax.Array,
297317
weights: jax.Array | None = None,
298318
has_weights: bool = False,
319+
enforce_fallback: bool = False,
299320
) -> jax.Array:
300321
"""Perform gather on indices within dynamic array start and end.
301322
@@ -309,6 +330,9 @@ def ragged_gather(
309330
kernel, avoiding an extra HBM read-write pass.
310331
has_weights: Static bool flag indicating whether ``weights`` should be
311332
applied. Must be ``True`` when ``weights`` is not ``None``.
333+
enforce_fallback: Static bool flag. When ``True``, unconditionally use the
334+
JAX reference implementation instead of the SparseCore kernel.
335+
When ``False`` (default), use the SparseCore kernel and raise any error.
312336
313337
Returns:
314338
Gathered output of shape ``(indices_size, hidden_size)``.
@@ -331,12 +355,9 @@ def ragged_gather(
331355
dtype = x.dtype
332356

333357
sc_info = pltpu.get_tpu_info().sparse_core
334-
if sc_info is None:
335-
# Sparse core is not available. Fallback to regular gather.
336-
out = x[indices]
337-
if has_weights:
338-
out = out * weights[:, None]
339-
return out
358+
if sc_info is None or enforce_fallback:
359+
# Sparse core is not available or fallback is enforced. Use JAX reference.
360+
return _fallback_implementation(x, indices, weights, has_weights)
340361

341362
hidden_size = x.shape[-1]
342363
out_size = indices.size
@@ -371,9 +392,8 @@ def ragged_gather(
371392
subcore_axis_name=vector_mesh.subcore_axis_name,
372393
has_weights=has_weights,
373394
),
374-
compiler_params=pltpu.CompilerParams(
375-
use_tc_tiling_on_sc=True,
376-
disable_bounds_checks=True,
395+
compiler_params=pltpu.CompilerParams( # pytype: disable=wrong-keyword-args
396+
**_COMPILER_PARAMS,
377397
),
378398
mesh=vector_mesh,
379399
name="sc_ragged_gather",

src/maxtext/kernels/ragged/ragged_gather_reduce.py

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import jax.numpy as jnp
2525
from packaging.version import Version
2626

27+
2728
# JAX <= 0.10.0 used `out_shape`/`scratch_shapes` kwargs for `pl.kernel`; later
2829
# versions renamed them to `out_type`/`scratch_types`.
2930
if Version(jax.__version__) <= Version("0.10.0"):
@@ -329,7 +330,7 @@ def _preprocess(
329330
reduce_group_size: int,
330331
num_row_partitions: int,
331332
num_simd_lanes: int,
332-
) -> tuple[jax.Array, jax.Array, jax.Array, jax.Array, jax.Array]:
333+
) -> tuple[jax.Array, jax.Array, jax.Array, jax.Array]:
333334
"""Preprocesses indices for ragged gather reduce."""
334335
assert indices.ndim == 1, "Ragged scatter only supports 1d indices."
335336

@@ -360,26 +361,23 @@ def _preprocess(
360361
num_src_rows_per_row_partition.astype(jnp.int32),
361362
(0, num_simd_lanes - num_row_partitions),
362363
)
363-
# If there is no valid source row in a reduce group, we set the mask to
364-
# False, so that the output for that group is set to zero.
365-
mask = jnp.any(valid_rows_mask.reshape(-1, reduce_group_size), axis=-1)
366364

367365
return (
368366
src_indices,
369367
dst_indices,
370368
topk_weights,
371369
num_src_rows_per_row_partition,
372-
mask,
373370
)
374371

375372

376-
@functools.partial(jax.jit, static_argnames=("reduce_group_size",))
373+
@functools.partial(jax.jit, static_argnames=("reduce_group_size", "enforce_fallback"))
377374
def ragged_gather_reduce(
378375
x: jax.Array,
379376
indices: jax.Array,
380377
topk_weights: jax.Array,
381378
valid_rows_mask: jax.Array,
382379
reduce_group_size: int,
380+
enforce_fallback: bool = False,
383381
) -> jax.Array:
384382
"""Gathers `x` according to `indices`, applies weights and masks, and reduces.
385383
@@ -402,6 +400,9 @@ def ragged_gather_reduce(
402400
valid, with shape `(input_size,)`.
403401
reduce_group_size: An integer representing the number of consecutive rows to
404402
reduce (sum) together.
403+
enforce_fallback: Static bool flag. When ``True``, unconditionally use the
404+
JAX reference implementation instead of the SparseCore kernel.
405+
When ``False`` (default), use the SparseCore kernel and raise any error.
405406
406407
Returns:
407408
A 2D JAX array of reduced data with shape
@@ -414,7 +415,8 @@ def ragged_gather_reduce(
414415
assert valid_rows_mask.ndim == 1, "ragged_gather_reduce only supports 1d valid_rows_mask."
415416

416417
sc_info = pltpu.get_tpu_info().sparse_core
417-
if sc_info is None:
418+
if sc_info is None or enforce_fallback:
419+
# Sparse core is not available or fallback is enforced. Use JAX reference.
418420
return _fallback_implementation(x, indices, topk_weights, valid_rows_mask, reduce_group_size)
419421

420422
# Heuristic threshold on whether to fallback for small inputs.
@@ -431,19 +433,20 @@ def ragged_gather_reduce(
431433
num_simd_lanes = sc_info.num_lanes
432434
num_cores = sc_info.num_cores * sc_info.num_subcores
433435

434-
# This kernel partitions the output's columns into `num_column_partitions` and
435-
# partition the output's rows into `num_row_partitions` and run each
436+
# This kernel partitions the output's columns into `num_column_partitions`
437+
# and partition the output's rows into `num_row_partitions` and run each
436438
# {row_partition} x {column_partition} combination on a separate SC subcore
437-
# for parallelism. With such work partitioning, we guarantee that there won't
438-
# be write collision (from different subcores) to the any output row X column.
439+
# for parallelism. With such work partitioning, we guarantee that there
440+
# won't be write collision (from different subcores) to any output row X
441+
# column.
439442
#
440443
# Each column partition should be multiple of 128 (number of lanes) due to
441444
# DMA requirements. Unless requiring padding on the column dimension, larger
442445
# column partitions (thus smaller row partitions given fixed num_cores) is
443446
# more preferable because large row partition may lead to imbalanced load
444447
# (valid_rows_mask may have more rows in some partitions than others).
445-
# Most LLM's hidden size is multiple of 1024, `num_column_partitions=8` should
446-
# work well in practice without requiring padding on the column size.
448+
# Most LLM's hidden size is multiple of 1024, `num_column_partitions=8`
449+
# should work well in practice without requiring padding on the column size.
447450
num_column_partitions = 8
448451
assert num_cores % num_column_partitions == 0
449452
num_rows_partitions = num_cores // num_column_partitions
@@ -471,7 +474,6 @@ def ragged_gather_reduce(
471474
dst_indices,
472475
topk_weights,
473476
num_src_rows_per_row_partition,
474-
mask,
475477
) = _preprocess(
476478
indices,
477479
topk_weights,
@@ -487,8 +489,8 @@ def ragged_gather_reduce(
487489
core_axis_name="core",
488490
subcore_axis_name="subcore",
489491
)
490-
# Each output row from `main_kernel` will be of type float32, and then casted
491-
# to the input dtype when doing the filter operation.
492+
# Each output row from `main_kernel` will be of type float32, and then
493+
# casted to the input dtype when doing the filter operation.
492494
out = pl.kernel( # pytype: disable=wrong-keyword-args
493495
functools.partial(
494496
main_kernel,
@@ -519,10 +521,4 @@ def ragged_gather_reduce(
519521
},
520522
)(num_src_rows_per_row_partition, x, src_indices, dst_indices, topk_weights)
521523

522-
# If there is no valid source row in a reduce group, set that group's output
523-
# to zero.
524-
return jnp.where(
525-
mask[:, None],
526-
out.astype(x.dtype),
527-
jnp.zeros_like(out, dtype=x.dtype),
528-
)[: (input_size // reduce_group_size), :hidden_size]
524+
return out.astype(x.dtype)

src/maxtext/kernels/ragged/ragged_sort.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ def ring_ragged_sort(
2828
ep_name,
2929
ep_size,
3030
buffer_size=None,
31+
enforce_gather_fallback=False,
32+
enforce_gather_reduce_fallback=False,
3133
):
3234
"""Ragged-gather variant for AG-RS Expert Parallelism token routing.
3335
@@ -102,6 +104,7 @@ def _ring_ragged_sort_fwd(hidden_states_local, topk_indices_local):
102104
token_indices_sorted,
103105
shard_output_start[None],
104106
shard_output_end[None],
107+
enforce_fallback=enforce_gather_fallback,
105108
)
106109
else:
107110
local_buffer_size = buffer_size
@@ -122,6 +125,7 @@ def _ring_ragged_sort_fwd(hidden_states_local, topk_indices_local):
122125
sliced_indices,
123126
jnp.int32(0)[None],
124127
gather_end[None],
128+
enforce_fallback=enforce_gather_fallback,
125129
)
126130

127131
out = (x, group_sizes_local, topk_argsort_revert_indices)
@@ -173,6 +177,7 @@ def _ring_ragged_sort_bwd(res, g_out):
173177
topk_weights=jnp.ones((n,), dtype=jnp.float32),
174178
valid_rows_mask=valid_rows_mask,
175179
reduce_group_size=topk,
180+
enforce_fallback=enforce_gather_reduce_fallback,
176181
)
177182
else:
178183
# Buffering: g_x has size `local_buffer_size` (packed).
@@ -195,6 +200,7 @@ def _ring_ragged_sort_bwd(res, g_out):
195200
topk_weights=jnp.ones((n,), dtype=jnp.float32),
196201
valid_rows_mask=valid_rows_mask,
197202
reduce_group_size=topk,
203+
enforce_fallback=enforce_gather_reduce_fallback,
198204
)
199205
return grad_hidden_states, None
200206

@@ -211,6 +217,8 @@ def ring_ragged_unsort(
211217
local_num_experts,
212218
ep_name,
213219
topk_weights,
220+
enforce_gather_fallback=False,
221+
enforce_gather_reduce_fallback=False,
214222
):
215223
"""Dual of :func:`ring_ragged_sort`.
216224
@@ -282,6 +290,7 @@ def _ring_ragged_unsort_fwd(sorted_tokens_local, group_sizes_local, topk_argsort
282290
topk_weights=topk_weights_flat,
283291
valid_rows_mask=valid_rows_mask,
284292
reduce_group_size=topk,
293+
enforce_fallback=enforce_gather_reduce_fallback,
285294
)
286295
else:
287296
# Shift indices so they map to the packed local buffer [0, local_num_tokens).
@@ -297,6 +306,7 @@ def _ring_ragged_unsort_fwd(sorted_tokens_local, group_sizes_local, topk_argsort
297306
topk_weights=topk_weights_flat,
298307
valid_rows_mask=valid_rows_mask,
299308
reduce_group_size=topk,
309+
enforce_fallback=enforce_gather_reduce_fallback,
300310
)
301311

302312
res = (
@@ -352,6 +362,7 @@ def _ring_ragged_unsort_bwd(res, g_out):
352362
shard_output_end[None],
353363
weights=weight_for_sorted,
354364
has_weights=True,
365+
enforce_fallback=enforce_gather_fallback,
355366
)
356367
else:
357368
# Slice the inverse permutation to match the packed local buffer.
@@ -368,6 +379,7 @@ def _ring_ragged_unsort_bwd(res, g_out):
368379
gather_end[None],
369380
weights=sliced_weights,
370381
has_weights=True,
382+
enforce_fallback=enforce_gather_fallback,
371383
)
372384
return grad_sorted_tokens, None, None, None
373385

@@ -379,7 +391,7 @@ def _ring_ragged_unsort_bwd(res, g_out):
379391
return _ring_ragged_unsort(sorted_tokens_local, group_sizes_local, topk_argsort_revert_indices, topk_weights_flat)
380392

381393

382-
def a2a_ragged_sort(inputs, sort_indices, valid_end):
394+
def a2a_ragged_sort(inputs, sort_indices, valid_end, enforce_gather_fallback=False, enforce_gather_reduce_fallback=False):
383395
"""Ragged-gather variant for ``local_permute``.
384396
385397
Unlike :func:`ring_ragged_sort`, the rows valid for this shard live in
@@ -442,6 +454,7 @@ def _a2a_ragged_sort_bwd(res, g_out):
442454
topk_weights=jnp.ones((n,), dtype=jnp.float32),
443455
valid_rows_mask=valid_rows_mask[idx_inv],
444456
reduce_group_size=1,
457+
enforce_fallback=enforce_gather_reduce_fallback,
445458
)
446459
# custom_vjp must return one gradient per primal arg; valid_end is integer
447460
# and non-differentiable, so we return None for it.
@@ -451,7 +464,9 @@ def _a2a_ragged_sort_bwd(res, g_out):
451464
return _a2a_ragged_sort(inputs, sort_indices, valid_end)
452465

453466

454-
def a2a_ragged_unsort(sorted_tokens, revert_indices, valid_end):
467+
def a2a_ragged_unsort(
468+
sorted_tokens, revert_indices, valid_end, enforce_gather_fallback=False, enforce_gather_reduce_fallback=False
469+
):
455470
"""Dual of :func:`a2a_ragged_sort`.
456471
457472
Forward:
@@ -492,6 +507,7 @@ def _a2a_ragged_unsort_fwd(sorted_tokens, revert_indices, valid_end):
492507
topk_weights=jnp.ones((n,), dtype=jnp.float32),
493508
valid_rows_mask=valid_rows_mask,
494509
reduce_group_size=1,
510+
enforce_fallback=enforce_gather_reduce_fallback,
495511
)
496512
res = (revert_indices, end, sorted_tokens.shape, start)
497513
return out, res

src/maxtext/layers/moe.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -862,6 +862,8 @@ def permute(
862862
self._expert_parallelism_name,
863863
num_expert_parallelism,
864864
buffer_size=buffer_size,
865+
enforce_gather_fallback=self.config.ragged_gather_fallback,
866+
enforce_gather_reduce_fallback=self.config.ragged_gather_reduce_fallback,
865867
)
866868
else:
867869
flatten_selected_experts = jnp.ravel(selected_experts)
@@ -941,6 +943,8 @@ def unpermute(
941943
local_num_experts,
942944
self._expert_parallelism_name,
943945
topk_weights=flat_weights,
946+
enforce_gather_fallback=self.config.ragged_gather_fallback,
947+
enforce_gather_reduce_fallback=self.config.ragged_gather_reduce_fallback,
944948
)
945949
else:
946950
unsort_intermediate = _sort_activations(

0 commit comments

Comments
 (0)