[perf] Add opt-in MiniMax-H3 Sol-Engine fusions

This commit is contained in:
H1yori233
2026-08-21 00:20:38 -07:00
parent c4ad4227c0
commit b158388733
10 changed files with 1211 additions and 18 deletions
+8
View File
@@ -21,6 +21,7 @@ if TYPE_CHECKING:
FASTVIDEO_TRACE_FUNCTION: int = 0
FASTVIDEO_ATTENTION_BACKEND: str | None = None
FASTVIDEO_FA4: bool = False
FASTVIDEO_MINIMAX_H3_FUSIONS: str = ""
FASTVIDEO_WORKER_MULTIPROC_METHOD: str = "spawn"
FASTVIDEO_TARGET_DEVICE: str = "cuda"
MAX_JOBS: str | None = None
@@ -217,6 +218,13 @@ environment_variables: dict[str, Callable[[], Any]] = {
"FASTVIDEO_FA4":
lambda: os.getenv("FASTVIDEO_FA4", "0") != "0",
# Opt-in MiniMax-H3 inference-only Triton fusions adapted from the
# NVlabs/Sana Sol-Engine implementation. Accepts `all`, `1`, or a
# comma-separated subset of `modulate,qknorm_rope,swiglu`. An empty value
# (the default), `0`, or `none` keeps the eager implementation.
"FASTVIDEO_MINIMAX_H3_FUSIONS":
lambda: os.getenv("FASTVIDEO_MINIMAX_H3_FUSIONS", ""),
# Use dedicated multiprocess context for workers.
"FASTVIDEO_WORKER_MULTIPROC_METHOD":
lambda: os.getenv("FASTVIDEO_WORKER_MULTIPROC_METHOD", "spawn"),
+91 -18
View File
@@ -10,6 +10,7 @@ import torch
import torch.nn as nn
import torch.nn.functional as F
from fastvideo import envs
from fastvideo.attention import DistributedAttention
from fastvideo.attention.layer import DistributedAttention_VSA
from fastvideo.attention.selector import get_attn_backend
@@ -24,11 +25,39 @@ from fastvideo.layers.mlp import MLP
from fastvideo.layers.quantization import QuantizationConfig
from fastvideo.layers.visual_embedding import Timesteps
from fastvideo.models.dits.base import BaseDiT
from fastvideo.models.dits.minimax_h3_fusions import (
fused_qknorm_rope,
fused_residual_gate_rmsnorm_modulate,
fused_rmsnorm_modulate,
minimax_h3_swiglu,
)
from fastvideo.platforms import AttentionBackendEnum
from fastvideo.utils import get_compute_dtype
MINIMAX_H3_MODALITY_NUM = 3
_CFG = MiniMaxH3Config()
_MINIMAX_H3_FUSION_NAMES = frozenset({"modulate", "qknorm_rope", "swiglu"})
def _enabled_minimax_h3_fusions(value: str | None = None) -> frozenset[str]:
"""Parse the independently switchable inference fusion set."""
raw = envs.FASTVIDEO_MINIMAX_H3_FUSIONS if value is None else value
normalized = raw.strip().lower()
if normalized in {"", "0", "none"}:
return frozenset()
if normalized in {"1", "all"}:
return _MINIMAX_H3_FUSION_NAMES
enabled = frozenset(item.strip() for item in normalized.split(",") if item.strip())
unknown = enabled - _MINIMAX_H3_FUSION_NAMES
if unknown:
supported = ",".join(sorted(_MINIMAX_H3_FUSION_NAMES))
raise ValueError(f"Unknown MiniMax H3 fusion(s) {sorted(unknown)}; expected a subset of {supported}.")
return enabled
def _can_run_minimax_h3_fusion(tensor: torch.Tensor) -> bool:
"""Triton kernels are inference-only and stay outside Dynamo capture."""
return tensor.is_cuda and not torch.is_grad_enabled() and not torch.compiler.is_compiling()
class MiniMaxH3RotaryPosEmbed(nn.Module):
@@ -62,6 +91,7 @@ class MiniMaxH3FeedForward(nn.Module):
ffn_dim: int,
quant_config: QuantizationConfig | None = None,
prefix: str = "",
fuse_swiglu: bool = False,
) -> None:
super().__init__()
self.fc_in = ReplicatedLinear(
@@ -78,11 +108,15 @@ class MiniMaxH3FeedForward(nn.Module):
quant_config=quant_config,
prefix=f"{prefix}.fc_out",
)
self.fuse_swiglu = fuse_swiglu
def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
hidden_states, _ = self.fc_in(hidden_states)
hidden_states, gate = hidden_states.chunk(2, dim=-1)
hidden_states = hidden_states * F.silu(gate)
if self.fuse_swiglu and _can_run_minimax_h3_fusion(hidden_states):
hidden_states = minimax_h3_swiglu(hidden_states)
else:
hidden_states, gate = hidden_states.chunk(2, dim=-1)
hidden_states = hidden_states * F.silu(gate)
hidden_states, _ = self.fc_out(hidden_states)
return hidden_states
@@ -99,6 +133,7 @@ class MiniMaxH3Attention(nn.Module):
supported_attention_backends: tuple[AttentionBackendEnum, ...],
quant_config: QuantizationConfig | None,
prefix: str,
fuse_qknorm_rope: bool = False,
) -> None:
super().__init__()
self.num_attention_heads = num_attention_heads
@@ -134,6 +169,7 @@ class MiniMaxH3Attention(nn.Module):
quant_config=quant_config,
prefix=f"{prefix}.to_out",
)
self.fuse_qknorm_rope = fuse_qknorm_rope
# VSA carries a learned gate on its pooled-compression branch. The H3
# checkpoint has no such weight, so the loader zero-initializes it
# (ALLOWED_NEW_PARAM_PATTERNS) and the branch is exactly disabled
@@ -211,11 +247,18 @@ class MiniMaxH3Attention(nn.Module):
query = query.unflatten(-1, (self.num_attention_heads, self.attention_head_dim))
key = key.unflatten(-1, (self.num_attention_heads, self.attention_head_dim))
value = value.unflatten(-1, (self.num_attention_heads, self.attention_head_dim))
query = self.norm_q(query)
key = self.norm_k(key)
if rotary_emb is not None:
query = self._apply_rotary_emb(query, rotary_emb)
key = self._apply_rotary_emb(key, rotary_emb)
if (self.fuse_qknorm_rope and rotary_emb is not None and _can_run_minimax_h3_fusion(query)):
cos, sin = rotary_emb
cos = cos.to(query.dtype)
sin = sin.to(query.dtype)
query = fused_qknorm_rope(query, self.norm_q.weight, cos, sin, self.norm_q.eps)
key = fused_qknorm_rope(key, self.norm_k.weight, cos, sin, self.norm_k.eps)
else:
query = self.norm_q(query)
key = self.norm_k(key)
if rotary_emb is not None:
query = self._apply_rotary_emb(query, rotary_emb)
key = self._apply_rotary_emb(key, rotary_emb)
# H3 rotates only 96/128 channels, which the generic `freqs_cis`
# branch cannot express. Apply it above, then pass no RoPE here.
@@ -397,6 +440,9 @@ class MiniMaxH3TransformerBlock(nn.Module):
quant_config: QuantizationConfig | None,
prefix: str,
adaln_apply_silu: bool = True,
fuse_modulate: bool = False,
fuse_qknorm_rope: bool = False,
fuse_swiglu: bool = False,
) -> None:
super().__init__()
self.norm1 = nn.RMSNorm(hidden_size, eps=norm_eps)
@@ -408,6 +454,7 @@ class MiniMaxH3TransformerBlock(nn.Module):
supported_attention_backends,
quant_config,
prefix=f"{prefix}.attn",
fuse_qknorm_rope=fuse_qknorm_rope,
)
self.norm2 = nn.RMSNorm(hidden_size, eps=norm_eps)
self.ff = MiniMaxH3FeedForward(
@@ -415,6 +462,7 @@ class MiniMaxH3TransformerBlock(nn.Module):
ffn_dim,
quant_config=quant_config,
prefix=f"{prefix}.ff",
fuse_swiglu=fuse_swiglu,
)
self.adaln_proj = MiniMaxH3AdaLayerNormModulation(
time_embed_dim,
@@ -423,6 +471,7 @@ class MiniMaxH3TransformerBlock(nn.Module):
prefix=f"{prefix}.adaln_proj",
apply_silu=adaln_apply_silu,
)
self.fuse_modulate = fuse_modulate
def forward(
self,
@@ -435,19 +484,39 @@ class MiniMaxH3TransformerBlock(nn.Module):
shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = (
t.to(hidden_states.dtype) for t in self.adaln_proj(temb))
residual = hidden_states
norm_hidden_states = self.norm1(hidden_states)
norm_hidden_states = norm_hidden_states * (
1.0 + scale_msa.index_select(0, adaln_indices)) + shift_msa.index_select(0, adaln_indices)
use_modulate_fusion = self.fuse_modulate and _can_run_minimax_h3_fusion(hidden_states)
if use_modulate_fusion:
norm_hidden_states = fused_rmsnorm_modulate(
hidden_states,
self.norm1.weight,
scale_msa,
shift_msa,
adaln_indices,
self.norm1.eps,
)
else:
norm_hidden_states = self.norm1(hidden_states)
norm_hidden_states = norm_hidden_states * (
1.0 + scale_msa.index_select(0, adaln_indices)) + shift_msa.index_select(0, adaln_indices)
attention_output = self.attn(norm_hidden_states, rotary_emb, original_seq_len)
hidden_states = residual + gate_msa.index_select(0, adaln_indices) * attention_output
residual = hidden_states
norm_hidden_states = self.norm2(hidden_states)
norm_hidden_states = norm_hidden_states * (
1.0 + scale_mlp.index_select(0, adaln_indices)) + shift_mlp.index_select(0, adaln_indices)
if use_modulate_fusion:
hidden_states, norm_hidden_states = fused_residual_gate_rmsnorm_modulate(
hidden_states,
attention_output,
gate_msa,
self.norm2.weight,
scale_mlp,
shift_mlp,
adaln_indices,
self.norm2.eps,
)
else:
hidden_states = hidden_states + gate_msa.index_select(0, adaln_indices) * attention_output
norm_hidden_states = self.norm2(hidden_states)
norm_hidden_states = norm_hidden_states * (
1.0 + scale_mlp.index_select(0, adaln_indices)) + shift_mlp.index_select(0, adaln_indices)
feed_forward_output = self.ff(norm_hidden_states)
return residual + gate_mlp.index_select(0, adaln_indices) * feed_forward_output
return hidden_states + gate_mlp.index_select(0, adaln_indices) * feed_forward_output
class MiniMaxH3Transformer3DModel(BaseDiT):
@@ -493,6 +562,7 @@ class MiniMaxH3Transformer3DModel(BaseDiT):
def __init__(self, config: MiniMaxH3Config, hf_config: dict[str, Any]) -> None:
super().__init__(config, hf_config)
arch = config.arch_config
self.enabled_fusions = _enabled_minimax_h3_fusions()
sp_world_size = get_sp_world_size() if model_parallel_is_initialized() else 1
if arch.num_attention_heads % sp_world_size:
raise ValueError(f"MiniMax H3 attention heads ({arch.num_attention_heads}) must be divisible by "
@@ -590,6 +660,9 @@ class MiniMaxH3Transformer3DModel(BaseDiT):
config.quant_config,
prefix=f"{config.prefix}.transformer_blocks.{index}",
adaln_apply_silu=self.adaln_rank is None,
fuse_modulate="modulate" in self.enabled_fusions,
fuse_qknorm_rope="qknorm_rope" in self.enabled_fusions,
fuse_swiglu="swiglu" in self.enabled_fusions,
) for index in range(arch.num_layers)
])
self.norm_out = MiniMaxH3AdaLayerNormOut(
@@ -0,0 +1,19 @@
# SPDX-License-Identifier: Apache-2.0
"""Inference-only MiniMax H3 fusions adapted from NVlabs/Sana Sol-Engine.
Source: https://github.com/NVlabs/Sana/tree/sol-engine/models/minimax_h3/GB200
"""
from .modulation import (
fused_residual_gate_rmsnorm_modulate,
fused_rmsnorm_modulate,
)
from .qknorm_rope import fused_qknorm_rope
from .swiglu import minimax_h3_swiglu
__all__ = [
"fused_qknorm_rope",
"fused_residual_gate_rmsnorm_modulate",
"fused_rmsnorm_modulate",
"minimax_h3_swiglu",
]
@@ -0,0 +1,292 @@
# SPDX-License-Identifier: Apache-2.0
"""MiniMax H3 RMSNorm and row-indexed modulation fusions."""
from __future__ import annotations
import math
import torch
try:
import triton
import triton.language as tl
except ImportError as exc: # pragma: no cover - depends on the runtime image
triton = None
tl = None
_TRITON_IMPORT_ERROR: ImportError | None = exc
else:
_TRITON_IMPORT_ERROR = None
__all__ = [
"fused_residual_gate_rmsnorm_modulate",
"fused_rmsnorm_modulate",
]
_SUPPORTED_DTYPES = (torch.float16, torch.bfloat16, torch.float32)
_rmsnorm_modulate_kernel = None
_residual_gate_rmsnorm_modulate_kernel = None
if triton is not None:
@triton.jit
def _rmsnorm_modulate_kernel(
out_ptr,
x_ptr,
weight_ptr,
scale_ptr,
shift_ptr,
index_ptr,
n_cols,
n_index,
eps,
stride_x_row,
stride_scale_row,
stride_shift_row,
BLOCK: tl.constexpr,
):
row = tl.program_id(0).to(tl.int64)
cols = tl.arange(0, BLOCK)
mask = cols < n_cols
x_offsets = row * stride_x_row + cols
table_row = tl.load(index_ptr + row % n_index).to(tl.int64)
x = tl.load(x_ptr + x_offsets, mask=mask, other=0.0).to(tl.float32)
variance = tl.sum(x * x, axis=0) / n_cols
normed = x * tl.math.rsqrt(variance + eps)
weight = tl.load(weight_ptr + cols, mask=mask, other=0.0).to(tl.float32)
scale = tl.load(
scale_ptr + table_row * stride_scale_row + cols,
mask=mask,
other=0.0,
).to(tl.float32)
shift = tl.load(
shift_ptr + table_row * stride_shift_row + cols,
mask=mask,
other=0.0,
).to(tl.float32)
output = normed * weight * (1.0 + scale) + shift
tl.store(out_ptr + x_offsets, output.to(out_ptr.dtype.element_ty), mask=mask)
@triton.jit
def _residual_gate_rmsnorm_modulate_kernel(
hidden_out_ptr,
normed_out_ptr,
residual_ptr,
branch_ptr,
gate_ptr,
weight_ptr,
scale_ptr,
shift_ptr,
index_ptr,
n_cols,
n_index,
eps,
stride_input_row,
stride_gate_row,
stride_scale_row,
stride_shift_row,
BLOCK: tl.constexpr,
):
row = tl.program_id(0).to(tl.int64)
cols = tl.arange(0, BLOCK)
mask = cols < n_cols
input_offsets = row * stride_input_row + cols
table_row = tl.load(index_ptr + row % n_index).to(tl.int64)
residual = tl.load(residual_ptr + input_offsets, mask=mask, other=0.0).to(tl.float32)
branch = tl.load(branch_ptr + input_offsets, mask=mask, other=0.0).to(tl.float32)
gate = tl.load(
gate_ptr + table_row * stride_gate_row + cols,
mask=mask,
other=0.0,
).to(tl.float32)
hidden = residual + gate * branch
tl.store(hidden_out_ptr + input_offsets, hidden.to(hidden_out_ptr.dtype.element_ty), mask=mask)
variance = tl.sum(hidden * hidden, axis=0) / n_cols
normed = hidden * tl.math.rsqrt(variance + eps)
weight = tl.load(weight_ptr + cols, mask=mask, other=0.0).to(tl.float32)
scale = tl.load(
scale_ptr + table_row * stride_scale_row + cols,
mask=mask,
other=0.0,
).to(tl.float32)
shift = tl.load(
shift_ptr + table_row * stride_shift_row + cols,
mask=mask,
other=0.0,
).to(tl.float32)
output = normed * weight * (1.0 + scale) + shift
tl.store(normed_out_ptr + input_offsets, output.to(normed_out_ptr.dtype.element_ty), mask=mask)
def _validate_contract(
x: torch.Tensor,
weight: torch.Tensor,
tables: tuple[torch.Tensor, ...],
index: torch.Tensor,
eps: float,
) -> None:
if x.ndim < 2:
raise ValueError(f"x must have shape (..., sequence_length, hidden_size), got {tuple(x.shape)}.")
if x.numel() == 0 or x.shape[-1] == 0:
raise ValueError("x must not be empty.")
if x.dtype not in _SUPPORTED_DTYPES:
raise TypeError(f"x must use float16, bfloat16, or float32, got {x.dtype}.")
hidden_size = x.shape[-1]
sequence_length = x.shape[-2]
if weight.shape != (hidden_size, ):
raise ValueError(f"weight must have shape ({hidden_size},), got {tuple(weight.shape)}.")
if weight.dtype not in _SUPPORTED_DTYPES:
raise TypeError(f"weight must use float16, bfloat16, or float32, got {weight.dtype}.")
if index.ndim != 1 or index.numel() != sequence_length:
raise ValueError(
f"index must have shape ({sequence_length},) so it can wrap over batch rows, got {tuple(index.shape)}."
)
if index.dtype not in (torch.int32, torch.int64):
raise TypeError(f"index must use int32 or int64, got {index.dtype}.")
if not isinstance(eps, (float, int)) or isinstance(eps, bool) or not math.isfinite(eps) or eps <= 0:
raise ValueError(f"eps must be a positive finite number, got {eps!r}.")
table_rows = tables[0].shape[0] if tables and tables[0].ndim == 2 else None
for name, table in zip(("gate", "scale", "shift")[-len(tables):], tables, strict=True):
if table.ndim != 2 or table.shape[1] != hidden_size:
raise ValueError(f"{name} must have shape (table_rows, {hidden_size}), got {tuple(table.shape)}.")
if table.shape[0] == 0 or table.shape[0] != table_rows:
raise ValueError("all modulation tables must have the same non-zero row count.")
if table.dtype not in _SUPPORTED_DTYPES:
raise TypeError(f"{name} must use float16, bfloat16, or float32, got {table.dtype}.")
tensors = (x, weight, *tables, index)
if any(tensor.device != x.device for tensor in tensors[1:]):
raise ValueError("x, weight, modulation tables, and index must be on the same device.")
def _validate_residual_branch(residual: torch.Tensor, branch: torch.Tensor) -> None:
if branch.shape != residual.shape:
raise ValueError(f"branch must match residual shape {tuple(residual.shape)}, got {tuple(branch.shape)}.")
if branch.dtype != residual.dtype:
raise TypeError(f"branch dtype must match residual dtype {residual.dtype}, got {branch.dtype}.")
if branch.device != residual.device:
raise ValueError("branch and residual must be on the same device.")
def _require_triton_cuda(x: torch.Tensor) -> None:
if triton is None:
detail = f": {_TRITON_IMPORT_ERROR}" if _TRITON_IMPORT_ERROR is not None else ""
raise RuntimeError(f"MiniMax H3 modulation fusion requires Triton{detail}.")
if x.device.type != "cuda":
raise RuntimeError(f"MiniMax H3 modulation fusion requires CUDA tensors, got device {x.device}.")
def _require_forward_only(*tensors: torch.Tensor) -> None:
if torch.is_grad_enabled() and any(tensor.requires_grad for tensor in tensors):
raise RuntimeError("MiniMax H3 modulation fusion is forward-only and does not support autograd.")
def _next_power_of_two(value: int) -> int:
return 1 << (value - 1).bit_length()
def _num_warps(block_size: int) -> int:
if block_size >= 8192:
return 16
if block_size >= 2048:
return 8
return 4
def _row_addressable(table: torch.Tensor) -> torch.Tensor:
return table if table.stride(-1) == 1 else table.contiguous()
def fused_rmsnorm_modulate(
x: torch.Tensor,
weight: torch.Tensor,
scale: torch.Tensor,
shift: torch.Tensor,
index: torch.Tensor,
eps: float,
) -> torch.Tensor:
"""Run RMSNorm and row-indexed modulation in one strict Triton kernel."""
_validate_contract(x, weight, (scale, shift), index, eps)
_require_forward_only(x, weight, scale, shift)
_require_triton_cuda(x)
hidden_size = x.shape[-1]
flat_x = x.reshape(-1, hidden_size).contiguous()
weight = weight.contiguous()
scale = _row_addressable(scale)
shift = _row_addressable(shift)
index = index.contiguous()
output = torch.empty_like(flat_x)
block_size = _next_power_of_two(hidden_size)
_rmsnorm_modulate_kernel[(flat_x.shape[0], )](
output,
flat_x,
weight,
scale,
shift,
index,
hidden_size,
index.numel(),
eps,
flat_x.stride(0),
scale.stride(0),
shift.stride(0),
BLOCK=block_size,
num_warps=_num_warps(block_size),
)
return output.view_as(x)
def fused_residual_gate_rmsnorm_modulate(
residual: torch.Tensor,
branch: torch.Tensor,
gate: torch.Tensor,
weight: torch.Tensor,
scale: torch.Tensor,
shift: torch.Tensor,
index: torch.Tensor,
eps: float,
) -> tuple[torch.Tensor, torch.Tensor]:
"""Fuse residual update, row-indexed gate, RMSNorm, and modulation."""
_validate_residual_branch(residual, branch)
_validate_contract(residual, weight, (gate, scale, shift), index, eps)
_require_forward_only(residual, branch, gate, weight, scale, shift)
_require_triton_cuda(residual)
hidden_size = residual.shape[-1]
flat_residual = residual.reshape(-1, hidden_size).contiguous()
flat_branch = branch.reshape(-1, hidden_size).contiguous()
weight = weight.contiguous()
gate = _row_addressable(gate)
scale = _row_addressable(scale)
shift = _row_addressable(shift)
index = index.contiguous()
hidden = torch.empty_like(flat_residual)
modulated = torch.empty_like(flat_residual)
block_size = _next_power_of_two(hidden_size)
_residual_gate_rmsnorm_modulate_kernel[(flat_residual.shape[0], )](
hidden,
modulated,
flat_residual,
flat_branch,
gate,
weight,
scale,
shift,
index,
hidden_size,
index.numel(),
eps,
flat_residual.stride(0),
gate.stride(0),
scale.stride(0),
shift.stride(0),
BLOCK=block_size,
num_warps=_num_warps(block_size),
)
return hidden.view_as(residual), modulated.view_as(residual)
@@ -0,0 +1,165 @@
# SPDX-License-Identifier: Apache-2.0
"""Fused per-head RMSNorm and partial rotary embedding for MiniMax H3."""
from __future__ import annotations
import math
import torch
try:
import triton
import triton.language as tl
HAVE_TRITON = True
except ImportError: # pragma: no cover - exercised only in environments without Triton
triton = None
tl = None
HAVE_TRITON = False
if HAVE_TRITON:
@triton.jit
def _qknorm_partial_rope_kernel(
out_ptr,
x_ptr,
weight_ptr,
cos_ptr,
sin_ptr,
head_dim,
rotary_dim,
half_rotary_dim,
num_heads,
seq_len,
eps,
BLOCK_SIZE: tl.constexpr,
):
row = tl.program_id(0)
seq_index = (row // num_heads) % seq_len
cols = tl.arange(0, BLOCK_SIZE)
head_mask = cols < head_dim
row_offset = row * head_dim
x = tl.load(x_ptr + row_offset + cols, mask=head_mask, other=0.0).to(tl.float32)
variance = tl.sum(x * x, axis=0) / head_dim
inv_rms = tl.math.rsqrt(variance + eps)
weight = tl.load(weight_ptr + cols, mask=head_mask, other=0.0).to(tl.float32)
normalized = x * inv_rms * weight
rotary_mask = cols < rotary_dim
first_half = cols < half_rotary_dim
partner_col = tl.where(first_half, cols + half_rotary_dim, cols - half_rotary_dim)
partner_x = tl.load(
x_ptr + row_offset + partner_col,
mask=rotary_mask,
other=0.0,
).to(tl.float32)
partner_weight = tl.load(weight_ptr + partner_col, mask=rotary_mask, other=0.0).to(tl.float32)
partner_normalized = partner_x * inv_rms * partner_weight
rotated = tl.where(first_half, -partner_normalized, partner_normalized)
table_offset = seq_index * rotary_dim + cols
cos = tl.load(cos_ptr + table_offset, mask=rotary_mask, other=1.0).to(tl.float32)
sin = tl.load(sin_ptr + table_offset, mask=rotary_mask, other=0.0).to(tl.float32)
rotary_output = normalized * cos + rotated * sin
output = tl.where(rotary_mask, rotary_output, normalized)
tl.store(out_ptr + row_offset + cols, output.to(out_ptr.dtype.element_ty), mask=head_mask)
_SUPPORTED_DTYPES = (torch.float16, torch.bfloat16, torch.float32)
def _validate_inputs(
x: torch.Tensor,
weight: torch.Tensor,
cos: torch.Tensor,
sin: torch.Tensor,
eps: float,
) -> tuple[int, int, int, int, int]:
for name, tensor in (("x", x), ("weight", weight), ("cos", cos), ("sin", sin)):
if not isinstance(tensor, torch.Tensor):
raise TypeError(f"{name} must be a torch.Tensor, got {type(tensor).__name__}")
if x.ndim != 4:
raise ValueError(f"x must have shape (batch, seq, heads, head_dim), got {tuple(x.shape)}")
batch, seq_len, num_heads, head_dim = x.shape
if min(batch, seq_len, num_heads, head_dim) <= 0:
raise ValueError(f"x dimensions must all be positive, got {tuple(x.shape)}")
if weight.shape != (head_dim, ):
raise ValueError(f"weight must have shape ({head_dim},), got {tuple(weight.shape)}")
if cos.ndim != 2:
raise ValueError(f"cos must have shape (seq, rotary_dim), got {tuple(cos.shape)}")
if sin.shape != cos.shape:
raise ValueError(f"sin must match cos shape {tuple(cos.shape)}, got {tuple(sin.shape)}")
if cos.shape[0] != seq_len:
raise ValueError(f"cos/sin sequence length must be {seq_len}, got {cos.shape[0]}")
rotary_dim = cos.shape[1]
if rotary_dim <= 0:
raise ValueError(f"rotary_dim must be positive, got {rotary_dim}")
if rotary_dim > head_dim:
raise ValueError(f"rotary_dim must not exceed head_dim, got rotary_dim={rotary_dim}, head_dim={head_dim}")
if rotary_dim % 2:
raise ValueError(f"rotary_dim must be even, got {rotary_dim}")
if x.dtype not in _SUPPORTED_DTYPES:
raise TypeError(f"x dtype must be float16, bfloat16, or float32, got {x.dtype}")
for name, tensor in (("weight", weight), ("cos", cos), ("sin", sin)):
if tensor.dtype != x.dtype:
raise TypeError(f"{name} dtype must match x dtype {x.dtype}, got {tensor.dtype}")
if tensor.device != x.device:
raise ValueError(f"{name} device must match x device {x.device}, got {tensor.device}")
if not isinstance(eps, (float, int)) or not math.isfinite(float(eps)) or eps <= 0:
raise ValueError(f"eps must be a positive finite number, got {eps!r}")
return batch, seq_len, num_heads, head_dim, rotary_dim
def fused_qknorm_rope(
x: torch.Tensor,
weight: torch.Tensor,
cos: torch.Tensor,
sin: torch.Tensor,
eps: float,
) -> torch.Tensor:
"""Run per-head RMSNorm and partial RoPE in one Sol-Engine-style kernel.
RMSNorm reduction and RoPE arithmetic stay in FP32 registers until the
final store. Triton's reduction order and the absence of eager's BF16
intermediate materializations can produce small, expected rounding drift.
"""
batch, seq_len, num_heads, head_dim, rotary_dim = _validate_inputs(x, weight, cos, sin, eps)
if not weight.is_contiguous():
raise ValueError("weight must be contiguous")
if not cos.is_contiguous() or not sin.is_contiguous():
raise ValueError("cos and sin must be contiguous (seq, rotary_dim) tables")
if not x.is_cuda:
raise RuntimeError("fused_qknorm_rope requires CUDA tensors")
if not HAVE_TRITON:
raise RuntimeError("fused_qknorm_rope requires Triton")
if torch.is_grad_enabled() and any(tensor.requires_grad for tensor in (x, weight, cos, sin)):
raise RuntimeError("fused_qknorm_rope is inference-only and does not implement autograd")
flat_x = x.reshape(-1, head_dim).contiguous()
flat_out = torch.empty_like(flat_x)
block_size = 1 << (head_dim - 1).bit_length()
_qknorm_partial_rope_kernel[(flat_x.shape[0], )](
flat_out,
flat_x,
weight,
cos,
sin,
head_dim,
rotary_dim,
rotary_dim // 2,
num_heads,
seq_len,
eps,
BLOCK_SIZE=block_size,
num_warps=4,
)
return flat_out.view(batch, seq_len, num_heads, head_dim)
__all__ = ["HAVE_TRITON", "fused_qknorm_rope"]
@@ -0,0 +1,104 @@
# SPDX-License-Identifier: Apache-2.0
"""MiniMax H3's value-first packed SwiGLU fusion."""
from __future__ import annotations
import torch
try:
import triton
import triton.language as tl
HAVE_TRITON = True
except ImportError: # pragma: no cover - exercised only in environments without Triton
triton = None
tl = None
HAVE_TRITON = False
def _validate_input(x: torch.Tensor) -> int:
if x.ndim == 0:
raise ValueError("MiniMax H3 SwiGLU expects at least one dimension")
packed_width = x.shape[-1]
if packed_width == 0 or packed_width % 2 != 0:
raise ValueError(
"MiniMax H3 SwiGLU expects a positive even last dimension containing packed (value, gate) halves, "
f"got {packed_width}"
)
if not x.is_floating_point():
raise TypeError(f"MiniMax H3 SwiGLU expects a floating-point tensor, got {x.dtype}")
return packed_width // 2
if HAVE_TRITON:
@triton.jit
def _minimax_h3_swiglu_kernel(
out_ptr,
x_ptr,
ffn_dim,
stride_in_row,
stride_out_row,
BLOCK_SIZE: tl.constexpr,
):
row = tl.program_id(0).to(tl.int64)
cols = tl.arange(0, BLOCK_SIZE)
mask = cols < ffn_dim
value = tl.load(x_ptr + row * stride_in_row + cols, mask=mask, other=0.0).to(tl.float32)
gate = tl.load(x_ptr + row * stride_in_row + ffn_dim + cols, mask=mask, other=0.0).to(tl.float32)
# Match Sol-Engine: keep the complete SwiGLU expression in FP32 and
# convert only the final output store.
out = value * (gate * tl.sigmoid(gate))
tl.store(out_ptr + row * stride_out_row + cols, out.to(out_ptr.dtype.element_ty), mask=mask)
else:
_minimax_h3_swiglu_kernel = None
def _num_warps(block_size: int) -> int:
if block_size >= 8192:
return 16
if block_size >= 2048:
return 8
return 4
def minimax_h3_swiglu(x: torch.Tensor) -> torch.Tensor:
"""Run the forward-only Triton fusion over an H3 ``(..., 2 * ffn_dim)`` input.
This is intentionally a strict kernel wrapper: callers own fallback policy and
must only invoke it for a supported CUDA inference path.
"""
ffn_dim = _validate_input(x)
if not x.is_cuda:
raise ValueError("MiniMax H3 fused SwiGLU requires a CUDA tensor")
if x.dtype not in (torch.float16, torch.bfloat16, torch.float32):
raise TypeError(f"MiniMax H3 fused SwiGLU supports float16, bfloat16, and float32, got {x.dtype}")
if torch.is_grad_enabled() and x.requires_grad:
raise RuntimeError("MiniMax H3 fused SwiGLU is forward-only and does not implement autograd")
if _minimax_h3_swiglu_kernel is None:
raise RuntimeError("MiniMax H3 fused SwiGLU requires Triton")
packed_width = x.shape[-1]
flat = x.reshape(-1, packed_width).contiguous()
output_shape = (*x.shape[:-1], ffn_dim)
if flat.shape[0] == 0:
return torch.empty(output_shape, dtype=x.dtype, device=x.device)
out = torch.empty((flat.shape[0], ffn_dim), dtype=x.dtype, device=x.device)
block_size = triton.next_power_of_2(ffn_dim)
_minimax_h3_swiglu_kernel[(flat.shape[0],)](
out,
flat,
ffn_dim,
flat.stride(0),
out.stride(0),
BLOCK_SIZE=block_size,
num_warps=_num_warps(block_size),
)
return out.view(output_shape)
__all__ = ["HAVE_TRITON", "minimax_h3_swiglu"]
@@ -0,0 +1,136 @@
# SPDX-License-Identifier: Apache-2.0
"""Focused routing and FA4 integration checks for MiniMax-H3 fusions."""
from __future__ import annotations
import pytest
import torch
from fastvideo.platforms import AttentionBackendEnum
@pytest.mark.parametrize(
("raw", "expected"),
[
("", frozenset()),
("0", frozenset()),
("none", frozenset()),
("all", frozenset({"modulate", "qknorm_rope", "swiglu"})),
("1", frozenset({"modulate", "qknorm_rope", "swiglu"})),
("swiglu, modulate", frozenset({"swiglu", "modulate"})),
],
)
def test_minimax_h3_fusion_selector(raw: str, expected: frozenset[str]) -> None:
from fastvideo.models.dits.minimax_h3 import _enabled_minimax_h3_fusions
assert _enabled_minimax_h3_fusions(raw) == expected
def test_minimax_h3_fusion_selector_rejects_unknown_name() -> None:
from fastvideo.models.dits.minimax_h3 import _enabled_minimax_h3_fusions
with pytest.raises(ValueError, match="Unknown MiniMax H3 fusion"):
_enabled_minimax_h3_fusions("swiglu,unknown")
def test_swiglu_fusion_stays_on_eager_path_with_grad(monkeypatch: pytest.MonkeyPatch) -> None:
import fastvideo.models.dits.minimax_h3 as h3
def unexpected_kernel(_: torch.Tensor) -> torch.Tensor:
raise AssertionError("inference-only fusion ran with grad enabled")
monkeypatch.setattr(h3, "minimax_h3_swiglu", unexpected_kernel)
layer = h3.MiniMaxH3FeedForward(8, 16, fuse_swiglu=True)
inputs = torch.randn(2, 3, 8, requires_grad=True)
layer(inputs).sum().backward()
assert inputs.grad is not None
def test_all_minimax_h3_fusions_match_one_eager_block_under_fa4(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Exercise the real block wiring without loading any H3 checkpoint."""
if not torch.cuda.is_available() or not torch.cuda.is_bf16_supported():
pytest.skip("BF16 CUDA is required")
pytest.importorskip("triton")
flash_attn = pytest.importorskip("flash_attn")
if "fa4" not in getattr(flash_attn, "__version__", "").lower():
pytest.skip("the focused integration test requires the FA4 environment")
monkeypatch.setenv("FASTVIDEO_ATTENTION_BACKEND", "FLASH_ATTN")
monkeypatch.setenv("FASTVIDEO_FA4", "1")
monkeypatch.setenv("MASTER_ADDR", "127.0.0.1")
monkeypatch.setenv("MASTER_PORT", "29573")
monkeypatch.setenv("RANK", "0")
monkeypatch.setenv("WORLD_SIZE", "1")
monkeypatch.setenv("LOCAL_RANK", "0")
from fastvideo.distributed import cleanup_dist_env_and_memory, maybe_init_distributed_environment_and_model_parallel
from fastvideo.forward_context import set_forward_context
from fastvideo.models.dits.minimax_h3 import MiniMaxH3RotaryPosEmbed, MiniMaxH3TransformerBlock
maybe_init_distributed_environment_and_model_parallel(1, 1)
try:
kwargs = dict(
hidden_size=128,
num_attention_heads=1,
attention_head_dim=128,
ffn_dim=256,
time_embed_dim=64,
norm_eps=1e-5,
qk_norm_eps=1e-5,
supported_attention_backends=(AttentionBackendEnum.FLASH_ATTN, ),
quant_config=None,
prefix="minimax_h3.test_block",
)
previous_default_dtype = torch.get_default_dtype()
torch.set_default_dtype(torch.bfloat16)
try:
eager = MiniMaxH3TransformerBlock(**kwargs)
fused = MiniMaxH3TransformerBlock(
**kwargs,
fuse_modulate=True,
fuse_qknorm_rope=True,
fuse_swiglu=True,
)
finally:
torch.set_default_dtype(previous_default_dtype)
with torch.no_grad():
for name, parameter in eager.named_parameters():
if "norm" in name and name.endswith("weight"):
parameter.fill_(1.0)
elif parameter.ndim > 1:
torch.nn.init.normal_(parameter, mean=0.0, std=0.02)
else:
parameter.zero_()
fused.load_state_dict(eager.state_dict(), strict=True)
device = torch.device("cuda")
eager = eager.to(device=device, dtype=torch.bfloat16).eval()
fused = fused.to(device=device, dtype=torch.bfloat16).eval()
generator = torch.Generator(device=device).manual_seed(2026)
hidden_states = torch.randn(2, 12, 128, generator=generator, device=device, dtype=torch.bfloat16)
temb = torch.randn(2, 64, generator=generator, device=device, dtype=torch.bfloat16)
adaln_indices = torch.arange(12, device=device, dtype=torch.long).remainder(6)
position_ids = torch.zeros(12, 3, device=device, dtype=torch.float32)
position_ids[:, 0] = torch.arange(12, device=device)
rotary_emb = MiniMaxH3RotaryPosEmbed(rope_freq_dim=16, rope_theta=10000.0).to(device)(position_ids)
inputs = dict(
hidden_states=hidden_states,
temb=temb,
adaln_indices=adaln_indices,
rotary_emb=tuple(value.to(torch.bfloat16) for value in rotary_emb),
original_seq_len=12,
)
with torch.inference_mode(), set_forward_context(current_timestep=0, attn_metadata=None):
eager_output = eager(**inputs)
fused_output = fused(**inputs)
# Sol-Engine keeps fused intermediates in FP32 registers until their
# final BF16 stores, so the opt-in path is close but not bit-identical.
torch.testing.assert_close(fused_output, eager_output, atol=3e-2, rtol=3e-2)
finally:
cleanup_dist_env_and_memory()
@@ -0,0 +1,173 @@
# SPDX-License-Identifier: Apache-2.0
import pytest
import torch
import torch.nn.functional as F
from fastvideo.models.dits.minimax_h3_fusions.modulation import (
fused_residual_gate_rmsnorm_modulate,
fused_rmsnorm_modulate,
)
EPS = 1e-6
SOL_ENGINE_BF16_TOLERANCE = 3e-2
def _chunk_tables(rows: int, hidden_size: int, *, device: torch.device | str = "cpu", dtype=torch.float32):
wide = torch.randn(rows, 6 * hidden_size, device=device, dtype=dtype)
tables = wide.chunk(6, dim=-1)
assert all(table.stride() == (6 * hidden_size, 1) for table in tables)
assert all(not table.is_contiguous() for table in tables)
return tables
def _eager_rmsnorm_modulate(x, weight, scale, shift, index):
normed = F.rms_norm(x, (x.shape[-1], ), weight, EPS)
return normed * (1.0 + scale.index_select(0, index)) + shift.index_select(0, index)
def _eager_residual_gate_rmsnorm_modulate(residual, branch, gate, weight, scale, shift, index):
hidden = residual + gate.index_select(0, index) * branch
normed = F.rms_norm(hidden, (hidden.shape[-1], ), weight, EPS)
modulated = normed * (1.0 + scale.index_select(0, index)) + shift.index_select(0, index)
return hidden, modulated
@pytest.mark.skipif(not torch.cuda.is_available(), reason="requires a CUDA GPU")
def test_bf16_sol_engine_fusions_match_production_eager_within_tolerance():
"""Sol-Engine keeps fused intermediates in FP32 until its output stores."""
pytest.importorskip("triton")
torch.manual_seed(2)
device = torch.device("cuda")
batch, sequence_length, hidden_size, table_rows = 2, 9, 5376, 6
residual = torch.randn(batch, sequence_length, hidden_size, device=device, dtype=torch.bfloat16)
branch = torch.randn_like(residual)
weight = torch.randn(hidden_size, device=device, dtype=torch.bfloat16)
shift, scale, gate, shift_mlp, scale_mlp, _ = _chunk_tables(
table_rows,
hidden_size,
device=device,
dtype=torch.bfloat16,
)
index = torch.tensor([5, 0, 4, 1, 3, 2, 5, 1, 0], device=device, dtype=torch.int64)
expected_norm1 = _eager_rmsnorm_modulate(residual, weight, scale, shift, index)
actual_norm1 = fused_rmsnorm_modulate(residual, weight, scale, shift, index, EPS)
expected_hidden, expected_norm2 = _eager_residual_gate_rmsnorm_modulate(
residual,
branch,
gate,
weight,
scale_mlp,
shift_mlp,
index,
)
actual_hidden, actual_norm2 = fused_residual_gate_rmsnorm_modulate(
residual,
branch,
gate,
weight,
scale_mlp,
shift_mlp,
index,
EPS,
)
# Production eager materializes BF16 after each PyTorch operator. The
# single-kernel Sol-Engine path deliberately removes those round points.
torch.testing.assert_close(
actual_norm1,
expected_norm1,
rtol=SOL_ENGINE_BF16_TOLERANCE,
atol=SOL_ENGINE_BF16_TOLERANCE,
)
torch.testing.assert_close(
actual_hidden,
expected_hidden,
rtol=SOL_ENGINE_BF16_TOLERANCE,
atol=SOL_ENGINE_BF16_TOLERANCE,
)
torch.testing.assert_close(
actual_norm2,
expected_norm2,
rtol=SOL_ENGINE_BF16_TOLERANCE,
atol=SOL_ENGINE_BF16_TOLERANCE,
)
@pytest.mark.parametrize(
("mutate", "error", "match"),
[
(lambda args: args | {"x": args["x"][0, 0]}, ValueError, "shape"),
(lambda args: args | {"weight": args["weight"][:-1]}, ValueError, "weight"),
(lambda args: args | {"index": args["index"][:-1]}, ValueError, "index"),
(lambda args: args | {"index": args["index"].float()}, TypeError, "index"),
(lambda args: args | {"eps": 0.0}, ValueError, "eps"),
],
)
def test_fusion_rejects_invalid_contracts(mutate, error, match):
args = {
"x": torch.randn(2, 3, 8),
"weight": torch.randn(8),
"scale": torch.randn(4, 8),
"shift": torch.randn(4, 8),
"index": torch.tensor([0, 3, 1]),
"eps": EPS,
}
with pytest.raises(error, match=match):
fused_rmsnorm_modulate(**mutate(args))
def test_residual_fusion_rejects_mismatched_branch():
residual = torch.randn(2, 3, 8)
with pytest.raises(ValueError, match="branch"):
fused_residual_gate_rmsnorm_modulate(
residual,
torch.randn(2, 2, 8),
torch.randn(4, 8),
torch.randn(8),
torch.randn(4, 8),
torch.randn(4, 8),
torch.tensor([0, 1, 2]),
EPS,
)
def test_triton_wrappers_fail_explicitly_on_cpu():
x = torch.randn(1, 2, 8)
branch = torch.randn_like(x)
weight = torch.randn(8)
gate = torch.randn(3, 8)
scale = torch.randn(3, 8)
shift = torch.randn(3, 8)
index = torch.tensor([0, 2])
with pytest.raises(RuntimeError, match="Triton|CUDA"):
fused_rmsnorm_modulate(x, weight, scale, shift, index, EPS)
with pytest.raises(RuntimeError, match="Triton|CUDA"):
fused_residual_gate_rmsnorm_modulate(x, branch, gate, weight, scale, shift, index, EPS)
def test_triton_wrappers_reject_autograd_before_backend_check():
x = torch.randn(1, 2, 8)
branch = torch.randn_like(x, requires_grad=True)
weight = torch.randn(8, requires_grad=True)
gate = torch.randn(3, 8)
scale = torch.randn(3, 8)
shift = torch.randn(3, 8)
index = torch.tensor([0, 2])
with pytest.raises(RuntimeError, match="forward-only"):
fused_rmsnorm_modulate(x, weight, scale, shift, index, EPS)
with pytest.raises(RuntimeError, match="forward-only"):
fused_residual_gate_rmsnorm_modulate(
x,
branch,
gate,
weight.detach(),
scale,
shift,
index,
EPS,
)
@@ -0,0 +1,150 @@
# SPDX-License-Identifier: Apache-2.0
import pytest
import torch
import torch.nn.functional as F
from fastvideo.models.dits.minimax_h3 import MiniMaxH3Attention
from fastvideo.models.dits.minimax_h3_fusions.qknorm_rope import (
HAVE_TRITON,
fused_qknorm_rope,
)
def _rotary_tables(
seq_len: int,
rotary_dim: int,
*,
dtype: torch.dtype,
device: torch.device | str,
) -> tuple[torch.Tensor, torch.Tensor]:
angles = torch.randn(seq_len, rotary_dim // 2, dtype=torch.float32, device=device)
angles = torch.cat((angles, angles), dim=-1)
return angles.cos().to(dtype), angles.sin().to(dtype)
def _eager_qknorm_rope(
x: torch.Tensor,
weight: torch.Tensor,
cos: torch.Tensor,
sin: torch.Tensor,
eps: float,
) -> torch.Tensor:
normalized = F.rms_norm(x, (x.shape[-1], ), weight, eps)
return MiniMaxH3Attention._apply_rotary_emb(normalized, (cos, sin))
def test_fused_qknorm_rope_rejects_invalid_rotary_dim() -> None:
x = torch.randn(2, 3, 4, 128)
weight = torch.ones(128)
cos, sin = _rotary_tables(3, 130, dtype=x.dtype, device=x.device)
with pytest.raises(ValueError, match="rotary_dim must not exceed head_dim"):
fused_qknorm_rope(x, weight, cos, sin, 1e-6)
cos = torch.randn(3, 95)
sin = torch.randn_like(cos)
with pytest.raises(ValueError, match="rotary_dim must be even"):
fused_qknorm_rope(x, weight, cos, sin, 1e-6)
def test_fused_qknorm_rope_rejects_shape_mismatches() -> None:
x = torch.randn(2, 3, 4, 128)
weight = torch.ones(128)
cos, sin = _rotary_tables(3, 96, dtype=x.dtype, device=x.device)
with pytest.raises(ValueError, match="x must have shape"):
fused_qknorm_rope(x[0], weight, cos, sin, 1e-6)
with pytest.raises(ValueError, match="weight must have shape"):
fused_qknorm_rope(x, weight[:-1], cos, sin, 1e-6)
with pytest.raises(ValueError, match="sequence length"):
fused_qknorm_rope(x, weight, cos[:-1], sin[:-1], 1e-6)
with pytest.raises(ValueError, match="sin must match cos shape"):
fused_qknorm_rope(x, weight, cos, sin[:, :-2], 1e-6)
@pytest.mark.parametrize("noncontiguous_input", ["weight", "cos", "sin"])
def test_fused_qknorm_rope_rejects_noncontiguous_linear_inputs(noncontiguous_input: str) -> None:
x = torch.randn(2, 3, 4, 128)
weight = torch.ones(256)[::2]
cos = torch.randn(3, 192)[:, ::2]
sin = torch.randn(3, 192)[:, ::2]
assert not weight.is_contiguous()
assert not cos.is_contiguous()
assert not sin.is_contiguous()
if noncontiguous_input != "weight":
weight = weight.contiguous()
if noncontiguous_input != "cos":
cos = cos.contiguous()
if noncontiguous_input != "sin":
sin = sin.contiguous()
with pytest.raises(ValueError, match="must be contiguous"):
fused_qknorm_rope(x, weight, cos, sin, 1e-6)
def test_fused_qknorm_rope_requires_matching_precast_dtype() -> None:
x = torch.randn(2, 3, 4, 128, dtype=torch.float32)
weight = torch.ones(128, dtype=torch.float32)
cos, sin = _rotary_tables(3, 96, dtype=torch.bfloat16, device=x.device)
with pytest.raises(TypeError, match="cos dtype must match x dtype"):
fused_qknorm_rope(x, weight, cos, sin, 1e-6)
def test_fused_qknorm_rope_does_not_accept_missing_rotary_tables() -> None:
x = torch.randn(2, 3, 4, 128)
weight = torch.ones(128)
with pytest.raises(TypeError, match="cos must be a torch.Tensor"):
fused_qknorm_rope(x, weight, None, None, 1e-6) # type: ignore[arg-type]
def test_fused_qknorm_rope_requires_cuda() -> None:
x = torch.randn(2, 3, 4, 128)
weight = torch.ones(128)
cos, sin = _rotary_tables(3, 96, dtype=x.dtype, device=x.device)
with pytest.raises(RuntimeError, match="requires CUDA"):
fused_qknorm_rope(x, weight, cos, sin, 1e-6)
@pytest.mark.parametrize(
"rotary_dim,shape,use_input_view",
[
pytest.param(96, (2, 11, 5, 128), False, id="partial-96-batch2-seq11-heads5"),
pytest.param(128, (2, 7, 3, 128), True, id="full-128-noncontiguous-input-view"),
],
)
def test_fused_qknorm_rope_matches_eager_bf16_cuda(
rotary_dim: int,
shape: tuple[int, ...],
use_input_view: bool,
) -> None:
if not torch.cuda.is_available():
pytest.skip("CUDA is required for the Triton fusion")
if not HAVE_TRITON:
pytest.skip("Triton is required for the fusion")
torch.manual_seed(1)
device = torch.device("cuda")
if use_input_view:
x = torch.randn(*shape[:-1], shape[-1] * 2, dtype=torch.bfloat16, device=device)[..., ::2]
assert not x.is_contiguous()
else:
x = torch.randn(shape, dtype=torch.bfloat16, device=device)
weight = (1.0 + 0.05 * torch.randn(shape[-1], dtype=torch.bfloat16, device=device)).contiguous()
cos, sin = _rotary_tables(shape[1], rotary_dim, dtype=x.dtype, device=device)
with torch.inference_mode():
actual = fused_qknorm_rope(x, weight, cos, sin, 1e-6)
expected = _eager_qknorm_rope(x, weight, cos, sin, 1e-6)
# The fused kernel keeps RMSNorm and both RoPE products in FP32 registers
# until its final BF16 store. Eager materializes BF16 intermediates, and
# PyTorch/Triton reductions need not use the same summation order.
torch.testing.assert_close(actual, expected, atol=2e-2, rtol=2e-2)
assert actual.shape == x.shape
assert actual.dtype == x.dtype
assert actual.is_contiguous()
@@ -0,0 +1,73 @@
# SPDX-License-Identifier: Apache-2.0
"""Focused tests for MiniMax H3's value-first packed SwiGLU fusion."""
from __future__ import annotations
import pytest
import torch
import torch.nn.functional as F
from fastvideo.models.dits.minimax_h3_fusions.swiglu import minimax_h3_swiglu
def _require_bf16_triton_cuda() -> None:
if not torch.cuda.is_available():
pytest.skip("MiniMax H3 fused SwiGLU requires CUDA")
if not torch.cuda.is_bf16_supported():
pytest.skip("MiniMax H3 fused SwiGLU parity requires BF16 support")
pytest.importorskip("triton", reason="MiniMax H3 fused SwiGLU requires Triton")
def _assert_bf16_parity(x: torch.Tensor) -> None:
value, gate = x.chunk(2, dim=-1)
expected = value * F.silu(gate)
actual = minimax_h3_swiglu(x)
assert actual.shape == (*x.shape[:-1], x.shape[-1] // 2)
assert actual.dtype == x.dtype
assert actual.device == x.device
# Sol-Engine keeps the full SwiGLU expression in FP32 until the output
# store, while eager F.silu materializes a BF16 intermediate.
torch.testing.assert_close(actual, expected, rtol=2e-2, atol=2e-2)
@pytest.mark.parametrize("last_dim", [0, 7])
def test_minimax_h3_swiglu_rejects_nonpositive_or_odd_last_dimension(last_dim: int) -> None:
x = torch.empty((2, last_dim), dtype=torch.float32)
with pytest.raises(ValueError, match="positive even last dimension"):
minimax_h3_swiglu(x)
def test_minimax_h3_swiglu_strict_wrapper_rejects_cpu() -> None:
with pytest.raises(ValueError, match="requires a CUDA tensor"):
minimax_h3_swiglu(torch.randn(2, 8))
@pytest.mark.gpu
def test_minimax_h3_swiglu_multidimensional_bf16_gpu_parity() -> None:
_require_bf16_triton_cuda()
torch.manual_seed(0)
x = torch.randn((2, 3, 5, 66), device="cuda", dtype=torch.bfloat16)
_assert_bf16_parity(x)
@pytest.mark.gpu
def test_minimax_h3_swiglu_noncontiguous_bf16_gpu_parity() -> None:
_require_bf16_triton_cuda()
torch.manual_seed(1)
storage = torch.randn((2, 3, 148), device="cuda", dtype=torch.bfloat16)
x = storage[..., ::2]
assert not x.is_contiguous()
_assert_bf16_parity(x)
@pytest.mark.gpu
def test_minimax_h3_swiglu_real_ffn_dim_bf16_gpu() -> None:
_require_bf16_triton_cuda()
torch.manual_seed(2)
ffn_dim = 14336
x = torch.randn((1, 2 * ffn_dim), device="cuda", dtype=torch.bfloat16)
_assert_bf16_parity(x)