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

231 lines
7.8 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 torch
from tokenspeed_kernel.ops.kvcache.triton import (
transfer_kv_all_layer,
transfer_kv_all_layer_mla,
transfer_kv_per_layer,
transfer_kv_per_layer_mla,
)
def test_transfer_kv_per_layer(device: str) -> None:
num_slots = 6
num_heads = 8
head_dim = 128
element_dim = num_heads * head_dim
k_cache_dst = torch.zeros(
num_slots, num_heads, head_dim, device=device, dtype=torch.float16
)
v_cache_dst = torch.zeros_like(k_cache_dst)
k_cache_src = torch.arange(
num_slots * num_heads * head_dim,
device=device,
dtype=torch.float16,
).reshape(num_slots, num_heads, head_dim)
v_cache_src = torch.arange(
10_000,
10_000 + num_slots * num_heads * head_dim,
device=device,
dtype=torch.float16,
).reshape(num_slots, num_heads, head_dim)
indices_dst = torch.tensor([1, 4], device=device, dtype=torch.int32)
indices_src = torch.tensor([0, 5], device=device, dtype=torch.int32)
expected_k = k_cache_dst.clone()
expected_v = v_cache_dst.clone()
expected_k[indices_dst.to(torch.int64)] = k_cache_src[indices_src.to(torch.int64)]
expected_v[indices_dst.to(torch.int64)] = v_cache_src[indices_src.to(torch.int64)]
transfer_kv_per_layer(
src_k=k_cache_src,
dst_k=k_cache_dst,
src_v=v_cache_src,
dst_v=v_cache_dst,
src_indices=indices_src,
dst_indices=indices_dst,
item_size=element_dim * k_cache_src.element_size(),
)
torch.cuda.synchronize()
assert torch.equal(k_cache_dst, expected_k)
assert torch.equal(v_cache_dst, expected_v)
def test_transfer_kv_all_layer(device: str) -> None:
num_layers = 3
num_slots = 6
num_heads = 8
head_dim = 128
k_layers_dst = [
torch.zeros(num_slots, num_heads, head_dim, device=device, dtype=torch.float16)
for _ in range(num_layers)
]
v_layers_dst = [torch.zeros_like(k_layers_dst[0]) for _ in range(num_layers)]
k_layers_src = [
torch.arange(
layer_idx * num_slots * num_heads * head_dim,
(layer_idx + 1) * num_slots * num_heads * head_dim,
device=device,
dtype=torch.float16,
).reshape(num_slots, num_heads, head_dim)
for layer_idx in range(num_layers)
]
v_layers_src = [
torch.arange(
20_000 + layer_idx * num_slots * num_heads * head_dim,
20_000 + (layer_idx + 1) * num_slots * num_heads * head_dim,
device=device,
dtype=torch.float16,
).reshape(num_slots, num_heads, head_dim)
for layer_idx in range(num_layers)
]
k_ptr_dst = torch.tensor(
[layer.data_ptr() for layer in k_layers_dst], device=device, dtype=torch.uint64
)
v_ptr_dst = torch.tensor(
[layer.data_ptr() for layer in v_layers_dst], device=device, dtype=torch.uint64
)
k_ptr_src = torch.tensor(
[layer.data_ptr() for layer in k_layers_src], device=device, dtype=torch.uint64
)
v_ptr_src = torch.tensor(
[layer.data_ptr() for layer in v_layers_src], device=device, dtype=torch.uint64
)
indices_dst = torch.tensor([1, 4], device=device, dtype=torch.int32)
indices_src = torch.tensor([0, 5], device=device, dtype=torch.int32)
slot_stride_bytes = k_layers_dst[0].stride(0) * k_layers_dst[0].element_size()
expected_k = [layer.clone() for layer in k_layers_dst]
expected_v = [layer.clone() for layer in v_layers_dst]
for layer_idx in range(num_layers):
expected_k[layer_idx][indices_dst.to(torch.int64)] = k_layers_src[layer_idx][
indices_src.to(torch.int64)
]
expected_v[layer_idx][indices_dst.to(torch.int64)] = v_layers_src[layer_idx][
indices_src.to(torch.int64)
]
transfer_kv_all_layer(
src_k_layers=k_ptr_src,
dst_k_layers=k_ptr_dst,
src_v_layers=v_ptr_src,
dst_v_layers=v_ptr_dst,
src_indices=indices_src,
dst_indices=indices_dst,
item_size=slot_stride_bytes,
num_layers=num_layers,
)
torch.cuda.synchronize()
for layer_idx in range(num_layers):
assert torch.equal(k_layers_dst[layer_idx], expected_k[layer_idx])
assert torch.equal(v_layers_dst[layer_idx], expected_v[layer_idx])
def test_transfer_kv_per_layer_mla(device: str) -> None:
num_slots = 6
kv_cache_dim = 576
cache_dst = torch.zeros(
num_slots, 1, kv_cache_dim, device=device, dtype=torch.float16
)
cache_src = torch.arange(
num_slots * kv_cache_dim,
device=device,
dtype=torch.float16,
).reshape(num_slots, 1, kv_cache_dim)
indices_dst = torch.tensor([1, 4], device=device, dtype=torch.int32)
indices_src = torch.tensor([0, 5], device=device, dtype=torch.int32)
expected = cache_dst.clone()
expected[indices_dst.to(torch.int64)] = cache_src[indices_src.to(torch.int64)]
transfer_kv_per_layer_mla(
src=cache_src,
dst=cache_dst,
src_indices=indices_src,
dst_indices=indices_dst,
item_size=kv_cache_dim * cache_src.element_size(),
)
torch.cuda.synchronize()
assert torch.equal(cache_dst, expected)
def test_transfer_kv_all_layer_mla(device: str) -> None:
num_layers = 3
num_slots = 6
kv_cache_dim = 576
layers_dst = [
torch.zeros(num_slots, 1, kv_cache_dim, device=device, dtype=torch.float16)
for _ in range(num_layers)
]
layers_src = [
torch.arange(
layer_idx * num_slots * kv_cache_dim,
(layer_idx + 1) * num_slots * kv_cache_dim,
device=device,
dtype=torch.float16,
).reshape(num_slots, 1, kv_cache_dim)
for layer_idx in range(num_layers)
]
ptr_dst = torch.tensor(
[layer.data_ptr() for layer in layers_dst], device=device, dtype=torch.uint64
)
ptr_src = torch.tensor(
[layer.data_ptr() for layer in layers_src], device=device, dtype=torch.uint64
)
indices_dst = torch.tensor([1, 4], device=device, dtype=torch.int32)
indices_src = torch.tensor([0, 5], device=device, dtype=torch.int32)
slot_stride_bytes = layers_dst[0].stride(0) * layers_dst[0].element_size()
expected = [layer.clone() for layer in layers_dst]
for layer_idx in range(num_layers):
expected[layer_idx][indices_dst.to(torch.int64)] = layers_src[layer_idx][
indices_src.to(torch.int64)
]
transfer_kv_all_layer_mla(
src_layers=ptr_src,
dst_layers=ptr_dst,
src_indices=indices_src,
dst_indices=indices_dst,
item_size=slot_stride_bytes,
num_layers=num_layers,
)
torch.cuda.synchronize()
for layer_idx in range(num_layers):
assert torch.equal(layers_dst[layer_idx], expected[layer_idx])