94057c3d3e
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
772 lines
24 KiB
Python
772 lines
24 KiB
Python
import logging
|
||
import math
|
||
from functools import lru_cache
|
||
from typing import Optional
|
||
|
||
import torch
|
||
import triton
|
||
import triton.language as tl
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
# This module is imported during model-registry discovery. Keep it free of
|
||
# TileLang imports so discovery does not load TileLang's native CUDA stubs.
|
||
|
||
FP8 = "float8_e4m3"
|
||
BF16 = "bfloat16"
|
||
FP32 = "float32"
|
||
INT32 = "int32"
|
||
|
||
|
||
def _yarn_get_mscale(scale: float = 1.0, mscale: float = 1.0) -> float:
|
||
if scale <= 1:
|
||
return 1.0
|
||
return 0.1 * mscale * math.log(scale) + 1.0
|
||
|
||
|
||
@lru_cache(2)
|
||
def precompute_freqs_cis(
|
||
dim,
|
||
seqlen,
|
||
original_seq_len,
|
||
base,
|
||
factor,
|
||
beta_fast,
|
||
beta_slow,
|
||
) -> torch.Tensor:
|
||
|
||
def find_correction_dim(num_rotations, dim, base, max_seq_len):
|
||
return (
|
||
dim
|
||
* math.log(max_seq_len / (num_rotations * 2 * math.pi))
|
||
/ (2 * math.log(base))
|
||
)
|
||
|
||
def find_correction_range(low_rot, high_rot, dim, base, max_seq_len):
|
||
low = math.floor(find_correction_dim(low_rot, dim, base, max_seq_len))
|
||
high = math.ceil(find_correction_dim(high_rot, dim, base, max_seq_len))
|
||
return max(low, 0), min(high, dim - 1)
|
||
|
||
def linear_ramp_factor(min, max, dim):
|
||
if min == max:
|
||
max += 0.001
|
||
linear_func = (torch.arange(dim, dtype=torch.float32) - min) / (max - min)
|
||
ramp_func = torch.clamp(linear_func, 0, 1)
|
||
return ramp_func
|
||
|
||
freqs = 1.0 / (base ** (torch.arange(0, dim, 2, dtype=torch.float32) / dim))
|
||
if original_seq_len > 0:
|
||
low, high = find_correction_range(
|
||
beta_fast, beta_slow, dim, base, original_seq_len
|
||
)
|
||
smooth = 1 - linear_ramp_factor(low, high, dim // 2)
|
||
freqs = freqs / factor * (1 - smooth) + freqs * smooth
|
||
|
||
t = torch.arange(seqlen)
|
||
freqs = torch.outer(t, freqs)
|
||
freqs_cis = torch.polar(torch.ones_like(freqs), freqs)
|
||
return freqs_cis
|
||
|
||
|
||
@triton.jit
|
||
def apply_rotary_emb_triton_kernel(
|
||
x_ptr,
|
||
freqs_ptr,
|
||
positions_ptr,
|
||
rope_dim,
|
||
stride_x_batch,
|
||
stride_x_head,
|
||
stride_x_dim,
|
||
stride_freq_pos,
|
||
stride_freq_dim,
|
||
USE_POS: tl.constexpr,
|
||
IS_INVERSE: tl.constexpr,
|
||
IS_3D: tl.constexpr,
|
||
BLOCK_SIZE: tl.constexpr,
|
||
):
|
||
pid_batch = tl.program_id(0)
|
||
pid_head = tl.program_id(1)
|
||
pid_dim = tl.program_id(2)
|
||
|
||
if USE_POS:
|
||
position = tl.load(positions_ptr + pid_batch)
|
||
else:
|
||
position = pid_batch
|
||
|
||
if IS_3D:
|
||
base_offset = pid_batch * stride_x_batch + pid_head * stride_x_head
|
||
else:
|
||
base_offset = pid_batch * stride_x_batch
|
||
|
||
offs_pair = pid_dim * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
|
||
mask = offs_pair < (rope_dim // 2)
|
||
|
||
offs_x_real = base_offset + offs_pair * 2 * stride_x_dim
|
||
offs_x_imag = base_offset + (offs_pair * 2 + 1) * stride_x_dim
|
||
|
||
x_real = tl.load(x_ptr + offs_x_real, mask=mask, other=0.0).to(tl.float32)
|
||
x_imag = tl.load(x_ptr + offs_x_imag, mask=mask, other=0.0).to(tl.float32)
|
||
|
||
offs_freq_real = position * stride_freq_pos + offs_pair * 2 * stride_freq_dim
|
||
offs_freq_imag = position * stride_freq_pos + (offs_pair * 2 + 1) * stride_freq_dim
|
||
|
||
freq_real = tl.load(freqs_ptr + offs_freq_real, mask=mask, other=0.0)
|
||
freq_imag = tl.load(freqs_ptr + offs_freq_imag, mask=mask, other=0.0)
|
||
|
||
if IS_INVERSE:
|
||
out_real = x_real * freq_real + x_imag * freq_imag
|
||
out_imag = x_imag * freq_real - x_real * freq_imag
|
||
else:
|
||
out_real = x_real * freq_real - x_imag * freq_imag
|
||
out_imag = x_real * freq_imag + x_imag * freq_real
|
||
|
||
tl.store(x_ptr + offs_x_real, out_real, mask=mask)
|
||
tl.store(x_ptr + offs_x_imag, out_imag, mask=mask)
|
||
|
||
|
||
@triton.jit
|
||
def apply_rotary_emb_triton_kernel_batched(
|
||
x_ptr,
|
||
freqs_ptr,
|
||
positions_ptr,
|
||
rope_dim,
|
||
n_tokens,
|
||
stride_x_batch,
|
||
stride_x_head,
|
||
stride_x_dim,
|
||
stride_freq_pos,
|
||
stride_freq_dim,
|
||
USE_POS: tl.constexpr,
|
||
IS_INVERSE: tl.constexpr,
|
||
IS_3D: tl.constexpr,
|
||
BLOCK_M: tl.constexpr,
|
||
BLOCK_P: tl.constexpr,
|
||
):
|
||
# Batched variant: BLOCK_M tokens per program
|
||
# which batches 32 tokens/program) to cut the per-token launch granularity of
|
||
# the original (one program per token).
|
||
pid_m = tl.program_id(0)
|
||
pid_head = tl.program_id(1)
|
||
|
||
tok = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)
|
||
tok_mask = tok < n_tokens
|
||
pair = tl.arange(0, BLOCK_P)
|
||
pair_mask = pair < (rope_dim // 2)
|
||
m2 = tok_mask[:, None] & pair_mask[None, :]
|
||
|
||
if USE_POS:
|
||
position = tl.load(positions_ptr + tok, mask=tok_mask, other=0)
|
||
else:
|
||
position = tok
|
||
|
||
if IS_3D:
|
||
base = tok[:, None] * stride_x_batch + pid_head * stride_x_head
|
||
else:
|
||
base = tok[:, None] * stride_x_batch
|
||
|
||
off_real = base + (pair[None, :] * 2) * stride_x_dim
|
||
off_imag = base + (pair[None, :] * 2 + 1) * stride_x_dim
|
||
|
||
x_real = tl.load(x_ptr + off_real, mask=m2, other=0.0).to(tl.float32)
|
||
x_imag = tl.load(x_ptr + off_imag, mask=m2, other=0.0).to(tl.float32)
|
||
|
||
off_f_real = (
|
||
position[:, None] * stride_freq_pos + (pair[None, :] * 2) * stride_freq_dim
|
||
)
|
||
off_f_imag = (
|
||
position[:, None] * stride_freq_pos + (pair[None, :] * 2 + 1) * stride_freq_dim
|
||
)
|
||
freq_real = tl.load(freqs_ptr + off_f_real, mask=m2, other=0.0)
|
||
freq_imag = tl.load(freqs_ptr + off_f_imag, mask=m2, other=0.0)
|
||
|
||
if IS_INVERSE:
|
||
out_real = x_real * freq_real + x_imag * freq_imag
|
||
out_imag = x_imag * freq_real - x_real * freq_imag
|
||
else:
|
||
out_real = x_real * freq_real - x_imag * freq_imag
|
||
out_imag = x_real * freq_imag + x_imag * freq_real
|
||
|
||
tl.store(x_ptr + off_real, out_real, mask=m2)
|
||
tl.store(x_ptr + off_imag, out_imag, mask=m2)
|
||
|
||
|
||
@triton.jit
|
||
def apply_rotary_emb_flat_kernel(
|
||
x_ptr,
|
||
fr_ptr,
|
||
pos_ptr,
|
||
n_rows,
|
||
n_heads,
|
||
sx_tok,
|
||
sx_head,
|
||
sx_d,
|
||
sfr_pos,
|
||
sfr_d,
|
||
USE_POS: tl.constexpr,
|
||
IS_INVERSE: tl.constexpr,
|
||
RD: tl.constexpr,
|
||
RDH: tl.constexpr,
|
||
BLOCK_ROWS: tl.constexpr,
|
||
):
|
||
# FLAT-row GPT-J rope: iterate over (token, head) pairs flattened as
|
||
# row = token * n_heads + head, BLOCK_ROWS *consecutive* rows per program.
|
||
# Consecutive rows are sx_head apart in memory (vs sx_tok == n_heads*sx_head
|
||
# for the per-head contig kernel), so the read/write is far less scattered ->
|
||
# ~2x higher achieved HBM bandwidth (cold) on the 128-head attention output
|
||
# (production rope ~168us -> ~59us).
|
||
pid = tl.program_id(0)
|
||
row = pid * BLOCK_ROWS + tl.arange(0, BLOCK_ROWS)
|
||
rmask = row < n_rows
|
||
tok = row // n_heads
|
||
head = row % n_heads
|
||
d = tl.arange(0, RD)
|
||
base = tok[:, None] * sx_tok + head[:, None] * sx_head
|
||
xo = base + d[None, :] * sx_d
|
||
x = tl.load(x_ptr + xo, mask=rmask[:, None], other=0.0).to(tl.float32)
|
||
|
||
if USE_POS:
|
||
pos = tl.load(pos_ptr + tok, mask=rmask, other=0)
|
||
else:
|
||
pos = tok
|
||
cos_idx = (d // 2) * 2
|
||
cos = tl.load(
|
||
fr_ptr + pos[:, None] * sfr_pos + cos_idx[None, :] * sfr_d,
|
||
mask=rmask[:, None],
|
||
other=0.0,
|
||
)
|
||
sin = tl.load(
|
||
fr_ptr + pos[:, None] * sfr_pos + (cos_idx[None, :] + 1) * sfr_d,
|
||
mask=rmask[:, None],
|
||
other=0.0,
|
||
)
|
||
|
||
x_sin = x * sin
|
||
even = (d % 2 == 0)[None, :]
|
||
if IS_INVERSE:
|
||
x_neg = tl.where(even, -x_sin, x_sin)
|
||
else:
|
||
x_neg = tl.where(even, x_sin, -x_sin)
|
||
x_neg = tl.reshape(x_neg, (BLOCK_ROWS, RDH, 2))
|
||
x_neg = tl.flip(x_neg, 2)
|
||
x_rot = tl.reshape(x_neg, (BLOCK_ROWS, RD))
|
||
|
||
out = x * cos + x_rot
|
||
tl.store(x_ptr + xo, out.to(x_ptr.dtype.element_ty), mask=rmask[:, None])
|
||
|
||
|
||
# Use the batched / contiguous-load rope kernels (faster, coalesced) instead of the
|
||
# per-token kernel. Default OFF; DeepseekV4 enables it via set_batched_rope(True).
|
||
# The env var SGLANG_ROPE_BATCHED=1 still works as an override.
|
||
_USE_BATCHED_ROPE: bool = False
|
||
|
||
|
||
def set_batched_rope(enabled: bool = True) -> None:
|
||
global _USE_BATCHED_ROPE
|
||
_USE_BATCHED_ROPE = enabled
|
||
|
||
|
||
def apply_rotary_emb_triton(
|
||
x: torch.Tensor,
|
||
freqs_cis: torch.Tensor,
|
||
positions: Optional[torch.Tensor] = None,
|
||
inverse: bool = False,
|
||
) -> torch.Tensor:
|
||
|
||
if _USE_BATCHED_ROPE:
|
||
is_3d = x.ndim == 3
|
||
if is_3d:
|
||
batch_size, n_heads, rope_dim = x.shape
|
||
else:
|
||
batch_size, rope_dim = x.shape
|
||
n_heads = 1
|
||
freqs_real = torch.view_as_real(freqs_cis).flatten(-2)
|
||
if positions is not None:
|
||
assert positions.shape == (batch_size,)
|
||
else:
|
||
assert freqs_real.shape[0] == batch_size
|
||
BLOCK_M = 32
|
||
# 3D (attention-output / q-k rope): contiguous-load kernel.
|
||
if is_3d:
|
||
RD = max(triton.next_power_of_2(rope_dim), 2)
|
||
# FLAT-row kernel: process (token, head) pairs flattened as
|
||
# row = token*n_heads + head, BLOCK_ROWS consecutive rows per program.
|
||
# The per-head contig kernel reads BLOCK_M tokens strided by
|
||
# n_heads*head_dim (very scattered on the 128-head attention output) and
|
||
# only reaches ~2.2 TB/s cold; the flat kernel's rows are head_dim apart
|
||
# -> ~4.5 TB/s cold (~2x). Microbench (MI300, 8192x128x64,
|
||
# cold): BLOCK_ROWS=16 + num_warps=1. Numerically bit-exact vs contig.
|
||
FLAT_BLOCK_ROWS = 16
|
||
n_rows = batch_size * n_heads
|
||
grid = (triton.cdiv(n_rows, FLAT_BLOCK_ROWS),)
|
||
apply_rotary_emb_flat_kernel[grid](
|
||
x,
|
||
freqs_real,
|
||
positions,
|
||
n_rows,
|
||
n_heads,
|
||
x.stride(0),
|
||
x.stride(1),
|
||
x.stride(2),
|
||
freqs_real.stride(0),
|
||
freqs_real.stride(1),
|
||
USE_POS=(positions is not None),
|
||
IS_INVERSE=inverse,
|
||
RD=RD,
|
||
RDH=RD // 2,
|
||
BLOCK_ROWS=FLAT_BLOCK_ROWS,
|
||
num_warps=1,
|
||
)
|
||
return x
|
||
BLOCK_P = max(triton.next_power_of_2(rope_dim // 2), 1)
|
||
grid = (triton.cdiv(batch_size, BLOCK_M), 1)
|
||
apply_rotary_emb_triton_kernel_batched[grid](
|
||
x,
|
||
freqs_real,
|
||
positions,
|
||
rope_dim,
|
||
batch_size,
|
||
x.stride(0),
|
||
0,
|
||
x.stride(-1),
|
||
freqs_real.stride(0),
|
||
freqs_real.stride(1),
|
||
USE_POS=(positions is not None),
|
||
IS_INVERSE=inverse,
|
||
IS_3D=False,
|
||
BLOCK_M=BLOCK_M,
|
||
BLOCK_P=BLOCK_P,
|
||
)
|
||
return x
|
||
|
||
is_3d = x.ndim == 3
|
||
|
||
if is_3d:
|
||
batch_size, n_heads, rope_dim = x.shape
|
||
else:
|
||
batch_size, rope_dim = x.shape
|
||
n_heads = 1
|
||
|
||
freqs_real = torch.view_as_real(freqs_cis).flatten(-2)
|
||
|
||
BLOCK_SIZE = 128
|
||
|
||
num_blocks_dim = triton.cdiv(rope_dim // 2, BLOCK_SIZE)
|
||
grid = (batch_size, n_heads if is_3d else 1, num_blocks_dim)
|
||
|
||
if positions is not None:
|
||
assert positions.shape == (
|
||
batch_size,
|
||
), f"positions shape {positions.shape} != ({batch_size},)"
|
||
|
||
apply_rotary_emb_triton_kernel[grid](
|
||
x,
|
||
freqs_real,
|
||
positions,
|
||
rope_dim,
|
||
x.stride(0),
|
||
x.stride(1) if is_3d else 0,
|
||
x.stride(-1),
|
||
freqs_real.stride(0),
|
||
freqs_real.stride(1),
|
||
USE_POS=True,
|
||
IS_INVERSE=inverse,
|
||
IS_3D=is_3d,
|
||
BLOCK_SIZE=BLOCK_SIZE,
|
||
)
|
||
else:
|
||
assert (
|
||
freqs_real.shape[0] == batch_size
|
||
), f"freqs_cis batch size {freqs_real.shape[0]} != x batch size {batch_size}"
|
||
|
||
apply_rotary_emb_triton_kernel[grid](
|
||
x,
|
||
freqs_real,
|
||
None,
|
||
rope_dim,
|
||
x.stride(0),
|
||
x.stride(1) if is_3d else 0,
|
||
x.stride(-1),
|
||
freqs_real.stride(0),
|
||
freqs_real.stride(1),
|
||
USE_POS=False,
|
||
IS_INVERSE=inverse,
|
||
IS_3D=is_3d,
|
||
BLOCK_SIZE=BLOCK_SIZE,
|
||
)
|
||
|
||
return x
|
||
|
||
|
||
@triton.jit
|
||
def _fused_norm_rope_kernel(
|
||
x_ptr,
|
||
weight_ptr,
|
||
freqs_real_ptr,
|
||
positions_ptr,
|
||
eps,
|
||
stride_x_row,
|
||
stride_freq_row,
|
||
HEAD_DIM: tl.constexpr,
|
||
ROPE_DIM: tl.constexpr,
|
||
HEAD_BLOCK: tl.constexpr,
|
||
ROPE_PAIR_BLOCK: tl.constexpr,
|
||
HAS_WEIGHT: tl.constexpr,
|
||
USE_POS: tl.constexpr,
|
||
):
|
||
# NOTE: avoids store-then-reload on the same kernel: rope-segment values
|
||
# are loaded a 2nd time as (real, imag) pairs straight from the input,
|
||
# rms_inv/weight applied in register, and all stores happen at the end.
|
||
pid = tl.program_id(0)
|
||
base = pid.to(tl.int64) * stride_x_row
|
||
|
||
offs = tl.arange(0, HEAD_BLOCK)
|
||
mask = offs < HEAD_DIM
|
||
x = tl.load(x_ptr + base + offs, mask=mask, other=0.0).to(tl.float32)
|
||
|
||
sum_sq = tl.sum(x * x, axis=0)
|
||
rms_inv = tl.rsqrt(sum_sq / HEAD_DIM + eps)
|
||
|
||
if HAS_WEIGHT:
|
||
w = tl.load(weight_ptr + offs, mask=mask, other=0.0).to(tl.float32)
|
||
x_normed = x * rms_inv * w
|
||
else:
|
||
x_normed = x * rms_inv
|
||
|
||
rope_start = HEAD_DIM - ROPE_DIM
|
||
|
||
pair_offs = tl.arange(0, ROPE_PAIR_BLOCK)
|
||
pair_mask = pair_offs < (ROPE_DIM // 2)
|
||
|
||
x_real = tl.load(
|
||
x_ptr + base + rope_start + 2 * pair_offs,
|
||
mask=pair_mask,
|
||
other=0.0,
|
||
).to(tl.float32)
|
||
x_imag = tl.load(
|
||
x_ptr + base + rope_start + 2 * pair_offs + 1,
|
||
mask=pair_mask,
|
||
other=0.0,
|
||
).to(tl.float32)
|
||
|
||
if HAS_WEIGHT:
|
||
w_real = tl.load(
|
||
weight_ptr + rope_start + 2 * pair_offs,
|
||
mask=pair_mask,
|
||
other=1.0,
|
||
).to(tl.float32)
|
||
w_imag = tl.load(
|
||
weight_ptr + rope_start + 2 * pair_offs + 1,
|
||
mask=pair_mask,
|
||
other=1.0,
|
||
).to(tl.float32)
|
||
x_real = x_real * rms_inv * w_real
|
||
x_imag = x_imag * rms_inv * w_imag
|
||
else:
|
||
x_real = x_real * rms_inv
|
||
x_imag = x_imag * rms_inv
|
||
|
||
if USE_POS:
|
||
position = tl.load(positions_ptr + pid).to(tl.int64)
|
||
else:
|
||
position = pid.to(tl.int64)
|
||
|
||
freq_base = position * stride_freq_row
|
||
f_real = tl.load(
|
||
freqs_real_ptr + freq_base + 2 * pair_offs,
|
||
mask=pair_mask,
|
||
other=0.0,
|
||
).to(tl.float32)
|
||
f_imag = tl.load(
|
||
freqs_real_ptr + freq_base + 2 * pair_offs + 1,
|
||
mask=pair_mask,
|
||
other=0.0,
|
||
).to(tl.float32)
|
||
|
||
out_real = x_real * f_real - x_imag * f_imag
|
||
out_imag = x_real * f_imag + x_imag * f_real
|
||
|
||
is_non_rope = offs < rope_start
|
||
tl.store(
|
||
x_ptr + base + offs,
|
||
x_normed.to(x_ptr.dtype.element_ty),
|
||
mask=mask & is_non_rope,
|
||
)
|
||
tl.store(
|
||
x_ptr + base + rope_start + 2 * pair_offs,
|
||
out_real.to(x_ptr.dtype.element_ty),
|
||
mask=pair_mask,
|
||
)
|
||
tl.store(
|
||
x_ptr + base + rope_start + 2 * pair_offs + 1,
|
||
out_imag.to(x_ptr.dtype.element_ty),
|
||
mask=pair_mask,
|
||
)
|
||
|
||
|
||
@triton.jit
|
||
def _fused_softmax_pool_kernel(
|
||
kv_score_ptr,
|
||
out_ptr,
|
||
stride_bs: tl.constexpr,
|
||
stride_k: tl.constexpr,
|
||
K: tl.constexpr,
|
||
HEAD_DIM: tl.constexpr,
|
||
HEAD_BLOCK: tl.constexpr,
|
||
):
|
||
pid = tl.program_id(0)
|
||
base = pid * stride_bs
|
||
|
||
offs = tl.arange(0, HEAD_BLOCK)
|
||
mask = offs < HEAD_DIM
|
||
|
||
max_val = tl.full([HEAD_BLOCK], float("-inf"), dtype=tl.float32)
|
||
for k in range(K):
|
||
s = tl.load(
|
||
kv_score_ptr + base + k * stride_k + HEAD_DIM + offs,
|
||
mask=mask,
|
||
other=float("-inf"),
|
||
).to(tl.float32)
|
||
max_val = tl.maximum(max_val, s)
|
||
|
||
sum_exp = tl.zeros([HEAD_BLOCK], dtype=tl.float32)
|
||
weighted = tl.zeros([HEAD_BLOCK], dtype=tl.float32)
|
||
for k in range(K):
|
||
s = tl.load(
|
||
kv_score_ptr + base + k * stride_k + HEAD_DIM + offs,
|
||
mask=mask,
|
||
other=float("-inf"),
|
||
).to(tl.float32)
|
||
v = tl.load(
|
||
kv_score_ptr + base + k * stride_k + offs,
|
||
mask=mask,
|
||
other=0.0,
|
||
).to(tl.float32)
|
||
w = tl.exp(s - max_val)
|
||
sum_exp += w
|
||
weighted += v * w
|
||
|
||
result = weighted / sum_exp
|
||
tl.store(
|
||
out_ptr + pid * HEAD_DIM + offs, result.to(out_ptr.dtype.element_ty), mask=mask
|
||
)
|
||
|
||
|
||
def fused_softmax_pool_triton(
|
||
kv_score: torch.Tensor,
|
||
head_dim: int,
|
||
) -> torch.Tensor:
|
||
"""Fused softmax-weighted-sum: out = (kv * softmax(score, dim=1)).sum(dim=1).
|
||
|
||
Replaces the generic cunn_SpatialSoftMaxForward + elementwise multiply + sum
|
||
with a single Triton kernel.
|
||
|
||
Args:
|
||
kv_score: [bs, K, 2 * head_dim] where first head_dim is kv, second is score.
|
||
head_dim: dimension of each of kv and score.
|
||
Returns:
|
||
output: [bs, head_dim]
|
||
"""
|
||
assert kv_score.dim() == 3
|
||
bs, K, last = kv_score.shape
|
||
assert last == 2 * head_dim
|
||
assert kv_score.is_contiguous()
|
||
|
||
out = torch.empty(bs, head_dim, dtype=kv_score.dtype, device=kv_score.device)
|
||
if bs == 0:
|
||
return out
|
||
|
||
HEAD_BLOCK = triton.next_power_of_2(head_dim)
|
||
grid = (bs,)
|
||
_fused_softmax_pool_kernel[grid](
|
||
kv_score,
|
||
out,
|
||
stride_bs=kv_score.stride(0),
|
||
stride_k=kv_score.stride(1),
|
||
K=K,
|
||
HEAD_DIM=head_dim,
|
||
HEAD_BLOCK=HEAD_BLOCK,
|
||
)
|
||
return out
|
||
|
||
|
||
def fused_norm_rope_inplace_triton(
|
||
kv: torch.Tensor,
|
||
weight: Optional[torch.Tensor],
|
||
eps: float,
|
||
freqs_cis: torch.Tensor,
|
||
positions: Optional[torch.Tensor] = None,
|
||
) -> None:
|
||
"""Fused RMSNorm (over head_dim) + RoPE (on last rope_dim of head_dim), in-place.
|
||
|
||
Equivalent to::
|
||
|
||
kv = rms_normalize(kv, eps, weight)
|
||
apply_rotary_emb_triton(kv[..., -rope_dim:], freqs_cis, positions=positions)
|
||
|
||
Args:
|
||
kv: [M, head_dim], any float dtype, contiguous along last dim. Modified in-place.
|
||
weight: [head_dim] or None.
|
||
eps: RMSNorm epsilon.
|
||
freqs_cis: complex tensor.
|
||
- If ``positions`` is None: shape [M, rope_dim // 2], one freq per token.
|
||
- Else: shape [max_seq, rope_dim // 2], full table; indexed by ``positions``.
|
||
positions: optional [M] int tensor, absolute positions to index into ``freqs_cis``.
|
||
"""
|
||
assert kv.dim() == 2 and kv.stride(-1) == 1
|
||
M, head_dim = kv.shape
|
||
|
||
freqs_real = torch.view_as_real(freqs_cis).flatten(-2)
|
||
rope_dim = freqs_real.shape[-1]
|
||
assert head_dim >= rope_dim and rope_dim % 2 == 0
|
||
if weight is not None:
|
||
assert weight.shape == (head_dim,)
|
||
if positions is None:
|
||
assert (
|
||
freqs_real.shape[0] == M
|
||
), f"freqs_cis row count {freqs_real.shape[0]} != M={M}"
|
||
else:
|
||
assert positions.shape == (M,) and positions.dim() == 1
|
||
|
||
if M == 0:
|
||
return
|
||
|
||
HEAD_BLOCK = triton.next_power_of_2(head_dim)
|
||
ROPE_PAIR_BLOCK = max(triton.next_power_of_2(rope_dim // 2), 1)
|
||
|
||
grid = (M,)
|
||
_fused_norm_rope_kernel[grid](
|
||
kv,
|
||
weight,
|
||
freqs_real,
|
||
positions,
|
||
eps,
|
||
kv.stride(0),
|
||
freqs_real.stride(0),
|
||
HEAD_DIM=head_dim,
|
||
ROPE_DIM=rope_dim,
|
||
HEAD_BLOCK=HEAD_BLOCK,
|
||
ROPE_PAIR_BLOCK=ROPE_PAIR_BLOCK,
|
||
HAS_WEIGHT=(weight is not None),
|
||
USE_POS=(positions is not None),
|
||
)
|
||
|
||
|
||
# Cache contiguous real/imag halves of each freqs_cis (its .real/.imag are
|
||
# strided views, stride=2 on the interleaved layout), keyed by id.
|
||
_NPU_ROPE_CONTIG_CACHE: dict[int, tuple] = {}
|
||
|
||
|
||
def _get_contig_freqs_real_imag(
|
||
freqs_cis: torch.Tensor,
|
||
) -> tuple[torch.Tensor, torch.Tensor]:
|
||
"""Return contiguous (real, imag) halves of ``freqs_cis``, cached by id.
|
||
|
||
Used by NPU rope paths to avoid the per-call StridedSlice materialization
|
||
triggered by aclnnIndex over the strided ``.real`` / ``.imag`` views of
|
||
the complex ``freqs_cis`` buffer. First call per freqs_cis pays the
|
||
contiguous() once; later calls reuse the cached tensors.
|
||
|
||
All callers within a single MQALayer (outer rope, indexer inner rope,
|
||
compressor epilog rope) get the same freqs_cis instance, so each layer
|
||
materializes at most one (real, imag) pair.
|
||
"""
|
||
cache_key = id(freqs_cis)
|
||
cached = _NPU_ROPE_CONTIG_CACHE.get(cache_key)
|
||
if cached is None:
|
||
cached = (freqs_cis.real.contiguous(), freqs_cis.imag.contiguous())
|
||
_NPU_ROPE_CONTIG_CACHE[cache_key] = cached
|
||
return cached
|
||
|
||
|
||
def get_fused_compressor_rope_cos_sin(
|
||
freqs_cis: torch.Tensor,
|
||
positions_cmp: torch.Tensor,
|
||
dtype: torch.dtype,
|
||
) -> tuple[torch.Tensor, torch.Tensor]:
|
||
"""Build (cos, sin) tensors shaped ``[T, rope_head_dim]`` for the fused
|
||
compressor op (``torch.ops.custom.compressor``).
|
||
|
||
The op consumes ``rope_cos`` / ``rope_sin`` of shape
|
||
``[min(T, T//cmp_ratio + B), rope_head_dim]`` in bf16/fp16. We index
|
||
the cached contig real/imag halves of the complex ``freqs_cis`` and
|
||
interleave-double the last dim to match the kernel's expected layout
|
||
(matches dsv4_release ``ComplexExpRotaryEmbedding.cos_cache``, which
|
||
is built as ``complex_cache.real.repeat_interleave(2, dim=-1)``).
|
||
|
||
Safe to call from inside a captured aclgraph: both ``index_select`` and
|
||
``repeat_interleave`` over a graph-input ``positions_cmp`` of fixed
|
||
capture-time shape produce static-shape outputs. Identical to what the
|
||
existing inplace_partial_rotary_mul fallback does at
|
||
:func:`v4_rope_inplace_npu`, just without the inverse / 4D-view step.
|
||
"""
|
||
real_contig, imag_contig = _get_contig_freqs_real_imag(freqs_cis)
|
||
cos_half = real_contig.index_select(0, positions_cmp)
|
||
sin_half = imag_contig.index_select(0, positions_cmp)
|
||
cos = cos_half.repeat_interleave(2, dim=-1).to(dtype)
|
||
sin = sin_half.repeat_interleave(2, dim=-1).to(dtype)
|
||
return cos, sin
|
||
|
||
|
||
def v4_rope_inplace_npu(
|
||
q_rope: torch.Tensor,
|
||
kv_rope: Optional[torch.Tensor],
|
||
freqs_cis: torch.Tensor,
|
||
positions: torch.Tensor,
|
||
inverse: bool = False,
|
||
) -> None:
|
||
"""In-place interleaved RoPE for V4 — torch fallback used on NPU.
|
||
|
||
Mirrors main's CUDA `fused_rope` kernel: consecutive (even, odd) pairs
|
||
of x form complex pairs, with `freqs_cis` a complex tensor where
|
||
`freqs_cis.real[t, k]` = cos(theta_{t,k}), `freqs_cis.imag` = sin(...)
|
||
indexed by frequency pair k in [0, rope_dim/2).
|
||
|
||
NOTE on V4-Flash YARN `mscale`: when the model was trained with the
|
||
YARN magnitude-scale `mscale` ≠ 1.0, the cos/sin values stored in
|
||
`freqs_cis` MUST already be pre-multiplied by `mscale` at precompute
|
||
time — see `precompute_freqs_cis`. This function
|
||
just reads what's stored; it does NOT apply mscale here.
|
||
|
||
Prefer the NPU-native `torch.ops.custom.inplace_partial_rotary_mul`:
|
||
the torch fallback differs by ~1 ULP per element vs the kernel because
|
||
torch does bf16*bf16 muls with bf16 accumulation while the NPU kernel
|
||
accumulates in fp32; 43 layers × (Q + K) = 86 rope calls compound that
|
||
drift enough to flip argmax on marginal prompts.
|
||
"""
|
||
# Build cos/sin caches in the kernel's expected (T, 1, 1, rope_dim) layout,
|
||
# each freq value repeated twice for the interleaved pairing convention.
|
||
freqs_real_contig, freqs_imag_contig = _get_contig_freqs_real_imag(freqs_cis)
|
||
cos_half = freqs_real_contig[positions] # (T, rope_dim/2)
|
||
sin_half = freqs_imag_contig[positions]
|
||
if inverse:
|
||
sin_half = -sin_half
|
||
cos_full = cos_half.repeat_interleave(2, dim=-1).to(q_rope.dtype)
|
||
sin_full = sin_half.repeat_interleave(2, dim=-1).to(q_rope.dtype)
|
||
rope_dim = cos_full.shape[-1]
|
||
# repeat_interleave produces a contiguous tensor, so the .view()
|
||
# below already returns a contiguous result — no .contiguous() needed.
|
||
cos4 = cos_full.view(-1, 1, 1, rope_dim)
|
||
sin4 = sin_full.view(-1, 1, 1, rope_dim)
|
||
# q_rope: (T, n_heads, rope_dim) → (T, 1, n_heads, rope_dim) view
|
||
# kv_rope: (T, 1, rope_dim) → (T, 1, 1, rope_dim) view
|
||
q_view = q_rope.unsqueeze(1)
|
||
torch.ops.custom.inplace_partial_rotary_mul(
|
||
q_view,
|
||
cos4,
|
||
sin4,
|
||
rotary_mode="interleave",
|
||
partial_slice=[0, rope_dim],
|
||
)
|
||
if kv_rope is not None:
|
||
if kv_rope.dim() == 3:
|
||
kv_view = kv_rope.unsqueeze(1)
|
||
else:
|
||
kv_view = kv_rope.view(-1, 1, 1, rope_dim)
|
||
torch.ops.custom.inplace_partial_rotary_mul(
|
||
kv_view,
|
||
cos4,
|
||
sin4,
|
||
rotary_mode="interleave",
|
||
partial_slice=[0, rope_dim],
|
||
)
|