Skip to content

[feat] mega stage2 of gfx1250 - #4785

Draft
yanboshao wants to merge 18 commits into
mainfrom
yanbo/mega_stage2_gfx1250
Draft

[feat] mega stage2 of gfx1250#4785
yanboshao wants to merge 18 commits into
mainfrom
yanbo/mega_stage2_gfx1250

Conversation

@yanboshao

Copy link
Copy Markdown
Contributor

Motivation

Technical Details

Test Plan

Test Result

Submission Checklist

yanboshao and others added 18 commits August 13, 2026 17:12
…to updated main

Reapply the gfx1250 fused-MoE ep_scatter feature (combine_mode="scatter_fused":
gemm2's TDM epilogue P2P-writes each route-weighted output row into peers'
comb_inp, so combine just sums -- no gather-reduce) onto the updated origin/main,
which meanwhile landed #4482 (real SiTUv2 + >512-expert chunked psum scan),
SiTUv2 path). Squashed to a single commit.

Per-file resolution:
- dispatch_combine_v2/*: vendored cco-LSA v2 intranode dispatch/combine op-layer.
- mxfp4_preshuffle_gfx1250_tdm.py: on upstream's make_lds_copy_ops LDS API +
  SiTUv2 epilogue, add the TDM gather-store ep epilogue (tdm_scatter with
  in-kernel global_view/lds_view). tdm_scatter is vendored locally
  (tdm_gather_shim.py, on the stock FlyDSL wheel's low-level TDM intrinsics)
  so this branch needs no FlyDSL-side patch; route weight hoisted per wm row (_wf_rows).
- moe_contiguous_psum.py: adopt upstream's chunked scan (E>512 correct) for the
  non-EP remap; keep the multi-block grid-stride remap + ep_rowmap kernels for EP.
- grouped_moe_gfx1250.py: keep upstream SiTUv2 (stage1_act=3, situ_beta); add the
  ep_scatter dispatch wiring (ep_rowmap build, _ep_gemm2_kwargs, ep_scatter return).
- batched_gemm_mxfp4.py / fused_moe.py: thread both situ_* and ep_* params.
- tuned_grouped_fmoe.csv: tuning points (99 rows).

Dropped only the full-subtile PF prefetch pipeline (da8d794): loaders and
lds_addr_keepalive stay at upstream. lalala-sh's ds-read hoist is kept.

gfx1250-only; not run in this environment. Compile-verify the LDS-API migration +
ep/SiTUv2 epilogue and re-run test_mega_moe --combine on hardware.

Co-authored-by: lalala-sh <Jiaxing.Wen@amd.com>
Co-authored-by: zhimding <zhimding@amd.com>
Co-authored-by: lalala-sh <Jiaxing.Wen@amd.com>
…50 a8w4)

End-to-end fixed-slot push path for the a8w4 MoE, off by default behind an
explicit config switch. Dispatch lands tokens grouped per local expert (no
consumer-side gather), a finalize kernel builds the tile schedule, GEMM1 reads A
contiguously from the fixed slots, and the GEMM2 TDM epilogue P2P-scatters each
route-weighted row into peers' comb_inp via the dispatch-emitted pg_rowmap;
combine then just sums. ~4% over the pull (gather) path at DeepSeek and balanced
shapes.

Squash of the push-group cherry-picks (dispatch base / GEMM1 A-load /
GEMM2+combine wiring + parallel finalize + tile_k2) plus these refinements:

- Explicit switch instead of env: EpDispatchCombineConfig.push_group (was
  AITER_EP_PUSH_GROUP); test_mega_moe gains a --push_group flag.
- cap_per_expert (was push_group_cap / push_group_safety): 0 => worst-case
  ws*max_tok_per_rank (never drops out of the box); >0 is a caller-pinned,
  tile_m-aligned capacity whose correctness the caller owns (overflow dropped in
  finalize). Auto/safety sizing removed. Padding M-tiles early-exit at the
  `expert < n_experts` guard (no N/K mainloop), so over-provisioning costs empty
  workgroup dispatch, not padded compute.
- SiTUv2 fixed in the fused GEMM1 path: map Situv2 -> stage1_act=3 and forward
  situ_beta / situ_linear_beta (previously silently ran as silu).

Co-authored-by: XingerZhu <xzhu@amd.com>
This reverts commit d3b3fb4.
MegaMoEGfx1250 owns dispatch -> gemm1 -> gemm2-fused-scatter -> fused
combine. This makes the first stage swappable: dispatch_backend="mori"
(or MEGA_DISPATCH=mori) runs mori's HIP/JIT gfx1250 TDM dispatch instead
of this package's FlyDSL one, and nothing else in the pipeline changes.

It works because the two dispatches leave byte-identical state behind:
disp_out rows at slot*hidden, out_idx/out_wts at slot*topk+k, the flat
dest map as dest_pe*max_recv+slot with null = npes*max_recv, and -- the
one field the fused path actually consumes -- recv_to_src_token encoded
src_pe*max_tok_per_rank+src_tok, which the GEMM host pass decodes to
build ep_rowmap. The recv_num/tok_off signal/ack handshake is the same
protocol, and mori's dispatch never touches cross_device_barrier, so the
fused combine's phase counter is undisturbed.

mori's plan layer takes an arena by duck typing (.handle/.offset), so
SymmetricArena is handed over as-is; no extra region and no extra device
memory. Two things do differ and are handled here:

  * geometry comes from mori's own tuning table, not _select_dispatch_config.
    That table asks for 32 warps above 256 tokens, and mori's dispatch
    stages a hidden-dim tile per warp in dynamic LDS -- 32*7168*2 = 458 KB
    against a 320 KB budget. EpCfgIsValid does not check LDS, so it would
    fail at launch rather than when the plan is built.
  * total_recv is zeroed here. This package's dispatch clears it in its own
    Phase 2; mori's only accumulates, and the fused combine never resets it.

The recv slot a token lands in does change -- mori's gfx1250 dispatch
reserves a block's slots with one atomic and hands them out block-local.
Nothing indexes by slot order, but a test diffing arena contents
slot-by-slot against the FlyDSL dispatch will see it.

Measured on 4x gfx1250, EP4 hidden 7168 topk 6, scatter_fused, against
test_mega_moe.py's fp32 reference:

  2 layers, 256 tok/rank    945.9 -> 925.8 us/layer   logits_diff 0.002174 both
  4 layers, 4096 tok/rank  2678.0 -> 2622.2 us/layer  logits_diff 0.004344 both

Identical diffs: dispatch only moves data, so the deviation from the
reference is unchanged. Profile confirms the geometry actually launched is
mori's -- 64x8 at 256 tokens, 64x16 at 4096.

Needs a mori built with JIT v2 (PR #548 or later) and its libmori_ops_v2.so.
@github-actions

Copy link
Copy Markdown
Contributor

🏷️ CI Guide

Runs automatically on every PR:

  • ✅ Pre-checks (submodule verification, code formatting)
  • ✅ Aiter op tests (gfx942 + gfx950)
  • ✅ Triton tests on MI35X (only when aiter/ops/triton/** or related paths are changed)

Extended tests (opt-in via labels):

Label Tests
ci:gfx1250-ffm-triton Run the five-shard gfx1250 FFM Triton test suite
ci:triton-300x Run an additional Triton test job on MI300X in PRs; main branch always runs both MI35X and MI300X
ci:sglang SGLang integration tests: DeepSeek-R1-MXFP4 accuracy, Qwen 3.5 accuracy
ci:atom ATOM benchmark: DeepSeek-R1-0528, GPT-OSS-120B
ci:atom_full ATOM accuracy suite for PR and main models from ATOM models_accuracy.json
ci:vllm vLLM benchmark: GPT-OSS-120B, DeepSeek-R1-0528, Kimi-K2.5
ci:all All standard extended tests (excludes ci:atom_full)

Only add ci:atom_full for FlyDSL or Triton upgrades.
Add labels via the sidebar or gh pr edit 4785 --add-label <label>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remaining comments which cannot be posted as a review comment to avoid GitHub Rate Limit

ruff

⚠️ [ruff] <I001> reported by reviewdog 🐶
Import block is un-sorted or un-formatted

import argparse
import os
import torch
import torch.distributed as dist
import torch.profiler as tprof
import aiter
from aiter import dtypes
from aiter import ActivationType, QuantType, get_gfx
from aiter.fused_moe import fused_moe
from aiter.ops.shuffle import shuffle_weight, moe_shuffle_scale
from aiter.ops.flydsl.moe_common import GateMode
from aiter.utility import fp4_utils
from aiter import get_hip_quant, get_torch_quant, pertoken_quant


⚠️ [ruff] <BLE001> reported by reviewdog 🐶
Do not catch blind exception: Exception

except Exception: # pragma: no cover


⚠️ [ruff] <RUF059> reported by reviewdog 🐶
Unpacked variable E is never used

E, two_inter = t.shape[:2]


⚠️ [ruff] <BLE001> reported by reviewdog 🐶
Do not catch blind exception: Exception

except Exception as _e:

Comment on lines +495 to +503
ep_scatter_params = dict(
gather_w=_gather_w_buf,
tis=stage2_scatter.source_token_map,
ep_rowmap=ep_rowmap,
cap_rows=_cap_rows,
topk=int(topk),
max_tok=int(stage2_scatter.max_tokens_per_rank),
slot_stride=int(stage2_scatter.max_tokens_per_rank) * int(topk),
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [ruff] <C408> reported by reviewdog 🐶
Unnecessary dict() call (rewrite as a literal)

Suggested change
ep_scatter_params = dict(
gather_w=_gather_w_buf,
tis=stage2_scatter.source_token_map,
ep_rowmap=ep_rowmap,
cap_rows=_cap_rows,
topk=int(topk),
max_tok=int(stage2_scatter.max_tokens_per_rank),
slot_stride=int(stage2_scatter.max_tokens_per_rank) * int(topk),
)
ep_scatter_params = {
"gather_w": _gather_w_buf,
"tis": stage2_scatter.source_token_map,
"ep_rowmap": ep_rowmap,
"cap_rows": _cap_rows,
"topk": int(topk),
"max_tok": int(stage2_scatter.max_tokens_per_rank),
"slot_stride": int(stage2_scatter.max_tokens_per_rank) * int(topk),
}

Comment on lines +517 to +521
dict(
stage2_scatter=stage2_scatter,
ep_destination_stride=(int(stage2_scatter.max_tokens_per_rank) * int(topk)),
ep_row_map=ep_rowmap,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [ruff] <C408> reported by reviewdog 🐶
Unnecessary dict() call (rewrite as a literal)

Suggested change
dict(
stage2_scatter=stage2_scatter,
ep_destination_stride=(int(stage2_scatter.max_tokens_per_rank) * int(topk)),
ep_row_map=ep_rowmap,
)
{
"stage2_scatter": stage2_scatter,
"ep_destination_stride": (int(stage2_scatter.max_tokens_per_rank) * int(topk)),
"ep_row_map": ep_rowmap,
}

Comment on lines +24 to +46
import os

import flydsl.compiler as flyc
import flydsl.expr as fx
import mori.cco.device.flydsl as cco
from aiter.ops.flydsl.kernels import communication_ops_utils as comm_ops
from aiter.ops.flydsl.kernels import vector
from aiter.ops.flydsl.kernels.buffer_ops import (
buffer_load,
buffer_store,
create_buffer_resource_from_addr,
)
from flydsl.expr import arith, const_expr, range_constexpr
from flydsl.expr.typing import Int32, Int64, T

from . import primitives as P
from .config import (
_LANE_MASK as LANE_MASK,
_LOG2_WAVE_SIZE as LOG2_WAVE,
_WAVE_SIZE as WAVE,
)

# NOTE: the cross-device xdb barrier is kept inlined per kernel (dispatch Phase 2,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [ruff] <I001> reported by reviewdog 🐶
Import block is un-sorted or un-formatted

Suggested change
import os
import flydsl.compiler as flyc
import flydsl.expr as fx
import mori.cco.device.flydsl as cco
from aiter.ops.flydsl.kernels import communication_ops_utils as comm_ops
from aiter.ops.flydsl.kernels import vector
from aiter.ops.flydsl.kernels.buffer_ops import (
buffer_load,
buffer_store,
create_buffer_resource_from_addr,
)
from flydsl.expr import arith, const_expr, range_constexpr
from flydsl.expr.typing import Int32, Int64, T
from . import primitives as P
from .config import (
_LANE_MASK as LANE_MASK,
_LOG2_WAVE_SIZE as LOG2_WAVE,
_WAVE_SIZE as WAVE,
)
# NOTE: the cross-device xdb barrier is kept inlined per kernel (dispatch Phase 2,
import os
import flydsl.compiler as flyc
import flydsl.expr as fx
import mori.cco.device.flydsl as cco
from flydsl.expr import arith, const_expr, range_constexpr
from flydsl.expr.typing import Int32, Int64, T
from aiter.ops.flydsl.kernels import communication_ops_utils as comm_ops
from aiter.ops.flydsl.kernels import vector
from aiter.ops.flydsl.kernels.buffer_ops import (
buffer_load,
buffer_store,
create_buffer_resource_from_addr,
)
from . import primitives as P
from .config import (
_LANE_MASK as LANE_MASK,
)
from .config import (
_LOG2_WAVE_SIZE as LOG2_WAVE,
)
from .config import (
_WAVE_SIZE as WAVE,
)
# NOTE: the cross-device xdb barrier is kept inlined per kernel (dispatch Phase 2,

arena: Int64,
addr_xdb_flag: Int64,
my_lsa_rank: Int32,
stream=fx.Stream(None),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [ruff] <B008> reported by reviewdog 🐶
Do not perform function call fx.Stream in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

Comment on lines +246 to +257
if const_expr(_XDB_FLAG_SLOTS > block_num):
if bid == 0:
_tail = _XDB_FLAG_SLOTS - block_num
_nthr = warp_num_per_block * WAVE
for r in range_constexpr((_tail + _nthr - 1) // _nthr):
idx = tid + r * _nthr
if idx < _tail:
buffer_store(
phase + arith.constant(1, type=T.i64),
rsrc_xdb_flag,
block_num + idx,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [ruff] <SIM102> reported by reviewdog 🐶
Use a single if statement instead of nested if statements

Suggested change
if const_expr(_XDB_FLAG_SLOTS > block_num):
if bid == 0:
_tail = _XDB_FLAG_SLOTS - block_num
_nthr = warp_num_per_block * WAVE
for r in range_constexpr((_tail + _nthr - 1) // _nthr):
idx = tid + r * _nthr
if idx < _tail:
buffer_store(
phase + arith.constant(1, type=T.i64),
rsrc_xdb_flag,
block_num + idx,
)
if const_expr(_XDB_FLAG_SLOTS > block_num) and bid == 0:
_tail = _XDB_FLAG_SLOTS - block_num
_nthr = warp_num_per_block * WAVE
for r in range_constexpr((_tail + _nthr - 1) // _nthr):
idx = tid + r * _nthr
if idx < _tail:
buffer_store(
phase + arith.constant(1, type=T.i64),
rsrc_xdb_flag,
block_num + idx,
)

global_byte_offset=None,
pad_interval: int = 0,
pad_amount: int = 0,
workgroup_mask: Union[int, "ir.Value"] = 0,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [ruff] <UP037> reported by reviewdog 🐶
Remove quotes from type annotation

Suggested change
workgroup_mask: Union[int, "ir.Value"] = 0,
workgroup_mask: Union[int, ir.Value] = 0,

Comment on lines 27 to 28
from aiter.ops.flydsl.kernels import buffer_ops
from aiter.ops.flydsl.kernels.tensor_shim import (

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [ruff] <F811> reported by reviewdog 🐶
Redefinition of unused buffer_ops from line 20: buffer_ops redefined here

Suggested change
from aiter.ops.flydsl.kernels import buffer_ops
from aiter.ops.flydsl.kernels.tensor_shim import (
from aiter.ops.flydsl.kernels.tensor_shim import (

tis_rsrc = ptr_rsrc(tis)
ep_rsrc = ptr_rsrc(ep_rowmap)

neg1 = arith.constant(-1, type=i32)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [ruff] <F841> reported by reviewdog 🐶
Local variable neg1 is assigned to but never used

Suggested change
neg1 = arith.constant(-1, type=i32)
arith.constant(-1, type=i32)

topk: fx.Int32,
max_tok: fx.Int32,
slot_stride: fx.Int32,
stream: fx.Stream = fx.Stream(None),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [ruff] <B008> reported by reviewdog 🐶
Do not perform function call fx.Stream in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

topk: fx.Int32,
max_tok: fx.Int32,
slot_stride: fx.Int32,
stream: fx.Stream = fx.Stream(None),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [ruff] <B008> reported by reviewdog 🐶
Do not perform function call fx.Stream in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [ruff] <I001> reported by reviewdog 🐶
Import block is un-sorted or un-formatted

import flydsl.compiler as flyc
import flydsl.expr as fx
from flydsl._mlir import ir
from flydsl._mlir.dialects import llvm, scf
from flydsl.expr import arith, const_expr, gpu, ptrtoint, range_constexpr
from aiter.ops.flydsl.kernels import buffer_ops
from flydsl.expr.typing import Int32, T
from flydsl.expr.arith import ArithValue, CmpIPredicate, CmpFPredicate, _to_raw as _raw
from flydsl.compiler.kernel_function import CompilationContext
from flydsl.runtime.device import get_rocm_arch
from flydsl.utils.smem_allocator import SmemAllocator, SmemPtr
from aiter.ops.flydsl.kernels import buffer_ops
from aiter.ops.flydsl.kernels.tensor_shim import (
STensor,
AITER_FLYDSL_KERNARG_PRELOAD,
AITER_FLYDSL_KERNARG_PRELOAD_COUNT,
ptr_rsrc,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ [ruff] <I001> reported by reviewdog 🐶
Import block is un-sorted or un-formatted

import math
from collections import namedtuple
import flydsl.compiler as flyc
import flydsl.expr as fx
from flydsl.expr import (
arith,
const_expr,
range_constexpr,
rocdl,
tdm_ops,
)
from aiter.ops.flydsl.kernels import vector
from flydsl.expr.typing import Constexpr, T
from flydsl.expr.typing import Vector as Vec
from aiter.utility.mx_types import MxDtypeInt as MxDtype
from .gemm_common_gfx1250 import (
batched_silu_swiglu,
batched_situv2,
fused_silu_swiglu_elem,
fused_situv2_elem,
make_lds_copy_ops,
pipeline_fence,
situv2_consts,
workgroup_barrier,
)
from .quant_utils import (
emit_amax_e8m0_native_scale,
emit_cvt_scalef32_pk8_fp8_f32,
)
# tdm_scatter is vendored under MegaMoE (self-contained on the stock FlyDSL
# wheel's low-level TDM intrinsics) so this kernel needs no FlyDSL-side patch.
# Once the gather/scatter wrappers land upstream, import them from
# flydsl.expr.rocdl.tdm_ops instead and delete the local shim.
from .mega_moe_gfx1250.tdm_gather_shim import make_tensor_gather_descriptor, tensor_store_gather
from .tensor_shim import AITER_FLYDSL_MOE_EXPERT_SCHEDULING_MODE

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants