Files
unslothai--unsloth/unsloth/kernels/rope_embedding.py
T
wehub-resource-sync e93507a09c
Lockfile supply-chain audit / lockfile supply-chain audit (push) Has been cancelled
Windows Studio GGUF CI / GPU prebuilt resolves without Visual Studio (push) Has been cancelled
Windows Studio GGUF CI / setup.ps1 unit tests (VS 2026 / CMake guard) (push) Has been cancelled
Windows Studio GGUF CI / real-VS detection (VS 2022) (push) Has been cancelled
Windows Studio GGUF CI / real-VS detection (VS 2026) (push) Has been cancelled
Windows Studio GGUF CI / VC++ runtime detect + install round-trip (windows-2025-vs2026) (push) Has been cancelled
Windows Studio GGUF CI / VC++ runtime detect + install round-trip (windows-latest) (push) Has been cancelled
Windows Studio Update CI / Studio Updating Tests (push) Has been cancelled
Wheel CI / Wheel build + content sanity + import smoke (push) Has been cancelled
Lint CI / Source lint (Python + shell + YAML + JSON + safety nets) (push) Has been cancelled
MLX CI on Mac M1 / dispatch (push) Has been cancelled
Security audit / advisory audit (pip + npm + cargo) (push) Has been cancelled
Security audit / pip scan-packages :: extras (push) Has been cancelled
Security audit / pip scan-packages :: studio (push) Has been cancelled
Security audit / pip scan-packages :: hf-stack (push) Has been cancelled
Security audit / npm scan-packages (Studio frontend tarballs) (push) Has been cancelled
Security audit / workflow-trigger lint (pull_request_target / cache-poisoning) (push) Has been cancelled
Security audit / pytest tests/security (push) Has been cancelled
Security audit / npm provenance + new install-script diff (push) Has been cancelled
Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Backend CI / (Python 3.10) (push) Has been cancelled
Backend CI / (Python 3.11) (push) Has been cancelled
Backend CI / (Python 3.12) (push) Has been cancelled
Backend CI / (Python 3.13) (push) Has been cancelled
Backend CI / Repo tests (CPU) (push) Has been cancelled
Frontend CI / Frontend build + bundle sanity (push) Has been cancelled
Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Studio GGUF CI / JSON, images (push) Has been cancelled
Mac Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Mac Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Mac Studio GGUF CI / JSON, images (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-14) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-15) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-26) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-15-intel) (push) Has been cancelled
Mac Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-26-intel) (push) Has been cancelled
Mac Studio UI CI / Chat UI Tests (push) Has been cancelled
Studio Tauri CI / Tauri Linux debug build (no codesign) (push) Has been cancelled
Mac Studio Update CI / Studio Updating Tests (push) Has been cancelled
Studio UI CI / Chat UI Tests (push) Has been cancelled
Windows Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Windows Studio UI CI / Chat UI Tests (push) Has been cancelled
Studio Update CI / Studio Updating Tests (push) Has been cancelled
Core / Core (HF=default + TRL=default) (push) Has been cancelled
Core / Core (HF=4.57.6 + TRL<1) (push) Has been cancelled
Core / Core (HF=latest + TRL=latest) (push) Has been cancelled
Core / llama.cpp build + smoke (push) Has been cancelled
Windows Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Windows Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Windows Studio GGUF CI / JSON, images (push) Has been cancelled
Windows Studio GGUF CI / Studio install + inference without Visual Studio (push) Has been cancelled
Studio export capability / capability (macos-latest) (push) Has been cancelled
Studio export capability / capability (ubuntu-latest) (push) Has been cancelled
Studio export capability / capability (windows-latest) (push) Has been cancelled
Cross-platform parity / parity (macos-latest) (push) Has been cancelled
Cross-platform parity / parity (windows-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
Studio load-orchestrator CI / test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:59:56 +08:00

440 lines
14 KiB
Python

# Copyright 2023-present Daniel Han-Chen, Michael Han-Chen & the Unsloth team. All rights reserved.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import triton
import triton.language as tl
import torch
from ..device_type import DEVICE_COUNT
from .utils import calculate_settings, torch_gpu_device, torch_device_stream
def _rope_embedding_QK(
Q,
Q_batch_stride,
Q_head_stride,
Q_seq_stride,
K,
K_batch_stride,
K_head_stride,
K_seq_stride,
cos,
cos_row_stride,
sin,
sin_row_stride,
rope_embedding_indices,
seqlen,
head_dim: tl.constexpr,
n_heads_K: tl.constexpr,
BACKWARD_PASS: tl.constexpr,
HAS_ROPE_INDICES: tl.constexpr,
BLOCK_SIZE: tl.constexpr,
):
row_position = tl.program_id(0)
head_position = tl.program_id(1)
col_offsets = tl.arange(0, BLOCK_SIZE)
half_head_dim = head_dim // 2
mask = col_offsets < half_head_dim
if HAS_ROPE_INDICES:
rot_position = tl.load(
rope_embedding_indices + row_position,
eviction_policy = "evict_first",
).to(tl.int32)
else:
rot_position = row_position % seqlen
cos_ptr = cos + rot_position * cos_row_stride
sin_ptr = sin + rot_position * sin_row_stride
sin1 = tl.load(
sin_ptr + col_offsets,
mask = mask,
other = 0,
)
cos1 = tl.load(
cos_ptr + col_offsets,
mask = mask,
other = 0,
)
if BACKWARD_PASS:
sin1 = -sin1
batch_id = row_position // seqlen
seq_index = row_position - batch_id * seqlen
q_ptr = Q + batch_id * Q_batch_stride + head_position * Q_head_stride + seq_index * Q_seq_stride
q0 = tl.load(q_ptr + col_offsets, mask = mask, other = 0)
q1 = tl.load(q_ptr + half_head_dim + col_offsets, mask = mask, other = 0)
tl.store(q_ptr + col_offsets, q0 * cos1 - q1 * sin1, mask = mask)
tl.store(q_ptr + half_head_dim + col_offsets, q1 * cos1 + q0 * sin1, mask = mask)
if head_position < n_heads_K:
k_ptr = (
K + batch_id * K_batch_stride + head_position * K_head_stride + seq_index * K_seq_stride
)
k0 = tl.load(k_ptr + col_offsets, mask = mask, other = 0)
k1 = tl.load(k_ptr + half_head_dim + col_offsets, mask = mask, other = 0)
tl.store(k_ptr + col_offsets, k0 * cos1 - k1 * sin1, mask = mask)
tl.store(k_ptr + half_head_dim + col_offsets, k1 * cos1 + k0 * sin1, mask = mask)
_rope_embedding_QK = triton.jit(_rope_embedding_QK)
_rope_embedding_QK = triton.heuristics(
{
"BACKWARD_PASS": lambda args: bool(args["BACKWARD_PASS"]),
"HAS_ROPE_INDICES": lambda args: bool(args["HAS_ROPE_INDICES"]),
}
)(_rope_embedding_QK)
ROPE_GROUP_SIZE: int = 4
def _rope_embedding(
Q,
Q_row_stride: tl.constexpr,
cos,
cos_row_stride: tl.constexpr,
sin,
sin_row_stride: tl.constexpr,
seqlen,
head_dim: tl.constexpr,
n_heads: tl.constexpr,
BACKWARD_PASS: tl.constexpr,
BLOCK_SIZE: tl.constexpr,
):
"""
Calculates the RoPE Embedding quickly
RoPE is Q * cos + rotate_half(Q) * sin
See our blog post for more info
"""
ROPE_GROUP_SIZE = 4
row_position = tl.program_id(0)
group_head_position = tl.program_id(1)
col_offsets = tl.arange(0, BLOCK_SIZE)
half_head_dim = head_dim // 2
mask = col_offsets < half_head_dim
sin1 = tl.load(
sin + (row_position % seqlen) * sin_row_stride + half_head_dim * 0 + col_offsets,
mask = mask,
other = 0,
)
cos1 = tl.load(
cos + (row_position % seqlen) * cos_row_stride + half_head_dim * 0 + col_offsets,
mask = mask,
other = 0,
)
if BACKWARD_PASS:
# See our blog post for more info.
sin1 = -sin1
# [TODO] Autotune ROPE_GROUP_SIZE to be 1, 2, 4, 8
head_start = group_head_position * ROPE_GROUP_SIZE
head_end = min((head_start + ROPE_GROUP_SIZE), n_heads)
# 10% Faster kernel from [HuyNguyen-hust](https://github.com/unslothai/unsloth/pull/238)
for k in range(head_start, head_end):
offs_q1 = row_position * Q_row_stride + k * head_dim + col_offsets
offs_q2 = row_position * Q_row_stride + k * head_dim + col_offsets + half_head_dim
# For Gemma - sometimes RoPE must be done in float32 and not bfloat16
Q1 = tl.load(Q + offs_q1, mask = mask, other = 0).to(sin1.dtype)
Q2 = tl.load(Q + offs_q2, mask = mask, other = 0).to(sin1.dtype)
tl.store(Q + offs_q1, Q1 * cos1 - Q2 * sin1, mask = mask)
tl.store(Q + offs_q2, Q2 * cos1 + Q1 * sin1, mask = mask)
_rope_embedding = triton.jit(_rope_embedding)
_rope_embedding = triton.heuristics(
{
"BACKWARD_PASS": lambda args: bool(args["BACKWARD_PASS"]),
}
)(_rope_embedding)
class Fast_RoPE_Embedding(torch.autograd.Function):
@staticmethod
def forward(ctx, Q, cos, sin):
cos, sin = cos.squeeze(), sin.squeeze()
batch: int
seq_len: int
n_heads: int
head_dim: int
batch, seq_len, n_heads, head_dim = Q.shape
Q = Q.reshape(batch * seq_len, n_heads * head_dim)
n_rows: int
n_cols: int
n_rows, n_cols = Q.shape
assert seq_len <= cos.shape[0]
# [TODO] Changing blocksize to head_dim//2 seems to have
# some concurrency / un-deterministic issues.
BLOCK_SIZE, num_warps = calculate_settings(head_dim // 2) # (head_dim//2)
# group_size = 4 # 4 or 8, too large group_size can hurt performance.
div: int
mod: int
div, mod = divmod(n_heads, ROPE_GROUP_SIZE)
n_groups: int = div + (mod != 0)
with torch_gpu_device(Q.device):
_rope_embedding[
(
n_rows,
n_groups,
)
](
Q,
Q.stride(0),
cos,
cos.stride(0),
sin,
sin.stride(0),
seq_len,
head_dim,
n_heads,
BACKWARD_PASS = False,
BLOCK_SIZE = BLOCK_SIZE,
num_warps = num_warps,
)
ctx.BLOCK_SIZE = BLOCK_SIZE
ctx.num_warps = num_warps
ctx.n_groups = n_groups
ctx.cos = cos
ctx.sin = sin
return Q.reshape(batch, seq_len, n_heads, head_dim)
@staticmethod
def backward(ctx, dY):
batch: int
seq_len: int
n_heads: int
head_dim: int
batch, seq_len, n_heads, head_dim = dY.shape
dY = dY.reshape(batch * seq_len, n_heads * head_dim)
n_rows: int
n_cols: int
n_rows, n_cols = dY.shape
cos = ctx.cos
sin = ctx.sin
with torch_gpu_device(dY.device):
_rope_embedding[
(
n_rows,
ctx.n_groups,
)
](
dY,
dY.stride(0),
cos,
cos.stride(0),
sin,
sin.stride(0),
seq_len,
head_dim,
n_heads,
BACKWARD_PASS = True,
BLOCK_SIZE = ctx.BLOCK_SIZE,
num_warps = ctx.num_warps,
)
dY = dY.reshape(batch, seq_len, n_heads, head_dim)
return (
dY,
None,
None,
)
# [TODO] Unsure why RoPE Embedding is not torch.compiling properly
@torch.compiler.disable
def fast_rope_embedding(
Q,
K,
cos,
sin,
rope_embedding_indices = None,
):
if rope_embedding_indices is not None:
Q_out, K_out = Fast_RoPE_Embedding_QK.apply(Q, K, cos, sin, rope_embedding_indices)
else:
Q_out = Fast_RoPE_Embedding.apply(Q.transpose(1, 2).contiguous(), cos, sin).transpose(1, 2)
K_out = Fast_RoPE_Embedding.apply(K.transpose(1, 2).contiguous(), cos, sin).transpose(1, 2)
if DEVICE_COUNT > 1:
torch_device_stream(Q.device).synchronize()
return Q_out, K_out
class Fast_RoPE_Embedding_QK(torch.autograd.Function):
@staticmethod
def forward(ctx, Q, K, cos, sin, rope_indices):
has_indices = rope_indices is not None
cos, sin = cos.squeeze(), sin.squeeze()
batch, n_heads_Q, seq_len, head_dim = Q.shape
_, n_heads_K, _, _ = K.shape
# Inplace rotary embedding is generally fine
Q_out = Q.clone() if not Q.is_contiguous() else Q
K_out = K.clone() if not K.is_contiguous() else K
if has_indices:
# TRL's rotary indices are always in int32, so casting is just for safety
rope_ptr = rope_indices.reshape(-1).to(dtype = torch.int32, device = Q.device)
else:
rope_ptr = cos.new_empty(1, dtype = torch.int32)
BLOCK_SIZE, num_warps = calculate_settings(head_dim)
Q_batch_stride, Q_head_stride, Q_seq_stride = (
Q_out.stride(0),
Q_out.stride(1),
Q_out.stride(2),
)
K_batch_stride, K_head_stride, K_seq_stride = (
K_out.stride(0),
K_out.stride(1),
K_out.stride(2),
)
with torch_gpu_device(Q.device):
_rope_embedding_QK[(batch * seq_len, n_heads_Q)](
Q_out,
Q_batch_stride,
Q_head_stride,
Q_seq_stride,
K_out,
K_batch_stride,
K_head_stride,
K_seq_stride,
cos,
cos.stride(0),
sin,
sin.stride(0),
rope_ptr,
seq_len,
head_dim = head_dim,
n_heads_K = n_heads_K,
BACKWARD_PASS = False,
HAS_ROPE_INDICES = has_indices,
BLOCK_SIZE = BLOCK_SIZE,
num_warps = num_warps,
)
ctx.block_size = BLOCK_SIZE
ctx.num_warps = num_warps
ctx.has_indices = has_indices
ctx.cos = cos
ctx.sin = sin
ctx.rope_indices = rope_ptr if has_indices else None
ctx.seq_len = seq_len
ctx.n_heads_Q = n_heads_Q
ctx.n_heads_K = n_heads_K
return (
Q_out,
K_out,
)
@staticmethod
def backward(ctx, dQ, dK):
batch, _, _, head_dim = dQ.shape
rope_ptr = ctx.rope_indices if ctx.has_indices else ctx.cos.new_empty(1, dtype = torch.int32)
# Inplace rotary embedding is generally fine
dQ_out = dQ.clone() if not dQ.is_contiguous() else dQ
dK_out = dK.clone() if not dK.is_contiguous() else dK
Q_batch_stride, Q_head_stride, Q_seq_stride = (
dQ_out.stride(0),
dQ_out.stride(1),
dQ_out.stride(2),
)
K_batch_stride, K_head_stride, K_seq_stride = (
dK_out.stride(0),
dK_out.stride(1),
dK_out.stride(2),
)
with torch_gpu_device(dQ.device):
_rope_embedding_QK[(batch * ctx.seq_len, ctx.n_heads_Q)](
dQ_out,
Q_batch_stride,
Q_head_stride,
Q_seq_stride,
dK_out,
K_batch_stride,
K_head_stride,
K_seq_stride,
ctx.cos,
ctx.cos.stride(0),
ctx.sin,
ctx.sin.stride(0),
rope_ptr,
ctx.seq_len,
head_dim = head_dim,
n_heads_K = ctx.n_heads_K,
BACKWARD_PASS = True,
HAS_ROPE_INDICES = ctx.has_indices,
BLOCK_SIZE = ctx.block_size,
num_warps = ctx.num_warps,
)
return (dQ_out, dK_out, None, None, None)
class Slow_RoPE_Embedding(torch.autograd.Function):
@staticmethod
def forward(ctx, Q, cos, sin, position_ids):
if position_ids is not None:
# The first two dimensions of cos and sin are always 1, so we can `squeeze` them.
cos = cos.squeeze(1).squeeze(0) # [seq_len, dim]
sin = sin.squeeze(1).squeeze(0) # [seq_len, dim]
cos = cos[position_ids].unsqueeze(2) # [bs, seq_len, 1, dim]
sin = sin[position_ids].unsqueeze(2) # [bs, seq_len, 1, dim]
# Q * cos + rotate_half(Q) * sin
half = Q.shape[-1] // 2
RH_Q = torch.cat((-Q[..., half:], Q[..., :half]), dim = -1)
Q *= cos
Q.addcmul_(RH_Q, sin)
# RH_Q *= sin
# Q += RH_Q
ctx.save_for_backward(cos, sin)
return Q
@staticmethod
def backward(ctx, dY):
cos, sin = ctx.saved_tensors
# Q * cos + rotate_half.T(Q) * sin
half = dY.shape[-1] // 2
RH_dY = torch.cat((dY[..., half:], -dY[..., :half]), dim = -1)
dY *= cos
dY.addcmul_(RH_dY, sin)
# RH_dY *= sin
# dY += RH_dY
return dY, None, None, None
def inplace_rope_embedding(Q, K, cos, sin, position_ids):
Q = Slow_RoPE_Embedding.apply(Q, cos, sin, position_ids)
K = Slow_RoPE_Embedding.apply(K, cos, sin, position_ids)
torch_device_stream(Q.device).synchronize()
return Q, K