Files
nvlabs--longlive/utils/rope_triton.py
T
2026-07-13 12:31:40 +08:00

136 lines
5.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES
#
# Licensed under the Apache License, Version 2.0 (the "License").
# You may not use this file except in compliance with the License.
# To view a copy of this license, visit http://www.apache.org/licenses/LICENSE-2.0
#
# SPDX-License-Identifier: Apache-2.0
"""Triton RoPE kernel for causal_rope_apply.
iter-42: replaces the complex<double> × view_as_complex × view_as_real chain
(33 ms / 1.3% of profile + feeding elementwise muls) with a single Triton
kernel. Internal precision is fp32 — bf16 outputs cannot resolve any precision
loss from fp32 vs fp64 arithmetic at this stage. cos / sin lookup tables come
from the complex128 freqs split into real / imag floats up-front (one-shot per
freqs_i cache entry).
"""
from __future__ import annotations
import torch
import triton
import triton.language as tl
# iter-45 (REVERTED): tried @triton.autotune over (BLOCK_N∈{4,8,16},
# num_warps∈{2,4,8}, num_stages∈{1,2,3}) = 27 configs. Result was FLAT vs
# iter-42 fixed BLOCK_N=8: median tied (-0.1%), total +0.4% (autotune warmup
# bled into p1/p2 p90). The original BLOCK_N=8 / default warps was already
# near-optimal for the (N=24, D_half=64) shape, autotune found no better.
# Reverted to fixed config — same kernel as iter-42.
#
# iter-46: kernel now accepts FULL x[i] of shape [S_total, N, D] and a
# runtime `seq_len` — for rows s < seq_len it applies rotation, for
# s >= seq_len it copies through. This subsumes the `torch.cat([rotated,
# x[i, seq_len:]])` step (1 fewer kernel + 1 fewer alloc per call). Also
# skips the upstream `.contiguous()` because we no longer slice x.
@triton.jit
def _rope_apply_kernel(
x_ptr, # [S_total, N, D] bf16 (D is even, pairs are (a,b)=(2d, 2d+1))
cos_ptr, # [seq_len, D/2] fp32 (valid only for s < seq_len)
sin_ptr, # [seq_len, D/2] fp32
out_ptr, # [S_total, N, D] bf16
SEQ_LEN, N, D_half,
x_stride_s, x_stride_n,
o_stride_s, o_stride_n,
cs_stride_s,
BLOCK_N: tl.constexpr,
BLOCK_D: tl.constexpr,
):
pid_s = tl.program_id(0)
pid_n = tl.program_id(1)
offs_n = pid_n * BLOCK_N + tl.arange(0, BLOCK_N)
offs_d = tl.arange(0, BLOCK_D) # over the D/2 pairs
n_mask = offs_n < N
d_mask = offs_d < D_half
x_row_base = pid_s * x_stride_s
o_row_base = pid_s * o_stride_s
a_offs = x_row_base + offs_n[:, None] * x_stride_n + (2 * offs_d)[None, :]
b_offs = a_offs + 1
mask = n_mask[:, None] & d_mask[None, :]
a = tl.load(x_ptr + a_offs, mask=mask, other=0.0).to(tl.float32)
b = tl.load(x_ptr + b_offs, mask=mask, other=0.0).to(tl.float32)
a_out_offs = o_row_base + offs_n[:, None] * o_stride_n + (2 * offs_d)[None, :]
b_out_offs = a_out_offs + 1
if pid_s < SEQ_LEN:
cs_base = pid_s * cs_stride_s
cos = tl.load(cos_ptr + cs_base + offs_d, mask=d_mask, other=0.0).to(tl.float32)
sin = tl.load(sin_ptr + cs_base + offs_d, mask=d_mask, other=0.0).to(tl.float32)
# Rotate: (a + bi) * (cos + sin i) = (a*cos - b*sin) + (a*sin + b*cos) i
out_a = a * cos[None, :] - b * sin[None, :]
out_b = a * sin[None, :] + b * cos[None, :]
tl.store(out_ptr + a_out_offs, out_a, mask=mask)
tl.store(out_ptr + b_out_offs, out_b, mask=mask)
else:
# passthrough copy for the unrotated tail
tl.store(out_ptr + a_out_offs, a, mask=mask)
tl.store(out_ptr + b_out_offs, b, mask=mask)
def _split_complex_to_cos_sin(freqs_complex: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
"""Convert complex128 freqs to (cos_f32, sin_f32) — once per cache entry."""
# freqs_complex shape: (S, 1, D/2). Squeeze the middle 1.
if freqs_complex.dim() == 3 and freqs_complex.size(1) == 1:
freqs_complex = freqs_complex.squeeze(1)
cos = freqs_complex.real.to(torch.float32).contiguous()
sin = freqs_complex.imag.to(torch.float32).contiguous()
return cos, sin
def rope_apply_triton(
x: torch.Tensor, # [S_total, N, D] bf16 (or fp16/fp32)
cos_f32: torch.Tensor, # [seq_len, D/2] fp32
sin_f32: torch.Tensor, # [seq_len, D/2] fp32
seq_len: int | None = None,
) -> torch.Tensor:
"""Apply rotary embedding via Triton kernel.
iter-46: when `seq_len < x.size(0)`, the kernel rotates the first
`seq_len` rows and copies through rows `[seq_len:]`. This replaces the
`cat([rotated, x[i, seq_len:]])` pattern in `causal_rope_apply` with a
single kernel + single allocation. `seq_len=None` (default) means rotate
all rows (equivalent to iter-42 behavior).
Returns a tensor of the same shape and dtype as `x`.
"""
assert x.dim() == 3, f"expected x.shape == (S, N, D), got {x.shape}"
S_total, N, D = x.shape
assert D % 2 == 0
D_half = D // 2
if seq_len is None:
seq_len = S_total
assert seq_len <= S_total
assert cos_f32.shape == (seq_len, D_half), \
f"cos_f32 expected ({seq_len},{D_half}), got {cos_f32.shape}"
assert sin_f32.shape == (seq_len, D_half)
out = torch.empty_like(x)
BLOCK_N = 8
BLOCK_D = triton.next_power_of_2(D_half)
grid = (S_total, triton.cdiv(N, BLOCK_N))
_rope_apply_kernel[grid](
x, cos_f32, sin_f32, out,
seq_len, N, D_half,
x.stride(0), x.stride(1),
out.stride(0), out.stride(1),
cos_f32.stride(0),
BLOCK_N=BLOCK_N, BLOCK_D=BLOCK_D,
)
return out