Files
2026-07-13 12:24:33 +08:00

85 lines
2.9 KiB
Python

# SPDX-License-Identifier: Apache-2.0
"""End-to-end benchmark: CacheGen encode/decode on GPU (CUDA or XPU).
Measures wall-clock to serialize and deserialize a synthetic KV-cache
blob, and records the compression ratio against the uncompressed baseline.
Run with: ``pytest tests/benchmarks/test_cachegen.py --benchmark-only``
"""
# Third Party
import pytest
import torch
# First Party
from lmcache import torch_device_type
from lmcache.storage_backend.serde.cachegen_decoder import CacheGenDeserializer
from lmcache.storage_backend.serde.cachegen_encoder import CacheGenSerializer
from lmcache.v1.config import LMCacheEngineConfig
from lmcache.v1.metadata import LMCacheMetadata
# CacheGen has hand-written CUDA and SYCL kernels only; gate the test on
# those backends explicitly (whitelist), so HPU / CPU-only CI / future
# backends without a CacheGen kernel are skipped automatically.
# torch_device_type is set to "cuda"/"xpu" only after is_available() passes
# in lmcache.__init__, so no extra availability check is needed here.
pytestmark = pytest.mark.skipif(
torch_device_type not in ("cuda", "xpu"),
reason="CacheGen kernels only exist for CUDA and XPU",
)
def _generate_kv(num_tokens, device):
num_layers, num_heads, head_size = 32, 8, 128
shape = [num_tokens, num_heads, head_size]
pairs = []
for _ in range(num_layers):
k = torch.rand(shape, dtype=torch.bfloat16, device=device)
v = torch.rand(shape, dtype=torch.bfloat16, device=device)
pairs.append((k, v))
return torch.stack([torch.stack(p, dim=0) for p in pairs], dim=0)
def _make_serde(chunk_size):
config = LMCacheEngineConfig.from_defaults(chunk_size=chunk_size)
metadata = LMCacheMetadata(
model_name="mistralai/Mistral-7B-Instruct-v0.2",
world_size=1,
local_world_size=1,
worker_id=0,
local_worker_id=0,
kv_dtype=torch.bfloat16,
kv_shape=None,
)
return (
CacheGenSerializer(config, metadata),
CacheGenDeserializer(config, metadata, torch.bfloat16),
)
@pytest.mark.benchmark(group="cachegen_encode")
@pytest.mark.parametrize("chunk_size", [64, 128, 256, 768])
def test_cachegen_encoder_bench(benchmark, chunk_size):
serializer, _ = _make_serde(chunk_size)
kv = _generate_kv(chunk_size, torch_device_type)
def run():
return serializer.to_bytes(kv)
out = benchmark(run)
raw_bytes = kv.element_size() * kv.numel()
print(
f"\n[chunk={chunk_size}] raw={raw_bytes} compressed={len(out)} "
f"ratio={raw_bytes / max(len(out), 1):.2f}x"
)
@pytest.mark.benchmark(group="cachegen_decode")
@pytest.mark.parametrize("chunk_size", [64, 128, 256, 768])
def test_cachegen_decoder_bench(benchmark, chunk_size):
serializer, deserializer = _make_serde(chunk_size)
kv = _generate_kv(chunk_size, torch_device_type)
payload = serializer.to_bytes(kv)
benchmark(deserializer.from_bytes, payload)