[AMD] [GLM-5.3-Flash Day 0] Route mHC through AITER on gfx950 (#38545)
Co-authored-by: Raiden-Makoto <Raiden-Makoto@users.noreply.github.com> Co-authored-by: Thomas Wang <thomawan@amd.com> Co-authored-by: Kevin Mi <45493463+kevin-mii@users.noreply.github.com> Co-authored-by: Kevin Mi <mikevin920@yahoo.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Raiden-Makoto
Thomas Wang
Kevin Mi
Kevin Mi
Claude Fable 5.1
parent
264da63319
commit
095e45100b
@@ -18,10 +18,99 @@ from sglang.srt.layers.attention.dsa.utils import is_dsa_prefill_cp_interleave
|
|||||||
from sglang.srt.layers.dp_attention import is_allocation_symmetric
|
from sglang.srt.layers.dp_attention import is_allocation_symmetric
|
||||||
from sglang.srt.layers.utils.common import strict_contiguous
|
from sglang.srt.layers.utils.common import strict_contiguous
|
||||||
from sglang.srt.runtime_context import get_parallel, get_platform
|
from sglang.srt.runtime_context import get_parallel, get_platform
|
||||||
|
from sglang.srt.utils import is_gfx95_supported, is_hip
|
||||||
from sglang.srt.utils.common import is_gfx1250_supported
|
from sglang.srt.utils.common import is_gfx1250_supported
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
_AITER_MHC_RUNTIME_DISABLED = False
|
||||||
|
_AITER_MHC_ACTIVE_LOGGED = False
|
||||||
|
|
||||||
|
|
||||||
|
def _use_aiter_mhc() -> bool:
|
||||||
|
return (
|
||||||
|
not _AITER_MHC_RUNTIME_DISABLED
|
||||||
|
and is_gfx95_supported()
|
||||||
|
and envs.SGLANG_USE_AITER.get()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _try_aiter_mhc_pre(
|
||||||
|
residual: torch.Tensor,
|
||||||
|
fn: torch.Tensor,
|
||||||
|
hc_scale: torch.Tensor,
|
||||||
|
hc_base: torch.Tensor,
|
||||||
|
rms_eps: float,
|
||||||
|
hc_pre_eps: float,
|
||||||
|
hc_sinkhorn_eps: float,
|
||||||
|
hc_post_mult_value: float,
|
||||||
|
sinkhorn_repeat: int,
|
||||||
|
norm_weight: torch.Tensor | None,
|
||||||
|
norm_eps: float | None,
|
||||||
|
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor] | None:
|
||||||
|
global _AITER_MHC_RUNTIME_DISABLED, _AITER_MHC_ACTIVE_LOGGED
|
||||||
|
|
||||||
|
try:
|
||||||
|
from aiter.ops.mhc import mhc_pre as aiter_mhc_pre
|
||||||
|
except Exception as err:
|
||||||
|
logger.warning("AITER mHC pre is unavailable, falling back: %s", err)
|
||||||
|
_AITER_MHC_RUNTIME_DISABLED = True
|
||||||
|
return None
|
||||||
|
|
||||||
|
kwargs = {}
|
||||||
|
if norm_weight is not None:
|
||||||
|
kwargs["norm_weight"] = norm_weight
|
||||||
|
kwargs["norm_eps"] = norm_eps if norm_eps is not None else rms_eps
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = aiter_mhc_pre(
|
||||||
|
residual,
|
||||||
|
fn,
|
||||||
|
hc_scale,
|
||||||
|
hc_base,
|
||||||
|
rms_eps,
|
||||||
|
hc_pre_eps,
|
||||||
|
hc_sinkhorn_eps,
|
||||||
|
hc_post_mult_value,
|
||||||
|
sinkhorn_repeat,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
except Exception as err:
|
||||||
|
logger.warning("AITER mHC pre failed, disabling fast path: %s", err)
|
||||||
|
_AITER_MHC_RUNTIME_DISABLED = True
|
||||||
|
return None
|
||||||
|
|
||||||
|
if not _AITER_MHC_ACTIVE_LOGGED:
|
||||||
|
logger.info("Using AITER gfx950 mHC pre/post kernels")
|
||||||
|
_AITER_MHC_ACTIVE_LOGGED = True
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def _try_aiter_mhc_post(
|
||||||
|
x: torch.Tensor,
|
||||||
|
residual: torch.Tensor,
|
||||||
|
post_layer_mix: torch.Tensor,
|
||||||
|
comb_res_mix: torch.Tensor,
|
||||||
|
) -> torch.Tensor | None:
|
||||||
|
global _AITER_MHC_RUNTIME_DISABLED
|
||||||
|
|
||||||
|
try:
|
||||||
|
from aiter.ops.mhc import mhc_post as aiter_mhc_post
|
||||||
|
except Exception as err:
|
||||||
|
logger.warning("AITER mHC post is unavailable, falling back: %s", err)
|
||||||
|
_AITER_MHC_RUNTIME_DISABLED = True
|
||||||
|
return None
|
||||||
|
|
||||||
|
out = torch.empty_like(residual)
|
||||||
|
try:
|
||||||
|
aiter_mhc_post(out, x, residual, post_layer_mix, comb_res_mix)
|
||||||
|
except Exception as err:
|
||||||
|
logger.warning("AITER mHC post failed, disabling fast path: %s", err)
|
||||||
|
_AITER_MHC_RUNTIME_DISABLED = True
|
||||||
|
return None
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
# This module is imported during model-registry discovery. Do not import the real
|
# This module is imported during model-registry discovery. Do not import the real
|
||||||
# TileLang package here: it loads native CUDA stubs. The proxy below lets
|
# TileLang package here: it loads native CUDA stubs. The proxy below lets
|
||||||
# module-level @tilelang.jit declarations parse, then imports and applies real
|
# module-level @tilelang.jit declarations parse, then imports and applies real
|
||||||
@@ -119,6 +208,24 @@ pass_configs = {
|
|||||||
tilelang.PassConfigKey.TL_DISABLE_TMA_LOWER: True,
|
tilelang.PassConfigKey.TL_DISABLE_TMA_LOWER: True,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _use_deep_gemm_hc_prenorm() -> bool:
|
||||||
|
if is_hip() or not envs.SGLANG_OPT_DEEPGEMM_HC_PRENORM.get():
|
||||||
|
return False
|
||||||
|
|
||||||
|
from sglang.srt.layers.deep_gemm_wrapper.configurer import ENABLE_JIT_DEEPGEMM
|
||||||
|
|
||||||
|
return ENABLE_JIT_DEEPGEMM
|
||||||
|
|
||||||
|
|
||||||
|
def _use_tilelang_mhc_pre() -> bool:
|
||||||
|
return envs.SGLANG_OPT_USE_TILELANG_MHC_PRE.get() and not is_hip()
|
||||||
|
|
||||||
|
|
||||||
|
def _use_tilelang_mhc_post() -> bool:
|
||||||
|
return envs.SGLANG_OPT_USE_TILELANG_MHC_POST.get() and not is_hip()
|
||||||
|
|
||||||
|
|
||||||
FP8 = "float8_e4m3"
|
FP8 = "float8_e4m3"
|
||||||
BF16 = "bfloat16"
|
BF16 = "bfloat16"
|
||||||
FP32 = "float32"
|
FP32 = "float32"
|
||||||
@@ -1041,7 +1148,7 @@ def mhc_pre(
|
|||||||
num_tokens, hidden_size, dtype=torch.bfloat16, device=residual.device
|
num_tokens, hidden_size, dtype=torch.bfloat16, device=residual.device
|
||||||
)
|
)
|
||||||
|
|
||||||
if envs.SGLANG_OPT_DEEPGEMM_HC_PRENORM.get():
|
if _use_deep_gemm_hc_prenorm():
|
||||||
n_splits = _compute_num_split_for_mhc_pre(num_tokens, hc_hidden_size)
|
n_splits = _compute_num_split_for_mhc_pre(num_tokens, hc_hidden_size)
|
||||||
|
|
||||||
gemm_out_mul = torch.empty(
|
gemm_out_mul = torch.empty(
|
||||||
@@ -1653,7 +1760,7 @@ def mhc_fused_post_pre(
|
|||||||
hidden_size,
|
hidden_size,
|
||||||
)
|
)
|
||||||
|
|
||||||
if envs.SGLANG_OPT_DEEPGEMM_HC_PRENORM.get():
|
if _use_deep_gemm_hc_prenorm():
|
||||||
import deep_gemm
|
import deep_gemm
|
||||||
|
|
||||||
deep_gemm.tf32_hc_prenorm_gemm(
|
deep_gemm.tf32_hc_prenorm_gemm(
|
||||||
@@ -1847,7 +1954,25 @@ def _mhc_pre_dispatch(
|
|||||||
norm_eps: float | None = None,
|
norm_eps: float | None = None,
|
||||||
) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor, bool]:
|
) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor, bool]:
|
||||||
assert residual.dim() == 3, f"residual must be (s, n, h); got {residual.shape}"
|
assert residual.dim() == 3, f"residual must be (s, n, h); got {residual.shape}"
|
||||||
if not envs.SGLANG_OPT_USE_TILELANG_MHC_PRE.get():
|
if _use_aiter_mhc():
|
||||||
|
result = _try_aiter_mhc_pre(
|
||||||
|
residual=residual,
|
||||||
|
fn=fn,
|
||||||
|
hc_scale=hc_scale,
|
||||||
|
hc_base=hc_base,
|
||||||
|
rms_eps=rms_eps,
|
||||||
|
hc_pre_eps=hc_pre_eps,
|
||||||
|
hc_sinkhorn_eps=hc_sinkhorn_eps,
|
||||||
|
hc_post_mult_value=hc_post_mult_value,
|
||||||
|
sinkhorn_repeat=sinkhorn_repeat,
|
||||||
|
norm_weight=norm_weight,
|
||||||
|
norm_eps=norm_eps,
|
||||||
|
)
|
||||||
|
if result is not None:
|
||||||
|
post_mix, comb_mix, layer_input = result
|
||||||
|
return post_mix, comb_mix, layer_input, norm_weight is not None
|
||||||
|
|
||||||
|
if not _use_tilelang_mhc_pre():
|
||||||
post_mix, comb_mix, layer_input = _mhc_pre_torch(
|
post_mix, comb_mix, layer_input = _mhc_pre_torch(
|
||||||
residual=residual,
|
residual=residual,
|
||||||
fn=fn,
|
fn=fn,
|
||||||
@@ -1886,7 +2011,17 @@ def _mhc_post_dispatch(
|
|||||||
) -> torch.Tensor:
|
) -> torch.Tensor:
|
||||||
assert x.dim() == 2 and residual.dim() == 3
|
assert x.dim() == 2 and residual.dim() == 3
|
||||||
assert post_layer_mix.dim() == 3 and comb_res_mix.dim() == 3
|
assert post_layer_mix.dim() == 3 and comb_res_mix.dim() == 3
|
||||||
if not envs.SGLANG_OPT_USE_TILELANG_MHC_POST.get():
|
if _use_aiter_mhc():
|
||||||
|
result = _try_aiter_mhc_post(
|
||||||
|
x=x,
|
||||||
|
residual=residual,
|
||||||
|
post_layer_mix=post_layer_mix,
|
||||||
|
comb_res_mix=comb_res_mix,
|
||||||
|
)
|
||||||
|
if result is not None:
|
||||||
|
return result
|
||||||
|
|
||||||
|
if not _use_tilelang_mhc_post():
|
||||||
return _mhc_post_torch(x, residual, post_layer_mix, comb_res_mix)
|
return _mhc_post_torch(x, residual, post_layer_mix, comb_res_mix)
|
||||||
return mhc_post(x, residual, post_layer_mix, comb_res_mix)
|
return mhc_post(x, residual, post_layer_mix, comb_res_mix)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,213 @@
|
|||||||
|
"""The AITER mHC route on gfx950: gate, fallback latch, and kernel numerics vs the Torch oracle."""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import types
|
||||||
|
import unittest
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import torch
|
||||||
|
|
||||||
|
from sglang.kernels.ops.layernorm import mhc
|
||||||
|
from sglang.srt.environ import envs
|
||||||
|
from sglang.srt.utils import is_gfx95_supported, is_hip
|
||||||
|
from sglang.test.ci.ci_register import register_amd_ci
|
||||||
|
from sglang.test.test_utils import CustomTestCase
|
||||||
|
|
||||||
|
register_amd_ci(est_time=120, suite="stage-b-test-1-gpu-small-amd-mi35x")
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.skipUnless(
|
||||||
|
torch.cuda.is_available() and is_hip() and is_gfx95_supported(),
|
||||||
|
"requires one gfx950 GPU",
|
||||||
|
)
|
||||||
|
class TestAiterMHCGLM53Flash(CustomTestCase):
|
||||||
|
hidden_size = 4096
|
||||||
|
hc_mult = 4
|
||||||
|
rms_eps = 1e-6
|
||||||
|
hc_eps = 1e-6
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
mhc._AITER_MHC_RUNTIME_DISABLED = False
|
||||||
|
|
||||||
|
def _inputs(self, tokens: int, seed: int = 0):
|
||||||
|
torch.manual_seed(seed)
|
||||||
|
device = torch.device("cuda")
|
||||||
|
mix_size = 2 * self.hc_mult + self.hc_mult**2
|
||||||
|
residual = (
|
||||||
|
torch.randn(
|
||||||
|
tokens,
|
||||||
|
self.hc_mult,
|
||||||
|
self.hidden_size,
|
||||||
|
device=device,
|
||||||
|
dtype=torch.bfloat16,
|
||||||
|
)
|
||||||
|
* 0.1
|
||||||
|
)
|
||||||
|
fn = (
|
||||||
|
torch.randn(
|
||||||
|
mix_size,
|
||||||
|
self.hc_mult * self.hidden_size,
|
||||||
|
device=device,
|
||||||
|
dtype=torch.float32,
|
||||||
|
)
|
||||||
|
* 0.01
|
||||||
|
)
|
||||||
|
scale = torch.tensor([0.5, 0.25, 0.25], device=device, dtype=torch.float32)
|
||||||
|
base = torch.zeros(mix_size, device=device, dtype=torch.float32)
|
||||||
|
return residual, fn, scale, base
|
||||||
|
|
||||||
|
def _rmsnorm(self, x, weight):
|
||||||
|
return (
|
||||||
|
x.float()
|
||||||
|
* torch.rsqrt(x.float().square().mean(dim=-1, keepdim=True) + self.rms_eps)
|
||||||
|
* weight.float()
|
||||||
|
).to(x.dtype)
|
||||||
|
|
||||||
|
def test_gate_selects_aiter_on_gfx950(self):
|
||||||
|
"""A gate that resolves False on real hardware silently serves the Torch path."""
|
||||||
|
with envs.SGLANG_USE_AITER.override(True):
|
||||||
|
self.assertTrue(mhc._use_aiter_mhc())
|
||||||
|
|
||||||
|
def test_hip_without_aiter_stays_on_torch_and_never_loads_tilelang(self):
|
||||||
|
"""The TileLang/DeepGEMM flags default on; only the HIP gate keeps them off this device."""
|
||||||
|
residual, fn, scale, base = self._inputs(8)
|
||||||
|
x = residual.reshape(8, self.hc_mult * self.hidden_size)
|
||||||
|
_, _, layer_ref = mhc._mhc_pre_torch(
|
||||||
|
residual, fn, scale, base, self.rms_eps, self.hc_eps, self.hc_eps, 2.0, 4
|
||||||
|
)
|
||||||
|
with (
|
||||||
|
envs.SGLANG_USE_AITER.override(False),
|
||||||
|
envs.SGLANG_OPT_USE_TILELANG_MHC_PRE.override(True),
|
||||||
|
envs.SGLANG_OPT_USE_TILELANG_MHC_POST.override(True),
|
||||||
|
envs.SGLANG_OPT_DEEPGEMM_HC_PRENORM.override(True),
|
||||||
|
patch.object(
|
||||||
|
mhc, "_load_tilelang", side_effect=AssertionError("TileLang imported")
|
||||||
|
),
|
||||||
|
):
|
||||||
|
self.assertFalse(mhc._use_aiter_mhc())
|
||||||
|
self.assertFalse(mhc._use_tilelang_mhc_pre())
|
||||||
|
self.assertFalse(mhc._use_tilelang_mhc_post())
|
||||||
|
self.assertFalse(mhc._use_deep_gemm_hc_prenorm())
|
||||||
|
layer_input, h_res, h_post, norm_fused = mhc.hc_pre(
|
||||||
|
x, fn, scale, base, self.hc_mult, self.rms_eps, self.hc_eps, 4
|
||||||
|
)
|
||||||
|
out = mhc.hc_post(layer_input, x, h_post, h_res, self.hc_mult)
|
||||||
|
self.assertFalse(norm_fused)
|
||||||
|
torch.testing.assert_close(layer_input, layer_ref)
|
||||||
|
self.assertTrue(torch.isfinite(out).all())
|
||||||
|
|
||||||
|
def test_aiter_import_and_runtime_failures_latch_to_torch(self):
|
||||||
|
"""A missing symbol or a raising kernel must disable the route once, not fail the request."""
|
||||||
|
residual, fn, scale, base = self._inputs(8)
|
||||||
|
x = residual.reshape(8, self.hc_mult * self.hidden_size)
|
||||||
|
modules = {
|
||||||
|
"aiter": types.ModuleType("aiter"),
|
||||||
|
"aiter.ops": types.ModuleType("aiter.ops"),
|
||||||
|
"aiter.ops.mhc": types.ModuleType("aiter.ops.mhc"),
|
||||||
|
}
|
||||||
|
with patch.dict(sys.modules, modules):
|
||||||
|
result = mhc._try_aiter_mhc_pre(
|
||||||
|
residual,
|
||||||
|
fn,
|
||||||
|
scale,
|
||||||
|
base,
|
||||||
|
self.rms_eps,
|
||||||
|
self.hc_eps,
|
||||||
|
self.hc_eps,
|
||||||
|
2.0,
|
||||||
|
4,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
self.assertIsNone(result)
|
||||||
|
self.assertTrue(mhc._AITER_MHC_RUNTIME_DISABLED)
|
||||||
|
|
||||||
|
mhc._AITER_MHC_RUNTIME_DISABLED = False
|
||||||
|
|
||||||
|
def fail_post(*_args, **_kwargs):
|
||||||
|
raise RuntimeError("synthetic failure")
|
||||||
|
|
||||||
|
failing = types.ModuleType("aiter.ops.mhc")
|
||||||
|
failing.mhc_post = fail_post
|
||||||
|
modules["aiter.ops.mhc"] = failing
|
||||||
|
with envs.SGLANG_USE_AITER.override(False):
|
||||||
|
layer_input, h_res, h_post, _ = mhc.hc_pre(
|
||||||
|
x, fn, scale, base, self.hc_mult, self.rms_eps, self.hc_eps, 4
|
||||||
|
)
|
||||||
|
with (
|
||||||
|
patch.dict(sys.modules, modules),
|
||||||
|
envs.SGLANG_USE_AITER.override(True),
|
||||||
|
):
|
||||||
|
out = mhc.hc_post(layer_input, x, h_post, h_res, self.hc_mult)
|
||||||
|
self.assertTrue(mhc._AITER_MHC_RUNTIME_DISABLED)
|
||||||
|
self.assertTrue(torch.isfinite(out).all())
|
||||||
|
|
||||||
|
def test_aiter_pre_post_match_torch_oracle(self):
|
||||||
|
"""A positional or kwarg mixup in the AITER call shows up only against the real kernel."""
|
||||||
|
norm_weight = torch.linspace(
|
||||||
|
0.75, 1.25, self.hidden_size, device="cuda", dtype=torch.bfloat16
|
||||||
|
)
|
||||||
|
for tokens in (1, 8, 17, 32, 64, 128):
|
||||||
|
for sinkhorn_iters in (2, 20):
|
||||||
|
for fused_norm in (False, True):
|
||||||
|
with self.subTest(
|
||||||
|
tokens=tokens, sinkhorn_iters=sinkhorn_iters, norm=fused_norm
|
||||||
|
):
|
||||||
|
residual, fn, scale, base = self._inputs(tokens)
|
||||||
|
post_ref, comb_ref, layer_ref = mhc._mhc_pre_torch(
|
||||||
|
residual,
|
||||||
|
fn,
|
||||||
|
scale,
|
||||||
|
base,
|
||||||
|
self.rms_eps,
|
||||||
|
self.hc_eps,
|
||||||
|
self.hc_eps,
|
||||||
|
2.0,
|
||||||
|
sinkhorn_iters,
|
||||||
|
)
|
||||||
|
result = mhc._try_aiter_mhc_pre(
|
||||||
|
residual,
|
||||||
|
fn,
|
||||||
|
scale,
|
||||||
|
base,
|
||||||
|
self.rms_eps,
|
||||||
|
self.hc_eps,
|
||||||
|
self.hc_eps,
|
||||||
|
2.0,
|
||||||
|
sinkhorn_iters,
|
||||||
|
norm_weight if fused_norm else None,
|
||||||
|
self.rms_eps if fused_norm else None,
|
||||||
|
)
|
||||||
|
self.assertIsNotNone(result, "AITER mHC pre fell back")
|
||||||
|
post_out, comb_out, layer_out = result
|
||||||
|
if fused_norm:
|
||||||
|
layer_ref = self._rmsnorm(layer_ref, norm_weight)
|
||||||
|
torch.cuda.synchronize()
|
||||||
|
|
||||||
|
torch.testing.assert_close(
|
||||||
|
post_out, post_ref, atol=2e-3, rtol=2e-3
|
||||||
|
)
|
||||||
|
torch.testing.assert_close(
|
||||||
|
comb_out, comb_ref, atol=2e-3, rtol=2e-3
|
||||||
|
)
|
||||||
|
torch.testing.assert_close(
|
||||||
|
layer_out, layer_ref, atol=2e-2, rtol=2e-2
|
||||||
|
)
|
||||||
|
|
||||||
|
x = (layer_ref.float() * 0.75).to(layer_ref.dtype)
|
||||||
|
post_ref_out = mhc._mhc_post_torch(
|
||||||
|
x, residual, post_ref, comb_ref
|
||||||
|
)
|
||||||
|
post_out_actual = mhc._try_aiter_mhc_post(
|
||||||
|
x, residual, post_out, comb_out
|
||||||
|
)
|
||||||
|
self.assertIsNotNone(
|
||||||
|
post_out_actual, "AITER mHC post fell back"
|
||||||
|
)
|
||||||
|
torch.testing.assert_close(
|
||||||
|
post_out_actual, post_ref_out, atol=2e-2, rtol=2e-2
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user