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

205 lines
8.1 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.runtime.cache.utils import (
get_mla_kv_buffer_triton,
set_mla_kv_buffer_triton,
)
pytestmark = pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA required")
# K2.5 / DSv3 MLA dims.
NOPE_DIM = 512
ROPE_DIM = 64
TOTAL_DIM = NOPE_DIM + ROPE_DIM
NUM_PAGES = 50_000
# Spans both dispatch branches (small n -> block-split, large n -> per-loc).
N_LOC_SMALL = [4, 64, 128, 256, 511]
N_LOC_LARGE = [512, 1024, 4096]
N_LOC_ALL = N_LOC_SMALL + N_LOC_LARGE
def _bitwise_equal(a: torch.Tensor, b: torch.Tensor) -> bool:
return torch.equal(a.view(torch.uint8), b.view(torch.uint8))
def _make_inputs(n_loc: int, dtype: torch.dtype, pattern: str, seed: int = 0):
torch.manual_seed(seed)
device = "cuda"
if pattern == "seq":
loc = torch.arange(n_loc, device=device, dtype=torch.int64)
else:
loc = torch.randperm(NUM_PAGES, device=device, dtype=torch.int64)[:n_loc]
if dtype == torch.float8_e4m3fn:
bf = torch.randn(n_loc, 1, NOPE_DIM, device=device, dtype=torch.bfloat16) * 50
k_nope = bf.to(dtype)
bf = torch.randn(n_loc, 1, ROPE_DIM, device=device, dtype=torch.bfloat16) * 50
k_rope = bf.to(dtype)
else:
k_nope = torch.randn(n_loc, 1, NOPE_DIM, device=device, dtype=dtype)
k_rope = torch.randn(n_loc, 1, ROPE_DIM, device=device, dtype=dtype)
return loc, k_nope, k_rope
def _empty_kv(dtype: torch.dtype) -> torch.Tensor:
"""Allocate an unused-cell sentinel-filled kv_buffer so untouched cells
diverge if the kernel writes to them."""
sentinel = torch.full(
(NUM_PAGES, TOTAL_DIM), 7.5, device="cuda", dtype=torch.bfloat16
)
return sentinel.to(dtype) if dtype == torch.float8_e4m3fn else sentinel.to(dtype)
def _torch_set_reference(kv: torch.Tensor, loc, k_nope, k_rope) -> torch.Tensor:
"""Pure-torch scatter-write reference."""
out = kv.clone()
out[loc, :NOPE_DIM] = k_nope[:, 0, :]
out[loc, NOPE_DIM:] = k_rope[:, 0, :]
return out
def _torch_get_reference(kv: torch.Tensor, loc) -> tuple[torch.Tensor, torch.Tensor]:
"""Pure-torch scatter-read reference."""
return (
kv[loc, :NOPE_DIM].unsqueeze(1).contiguous(),
kv[loc, NOPE_DIM:].unsqueeze(1).contiguous(),
)
# ─── set ─────────────────────────────────────────────────────────────
@pytest.mark.parametrize("n_loc", N_LOC_ALL)
@pytest.mark.parametrize("dtype", [torch.bfloat16, torch.float8_e4m3fn])
@pytest.mark.parametrize("pattern", ["seq", "rand"])
def test_set_matches_torch_reference(n_loc, dtype, pattern):
"""set_mla_kv_buffer_triton scatters k_nope/k_rope into kv_buffer at loc
indices, byte-for-byte vs a torch reference. Spans both dispatch branches
via the n_loc parametrization."""
loc, k_nope, k_rope = _make_inputs(n_loc, dtype, pattern)
kv = _empty_kv(dtype)
ref = _torch_set_reference(kv, loc, k_nope, k_rope)
set_mla_kv_buffer_triton(kv, loc, k_nope, k_rope)
torch.cuda.synchronize()
assert _bitwise_equal(kv, ref)
@pytest.mark.parametrize("n_loc", [4, 511, 512, 4096])
@pytest.mark.parametrize("dtype", [torch.bfloat16, torch.float8_e4m3fn])
def test_set_pdl_invariant(n_loc, dtype):
"""PDL is a scheduling hint; output must be bitwise-identical regardless."""
loc, k_nope, k_rope = _make_inputs(n_loc, dtype, "rand")
kv_off = _empty_kv(dtype)
kv_on = _empty_kv(dtype)
set_mla_kv_buffer_triton(kv_off, loc, k_nope, k_rope, enable_pdl=False)
set_mla_kv_buffer_triton(kv_on, loc, k_nope, k_rope, enable_pdl=True)
torch.cuda.synchronize()
assert _bitwise_equal(kv_off, kv_on)
# ─── get ─────────────────────────────────────────────────────────────
@pytest.mark.parametrize("n_loc", N_LOC_ALL)
@pytest.mark.parametrize("dtype", [torch.bfloat16, torch.float8_e4m3fn])
@pytest.mark.parametrize("pattern", ["seq", "rand"])
def test_get_matches_torch_reference(n_loc, dtype, pattern):
"""get_mla_kv_buffer_triton gathers from kv_buffer at loc indices into
cache_k_nope / cache_k_rope outputs, byte-for-byte vs a torch reference."""
# Populate kv_buffer with random data we'll read back.
if dtype == torch.float8_e4m3fn:
bf = torch.randn(NUM_PAGES, TOTAL_DIM, device="cuda", dtype=torch.bfloat16) * 50
kv = bf.to(dtype)
else:
kv = torch.randn(NUM_PAGES, TOTAL_DIM, device="cuda", dtype=dtype)
if pattern == "seq":
loc = torch.arange(n_loc, device="cuda", dtype=torch.int64)
else:
loc = torch.randperm(NUM_PAGES, device="cuda", dtype=torch.int64)[:n_loc]
k_nope = torch.empty((n_loc, 1, NOPE_DIM), dtype=dtype, device="cuda")
k_rope = torch.empty((n_loc, 1, ROPE_DIM), dtype=dtype, device="cuda")
nope_ref, rope_ref = _torch_get_reference(kv, loc)
get_mla_kv_buffer_triton(kv, loc, k_nope, k_rope)
torch.cuda.synchronize()
assert _bitwise_equal(k_nope, nope_ref)
assert _bitwise_equal(k_rope, rope_ref)
@pytest.mark.parametrize("n_loc", [4, 511, 512, 4096])
@pytest.mark.parametrize("dtype", [torch.bfloat16, torch.float8_e4m3fn])
def test_get_pdl_invariant(n_loc, dtype):
if dtype == torch.float8_e4m3fn:
bf = torch.randn(NUM_PAGES, TOTAL_DIM, device="cuda", dtype=torch.bfloat16) * 50
kv = bf.to(dtype)
else:
kv = torch.randn(NUM_PAGES, TOTAL_DIM, device="cuda", dtype=dtype)
loc = torch.randperm(NUM_PAGES, device="cuda", dtype=torch.int64)[:n_loc]
k_nope_off = torch.empty((n_loc, 1, NOPE_DIM), dtype=dtype, device="cuda")
k_rope_off = torch.empty((n_loc, 1, ROPE_DIM), dtype=dtype, device="cuda")
k_nope_on = torch.empty_like(k_nope_off)
k_rope_on = torch.empty_like(k_rope_off)
get_mla_kv_buffer_triton(kv, loc, k_nope_off, k_rope_off, enable_pdl=False)
get_mla_kv_buffer_triton(kv, loc, k_nope_on, k_rope_on, enable_pdl=True)
torch.cuda.synchronize()
assert _bitwise_equal(k_nope_off, k_nope_on)
assert _bitwise_equal(k_rope_off, k_rope_on)
# ─── round trip ─────────────────────────────────────────────────────
@pytest.mark.parametrize("n_loc", [128, 4096])
@pytest.mark.parametrize("dtype", [torch.bfloat16, torch.float8_e4m3fn])
def test_set_then_get_round_trip(n_loc, dtype):
"""set followed by get on the same loc indices recovers the original
k_nope / k_rope inputs bitwise."""
loc, k_nope_in, k_rope_in = _make_inputs(n_loc, dtype, "rand")
kv = _empty_kv(dtype)
set_mla_kv_buffer_triton(kv, loc, k_nope_in, k_rope_in)
k_nope_out = torch.empty_like(k_nope_in)
k_rope_out = torch.empty_like(k_rope_in)
get_mla_kv_buffer_triton(kv, loc, k_nope_out, k_rope_out)
torch.cuda.synchronize()
assert _bitwise_equal(k_nope_out, k_nope_in)
assert _bitwise_equal(k_rope_out, k_rope_in)