Skip to content

Commit cc36e67

Browse files
committed
feat(flydsl): add fused heterogeneous MoE
Add a dedicated FHMoE dispatch, custom op, FlyDSL runtime, GPU kernel, AOT path, and correctness suite for MXFP4 routed experts with a native FP8 shared expert. Keep ordinary MoE schemas and compiler APIs unchanged behind narrow compatibility bridges. Start every FHMoE runtime and AOT job at XCD0 so this enablement has one validated scheduling contract. FHMoE-specific XCD performance tuning can follow as a separate stacked change. Validation covers exact DSV4 and small-shape sweeps, precision oracles, graph replay, compatibility, invalid contracts, XCD0 runtime/AOT normalization, Ruff, py_compile, and diff checks. AI assistance was used to implement, test, review, and organize this change. Co-authored-by: OpenAI Codex <codex@openai.com> Signed-off-by: Fangzhou Ai <fangzhouai@gmail.com>
1 parent 1d5dc26 commit cc36e67

8 files changed

Lines changed: 7489 additions & 56 deletions

File tree

aiter/aot/flydsl/fhmoe.py

Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
#!/usr/bin/env python3
2+
3+
# SPDX-License-Identifier: MIT
4+
# Copyright (C) 2024-2026, Advanced Micro Devices, Inc. All rights reserved.
5+
6+
"""AOT adapters for fused heterogeneous MoE (FHMoE)."""
7+
8+
from __future__ import annotations
9+
10+
import csv
11+
import re
12+
from dataclasses import dataclass
13+
from typing import Any
14+
15+
from aiter.aot.flydsl.common import cu_num_to_arch, job_identity
16+
17+
18+
def _without_xcd(kernel_name: str) -> str:
19+
return re.sub(r"_xcd\d+", "", kernel_name)
20+
21+
22+
def _normalized_enum(value: str) -> str:
23+
return value.strip().split(".")[-1].lower()
24+
25+
26+
def _is_dsv4_fhmoe_row(row: dict[str, str]) -> bool:
27+
"""Return whether a tuned row describes the supported DSV4 FHMoE path."""
28+
return (
29+
int(row["model_dim"]) == 7168
30+
and int(row["inter_dim"]) == 512
31+
and int(row["expert"]) == 385
32+
and int(row["topk"]) == 7
33+
and _normalized_enum(row.get("act_type", "")) == "silu"
34+
and row.get("dtype", "") == "torch.bfloat16"
35+
and row.get("use_g1u1", "") == "1"
36+
and "_gui" in row.get("kernelName1", "")
37+
and _normalized_enum(row.get("q_type", "")) == "per_1x32"
38+
and "float8_e4m3fn" in row.get("q_dtype_a", "")
39+
and "float4_e2m1fn_x2" in row.get("q_dtype_w", "")
40+
)
41+
42+
43+
def _row_job_keys(row: dict[str, str]) -> set[tuple[Any, ...]]:
44+
from aiter.ops.flydsl.moe_kernels import get_flydsl_kernel_params
45+
46+
stage1_name = row.get("kernelName1", "").strip()
47+
stage1_params = get_flydsl_kernel_params(stage1_name)
48+
stage1_out_dtype = stage1_params.get("out_dtype") if stage1_params else None
49+
stage1_fuse_quant = (
50+
stage1_out_dtype if stage1_out_dtype in ("fp4", "fp8") else None
51+
)
52+
prefix = (
53+
int(row["token"]),
54+
int(row["model_dim"]),
55+
int(row["inter_dim"]),
56+
int(row["expert"]),
57+
int(row["topk"]),
58+
bool(int(row.get("doweight_stage1", "0"))),
59+
int(row.get("cu_num", "0")),
60+
int(row.get("block_m", "0") or "0"),
61+
_normalized_enum(row.get("act_type", "")),
62+
)
63+
return {
64+
(*prefix, name, fuse_quant)
65+
for name, fuse_quant in (
66+
(stage1_name, None),
67+
(row.get("kernelName2", "").strip(), stage1_fuse_quant),
68+
)
69+
if name.startswith("flydsl_")
70+
}
71+
72+
73+
def _job_key(job: dict[str, Any]) -> tuple[Any, ...]:
74+
return (
75+
job["token_num"],
76+
job["model_dim"],
77+
job["inter_dim"],
78+
job["experts"],
79+
job["topk"],
80+
job["doweight_stage1"],
81+
job["cu_num"],
82+
job["block_m"],
83+
job["act"],
84+
job["kernel_name"],
85+
job.get("stage1_fuse_quant"),
86+
)
87+
88+
89+
def extend_fhmoe_jobs(
90+
csv_path: str,
91+
ordinary_jobs: list[dict[str, Any]],
92+
seen: set[tuple[Any, ...]],
93+
) -> list[dict[str, Any]]:
94+
"""Append FHMoE variants for eligible ordinary jobs in ``csv_path``."""
95+
eligible_keys: set[tuple[Any, ...]] = set()
96+
with open(csv_path, newline="") as f:
97+
for row in csv.DictReader(f):
98+
if _is_dsv4_fhmoe_row(row):
99+
eligible_keys.update(_row_job_keys(row))
100+
101+
fhmoe_jobs = []
102+
for job in ordinary_jobs:
103+
if (
104+
job.get("stage") not in (1, 2)
105+
or job.get("enable_bias", False)
106+
or _job_key(job) not in eligible_keys
107+
):
108+
continue
109+
fhmoe_job = {
110+
**job,
111+
"kernel_name": _without_xcd(job["kernel_name"]),
112+
"xcd_swizzle": 0,
113+
"shared_expert_id": job["experts"] - 1,
114+
}
115+
key = job_identity(fhmoe_job)
116+
if key in seen:
117+
continue
118+
seen.add(key)
119+
fhmoe_jobs.append(fhmoe_job)
120+
121+
return [*ordinary_jobs, *fhmoe_jobs]
122+
123+
124+
def _shared_weight(device, n_in: int, k_in: int):
125+
import torch
126+
127+
return torch.zeros((1, n_in, k_in), dtype=torch.uint8, device=device)
128+
129+
130+
def _shared_scale(device, n_in: int, k_in: int):
131+
import torch
132+
133+
rows = (n_in + 255) // 256 * 256
134+
cols = ((k_in + 255) // 256 * 256) // 32
135+
return torch.zeros((rows, cols), dtype=torch.uint8, device=device)
136+
137+
138+
@dataclass(frozen=True)
139+
class _FHMoEAOTBackend:
140+
shared_expert_id: int
141+
142+
def build_stage1_args(
143+
self,
144+
out,
145+
a,
146+
w,
147+
a_scale,
148+
w_scale,
149+
sorted_ids,
150+
sorted_expert_ids,
151+
sorted_weights,
152+
num_valid_ids,
153+
out_scale_sorted,
154+
token_num,
155+
n_in,
156+
k_in,
157+
size_expert_ids_in,
158+
dev,
159+
bias=None,
160+
stream=None,
161+
swiglu_limit=float("inf"),
162+
):
163+
from aiter.ops.flydsl.fhmoe import _s1_args_fhmoe
164+
165+
shared_w = _shared_weight(dev, n_in, k_in)
166+
shared_w_scale = _shared_scale(dev, n_in, k_in)
167+
return _s1_args_fhmoe(
168+
out,
169+
a,
170+
w,
171+
a_scale,
172+
w_scale,
173+
sorted_ids,
174+
sorted_expert_ids,
175+
sorted_weights,
176+
num_valid_ids,
177+
out_scale_sorted,
178+
token_num,
179+
n_in,
180+
k_in,
181+
size_expert_ids_in,
182+
dev,
183+
bias=bias,
184+
stream=stream,
185+
swiglu_limit=swiglu_limit,
186+
shared_w=shared_w.view(-1),
187+
shared_w_scale=shared_w_scale.view(-1),
188+
)
189+
190+
def build_stage2_args(
191+
self,
192+
target,
193+
a,
194+
w,
195+
a_scale,
196+
w_scale,
197+
sorted_ids,
198+
sorted_expert_ids,
199+
sorted_weights,
200+
num_valid_ids,
201+
token_num,
202+
n_in,
203+
k_in,
204+
blocks,
205+
dev,
206+
bias=None,
207+
stream=None,
208+
):
209+
from aiter.ops.flydsl.fhmoe import _s2_args_fhmoe
210+
211+
shared_w = _shared_weight(dev, n_in, k_in)
212+
shared_w_scale = _shared_scale(dev, n_in, k_in)
213+
return _s2_args_fhmoe(
214+
target,
215+
a,
216+
w,
217+
a_scale,
218+
w_scale,
219+
sorted_ids,
220+
sorted_expert_ids,
221+
sorted_weights,
222+
num_valid_ids,
223+
token_num,
224+
n_in,
225+
k_in,
226+
blocks,
227+
dev,
228+
bias=bias,
229+
stream=stream,
230+
shared_w=shared_w,
231+
shared_w_scale=shared_w_scale,
232+
)
233+
234+
def compile_stage1(self, **kwargs):
235+
from aiter.ops.flydsl.fhmoe import compile_flydsl_fhmoe_stage1
236+
237+
kwargs.pop("xcd_swizzle", None)
238+
return compile_flydsl_fhmoe_stage1(
239+
**kwargs,
240+
shared_expert_id=self.shared_expert_id,
241+
)
242+
243+
def compile_stage2(self, **kwargs):
244+
from aiter.ops.flydsl.fhmoe import compile_flydsl_fhmoe_stage2
245+
246+
kwargs.pop("xcd_swizzle", None)
247+
return compile_flydsl_fhmoe_stage2(
248+
**kwargs,
249+
shared_expert_id=self.shared_expert_id,
250+
)
251+
252+
253+
def precompile_fhmoe_to_cache(
254+
*,
255+
experts: int,
256+
shared_expert_id: int,
257+
a_dtype: str = "fp8",
258+
b_dtype: str = "fp4",
259+
act: str = "silu",
260+
cu_num: int = 0,
261+
enable_bias: bool = False,
262+
**kwargs,
263+
):
264+
"""Precompile one heterogeneous MoE job through the shared AOT harness."""
265+
if shared_expert_id != experts - 1:
266+
raise ValueError(
267+
"FHMoE AOT expects the shared expert to be the final logical expert; "
268+
f"got {shared_expert_id=} for {experts=}"
269+
)
270+
if a_dtype != "fp8" or b_dtype != "fp4":
271+
raise ValueError(
272+
"FHMoE AOT supports routed FP8 activations and MXFP4 weights; "
273+
f"got {a_dtype=} and {b_dtype=}"
274+
)
275+
if enable_bias:
276+
raise ValueError("FHMoE AOT does not support expert bias")
277+
if act != "silu":
278+
raise ValueError(f"FHMoE AOT supports only SiLU, got {act=}")
279+
if cu_num_to_arch(cu_num) != "gfx950":
280+
raise ValueError(f"FHMoE AOT supports only gfx950, got {cu_num=}")
281+
282+
from aiter.aot.flydsl.moe import _precompile_to_cache
283+
284+
return _precompile_to_cache(
285+
experts=experts,
286+
a_dtype=a_dtype,
287+
b_dtype=b_dtype,
288+
act=act,
289+
cu_num=cu_num,
290+
enable_bias=enable_bias,
291+
_aot_backend=_FHMoEAOTBackend(shared_expert_id),
292+
**kwargs,
293+
)

aiter/aot/flydsl/moe.py

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,9 @@ def parse_csv(csv_path: str):
177177

178178
jobs.append(full_job)
179179

180-
return jobs
180+
from aiter.aot.flydsl.fhmoe import extend_fhmoe_jobs
181+
182+
return extend_fhmoe_jobs(csv_path, jobs, seen)
181183

182184

183185
def _precompile_to_cache(
@@ -221,6 +223,7 @@ def _precompile_to_cache(
221223
# `compile_flydsl_moe_stage2` for stage 2 AOT compilation.
222224
use_async_copy: bool = False,
223225
cu_num_mul: int = 1,
226+
_aot_backend=None,
224227
**kwargs,
225228
):
226229
"""Trigger MLIR compilation by calling the runtime stage1/stage2 entry points
@@ -512,7 +515,12 @@ def _make_a_user(a_dtype_user_shape):
512515
)
513516

514517
if use_mx_gemm:
515-
args = _s1_args_fp4(
518+
build_stage1_args = (
519+
_s1_args_fp4
520+
if _aot_backend is None
521+
else _aot_backend.build_stage1_args
522+
)
523+
args = build_stage1_args(
516524
_kernel_out.view(-1),
517525
a.view(-1),
518526
w1.view(-1),
@@ -554,7 +562,12 @@ def _make_a_user(a_dtype_user_shape):
554562
stream=0,
555563
)
556564

557-
exe = compile_flydsl_moe_stage1(
565+
compile_stage1 = (
566+
compile_flydsl_moe_stage1
567+
if _aot_backend is None
568+
else _aot_backend.compile_stage1
569+
)
570+
exe = compile_stage1(
558571
model_dim=model_dim,
559572
inter_dim=inter_dim,
560573
experts=E,
@@ -691,7 +704,12 @@ def _make_a_user(a_dtype_user_shape):
691704
_k_in = inter_dim
692705

693706
if use_mx_gemm:
694-
args = _s2_args_fp4(
707+
build_stage2_args = (
708+
_s2_args_fp4
709+
if _aot_backend is None
710+
else _aot_backend.build_stage2_args
711+
)
712+
args = build_stage2_args(
695713
target,
696714
a,
697715
w2,
@@ -727,7 +745,12 @@ def _make_a_user(a_dtype_user_shape):
727745
stream=0,
728746
)
729747

730-
exe = compile_flydsl_moe_stage2(
748+
compile_stage2 = (
749+
compile_flydsl_moe_stage2
750+
if _aot_backend is None
751+
else _aot_backend.compile_stage2
752+
)
753+
exe = compile_stage2(
731754
model_dim=model_dim,
732755
inter_dim=inter_dim,
733756
experts=E,
@@ -883,7 +906,17 @@ def compile_one_config(
883906
topk=topk,
884907
)
885908
else:
886-
_precompile_to_cache(
909+
shared_expert_id = kwargs.pop("shared_expert_id", -1)
910+
if shared_expert_id >= 0:
911+
from aiter.aot.flydsl.fhmoe import (
912+
precompile_fhmoe_to_cache,
913+
)
914+
915+
precompile = precompile_fhmoe_to_cache
916+
kwargs["shared_expert_id"] = shared_expert_id
917+
else:
918+
precompile = _precompile_to_cache
919+
precompile(
887920
model_dim=model_dim,
888921
inter_dim=inter_dim,
889922
experts=experts,

0 commit comments

Comments
 (0)