Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 57 additions & 74 deletions aiter/fused_moe.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import re
from collections.abc import Callable
from dataclasses import dataclass
from enum import Enum

import torch

Expand All @@ -32,20 +31,7 @@
)
from aiter.jit.utils.torch_guard import torch_compile_guard
from aiter.ops.flydsl.kernels.mega_moe_gfx1250.types import Stage2ScatterContext

try:
from aiter.ops.flydsl.moe_common import GateMode
from aiter.ops.flydsl.utils import is_flydsl_available
except ImportError:

class GateMode(Enum):
SEPARATED = "separated"
INTERLEAVE = "interleave"

def is_flydsl_available():
return False


from aiter.ops.flydsl.moe_common import GateMode
from aiter.ops.flydsl.mxfp4_kname import (
_is_mxfp4_kname,
_parse_mxfp4_g1_kname,
Expand All @@ -58,10 +44,18 @@ def is_flydsl_available():
opus_a8w4_stage1_wrapper as _opus_a8w4_stage1_wrapper,
)


@functools.lru_cache(maxsize=1)
def _get_flydsl_moe_kernels():
from aiter.ops.flydsl import moe_kernels

return moe_kernels


BLOCK_SIZE_M = 32

# Sorting backend flags (mutually exclusive; CK > FlyDSL > Opus priority).
# Default is Opus. Set AITER_USE_FLYDSL_MOE_SORTING=1 to prefer FlyDSL when available.
# Default is Opus. Set AITER_USE_FLYDSL_MOE_SORTING=1 to prefer FlyDSL.
_USE_CK_MOE_SORTING = os.environ.get("AITER_USE_CK_MOE_SORTING", "0") == "1"
_USE_FLYDSL_MOE_SORTING = os.environ.get("AITER_USE_FLYDSL_MOE_SORTING", "0") == "1"
# "adaptive sort" backend selection (mxfp4 sort as a general World-1 backend):
Expand Down Expand Up @@ -368,7 +362,6 @@ def moe_sorting(
if (
not _USE_CK_MOE_SORTING
and _USE_FLYDSL_MOE_SORTING
and is_flydsl_available()
and not return_local_topk_ids
and not flat
and not output_aux
Expand Down Expand Up @@ -437,7 +430,7 @@ def stage2_uses_route_reduce(stage2: Callable) -> bool:
func = getattr(stage2, "func", stage2)
kernel_name = getattr(stage2, "keywords", {}).get("kernelName", "")
if func is _flydsl_stage2_wrapper or getattr(func, "_is_flydsl_stage2", False):
parsed = aiter.ops.flydsl.moe_kernels.get_flydsl_kernel_params(kernel_name)
parsed = _get_flydsl_moe_kernels().get_flydsl_kernel_params(kernel_name)
if parsed is None:
return False
# a16w4 (bf16 A x mxfp4 W) down-proj only supports atomic scatter into a
Expand Down Expand Up @@ -824,47 +817,41 @@ def _fused_moe_impl(
q_dtype_a = _q_dtype_a

grouped_a8w4_out = None
if is_flydsl_available():
try:
from aiter.ops.flydsl.grouped_moe_gfx1250 import (
grouped_gemm_gfx1250_a8w4,
)
except ImportError:
grouped_gemm_gfx1250_a8w4 = None

# grouped_gemm_gfx1250_a8w4 reads GUGU (gate/up row-interleaved) w1 only,
# so it is reachable exclusively from GateMode.INTERLEAVE. SEPARATED
# weights fall through to the generic MoE below.
if grouped_gemm_gfx1250_a8w4 is not None and gate_mode == GateMode.INTERLEAVE:
grouped_a8w4_out = grouped_gemm_gfx1250_a8w4(
hidden_states,
w1,
w2,
topk_weight,
topk_ids,
E=E,
model_dim=model_dim,
inter_dim=inter_dim,
dtype=dtype,
activation=activation,
quant_type=quant_type,
q_dtype_a=q_dtype_a,
q_dtype_w=q_dtype_w,
isG1U1=isG1U1,
doweight_stage1=doweight_stage1,
w1_scale=w1_scale,
w2_scale=w2_scale,
expert_mask=expert_mask,
hidden_pad=hidden_pad,
intermediate_pad=intermediate_pad,
bias1=bias1,
bias2=bias2,
swiglu_limit=swiglu_limit,
num_local_tokens=num_local_tokens,
situ_beta=1.0 if beta is None else float(beta),
situ_linear_beta=1.0 if linear_beta is None else float(linear_beta),
stage2_scatter=stage2_scatter,
)
from aiter.ops.flydsl.grouped_moe_gfx1250 import grouped_gemm_gfx1250_a8w4

# grouped_gemm_gfx1250_a8w4 reads GUGU (gate/up row-interleaved) w1 only,
# so it is reachable exclusively from GateMode.INTERLEAVE. SEPARATED
# weights fall through to the generic MoE below.
if gate_mode == GateMode.INTERLEAVE:
grouped_a8w4_out = grouped_gemm_gfx1250_a8w4(
hidden_states,
w1,
w2,
topk_weight,
topk_ids,
E=E,
model_dim=model_dim,
inter_dim=inter_dim,
dtype=dtype,
activation=activation,
quant_type=quant_type,
q_dtype_a=q_dtype_a,
q_dtype_w=q_dtype_w,
isG1U1=isG1U1,
doweight_stage1=doweight_stage1,
w1_scale=w1_scale,
w2_scale=w2_scale,
expert_mask=expert_mask,
hidden_pad=hidden_pad,
intermediate_pad=intermediate_pad,
bias1=bias1,
bias2=bias2,
swiglu_limit=swiglu_limit,
num_local_tokens=num_local_tokens,
situ_beta=1.0 if beta is None else float(beta),
situ_linear_beta=1.0 if linear_beta is None else float(linear_beta),
stage2_scatter=stage2_scatter,
)

if grouped_a8w4_out is not None:
return grouped_a8w4_out
Expand All @@ -879,10 +866,10 @@ def _fused_moe_impl(
if _is_a16w4_situv2:
for _bad, _why in (
(
get_gfx() not in ("gfx942", "gfx950") or not is_flydsl_available(),
get_gfx() not in ("gfx942", "gfx950"),
(
f"requires the FlyDSL kernel on CDNA gfx942/gfx950 "
f"(gfx={get_gfx()!r}, flydsl_available={is_flydsl_available()})"
f"(gfx={get_gfx()!r})"
),
),
(bias1 is not None or bias2 is not None, "per-expert bias"),
Expand Down Expand Up @@ -1443,7 +1430,8 @@ def _flydsl_stage1_wrapper(
inter_dim_pad, model_dim_pad = _get_padding_for_flydsl(
inter_dim_pad, model_dim_pad, bias1
)
parsed = aiter.ops.flydsl.moe_kernels.get_flydsl_kernel_params(kernelName)
moe_kernels = _get_flydsl_moe_kernels()
parsed = moe_kernels.get_flydsl_kernel_params(kernelName)
if parsed is None:
raise ValueError(f"Invalid FlyDSL kernel name: {kernelName}")
if out_dtype is not None:
Expand All @@ -1457,7 +1445,7 @@ def _flydsl_stage1_wrapper(
else:
raise ValueError(f"Unsupported activation for FlyDSL MoE stage1: {activation}")
_a_scale_one = parsed.get("a_scale_one", False)
return aiter.ops.flydsl.flydsl_moe_stage1(
return moe_kernels.flydsl_moe_stage1(
a=hidden_states,
w1=w1,
sorted_token_ids=sorted_token_ids,
Expand Down Expand Up @@ -1527,10 +1515,11 @@ def _flydsl_stage2_wrapper(
# already baked into this dict, so the `parsed.get(..., default)`
# calls below pick up the registered values for that kernel name
# rather than always falling back to defaults.
parsed = aiter.ops.flydsl.moe_kernels.get_flydsl_kernel_params(kernelName)
moe_kernels = _get_flydsl_moe_kernels()
parsed = moe_kernels.get_flydsl_kernel_params(kernelName)
if parsed is None:
raise ValueError(f"Invalid FlyDSL kernel name: {kernelName}")
return aiter.ops.flydsl.flydsl_moe_stage2(
return moe_kernels.flydsl_moe_stage2(
inter_states=inter_states,
w2=w2,
sorted_token_ids=sorted_token_ids,
Expand Down Expand Up @@ -2576,7 +2565,7 @@ def get_block_m() -> int:
is_opus1 = isinstance(kernelName1, str) and kernelName1.startswith("opus_moe1_")
is_cktile2 = isinstance(kernelName2, str) and kernelName2.startswith("cktile_")
is_opus2 = _opus_a8w4.is_opus_a8w4_stage2_kernel(kernelName2)
if is_opus1 or ((is_flydsl1 or is_flydsl2) and is_flydsl_available()):
if is_opus1 or is_flydsl1 or is_flydsl2:
enable_bias = (
_needs_swiglu_bias_support(dtype, q_type) and q_dtype_w == dtypes.fp4x2
)
Expand Down Expand Up @@ -2704,11 +2693,7 @@ def get_block_m() -> int:
and q_dtype_w == dtypes.fp4x2
and is_shuffled
)
if (
q_type == QuantType.per_1x32
and q_dtype_w == dtypes.i4x2
and is_flydsl_available()
):
if q_type == QuantType.per_1x32 and q_dtype_w == dtypes.i4x2:
# Untuned a16wi4 fallback: one shape-safe config on the shared a16w-mix port.
# Tiles belong in the tuned CSV, not in a heuristic here. ksplit is 0 because
# the port has no grid split-K (it uses intra-block k_wave); asking for it
Expand Down Expand Up @@ -2757,7 +2742,6 @@ def get_block_m() -> int:
and is_shuffled
and use_g1u1
and not doweight_stage1
and is_flydsl_available()
)
use_mxfp4_flydsl = _is_a16w4_situv2 or (
dtype in [dtypes.bf16, dtypes.fp16]
Expand All @@ -2773,7 +2757,6 @@ def get_block_m() -> int:
and is_shuffled
and use_g1u1
and not doweight_stage1
and is_flydsl_available()
)
if use_mxfp4_flydsl:
from aiter.ops.flydsl.moe_kernels import (
Expand Down Expand Up @@ -2940,7 +2923,7 @@ def get_block_m() -> int:
]
)
):
if kernelName2 and kernelName2.startswith("flydsl_") and is_flydsl_available():
if kernelName2 and kernelName2.startswith("flydsl_"):
stage2_func = functools.partial(
_flydsl_stage2_wrapper,
kernelName=kernelName2,
Expand Down
9 changes: 9 additions & 0 deletions aiter/jit/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,11 @@ def mp_lock(
f"{AITER_ROOT_DIR}/aiter/configs/bf16_tuned_gemm.csv",
)

AITER_CONFIG_GDR_DECODE = os.getenv(
"AITER_CONFIG_GDR_DECODE",
f"{AITER_ROOT_DIR}/aiter/configs/gdr_decode_tuned.csv",
)

# K5 opt BV tuned config. Per-model tuned rows live under model_configs/
# (qwen3_5_*_chunk_gdn_h_opt_tuned.csv) and get merged into this canonical file by
# get_config_file. It ships header-only: with no per-model table present
Expand Down Expand Up @@ -260,6 +265,10 @@ def AITER_CONFIG_GEMM_BF16_FILE(self):
"AITER_CONFIG_GEMM_BF16", AITER_CONFIG_GEMM_BF16, "bf16_tuned_gemm"
)

@property
def AITER_CONFIG_GDR_DECODE_FILE(self):
return AITER_CONFIG_GDR_DECODE

@property
def AITER_CONFIG_GDN_K5_OPT_FILE(self):
return self.get_config_file(
Expand Down
20 changes: 20 additions & 0 deletions aiter/jit/utils/chip_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,26 @@ def get_gfx():
return GFX_MAP.get(gfx_num, "unknown")


_LDS_CAPACITY_BYTES = {
"gfx90a": 64 * 1024,
"gfx942": 64 * 1024,
"gfx950": 160 * 1024,
"gfx1100": 64 * 1024,
"gfx1151": 64 * 1024,
"gfx1201": 64 * 1024,
"gfx1250": 320 * 1024,
}


def get_lds_capacity_bytes(gfx: str | None = None) -> int:
"""Return the architectural LDS capacity for one workgroup."""
arch = (gfx or get_gfx()).split(":", 1)[0].lower()
try:
return _LDS_CAPACITY_BYTES[arch]
except KeyError as exc:
raise ValueError(f"Unknown LDS capacity for architecture {arch!r}") from exc


@functools.lru_cache(maxsize=1)
def get_gfx_runtime() -> str:
"""Return the arch of the live GPU, always via rocminfo.
Expand Down
27 changes: 5 additions & 22 deletions aiter/mla.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,6 @@
_FLYDSL_MLA_REDUCE_TARGET_DV = 512


@functools.lru_cache(maxsize=1)
def _flydsl_mla_reduce_available() -> bool:
"""Whether the optional FlyDSL package is available on this device."""
try:
from aiter.ops.flydsl import is_flydsl_available

return is_flydsl_available()
except (ImportError, OSError, RuntimeError):
return False


def _flydsl_mla_reduce_supported(
partial_output: torch.Tensor,
partial_lse: torch.Tensor,
Expand Down Expand Up @@ -99,20 +88,14 @@ def _flydsl_mla_reduce_enabled() -> bool:
use the HIP path; the latter is routed directly by its caller rather than
inferred from ``max_seqlen_q``. Calls outside the permitted ABI and shape scope
use the HIP path.
Not memoized, so the env var can be toggled at runtime; only the optional
package availability probe above is cached.
Not memoized, so the env var can be toggled at runtime.
"""
try:
from flydsl.utils.env import EnvManager, OptBool
from flydsl.utils.env import EnvManager, OptBool

class _Env(EnvManager):
enabled = OptBool(False, env_var="AITER_MLA_REDUCE_FLYDSL")
class _Env(EnvManager):
enabled = OptBool(False, env_var="AITER_MLA_REDUCE_FLYDSL")

if not _Env().enabled:
return False
return _flydsl_mla_reduce_available()
except (ImportError, OSError, RuntimeError, ValueError):
return False
return bool(_Env().enabled)


def _mla_decode_reduce_v1_dispatch(
Expand Down
Loading
Loading