# SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project """Q/K RMSNorm followed by packed RoPE (non-interleaved or interleaved). The public contract is shared by diffusion attention implementations: * ``q`` and ``k`` are ``[tokens, heads, head_dim]``; * norm weights are one-dimensional ``[head_dim]`` tensors; * ``rope_table`` is ``[tokens, rotary_dim]`` and stores ``[cos(theta), sin(theta)]`` with ``theta`` of width ``rotary_dim // 2``; its dtype is either the activation dtype or float32; * ``interleaved=False`` rotates half-split pairs ``(d, d + rotary_dim/2)`` (MiniMax-H3); ``interleaved=True`` rotates adjacent pairs ``(2i, 2i + 1)`` with ``theta_i`` (Boogu-Image, matching its ``apply_rotary_emb``). The CUDA fast path fuses RMSNorm and RoPE without materializing normalized Q/K or rotary-product intermediates. The interleaved mode supports any even ``rotary_dim <= head_dim <= 256`` (``tl.arange`` padding to the next power of two); the half-split mode keeps the pre-existing kernel and its MiniMax-H3 geometry contract (``head_dim == 128``, ``rotary_dim == 96``). Ascend composes its RMSNorm and rotary fused primitives on the MiniMax-H3 geometry; unsupported inputs use the eager reference. """ from __future__ import annotations from importlib.util import find_spec import torch import torch.nn.functional as F from torch.library import Library from vllm.platforms import current_platform from vllm.triton_utils import HAS_TRITON, tl, triton from vllm.utils.torch_utils import direct_register_custom_op from vllm_omni.diffusion import envs from vllm_omni.platforms import current_omni_platform _FUSED_HEAD_DIM = 128 _FUSED_ROTARY_DIM = 96 # Interleaved-mode geometry bounds (any even rotary_dim <= head_dim <= this). _FUSED_MAX_HEAD_DIM = 256 _HEADS_PER_PROGRAM = 8 # The combined interleaved kernel: small tiles + one warp measured fastest # (latency-bound shapes; see the kernel docstring). _INTERLEAVED_HEADS_PER_PROGRAM = 4 def _apply_rope_table( x: torch.Tensor, rope_table: torch.Tensor, rotary_dim: int, *, interleaved: bool = False, dtype: torch.dtype | None = None, output_dtype: torch.dtype | None = None, ) -> torch.Tensor: """Eager RoPE application: the fallback and the unit-test reference. Shapes: ``x`` is the normalized ``[tokens, heads, head_dim]`` tensor; ``rope_table`` is ``[tokens, rotary_dim]`` storing ``[cos(theta) | sin(theta)]`` with ``theta`` of width ``rotary_dim // 2``. ``interleaved`` selects the pairing: half-split ``(d, d + rotary_dim/2)`` sharing ``theta_d`` (MiniMax-H3) or adjacent pairs ``(2i, 2i + 1)`` sharing ``theta_i`` (Boogu-Image). ``dtype`` is the precision the rotation arithmetic runs in and ``output_dtype`` the precision the result is cast to; both default to ``x``'s dtype (the historical half-split behaviour). The interleaved caller passes ``dtype=torch.float32`` to match the Triton kernel's composition — fp32 rotation of the bf16-rounded normalized value, one final rounding — which is also how vLLM's own ``fused_qk_norm_rope`` Triton kernel and Boogu's eager ``apply_rotary_emb`` order their rounding. """ compute_dtype = x.dtype if dtype is None else dtype out_dtype = x.dtype if output_dtype is None else output_dtype half = rotary_dim // 2 x_c = x.to(compute_dtype) cos = rope_table[..., :half].to(compute_dtype).unsqueeze(1) sin = rope_table[..., half:].to(compute_dtype).unsqueeze(1) first = x_c[..., 0:rotary_dim:2] if interleaved else x_c[..., :half] second = x_c[..., 1:rotary_dim:2] if interleaved else x_c[..., half:rotary_dim] rotated_first = first * cos - second * sin rotated_second = second * cos + first * sin if interleaved: # stack(..., dim=-1) pairs (new_even_i, new_odd_i) into # [..., half, 2]; flatten(-2) lays the pairs back out as # [e0, o0, e1, o1, ...] — back to their interleaved positions. rotated = torch.stack( (rotated_first, rotated_second), dim=-1, ).flatten(-2) return torch.cat((rotated, x_c[..., rotary_dim:]), dim=-1).to(out_dtype) else: return torch.cat( ( rotated_first, rotated_second, x_c[..., rotary_dim:], ), dim=-1, ).to(out_dtype) if HAS_TRITON: @triton.jit def _rms_norm_rope_kernel( x_ptr, weight_ptr, rope_table_ptr, out_ptr, x_stride_t, x_stride_h, x_stride_d, rope_stride_t, out_stride_t, out_stride_h, out_stride_d, num_heads: tl.constexpr, head_dim: tl.constexpr, rotary_half: tl.constexpr, eps: tl.constexpr, heads_per_program: tl.constexpr, ): # Long-video Q/K views can span more than 2**31 elements of fused QKV storage. token = tl.program_id(0).to(tl.int64) head_group = tl.program_id(1) heads = head_group * heads_per_program + tl.arange(0, heads_per_program) dims = tl.arange(0, head_dim) mask = heads[:, None] < num_heads offsets = token * x_stride_t + heads[:, None] * x_stride_h + dims[None, :] * x_stride_d x = tl.load(x_ptr + offsets, mask=mask, other=0.0).to(tl.float32) weight = tl.load(weight_ptr + dims).to(tl.float32) inv_rms = tl.rsqrt(tl.sum(x * x, axis=1) / head_dim + eps) normalized = (x * inv_rms[:, None] * weight[None, :]).to(tl.bfloat16) rotary_dim = rotary_half * 2 pair_dims = tl.where( dims < rotary_half, dims + rotary_half, tl.where(dims < rotary_dim, dims - rotary_half, dims), ) pair_offsets = token * x_stride_t + heads[:, None] * x_stride_h + pair_dims[None, :] * x_stride_d pair_x = tl.load(x_ptr + pair_offsets, mask=mask, other=0.0).to(tl.float32) pair_weight = tl.load(weight_ptr + pair_dims).to(tl.float32) pair_normalized = (pair_x * inv_rms[:, None] * pair_weight[None, :]).to(tl.bfloat16) freq_dims = tl.where( dims < rotary_half, dims, tl.where(dims < rotary_dim, dims - rotary_half, 0), ) table_offsets = token * rope_stride_t + freq_dims cos = tl.load(rope_table_ptr + table_offsets).to(tl.float32) sin = tl.load(rope_table_ptr + table_offsets + rotary_half).to(tl.float32) first = normalized.to(tl.float32) * cos - pair_normalized.to(tl.float32) * sin second = normalized.to(tl.float32) * cos + pair_normalized.to(tl.float32) * sin output = tl.where( dims < rotary_dim, tl.where(dims < rotary_half, first, second), normalized.to(tl.float32), ) out_offsets = token * out_stride_t + heads[:, None] * out_stride_h + dims[None, :] * out_stride_d tl.store(out_ptr + out_offsets, output, mask=mask) @triton.jit def _qk_norm_rope_kernel( q_ptr, k_ptr, q_weight_ptr, k_weight_ptr, rope_table_ptr, out_q_ptr, out_k_ptr, q_stride_t, q_stride_h, q_stride_d, k_stride_t, k_stride_h, k_stride_d, rope_stride_t, out_q_stride_t, out_q_stride_h, out_q_stride_d, out_k_stride_t, out_k_stride_h, out_k_stride_d, num_q_heads: tl.constexpr, num_kv_heads: tl.constexpr, head_dim: tl.constexpr, padded_dim: tl.constexpr, # next power of 2 >= head_dim (tl.arange needs a power of 2) rotary_half: tl.constexpr, eps: tl.constexpr, heads_per_program: tl.constexpr, q_head_groups: tl.constexpr, interleaved: tl.constexpr, ): """Fused per-head RMSNorm + RoPE for Q and K in one launch. Grid axis 1 assigns the first ``q_head_groups`` programs of a token to Q and the rest to K, so the small K grid hides inside Q's waves instead of paying its own latency-bound launch. ``interleaved`` selects the pairing, mirroring ``_apply_rope_table``: * ``True`` — adjacent pairs ``(2i, 2i+1)`` sharing ``theta_i``. * ``False`` — half-split pairs ``(d, d + rotary_half)`` sharing ``theta_d``. Production traffic still routes half-split inputs through ``_rms_norm_rope_kernel``; this mode is test-covered and awaits the maintainers' call before taking over that path. The modes differ only in the pair/frequency/selector index expressions; the arithmetic is shared. Beyond the rotary width (partial rotary or lane padding) the output is the normalized value unchanged. """ # Long-video Q/K views can span more than 2**31 elements of fused QKV storage. token = tl.program_id(0).to(tl.int64) head_group = tl.program_id(1) dims = tl.arange(0, padded_dim) dim_mask = dims < head_dim if head_group < q_head_groups: heads = head_group * heads_per_program + tl.arange(0, heads_per_program) # Valid-domain mask: rows beyond the head count (when it is not a # multiple of heads_per_program) and padded lanes beyond head_dim. mask = (heads[:, None] < num_q_heads) & dim_mask[None, :] in_ptr = q_ptr weight_ptr = q_weight_ptr out_ptr = out_q_ptr in_stride_t, in_stride_h, in_stride_d = q_stride_t, q_stride_h, q_stride_d out_stride_t, out_stride_h, out_stride_d = ( out_q_stride_t, out_q_stride_h, out_q_stride_d, ) else: heads = (head_group - q_head_groups) * heads_per_program + tl.arange(0, heads_per_program) mask = (heads[:, None] < num_kv_heads) & dim_mask[None, :] in_ptr = k_ptr weight_ptr = k_weight_ptr out_ptr = out_k_ptr in_stride_t, in_stride_h, in_stride_d = k_stride_t, k_stride_h, k_stride_d out_stride_t, out_stride_h, out_stride_d = ( out_k_stride_t, out_k_stride_h, out_k_stride_d, ) offsets = token * in_stride_t + heads[:, None] * in_stride_h + dims[None, :] * in_stride_d x = tl.load(in_ptr + offsets, mask=mask, other=0.0).to(tl.float32) weight = tl.load(weight_ptr + dims, mask=dim_mask, other=0.0).to(tl.float32) # Padding lanes load 0 and do not perturb the sum; divide by the true # head_dim, not the padded width. inv_rms = tl.rsqrt(tl.sum(x * x, axis=1) / head_dim + eps) normalized = (x * inv_rms[:, None] * weight[None, :]).to(tl.bfloat16) # The two pairings differ only in three index expressions; the pair # reload, the table gather, the rotation formulas and the write-back # are shared (the same structure as _apply_rope_table). rotary_dim = rotary_half * 2 if interleaved: # Adjacent pairs (2i, 2i+1) sharing theta_i. pair_dims = tl.where(dims < rotary_dim, dims ^ 1, dims) freq_dims = tl.where(dims < rotary_dim, dims // 2, 0) is_first = (dims % 2) == 0 else: # Half-split pairs (d, d + rotary_half) sharing theta_d. pair_dims = tl.where( dims < rotary_half, dims + rotary_half, tl.where(dims < rotary_dim, dims - rotary_half, dims), ) freq_dims = tl.where( dims < rotary_half, dims, tl.where(dims < rotary_dim, dims - rotary_half, 0), ) is_first = dims < rotary_half pair_offsets = token * in_stride_t + heads[:, None] * in_stride_h + pair_dims[None, :] * in_stride_d pair_x = tl.load(in_ptr + pair_offsets, mask=mask, other=0.0).to(tl.float32) pair_weight = tl.load(weight_ptr + pair_dims, mask=pair_dims < head_dim, other=0.0).to(tl.float32) pair_normalized = (pair_x * inv_rms[:, None] * pair_weight[None, :]).to(tl.bfloat16) table_offsets = token * rope_stride_t + freq_dims cos = tl.load(rope_table_ptr + table_offsets).to(tl.float32) sin = tl.load(rope_table_ptr + table_offsets + rotary_half).to(tl.float32) first = normalized.to(tl.float32) * cos - pair_normalized.to(tl.float32) * sin second = normalized.to(tl.float32) * cos + pair_normalized.to(tl.float32) * sin output = tl.where( dims < rotary_dim, tl.where(is_first, first, second), normalized.to(tl.float32), ) out_offsets = token * out_stride_t + heads[:, None] * out_stride_h + dims[None, :] * out_stride_d tl.store(out_ptr + out_offsets, output, mask=mask) def _eager_qk_norm_rope( q: torch.Tensor, k: torch.Tensor, q_weight: torch.Tensor, k_weight: torch.Tensor, rope_table: torch.Tensor, eps: float, head_dim: int, rotary_dim: int, interleaved: bool = False, ) -> tuple[torch.Tensor, torch.Tensor]: q_norm = F.rms_norm(q, (head_dim,), q_weight, eps) k_norm = F.rms_norm(k, (head_dim,), k_weight, eps) # fp32 rotation for the interleaved (Boogu) semantics; the half-split # (H3) reference keeps its historical x-dtype arithmetic via the defaults. rope_dtype = torch.float32 if interleaved else None return ( _apply_rope_table(q_norm, rope_table, rotary_dim, interleaved=interleaved, dtype=rope_dtype), _apply_rope_table(k_norm, rope_table, rotary_dim, interleaved=interleaved, dtype=rope_dtype), ) def _npu_apply_rope_table( x: torch.Tensor, rope_table: torch.Tensor, rotary_dim: int, ) -> torch.Tensor: """Apply H3's rotary dimensions through Ascend's fused rotary kernel. ``mindiesd.rotary_position_embedding`` takes a 4-D BSND tensor, whereas MiniMax-H3 keeps packed activations as ``[tokens, heads, head_dim]``. The shared MindIE-SD wrapper normalizes this 3-D layout to BSND and restores it afterwards. It receives the half-width cos/sin values from the packed table; the wrapper expands them to H3's non-interleaved 96-D rotary layout. The 32 non-rotary head dimensions bypass the kernel. Some CANN environments package ``torch_npu`` without MindIE-SD. Keep those deployments on the same Ascend fused rotary primitive rather than silently falling back to eager elementwise RoPE. """ import torch_npu half = rotary_dim // 2 x_rot, x_pass = x[..., :rotary_dim], x[..., rotary_dim:] cos = rope_table[..., :half] sin = rope_table[..., half:] if find_spec("mindiesd") is not None: from vllm_omni.diffusion.layers.rope import apply_rotary_emb_mindiesd x_rot = apply_rotary_emb_mindiesd( x_rot, cos, sin, interleaved=False, half_head_dim=True, ) else: # npu_rotary_mul uses BSND and full rotary-width cos/sin. H3 uses # NeoX/rotated-half ordering, so duplicate rather than interleave the # half-width table along the final dimension. cos = cos.unsqueeze(0).unsqueeze(2).repeat(1, 1, 1, 2) sin = sin.unsqueeze(0).unsqueeze(2).repeat(1, 1, 1, 2) x_rot = torch_npu.npu_rotary_mul(x_rot.unsqueeze(0), cos, sin).squeeze(0) return torch.cat((x_rot, x_pass), dim=-1) def _npu_qk_norm_rope( q: torch.Tensor, k: torch.Tensor, q_weight: torch.Tensor, k_weight: torch.Tensor, rope_table: torch.Tensor, eps: float, rotary_dim: int, ) -> tuple[torch.Tensor, torch.Tensor]: """Use Ascend RMSNorm and RoPE fused primitives for MiniMax-H3 DiT.""" import torch_npu q_norm = torch_npu.npu_rms_norm(q, q_weight, epsilon=eps)[0] k_norm = torch_npu.npu_rms_norm(k, k_weight, epsilon=eps)[0] return ( _npu_apply_rope_table(q_norm, rope_table, rotary_dim), _npu_apply_rope_table(k_norm, rope_table, rotary_dim), ) def _fused_cuda_supported( q: torch.Tensor, k: torch.Tensor, head_dim: int, rotary_dim: int, interleaved: bool = False, ) -> bool: if not ( HAS_TRITON and current_platform.is_cuda() and q.is_cuda and k.is_cuda and q.dtype == torch.bfloat16 and k.dtype == torch.bfloat16 ): return False if interleaved: return rotary_dim % 2 == 0 and 2 <= rotary_dim <= head_dim <= _FUSED_MAX_HEAD_DIM # Half-split traffic keeps the pre-existing per-tensor kernel and its # exact MiniMax-H3 geometry contract (that kernel is untouched by the # interleaved extension). return head_dim == _FUSED_HEAD_DIM and rotary_dim == _FUSED_ROTARY_DIM def _fused_npu_supported( q: torch.Tensor, k: torch.Tensor, head_dim: int, rotary_dim: int, interleaved: bool = False, ) -> bool: """Return whether the MiniMax-H3 Ascend fused-op contract is satisfied.""" return ( not interleaved and current_omni_platform.is_npu() and q.device.type == "npu" and k.device.type == "npu" and q.dtype == torch.bfloat16 and k.dtype == torch.bfloat16 and head_dim == _FUSED_HEAD_DIM and rotary_dim == _FUSED_ROTARY_DIM ) def _launch_fused_rms_norm_rope( x: torch.Tensor, weight: torch.Tensor, rope_table: torch.Tensor, eps: float, ) -> torch.Tensor: tokens, heads, head_dim = x.shape rotary_half = rope_table.shape[-1] // 2 out = torch.empty(x.shape, dtype=x.dtype, device=x.device) if tokens == 0: return out grid = (tokens, triton.cdiv(heads, _HEADS_PER_PROGRAM)) _rms_norm_rope_kernel[grid]( x, weight, rope_table, out, x.stride(0), x.stride(1), x.stride(2), rope_table.stride(0), out.stride(0), out.stride(1), out.stride(2), num_heads=heads, head_dim=head_dim, rotary_half=rotary_half, eps=eps, heads_per_program=_HEADS_PER_PROGRAM, num_warps=8, ) return out def _launch_fused_qk_norm_rope( q: torch.Tensor, k: torch.Tensor, q_weight: torch.Tensor, k_weight: torch.Tensor, rope_table: torch.Tensor, eps: float, interleaved: bool, ) -> tuple[torch.Tensor, torch.Tensor]: tokens, q_heads, head_dim = q.shape kv_heads = k.shape[1] rotary_half = rope_table.shape[-1] // 2 out_q = torch.empty(q.shape, dtype=q.dtype, device=q.device) out_k = torch.empty(k.shape, dtype=k.dtype, device=k.device) if tokens == 0: return out_q, out_k if interleaved: heads_per_program = _INTERLEAVED_HEADS_PER_PROGRAM num_warps, num_stages = 1, 2 else: # Match the historical per-tensor launch shape (8 heads per program, # 8 warps) so the half-split arithmetic keeps its reduction tree. heads_per_program = _HEADS_PER_PROGRAM num_warps, num_stages = 8, 3 q_head_groups = triton.cdiv(q_heads, heads_per_program) kv_head_groups = triton.cdiv(kv_heads, heads_per_program) grid = (tokens, q_head_groups + kv_head_groups) _qk_norm_rope_kernel[grid]( q, k, q_weight, k_weight, rope_table, out_q, out_k, q.stride(0), q.stride(1), q.stride(2), k.stride(0), k.stride(1), k.stride(2), rope_table.stride(0), out_q.stride(0), out_q.stride(1), out_q.stride(2), out_k.stride(0), out_k.stride(1), out_k.stride(2), num_q_heads=q_heads, num_kv_heads=kv_heads, head_dim=head_dim, padded_dim=triton.next_power_of_2(head_dim), rotary_half=rotary_half, eps=eps, heads_per_program=heads_per_program, q_head_groups=q_head_groups, interleaved=interleaved, num_warps=num_warps, num_stages=num_stages, ) return out_q, out_k def _fused_qk_norm_rope_impl( q: torch.Tensor, k: torch.Tensor, q_weight: torch.Tensor, k_weight: torch.Tensor, rope_table: torch.Tensor, eps: float, head_dim: int, rotary_dim: int, interleaved: bool, ) -> tuple[torch.Tensor, torch.Tensor]: if _fused_npu_supported(q, k, head_dim, rotary_dim, interleaved): return _npu_qk_norm_rope( q, k, q_weight, k_weight, rope_table, eps, rotary_dim, ) if not _fused_cuda_supported(q, k, head_dim, rotary_dim, interleaved): return _eager_qk_norm_rope( q, k, q_weight, k_weight, rope_table, eps, head_dim, rotary_dim, interleaved, ) if interleaved: return _launch_fused_qk_norm_rope(q, k, q_weight, k_weight, rope_table, eps, interleaved=True) # Half-split production traffic keeps the historical per-tensor kernel; # _qk_norm_rope_kernel's half-split mode awaits the maintainers' call. return ( _launch_fused_rms_norm_rope(q, q_weight, rope_table, eps), _launch_fused_rms_norm_rope(k, k_weight, rope_table, eps), ) def _fused_qk_norm_rope_fake( q: torch.Tensor, k: torch.Tensor, q_weight: torch.Tensor, k_weight: torch.Tensor, rope_table: torch.Tensor, eps: float, head_dim: int, rotary_dim: int, interleaved: bool, ) -> tuple[torch.Tensor, torch.Tensor]: del q_weight, k_weight, rope_table, eps, head_dim, rotary_dim, interleaved return torch.empty_like(q), torch.empty_like(k) _OMNI_OP_LIB = Library("vllm_omni", "FRAGMENT") if not hasattr(torch.ops.vllm_omni, "fused_qk_norm_rope"): direct_register_custom_op( op_name="fused_qk_norm_rope", op_func=_fused_qk_norm_rope_impl, fake_impl=_fused_qk_norm_rope_fake, mutates_args=[], target_lib=_OMNI_OP_LIB, ) _MIN_TOKENS_ENV = "VLLM_OMNI_FUSED_QK_NORM_ROPE_MIN_TOKENS" def fused_qk_norm_rope_min_tokens(default: int) -> int: """Token span below which a consumer should keep its eager chain. Taking the fused path costs a fixed amount of host time per call (the custom-op dispatch into the Python impl and the Triton launcher), while the GPU time it saves grows with the number of tokens the call covers. Below a host- and stack-dependent crossover a call is host-bound and the fused path can cost more end-to-end than it saves; above it the fused path wins. Consumers pass the default they measured on their reference hardware (Boogu-Image: 2048 tokens on one H200) and a deployment on other hardware overrides it once, for every consumer, through ``VLLM_OMNI_FUSED_QK_NORM_ROPE_MIN_TOKENS`` (``0`` = always fuse). Resolved at call time — consumers call this once per forward, not per attention site — so tests and operators can flip it without re-importing. Unset or blank means the caller's default; anything else that is not a non-negative integer raises ``ValueError``. """ raw = envs.VLLM_OMNI_FUSED_QK_NORM_ROPE_MIN_TOKENS if raw is None or not raw.strip(): return default try: value = int(raw) except ValueError: value = -1 if value < 0: raise ValueError(f"{_MIN_TOKENS_ENV} must be a non-negative integer, got {raw!r}") return value def fused_qk_norm_rope( q: torch.Tensor, k: torch.Tensor, q_weight: torch.Tensor, k_weight: torch.Tensor, rope_table: torch.Tensor, eps: float, *, head_dim: int | None = None, rotary_dim: int | None = None, interleaved: bool = False, ) -> tuple[torch.Tensor, torch.Tensor]: """Apply Q/K RMSNorm and packed RoPE (half-split or adjacent pairs).""" if q.ndim != 3 or k.ndim != 3: raise ValueError(f"q and k must be [tokens, heads, head_dim], got {q.shape} and {k.shape}") if q.shape[0] != k.shape[0] or q.shape[2] != k.shape[2]: raise ValueError(f"q and k shapes are incompatible: {q.shape} and {k.shape}") if q.dtype != k.dtype or q.device != k.device: raise ValueError("q and k must have the same dtype and device") if q.dtype not in (torch.bfloat16, torch.float16, torch.float32): raise TypeError(f"Fused QK RMSNorm/RoPE requires floating inputs, got {q.dtype}") head_dim = q.shape[-1] if head_dim is None else head_dim rotary_dim = rope_table.shape[-1] if rotary_dim is None else rotary_dim if q.shape[-1] != head_dim: raise ValueError(f"Expected q/k head_dim={head_dim}, got {q.shape[-1]}") if rotary_dim <= 0 or rotary_dim > head_dim or rotary_dim % 2: raise ValueError(f"rotary_dim must be even and in [2, {head_dim}], got {rotary_dim}") if q_weight.shape != (head_dim,) or k_weight.shape != (head_dim,): raise ValueError(f"Expected norm weights [{head_dim}], got {tuple(q_weight.shape)} and {tuple(k_weight.shape)}") if q_weight.device != q.device or k_weight.device != q.device: raise ValueError("Q/K norm weights must be on the activation device") if rope_table.device != q.device or rope_table.dtype not in ( q.dtype, torch.float32, ): raise ValueError("rope_table must be on q/k's device with their dtype or float32") if rope_table.shape != (q.shape[0], rotary_dim): raise ValueError(f"Expected rope_table [{q.shape[0]}, {rotary_dim}], got {tuple(rope_table.shape)}") q_weight = q_weight.contiguous() k_weight = k_weight.contiguous() rope_table = rope_table.contiguous() if _fused_npu_supported(q, k, head_dim, rotary_dim, interleaved): return _npu_qk_norm_rope( q, k, q_weight, k_weight, rope_table, eps, rotary_dim, ) if not _fused_cuda_supported(q, k, head_dim, rotary_dim, interleaved): return _fused_qk_norm_rope_impl( q, k, q_weight, k_weight, rope_table, eps, head_dim, rotary_dim, interleaved, ) return torch.ops.vllm_omni.fused_qk_norm_rope( q, k, q_weight, k_weight, rope_table, eps, head_dim, rotary_dim, interleaved, ) __all__ = ["fused_qk_norm_rope", "fused_qk_norm_rope_min_tokens"]