177 lines
4.9 KiB
Python
177 lines
4.9 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
"""Microbenchmarks: SYCL (XPU) kernels vs python_ops_fallback.
|
|
|
|
Per the project plan, Phase 0 confirmed all four fallback functions
|
|
accept XPU tensors directly, so we can do an apples-to-apples timing
|
|
comparison on the same device.
|
|
|
|
Run with: ``pytest tests/benchmarks/test_xpu_kernels_microbench.py --benchmark-only``
|
|
"""
|
|
|
|
# Third Party
|
|
import pytest
|
|
import torch
|
|
|
|
# First Party
|
|
import lmcache.python_ops_fallback as F
|
|
|
|
pytestmark = pytest.mark.skipif(
|
|
not (hasattr(torch, "xpu") and torch.xpu.is_available()),
|
|
reason="Intel XPU not available",
|
|
)
|
|
|
|
XPU = "xpu"
|
|
|
|
|
|
def _xpu_sync():
|
|
if hasattr(torch, "xpu") and torch.xpu.is_available():
|
|
torch.xpu.synchronize()
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def xops():
|
|
# First Party
|
|
import lmcache.xpu_ops as XOPS # noqa: F401
|
|
|
|
return XOPS
|
|
|
|
|
|
# ---------------- calculate_cdf ----------------
|
|
|
|
|
|
@pytest.mark.benchmark(group="calculate_cdf")
|
|
@pytest.mark.parametrize("ntokens", [256, 1024, 4096])
|
|
def test_bench_cdf_sycl(benchmark, xops, ntokens):
|
|
nlayers, nchannels, max_bins = 32, 1024, 32
|
|
sym = torch.randint(
|
|
0, max_bins, (nlayers, ntokens, nchannels), dtype=torch.uint8, device=XPU
|
|
)
|
|
_xpu_sync()
|
|
|
|
def run():
|
|
out = xops.calculate_cdf(sym, max_bins)
|
|
_xpu_sync()
|
|
return out
|
|
|
|
benchmark(run)
|
|
|
|
|
|
@pytest.mark.benchmark(group="calculate_cdf")
|
|
@pytest.mark.parametrize("ntokens", [256, 1024, 4096])
|
|
def test_bench_cdf_fallback(benchmark, ntokens):
|
|
nlayers, nchannels, max_bins = 32, 1024, 32
|
|
sym = torch.randint(
|
|
0, max_bins, (nlayers, ntokens, nchannels), dtype=torch.uint8, device=XPU
|
|
)
|
|
_xpu_sync()
|
|
|
|
def run():
|
|
out = F.calculate_cdf(sym, max_bins)
|
|
_xpu_sync()
|
|
return out
|
|
|
|
benchmark(run)
|
|
|
|
|
|
# ---------------- encode_fast_new ----------------
|
|
|
|
|
|
def _make_encode_inputs(xops, nlayers, ntokens, nchannels, max_bins):
|
|
sym = torch.randint(
|
|
0, max_bins, (nlayers, ntokens, nchannels), dtype=torch.uint8, device=XPU
|
|
)
|
|
cdf = xops.calculate_cdf(sym, max_bins)
|
|
buf = torch.zeros((nlayers, nchannels, 256), dtype=torch.uint8, device=XPU)
|
|
lens = torch.zeros((nlayers, nchannels), dtype=torch.int32, device=XPU)
|
|
return sym, cdf, buf, lens
|
|
|
|
|
|
@pytest.mark.benchmark(group="encode_fast_new")
|
|
@pytest.mark.parametrize("ntokens", [64, 256])
|
|
def test_bench_encode_sycl(benchmark, xops, ntokens):
|
|
nlayers, nchannels, max_bins = 32, 1024, 32
|
|
sym, cdf, buf, lens = _make_encode_inputs(
|
|
xops, nlayers, ntokens, nchannels, max_bins
|
|
)
|
|
_xpu_sync()
|
|
|
|
def run():
|
|
xops.encode_fast_new(cdf, sym, buf, lens)
|
|
_xpu_sync()
|
|
|
|
benchmark(run)
|
|
|
|
|
|
@pytest.mark.benchmark(group="encode_fast_new")
|
|
@pytest.mark.parametrize("ntokens", [64, 256])
|
|
def test_bench_encode_fallback(benchmark, xops, ntokens):
|
|
nlayers, nchannels, max_bins = 32, 1024, 32
|
|
sym, cdf, buf, lens = _make_encode_inputs(
|
|
xops, nlayers, ntokens, nchannels, max_bins
|
|
)
|
|
_xpu_sync()
|
|
|
|
def run():
|
|
F.encode_fast_new(cdf, sym, buf, lens)
|
|
_xpu_sync()
|
|
|
|
benchmark(run)
|
|
|
|
|
|
# ---------------- decode_fast_new ----------------
|
|
|
|
|
|
@pytest.mark.benchmark(group="decode_fast_new")
|
|
@pytest.mark.parametrize("ntokens", [256])
|
|
def test_bench_decode_sycl(benchmark, xops, ntokens):
|
|
nlayers, nchannels, max_bins = 32, 1024, 32
|
|
sym, cdf, buf, lens = _make_encode_inputs(
|
|
xops, nlayers, ntokens, nchannels, max_bins
|
|
)
|
|
xops.encode_fast_new(cdf, sym, buf, lens)
|
|
out = torch.zeros_like(sym)
|
|
_xpu_sync()
|
|
|
|
def run():
|
|
xops.decode_fast_new(cdf, buf, lens, out)
|
|
_xpu_sync()
|
|
|
|
benchmark(run)
|
|
|
|
|
|
# NOTE: decode_fast_new fallback crashes on XPU with an internal
|
|
# IndexKernel gather OOB on these shapes (a torch-xpu fallback bug,
|
|
# not a CacheGen bug). Per the project plan we do not modify
|
|
# python_ops_fallback to accommodate XPU; the SYCL kernel is the
|
|
# correct/fast path. Recording the absolute SYCL throughput is
|
|
# sufficient.
|
|
|
|
|
|
# ---------------- rotary_embedding_k_fused ----------------
|
|
|
|
|
|
@pytest.mark.benchmark(group="rope_k_fused")
|
|
@pytest.mark.parametrize("ntokens", [256, 1024, 4096])
|
|
def test_bench_rope_sycl(benchmark, xops, ntokens):
|
|
num_kv_heads, head_size, rot_dim = 8, 128, 128
|
|
embed_dim = num_kv_heads * head_size
|
|
old_positions = torch.arange(ntokens, dtype=torch.int64, device=XPU)
|
|
new_positions = (old_positions + 1) % 2048
|
|
key = torch.randn(ntokens, embed_dim, dtype=torch.float16, device=XPU)
|
|
cos_sin = torch.randn(2048, rot_dim, dtype=torch.float16, device=XPU)
|
|
_xpu_sync()
|
|
|
|
def run():
|
|
xops.rotary_embedding_k_fused(
|
|
old_positions, new_positions, key, head_size, cos_sin, True
|
|
)
|
|
_xpu_sync()
|
|
|
|
benchmark(run)
|
|
|
|
|
|
# NOTE: rotary_embedding_k_fused fallback uses advanced indexing that
|
|
# triggers an internal IndexKernel OOB on XPU at these shapes. Per the
|
|
# project plan we do not patch fallback to fit XPU; SYCL is the
|
|
# performance and correctness path.
|