Files
vLLM/vllm/model_executor/layers/mhc.py
T

666 lines
18 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
import inspect
import torch
# this import will also register the custom ops
# import vllm.model_executor.kernels.mhc # noqa: F401
import vllm.model_executor.kernels.mhc as mhc_kernels
from vllm._aiter_ops import is_aiter_found_and_supported
from vllm.model_executor.custom_op import CustomOp
from vllm.platforms import current_platform
from vllm.utils.import_utils import has_tilelang
def _has_tilelang_mhc() -> bool:
if not has_tilelang():
return False
if current_platform.is_cuda():
return True
if current_platform.is_rocm():
from vllm.platforms.rocm import on_gfx942
# TileLang MHC currently produces incorrect results on gfx942. Keep
# gfx942 on the existing torch/triton fallbacks until that path is fixed.
return not on_gfx942()
return False
HAS_TILELANG_MHC = _has_tilelang_mhc()
HAS_AITER_MHC = is_aiter_found_and_supported()
def _has_aiter_mhc_fused() -> bool:
if not HAS_AITER_MHC:
return False
try:
from aiter.ops.mhc import mhc_fused_post_pre
except Exception:
return False
return callable(mhc_fused_post_pre)
def _aiter_mhc_op_accepts_norm(op_name: str) -> bool:
if not HAS_AITER_MHC:
return False
try:
from aiter.ops import mhc as aiter_mhc
for candidate_name in (op_name, f"{op_name}_fake"):
op = getattr(aiter_mhc, candidate_name, None)
if op is None:
continue
parameters = inspect.signature(op).parameters
if "norm_weight" in parameters and "norm_eps" in parameters:
return True
except (AttributeError, ImportError, TypeError, ValueError):
pass
return False
HAS_AITER_MHC_FUSED = _has_aiter_mhc_fused()
HAS_AITER_MHC_PRE_NORM = _aiter_mhc_op_accepts_norm("mhc_pre")
HAS_AITER_MHC_FUSED_NORM = _aiter_mhc_op_accepts_norm("mhc_fused_post_pre")
def _aiter_mhc_supported(
residual: torch.Tensor,
norm_weight: torch.Tensor | None,
*,
supports_norm: bool,
) -> bool:
hidden_size = residual.shape[-1]
hc_mult = residual.shape[-2]
return (
HAS_AITER_MHC
and hidden_size % 256 == 0
and hc_mult == 4
and (norm_weight is None or supports_norm)
)
def _apply_mhc_norm(
layer_input: torch.Tensor,
norm_weight: torch.Tensor | None,
norm_eps: float,
) -> torch.Tensor:
if norm_weight is None:
return layer_input
from vllm import ir
return ir.ops.rms_norm(layer_input, norm_weight, norm_eps)
# --8<-- [start:mhc_pre]
@CustomOp.register("mhc_pre")
class MHCPreOp(CustomOp):
"""MHC pre block.
Computes mix logits from RMS-normalized HC residual streams, then
returns post_mix, comb_mix, and
layer_input = sum_i pre_mix_i * residual_i.
"""
# --8<-- [end:mhc_pre]
@classmethod
def enabled(cls) -> bool:
return True
def forward_cuda(
self,
residual: torch.Tensor,
fn: torch.Tensor,
hc_scale: torch.Tensor,
hc_base: torch.Tensor,
rms_eps: float,
hc_pre_eps: float,
hc_sinkhorn_eps: float,
hc_post_mult_value: float,
sinkhorn_repeat: int,
n_splits: int = 1,
norm_weight: torch.Tensor | None = None,
norm_eps: float = 0.0,
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
return torch.ops.vllm.mhc_pre_tilelang(
residual,
fn,
hc_scale,
hc_base,
rms_eps,
hc_pre_eps,
hc_sinkhorn_eps,
hc_post_mult_value,
sinkhorn_repeat,
n_splits,
norm_weight,
norm_eps,
)
def forward_hip(
self,
residual: torch.Tensor,
fn: torch.Tensor,
hc_scale: torch.Tensor,
hc_base: torch.Tensor,
rms_eps: float,
hc_pre_eps: float,
hc_sinkhorn_eps: float,
hc_post_mult_value: float,
sinkhorn_repeat: int,
n_splits: int = 1,
norm_weight: torch.Tensor | None = None,
norm_eps: float = 0.0,
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
if _aiter_mhc_supported(
residual,
norm_weight,
supports_norm=HAS_AITER_MHC_PRE_NORM,
):
return torch.ops.vllm.mhc_pre_aiter(
residual,
fn,
hc_scale,
hc_base,
rms_eps,
hc_pre_eps,
hc_sinkhorn_eps,
hc_post_mult_value,
sinkhorn_repeat,
n_splits,
norm_weight,
norm_eps,
)
elif HAS_TILELANG_MHC:
return torch.ops.vllm.mhc_pre_tilelang(
residual,
fn,
hc_scale,
hc_base,
rms_eps,
hc_pre_eps,
hc_sinkhorn_eps,
hc_post_mult_value,
sinkhorn_repeat,
n_splits,
norm_weight,
norm_eps,
)
else:
post_mix, comb_mix, layer_input = self.forward_native(
residual,
fn,
hc_scale,
hc_base,
rms_eps,
hc_pre_eps,
hc_sinkhorn_eps,
hc_post_mult_value,
sinkhorn_repeat,
n_splits,
norm_weight,
norm_eps,
)
return (
post_mix,
comb_mix,
_apply_mhc_norm(layer_input, norm_weight, norm_eps),
)
def forward_native(
self,
residual: torch.Tensor,
fn: torch.Tensor,
hc_scale: torch.Tensor,
hc_base: torch.Tensor,
rms_eps: float,
hc_pre_eps: float,
hc_sinkhorn_eps: float,
hc_post_mult_value: float,
sinkhorn_repeat: int,
n_splits: int = 1,
norm_weight: torch.Tensor | None = None,
norm_eps: float = 0.0,
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
return mhc_kernels.mhc_pre_torch(
residual,
fn,
hc_scale,
hc_base,
rms_eps,
hc_pre_eps,
hc_sinkhorn_eps,
hc_post_mult_value,
sinkhorn_repeat,
)
def forward_xpu(
self,
residual: torch.Tensor,
fn: torch.Tensor,
hc_scale: torch.Tensor,
hc_base: torch.Tensor,
rms_eps: float,
hc_pre_eps: float,
hc_sinkhorn_eps: float,
hc_post_mult_value: float,
sinkhorn_repeat: int,
n_splits: int = 1,
norm_weight: torch.Tensor | None = None,
norm_eps: float = 0.0,
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
return torch.ops._xpu_C.mhc_pre(
residual,
fn,
hc_scale,
hc_base,
rms_eps,
hc_pre_eps,
hc_sinkhorn_eps,
hc_post_mult_value,
sinkhorn_repeat,
)
# --8<-- [start:mhc_post]
@CustomOp.register("mhc_post")
class MHCPostOp(CustomOp):
"""MHC post block.
Combines the layer output with the HC residual streams:
out_j = post_layer_mix_j * x + sum_i comb_res_mix_ij * residual_i.
"""
# --8<-- [end:mhc_post]
@classmethod
def enabled(cls) -> bool:
return True
def forward_cuda(
self,
x: torch.Tensor,
residual: torch.Tensor,
post_layer_mix: torch.Tensor,
comb_res_mix: torch.Tensor,
) -> torch.Tensor:
return torch.ops.vllm.mhc_post_tilelang(
x, residual, post_layer_mix, comb_res_mix
)
def forward_hip(
self,
x: torch.Tensor,
residual: torch.Tensor,
post_layer_mix: torch.Tensor,
comb_res_mix: torch.Tensor,
) -> torch.Tensor:
if _aiter_mhc_supported(residual, None, supports_norm=True):
return torch.ops.vllm.mhc_post_aiter(
x,
residual,
post_layer_mix,
comb_res_mix,
)
if HAS_TILELANG_MHC:
return torch.ops.vllm.mhc_post_tilelang(
x, residual, post_layer_mix, comb_res_mix
)
else:
return self.forward_native(x, residual, post_layer_mix, comb_res_mix)
def forward_native(
self,
x: torch.Tensor,
residual: torch.Tensor,
post_layer_mix: torch.Tensor,
comb_res_mix: torch.Tensor,
) -> torch.Tensor:
return mhc_kernels.mhc_post_torch(
x,
residual,
post_layer_mix,
comb_res_mix,
)
def forward_xpu(
self,
x: torch.Tensor,
residual: torch.Tensor,
post_layer_mix: torch.Tensor,
comb_res_mix: torch.Tensor,
) -> torch.Tensor:
return torch.ops._xpu_C.mhc_post(
x,
residual,
post_layer_mix,
comb_res_mix,
)
# --8<-- [start:hc_head]
@CustomOp.register("hc_head")
class HCHeadOp(CustomOp):
"""HC head reduction for DeepSeek V4.
Computes gates from the RMS-normalized flattened HC residual and
returns out = sum_i gate_i * residual_i, collapsing hc_mult streams
to one.
"""
# --8<-- [end:hc_head]
@classmethod
def enabled(cls) -> bool:
return True
def forward_cuda(
self,
hidden_states: torch.Tensor,
hc_fn: torch.Tensor,
hc_scale: torch.Tensor,
hc_base: torch.Tensor,
rms_norm_eps: float,
hc_eps: float,
) -> torch.Tensor:
hc_mult, hidden_size = hidden_states.shape[-2:]
outer_shape = hidden_states.shape[:-2]
hs_flat = hidden_states.view(-1, hc_mult, hidden_size)
out = torch.ops.vllm.hc_head_fused_kernel_tilelang(
hs_flat,
hc_fn,
hc_scale,
hc_base,
rms_norm_eps,
hc_eps,
)
return out.view(*outer_shape, hidden_size)
def forward_hip(
self,
hidden_states: torch.Tensor,
hc_fn: torch.Tensor,
hc_scale: torch.Tensor,
hc_base: torch.Tensor,
rms_norm_eps: float,
hc_eps: float,
) -> torch.Tensor:
hc_mult, hidden_size = hidden_states.shape[-2:]
outer_shape = hidden_states.shape[:-2]
hs_flat = hidden_states.view(-1, hc_mult, hidden_size)
if HAS_TILELANG_MHC:
out = torch.ops.vllm.hc_head_fused_kernel_tilelang(
hs_flat,
hc_fn,
hc_scale,
hc_base,
rms_norm_eps,
hc_eps,
)
else:
num_tokens = hs_flat.shape[0]
out = torch.empty(
num_tokens,
hidden_size,
dtype=torch.bfloat16,
device=hidden_states.device,
)
torch.ops.vllm.hc_head_triton(
hs_flat,
hc_fn,
hc_scale,
hc_base,
out,
hidden_size,
rms_norm_eps,
hc_eps,
hc_mult,
)
return out.view(*outer_shape, hidden_size)
def forward_native(self, *args, **kwargs):
raise NotImplementedError("Native implementation of hc_head is not available")
def forward_xpu(
self,
hidden_states: torch.Tensor,
hc_fn: torch.Tensor,
hc_scale: torch.Tensor,
hc_base: torch.Tensor,
rms_norm_eps: float,
hc_eps: float,
) -> torch.Tensor:
hc_mult, hidden_size = hidden_states.shape[-2:]
outer_shape = hidden_states.shape[:-2]
hs_flat = hidden_states.view(-1, hc_mult, hidden_size)
num_tokens = hs_flat.shape[0]
out = torch.empty(
num_tokens, hidden_size, dtype=torch.bfloat16, device=hidden_states.device
)
torch.ops._xpu_C.hc_head_fused(
hs_flat, hc_fn, hc_scale, hc_base, out, rms_norm_eps, hc_eps
)
return out.view(*outer_shape, hidden_size)
# --8<-- [start:mhc_fused_post_pre]
@CustomOp.register("mhc_fused_post_pre")
class MHCFusedPostPreOp(CustomOp):
"""Fused MHC post block followed by the next MHC pre block.
Equivalent to applying MHCPostOp and then MHCPreOp to the updated
residual streams, returning residual_cur, post_mix_cur, comb_mix_cur,
and layer_input_cur.
"""
# --8<-- [end:mhc_fused_post_pre]
@classmethod
def enabled(cls) -> bool:
return True
def forward_cuda(
self,
x: torch.Tensor,
residual: torch.Tensor,
post_layer_mix: torch.Tensor,
comb_res_mix: torch.Tensor,
fn: torch.Tensor,
hc_scale: torch.Tensor,
hc_base: torch.Tensor,
rms_eps: float,
hc_pre_eps: float,
hc_sinkhorn_eps: float,
hc_post_mult_value: float,
sinkhorn_repeat: int,
n_splits: int = 1,
tile_n: int = 1,
norm_weight: torch.Tensor | None = None,
norm_eps: float = 0.0,
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
return torch.ops.vllm.mhc_fused_post_pre_tilelang(
x,
residual,
post_layer_mix,
comb_res_mix,
fn,
hc_scale,
hc_base,
rms_eps,
hc_pre_eps,
hc_sinkhorn_eps,
hc_post_mult_value,
sinkhorn_repeat,
n_splits,
tile_n,
norm_weight,
norm_eps,
)
def forward_hip(
self,
x: torch.Tensor,
residual: torch.Tensor,
post_layer_mix: torch.Tensor,
comb_res_mix: torch.Tensor,
fn: torch.Tensor,
hc_scale: torch.Tensor,
hc_base: torch.Tensor,
rms_eps: float,
hc_pre_eps: float,
hc_sinkhorn_eps: float,
hc_post_mult_value: float,
sinkhorn_repeat: int,
n_splits: int = 1,
tile_n: int = 1,
norm_weight: torch.Tensor | None = None,
norm_eps: float = 0.0,
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
if HAS_AITER_MHC_FUSED and _aiter_mhc_supported(
residual,
norm_weight,
supports_norm=HAS_AITER_MHC_FUSED_NORM,
):
return torch.ops.vllm.mhc_fused_post_pre_aiter(
x,
residual,
post_layer_mix,
comb_res_mix,
fn,
hc_scale,
hc_base,
rms_eps,
hc_pre_eps,
hc_sinkhorn_eps,
hc_post_mult_value,
sinkhorn_repeat,
n_splits,
tile_n,
norm_weight,
norm_eps,
)
if HAS_TILELANG_MHC:
return torch.ops.vllm.mhc_fused_post_pre_tilelang(
x,
residual,
post_layer_mix,
comb_res_mix,
fn,
hc_scale,
hc_base,
rms_eps,
hc_pre_eps,
hc_sinkhorn_eps,
hc_post_mult_value,
sinkhorn_repeat,
n_splits,
tile_n,
norm_weight,
norm_eps,
)
residual_cur, post_mix_cur, comb_mix_cur, layer_input_cur = self.forward_native(
x,
residual,
post_layer_mix,
comb_res_mix,
fn,
hc_scale,
hc_base,
rms_eps,
hc_pre_eps,
hc_sinkhorn_eps,
hc_post_mult_value,
sinkhorn_repeat,
n_splits,
tile_n,
norm_weight,
norm_eps,
)
return (
residual_cur,
post_mix_cur,
comb_mix_cur,
_apply_mhc_norm(layer_input_cur, norm_weight, norm_eps),
)
def forward_native(
self,
x: torch.Tensor,
residual: torch.Tensor,
post_layer_mix: torch.Tensor,
comb_res_mix: torch.Tensor,
fn: torch.Tensor,
hc_scale: torch.Tensor,
hc_base: torch.Tensor,
rms_eps: float,
hc_pre_eps: float,
hc_sinkhorn_eps: float,
hc_post_mult_value: float,
sinkhorn_repeat: int,
n_splits: int = 1,
tile_n: int = 1,
norm_weight: torch.Tensor | None = None,
norm_eps: float = 0.0,
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
# Decompose into post + pre (no fused kernel available).
residual_cur = mhc_kernels.mhc_post_torch(
x, residual, post_layer_mix, comb_res_mix
)
post_mix_cur, comb_mix_cur, layer_input_cur = mhc_kernels.mhc_pre_torch(
residual_cur,
fn,
hc_scale,
hc_base,
rms_eps,
hc_pre_eps,
hc_sinkhorn_eps,
hc_post_mult_value,
sinkhorn_repeat,
)
return residual_cur, post_mix_cur, comb_mix_cur, layer_input_cur
def forward_xpu(
self,
x: torch.Tensor,
residual: torch.Tensor,
post_layer_mix: torch.Tensor,
comb_res_mix: torch.Tensor,
fn: torch.Tensor,
hc_scale: torch.Tensor,
hc_base: torch.Tensor,
rms_eps: float,
hc_pre_eps: float,
hc_sinkhorn_eps: float,
hc_post_mult_value: float,
sinkhorn_repeat: int,
n_splits: int = 1,
tile_n: int = 1,
norm_weight: torch.Tensor | None = None,
norm_eps: float = 0.0,
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
return torch.ops._xpu_C.mhc_fused_post_pre(
x,
residual,
post_layer_mix,
comb_res_mix,
fn,
hc_scale,
hc_base,
rms_eps,
hc_pre_eps,
hc_sinkhorn_eps,
hc_post_mult_value,
sinkhorn_repeat,
)
def hc_expand(x: torch.Tensor, n: int) -> torch.Tensor:
"""[s, hidden_size] -> [s, n * hidden_size] by replication."""
return x.unsqueeze(1).expand(-1, n, -1).contiguous()
def hc_contract(x: torch.Tensor, n: int) -> torch.Tensor:
"""[s, n * hidden_size] -> [s, hidden_size] by averaging."""
return x.mean(dim=1)