feat(magi2): add fused BF16 routed MoE path (#7206)

Signed-off-by: Xiaodong Ye <xiaodong.ye@mthreads.com>
This commit is contained in:
R0CKSTAR authored and GitHub committed 2026-09-28 23:20:30 +08:00
1 parent 79a46a4f54
commit 29b818a785
8 files changed
+1818 -299

No files matched your search

@@ -0,0 +1,310 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""Compare MAGI-2 routed MoE with equal preprocessing boundaries.
python -m benchmarks.kernels.benchmark_magi2_bf16_moe --tokens 2 4096 10000
Default (`--mode dispatch`): the same MAGI-2 layer is timed with its native
reference and default BF16 fast path, including routing and all preprocessing.
With `--mode routed`, routing probabilities/IDs are inputs to BOTH timed paths. Sorting,
padding, packing, allocations and reduction remain inside their respective
calls. The reference uses deterministic routed storage in diagnostic mode.
Use --mode dispatch to measure the
actual layer with the native and default fast paths, including the router. Unsupported
dispatch fails explicitly; it is never silently replaced by another baseline.
Stage timings are standalone diagnostics, not additive or env speedup claims.
These synthetic local shapes are not full-model performance measurements.
"""
from __future__ import annotations
import argparse
import importlib
import json
import math
import os
import statistics
import subprocess
import time
from contextlib import contextmanager
from pathlib import Path
from unittest.mock import patch
def parse_args(argv=None):
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--tokens", type=int, nargs="+", default=[2, 4096])
parser.add_argument("--heads", type=int, default=3, help="EP4 local head count for MAGI-2 Preview.")
parser.add_argument("--experts", type=int, default=256, help="Released experts per head.")
parser.add_argument("--top-k", type=int, default=6)
parser.add_argument("--warmup", type=int, default=10)
parser.add_argument("--iters", type=int, default=50)
parser.add_argument("--seed", type=int, default=419)
parser.add_argument("--mode", choices=["routed", "dispatch"], default="dispatch")
parser.add_argument(
"--native-deterministic",
action="store_true",
help="Use the portable deterministic native reference (needed by Triton-MUSA 3.2).",
)
parser.add_argument("--stages", action="store_true")
parser.add_argument("--output", type=Path)
args = parser.parse_args(argv)
if min(*args.tokens, args.heads, args.experts, args.top_k, args.iters) < 1 or args.warmup < 0:
parser.error("Shapes/iters must be positive; warmup must be nonnegative")
if args.top_k > args.experts:
parser.error("top-k must not exceed experts per head")
return args
@contextmanager
def env_switch(enabled):
# Keep the legacy benchmark context API; dispatch is no longer opt-in.
with patch.dict(os.environ, MAGI2_DETERMINISTIC="0"):
yield
def stats(values):
if not values or any(not math.isfinite(x) or x <= 0 for x in values):
raise ValueError("Missing/non-positive/non-finite timing sample")
ordered = sorted(values)
return {
"mean_us": statistics.mean(values),
"p50_us": statistics.median(values),
"p90_us": ordered[math.ceil(0.9 * len(ordered)) - 1],
"samples_us": values,
}
def measure(torch, calls, args):
"""Alternating AB/BA; cold-cache flush is outside each timed region."""
cuda = getattr(torch, "cuda")
names = list(calls)
sequence = names + names[::-1]
wall_samples = []
labels = []
counter = 0
def invoke():
nonlocal counter
name = sequence[counter % len(sequence)]
counter += 1
cuda.synchronize()
start = time.perf_counter_ns()
output = calls[name]()
cuda.synchronize()
elapsed = (time.perf_counter_ns() - start) / 1000
wall_samples.append(elapsed)
labels.append(name)
return output
count = len(names) * args.iters
cache = torch.empty(512 * 1024 * 1024, device="cuda", dtype=torch.uint8)
gpu_ms = []
for index in range(len(names) * args.warmup + count):
cache.zero_()
cuda.synchronize()
begin, end = cuda.Event(enable_timing=True), cuda.Event(enable_timing=True)
begin.record()
invoke()
end.record()
end.synchronize()
if index >= len(names) * args.warmup:
gpu_ms.append(begin.elapsed_time(end))
labels, wall_samples = labels[-count:], wall_samples[-count:]
result = {}
for name in names:
wall = [x for label, x in zip(labels, wall_samples) if label == name]
event = [float(x) * 1000 for label, x in zip(labels, gpu_ms) if label == name]
if len(wall) != args.iters or len(event) != args.iters:
raise RuntimeError("Timing harness did not return the requested sample counts")
result[name] = {"synchronized_wall": stats(wall), "gpu_event": stats(event)}
return result
def make_inputs(torch, tokens, args):
# Fan-in-scaled synthetic weights. Unlike the old script, W_up and W_gate
# have the same distribution and top-k IDs are unique within each token.
torch.manual_seed(args.seed)
x = torch.randn(tokens, args.heads, 256, device="cuda").to(torch.bfloat16)
shape = (args.heads * args.experts, 256, 1280)
gate, up = [(torch.randn(shape, device="cuda") / 16).to(torch.bfloat16) for _ in range(2)]
down = (torch.randn(shape[0], 1280, 256, device="cuda") / math.sqrt(1280)).to(torch.bfloat16)
logits = torch.randn(args.heads, tokens, args.experts, device="cuda")
selected = logits.topk(args.top_k, dim=-1)
probabilities = selected.values.softmax(-1)
return x, probabilities, selected.indices, gate, up, down
def check_and_capture(torch, moe, kernels, data, args):
x, probabilities, indices, gate, up, down = data
gather, weights, offsets = moe.global_sort_routes(probabilities, indices, args.experts)
sorted_routes, launches = [], []
scatter, invoke = kernels._deterministic_scatter, moe.invoke_fused_moe_bf16
def capture_scatter(routes, *params):
sorted_routes.append(routes.detach().clone())
return scatter(routes, *params)
def capture_launch(*params, **kwargs):
launches.append((params, kwargs))
return invoke(*params, **kwargs)
with patch.object(kernels, "_deterministic_scatter", capture_scatter):
moe.triton_mh_moe_forward(x, gather, weights, offsets, gate, up, down, deterministic=True)
packed_w13 = moe._pack_bf16_w13(gate, up)
route_buffers = moe._allocate_bf16_route_buffers(
x.shape[0] * args.heads * args.top_k,
args.heads * args.experts,
128,
x.device,
)
with patch.object(moe, "invoke_fused_moe_bf16", capture_launch):
actual = moe._bf16_fused_moe_forward(x, probabilities, indices, packed_w13, down, route_buffers)
if len(sorted_routes) != 1 or len(launches) != 2:
raise RuntimeError("Unexpected kernel dispatch: benchmark contract needs updating")
flat_experts = (indices + torch.arange(args.heads, device=x.device)[:, None, None] * args.experts).flatten()
order = flat_experts.argsort(stable=True)
new_routes = launches[1][0][2].reshape(-1, 256).index_select(0, order)
scatter_ids = gather.long() * args.heads + flat_experts[order] // args.experts
expected = torch.zeros(x.shape[0] * args.heads, 256, device=x.device, dtype=torch.float32)
expected.index_add_(0, scatter_ids, sorted_routes[0].float())
expected = expected.to(x.dtype).view_as(x)
errors = []
# Same expert/FP32 reduction contract as test_bf16_moe_wiring.py, rather
# than relaxing tolerances on the inherited BF16 scatter reduction.
for result, reference in ((new_routes, sorted_routes[0]), (actual, expected)):
if not (torch.isfinite(result).all() and torch.isfinite(reference).all()):
raise RuntimeError("Non-finite MoE output")
torch.testing.assert_close(result, reference, rtol=2e-2, atol=0.5)
relative_l2 = (result.float() - reference.float()).norm() / reference.float().norm().clamp_min(1e-6)
if relative_l2.item() >= 1e-3:
raise AssertionError(f"MoE relative L2 {relative_l2.item()} >= 1e-3")
errors.append(
{"max_abs": (result.float() - reference.float()).abs().max().item(), "relative_l2": relative_l2.item()}
)
return launches, errors
def benchmark(torch, moe, kernels, tokens, args):
data = make_inputs(torch, tokens, args)
x, probabilities, indices, gate, up, down = data
launches, errors = check_and_capture(torch, moe, kernels, data, args)
packed_w13 = moe._pack_bf16_w13(gate, up)
route_count = tokens * args.heads * args.top_k
route_buffers = moe._allocate_bf16_route_buffers(route_count, args.heads * args.experts, 128, x.device)
def reference():
gather, weights, offsets = moe.global_sort_routes(probabilities, indices, args.experts)
return moe.triton_mh_moe_forward(x, gather, weights, offsets, gate, up, down, deterministic=True)
calls = {
"reference_deterministic_routed": reference,
"candidate_routed": lambda: moe._bf16_fused_moe_forward(
x, probabilities, indices, packed_w13, down, route_buffers
),
}
if args.mode == "dispatch":
config = moe.Magi2MultiHeadMoEConfig(
args.heads * 256, args.heads, args.experts, args.top_k, 1280, torch.bfloat16
)
layer = moe.Magi2MultiHeadMoE(config).to(x.device)
for parameter, value in ((layer.W_gate, gate), (layer.W_up, up), (layer.W_down, down)):
parameter.copy_(value)
layer.gate.normal_(std=1 / 16)
flat_x = x.reshape(tokens, -1)
def native_dispatch():
x_heads = flat_x.view(tokens, args.heads, 256)
probs, idx = layer._route(x_heads)
gather, weights, offsets = moe.global_sort_routes(probs, idx, args.experts)
return moe.triton_mh_moe_forward(
x_heads,
gather,
weights,
offsets,
layer.W_gate,
layer.W_up,
layer.W_down,
deterministic=args.native_deterministic,
).reshape(tokens, -1)
calls = {"native_dispatch": native_dispatch, "fused_dispatch": lambda: layer(flat_x)}
torch.testing.assert_close(calls["fused_dispatch"](), calls["native_dispatch"](), rtol=2e-2, atol=0.5)
timings = measure(torch, calls, args)
names = list(calls)
result = {"status": "pass", "tokens": tokens, "mode": args.mode, "correctness": errors, "timings": timings}
result["speedup_p50_wall"] = (
timings[names[0]]["synchronized_wall"]["p50_us"] / timings[names[1]]["synchronized_wall"]["p50_us"]
)
if args.stages:
route_ids = (indices.int() + torch.arange(args.heads, device=x.device)[:, None, None] * args.experts).reshape(
-1, args.top_k
)
def gems():
for params, kwargs in launches:
moe.invoke_fused_moe_bf16(*params, **kwargs)
result["standalone_stages_not_additive"] = measure(
torch,
{
"reference_sort": lambda: moe.global_sort_routes(probabilities, indices, args.experts),
"candidate_align": lambda: moe._align_bf16_routes(route_ids, args.heads * args.experts, 128),
"candidate_pack_w13": lambda: torch.stack((gate.transpose(1, 2), up.transpose(1, 2)), dim=2).reshape(
args.heads * args.experts, 2560, 256
),
"candidate_two_gemms_prepared": gems,
},
args,
)
return result
def main():
args = parse_args()
import torch
if hasattr(torch.version, "musa") and torch.version.musa is not None:
importlib.import_module("torchada") # before importing vLLM / capturing CUDA APIs
cuda = getattr(torch, "cuda")
has_gpu = cuda.is_available() or (
hasattr(torch.version, "musa") and torch.version.musa is not None and torch.musa.is_available()
)
if not has_gpu:
print(json.dumps({"status": "skip", "reason": "No visible CUDA/MUSA device"}))
return
import vllm_omni.diffusion.models.magi2.fused_moe_kernels as kernels
import vllm_omni.diffusion.models.magi2.mh_moe as moe
root = Path(__file__).resolve().parents[2]
try:
sha = subprocess.check_output(
["git", "-C", str(root), "rev-parse", "HEAD"], text=True, stderr=subprocess.DEVNULL
).strip()
except (OSError, subprocess.CalledProcessError):
sha = os.environ.get("MAGI2_BENCH_SOURCE_SHA", "unknown")
report = {
"device": cuda.get_device_name(),
"sm_count": cuda.get_device_properties(0).multi_processor_count,
"source_sha": sha,
"module_path": moe.__file__,
"torch": torch.__version__,
"args": {**vars(args), "output": str(args.output)},
"cache": "512 MiB flush outside each region",
"capture": "eager; JIT warmed; no graph/compile",
"results": [],
}
with torch.inference_mode():
for tokens in args.tokens:
result = benchmark(torch, moe, kernels, tokens, args)
report["results"].append(result)
print(json.dumps(result), flush=True)
if args.output:
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(json.dumps(report, indent=2) + "\n")
if __name__ == "__main__":
main()
@@ -0,0 +1,90 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""CPU contracts for the benchmark, independent of GPU availability."""
import os
from types import SimpleNamespace
import pytest
from benchmarks.kernels import benchmark_magi2_bf16_moe as bench
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
def test_shape_sweep_and_cli_validation():
assert bench.parse_args(["--tokens", "2", "10000"]).tokens == [2, 10000]
for args in (["--iters", "0"], ["--warmup", "-1"], ["--top-k", "257"], ["--tokens", "0"]):
with pytest.raises(SystemExit):
bench.parse_args(args)
@pytest.mark.parametrize("values", [[], [0], [-1], [float("nan")], [float("inf")]])
def test_invalid_timings_are_not_reported(values):
with pytest.raises(ValueError):
bench.stats(values)
def test_stats_preserve_all_samples():
result = bench.stats([1.0, 4.0, 2.0, 3.0])
assert result["p50_us"] == 2.5
assert result["p90_us"] == 4.0
assert result["samples_us"] == [1.0, 4.0, 2.0, 3.0]
def test_switch_restores_environment_on_error(monkeypatch):
monkeypatch.setenv("MAGI2_DETERMINISTIC", "1")
with pytest.raises(RuntimeError), bench.env_switch(True):
assert os.environ["MAGI2_DETERMINISTIC"] == "0"
raise RuntimeError("compile failure")
assert os.environ["MAGI2_DETERMINISTIC"] == "1"
def test_routed_reference_sort_is_inside_every_timed_call(monkeypatch):
data = (SimpleNamespace(device="device"), *(object() for _ in range(5)))
events = []
monkeypatch.setattr(bench, "make_inputs", lambda *args: data)
monkeypatch.setattr(bench, "check_and_capture", lambda *args: ([], []))
def sort(*args):
assert args == (data[1], data[2], 256)
events.append("sort")
return (1, 2, 3)
def legacy(*args, **kwargs):
assert kwargs == {"deterministic": True}
events.append("reference")
def candidate(*args):
assert args == (data[0], data[1], data[2], "packed-w13", data[5], "route-buffers")
events.append("candidate")
def measure(torch, calls, args):
assert events == ["pack", "allocate"]
events.clear()
for _ in range(2):
for call in calls.values():
call()
return {name: {"synchronized_wall": {"p50_us": 1}} for name in calls}
monkeypatch.setattr(bench, "measure", measure)
def pack_w13(*args):
events.append("pack")
return "packed-w13"
def allocate_route_buffers(*args):
events.append("allocate")
return "route-buffers"
moe = SimpleNamespace(
global_sort_routes=sort,
triton_mh_moe_forward=legacy,
_bf16_fused_moe_forward=candidate,
_pack_bf16_w13=pack_w13,
_allocate_bf16_route_buffers=allocate_route_buffers,
)
result = bench.benchmark(None, moe, None, 2, bench.parse_args(["--mode", "routed"]))
assert events == ["sort", "reference", "candidate"] * 2
assert result["mode"] == "routed"
assert "env_off" not in str(result["timings"])
@@ -0,0 +1,496 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
from types import SimpleNamespace
from unittest.mock import Mock
import pytest
import torch
import vllm_omni.diffusion.models.magi2.mh_moe as moe
from vllm_omni.diffusion.models.magi2 import fused_moe_kernels
from vllm_omni.diffusion.models.magi2.parallel import Magi2ParallelGroup
from vllm_omni.platforms import current_omni_platform
pytestmark = [pytest.mark.diffusion, pytest.mark.core_model]
def _moe_inputs(
num_tokens: int,
num_heads: int,
*,
hidden_size: int = 7,
intermediate_size: int = 9,
num_experts: int = 4,
top_k: int = 2,
seed: int = 419,
):
generator = torch.Generator().manual_seed(seed)
x = torch.randn(num_tokens, num_heads, hidden_size, generator=generator).to(torch.bfloat16)
shape = (num_heads * num_experts, hidden_size, intermediate_size)
gate = (torch.randn(shape, generator=generator) * 0.3).to(torch.bfloat16)
up = (torch.randn(shape, generator=generator) * 0.7 - 0.5).to(torch.bfloat16)
down = torch.randn(num_heads * num_experts, intermediate_size, hidden_size, generator=generator)
down = (down * 0.2).to(torch.bfloat16)
# Repeat head-local IDs so different heads must use different expert banks.
# Experts 1 and 3 have no routes, exercising empty experts and padding.
selected = torch.tensor([0, 2]) if top_k == 2 else torch.arange(top_k)
indices = selected.expand(num_heads, num_tokens, top_k).contiguous()
probabilities = torch.rand(num_heads, num_tokens, top_k, generator=generator)
probabilities = (probabilities / probabilities.sum(-1, keepdim=True)).contiguous()
return x, probabilities, indices, gate, up, down
def _reference_forward(x, probabilities, indices, gate, up, down):
"""FP32 fused-expert math with BF16 activation and weighted-route stores."""
num_tokens, num_heads, _ = x.shape
experts_per_head = gate.shape[0] // num_heads
output = torch.zeros_like(x)
for head in range(num_heads):
for token in range(num_tokens):
routes = []
for choice in range(indices.shape[-1]):
expert = head * experts_per_head + int(indices[head, token, choice])
gate_value = (x[token, head].float() @ gate[expert].float()).clamp(max=7)
up_value = (x[token, head].float() @ up[expert].float()).clamp(-7, 7)
hidden = (gate_value * torch.sigmoid(1.702 * gate_value) * (up_value + 1)).to(x.dtype)
down_value = hidden.float() @ down[expert].float()
routes.append((down_value * probabilities[head, token, choice]).to(x.dtype))
output[token, head] = torch.stack(routes).sum(0)
return output
def _assert_call_args_identity(call_args, *expected):
assert len(call_args.args) == len(expected)
assert all(actual is wanted for actual, wanted in zip(call_args.args, expected))
@pytest.mark.cpu
def test_reference_keeps_fp32_gate_up_until_activation():
x = torch.ones(1, 1, 2, dtype=torch.bfloat16)
probabilities = torch.ones(1, 1, 1)
indices = torch.zeros(1, 1, 1, dtype=torch.long)
weight = torch.tensor([[[1.0], [2**-8]]], dtype=torch.bfloat16)
down = torch.ones(1, 1, 2, dtype=torch.bfloat16)
actual = _reference_forward(x, probabilities, indices, weight, weight, down)
projection = torch.tensor(1.0 + 2**-8)
expected = (projection * torch.sigmoid(1.702 * projection) * (projection + 1)).to(x.dtype)
rounded = projection.to(x.dtype).float()
early_rounded = (rounded * torch.sigmoid(1.702 * rounded) * (rounded + 1)).to(x.dtype)
torch.testing.assert_close(actual, expected.expand_as(actual), rtol=0, atol=0)
assert not torch.equal(actual, early_rounded.expand_as(actual))
def _torch_invoke(a, b, c, weights, sorted_ids, expert_ids, num_padded, *, top_k, config, fuse_swiglu):
"""Execute the launcher's route contract with Torch instead of Triton."""
route_count = weights.numel()
output = c.view(route_count, c.shape[-1])
block_size = config["BLOCK_SIZE_M"]
live_rows = int(num_padded.item())
live_blocks = live_rows // block_size
assert sorted_ids.numel() >= live_rows
assert expert_ids.numel() * block_size == sorted_ids.numel()
written = []
for block, expert in enumerate(expert_ids[:live_blocks].tolist()):
assert 0 <= expert < b.shape[0]
routes = sorted_ids[block * block_size : (block + 1) * block_size].long()
routes = routes[routes < route_count]
written.extend(routes.tolist())
gemm = a[routes // top_k].float() @ b[expert].float().T
if fuse_swiglu:
gate = gemm[:, 0::2].clamp(max=7)
up = gemm[:, 1::2].clamp(-7, 7)
result = gate * torch.sigmoid(1.702 * gate) * (up + 1)
else:
result = gemm * weights.reshape(-1)[routes, None]
output[routes] = result.to(c.dtype)
assert sorted(written) == list(range(route_count))
@pytest.mark.cpu
@pytest.mark.parametrize("route_ids", [[], [2, 0, 2, 0, 2], [3] * 9 + [0, 3, 0]])
def test_align_bf16_routes_preserves_routes_and_pads_expert_blocks(route_ids):
ids = torch.tensor(route_ids, dtype=torch.int64)
sorted_ids, expert_ids, num_padded = moe._align_bf16_routes(ids, num_experts=5, block_size=4)
counts = torch.bincount(ids, minlength=5)
expected_blocks = ((counts + 3) // 4).sum().item()
assert num_padded.tolist() == [expected_blocks * 4]
assert expert_ids.numel() >= expected_blocks
assert sorted_ids.dtype == expert_ids.dtype == num_padded.dtype == torch.int32
live_rows = int(num_padded.item())
assert sorted_ids.numel() >= live_rows
valid_ids = sorted_ids[:live_rows][sorted_ids[:live_rows] < ids.numel()].long()
assert sorted(valid_ids.tolist()) == list(range(ids.numel()))
for block, expert in enumerate(expert_ids[:expected_blocks].tolist()):
block_routes = sorted_ids[block * 4 : (block + 1) * 4]
valid = block_routes[block_routes < ids.numel()].long()
assert torch.all(ids[valid] == expert)
assert torch.all(block_routes[block_routes >= ids.numel()] == ids.numel())
@pytest.mark.cpu
@pytest.mark.parametrize(("num_tokens", "num_heads"), [(0, 3), (1, 1), (5, 2), (7, 3)])
def test_bf16_forward_head_offsets_and_interleaved_weights(monkeypatch, num_tokens, num_heads):
inputs = _moe_inputs(num_tokens, num_heads)
calls = []
def invoke(*args, **kwargs):
calls.append(kwargs["fuse_swiglu"])
return _torch_invoke(*args, **kwargs)
monkeypatch.setattr(moe, "invoke_fused_moe_bf16", invoke)
x, probabilities, indices, gate, up, down = inputs
actual = moe._bf16_fused_moe_forward(x, probabilities, indices, moe._pack_bf16_w13(gate, up), down)
expected = _reference_forward(*inputs)
assert actual.shape == inputs[0].shape
assert actual.dtype == torch.bfloat16
if num_tokens:
assert calls == [True, False]
torch.testing.assert_close(actual, expected, rtol=0, atol=0)
@pytest.mark.cpu
@pytest.mark.parametrize(
("device_type", "dtype", "deterministic", "num_tokens", "is_musa", "expected_path"),
[
("cuda", torch.bfloat16, None, 1, False, "triton"),
("cuda", torch.bfloat16, "0", 4096, False, "bf16"),
("cuda", torch.bfloat16, "1", 4096, False, "triton"),
("cuda", torch.float16, None, 4096, False, "triton"),
("cuda", torch.float32, None, 4096, False, "triton"),
("cuda", torch.bfloat16, "0", 1, True, "bf16"),
("cpu", torch.bfloat16, None, 1, False, "torch"),
],
)
def test_local_forward_dispatch_contract(
monkeypatch,
device_type,
dtype,
deterministic,
num_tokens,
is_musa,
expected_path,
):
config = moe.Magi2MultiHeadMoEConfig(
hidden_size=4,
num_heads=1,
num_experts=2,
top_k=1,
expert_intermediate_size=4,
params_dtype=torch.bfloat16,
)
layer = moe.Magi2MultiHeadMoE(config, ep_group=Magi2ParallelGroup(None, 1, 0))
if device_type == "cpu":
x_heads = torch.randn(num_tokens, 1, 4, dtype=dtype)
else:
# Accelerator metadata exercises dispatch without allocating on a GPU.
x_heads = SimpleNamespace(
device=SimpleNamespace(type=device_type),
dtype=dtype,
is_cuda=True,
shape=(num_tokens, 1, 4),
)
probabilities = torch.tensor([[[1.0]]])
indices = torch.tensor([[[0]]])
route = Mock(return_value=(probabilities, indices))
gather_ids, sorted_probs, offsets = object(), object(), object()
sort = Mock(return_value=(gather_ids, sorted_probs, offsets))
fast = Mock(return_value="bf16-output")
route_buffers = object()
torch_kernel = Mock(return_value="torch-output")
triton_kernel = Mock(return_value="triton-output")
monkeypatch.setattr(layer, "_route", route)
monkeypatch.setattr(moe, "global_sort_routes", sort)
monkeypatch.setattr(moe, "_bf16_fused_moe_forward", fast)
monkeypatch.setattr(layer, "_get_bf16_route_buffers", Mock(return_value=route_buffers))
monkeypatch.setattr(moe, "torch_mh_moe_forward", torch_kernel)
monkeypatch.setattr(moe, "triton_mh_moe_forward", triton_kernel)
monkeypatch.setattr(moe.current_omni_platform, "is_musa", Mock(return_value=is_musa))
if deterministic is None:
monkeypatch.delenv("MAGI2_DETERMINISTIC", raising=False)
else:
monkeypatch.setenv("MAGI2_DETERMINISTIC", deterministic)
actual = layer._local_forward(x_heads)
route.assert_called_once()
_assert_call_args_identity(route.call_args, x_heads)
if expected_path == "bf16":
assert actual == "bf16-output"
fast.assert_called_once()
_assert_call_args_identity(
fast.call_args,
x_heads,
probabilities,
indices,
layer._get_bf16_packed_w13(),
layer.W_down,
route_buffers,
)
assert fast.call_args.kwargs == {}
sort.assert_not_called()
torch_kernel.assert_not_called()
triton_kernel.assert_not_called()
else:
sort.assert_called_once()
_assert_call_args_identity(sort.call_args, probabilities, indices, layer.num_experts)
fast.assert_not_called()
if expected_path == "torch":
assert actual == "torch-output"
torch_kernel.assert_called_once()
_assert_call_args_identity(
torch_kernel.call_args,
x_heads,
gather_ids,
sorted_probs,
offsets,
layer.W_gate,
layer.W_up,
layer.W_down,
)
triton_kernel.assert_not_called()
else:
assert actual == "triton-output"
triton_kernel.assert_called_once()
_assert_call_args_identity(
triton_kernel.call_args,
x_heads,
gather_ids,
sorted_probs,
offsets,
layer.W_gate,
layer.W_up,
layer.W_down,
)
assert triton_kernel.call_args.kwargs == {
"deterministic": deterministic == "1",
}
torch_kernel.assert_not_called()
@pytest.mark.cpu
def test_local_forward_fast_path_propagates_kernel_failure(monkeypatch):
config = moe.Magi2MultiHeadMoEConfig(
hidden_size=4,
num_heads=1,
num_experts=2,
top_k=1,
expert_intermediate_size=4,
params_dtype=torch.bfloat16,
)
layer = moe.Magi2MultiHeadMoE(config, ep_group=Magi2ParallelGroup(None, 1, 0))
x_heads = SimpleNamespace(
device=SimpleNamespace(type="cuda"),
dtype=torch.bfloat16,
is_cuda=True,
# The fused route is selected for CUDA/MUSA only once the token count
# reaches the production fast-path threshold.
shape=(4096, 1, 4),
)
probabilities = torch.tensor([[[1.0]]])
indices = torch.tensor([[[0]]])
monkeypatch.setattr(layer, "_route", Mock(return_value=(probabilities, indices)))
sort = Mock()
torch_kernel = Mock()
triton_kernel = Mock()
error = RuntimeError("BF16 kernel failed")
fast = Mock(side_effect=error)
monkeypatch.setattr(moe, "global_sort_routes", sort)
monkeypatch.setattr(moe, "_bf16_fused_moe_forward", fast)
monkeypatch.setattr(layer, "_get_bf16_route_buffers", Mock(return_value=object()))
monkeypatch.setattr(moe, "torch_mh_moe_forward", torch_kernel)
monkeypatch.setattr(moe, "triton_mh_moe_forward", triton_kernel)
monkeypatch.delenv("MAGI2_DETERMINISTIC", raising=False)
with pytest.raises(RuntimeError, match="BF16 kernel failed") as exc_info:
layer._local_forward(x_heads)
assert exc_info.value is error
fast.assert_called_once()
sort.assert_not_called()
torch_kernel.assert_not_called()
triton_kernel.assert_not_called()
@pytest.mark.cpu
def test_bf16_weight_and_route_caches_are_reused_and_invalidated():
config = moe.Magi2MultiHeadMoEConfig(
hidden_size=4,
num_heads=1,
num_experts=2,
top_k=1,
expert_intermediate_size=4,
params_dtype=torch.bfloat16,
)
layer = moe.Magi2MultiHeadMoE(config, ep_group=Magi2ParallelGroup(None, 1, 0))
with torch.no_grad():
layer.W_gate.fill_(0.25)
layer.W_up.fill_(-0.5)
first = layer._get_bf16_packed_w13()
assert layer._get_bf16_packed_w13() is first
with torch.no_grad():
layer.W_gate.add_(1)
second = layer._get_bf16_packed_w13()
assert second is not first
torch.testing.assert_close(second, moe._pack_bf16_w13(layer.W_gate, layer.W_up))
buffers = layer._get_bf16_route_buffers(8, layer.W_gate.device)
assert layer._get_bf16_route_buffers(8, layer.W_gate.device) is buffers
assert layer._get_bf16_route_buffers(257, layer.W_gate.device) is not buffers
def _gpu_device(device_type: str) -> torch.device:
if device_type == "musa":
if getattr(torch.version, "musa", None) is None or not hasattr(torch, "musa") or not torch.musa.is_available():
pytest.skip("MUSA runtime and device required")
elif not current_omni_platform.is_cuda() or current_omni_platform.get_device_count() == 0:
pytest.skip("CUDA runtime and device required")
return torch.device(device_type)
@pytest.mark.parametrize(
"device_type",
[
pytest.param("cuda", marks=[pytest.mark.cuda, pytest.mark.gpu]),
pytest.param("musa", marks=[pytest.mark.musa, pytest.mark.gpu]),
],
)
@pytest.mark.parametrize(
("num_tokens", "num_heads", "hidden_size", "intermediate_size", "top_k", "num_experts"),
[
(1, 1, 64, 80, 2, 4),
(17, 3, 64, 80, 2, 4),
(137, 2, 64, 80, 2, 4),
(2, 3, 256, 1280, 6, 8),
(129, 1, 256, 1280, 6, 8),
],
)
def test_bf16_forward_real_kernel_parity(
device_type, num_tokens, num_heads, hidden_size, intermediate_size, top_k, num_experts
):
device = _gpu_device(device_type)
inputs = _moe_inputs(
num_tokens,
num_heads,
hidden_size=hidden_size,
intermediate_size=intermediate_size,
top_k=top_k,
num_experts=num_experts,
)
expected = _reference_forward(*inputs)
x, probabilities, indices, gate, up, down = (tensor.to(device) for tensor in inputs)
actual = moe._bf16_fused_moe_forward(x, probabilities, indices, moe._pack_bf16_w13(gate, up), down).cpu()
assert torch.isfinite(actual).all()
# BF16 activation/output rounding and cancellation across routes amplify
# small arithmetic differences. Bound both individual and aggregate error;
# retain the tighter absolute tolerance for the small masked-tile cases.
atol = 5e-1 if intermediate_size == 1280 else 4e-2
torch.testing.assert_close(actual, expected, rtol=2e-2, atol=atol)
relative_l2 = (actual.float() - expected.float()).norm() / expected.float().norm().clamp_min(1e-6)
assert relative_l2 < 1e-3
@pytest.mark.parametrize(
"device_type",
[pytest.param("cuda", marks=pytest.mark.cuda), pytest.param("musa", marks=pytest.mark.musa)],
)
@pytest.mark.parametrize("seed", [419, 420])
@pytest.mark.parametrize(
("num_tokens", "num_heads", "hidden_size", "intermediate_size", "top_k", "num_experts"),
[
(1, 1, 64, 96, 2, 4),
(17, 3, 64, 96, 2, 4),
(137, 2, 64, 96, 2, 4),
(2, 3, 256, 1280, 6, 8),
(129, 1, 256, 1280, 6, 8),
],
)
def test_bf16_forward_matches_reference_routes_and_fp32_reduction(
monkeypatch, device_type, seed, num_tokens, num_heads, hidden_size, intermediate_size, top_k, num_experts
):
"""Compare expert math and FP32 reduction, not BF16 scatter accumulation."""
device = _gpu_device(device_type)
inputs = _moe_inputs(
num_tokens,
num_heads,
hidden_size=hidden_size,
intermediate_size=intermediate_size,
top_k=top_k,
num_experts=num_experts,
seed=seed,
)
x, probabilities, indices, gate, up, down = (tensor.to(device) for tensor in inputs)
gather_ids, sorted_probs, offsets = moe.global_sort_routes(probabilities, indices, num_experts)
reference_routes, new_routes = [], []
original_scatter = fused_moe_kernels._deterministic_scatter
original_invoke = moe.invoke_fused_moe_bf16
def capture_reference(sorted_output, *args):
reference_routes.append(sorted_output.detach().cpu())
return original_scatter(sorted_output, *args)
def capture_new(*args, **kwargs):
result = original_invoke(*args, **kwargs)
if not kwargs.get("fuse_swiglu", False):
new_routes.append(args[2].detach().cpu().reshape(-1, hidden_size))
return result
monkeypatch.setattr(fused_moe_kernels, "_deterministic_scatter", capture_reference)
monkeypatch.setattr(moe, "invoke_fused_moe_bf16", capture_new)
# Capture before BF16 scatter; this path intentionally reduces in FP32.
moe.triton_mh_moe_forward(x, gather_ids, sorted_probs, offsets, gate, up, down, deterministic=True)
actual = moe._bf16_fused_moe_forward(x, probabilities, indices, moe._pack_bf16_w13(gate, up), down).cpu()
assert len(reference_routes) == len(new_routes) == 1
flat_experts = (inputs[2] + torch.arange(num_heads).view(-1, 1, 1) * num_experts).reshape(-1)
order = flat_experts.argsort(stable=True)
new_sorted = new_routes[0].index_select(0, order)
scatter_ids = gather_ids.cpu().long() * num_heads + flat_experts[order] // num_experts
expected_fp32 = torch.zeros(num_tokens * num_heads, hidden_size, dtype=torch.float32)
expected_fp32.index_add_(0, scatter_ids, reference_routes[0].float())
expected = expected_fp32.to(x.dtype).view(num_tokens, num_heads, hidden_size)
atol = 5e-1 if intermediate_size == 1280 else 4e-2
for result, reference in ((new_sorted, reference_routes[0]), (actual, expected)):
assert torch.isfinite(result).all() and torch.isfinite(reference).all()
torch.testing.assert_close(result, reference, rtol=2e-2, atol=atol)
relative_l2 = (result.float() - reference.float()).norm() / reference.float().norm().clamp_min(1e-6)
assert relative_l2 < 1e-3
@pytest.mark.parametrize(
"device_type",
[
pytest.param("cuda", marks=[pytest.mark.cuda, pytest.mark.gpu]),
pytest.param("musa", marks=[pytest.mark.musa, pytest.mark.gpu]),
],
)
def test_bf16_reference_atomic_add_capability(device_type):
"""The non-deterministic BF16 reference path must compile on each GPU.
This is intentionally separate from candidate parity: it exercises the
``deterministic=False`` branch and therefore the ``tl.atomic_add`` used by
the original MAGI-2 reference path. An unsupported backend must fail the
test with its compiler error instead of being silently treated as a skip.
"""
device = _gpu_device(device_type)
if device_type == "musa":
version = tuple(int(part) for part in fused_moe_kernels.triton.__version__.split(".")[:2])
if version < (3, 6):
pytest.skip("Triton-MUSA < 3.6 does not support BF16 atomic_add")
inputs = _moe_inputs(
num_tokens=2,
num_heads=1,
hidden_size=64,
intermediate_size=96,
top_k=2,
num_experts=4,
seed=419,
)
x, probabilities, indices, gate, up, down = (tensor.to(device) for tensor in inputs)
gather_ids, sorted_probs, offsets = moe.global_sort_routes(probabilities, indices, 4)
actual = moe.triton_mh_moe_forward(x, gather_ids, sorted_probs, offsets, gate, up, down, deterministic=False)
deterministic = moe.triton_mh_moe_forward(x, gather_ids, sorted_probs, offsets, gate, up, down, deterministic=True)
assert torch.isfinite(actual).all()
torch.testing.assert_close(actual, deterministic, rtol=2e-2, atol=4e-2)
@@ -0,0 +1,123 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
import pytest
import torch
import vllm_omni.diffusion.models.magi2.fused_moe_kernels as kernels
pytestmark = [pytest.mark.diffusion, pytest.mark.core_model, pytest.mark.cpu]
CONFIG = {
"BLOCK_SIZE_M": 128,
"BLOCK_SIZE_N": 128,
"BLOCK_SIZE_K": 64,
"GROUP_SIZE_M": 16,
"num_warps": 16,
"num_stages": 1,
}
class _LaunchProbe:
def __init__(self):
self.calls = []
def __getitem__(self, grid):
def launch(*args, **kwargs):
self.calls.append((grid, args, kwargs))
return launch
def _inputs(*, gate_up: bool):
route_weights = torch.ones((2, 6), dtype=torch.float32)
if gate_up:
A = torch.zeros((2, 4), dtype=torch.bfloat16)
B = torch.zeros((2, 8, 4), dtype=torch.bfloat16)
C = torch.empty((12, 4), dtype=torch.bfloat16)
top_k = 6
else:
A = torch.zeros((12, 4), dtype=torch.bfloat16)
B = torch.zeros((2, 4, 4), dtype=torch.bfloat16)
C = torch.empty((2, 6, 4), dtype=torch.bfloat16)
top_k = 1
sorted_ids = torch.zeros(128, dtype=torch.int32)
expert_ids = torch.zeros(1, dtype=torch.int32)
padded = torch.tensor([128], dtype=torch.int32)
return A, B, C, route_weights, sorted_ids, expert_ids, padded, top_k
@pytest.mark.parametrize(
("gate_up", "fuse_swiglu", "mul_weight"),
[(True, True, False), (False, False, True)],
)
def test_bf16_launch_contract(monkeypatch, gate_up, fuse_swiglu, mul_weight):
probe = _LaunchProbe()
monkeypatch.setattr(kernels, "_fused_moe_bf16_kernel", probe)
args = _inputs(gate_up=gate_up)
kernels.invoke_fused_moe_bf16(*args[:-1], top_k=args[-1], config=CONFIG, fuse_swiglu=fuse_swiglu)
assert len(probe.calls) == 1
_, launch_args, launch_kwargs = probe.calls[0]
assert launch_args[0].dtype == torch.bfloat16
assert launch_kwargs["compute_type"] == kernels.tl.bfloat16
assert launch_kwargs["FUSE_SWIGLU"] is fuse_swiglu
assert launch_kwargs["MUL_ROUTED_WEIGHT"] is mul_weight
assert launch_kwargs["SWIGLU_ALPHA"] == (1.702 if fuse_swiglu else 0.0)
assert launch_kwargs["SWIGLU_LIMIT"] == (7.0 if fuse_swiglu else 0.0)
def test_rejects_non_bf16_activation(monkeypatch):
args = list(_inputs(gate_up=True))
args[0] = args[0].float()
with pytest.raises(ValueError, match="requires BF16"):
kernels.invoke_fused_moe_bf16(*args[:-1], top_k=args[-1], config=CONFIG, fuse_swiglu=True)
@pytest.mark.parametrize(
"change",
[
{"BLOCK_SIZE_N": 32},
{"num_warps": 3},
{"num_stages": 5},
{"unexpected": 1},
],
)
def test_rejects_invalid_launch_config(monkeypatch, change):
config = dict(CONFIG)
config.update(change)
args = _inputs(gate_up=True)
with pytest.raises(ValueError, match="invalid MAGI-2 BF16 MoE launch config"):
kernels.invoke_fused_moe_bf16(*args[:-1], top_k=args[-1], config=config, fuse_swiglu=True)
def test_rejects_incompatible_swiglu_output():
args = list(_inputs(gate_up=True))
args[2] = torch.empty((12, 8), dtype=torch.bfloat16)
with pytest.raises(ValueError, match="output"):
kernels.invoke_fused_moe_bf16(*args[:-1], top_k=args[-1], config=CONFIG, fuse_swiglu=True)
@pytest.mark.parametrize("gate_up", [True, False])
def test_rejects_wrong_routing_top_k(gate_up):
args = _inputs(gate_up=gate_up)
with pytest.raises(ValueError, match="routing layout"):
kernels.invoke_fused_moe_bf16(*args[:-1], top_k=2, config=CONFIG, fuse_swiglu=gate_up)
@pytest.mark.parametrize(
("argument", "replacement", "message"),
[
(4, torch.zeros(128, dtype=torch.float32), "sorted_token_ids"),
(5, torch.zeros(1, dtype=torch.float32), "expert_ids"),
(6, torch.tensor([128.0]), "num_tokens_post_padded"),
(6, torch.tensor([128, 256]), "num_tokens_post_padded"),
(4, torch.zeros(127, dtype=torch.int32), "block aligned"),
(5, torch.empty(0, dtype=torch.int32), "one expert per padded token block"),
],
)
def test_rejects_invalid_route_metadata(argument, replacement, message):
args = list(_inputs(gate_up=True))
args[argument] = replacement
with pytest.raises(ValueError, match=message):
kernels.invoke_fused_moe_bf16(*args[:-1], top_k=args[-1], config=CONFIG, fuse_swiglu=True)
@@ -0,0 +1,43 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""No vLLM/model/MATE dependency: python -m pytest -q this_file.py."""
import importlib
import pytest
import torch
if hasattr(torch.version, "musa") and torch.version.musa is not None:
importlib.import_module("torchada")
from vllm.triton_utils import tl, triton
pytestmark = [pytest.mark.core_model, pytest.mark.cpu, pytest.mark.musa]
@triton.jit
def _atomic_sum(values, output, count: tl.constexpr, block: tl.constexpr):
offsets = tl.program_id(0) * block + tl.arange(0, block)
increments = tl.load(values + offsets, mask=offsets < count, other=0)
tl.atomic_add(output + offsets % 32, increments, mask=offsets < count)
@pytest.mark.parametrize("dtype", [torch.float32, torch.bfloat16])
def test_atomic_add(dtype):
cuda = getattr(torch, "cuda")
available = cuda.is_available() or (hasattr(torch, "musa") and torch.musa.is_available())
if not available:
pytest.skip("No CUDA/MUSA device")
if dtype == torch.bfloat16 and getattr(torch.version, "musa", None) is not None:
if tuple(int(part) for part in triton.__version__.split(".")[:2]) < (3, 6):
pytest.skip("Triton-MUSA < 3.6 does not support BF16 atomic_add")
# Each destination receives eight exactly representable increments from
# two independent blocks. Reset between runs to exercise real atomics.
values = torch.ones(256, device="cuda", dtype=dtype)
output = torch.zeros(32, device="cuda", dtype=dtype)
for _ in range(5):
output.zero_()
_atomic_sum[(2,)](values, output, 256, 128)
cuda.synchronize()
torch.testing.assert_close(output, torch.full_like(output, 8), rtol=0, atol=0)
@@ -0,0 +1,563 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
# Copyright (c) 2025-2026 SandAI. All Rights Reserved.
# ruff: noqa: N803
"""BF16 routed GEMMs for the MAGI-2 fast path.
The BF16 grouped GEMM is adapted from SGLang's Apache-2.0 implementation at
``d533d3bf464d280f180e18d4d396e613a5998502``. This module implements only
MAGI-2's interleaved gate/up GEMM with SwiGLU7 and routed down GEMM.
The existing SandAI-derived reference kernel is retained below unchanged.
"""
from __future__ import annotations
import math
import torch
from vllm.triton_utils import HAS_TRITON, tl, triton
from vllm_omni.platforms import current_omni_platform
def swiglu7_pair(gate: torch.Tensor, up: torch.Tensor) -> torch.Tensor:
"""Reference SwiGLU7 activation used by the CPU correctness path."""
dtype = gate.dtype
gate = gate.float().clamp(max=7.0)
up = up.float().clamp(min=-7.0, max=7.0)
return (gate * torch.sigmoid(1.702 * gate) * (up + 1.0)).to(dtype)
@triton.jit
def _write_zeros_to_output(
c_ptr,
stride_cm,
stride_cn,
pid_n,
N,
offs_token,
token_mask,
BLOCK_SIZE_M,
BLOCK_SIZE_N,
compute_type,
):
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=compute_type)
offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
c_ptrs = c_ptr + stride_cm * offs_token[:, None] + stride_cn * offs_cn[None, :]
tl.store(c_ptrs, accumulator, mask=token_mask[:, None] & (offs_cn[None, :] < N))
@triton.jit
def _fused_moe_bf16_kernel(
a_ptr,
b_ptr,
c_ptr,
topk_weights_ptr,
sorted_token_ids_ptr,
expert_ids_ptr,
num_tokens_post_padded_ptr,
N,
K,
EM,
num_valid_tokens,
stride_am,
stride_ak,
stride_be,
stride_bk,
stride_bn,
stride_cm,
stride_cn,
BLOCK_SIZE_M: tl.constexpr,
BLOCK_SIZE_N: tl.constexpr,
BLOCK_SIZE_K: tl.constexpr,
GROUP_SIZE_M: tl.constexpr,
MUL_ROUTED_WEIGHT: tl.constexpr,
top_k: tl.constexpr,
compute_type: tl.constexpr,
even_Ks: tl.constexpr,
FUSE_SWIGLU: tl.constexpr,
SWIGLU_ALPHA: tl.constexpr,
SWIGLU_LIMIT: tl.constexpr,
):
# Group program IDs to preserve the original L2 reuse order.
pid = tl.program_id(axis=0)
num_pid_m = tl.cdiv(EM, BLOCK_SIZE_M)
num_pid_n = tl.cdiv(N, BLOCK_SIZE_N)
num_pid_in_group = GROUP_SIZE_M * num_pid_n
group_id = pid // num_pid_in_group
first_pid_m = group_id * GROUP_SIZE_M
group_size_m = min(num_pid_m - first_pid_m, GROUP_SIZE_M)
pid_m = first_pid_m + ((pid % num_pid_in_group) % group_size_m)
pid_n = (pid % num_pid_in_group) // group_size_m
num_tokens_post_padded = tl.load(num_tokens_post_padded_ptr)
if pid_m * BLOCK_SIZE_M >= num_tokens_post_padded:
return
offs_token_id = pid_m * BLOCK_SIZE_M + tl.arange(0, BLOCK_SIZE_M).to(tl.int64)
offs_token = tl.load(sorted_token_ids_ptr + offs_token_id).to(tl.int64)
token_mask = offs_token < num_valid_tokens
off_experts_i32 = tl.load(expert_ids_ptr + pid_m)
off_experts = off_experts_i32.to(tl.int64)
if off_experts == -1:
if FUSE_SWIGLU:
# The filtered down GEMM never reads these half-width rows.
return
_write_zeros_to_output(
c_ptr,
stride_cm,
stride_cn,
pid_n,
N,
offs_token,
token_mask,
BLOCK_SIZE_M,
BLOCK_SIZE_N,
compute_type,
)
return
offs_bn = (pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N).to(tl.int64)) % N
offs_k = tl.arange(0, BLOCK_SIZE_K)
a_ptrs = a_ptr + (offs_token[:, None] // top_k * stride_am + offs_k[None, :] * stride_ak)
b_ptrs = b_ptr + off_experts * stride_be + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
accumulator = tl.zeros((BLOCK_SIZE_M, BLOCK_SIZE_N), dtype=tl.float32)
for k_start in range(0, K, BLOCK_SIZE_K):
if even_Ks:
a = tl.load(a_ptrs, mask=token_mask[:, None], other=0.0)
b = tl.load(b_ptrs)
else:
a = tl.load(
a_ptrs,
mask=token_mask[:, None] & (offs_k[None, :] < K - k_start),
other=0.0,
)
b = tl.load(b_ptrs, mask=offs_k[:, None] < K - k_start, other=0.0)
accumulator += tl.dot(a, b)
a_ptrs += BLOCK_SIZE_K * stride_ak
b_ptrs += BLOCK_SIZE_K * stride_bk
if MUL_ROUTED_WEIGHT:
moe_weight = tl.load(topk_weights_ptr + offs_token, mask=token_mask, other=0)
accumulator *= moe_weight[:, None]
offs_cn = pid_n * BLOCK_SIZE_N + tl.arange(0, BLOCK_SIZE_N)
if FUSE_SWIGLU:
# Keep GEMM accumulators in FP32 through SwiGLU7.
acc_pairs = tl.reshape(accumulator, (BLOCK_SIZE_M, BLOCK_SIZE_N // 2, 2))
gate, up = tl.split(acc_pairs)
gate_c = tl.minimum(gate, SWIGLU_LIMIT)
up_c = tl.maximum(tl.minimum(up, SWIGLU_LIMIT), -SWIGLU_LIMIT)
out_act = (gate_c * tl.sigmoid(SWIGLU_ALPHA * gate_c) * (up_c + 1.0)).to(compute_type)
offs_half = pid_n * (BLOCK_SIZE_N // 2) + tl.arange(0, BLOCK_SIZE_N // 2)
c_ptrs = c_ptr + stride_cm * offs_token[:, None] + stride_cn * offs_half[None, :]
tl.store(c_ptrs, out_act, mask=token_mask[:, None] & (offs_half[None, :] < N // 2))
else:
c_ptrs = c_ptr + stride_cm * offs_token[:, None] + stride_cn * offs_cn[None, :]
tl.store(c_ptrs, accumulator.to(compute_type), mask=token_mask[:, None] & (offs_cn[None, :] < N))
def invoke_fused_moe_bf16(
A: torch.Tensor,
B: torch.Tensor,
C: torch.Tensor,
topk_weights: torch.Tensor,
sorted_token_ids: torch.Tensor,
expert_ids: torch.Tensor,
num_tokens_post_padded: torch.Tensor,
*,
top_k: int,
config: dict[str, int],
fuse_swiglu: bool,
) -> None:
"""Launch one BF16 MAGI-2 GEMM in the original route order.
Gate/up calls use interleaved ``B=[experts, 2*intermediate, hidden]`` and
retain FP32 accumulators through SwiGLU7 before writing BF16
``C=[routes, intermediate]``. Down calls use ``B=[experts, hidden,
intermediate]`` and write ``C=[tokens, router_top_k, hidden]``. The latter
multiplies routing weights in FP32 before the BF16 output cast.
"""
if any(t.dtype != torch.bfloat16 for t in (A, B, C)):
raise ValueError("MAGI-2 fused MoE requires BF16 activations, weights, and output")
if A.ndim != 2 or B.ndim != 3 or topk_weights.ndim != 2:
raise ValueError("MAGI-2 fused MoE expects A [tokens, K], B [experts, N, K], and 2D routing weights")
if not isinstance(fuse_swiglu, bool) or not isinstance(top_k, int) or top_k < 1:
raise ValueError("fuse_swiglu must be a bool and top_k must be a positive integer")
expected_top_k = topk_weights.shape[1] if fuse_swiglu else 1
if top_k != expected_top_k or A.shape[0] * top_k != topk_weights.numel():
raise ValueError("top_k and activation rows do not match the gate/up or down routing layout")
if B.shape[1] == 0 or B.shape[2] == 0 or A.shape[1] != B.shape[2]:
raise ValueError("MAGI-2 fused MoE requires matching nonempty GEMM dimensions")
if fuse_swiglu and B.shape[1] % 2:
raise ValueError("SwiGLU7 requires an even number of interleaved gate/up rows")
expected_shape = (topk_weights.numel(), B.shape[1] // 2) if fuse_swiglu else (*topk_weights.shape, B.shape[1])
if C.shape != expected_shape or not C.is_contiguous():
raise ValueError("MAGI-2 fused MoE output must have the contiguous routed output layout")
if not topk_weights.is_floating_point() or not topk_weights.is_contiguous():
raise ValueError("routing weights must be a contiguous floating-point tensor")
for name, tensor in (
("sorted_token_ids", sorted_token_ids),
("expert_ids", expert_ids),
):
if tensor.ndim != 1 or not tensor.is_contiguous() or tensor.dtype not in (torch.int32, torch.int64):
raise ValueError(f"{name} must be a contiguous 1D integer tensor")
if num_tokens_post_padded.numel() != 1 or num_tokens_post_padded.dtype not in (
torch.int32,
torch.int64,
):
raise ValueError("num_tokens_post_padded must contain one integer")
tensors = (B, C, topk_weights, sorted_token_ids, expert_ids, num_tokens_post_padded)
if any(t.device != A.device for t in tensors):
raise ValueError("MAGI-2 fused MoE tensors must share a device")
supported_config = {
"BLOCK_SIZE_M": (128,),
"BLOCK_SIZE_N": (64, 128, 256, 512),
"BLOCK_SIZE_K": (16, 32, 64, 128),
"GROUP_SIZE_M": (16,),
"num_warps": (4, 8, 16, 32),
"num_stages": (1, 2, 3, 4),
}
if config.keys() != supported_config.keys() or any(
type(config[key]) is not int or config[key] not in values for key, values in supported_config.items()
):
raise ValueError("invalid MAGI-2 BF16 MoE launch config")
if expert_ids.numel() < triton.cdiv(sorted_token_ids.numel(), config["BLOCK_SIZE_M"]):
raise ValueError("expert_ids must contain one expert per padded token block")
if sorted_token_ids.numel() % config["BLOCK_SIZE_M"]:
raise ValueError("sorted_token_ids capacity must be block aligned")
def grid(meta: dict[str, int]) -> tuple[int]:
return (
triton.cdiv(sorted_token_ids.shape[0], meta["BLOCK_SIZE_M"])
* triton.cdiv(B.shape[1], meta["BLOCK_SIZE_N"]),
)
_fused_moe_bf16_kernel[grid](
A,
B,
C,
topk_weights,
sorted_token_ids,
expert_ids,
num_tokens_post_padded,
B.shape[1],
B.shape[2],
sorted_token_ids.shape[0],
topk_weights.numel(),
A.stride(0),
A.stride(1),
B.stride(0),
B.stride(2),
B.stride(1),
C.stride(-2),
C.stride(-1),
MUL_ROUTED_WEIGHT=not fuse_swiglu,
top_k=top_k,
compute_type=tl.bfloat16,
even_Ks=B.shape[2] % config["BLOCK_SIZE_K"] == 0,
FUSE_SWIGLU=fuse_swiglu,
SWIGLU_ALPHA=1.702 if fuse_swiglu else 0.0,
SWIGLU_LIMIT=7.0 if fuse_swiglu else 0.0,
**config,
)
def global_sort_routes(
topk_probs: torch.Tensor,
topk_indices: torch.Tensor,
num_experts: int,
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
"""Convert per-head routes into a stable flattened-expert CSR layout."""
if topk_probs.shape != topk_indices.shape or topk_indices.ndim != 3:
raise ValueError("top-k probabilities and indices must have the same [H,S,K] shape")
heads, sequence, top_k = topk_indices.shape
device = topk_indices.device
head_offset = torch.arange(heads, device=device).view(heads, 1, 1) * num_experts
flattened_experts = (topk_indices + head_offset).reshape(-1)
flat_probs = topk_probs.reshape(-1)
flat_tokens = torch.arange(sequence, device=device).view(1, sequence, 1).expand(heads, sequence, top_k).reshape(-1)
order = flattened_experts.argsort(stable=True)
gather_ids = flat_tokens[order].to(torch.int32)
sorted_probs = flat_probs[order].float()
counts = torch.bincount(flattened_experts, minlength=heads * num_experts)
offsets = torch.zeros(heads * num_experts + 1, device=device, dtype=torch.long)
offsets[1:] = counts.cumsum(0)
return gather_ids, sorted_probs, offsets
def torch_mh_moe_forward(
x: torch.Tensor,
gather_ids: torch.Tensor,
probs: torch.Tensor,
expert_offsets: torch.Tensor,
w_gate: torch.Tensor,
w_up: torch.Tensor,
w_down: torch.Tensor,
) -> torch.Tensor:
"""Small-shape correctness oracle for the fused expert kernel."""
if x.ndim != 3:
raise ValueError("multi-head MoE input must be [tokens,heads,head_dim]")
output = torch.zeros_like(x)
experts_per_head = (expert_offsets.numel() - 1) // x.shape[1]
for flat_expert in range(expert_offsets.numel() - 1):
begin = int(expert_offsets[flat_expert].item())
end = int(expert_offsets[flat_expert + 1].item())
if begin == end:
continue
head = flat_expert // experts_per_head
token_ids = gather_ids[begin:end].long()
expert_input = x.index_select(0, token_ids)[:, head]
gate = expert_input @ w_gate[flat_expert]
up = expert_input @ w_up[flat_expert]
hidden = swiglu7_pair(gate, up)
expert_output = hidden @ w_down[flat_expert]
expert_output = expert_output * probs[begin:end, None].to(expert_output.dtype)
output[:, head].index_add_(0, token_ids, expert_output)
return output
# Keep these compile-time constants portable across Triton 3.2 and 3.7 while
# allowing CPU-only imports through vLLM's no-Triton placeholder.
_SWIGLU7_ALPHA = tl.constexpr(1.702) if HAS_TRITON else 1.702
_SWIGLU7_LIMIT = tl.constexpr(7.0) if HAS_TRITON else 7.0
_SWIGLU7_BIAS = tl.constexpr(1.0) if HAS_TRITON else 1.0
@triton.jit
def _swiglu7_kernel(gate, up, out_dtype: tl.constexpr):
gate_clamped = tl.minimum(gate, _SWIGLU7_LIMIT)
up_clamped = tl.maximum(tl.minimum(up, _SWIGLU7_LIMIT), -_SWIGLU7_LIMIT)
sigmoid = tl.sigmoid(_SWIGLU7_ALPHA * gate_clamped)
swish = gate_clamped * sigmoid
return (swish * (up_clamped + _SWIGLU7_BIAS)).to(out_dtype)
@triton.jit
def _binary_search_expert(
cumulative_tiles,
tile_id,
num_experts: tl.constexpr,
log2_num_experts: tl.constexpr,
):
lo = 0
hi = num_experts
for _ in tl.static_range(0, log2_num_experts + 1):
mid = (lo + hi + 1) // 2
below = tl.load(cumulative_tiles + mid) <= tile_id
lo = tl.where(below, mid, lo)
hi = tl.where(below, hi, mid - 1)
return lo
@triton.jit
def _mh_moe_kernel(
x_ptr,
wg_ptr,
wu_ptr,
wd_ptr,
y_ptr,
gather_ids_ptr,
probs_ptr,
expert_offsets_ptr,
cumulative_tiles_ptr,
stride_x_s,
stride_x_h,
stride_x_dh,
stride_wg_e,
stride_wg_dh,
stride_wg_de,
stride_wu_e,
stride_wu_dh,
stride_wu_de,
stride_wd_e,
stride_wd_de,
stride_wd_dh,
stride_y_s,
stride_y_h,
stride_y_dh,
d_head: tl.constexpr,
d_expert: tl.constexpr,
num_heads: tl.constexpr,
num_flat_experts: tl.constexpr,
log2_num_experts: tl.constexpr,
block_t: tl.constexpr,
block_dh: tl.constexpr,
block_de: tl.constexpr,
acc_dtype: tl.constexpr,
deterministic: tl.constexpr = False,
):
tile_id = tl.program_id(0)
total_tiles = tl.load(cumulative_tiles_ptr + num_flat_experts)
if tile_id >= total_tiles:
return
expert = _binary_search_expert(cumulative_tiles_ptr, tile_id, num_flat_experts, log2_num_experts)
expert_i64 = expert.to(tl.int64)
head = expert // (num_flat_experts // num_heads)
tile_in_expert = tile_id - tl.load(cumulative_tiles_ptr + expert)
token_start = tl.load(expert_offsets_ptr + expert) + tile_in_expert * block_t
expert_end = tl.load(expert_offsets_ptr + expert + 1)
count = tl.minimum(token_start + block_t, expert_end) - token_start
dh_block_offsets = tl.arange(0, block_dh)
de_block_offsets = tl.arange(0, block_de)
token_offsets = tl.arange(0, block_t)
dh_offsets = tl.arange(0, d_head)
token_positions = token_start + token_offsets
token_mask = token_offsets < count
# Token indices fit in int32, but multiplying a large packed-batch index by
# the hidden-width stride does not. Promote before computing element offsets.
gather_ids = tl.load(gather_ids_ptr + token_positions, mask=token_mask, other=0).to(tl.int64)
probabilities = tl.load(probs_ptr + token_positions, mask=token_mask, other=0.0)
x_base = gather_ids * stride_x_s + head * stride_x_h
output_acc = tl.zeros([block_t, d_head], dtype=acc_dtype)
for de_start in tl.range(0, d_expert, block_de):
de_offsets = de_start + de_block_offsets
gate_acc = tl.zeros([block_t, block_de], dtype=acc_dtype)
up_acc = tl.zeros([block_t, block_de], dtype=acc_dtype)
for dh_start in tl.static_range(0, d_head, block_dh):
local_dh = dh_start + dh_block_offsets
x_block = tl.load(
x_ptr + x_base[:, None] + local_dh[None, :] * stride_x_dh,
mask=token_mask[:, None],
other=0.0,
)
wg = tl.load(
wg_ptr
+ expert_i64 * stride_wg_e
+ local_dh[:, None] * stride_wg_dh
+ de_offsets[None, :] * stride_wg_de
)
wu = tl.load(
wu_ptr
+ expert_i64 * stride_wu_e
+ local_dh[:, None] * stride_wu_dh
+ de_offsets[None, :] * stride_wu_de
)
gate_acc += tl.dot(x_block, wg)
up_acc += tl.dot(x_block, wu)
hidden = _swiglu7_kernel(gate_acc, up_acc, wd_ptr.dtype.element_ty)
down = tl.load(
wd_ptr + expert_i64 * stride_wd_e + de_offsets[:, None] * stride_wd_de + dh_offsets[None, :] * stride_wd_dh
)
output_acc += tl.dot(hidden, down)
output_acc = output_acc * probabilities[:, None]
if deterministic:
output_ptrs = y_ptr + token_positions[:, None] * stride_y_s + dh_offsets[None, :] * stride_y_dh
tl.store(output_ptrs, output_acc.to(y_ptr.dtype.element_ty), mask=token_mask[:, None])
else:
output_base = gather_ids * stride_y_s + head * stride_y_h
output_ptrs = y_ptr + output_base[:, None] + dh_offsets[None, :] * stride_y_dh
tl.atomic_add(output_ptrs, output_acc.to(y_ptr.dtype.element_ty), mask=token_mask[:, None])
def _deterministic_scatter(
sorted_output: torch.Tensor,
reference: torch.Tensor,
gather_ids: torch.Tensor,
expert_offsets: torch.Tensor,
) -> torch.Tensor:
num_flat_experts = expert_offsets.numel() - 1
experts_per_head = num_flat_experts // reference.shape[1]
expert_lengths = torch.diff(expert_offsets)
head_values = torch.arange(num_flat_experts, device=gather_ids.device) // experts_per_head
head_ids = torch.repeat_interleave(head_values, expert_lengths)
scatter_ids = gather_ids.long() * reference.shape[1] + head_ids.long()
output = torch.zeros_like(reference).view(-1, reference.shape[-1])
output.scatter_add_(0, scatter_ids[:, None].expand_as(sorted_output), sorted_output.to(output.dtype))
return output.view_as(reference)
def _select_block_config() -> tuple[int, int, int, int, int]:
"""Return the reference kernel config, capped for pre-Blackwell GPUs."""
capability = current_omni_platform.get_device_capability()
if capability is not None and capability.major >= 10: # Blackwell
return (128, 64, 32, 2, 8)
# BLOCK_T=128 needs 122,880 bytes of shared memory and is not safe on the
# qualified L20X path. This is the reference kernel's portable config.
return (64, 64, 32, 2, 4)
def triton_mh_moe_forward(
x: torch.Tensor,
gather_ids: torch.Tensor,
probs: torch.Tensor,
expert_offsets: torch.Tensor,
w_gate: torch.Tensor,
w_up: torch.Tensor,
w_down: torch.Tensor,
*,
deterministic: bool = False,
) -> torch.Tensor:
"""Fused gather/expert/scatter kernel for released MAGI dimensions."""
routed_tokens = gather_ids.numel()
if routed_tokens == 0:
return torch.zeros_like(x)
d_head, d_expert = x.shape[-1], w_down.shape[1]
block_t, block_dh, block_de, num_stages, num_warps = _select_block_config()
if d_head % block_dh or d_expert % block_de:
return torch_mh_moe_forward(x, gather_ids, probs, expert_offsets, w_gate, w_up, w_down)
if deterministic:
output = torch.empty((routed_tokens, 1, d_head), device=x.device, dtype=x.dtype)
else:
output = torch.zeros_like(x)
num_flat_experts = expert_offsets.numel() - 1
expert_tiles = (torch.diff(expert_offsets) + block_t - 1) // block_t
cumulative_tiles = torch.cat(
(torch.zeros(1, dtype=torch.int32, device=x.device), expert_tiles.cumsum(0, dtype=torch.int32))
)
# Match the reference launch bound. Empty/excess programs return after
# comparing against ``cumulative_tiles[-1]`` inside the kernel.
grid = ((routed_tokens + block_t - 1) // block_t + num_flat_experts,)
log2_experts = max(1, math.ceil(math.log2(max(num_flat_experts, 1) + 1)))
_mh_moe_kernel[grid](
x,
w_gate,
w_up,
w_down,
output,
gather_ids,
probs,
expert_offsets,
cumulative_tiles,
x.stride(0),
x.stride(1),
x.stride(2),
w_gate.stride(0),
w_gate.stride(1),
w_gate.stride(2),
w_up.stride(0),
w_up.stride(1),
w_up.stride(2),
w_down.stride(0),
w_down.stride(1),
w_down.stride(2),
output.stride(0),
output.stride(1),
output.stride(2),
d_head,
d_expert,
x.shape[1],
num_flat_experts,
log2_experts,
block_t,
block_dh,
block_de,
tl.float32,
deterministic,
num_stages=num_stages,
num_warps=num_warps,
)
if deterministic:
return _deterministic_scatter(output.view(routed_tokens, d_head), x, gather_ids, expert_offsets)
return output
+189 -299
View File
@@ -21,22 +21,19 @@ from typing import Literal
import torch
import torch.nn as nn
import torch.nn.functional as F
from vllm.triton_utils import HAS_TRITON, tl, triton
from vllm_omni.platforms import current_omni_platform
from .fused_moe_kernels import (
global_sort_routes,
invoke_fused_moe_bf16,
torch_mh_moe_forward,
triton_mh_moe_forward,
)
from .parallel import Magi2ParallelGroup, ep_dispatch, ep_undispatch, get_magi2_ep_group
RoutingScore = Literal["softmax", "sigmoid"]
def swiglu7_pair(gate: torch.Tensor, up: torch.Tensor) -> torch.Tensor:
"""Released clamped SwiGLU7 expert activation, evaluated in fp32."""
dtype = gate.dtype
gate = gate.float().clamp(max=7.0)
up = up.float().clamp(min=-7.0, max=7.0)
return (gate * torch.sigmoid(1.702 * gate) * (up + 1.0)).to(dtype)
_BF16_MOE_CUDA_MIN_TOKENS = 4096
def compute_topk_probs_and_indices(
@@ -77,302 +74,127 @@ def compute_topk_probs_and_indices(
return topk_probs, topk_indices
def global_sort_routes(
topk_probs: torch.Tensor,
topk_indices: torch.Tensor,
def _align_bf16_routes(
route_ids: torch.Tensor,
num_experts: int,
block_size: int,
buffers: tuple[torch.Tensor, torch.Tensor, torch.Tensor] | None = None,
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
"""Convert per-head routes into a stable flattened-expert CSR layout."""
"""Build route metadata without reading the padded count back to the CPU."""
if topk_probs.shape != topk_indices.shape or topk_indices.ndim != 3:
raise ValueError("top-k probabilities and indices must have the same [H,S,K] shape")
heads, sequence, top_k = topk_indices.shape
device = topk_indices.device
head_offset = torch.arange(heads, device=device).view(heads, 1, 1) * num_experts
flattened_experts = (topk_indices + head_offset).reshape(-1)
flat_probs = topk_probs.reshape(-1)
flat_tokens = torch.arange(sequence, device=device).view(1, sequence, 1).expand(heads, sequence, top_k).reshape(-1)
order = flattened_experts.argsort(stable=True)
gather_ids = flat_tokens[order].to(torch.int32)
sorted_probs = flat_probs[order].float()
counts = torch.bincount(flattened_experts, minlength=heads * num_experts)
offsets = torch.zeros(heads * num_experts + 1, device=device, dtype=torch.long)
offsets[1:] = counts.cumsum(0)
return gather_ids, sorted_probs, offsets
flat_ids = route_ids.reshape(-1).to(torch.int32)
route_count = flat_ids.numel()
order = torch.argsort(flat_ids)
sorted_experts = flat_ids[order]
counts = torch.zeros(num_experts, device=flat_ids.device, dtype=torch.int64)
counts.scatter_add_(0, flat_ids.long(), torch.ones_like(flat_ids, dtype=torch.int64))
padded_counts = ((counts + block_size - 1) // block_size) * block_size
starts = torch.cumsum(padded_counts, 0) - padded_counts
ends = torch.cumsum(counts, 0)
begins = ends - counts
positions = torch.arange(route_count, device=flat_ids.device, dtype=torch.int64)
destinations = starts[sorted_experts.long()] + positions - begins[sorted_experts.long()]
if buffers is None:
buffers = _allocate_bf16_route_buffers(route_count, num_experts, block_size, flat_ids.device)
sorted_ids, expert_ids, num_padded = buffers
sorted_ids.fill_(route_count)
sorted_ids[destinations] = order.to(torch.int32)
block_starts = torch.arange(expert_ids.numel(), device=flat_ids.device) * block_size
expert_ids.copy_(torch.searchsorted(padded_counts.cumsum(0), block_starts, right=True))
num_padded.copy_(padded_counts.sum().reshape(1))
return sorted_ids, expert_ids, num_padded
def torch_mh_moe_forward(
x: torch.Tensor,
gather_ids: torch.Tensor,
probs: torch.Tensor,
expert_offsets: torch.Tensor,
w_gate: torch.Tensor,
w_up: torch.Tensor,
w_down: torch.Tensor,
) -> torch.Tensor:
"""Small-shape correctness oracle for the fused expert kernel."""
if x.ndim != 3:
raise ValueError("multi-head MoE input must be [tokens,heads,head_dim]")
output = torch.zeros_like(x)
experts_per_head = (expert_offsets.numel() - 1) // x.shape[1]
for flat_expert in range(expert_offsets.numel() - 1):
begin = int(expert_offsets[flat_expert].item())
end = int(expert_offsets[flat_expert + 1].item())
if begin == end:
continue
head = flat_expert // experts_per_head
token_ids = gather_ids[begin:end].long()
expert_input = x.index_select(0, token_ids)[:, head]
gate = expert_input @ w_gate[flat_expert]
up = expert_input @ w_up[flat_expert]
hidden = swiglu7_pair(gate, up)
expert_output = hidden @ w_down[flat_expert]
expert_output = expert_output * probs[begin:end, None].to(expert_output.dtype)
output[:, head].index_add_(0, token_ids, expert_output)
return output
# vLLM's no-Triton placeholder exposes ``tl.constexpr`` as ``None``.
_SWIGLU7_ALPHA = tl.constexpr(1.702) if HAS_TRITON else 1.702
_SWIGLU7_LIMIT = tl.constexpr(7.0) if HAS_TRITON else 7.0
_SWIGLU7_BIAS = tl.constexpr(1.0) if HAS_TRITON else 1.0
@triton.jit
def _swiglu7_kernel(gate, up, out_dtype: tl.constexpr):
gate_clamped = tl.minimum(gate, _SWIGLU7_LIMIT)
up_clamped = tl.maximum(tl.minimum(up, _SWIGLU7_LIMIT), -_SWIGLU7_LIMIT)
sigmoid = tl.sigmoid(_SWIGLU7_ALPHA * gate_clamped)
swish = gate_clamped * sigmoid
return (swish * (up_clamped + _SWIGLU7_BIAS)).to(out_dtype)
@triton.jit
def _binary_search_expert(
cumulative_tiles,
tile_id,
num_experts: tl.constexpr,
log2_num_experts: tl.constexpr,
):
lo = 0
hi = num_experts
for _ in tl.static_range(0, log2_num_experts + 1):
mid = (lo + hi + 1) // 2
below = tl.load(cumulative_tiles + mid) <= tile_id
lo = tl.where(below, mid, lo)
hi = tl.where(below, hi, mid - 1)
return lo
@triton.jit
def _mh_moe_kernel(
x_ptr,
wg_ptr,
wu_ptr,
wd_ptr,
y_ptr,
gather_ids_ptr,
probs_ptr,
expert_offsets_ptr,
cumulative_tiles_ptr,
stride_x_s,
stride_x_h,
stride_x_dh,
stride_wg_e,
stride_wg_dh,
stride_wg_de,
stride_wu_e,
stride_wu_dh,
stride_wu_de,
stride_wd_e,
stride_wd_de,
stride_wd_dh,
stride_y_s,
stride_y_h,
stride_y_dh,
d_head: tl.constexpr,
d_expert: tl.constexpr,
num_heads: tl.constexpr,
num_flat_experts: tl.constexpr,
log2_num_experts: tl.constexpr,
block_t: tl.constexpr,
block_dh: tl.constexpr,
block_de: tl.constexpr,
acc_dtype: tl.constexpr,
deterministic: tl.constexpr = False,
):
tile_id = tl.program_id(0)
total_tiles = tl.load(cumulative_tiles_ptr + num_flat_experts)
if tile_id >= total_tiles:
return
expert = _binary_search_expert(cumulative_tiles_ptr, tile_id, num_flat_experts, log2_num_experts)
expert_i64 = expert.to(tl.int64)
head = expert // (num_flat_experts // num_heads)
tile_in_expert = tile_id - tl.load(cumulative_tiles_ptr + expert)
token_start = tl.load(expert_offsets_ptr + expert) + tile_in_expert * block_t
expert_end = tl.load(expert_offsets_ptr + expert + 1)
count = tl.minimum(token_start + block_t, expert_end) - token_start
dh_block_offsets = tl.arange(0, block_dh)
de_block_offsets = tl.arange(0, block_de)
token_offsets = tl.arange(0, block_t)
dh_offsets = tl.arange(0, d_head)
token_positions = token_start + token_offsets
token_mask = token_offsets < count
# Token indices fit in int32, but multiplying a large packed-batch index by
# the hidden-width stride does not. Promote before computing element offsets.
gather_ids = tl.load(gather_ids_ptr + token_positions, mask=token_mask, other=0).to(tl.int64)
probabilities = tl.load(probs_ptr + token_positions, mask=token_mask, other=0.0)
x_base = gather_ids * stride_x_s + head * stride_x_h
output_acc = tl.zeros([block_t, d_head], dtype=acc_dtype)
for de_start in tl.range(0, d_expert, block_de):
de_offsets = de_start + de_block_offsets
gate_acc = tl.zeros([block_t, block_de], dtype=acc_dtype)
up_acc = tl.zeros([block_t, block_de], dtype=acc_dtype)
for dh_start in tl.static_range(0, d_head, block_dh):
local_dh = dh_start + dh_block_offsets
x_block = tl.load(
x_ptr + x_base[:, None] + local_dh[None, :] * stride_x_dh,
mask=token_mask[:, None],
other=0.0,
)
wg = tl.load(
wg_ptr
+ expert_i64 * stride_wg_e
+ local_dh[:, None] * stride_wg_dh
+ de_offsets[None, :] * stride_wg_de
)
wu = tl.load(
wu_ptr
+ expert_i64 * stride_wu_e
+ local_dh[:, None] * stride_wu_dh
+ de_offsets[None, :] * stride_wu_de
)
gate_acc += tl.dot(x_block, wg)
up_acc += tl.dot(x_block, wu)
hidden = _swiglu7_kernel(gate_acc, up_acc, wd_ptr.dtype.element_ty)
down = tl.load(
wd_ptr + expert_i64 * stride_wd_e + de_offsets[:, None] * stride_wd_de + dh_offsets[None, :] * stride_wd_dh
)
output_acc += tl.dot(hidden, down)
output_acc = output_acc * probabilities[:, None]
if deterministic:
output_ptrs = y_ptr + token_positions[:, None] * stride_y_s + dh_offsets[None, :] * stride_y_dh
tl.store(output_ptrs, output_acc.to(y_ptr.dtype.element_ty), mask=token_mask[:, None])
else:
output_base = gather_ids * stride_y_s + head * stride_y_h
output_ptrs = y_ptr + output_base[:, None] + dh_offsets[None, :] * stride_y_dh
tl.atomic_add(output_ptrs, output_acc.to(y_ptr.dtype.element_ty), mask=token_mask[:, None])
def _deterministic_scatter(
sorted_output: torch.Tensor,
reference: torch.Tensor,
gather_ids: torch.Tensor,
expert_offsets: torch.Tensor,
) -> torch.Tensor:
num_flat_experts = expert_offsets.numel() - 1
experts_per_head = num_flat_experts // reference.shape[1]
expert_lengths = torch.diff(expert_offsets)
head_values = torch.arange(num_flat_experts, device=gather_ids.device) // experts_per_head
head_ids = torch.repeat_interleave(head_values, expert_lengths)
scatter_ids = gather_ids.long() * reference.shape[1] + head_ids.long()
output = torch.zeros_like(reference).view(-1, reference.shape[-1])
output.scatter_add_(0, scatter_ids[:, None].expand_as(sorted_output), sorted_output.to(output.dtype))
return output.view_as(reference)
def _select_block_config() -> tuple[int, int, int, int, int]:
"""Return the reference kernel config, capped for pre-Blackwell GPUs."""
capability = current_omni_platform.get_device_capability()
if capability is not None and capability.major >= 10: # Blackwell
return (128, 64, 32, 2, 8)
# BLOCK_T=128 needs 122,880 bytes of shared memory and is not safe on the
# qualified L20X path. This is the reference kernel's portable config.
return (64, 64, 32, 2, 4)
def triton_mh_moe_forward(
x: torch.Tensor,
gather_ids: torch.Tensor,
probs: torch.Tensor,
expert_offsets: torch.Tensor,
w_gate: torch.Tensor,
w_up: torch.Tensor,
w_down: torch.Tensor,
*,
deterministic: bool = False,
) -> torch.Tensor:
"""Fused gather/expert/scatter kernel for released MAGI dimensions."""
routed_tokens = gather_ids.numel()
if routed_tokens == 0:
return torch.zeros_like(x)
d_head, d_expert = x.shape[-1], w_down.shape[1]
block_t, block_dh, block_de, num_stages, num_warps = _select_block_config()
if d_head % block_dh or d_expert % block_de:
return torch_mh_moe_forward(x, gather_ids, probs, expert_offsets, w_gate, w_up, w_down)
if deterministic:
output = torch.empty((routed_tokens, 1, d_head), device=x.device, dtype=x.dtype)
else:
output = torch.zeros_like(x)
num_flat_experts = expert_offsets.numel() - 1
expert_tiles = (torch.diff(expert_offsets) + block_t - 1) // block_t
cumulative_tiles = torch.cat(
(torch.zeros(1, dtype=torch.int32, device=x.device), expert_tiles.cumsum(0, dtype=torch.int32))
def _allocate_bf16_route_buffers(
route_count: int, num_experts: int, block_size: int, device: torch.device
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
# Each expert adds at most block_size - 1 padding rows. The kernel uses
# the device-side padded count to skip programs beyond the live prefix.
capacity = math.ceil((route_count + num_experts * (block_size - 1)) / block_size) * block_size
return (
torch.empty(capacity, device=device, dtype=torch.int32),
torch.empty(capacity // block_size, device=device, dtype=torch.int32),
torch.empty(1, device=device, dtype=torch.int32),
)
# Match the reference launch bound. Empty/excess programs return after
# comparing against ``cumulative_tiles[-1]`` inside the kernel.
grid = ((routed_tokens + block_t - 1) // block_t + num_flat_experts,)
log2_experts = max(1, math.ceil(math.log2(max(num_flat_experts, 1) + 1)))
_mh_moe_kernel[grid](
x,
w_gate,
w_up,
w_down,
output,
gather_ids,
probs,
expert_offsets,
cumulative_tiles,
x.stride(0),
x.stride(1),
x.stride(2),
w_gate.stride(0),
w_gate.stride(1),
w_gate.stride(2),
w_up.stride(0),
w_up.stride(1),
w_up.stride(2),
w_down.stride(0),
w_down.stride(1),
w_down.stride(2),
output.stride(0),
output.stride(1),
output.stride(2),
d_head,
d_expert,
x.shape[1],
num_flat_experts,
log2_experts,
block_t,
block_dh,
block_de,
tl.float32,
deterministic,
num_stages=num_stages,
num_warps=num_warps,
def _pack_bf16_w13(w_gate: torch.Tensor, w_up: torch.Tensor) -> torch.Tensor:
"""Pack gate/up weights into adjacent rows for the grouped W13 GEMM."""
num_experts, hidden_size, intermediate_size = w_gate.shape
return torch.stack((w_gate.transpose(1, 2), w_up.transpose(1, 2)), dim=2).reshape(
num_experts, 2 * intermediate_size, hidden_size
)
if deterministic:
return _deterministic_scatter(output.view(routed_tokens, d_head), x, gather_ids, expert_offsets)
return output
def _bf16_fused_moe_forward(
x_heads: torch.Tensor,
probabilities: torch.Tensor,
indices: torch.Tensor,
packed_w13: torch.Tensor,
w_down: torch.Tensor,
route_buffers: tuple[torch.Tensor, torch.Tensor, torch.Tensor] | None = None,
) -> torch.Tensor:
"""Run the BF16 grouped GEMM path with head-local routing."""
num_tokens, num_heads, hidden_size = x_heads.shape
top_k = probabilities.shape[-1]
num_experts = packed_w13.shape[0]
if num_experts % num_heads:
raise ValueError("expert bank must be divisible by local MoE heads")
experts_per_head = num_experts // num_heads
if num_tokens == 0:
return torch.zeros_like(x_heads)
head_offsets = (
torch.arange(num_heads, device=x_heads.device, dtype=torch.int32).view(num_heads, 1, 1) * experts_per_head
)
route_ids = (indices.to(torch.int32) + head_offsets).reshape(num_heads * num_tokens, top_k)
route_weights = probabilities.reshape(num_heads * num_tokens, top_k).contiguous()
sorted_ids, expert_ids, num_padded = _align_bf16_routes(route_ids, num_experts, 128, route_buffers)
hidden = x_heads.permute(1, 0, 2).contiguous().reshape(num_heads * num_tokens, hidden_size)
intermediate_size = packed_w13.shape[1] // 2
intermediate = torch.empty(
(num_heads * num_tokens * top_k, intermediate_size), device=x_heads.device, dtype=x_heads.dtype
)
# Keep the qualified MUSA point unchanged. CUDA/H20 benefits from the
# pre-Blackwell tile found by the MAGI-2 BF16 sweep (smaller K/warp count
# and deeper pipelining reduce register pressure). The launch contract
# remains the same; only legal, device-specific values are selected.
config = {
"BLOCK_SIZE_M": 128,
"BLOCK_SIZE_N": 128,
"BLOCK_SIZE_K": 32,
"GROUP_SIZE_M": 16,
"num_warps": 4 if not current_omni_platform.is_musa() else 16,
"num_stages": 3 if not current_omni_platform.is_musa() else 1,
}
invoke_fused_moe_bf16(
hidden,
packed_w13,
intermediate,
route_weights,
sorted_ids,
expert_ids,
num_padded,
top_k=top_k,
config=config,
fuse_swiglu=True,
)
route_output = torch.empty((num_heads * num_tokens, top_k, hidden_size), device=x_heads.device, dtype=x_heads.dtype)
invoke_fused_moe_bf16(
intermediate,
w_down.transpose(1, 2),
route_output,
route_weights,
sorted_ids,
expert_ids,
num_padded,
top_k=1,
config=config,
fuse_swiglu=False,
)
return route_output.sum(dim=1).reshape(num_heads, num_tokens, hidden_size).permute(1, 0, 2)
@dataclass(frozen=True)
@@ -450,6 +272,59 @@ class Magi2MultiHeadMoE(nn.Module):
parameter = getattr(target, parts[-1])
parameter.mmap_weight_transform = self.ep_slice
# Derived, non-persistent weight storage. The model loader populates
# it after checkpoint loading; the lazy guard also supports direct
# layer construction and later weight reloads in tests/tools.
self.register_buffer("_bf16_packed_w13", None, persistent=False)
self._bf16_packed_w13_key: tuple | None = None
self._bf16_route_buffers: tuple[torch.Tensor, torch.Tensor, torch.Tensor] | None = None
def _apply(self, fn, recurse: bool = True):
# Do not migrate an extra full-size derived weight bank on .to().
self._bf16_packed_w13 = None
self._bf16_packed_w13_key = None
self._bf16_route_buffers = None
return super()._apply(fn, recurse=recurse)
def _get_bf16_packed_w13(self) -> torch.Tensor:
# In-place loading increments _version; mmap/.data replacement may
# not. Include object/storage identity and handle inference tensors,
# which intentionally do not expose a version counter.
key = tuple(
(id(weight), weight.data_ptr(), None if weight.is_inference() else weight._version)
for weight in (self.W_gate, self.W_up)
)
packed = self._bf16_packed_w13
if (
packed is None
or self._bf16_packed_w13_key != key
or packed.device != self.W_gate.device
or packed.dtype != self.W_gate.dtype
):
# [E, I, 2, D] -> [E, 2I, D]: adjacent rows are gate/up pairs.
with torch.no_grad():
packed = _pack_bf16_w13(self.W_gate, self.W_up)
self._bf16_packed_w13 = packed
self._bf16_packed_w13_key = key
return packed
def prepare_bf16_weights(self) -> None:
"""Materialize derived BF16 weights outside the inference hot path."""
# Reload may mutate inference tensors without a version counter.
self._bf16_packed_w13 = None
self._bf16_packed_w13_key = None
if self.W_gate.dtype == torch.bfloat16 and self.W_gate.device.type not in ("cpu", "meta"):
self._get_bf16_packed_w13()
def _get_bf16_route_buffers(self, route_count: int, device: torch.device):
buffers = self._bf16_route_buffers
capacity = math.ceil((route_count + self.local_flatten_num_experts * 127) / 128) * 128
if buffers is None or buffers[0].numel() != capacity or buffers[0].device != device:
buffers = _allocate_bf16_route_buffers(route_count, self.local_flatten_num_experts, 128, device)
self._bf16_route_buffers = buffers
return buffers
def ep_slice(self, checkpoint_tensor: torch.Tensor) -> torch.Tensor:
"""Slice flattened ``(head,expert)`` checkpoint rows for this rank."""
@@ -492,6 +367,21 @@ class Magi2MultiHeadMoE(nn.Module):
def _local_forward(self, x_heads: torch.Tensor) -> torch.Tensor:
probabilities, indices = self._route(x_heads)
if (
x_heads.device.type in ("cuda", "musa")
and x_heads.dtype == torch.bfloat16
and os.environ.get("MAGI2_DETERMINISTIC", "0") != "1"
and (current_omni_platform.is_musa() or x_heads.shape[0] >= _BF16_MOE_CUDA_MIN_TOKENS)
and not torch.compiler.is_compiling()
):
return _bf16_fused_moe_forward(
x_heads,
probabilities,
indices,
self._get_bf16_packed_w13(),
self.W_down,
self._get_bf16_route_buffers(x_heads.shape[0] * self.local_num_heads * self.top_k, x_heads.device),
)
gather_ids, sorted_probs, offsets = global_sort_routes(probabilities, indices, self.num_experts)
if x_heads.is_cuda:
return triton_mh_moe_forward(
@@ -806,6 +806,10 @@ class Magi2PreviewTransformer(nn.Module):
module.router.expert_bias.copy_(module.router.expert_bias_ema)
loaded.add(f"{module_name}.router.expert_bias")
for module in self.modules():
if isinstance(module, Magi2MultiHeadMoE):
module.prepare_bf16_weights()
missing = set(targets) - loaded
if missing:
raise ValueError(f"MAGI-2 preview checkpoint is missing {len(missing)} weights: {sorted(missing)[:8]}")