[test] gate compiled H3 fusion dispatcher contracts

This commit is contained in:
Will Lin
2026-08-22 03:13:11 +00:00
parent 9709cc037e
commit 279071df7e
4 changed files with 70 additions and 1 deletions
@@ -380,6 +380,7 @@ def fused_rmsnorm_modulate(
device-side bounds check would synchronize); callers are safe by
construction (``timestep_indices * 3 + token_tags``, SP pads with 0).
"""
_require_forward_only(x, weight, scale, shift)
if torch.compiler.is_compiling():
return torch.ops.fastvideo._minimax_h3_rmsnorm_modulate(x, weight, scale, shift, index, eps)
return _fused_rmsnorm_modulate_impl(x, weight, scale, shift, index, eps)
@@ -400,6 +401,7 @@ def fused_residual_gate_rmsnorm_modulate(
``index`` values must lie in ``[0, table_rows)``; see
:func:`fused_rmsnorm_modulate` for why the wrapper does not check them.
"""
_require_forward_only(residual, branch, gate, weight, scale, shift)
if torch.compiler.is_compiling():
return torch.ops.fastvideo._minimax_h3_residual_gate_rmsnorm_modulate(
residual,
@@ -205,6 +205,9 @@ def fused_qknorm_rope(
(about 300k tokens per rank at H3's 56 heads x 128 head_dim) address
correctly.
"""
if torch.is_grad_enabled() and any(
isinstance(tensor, torch.Tensor) and tensor.requires_grad for tensor in (x, weight, cos, sin)):
raise RuntimeError("fused_qknorm_rope is inference-only and does not implement autograd")
if torch.compiler.is_compiling():
return torch.ops.fastvideo._minimax_h3_qknorm_rope(x, weight, cos, sin, eps)
return _fused_qknorm_rope_impl(x, weight, cos, sin, eps)
@@ -116,6 +116,8 @@ def minimax_h3_swiglu(x: torch.Tensor) -> torch.Tensor:
This is intentionally a strict kernel wrapper: callers own fallback policy and
must only invoke it for a supported CUDA inference path.
"""
if torch.is_grad_enabled() and x.requires_grad:
raise RuntimeError("MiniMax H3 fused SwiGLU is forward-only and does not implement autograd")
if torch.compiler.is_compiling():
return torch.ops.fastvideo._minimax_h3_swiglu(x)
return _minimax_h3_swiglu_impl(x)
@@ -3,6 +3,7 @@
from __future__ import annotations
import pytest
import torch
from torch._subclasses.fake_tensor import FakeTensorMode
@@ -55,7 +56,7 @@ def test_minimax_h3_fusions_are_dynamic_fullgraph_traceable() -> None:
compiled = torch.compile(_all_fusions, backend=capture_graph, fullgraph=True, dynamic=True)
with FakeTensorMode():
with torch.no_grad(), FakeTensorMode():
x = torch.empty((2, 3, 8), device="cuda", dtype=torch.bfloat16)
weight = torch.empty((8,), device="cuda", dtype=torch.bfloat16)
table = torch.empty((6, 8), device="cuda", dtype=torch.bfloat16)
@@ -95,3 +96,64 @@ def test_minimax_h3_fusions_are_dynamic_fullgraph_traceable() -> None:
torch.ops.fastvideo._minimax_h3_qknorm_rope,
torch.ops.fastvideo._minimax_h3_swiglu,
} <= call_targets
@pytest.mark.gpu
def test_minimax_h3_fusion_custom_ops_match_real_fullgraph_cuda() -> None:
"""Cross-check fake metadata and execute the real CUDA dispatcher path."""
if not torch.cuda.is_available() or not torch.cuda.is_bf16_supported():
pytest.skip("BF16 CUDA is required")
pytest.importorskip("triton")
device = torch.device("cuda")
generator = torch.Generator(device=device).manual_seed(2026)
x = torch.randn((2, 3, 128), generator=generator, device=device, dtype=torch.bfloat16)
branch = torch.randn_like(x)
weight = torch.randn((128,), generator=generator, device=device, dtype=torch.bfloat16)
scale = torch.randn((6, 128), generator=generator, device=device, dtype=torch.bfloat16)
shift = torch.randn((6, 128), generator=generator, device=device, dtype=torch.bfloat16)
gate = torch.randn((6, 128), generator=generator, device=device, dtype=torch.bfloat16)
index = torch.tensor((0, 3, 5), device=device, dtype=torch.int64)
q = torch.randn((2, 3, 4, 128), generator=generator, device=device, dtype=torch.bfloat16)
q_weight = torch.randn((128,), generator=generator, device=device, dtype=torch.bfloat16).contiguous()
rotary = torch.randn((3, 96), generator=generator, device=device, dtype=torch.bfloat16).contiguous()
packed_mlp = torch.randn((2, 3, 256), generator=generator, device=device, dtype=torch.bfloat16)
opchecks = (
(
torch.ops.fastvideo._minimax_h3_rmsnorm_modulate,
(x, weight, scale, shift, index, 1e-5),
),
(
torch.ops.fastvideo._minimax_h3_residual_gate_rmsnorm_modulate,
(x, branch, gate, weight, scale, shift, index, 1e-5),
),
(
torch.ops.fastvideo._minimax_h3_qknorm_rope,
(q, q_weight, rotary, rotary.clone(), 1e-5),
),
(torch.ops.fastvideo._minimax_h3_swiglu, (packed_mlp,)),
)
for op, args in opchecks:
torch.library.opcheck(op, args)
compiled = torch.compile(_all_fusions, fullgraph=True, dynamic=True)
inputs = (
x,
branch,
weight,
scale,
shift,
gate,
index,
q,
q_weight,
rotary,
rotary.clone(),
packed_mlp,
)
with torch.no_grad():
expected = _all_fusions(*inputs)
actual = compiled(*inputs)
for actual_output, expected_output in zip(actual, expected, strict=True):
torch.testing.assert_close(actual_output, expected_output, atol=0.0, rtol=0.0)