# SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project """Custom normalization layers.""" import torch import torch.nn as nn import torch.nn.functional as F # Import kernels import vllm.kernels # noqa: F401 from vllm import envs, ir from vllm.logger import init_logger from vllm.model_executor.custom_op import CustomOp from vllm.model_executor.determinism.batch_invariant import rms_norm_batch_invariant from vllm.model_executor.utils import set_weight_attrs logger = init_logger(__name__) def poly_norm( x: torch.Tensor, weight: torch.Tensor, bias: torch.Tensor, variance_epsilon: float ) -> torch.Tensor: from vllm import _custom_ops as ops out = torch.empty_like(x) ops.poly_norm( # type: ignore[attr-defined] out, x, weight, bias, variance_epsilon, ) return out # --8<-- [start:rms_norm] @CustomOp.register("rms_norm") class RMSNorm(CustomOp): """Root mean square normalization. Computes x -> w * x / sqrt(E[x^2] + eps) where w is the learned weight. Refer to https://arxiv.org/abs/1910.07467 """ # --8<-- [end:rms_norm] def __init__( self, hidden_size: int, eps: float = 1e-6, var_hidden_size: int | None = None, has_weight: bool = True, dtype: torch.dtype | None = None, ) -> None: super().__init__() self.hidden_size = hidden_size self.variance_epsilon = eps self.variance_size_override = ( None if var_hidden_size == hidden_size else var_hidden_size ) weight_dtype = dtype or torch.get_default_dtype() self.has_weight = has_weight self.weight = torch.ones(hidden_size, dtype=weight_dtype) if self.has_weight: self.weight = nn.Parameter(self.weight) # Dummy weights keep norms neutral: random ones would shrink every # activation and skew data-dependent kernels (e.g. sparse top-k). set_weight_attrs(self.weight, {"dummy_weight_value": 1.0}) # When has_weight=False, pass weight=None so implementations that # support a weightless path can skip the per-channel multiply. # Implementations that require weight (e.g. oink) fall back via IR # op priority when weight=None is unsupported. self.pass_weight = self.has_weight self.pass_weight_add = self.has_weight def forward_native( self, x: torch.Tensor, residual: torch.Tensor | None = None, ) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]: """PyTorch-native implementation equivalent to forward().""" if residual is None: return ir.ops.rms_norm( x, self.weight.data if self.pass_weight else None, self.variance_epsilon, self.variance_size_override, ) else: return ir.ops.fused_add_rms_norm.maybe_inplace( x, residual, self.weight.data if self.pass_weight_add else None, self.variance_epsilon, self.variance_size_override, ) def forward_cuda( self, x: torch.Tensor, residual: torch.Tensor | None = None, ) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]: if envs.VLLM_BATCH_INVARIANT: assert self.variance_size_override is None, ( "Batch invariance is not supported for variance_size_override" ) pass_weight = ( self.pass_weight_add if residual is not None else self.pass_weight ) return rms_norm_batch_invariant( x, self.weight.data if pass_weight else None, self.variance_epsilon, residual=residual, ) return self.forward_native(x, residual) def forward_xpu( self, x: torch.Tensor, residual: torch.Tensor | None = None, ) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]: if envs.VLLM_BATCH_INVARIANT and residual is not None: assert self.variance_size_override is None, ( "Batch invariance is not supported for variance_size_override" ) weight = self.weight.data if self.pass_weight_add else None return ir.ops.fused_add_rms_norm.impls["native"].impl_fn( x, residual, weight, self.variance_epsilon ) return self.forward_cuda(x, residual) def extra_repr(self) -> str: s = f"hidden_size={self.weight.data.size(0)}" s += f", eps={self.variance_epsilon}" return s # --8<-- [start:gemma_rms_norm] @CustomOp.register("gemma_rms_norm") class GemmaRMSNorm(CustomOp): """RMS normalization for Gemma. Two differences from the above RMSNorm: 1. x * (1 + w) instead of x * w. 2. (x * w).to(orig_dtype) instead of x.to(orig_dtype) * w. """ # --8<-- [end:gemma_rms_norm] def __init__( self, hidden_size: int, eps: float = 1e-6, ) -> None: super().__init__() self.weight = nn.Parameter(torch.zeros(hidden_size)) set_weight_attrs(self.weight, {"dummy_weight_value": 0.0}) self.variance_epsilon = eps def forward_native( self, x: torch.Tensor, residual: torch.Tensor | None = None, ) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]: """PyTorch-native implementation equivalent to forward().""" weight = self.weight.float() + 1.0 if residual is None: return ir.ops.rms_norm(x, weight, self.variance_epsilon) return ir.ops.fused_add_rms_norm(x, residual, weight, self.variance_epsilon) def forward_cuda( self, x: torch.Tensor, residual: torch.Tensor | None = None, ) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]: return self.forward_native(x, residual) def forward_xpu( self, x: torch.Tensor, residual: torch.Tensor | None = None, ) -> torch.Tensor | tuple[torch.Tensor, torch.Tensor]: if envs.VLLM_BATCH_INVARIANT: if residual is None: return self.forward_cuda(x, residual) return ir.ops.fused_add_rms_norm.impls["native"].impl_fn( x, residual, self.weight.float() + 1.0, self.variance_epsilon, ) import vllm._xpu_ops # noqa: F401 registers torch.ops.vllm.xpu_gemma_rms_norm # Fall back to the native path if the fused gemma kernels are not # available in the installed vllm-xpu-kernels package. if not hasattr(torch.ops._C, "gemma_rms_norm"): return self.forward_native(x, residual) # Pass the raw (bf16/fp16) weight; the +1 offset and the fp32 multiply # are folded into the kernel (matches forward_native numerics). if residual is not None: torch.ops.vllm.xpu_fused_add_gemma_rms_norm( x, residual, self.weight.data, self.variance_epsilon ) return x, residual # empty_like preserves x's strides, but the kernel requires a # contiguous out (unlike x, which it can handle non-contiguous). out = torch.empty(x.shape, device=x.device, dtype=x.dtype) torch.ops.vllm.xpu_gemma_rms_norm( out, x, self.weight.data, self.variance_epsilon ) return out @CustomOp.register("layer_norm") class LayerNorm(CustomOp): """Standard (mean-centered) LayerNorm. Drop-in for a bare `nn.LayerNorm` that dispatches to a fused XPU kernel when one is available, and to the native implementation otherwise. Normalization runs in the wider of the input and parameter dtypes and the result is cast back to the input dtype, so `dtype=torch.float32` gives an fp32 reduction for a lower-precision input. """ def __init__( self, hidden_size: int, eps: float = 1e-5, elementwise_affine: bool = True, bias: bool = True, dtype: torch.dtype | None = None, ) -> None: super().__init__() self.normalized_shape = (hidden_size,) self.eps = eps weight_dtype = dtype or torch.get_default_dtype() self.weight: nn.Parameter | None = None self.bias: nn.Parameter | None = None if elementwise_affine: self.weight = nn.Parameter(torch.ones(hidden_size, dtype=weight_dtype)) set_weight_attrs(self.weight, {"dummy_weight_value": 1.0}) if bias: self.bias = nn.Parameter(torch.zeros(hidden_size, dtype=weight_dtype)) set_weight_attrs(self.bias, {"dummy_weight_value": 0.0}) def _compute_dtype(self, x: torch.Tensor) -> torch.dtype: if self.weight is None: return x.dtype return torch.promote_types(x.dtype, self.weight.dtype) def forward_native(self, x: torch.Tensor) -> torch.Tensor: compute_dtype = self._compute_dtype(x) if compute_dtype != x.dtype: return F.layer_norm( x.to(compute_dtype), self.normalized_shape, self.weight, self.bias, self.eps, ).type_as(x) return F.layer_norm(x, self.normalized_shape, self.weight, self.bias, self.eps) def forward_cuda(self, x: torch.Tensor) -> torch.Tensor: return self.forward_native(x) def forward_xpu(self, x: torch.Tensor) -> torch.Tensor: import vllm._xpu_ops # noqa: F401 registers torch.ops.vllm.xpu_layer_norm if ( self.weight is None or self.bias is None or not hasattr(torch.ops._C, "layer_norm") # The kernel reduces in the input dtype, so a widened reduction # takes the native path. or self._compute_dtype(x) != x.dtype ): return self.forward_native(x) # empty_like preserves x's strides, but the kernel requires a # contiguous out (unlike x, which it can handle non-contiguous). out = torch.empty(x.shape, device=x.device, dtype=x.dtype) torch.ops.vllm.xpu_layer_norm(out, x, self.weight, self.bias, self.eps) return out def extra_repr(self) -> str: return f"hidden_size={self.normalized_shape[0]}, eps={self.eps}" # --8<-- [start:rms_norm_gated] @CustomOp.register("rms_norm_gated") class RMSNormGated(CustomOp): """RMS Normalization with optional gating. This is a native PyTorch implementation that supports: - Standard RMS normalization - Group RMS normalization - Optional gating with SiLU activation """ # --8<-- [end:rms_norm_gated] def __init__( self, hidden_size: int, eps: float = 1e-5, group_size: int | None = None, norm_before_gate: bool = False, device: torch.device | None = None, dtype: torch.dtype | None = None, activation: str = "swish", ): """Initialize RMSNormGated. Args: hidden_size: Size of the hidden dimension eps: Epsilon for numerical stability group_size: If not None, do GroupNorm with each group having group_size elements. group_size=None is equivalent to group_size=hidden_size (i.e. there's only 1 group). norm_before_gate: If True and z is provided: out = norm(x) * silu(z) If False and z is provided: out = norm(x * silu(z)) device: Device to create parameters on dtype: Data type for parameters activation: Activation function name for gating """ factory_kwargs = {"device": device, "dtype": dtype} super().__init__() self.eps = eps self.activation = activation self.weight = nn.Parameter(torch.empty(hidden_size, **factory_kwargs)) set_weight_attrs(self.weight, {"dummy_weight_value": 1.0}) self.register_parameter("bias", None) self.group_size = group_size self.norm_before_gate = norm_before_gate self.reset_parameters() def reset_parameters(self): torch.nn.init.ones_(self.weight) @staticmethod def forward_static( x: torch.Tensor, z: torch.Tensor | None, weight: torch.Tensor, epsilon: float, orig_dtype: torch.dtype, group_size: int | None = None, norm_before_gate: bool = True, activation: str = "swish", ) -> torch.Tensor: """Pure-PyTorch RMS normalization with optional gating. This static method contains the full native logic so that both ``forward_native`` and ``MatcherRMSNormGated`` (used by the compilation pattern matcher) can share the same implementation. If *z* is not None and *norm_before_gate* is True: ``out = rms_norm(x) * act(z)`` If *z* is not None and *norm_before_gate* is False: ``out = rms_norm(x * act(z))`` """ x = x.float() weight = weight.float() if z is not None: z = z.float() assert activation in ["silu", "sigmoid", "swish"] act_fn = F.sigmoid if activation == "sigmoid" else F.silu if z is not None and not norm_before_gate: x = x * act_fn(z) if group_size is None: variance = x.pow(2).mean(dim=-1, keepdim=True) x_normed = x * torch.rsqrt(variance + epsilon) out = x_normed * weight else: from einops import rearrange x_group = rearrange(x, "... (g d) -> ... g d", d=group_size) variance = x_group.pow(2).mean(dim=-1, keepdim=True) x_normed = x_group * torch.rsqrt(variance + epsilon) out = rearrange(x_normed, "... g d -> ... (g d)") * weight if z is not None and norm_before_gate: out = out * act_fn(z) return out.to(orig_dtype) def forward_native( self, x: torch.Tensor, z: torch.Tensor | None = None ) -> torch.Tensor: """PyTorch-native implementation equivalent to forward().""" return self.forward_static( x, z, self.weight, self.eps, x.dtype, group_size=self.group_size, norm_before_gate=self.norm_before_gate, activation=self.activation, ) def forward_cuda( self, x: torch.Tensor, z: torch.Tensor | None = None ) -> torch.Tensor: from vllm.third_party.flash_linear_attention.ops.layernorm_guard import ( rmsnorm_fn, ) return rmsnorm_fn( x, self.weight, self.bias, z=z, eps=self.eps, group_size=self.group_size, norm_before_gate=self.norm_before_gate, activation=self.activation, ) def forward_xpu( self, x: torch.Tensor, z: torch.Tensor | None = None ) -> torch.Tensor: return self.forward_cuda(x, z)