[feat] mega stage2 of gfx1250 - #4785
Conversation
…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.
…r (gfx1250 a8w4)" This reverts commit 90b0d59.
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.
mega_moe: add a mori HIP dispatch backend
🏷️ CI GuideRuns automatically on every PR:
Extended tests (opt-in via labels):
|
There was a problem hiding this comment.
Remaining comments which cannot be posted as a review comment to avoid GitHub Rate Limit
ruff
Import block is un-sorted or un-formatted
aiter/op_tests/multigpu_tests/test_mega_moe.py
Lines 32 to 46 in cb929e1
Do not catch blind exception: Exception
Unpacked variable E is never used
Do not catch blind exception: Exception
| 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), | ||
| ) |
There was a problem hiding this comment.
Unnecessary dict() call (rewrite as a literal)
| 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), | |
| } |
| dict( | ||
| stage2_scatter=stage2_scatter, | ||
| ep_destination_stride=(int(stage2_scatter.max_tokens_per_rank) * int(topk)), | ||
| ep_row_map=ep_rowmap, | ||
| ) |
There was a problem hiding this comment.
Unnecessary dict() call (rewrite as a literal)
| 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, | |
| } |
| 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, |
There was a problem hiding this comment.
Import block is un-sorted or un-formatted
| 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), |
| 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, | ||
| ) |
There was a problem hiding this comment.
Use a single if statement instead of nested if statements
| 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, |
| from aiter.ops.flydsl.kernels import buffer_ops | ||
| from aiter.ops.flydsl.kernels.tensor_shim import ( |
There was a problem hiding this comment.
| tis_rsrc = ptr_rsrc(tis) | ||
| ep_rsrc = ptr_rsrc(ep_rowmap) | ||
|
|
||
| neg1 = arith.constant(-1, type=i32) |
| topk: fx.Int32, | ||
| max_tok: fx.Int32, | ||
| slot_stride: fx.Int32, | ||
| stream: fx.Stream = fx.Stream(None), |
| topk: fx.Int32, | ||
| max_tok: fx.Int32, | ||
| slot_stride: fx.Int32, | ||
| stream: fx.Stream = fx.Stream(None), |
There was a problem hiding this comment.
Import block is un-sorted or un-formatted
aiter/aiter/ops/flydsl/kernels/moe_contiguous_psum.py
Lines 15 to 33 in cb929e1
There was a problem hiding this comment.
Import block is un-sorted or un-formatted
aiter/aiter/ops/flydsl/kernels/mxfp4_preshuffle_gfx1250_tdm.py
Lines 6 to 44 in cb929e1
Motivation
Technical Details
Test Plan
Test Result
Submission Checklist