Files
wehub-resource-sync 59a0a3844c
PR Test AMD / cancel-on-close (push) Has been skipped
PR Test NVIDIA ARM / scan (push) Has been skipped
PR Test NVIDIA / cancel-on-close (push) Has been skipped
PR Test AMD / scan (push) Has been skipped
PR Test NVIDIA ARM / cancel-on-close (push) Has been skipped
PR Test NVIDIA / scan (push) Has been skipped
Release Docker Images / build (cu129-torch-2.11.0) (push) Has been skipped
Release Docker Images / build (cu130-torch-2.11.0) (push) Has been skipped
Release PyPI / publish (push) Has been skipped
Scheduler Python Test / test (push) Successful in 27m19s
Docs / build (push) Successful in 28m8s
Scheduler C++ Test / test (push) Successful in 28m19s
Scheduler C++ Test / test-flat (push) Successful in 28m18s
Docs / deploy (push) Has been cancelled
PR Test AMD / finish (push) Has been cancelled
PR Test NVIDIA / finish (push) Has been cancelled
PR Test NVIDIA ARM / finish (push) Has been cancelled
PR Test NVIDIA ARM / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
PR Test AMD / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
PR Test NVIDIA / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:32:31 +08:00

485 lines
16 KiB
Python

# Copyright (c) 2026 LightSeek Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
from __future__ import annotations
import pytest
import torch
from tokenspeed_kernel.ops.embedding import (
FusedSetKVBufferArg,
apply_rope,
apply_rope_mla,
)
@pytest.mark.parametrize("solution", ["triton", "cuda"])
def test_rope_neox_full_bf16(
device: str,
solution: str,
require,
) -> None:
torch.manual_seed(0)
num_tokens = 17
num_q_heads = 8
num_k_heads = 2
head_size = 128
rotary_dim = 128
max_position = 1024
dtype = torch.bfloat16
require("embedding", "rope", solution, dtype, "q")
inv_freq = 1.0 / (
10000.0
** (
torch.arange(0, rotary_dim, 2, device=device, dtype=torch.float32)
/ rotary_dim
)
)
t = torch.arange(max_position, device=device, dtype=torch.float32)
freqs = torch.einsum("i,j -> ij", t, inv_freq)
cos_sin_cache = torch.cat((freqs.cos(), freqs.sin()), dim=-1).contiguous()
positions = torch.randint(
0, max_position, (num_tokens,), device=device, dtype=torch.int64
)
query = torch.randn(num_tokens, num_q_heads * head_size, device=device, dtype=dtype)
key = torch.randn(num_tokens, num_k_heads * head_size, device=device, dtype=dtype)
# Reference (PyTorch).
cos_sin_ref = cos_sin_cache.index_select(0, positions)
cos_ref, sin_ref = cos_sin_ref.chunk(2, dim=-1)
cos_ref = cos_ref.unsqueeze(-2).to(dtype)
sin_ref = sin_ref.unsqueeze(-2).to(dtype)
q_ref = query.clone().view(num_tokens, num_q_heads, head_size)
q1, q2 = torch.chunk(q_ref, 2, dim=-1)
q_out = torch.cat(
(q1 * cos_ref - q2 * sin_ref, q2 * cos_ref + q1 * sin_ref), dim=-1
)
q_ref = q_out.reshape(num_tokens, num_q_heads * head_size)
k_ref = key.clone().view(num_tokens, num_k_heads, head_size)
k1, k2 = torch.chunk(k_ref, 2, dim=-1)
k_out = torch.cat(
(k1 * cos_ref - k2 * sin_ref, k2 * cos_ref + k1 * sin_ref), dim=-1
)
k_ref = k_out.reshape(num_tokens, num_k_heads * head_size)
apply_rope(
positions=positions,
q=query,
k=key,
head_size=head_size,
cos_sin_cache=cos_sin_cache,
is_neox=True,
solution=solution,
)
torch.testing.assert_close(query, q_ref, rtol=2e-2, atol=2e-2)
torch.testing.assert_close(key, k_ref, rtol=2e-2, atol=2e-2)
@pytest.mark.parametrize("solution", ["triton", "cuda"])
def test_rope_gptj_full_bf16(
device: str,
solution: str,
require,
) -> None:
torch.manual_seed(1)
num_tokens = 9
num_q_heads = 4
num_k_heads = 2
head_size = 64
rotary_dim = 64
max_position = 512
dtype = torch.bfloat16
require("embedding", "rope", solution, dtype, "q")
inv_freq = 1.0 / (
10000.0
** (
torch.arange(0, rotary_dim, 2, device=device, dtype=torch.float32)
/ rotary_dim
)
)
t = torch.arange(max_position, device=device, dtype=torch.float32)
freqs = torch.einsum("i,j -> ij", t, inv_freq)
cos_sin_cache = torch.cat((freqs.cos(), freqs.sin()), dim=-1).contiguous()
positions = torch.randint(
0, max_position, (num_tokens,), device=device, dtype=torch.int64
)
query = torch.randn(num_tokens, num_q_heads * head_size, device=device, dtype=dtype)
key = torch.randn(num_tokens, num_k_heads * head_size, device=device, dtype=dtype)
cos_sin_ref = cos_sin_cache.index_select(0, positions)
cos_ref, sin_ref = cos_sin_ref.chunk(2, dim=-1)
cos_ref = cos_ref.unsqueeze(-2).to(dtype)
sin_ref = sin_ref.unsqueeze(-2).to(dtype)
def _gptj_ref(x, num_heads):
x = x.view(num_tokens, num_heads, head_size)
x1 = x[..., ::2]
x2 = x[..., 1::2]
o1 = x1 * cos_ref - x2 * sin_ref
o2 = x2 * cos_ref + x1 * sin_ref
return (
torch.stack((o1, o2), dim=-1)
.flatten(-2)
.reshape(num_tokens, num_heads * head_size)
)
q_ref = _gptj_ref(query.clone(), num_q_heads)
k_ref = _gptj_ref(key.clone(), num_k_heads)
apply_rope(
positions=positions,
q=query,
k=key,
head_size=head_size,
cos_sin_cache=cos_sin_cache,
is_neox=False,
solution=solution,
)
torch.testing.assert_close(query, q_ref, rtol=2e-2, atol=2e-2)
torch.testing.assert_close(key, k_ref, rtol=2e-2, atol=2e-2)
@pytest.mark.parametrize("solution", ["triton", "cuda"])
def test_rope_neox_partial_bf16(
device: str,
solution: str,
require,
) -> None:
torch.manual_seed(2)
num_tokens = 5
num_q_heads = 4
num_k_heads = 1
head_size = 128
rotary_dim = 64
max_position = 256
dtype = torch.bfloat16
require("embedding", "rope", solution, dtype, "q")
inv_freq = 1.0 / (
10000.0
** (
torch.arange(0, rotary_dim, 2, device=device, dtype=torch.float32)
/ rotary_dim
)
)
t = torch.arange(max_position, device=device, dtype=torch.float32)
freqs = torch.einsum("i,j -> ij", t, inv_freq)
cos_sin_cache = torch.cat((freqs.cos(), freqs.sin()), dim=-1).contiguous()
positions = torch.randint(
0, max_position, (num_tokens,), device=device, dtype=torch.int64
)
query = torch.randn(num_tokens, num_q_heads * head_size, device=device, dtype=dtype)
key = torch.randn(num_tokens, num_k_heads * head_size, device=device, dtype=dtype)
query_orig = query.clone()
key_orig = key.clone()
cos_sin_ref = cos_sin_cache.index_select(0, positions)
cos_ref, sin_ref = cos_sin_ref.chunk(2, dim=-1)
cos_ref = cos_ref.unsqueeze(-2).to(dtype)
sin_ref = sin_ref.unsqueeze(-2).to(dtype)
def _ref(x, num_heads):
x = x.view(num_tokens, num_heads, head_size)
rot = x[..., :rotary_dim]
rest = x[..., rotary_dim:]
r1, r2 = torch.chunk(rot, 2, dim=-1)
rot_out = torch.cat(
(r1 * cos_ref - r2 * sin_ref, r2 * cos_ref + r1 * sin_ref), dim=-1
)
return torch.cat((rot_out, rest), dim=-1).reshape(
num_tokens, num_heads * head_size
)
q_ref = _ref(query.clone(), num_q_heads)
k_ref = _ref(key.clone(), num_k_heads)
apply_rope(
positions=positions,
q=query,
k=key,
head_size=head_size,
cos_sin_cache=cos_sin_cache,
is_neox=True,
solution=solution,
)
torch.testing.assert_close(query, q_ref, rtol=2e-2, atol=2e-2)
torch.testing.assert_close(key, k_ref, rtol=2e-2, atol=2e-2)
q_view = query.view(num_tokens, num_q_heads, head_size)
q_orig_view = query_orig.view(num_tokens, num_q_heads, head_size)
assert torch.equal(q_view[..., rotary_dim:], q_orig_view[..., rotary_dim:])
k_view = key.view(num_tokens, num_k_heads, head_size)
k_orig_view = key_orig.view(num_tokens, num_k_heads, head_size)
assert torch.equal(k_view[..., rotary_dim:], k_orig_view[..., rotary_dim:])
@pytest.mark.parametrize("solution", ["triton", "cuda"])
def test_rope_single_token(
device: str,
solution: str,
require,
) -> None:
"""Edge case: num_tokens == 1 (decode step)."""
torch.manual_seed(4)
num_tokens = 1
num_q_heads = 8
num_k_heads = 1
head_size = 128
rotary_dim = 128
max_position = 64
dtype = torch.bfloat16
require("embedding", "rope", solution, dtype, "q")
inv_freq = 1.0 / (
10000.0
** (
torch.arange(0, rotary_dim, 2, device=device, dtype=torch.float32)
/ rotary_dim
)
)
t = torch.arange(max_position, device=device, dtype=torch.float32)
freqs = torch.einsum("i,j -> ij", t, inv_freq)
cos_sin_cache = torch.cat((freqs.cos(), freqs.sin()), dim=-1).contiguous()
positions = torch.tensor([5], device=device, dtype=torch.int64)
query = torch.randn(num_tokens, num_q_heads * head_size, device=device, dtype=dtype)
key = torch.randn(num_tokens, num_k_heads * head_size, device=device, dtype=dtype)
cos_sin_ref = cos_sin_cache.index_select(0, positions)
cos_ref, sin_ref = cos_sin_ref.chunk(2, dim=-1)
cos_ref = cos_ref.unsqueeze(-2).to(dtype)
sin_ref = sin_ref.unsqueeze(-2).to(dtype)
def _ref(x, num_heads):
x = x.view(num_tokens, num_heads, head_size)
x1, x2 = torch.chunk(x, 2, dim=-1)
return torch.cat(
(x1 * cos_ref - x2 * sin_ref, x2 * cos_ref + x1 * sin_ref), dim=-1
).reshape(num_tokens, num_heads * head_size)
q_ref = _ref(query.clone(), num_q_heads)
k_ref = _ref(key.clone(), num_k_heads)
apply_rope(
positions=positions,
q=query,
k=key,
head_size=head_size,
cos_sin_cache=cos_sin_cache,
is_neox=True,
solution=solution,
)
torch.testing.assert_close(query, q_ref, rtol=2e-2, atol=2e-2)
torch.testing.assert_close(key, k_ref, rtol=2e-2, atol=2e-2)
@pytest.mark.parametrize("solution", ["triton", "cuda"])
def test_rope_fused_set_kv_buffer(
device: str,
solution: str,
require,
) -> None:
torch.manual_seed(5)
num_tokens = 13
num_q_heads = 4
num_k_heads = 2
head_size = 128
rotary_dim = 128
max_position = 512
cache_size = 32
dtype = torch.bfloat16
require("embedding", "rope", solution, dtype, "q")
inv_freq = 1.0 / (
10000.0
** (
torch.arange(0, rotary_dim, 2, device=device, dtype=torch.float32)
/ rotary_dim
)
)
t = torch.arange(max_position, device=device, dtype=torch.float32)
freqs = torch.einsum("i,j -> ij", t, inv_freq)
cos_sin_cache = torch.cat((freqs.cos(), freqs.sin()), dim=-1).contiguous()
positions = torch.randint(
0, max_position, (num_tokens,), device=device, dtype=torch.int64
)
query = torch.randn(num_tokens, num_q_heads * head_size, device=device, dtype=dtype)
key = torch.randn(num_tokens, num_k_heads * head_size, device=device, dtype=dtype)
value = torch.randn(num_tokens, num_k_heads, head_size, device=device, dtype=dtype)
query_orig = query.clone()
key_orig = key.clone()
cache_loc = torch.arange(num_tokens, device=device, dtype=torch.int32) + 3
k_buffer = torch.zeros(
cache_size, num_k_heads * head_size, device=device, dtype=dtype
)
v_buffer = torch.zeros_like(k_buffer)
q_rope_out = torch.empty_like(query)
cos_sin_ref = cos_sin_cache.index_select(0, positions)
cos_ref, sin_ref = cos_sin_ref.chunk(2, dim=-1)
cos_ref = cos_ref.unsqueeze(-2).to(dtype)
sin_ref = sin_ref.unsqueeze(-2).to(dtype)
q_ref_view = query_orig.view(num_tokens, num_q_heads, head_size)
q1, q2 = torch.chunk(q_ref_view, 2, dim=-1)
q_ref = torch.cat(
(q1 * cos_ref - q2 * sin_ref, q2 * cos_ref + q1 * sin_ref), dim=-1
).reshape(num_tokens, num_q_heads * head_size)
k_ref_view = key_orig.view(num_tokens, num_k_heads, head_size)
k1, k2 = torch.chunk(k_ref_view, 2, dim=-1)
k_ref = torch.cat(
(k1 * cos_ref - k2 * sin_ref, k2 * cos_ref + k1 * sin_ref), dim=-1
).reshape(num_tokens, num_k_heads * head_size)
apply_rope(
positions=positions,
q=query,
k=key,
head_size=head_size,
cos_sin_cache=cos_sin_cache,
is_neox=True,
fused_set_kv_buffer_arg=FusedSetKVBufferArg(
value=value,
k_buffer=k_buffer,
v_buffer=v_buffer,
k_scale=None,
v_scale=None,
cache_loc=cache_loc,
),
q_rope_out=q_rope_out,
solution=solution,
)
torch.testing.assert_close(query, query_orig, rtol=0, atol=0)
torch.testing.assert_close(q_rope_out, q_ref, rtol=2e-2, atol=2e-2)
torch.testing.assert_close(key, k_ref, rtol=2e-2, atol=2e-2)
torch.testing.assert_close(
k_buffer.index_select(0, cache_loc), k_ref, rtol=2e-2, atol=2e-2
)
torch.testing.assert_close(
v_buffer.index_select(0, cache_loc),
value.reshape(num_tokens, num_k_heads * head_size),
rtol=0,
atol=0,
)
@pytest.mark.parametrize("solution", [None, "triton", "flashinfer"])
@pytest.mark.parametrize("is_neox", [True, False])
def test_rope_mla_quantize(
device: str,
solution: str,
is_neox: bool,
require,
) -> None:
torch.manual_seed(6)
dtype = torch.bfloat16
if solution is not None:
require("embedding", "rope_mla", solution, dtype, "q_rope")
num_tokens = 13
num_heads = 4
nope_dim = 32
rope_dim = 64
max_position = 512
inv_freq = 1.0 / (
10000.0
** (torch.arange(0, rope_dim, 2, device=device, dtype=torch.float32) / rope_dim)
)
t = torch.arange(max_position, device=device, dtype=torch.float32)
freqs = torch.einsum("i,j -> ij", t, inv_freq)
cos_sin_cache = torch.cat((freqs.cos(), freqs.sin()), dim=-1).contiguous()
positions = torch.randint(
0, max_position, (num_tokens,), device=device, dtype=torch.int64
)
cos, sin = cos_sin_cache.index_select(0, positions).chunk(2, dim=-1)
cos = cos.unsqueeze(1)
sin = sin.unsqueeze(1)
rope_ref = lambda x: (
torch.cat(
(
torch.chunk(x.float(), 2, dim=-1)[0] * cos
- torch.chunk(x.float(), 2, dim=-1)[1] * sin,
torch.chunk(x.float(), 2, dim=-1)[1] * cos
+ torch.chunk(x.float(), 2, dim=-1)[0] * sin,
),
dim=-1,
)
if is_neox
else torch.stack(
(
x.float()[..., ::2] * cos - x.float()[..., 1::2] * sin,
x.float()[..., 1::2] * cos + x.float()[..., ::2] * sin,
),
dim=-1,
).flatten(-2)
).to(x.dtype)
q_rope = torch.randn(num_tokens, num_heads, rope_dim, device=device, dtype=dtype)
k_rope = torch.randn(num_tokens, num_heads, rope_dim, device=device, dtype=dtype)
q_nope = torch.randn(num_tokens, num_heads, nope_dim, device=device, dtype=dtype)
k_nope = torch.randn(num_tokens, num_heads, nope_dim, device=device, dtype=dtype)
quant_scale_q = 2.0
quant_scale_kv = 2.0
query_fp8, key_fp8 = apply_rope_mla(
positions=positions,
q_rope=q_rope,
k_rope=k_rope,
q_nope=q_nope,
k_nope=k_nope,
cos_sin_cache=cos_sin_cache,
is_neox=is_neox,
quant_scale_q=quant_scale_q,
quant_scale_kv=quant_scale_kv,
solution=solution,
)
q_rope_ref = rope_ref(q_rope)
k_rope_ref = rope_ref(k_rope)
q_ref = torch.cat(
(q_nope.float() * quant_scale_q, q_rope_ref.float() * quant_scale_q),
dim=-1,
).to(torch.float8_e4m3fn)
k_ref = torch.cat(
(k_nope.float() * quant_scale_kv, k_rope_ref.float() * quant_scale_kv),
dim=-1,
).to(torch.float8_e4m3fn)
assert query_fp8.shape == q_ref.shape
assert key_fp8.shape == k_ref.shape
assert query_fp8.dtype == torch.float8_e4m3fn
assert key_fp8.dtype == torch.float8_e4m3fn
torch.testing.assert_close(query_fp8.float(), q_ref.float(), rtol=0, atol=0.5)
torch.testing.assert_close(key_fp8.float(), k_ref.float(), rtol=0, atol=0.5)