# SPDX-License-Identifier: Apache-2.0 # Standard from typing import Any, Union import ctypes import os import time import unittest.mock # Third Party import pytest import torch # First Party from lmcache.v1.multiprocess.native_completion import ( DeviceHostFuncDispatcher, submit_callback_to_stream, ) import lmcache.python_ops_fallback as _py_ops # ========================================== # 0. utils functions. # ========================================== # Device utility functions def device_sync(device: str) -> None: """Synchronize device operations. Args: device: Device string ("cuda", "xpu", or "cpu") This function synchronizes operations for GPU devices: - For CUDA devices, calls torch.cuda.synchronize() - For XPU devices, calls torch.xpu.synchronize() - For CPU, no synchronization is needed (returns immediately) """ if device == "cuda": torch.cuda.synchronize() elif device == "xpu": if hasattr(torch, "xpu") and torch.xpu.is_available(): torch.xpu.synchronize() else: # TODO: add more device here pass # CPU requires no synchronization # ========================================== # 1. Backend Configuration # ========================================== def _build_backend_params() -> list: """Build pytest parameter list for the backend fixture. Returns one entry per available backend configuration: - cuda_c_ops: uses lmcache.c_ops (requires CUDA and the CUDA extension) - cuda_py_ops: uses lmcache.python_ops_fallback with GPU visible - cpy_py_ops: uses lmcache.python_ops_fallback with GPU mocked away - xpu_sycl_ops: uses lmcache.xpu_ops (requires XPU and the SYCL extension) - xpu_py_ops: uses lmcache.python_ops_fallback with XPU visible """ params = [] cuda_available = torch.cuda.is_available() params.append(pytest.param(("cpu_py_ops", _py_ops, "cpu"), id="cpu_py_ops")) if cuda_available: try: # First Party import lmcache.c_ops as cuda_c_ops params.append( pytest.param(("cuda_c_ops", cuda_c_ops, "cuda"), id="cuda_c_ops") ) except ImportError: pass if _py_ops._get_copy_lib() is not None: params.append( pytest.param(("cuda_py_ops", _py_ops, "cuda"), id="cuda_cuda_py_ops") ) else: params.append( pytest.param(("cuda_py_ops", _py_ops, "cpu"), id="cuda_cpu_py_ops") ) if hasattr(torch, "xpu") and torch.xpu.is_available(): try: # First Party import lmcache.c_ops as xpu_sycl_ops params.append( pytest.param(("xpu_sycl_ops", xpu_sycl_ops, "xpu"), id="xpu_sycl_ops") ) except ImportError: pass return params _BACKEND_PARAMS = _build_backend_params() # Module-level storage: (scenario_name, backend_id) -> {result_key: tensor} _results: dict[tuple[str, str], dict[str, Any]] = {} @pytest.fixture(scope="module", params=_BACKEND_PARAMS) def backend(request: pytest.FixtureRequest) -> Any: """Yield (backend_id, ops_module, device) for each backend config. For the cpu_py_ops variant, torch.cuda.is_available is patched to return False so the scenario code behaves as if no GPU is present. """ backend_id, ops, device = request.param if backend_id == "cpu_py_ops": with unittest.mock.patch("torch.cuda.is_available", return_value=False): yield backend_id, ops, device else: yield backend_id, ops, device # ========================================== # 2. Scenario functions # ========================================== def scenario_get_gpu_pci_bus_id(ops: Any, device: str) -> dict[str, torch.Tensor]: """Test get_gpu_pci_bus_id returns a valid string on CUDA backends.""" res = ops.get_gpu_pci_bus_id(0) is_valid = isinstance(res, str) and len(res) > 0 if torch.cuda.is_available() is False: # for non cuda device, not expecting a valid return # but crash should not happen # and we mock a valid return here is_valid = True # 1 = PASS (call succeeded without crash) # 0 = FAIL return { "get_gpu_pci_bus_id": torch.tensor([1 if is_valid else 0], dtype=torch.int32) } def scenario_calculate_cdf(ops: Any, device: str) -> dict[str, torch.Tensor]: """Test calculate_cdf for multiple bin counts.""" num_bins_list = [1, 2, 5, 11, 15, 31, 32, 63] results: dict[str, torch.Tensor] = {} # all bins should be smaller than 64 -> align with C ops assert all(n < 64 for n in num_bins_list), ( f"All num_bins must be < 64, got {[n for n in num_bins_list if n >= 64]}" ) for num_bins in num_bins_list: torch.manual_seed(42) # Create on CPU first for consistent RNG across backends input_tensor = torch.randint(0, num_bins, (1, 1000, 1), dtype=torch.int8).to( device ) raw_output = ops.calculate_cdf(input_tensor, num_bins) # Both CUDA and non-CUDA return int16 with shape # [nlayers, nchannels, num_bins + 1] nlayers, _, nchannels = input_tensor.shape assert raw_output.shape == (nlayers, nchannels, num_bins + 1), ( f"Expected shape ({nlayers}, {nchannels}, {num_bins + 1}), " f"got {raw_output.shape}" ) out_cpu = raw_output.flatten().cpu() out_int32 = out_cpu.to(torch.int32) out_uint16 = torch.where(out_int32 < 0, out_int32 + 65536, out_int32) final_result = out_uint16.float() / 65536.0 results[f"calculate_cdf_bins{num_bins}"] = final_result return results def scenario_rotary_embedding_k_fused(ops: Any, device: str) -> dict[str, torch.Tensor]: """Test rotary_embedding_k_fused for both NeoX and GPT-J rotation styles.""" torch.manual_seed(42) # 1. Setup Dimensions num_tokens = 128 num_kv_heads = 32 head_size = 128 max_position = 2048 rotary_dim = head_size # 2. Generate Inputs on CPU first for consistent RNG across backends old_positions = torch.randint(0, 1000, (num_tokens,), dtype=torch.long).to(device) new_positions = old_positions + 1 cos_sin_cache = torch.randn(max_position, rotary_dim, dtype=torch.float32).to( device ) # Test both is_neox=True (NeoX-style, contiguous halves) and # is_neox=False (GPT-J-style, interleaved) results: dict[str, torch.Tensor] = {} for is_neox in [True, False]: # Reset seed for consistent key tensor across both tests torch.manual_seed(42) key = torch.randn(num_tokens, num_kv_heads, head_size, dtype=torch.float32).to( device ) # 3. Execute (in-place update on key) ops.rotary_embedding_k_fused( old_positions, new_positions, key, head_size, cos_sin_cache, is_neox, ) # 4. Collect with is_neox suffix to distinguish the two test cases neox_suffix = "neox" if is_neox else "gptj" results[f"rotary_embedding_k_fused_{neox_suffix}"] = key.cpu() return results def scenario_lmcache_memcpy_async(ops: Any, device: str) -> dict[str, torch.Tensor]: """Test lmcache_memcpy_async for H2D and D2H memory transfers. Tests both pointer mode (int pointers) and tensor mode (torch.Tensor objects). Uses pointer mode for CPU/CUDA devices and tensor mode for other devices. Exercises multiple boundary conditions to verify correct behaviour for both the CUDA c_ops backend (which chunks at alignment boundaries via cudaMemcpyAsync) and the Python fallback backend (which issues a single synchronous copy): - copy spanning exactly one aligned block - copy entirely within one aligned block (no boundary crossing) - copy crossing a single alignment boundary - copy crossing multiple alignment boundaries - copy starting exactly at an alignment boundary - copy with an unaligned start offset crossing many boundaries Both H2D and D2H directions are verified for each boundary condition. """ torch.manual_seed(42) # Buffer large enough for all boundary test cases (max offset+nbytes = 16+200 = 216) buf_size = 256 alignment = 64 # boundary interval in bytes (must be a power of two) src_host = torch.randint(0, 256, (buf_size,), dtype=torch.uint8) gpu_buffer = torch.zeros(buf_size, dtype=torch.uint8, device=device) dst_host = torch.zeros(buf_size, dtype=torch.uint8) if device in ("cuda", "xpu"): dst_host = dst_host.pin_memory() h2d_dir = ops.TransferDirection.H2D d2h_dir = ops.TransferDirection.D2H # Decide mode based on the running device. # The native CUDA/XPU backend only accepts a tensor of uint64 pointers; # only the Python fallback supports list[Tensor]. use_tensor_mode = device not in ("cpu", "cuda", "xpu") # (host_buffer_offset, nbytes) boundary test cases: # (0, 64): exactly one aligned block from the start # (32, 32): entirely within one block, no boundary crossing # (32, 64): crosses one boundary — [32..64) then [64..96) # (0, 192): three full aligned blocks (boundaries at 64 and 128) # (64, 128): starts at alignment boundary, spans two full blocks # (16, 200): unaligned start, crosses multiple boundaries test_cases = [ (0, 64), (32, 32), (32, 64), (0, 192), (64, 128), (16, 200), ] all_results: list[torch.Tensor] = [] for offset, nbytes in test_cases: gpu_buffer.zero_() dst_host.zero_() expected = src_host[offset : offset + nbytes].clone() device_sync(device) if use_tensor_mode: ops.lmcache_memcpy_async( gpu_buffer[offset : offset + nbytes], src_host[offset : offset + nbytes], nbytes, h2d_dir, 0, alignment, ) device_sync(device) ops.lmcache_memcpy_async( dst_host[offset : offset + nbytes], gpu_buffer[offset : offset + nbytes], nbytes, d2h_dir, 0, alignment, ) else: ops.lmcache_memcpy_async( gpu_buffer.data_ptr() + offset, src_host.data_ptr() + offset, nbytes, h2d_dir, offset, alignment, ) device_sync(device) ops.lmcache_memcpy_async( dst_host.data_ptr() + offset, gpu_buffer.data_ptr() + offset, nbytes, d2h_dir, offset, alignment, ) device_sync(device) result = dst_host[offset : offset + nbytes].cpu() assert torch.equal(result, expected), ( f"Boundary test (offset={offset}, nbytes={nbytes}, alignment={alignment}): " f"data corrupted, max diff = " f"{(result.float() - expected.float()).abs().max().item()}" ) all_results.append(result) return {"lmcache_memcpy_async": torch.cat(all_results)} def scenario_load_and_reshape_flash(ops: Any, device: str) -> dict[str, torch.Tensor]: """Test load_and_reshape_flash extracts KV cache tokens into contiguous buffer.""" torch.manual_seed(42) # 1. Standard Params src_device = device dst_device = "cpu" num_blocks = 100 block_size = 16 num_heads = 8 head_size = 128 num_layers = 32 num_tokens = 256 chunk_size = 256 dtype = torch.bfloat16 # 2. Setup Data (Deterministic Pattern) total_elements = num_blocks * block_size * num_heads * head_size kv_cache_cpu = [] for i in range(num_layers): base_tensor = torch.linspace(i, i + 1, total_elements, dtype=torch.float32) base_tensor = base_tensor.reshape( num_blocks, block_size, num_heads, head_size ).to(dtype) k = base_tensor v = base_tensor + 0.5 kv_cache_cpu.append([k, v]) kv_cache = [ [layer[0].to(src_device), layer[1].to(src_device)] for layer in kv_cache_cpu ] # Slot mapping: deterministic strided selection step = (num_blocks * block_size) // num_tokens slot_indices = list(range(0, num_blocks * block_size, step))[:num_tokens] slot_mapping = torch.tensor(slot_indices, device=src_device, dtype=torch.int64) slot_mapping_chunked = torch.split(slot_mapping, chunk_size) # 3. Extract (to CPU pinned) extracted_chunks = [] for chunk_id, slot_mapping_temp in enumerate(slot_mapping_chunked): mem_obj_shape = (2, num_layers, len(slot_mapping_temp), num_heads * head_size) mem_obj_tensor = torch.zeros(mem_obj_shape, dtype=dtype, device=dst_device) if device in ("cuda", "xpu"): mem_obj_tensor = mem_obj_tensor.pin_memory() for layer_id in range(num_layers): ops.load_and_reshape_flash( mem_obj_tensor, kv_cache[layer_id][0], kv_cache[layer_id][1], slot_mapping_temp, layer_id, ) extracted_chunks.append(mem_obj_tensor) device_sync(device) # 4. Verify: compare extracted data against original kv_cache # mem_obj_tensor layout: # [2, num_layers, num_tokens_in_chunk, num_heads * head_size] # dim 0: K=0, V=1 # Original kv_cache layout: [num_blocks, block_size, num_heads, head_size] # slot_mapping tells us which (block, offset) each token comes from for chunk_id, slot_mapping_temp in enumerate(slot_mapping_chunked): slots = slot_mapping_temp.cpu() extracted = extracted_chunks[chunk_id].cpu() for layer_id in range(num_layers): orig_k = kv_cache_cpu[layer_id][ 0 ] # [num_blocks, block_size, num_heads, head_size] orig_v = kv_cache_cpu[layer_id][1] for tok_idx, slot in enumerate(slots): block_idx = slot.item() // block_size offset = slot.item() % block_size # Expected: flattened [num_heads * head_size] expected_k = orig_k[block_idx, offset].reshape(-1) expected_v = orig_v[block_idx, offset].reshape(-1) # Extracted got_k = extracted[0, layer_id, tok_idx] got_v = extracted[1, layer_id, tok_idx] k_diff = (got_k.float() - expected_k.float()).abs().max().item() assert torch.equal(got_k, expected_k), ( f"K mismatch layer={layer_id}, slot={slot.item()}, " f"max diff={k_diff}" ) v_diff = (got_v.float() - expected_v.float()).abs().max().item() assert torch.equal(got_v, expected_v), ( f"V mismatch layer={layer_id}, slot={slot.item()}, " f"max diff={v_diff}" ) # 5. Return ALL extracted chunks concatenated for cross-backend comparison return { "load_and_reshape_flash": torch.cat([c.cpu() for c in extracted_chunks], dim=2) } def scenario_reshape_and_cache_back_flash( ops: Any, device: str ) -> dict[str, torch.Tensor]: """Test reshape_and_cache_back_flash writes tokens back into paged KV cache.""" torch.manual_seed(42) # 1. Environment Setup src_device = "cpu" dst_device = device num_blocks = 100 block_size = 16 num_heads = 8 head_size = 128 num_layers = 32 num_tokens = 256 chunk_size = 256 dtype = torch.bfloat16 # 2. Prepare Source Data (CPU Buffer) # Shape: [2, num_layers, num_tokens, num_heads * head_size] mem_obj_shape = (2, num_layers, num_tokens, num_heads * head_size) src_buffer = torch.zeros(mem_obj_shape, dtype=dtype, device=src_device) # Data Pattern: Odd numbers (1.0, 3.0, 5.0, ...) for i in range(num_tokens): val = 1.0 + (i * 2.0) src_buffer[0, :, i, :] = val # Key src_buffer[1, :, i, :] = val + 0.5 # Value if device in ("cuda", "xpu"): src_buffer = src_buffer.pin_memory() # 3. Prepare Destination (Empty Cache) kv_cache = [ [ torch.zeros( num_blocks, block_size, num_heads, head_size, device=dst_device, dtype=dtype, ), torch.zeros( num_blocks, block_size, num_heads, head_size, device=dst_device, dtype=dtype, ), ] for _ in range(num_layers) ] # 4. Slot Mapping (Continuous: Token 0 → Slot 0, Token 1 → Slot 1, ...) slot_indices = list(range(num_tokens)) slot_mapping = torch.tensor(slot_indices, device=dst_device, dtype=torch.int64) slot_mapping_chunked = torch.split(slot_mapping, chunk_size) # 5. Execute Operator (Load Back) current_token_offset = 0 for chunk_id, slot_chunk in enumerate(slot_mapping_chunked): chunk_len = len(slot_chunk) buffer_chunk = src_buffer[ :, :, current_token_offset : current_token_offset + chunk_len, : ] if not buffer_chunk.is_contiguous(): buffer_chunk = buffer_chunk.contiguous() for layer_id in range(num_layers): ops.reshape_and_cache_back_flash( buffer_chunk, kv_cache[layer_id][0], kv_cache[layer_id][1], slot_chunk, layer_id, ) current_token_offset += chunk_len device_sync(device) # 6. Verify: check written values against source pattern for layer_id in range(num_layers): k_cache = kv_cache[layer_id][ 0 ].cpu() # [num_blocks, block_size, num_heads, head_size] v_cache = kv_cache[layer_id][1].cpu() for tok_idx, slot in enumerate(slot_indices): block_idx = slot // block_size offset = slot % block_size expected_k_val = 1.0 + (tok_idx * 2.0) expected_v_val = expected_k_val + 0.5 got_k = k_cache[block_idx, offset] got_v = v_cache[block_idx, offset] expected_k = torch.full_like(got_k, expected_k_val) expected_v = torch.full_like(got_v, expected_v_val) assert torch.allclose(got_k.float(), expected_k.float(), atol=0.1), ( f"K mismatch at layer={layer_id}, slot={slot}, " f"expected={expected_k_val}, got={got_k[0, 0].item()}" ) assert torch.allclose(got_v.float(), expected_v.float(), atol=0.1), ( f"V mismatch at layer={layer_id}, slot={slot}, " f"expected={expected_v_val}, got={got_v[0, 0].item()}" ) # 7. Return first block of layer 0 key cache for cross-backend comparison return {"reshape_and_cache_back_flash": kv_cache[0][0][0].cpu()} def scenario_encode_fast_new(ops: Any, device: str) -> dict[str, torch.Tensor]: """Test encode_fast_new produces valid, non-empty encoded output.""" torch.manual_seed(42) # 1. Hyperparameters nlayers = 2 nchannels = 4 ntokens = 128 alphabet_size = 16 max_buf_len = ntokens * 2 # 2. Construct Data on target device # A. CDF: uniform distribution, strictly increasing step = 100 // alphabet_size base_cdf = torch.arange(0, 100, step, dtype=torch.int32) base_cdf = base_cdf[:alphabet_size] cdf_cpu = ( base_cdf.unsqueeze(0).unsqueeze(0).expand(nlayers, nchannels, -1).contiguous() ) cdf = cdf_cpu.to(dtype=torch.int16, device=device) # B. Input symbols: cycling 0..14 total_syms = nlayers * ntokens * nchannels input_cpu = torch.arange(total_syms, dtype=torch.float32) input_cpu = (input_cpu % (alphabet_size - 1)).to(torch.int8) input_cpu = input_cpu.reshape(nlayers, ntokens, nchannels) input_sym = input_cpu.to(device=device) # 3. Prepare Outputs output_buffer = torch.zeros( (nlayers, nchannels, max_buf_len), dtype=torch.uint8, device=device, ) output_lengths = torch.zeros( (nlayers, nchannels), dtype=torch.int32, device=device, ) # 4. Execute ops.encode_fast_new( cdf, input_sym, output_buffer, output_lengths, ) device_sync(device) # 5. Verify lengths_cpu = output_lengths.cpu() assert (lengths_cpu > 0).all(), "Encoding produced zero-length output!" assert (lengths_cpu <= max_buf_len).all(), "Buffer overflow detected!" # 6. Return: first 200 bytes of layer 0, channel 0 valid_len = int(lengths_cpu[0, 0].item()) res = output_buffer[0, 0, : min(valid_len, 200)].cpu() return {"encode_fast_new": res} def scenario_decode_fast_new(ops: Any, device: str) -> dict[str, torch.Tensor]: """Test decode_fast_new correctly decodes a round-tripped encoded stream.""" torch.manual_seed(42) # 1. Config nlayers = 2 nchannels = 4 ntokens = 128 alphabet_size = 16 max_buf_len = ntokens * 2 # 2. Data Generation on CPU first for consistent RNG across backends cdf = torch.randint( 1, 100, (nlayers, nchannels, alphabet_size), dtype=torch.int32, ) cdf = torch.cumsum(cdf, dim=-1).to(torch.int16).to(device) input_sym = torch.randint( 0, alphabet_size - 2, (nlayers, ntokens, nchannels), dtype=torch.int8, ).to(device) # 3. Encode first (need encoded data to test decode) encoded_buffer = torch.zeros( (nlayers, nchannels, max_buf_len), dtype=torch.uint8, device=device, ) encoded_lengths = torch.zeros( (nlayers, nchannels), dtype=torch.int32, device=device, ) ops.encode_fast_new( cdf, input_sym, encoded_buffer, encoded_lengths, ) device_sync(device) # 4. Decode decoded_sym = torch.zeros_like(input_sym, dtype=torch.uint8) ops.decode_fast_new( cdf, encoded_buffer, encoded_lengths, decoded_sym, ) device_sync(device) # 5. Verify: decoded must match original input_uint8 = input_sym.to(torch.uint8) mismatch = (input_uint8 != decoded_sym).sum().item() if mismatch > 0: mask = input_uint8 != decoded_sym ly, t, c = mask.nonzero()[0].tolist() pytest.fail( f"Decode mismatch: {mismatch} errors. " f"First diff at L{ly}T{t}C{c}: " f"orig={input_uint8[ly, t, c].item()} " f"decoded={decoded_sym[ly, t, c].item()}" ) # 6. Return decoded slice for cross-backend comparison return {"decode_fast_new": decoded_sym[0, :20, 0].cpu()} def scenario_decode_fast_prefsum(ops: Any, device: str) -> dict[str, torch.Tensor]: """Test decode_fast_prefsum correctly decodes with prefix-sum offsets.""" torch.manual_seed(42) # 1. Configuration nlayers = 2 nchannels = 4 ntokens = 128 alphabet_size = 16 max_buf_len = ntokens * 2 # 2. Data Generation (Normalized CDF) on CPU first for consistent RNG cdf = torch.randint( 1, 100, (nlayers, nchannels, alphabet_size), dtype=torch.int32, ) cdf = torch.cumsum(cdf, dim=-1).float() cdf = (cdf / cdf[..., -1:] * 65536).to(torch.int32) cdf[..., -1] = 65536 cdf = cdf.to(torch.int16).to(device).contiguous() input_sym = torch.randint( 0, alphabet_size - 2, (nlayers, ntokens, nchannels), dtype=torch.int8, ).to(device) # 3. Encode to get variable lengths tmp_buf = torch.zeros( (nlayers, nchannels, max_buf_len), dtype=torch.uint8, device=device, ) tmp_len = torch.zeros( (nlayers, nchannels), dtype=torch.int32, device=device, ) ops.encode_fast_new(cdf, input_sym, tmp_buf, tmp_len) device_sync(device) # 4. Pack into 1D dense bytestream lens_flat = tmp_len.cpu().flatten().tolist() bufs_flat = tmp_buf.cpu().reshape(-1, max_buf_len).numpy() all_bytes = [] for i, length in enumerate(lens_flat): all_bytes.extend(bufs_flat[i, :length].tolist()) bytestream_1d = torch.tensor( all_bytes, dtype=torch.uint8, device=device, ).contiguous() # 5. Offsets (end-position via cumsum) lengths_prefsum = ( tmp_len.flatten().cumsum(0).reshape(tmp_len.shape).to(torch.int64).to(device) ).contiguous() # 6. Decode decoded_sym = ( torch.zeros_like( input_sym, dtype=torch.uint8, ) .to(device) .contiguous() ) ops.decode_fast_prefsum( cdf, bytestream_1d, lengths_prefsum, decoded_sym, ) device_sync(device) # 7. Verify roundtrip input_ref = input_sym.to(torch.uint8) mismatch = (input_ref != decoded_sym).sum().item() if mismatch > 0: mask = input_ref != decoded_sym ly, t, c = mask.nonzero()[0].tolist() pytest.fail( f"Prefsum mismatch: {mismatch} errors. " f"First diff at L{ly}T{t}C{c}: " f"orig={input_ref[ly, t, c].item()} " f"decoded={decoded_sym[ly, t, c].item()}" ) # 8. Return return { "decode_fast_prefsum": decoded_sym[0, :20, 0].cpu(), } def scenario_single_layer_kv_transfer(ops: Any, device: str) -> dict[str, torch.Tensor]: """Test single_layer_kv_transfer for multiple KV formats and directions.""" torch.manual_seed(42) num_tokens = 64 num_blocks = 256 block_size = 16 num_heads = 12 head_size = 64 hidden_size = num_heads * head_size slot_mapping = torch.arange( 0, num_tokens * 2, 2, device=device, ).to(torch.int64) # (engine_kv_format, is_mla, token_major, direction) # direction: False = LMC→vLLM (H2D), True = vLLM→LMC (D2H) test_cases = [ # flash attn: [2, NB, BS, NH, HS] — two_major (ops.EngineKVFormat.NL_X_TWO_NB_BS_NH_HS, False, True, False), (ops.EngineKVFormat.NL_X_TWO_NB_BS_NH_HS, False, False, False), (ops.EngineKVFormat.NL_X_TWO_NB_BS_NH_HS, False, True, True), # flash infer: [NB, 2, BS, NH, HS] (ops.EngineKVFormat.NL_X_NB_TWO_BS_NH_HS, False, True, False), (ops.EngineKVFormat.NL_X_NB_TWO_BS_NH_HS, False, False, False), (ops.EngineKVFormat.NL_X_NB_TWO_BS_NH_HS, False, True, True), # vLLM MLA: [NB, BS, HS] (ops.EngineKVFormat.NL_X_NB_BS_HS, True, True, False), (ops.EngineKVFormat.NL_X_NB_BS_HS, True, True, True), ] for engine_kv_format, is_mla, token_major, direction in test_cases: dir_tag = "v2l" if direction else "l2v" is_two_major = engine_kv_format == ops.EngineKVFormat.NL_X_TWO_NB_BS_NH_HS case_desc = ( f"fmt={engine_kv_format}, MLA={is_mla}, TM={token_major}, Dir={dir_tag}" ) # ── 1. Setup Shapes ── lmc_shape: tuple[int, ...] = () vllm_shape: tuple[int, ...] = () if is_mla: lmc_shape = (num_tokens, hidden_size) vllm_shape = (num_blocks, block_size, hidden_size) else: lmc_shape = ( (num_tokens, 2, hidden_size) if token_major else (2, num_tokens, hidden_size) ) if is_two_major: # flash attn: [2, num_blocks, block_size, num_heads, head_size] vllm_shape = (2, num_blocks, block_size, num_heads, head_size) else: # flash infer: [num_blocks, 2, block_size, num_heads, head_size] vllm_shape = (num_blocks, 2, block_size, num_heads, head_size) # ── 2. Deterministic Data ── lmc_size = 1 for s in lmc_shape: lmc_size *= s vllm_size = 1 for s in vllm_shape: vllm_size *= s lmc_tensor = ( (torch.arange(lmc_size, device=device) % 1000) .to(torch.float16) .reshape(lmc_shape) ) vllm_tensor = ( (torch.arange(vllm_size, device=device) % 1000) .to(torch.float16) .reshape(vllm_shape) ) # ── 3. Golden Reference ── lmc_ref = lmc_tensor.clone() vllm_ref = vllm_tensor.clone() block_indices = slot_mapping // block_size block_offsets = slot_mapping % block_size if not direction: # LMC → vLLM if is_mla: vllm_ref[block_indices, block_offsets, :] = lmc_ref else: src = lmc_ref if token_major else lmc_ref.permute(1, 0, 2) src = src.view(num_tokens, 2, num_heads, head_size) if is_two_major: # [2, NB, BS, NH, HS] vllm_ref[0, block_indices, block_offsets] = src[:, 0, :, :] vllm_ref[1, block_indices, block_offsets] = src[:, 1, :, :] else: # [NB, 2, BS, NH, HS] vllm_ref[block_indices, 0, block_offsets] = src[:, 0, :, :] vllm_ref[block_indices, 1, block_offsets] = src[:, 1, :, :] else: # vLLM → LMC if is_mla: lmc_ref = vllm_ref[block_indices, block_offsets, :] else: if is_two_major: k = vllm_ref[0, block_indices, block_offsets] v = vllm_ref[1, block_indices, block_offsets] else: k = vllm_ref[block_indices, 0, block_offsets] v = vllm_ref[block_indices, 1, block_offsets] combined = torch.stack( [k, v], dim=1, ).view(num_tokens, 2, hidden_size) lmc_ref = combined if token_major else combined.permute(1, 0, 2) # ── 4. Execute ── xfer_dir = ops.TransferDirection.D2H if direction else ops.TransferDirection.H2D ops.single_layer_kv_transfer( lmc_tensor, vllm_tensor, slot_mapping, xfer_dir, engine_kv_format, token_major, ) device_sync(device) # ── 5. Verify ── if not direction: torch.testing.assert_close( vllm_tensor, vllm_ref, rtol=1e-3, atol=1e-3, msg=f"Mismatch in {case_desc}", ) else: torch.testing.assert_close( lmc_tensor, lmc_ref, rtol=1e-3, atol=1e-3, msg=f"Mismatch in {case_desc}", ) # ── 6. Collect canonical results for cross-backend comparison ── # Use flash attn (two_major) format to match original file names canonical_cases = [ (False, True, False), # l2v, non-MLA (False, True, True), # v2l, non-MLA (True, True, False), # l2v, MLA (True, True, True), # v2l, MLA ] results: dict[str, torch.Tensor] = {} for is_mla, token_major, direction in canonical_cases: dir_tag = "v2l" if direction else "l2v" if is_mla: lmc_shape = (num_tokens, hidden_size) vllm_shape = (num_blocks, block_size, hidden_size) fmt = ops.EngineKVFormat.NL_X_NB_BS_HS else: lmc_shape = (num_tokens, 2, hidden_size) vllm_shape = (2, num_blocks, block_size, num_heads, head_size) fmt = ops.EngineKVFormat.NL_X_TWO_NB_BS_NH_HS lmc_size = 1 for s in lmc_shape: lmc_size *= s vllm_size = 1 for s in vllm_shape: vllm_size *= s lmc_tensor = ( (torch.arange(lmc_size, device=device) % 1000) .to(torch.float16) .reshape(lmc_shape) ) vllm_tensor = ( (torch.arange(vllm_size, device=device) % 1000) .to(torch.float16) .reshape(vllm_shape) ) xfer_dir = ops.TransferDirection.D2H if direction else ops.TransferDirection.H2D ops.single_layer_kv_transfer( lmc_tensor, vllm_tensor, slot_mapping, xfer_dir, fmt, token_major, ) device_sync(device) result = lmc_tensor.cpu() if direction else vllm_tensor.cpu() results[f"single_layer_kv_transfer_{dir_tag}_mla_{is_mla}"] = result return results def scenario_single_layer_kv_transfer_sgl( ops: Any, device: str ) -> dict[str, torch.Tensor]: """Test single_layer_kv_transfer_sgl for SGLang KV format.""" torch.manual_seed(42) num_tokens = 32 num_blocks = 128 block_size = 16 num_heads = 8 head_size = 64 hidden_size = num_heads * head_size slot_mapping = torch.arange( 0, num_tokens * 3, 3, device=device, ).to(torch.int64) # (token_major, direction) # direction: False = LMC→SGL, True = SGL→LMC test_cases = [ (True, False), (False, False), (True, True), (False, True), ] results: dict[str, torch.Tensor] = {} for token_major, direction in test_cases: dir_tag = "s2l" if direction else "l2s" # 1. Setup Shapes lmc_shape = ( (num_tokens, 2, hidden_size) if token_major else (2, num_tokens, hidden_size) ) sgl_shape = ( num_blocks, block_size, num_heads, head_size, ) # 2. Deterministic Data lmc_size = 1 for s in lmc_shape: lmc_size *= s sgl_size = 1 for s in sgl_shape: sgl_size *= s lmc_tensor = ( (torch.arange(lmc_size, device=device) % 500) .to(torch.float16) .reshape(lmc_shape) ) sgl_k_tensor = ( (torch.arange(sgl_size, device=device) % 500 + 500) .to(torch.float16) .reshape(sgl_shape) ) sgl_v_tensor = ( (torch.arange(sgl_size, device=device) % 500 + 1000) .to(torch.float16) .reshape(sgl_shape) ) # 3. Golden Reference lmc_ref = lmc_tensor.clone() sgl_k_ref = sgl_k_tensor.clone() sgl_v_ref = sgl_v_tensor.clone() block_indices = slot_mapping // block_size block_offsets = slot_mapping % block_size if not direction: # LMC → SGL src = lmc_ref if token_major else lmc_ref.permute(1, 0, 2) src_k = src[:, 0, :].view( num_tokens, num_heads, head_size, ) src_v = src[:, 1, :].view( num_tokens, num_heads, head_size, ) sgl_k_ref[block_indices, block_offsets] = src_k sgl_v_ref[block_indices, block_offsets] = src_v else: # SGL → LMC k_data = sgl_k_ref[block_indices, block_offsets].reshape( num_tokens, hidden_size ) v_data = sgl_v_ref[block_indices, block_offsets].reshape( num_tokens, hidden_size ) combined = torch.stack( [k_data, v_data], dim=1, ) # [N, 2, H] lmc_ref = combined if token_major else combined.permute(1, 0, 2) # 4. Execute ops.single_layer_kv_transfer_sgl( lmc_tensor, sgl_k_tensor, sgl_v_tensor, slot_mapping, ops.TransferDirection.D2H if direction else ops.TransferDirection.H2D, token_major, ) device_sync(device) # 5. Verify case_desc = f"TM={token_major}, Dir={dir_tag}" if not direction: torch.testing.assert_close( sgl_k_tensor, sgl_k_ref, rtol=1e-3, atol=1e-3, msg=f"K mismatch in {case_desc}", ) torch.testing.assert_close( sgl_v_tensor, sgl_v_ref, rtol=1e-3, atol=1e-3, msg=f"V mismatch in {case_desc}", ) else: torch.testing.assert_close( lmc_tensor, lmc_ref, rtol=1e-3, atol=1e-3, msg=f"Mismatch in {case_desc}", ) # 6. Collect each case separately result = lmc_tensor.cpu() if direction else sgl_k_tensor.cpu() results[f"single_layer_kv_transfer_sgl_{dir_tag}_tm_{token_major}"] = result return results def scenario_multi_layer_kv_transfer(ops: Any, device: str) -> dict[str, torch.Tensor]: """Test multi_layer_kv_transfer for multiple paged KV formats and directions. Tests both pointer mode (torch.Tensor of int64 pointers) and tensor list mode (list[torch.Tensor]) for key_value_ptrs. """ torch.manual_seed(42) num_layers = 2 num_tokens = 4 head_size = 16 page_buffer_size = 10 block_size = 5 dtype = torch.float32 slot_mapping = torch.tensor( [0, 2, 5, 9], dtype=torch.int64, device=device, ) # ── Format-specific test cases ── # Each: (engine_kv_format, is_mla, block_size_arg) format_cases = [ (ops.EngineKVFormat.NB_NL_TWO_BS_NH_HS, False, 1), # vLLM cross layer (ops.EngineKVFormat.NL_X_TWO_NB_BS_NH_HS, False, 1), # flash attn (ops.EngineKVFormat.NL_X_NB_TWO_BS_NH_HS, False, block_size), # flash infer (ops.EngineKVFormat.NL_X_NB_BS_HS, True, 1), # vLLM MLA (ops.EngineKVFormat.NL_X_NBBS_ONE_HS, True, 1), # SGLang MLA ] # Decide mode based on the running device. # The native CUDA/XPU backend only accepts a tensor of uint64 pointers; # only the Python fallback supports list[Tensor]. use_tensor_list = device not in ("cpu", "cuda", "xpu") for engine_kv_format, is_mla, bs_arg in format_cases: k_or_v_size = 1 if is_mla else 2 for direction in [True, False]: dir_tag = "paged2lmc" if direction else "lmc2paged" fmt_name = str(engine_kv_format).split(".")[-1] # ── 1. LMCache Tensor ── lmc_shape = (k_or_v_size, num_layers, num_tokens, head_size) key_value = torch.zeros(lmc_shape, dtype=dtype, device="cpu") if device in ("cuda", "xpu"): key_value = key_value.pin_memory() if not direction: # LMC → Paged for kv in range(k_or_v_size): for ly in range(num_layers): for t in range(num_tokens): val = ( kv * 5000 + ly * 1000 + t * 10 + torch.arange(head_size, device=device) ).to(dtype) key_value[kv, ly, t] = val # ── 2. Paged Buffers (one per layer) ── page_buffers = [] for ly in range(num_layers): if engine_kv_format == ops.EngineKVFormat.NL_X_NB_TWO_BS_NH_HS: num_blocks = page_buffer_size // bs_arg pb = torch.zeros( (num_blocks, 2, bs_arg, head_size), dtype=dtype, device=device, ) elif is_mla: pb = torch.zeros( (page_buffer_size, head_size), dtype=dtype, device=device, ) else: # Handles NB_NL_TWO_BS_NH_HS and NL_X_TWO_NB_BS_NH_HS pb = torch.zeros( (2, page_buffer_size, head_size), dtype=dtype, device=device, ) if direction: # Paged → LMC for s in range(page_buffer_size): for kv in range(k_or_v_size): val = ( kv * 7000 + ly * 2000 + s * 10 + torch.arange(head_size, device=device) ).to(dtype) if ( engine_kv_format == ops.EngineKVFormat.NL_X_NB_TWO_BS_NH_HS ): blk_idx = s // bs_arg blk_off = s % bs_arg pb[blk_idx, kv, blk_off] = val elif is_mla: pb[s] = val else: # Handles NB_NL_TWO_BS_NH_HS and NL_X_TWO_NB_BS_NH_HS pb[kv, s] = val page_buffers.append(pb) # ── 3. Prepare key_value_ptrs (pointer mode or tensor list mode) ── key_value_ptrs: Union[list[torch.Tensor], torch.Tensor] if use_tensor_list: # Tensor list mode: pass the tensor objects directly key_value_ptrs = page_buffers else: # Pointer mode: create tensor of uint64 pointers key_value_ptrs = torch.tensor( [pb.data_ptr() for pb in page_buffers], dtype=torch.uint64, device=device, ) # ── 4. Execute ── xfer_dir = ( ops.TransferDirection.D2H if direction else ops.TransferDirection.H2D ) ops.multi_layer_kv_transfer( key_value, key_value_ptrs, slot_mapping, torch.device(device), page_buffer_size, xfer_dir, engine_kv_format, bs_arg, ) device_sync(device) # ── 5. Verify (internal, per-format) ── for t_id in range(num_tokens): s_idx = int(slot_mapping[t_id].item()) for ly in range(num_layers): for kv in range(k_or_v_size): lmc_val = key_value[kv, ly, t_id] if engine_kv_format == ops.EngineKVFormat.NL_X_NB_TWO_BS_NH_HS: blk_idx = s_idx // bs_arg blk_off = s_idx % bs_arg paged_val = page_buffers[ly][blk_idx, kv, blk_off] elif is_mla: paged_val = page_buffers[ly][s_idx] else: # Handles NB_NL_TWO_BS_NH_HS and NL_X_TWO_NB_BS_NH_HS paged_val = page_buffers[ly][kv, s_idx] torch.testing.assert_close( lmc_val.to("cpu"), paged_val.to("cpu"), msg=( f"Mismatch: {fmt_name} {dir_tag} " f"(tensor_list={use_tensor_list}), " f"kv={kv}, layer={ly}, token={t_id}" ), ) # ── 6. Collect ONE canonical result for cross-backend comparison ── # Use flash attn format (NL_X_TWO_NB_BS_NH_HS), re-run canonical cases canonical_format = ops.EngineKVFormat.NL_X_TWO_NB_BS_NH_HS results: dict[str, torch.Tensor] = {} for direction in [True, False]: dir_tag = "paged2lmc" if direction else "lmc2paged" lmc_shape = (2, num_layers, num_tokens, head_size) key_value = torch.zeros(lmc_shape, dtype=dtype, device=device) if not direction: for ly in range(num_layers): for t in range(num_tokens): val = ( ly * 1000 + t * 10 + torch.arange(head_size, device=device) ).to(dtype) key_value[0, ly, t] = val key_value[1, ly, t] = val + 500 page_buffers = [] for ly in range(num_layers): pb = torch.zeros( (2, page_buffer_size, head_size), dtype=dtype, device=device, ) if direction: for s in range(page_buffer_size): val = ( ly * 2000 + s * 10 + torch.arange(head_size, device=device) ).to(dtype) pb[0, s] = val pb[1, s] = val + 700 page_buffers.append(pb) if use_tensor_list: key_value_ptrs = page_buffers else: key_value_ptrs = torch.tensor( [pb.data_ptr() for pb in page_buffers], dtype=torch.uint64, device=device, ) xfer_dir = ops.TransferDirection.D2H if direction else ops.TransferDirection.H2D ops.multi_layer_kv_transfer( key_value, key_value_ptrs, slot_mapping, torch.device(device), page_buffer_size, xfer_dir, canonical_format, 1, ) device_sync(device) results[f"multi_layer_kv_transfer_{dir_tag}"] = key_value.cpu() return results def scenario_multi_layer_kv_transfer_unilateral( ops: Any, device: str ) -> dict[str, torch.Tensor]: """Test multi_layer_kv_transfer_unilateral for non-interleaved K/V pointers. Tests both pointer mode (torch.Tensor of int64 pointers) and tensor list mode (list[torch.Tensor]) for key_value_ptrs. """ torch.manual_seed(42) num_layers = 2 num_tokens = 4 head_size = 16 page_buffer_size = 10 dtype = torch.float32 slot_mapping = torch.tensor( [1, 3, 4, 7], dtype=torch.int64, device=device, ) # ── Test cases: (engine_kv_format, is_mla) ── format_cases = [ ( ops.EngineKVFormat.NL_X_TWO_NB_BS_NH_HS, False, ), # SGLang MHA (unilateral path) ( ops.EngineKVFormat.NL_X_NB_BS_HS, True, ), # vLLM MLA (delegates to multi_layer_kv_transfer) ( ops.EngineKVFormat.NL_X_NBBS_ONE_HS, True, ), # SGLang MLA (delegates to multi_layer_kv_transfer) ] # Decide mode based on the running device. # The native CUDA/XPU backend only accepts a tensor of uint64 pointers; # only the Python fallback supports list[Tensor]. use_tensor_list = device not in ("cpu", "cuda", "xpu") for engine_kv_format, is_mla in format_cases: k_or_v_size = 1 if is_mla else 2 for direction in [True, False]: dir_tag = "p2l" if direction else "l2p" # ── 1. LMCache Tensor ── lmc_shape = (k_or_v_size, num_layers, num_tokens, head_size) lmc_tensor = torch.zeros(lmc_shape, dtype=dtype, device="cpu") if device in ("cuda", "xpu"): lmc_tensor = lmc_tensor.pin_memory() if not direction: # LMC → Paged for kv in range(k_or_v_size): for ly in range(num_layers): for t in range(num_tokens): val = ( kv * 5000 + ly * 1000 + t * 10 + torch.arange(head_size, device=device) ).to(dtype) lmc_tensor[kv, ly, t] = val # ── 2. Paged Buffers ── key_value_ptrs: Union[list[torch.Tensor], torch.Tensor] if is_mla: # MLA delegates to multi_layer_kv_transfer # ptrs: [layer0, layer1, ...], each -> [page_buffer_size, head_size] page_buffers = [] for ly in range(num_layers): pb = torch.zeros( (page_buffer_size, head_size), dtype=dtype, device=device, ) if direction: # Paged → LMC for s in range(page_buffer_size): val = ( ly * 2000 + s * 10 + torch.arange(head_size, device=device) ).to(dtype) pb[s] = val page_buffers.append(pb) if use_tensor_list: key_value_ptrs = page_buffers else: key_value_ptrs = torch.tensor( [pb.data_ptr() for pb in page_buffers], dtype=torch.uint64, device=device, ) else: # Non-MLA unilateral: separate K/V buffers # ptrs: [K_l0, K_l1, ..., V_l0, V_l1, ...] # each -> [page_buffer_size, head_size] buffers = {} for kv in range(2): for ly in range(num_layers): pb = torch.zeros( (page_buffer_size, head_size), dtype=dtype, device=device, ) if direction: # Paged → LMC for s in range(page_buffer_size): val = ( kv * 7000 + ly * 2000 + s * 10 + torch.arange(head_size, device=device) ).to(dtype) pb[s] = val buffers[(kv, ly)] = pb if use_tensor_list: # Tensor list mode: [K_l0, K_l1, ..., V_l0, V_l1, ...] tensor_list = [] for ly in range(num_layers): tensor_list.append(buffers[(0, ly)]) for ly in range(num_layers): tensor_list.append(buffers[(1, ly)]) key_value_ptrs = tensor_list else: # Pointer mode ptr_list = [] for ly in range(num_layers): ptr_list.append(buffers[(0, ly)].data_ptr()) for ly in range(num_layers): ptr_list.append(buffers[(1, ly)].data_ptr()) key_value_ptrs = torch.tensor( ptr_list, dtype=torch.uint64, device=device, ).contiguous() # ── 3. Execute ── xfer_dir = ( ops.TransferDirection.D2H if direction else ops.TransferDirection.H2D ) ops.multi_layer_kv_transfer_unilateral( lmc_tensor, key_value_ptrs, slot_mapping, torch.device(device), page_buffer_size, xfer_dir, engine_kv_format, ) device_sync(device) # ── 4. Verify ── for t_id in range(num_tokens): s_idx = int(slot_mapping[t_id].item()) for ly in range(num_layers): for kv in range(k_or_v_size): lmc_val = lmc_tensor[kv, ly, t_id] if is_mla: paged_val = page_buffers[ly][s_idx] else: paged_val = buffers[(kv, ly)][s_idx] torch.testing.assert_close( lmc_val.to("cpu"), paged_val.to("cpu"), msg=( f"Mismatch: {engine_kv_format} {dir_tag} " f"(tensor_list={use_tensor_list}), " f"KV={kv}, layer={ly}, " f"token={t_id}, slot={s_idx}" ), ) # ── 5. Collect canonical result for cross-backend comparison ── # Use non-MLA unilateral (the primary use case of this function) results: dict[str, torch.Tensor] = {} for direction in [True, False]: dir_tag = "p2l" if direction else "l2p" lmc_shape = (2, num_layers, num_tokens, head_size) lmc_tensor = torch.zeros(lmc_shape, dtype=dtype, device=device) if not direction: for kv in range(2): for ly in range(num_layers): for t in range(num_tokens): val = ( kv * 5000 + ly * 1000 + t * 10 + torch.arange(head_size, device=device) ).to(dtype) lmc_tensor[kv, ly, t] = val buffers = {} for kv in range(2): for ly in range(num_layers): pb = torch.zeros( (page_buffer_size, head_size), dtype=dtype, device=device, ) if direction: for s in range(page_buffer_size): val = ( kv * 7000 + ly * 2000 + s * 10 + torch.arange(head_size, device=device) ).to(dtype) pb[s] = val buffers[(kv, ly)] = pb key_value_ptrs: Union[list[torch.Tensor], torch.Tensor] # type: ignore[no-redef] if use_tensor_list: # Tensor list mode: [K_l0, K_l1, ..., V_l0, V_l1, ...] tensor_list = [] for ly in range(num_layers): tensor_list.append(buffers[(0, ly)]) for ly in range(num_layers): tensor_list.append(buffers[(1, ly)]) key_value_ptrs = tensor_list else: ptr_list = [] for ly in range(num_layers): ptr_list.append(buffers[(0, ly)].data_ptr()) for ly in range(num_layers): ptr_list.append(buffers[(1, ly)].data_ptr()) key_value_ptrs = torch.tensor( ptr_list, dtype=torch.uint64, device=device, ).contiguous() xfer_dir = ops.TransferDirection.D2H if direction else ops.TransferDirection.H2D ops.multi_layer_kv_transfer_unilateral( lmc_tensor, key_value_ptrs, slot_mapping, torch.device(device), page_buffer_size, xfer_dir, ops.EngineKVFormat.NL_X_TWO_NB_BS_NH_HS, ) device_sync(device) results[f"multi_layer_kv_transfer_unilateral_{dir_tag}"] = lmc_tensor.cpu() return results def scenario_alloc_free_pinned_ptr(ops: Any, device: str) -> dict[str, torch.Tensor]: """Test alloc_pinned_ptr and free_pinned_ptr round-trip.""" alloc_size = 4096 flags = 0 # cudaHostAllocDefault # 1. Allocate ptr = ops.alloc_pinned_ptr(alloc_size, flags) assert isinstance(ptr, int), f"Expected int, got {type(ptr)}" assert ptr != 0, "alloc_pinned_ptr returned null" # 2. Free ops.free_pinned_ptr(ptr) return {"alloc_free_pinned_ptr": torch.tensor([1], dtype=torch.int32)} def scenario_alloc_free_numa_ptr(ops: Any, device: str) -> dict[str, torch.Tensor]: """Test alloc_numa_ptr and free_numa_ptr round-trip.""" alloc_size = 4096 node = 0 # NUMA node 0 (always exists) # 1. Allocate ptr = ops.alloc_numa_ptr(alloc_size, node) assert isinstance(ptr, int), f"Expected int, got {type(ptr)}" assert ptr != 0, "alloc_numa_ptr returned null" # 2. Free (must pass same size as alloc) ops.free_numa_ptr(ptr, alloc_size) return {"alloc_free_numa_ptr": torch.tensor([1], dtype=torch.int32)} def scenario_alloc_free_pinned_numa_ptr( ops: Any, device: str ) -> dict[str, torch.Tensor]: """Test alloc_pinned_numa_ptr and free_pinned_numa_ptr round-trip.""" alloc_size = 4096 node = 0 # NUMA node 0 # 1. Allocate (NUMA + cudaHostRegister) ptr = ops.alloc_pinned_numa_ptr(alloc_size, node) assert isinstance(ptr, int), f"Expected int, got {type(ptr)}" assert ptr != 0, "alloc_pinned_numa_ptr returned null" # 2. Free (cudaHostUnregister + munmap) ops.free_pinned_numa_ptr(ptr, alloc_size) return {"alloc_free_pinned_numa_ptr": torch.tensor([1], dtype=torch.int32)} def scenario_alloc_free_shm_pinned_ptr( ops: Any, device: str ) -> dict[str, torch.Tensor]: """Test alloc_shm_pinned_ptr and free_shm_pinned_ptr round-trip.""" alloc_size = 4096 shm_name = "/test_lmcache_shm" # 1. Allocate ptr = ops.alloc_shm_pinned_ptr(alloc_size, shm_name) assert isinstance(ptr, int), f"Expected int, got {type(ptr)}" assert ptr != 0, "alloc_shm_pinned_ptr returned null" # 2. Free ops.free_shm_pinned_ptr(ptr, alloc_size, shm_name) return {"alloc_free_shm_pinned_ptr": torch.tensor([1], dtype=torch.int32)} def scenario_transfer_direction_enum(ops: Any, device: str) -> dict[str, torch.Tensor]: """Test TransferDirection enum has distinct H2D and D2H members.""" # 1. Verify enum members exist td = ops.TransferDirection assert hasattr(td, "H2D"), "Missing TransferDirection.H2D" assert hasattr(td, "D2H"), "Missing TransferDirection.D2H" # 2. Verify values are distinct assert td.H2D != td.D2H, "H2D and D2H should be distinct" # 3. Extract int value (compatible with both # pybind11 enum and Python enum) h2d = td.H2D d2h = td.D2H h2d_val = h2d.value if hasattr(h2d, "value") else int(h2d) d2h_val = d2h.value if hasattr(d2h, "value") else int(d2h) return { "transfer_direction_enum": torch.tensor( [h2d_val, d2h_val], dtype=torch.int32, ) } def scenario_record_drain_completion(ops: Any, device: str) -> dict[str, torch.Tensor]: """Test record_completion_on_stream / drain_recorded_completions contracts. Verified backend-agnostic: native c_ops uses cudaLaunchHostFunc on the default stream (ptr=0), which fires synchronously after device_sync; the fallback enqueues immediately. Both paths satisfy every assertion below. """ ops.drain_recorded_completions() # clear residual global state assert ops.drain_recorded_completions() == [] ops.record_completion_on_stream(0, "kind-a", b"payload-a") ops.record_completion_on_stream(0, "kind-b", b"payload-b") device_sync(device) result = ops.drain_recorded_completions() assert result == [("kind-a", b"payload-a"), ("kind-b", b"payload-b")] assert all(isinstance(k, str) and isinstance(p, bytes) for k, p in result) assert ops.drain_recorded_completions() == [] # Multiple records on the default stream (ptr=0) must all enqueue. # Note: ptr=0 is the only value safe on both backends — the fallback # ignores the field, but the native path casts it to cudaStream_t, so # arbitrary values like -1 / 2**32 would be invalid there. for _ in range(3): ops.record_completion_on_stream(0, "k", b"v") device_sync(device) assert len(ops.drain_recorded_completions()) == 3 return {"record_drain_completion": torch.tensor([1], dtype=torch.int32)} def scenario_dispatcher_integration(ops: Any, device: str) -> dict[str, torch.Tensor]: """Test submit_callback_to_stream -> DeviceHostFuncDispatcher -> handler. Works on all backends: submit_callback_to_stream only reads stream.ptr, so _FakeStream(ptr=0) is accepted; the native path routes through the CUDA default stream which fires before the dispatcher's drain loop polls. """ # First Party import lmcache.v1.multiprocess.native_completion as nc original = nc._lmc_ops nc._lmc_ops = ops try: ops.drain_recorded_completions() class _FakeStream: ptr: int = 0 dispatcher = DeviceHostFuncDispatcher(drain_interval_seconds=0.001) received: list[list[bytes]] = [] dispatcher.register("finish_write", received.append, payload_type=list[bytes]) dispatcher.start() try: submit_callback_to_stream(_FakeStream(), "finish_write", [b"k0", b"k1"]) deadline = time.monotonic() + 5.0 while time.monotonic() < deadline and not received: time.sleep(0.01) finally: dispatcher.stop() assert received == [[b"k0", b"k1"]] finally: nc._lmc_ops = original return {"dispatcher_integration": torch.tensor([1], dtype=torch.int32)} def scenario_multi_layer_block_kv_transfer( ops: Any, device: str ) -> dict[str, torch.Tensor]: """Test multi_layer_block_kv_transfer across all GPU KV formats. Exercises the block-based transfer path for: - NHD per-layer format (D2H and H2D round-trip) - FlashInfer NHD per-layer format (interleaved K/V) - HND per-layer format (with permute) - FlashInfer HND per-layer format (interleaved K/V) - MLA per-layer format (no K/V split) - SGLang MLA flat per-layer format - Cross-layer NHD (single tensor [NB, NL, 2, BS, NH, HS]) - Cross-layer HND (single tensor [NB, NL, 2, NH, BS, HS]) - SGLang MHA flat (2*NL tensors [NB*BS, NH, HS]) - SGLang MHA block (2*NL tensors [NB, BS, NH, HS]) - non-sequential block_ids and list[int] block_ids input - skip_prefix_n_blocks > 0 - num_blocks > blocks_per_chunk (multi-chunk) """ results = {} # C++ bindings (cuda_c_ops, xpu_sycl_ops) expect uint64 pointer tensors for # paged_buffer_ptrs_tensor and list[int] for lmcache_objects_ptrs. # The Python fallback also supports both modes on cpu/cuda (pointer inputs # are reconstructed internally via _tensor_from_ptr). use_tensor_list = device not in ("cpu", "cuda") def _alloc_chunks(shape: tuple[int, ...], count: int) -> list[torch.Tensor]: chunks = [torch.zeros(shape, dtype=dtype) for _ in range(count)] if device in ("cuda"): chunks = [chunk.pin_memory() for chunk in chunks] return chunks # --- NHD per-layer --- torch.manual_seed(123) num_layers, num_blocks, block_size = 2, 8, 4 num_heads, head_size = 2, 8 blocks_per_chunk = 4 chunk_tokens = blocks_per_chunk * block_size hidden_dim = num_heads * head_size dtype = torch.float32 paged_layers = [ torch.randn(2, num_blocks, block_size, num_heads, head_size, dtype=dtype).to( device ) for _ in range(num_layers) ] shape_desc = ops.PageBufferShapeDesc() shape_desc.nl = num_layers shape_desc.nb = num_blocks shape_desc.bs = block_size shape_desc.nh = num_heads shape_desc.hs = head_size shape_desc.element_size = dtype.itemsize shape_desc.kv_size = 2 engine_kv_format = ops.EngineKVFormat.NL_X_TWO_NB_BS_NH_HS num_chunks = num_blocks // blocks_per_chunk d2h_chunks = _alloc_chunks((2, num_layers, chunk_tokens, hidden_dim), num_chunks) block_ids = list(range(num_blocks)) ops.multi_layer_block_kv_transfer( paged_layers if use_tensor_list else torch.tensor( [layer.data_ptr() for layer in paged_layers], dtype=torch.uint64, device=device, ), d2h_chunks if use_tensor_list else [c.data_ptr() for c in d2h_chunks], torch.tensor(block_ids, dtype=torch.int64, device=device), torch.device(device), ops.TransferDirection.D2H, shape_desc, chunk_tokens, engine_kv_format, 0, ) paged_h2d = [torch.zeros_like(layer) for layer in paged_layers] ops.multi_layer_block_kv_transfer( paged_h2d if use_tensor_list else torch.tensor( [layer.data_ptr() for layer in paged_h2d], dtype=torch.uint64, device=device ), d2h_chunks if use_tensor_list else [c.data_ptr() for c in d2h_chunks], torch.tensor(block_ids, dtype=torch.int64, device=device), torch.device(device), ops.TransferDirection.H2D, shape_desc, chunk_tokens, engine_kv_format, 0, ) for i in range(num_layers): orig = paged_layers[i].cpu() recon = paged_h2d[i].cpu() results[f"nhd_layer{i}"] = torch.stack([orig, recon]) assert torch.allclose(orig, recon, atol=1e-6), ( f"NHD Layer {i} round-trip mismatch" ) # --- FlashInfer NHD per-layer --- torch.manual_seed(234) paged_layers_fi_nhd = [ torch.randn(num_blocks, 2, block_size, num_heads, head_size, dtype=dtype).to( device ) for _ in range(num_layers) ] engine_kv_format_fi_nhd = ops.EngineKVFormat.NL_X_NB_TWO_BS_NH_HS d2h_chunks_fi_nhd = _alloc_chunks( (2, num_layers, chunk_tokens, hidden_dim), num_chunks ) ops.multi_layer_block_kv_transfer( paged_layers_fi_nhd if use_tensor_list else torch.tensor( [layer.data_ptr() for layer in paged_layers_fi_nhd], dtype=torch.uint64, device=device, ), d2h_chunks_fi_nhd if use_tensor_list else [c.data_ptr() for c in d2h_chunks_fi_nhd], torch.tensor(block_ids, dtype=torch.int64, device=device), torch.device(device), ops.TransferDirection.D2H, shape_desc, chunk_tokens, engine_kv_format_fi_nhd, 0, ) paged_h2d_fi_nhd = [torch.zeros_like(layer) for layer in paged_layers_fi_nhd] ops.multi_layer_block_kv_transfer( paged_h2d_fi_nhd if use_tensor_list else torch.tensor( [layer.data_ptr() for layer in paged_h2d_fi_nhd], dtype=torch.uint64, device=device, ), d2h_chunks_fi_nhd if use_tensor_list else [c.data_ptr() for c in d2h_chunks_fi_nhd], torch.tensor(block_ids, dtype=torch.int64, device=device), torch.device(device), ops.TransferDirection.H2D, shape_desc, chunk_tokens, engine_kv_format_fi_nhd, 0, ) for i in range(num_layers): orig = paged_layers_fi_nhd[i].cpu() recon = paged_h2d_fi_nhd[i].cpu() results[f"flashinfer_nhd_layer{i}"] = torch.stack([orig, recon]) assert torch.allclose(orig, recon, atol=1e-6), ( f"FlashInfer NHD Layer {i} round-trip mismatch" ) # --- HND per-layer --- torch.manual_seed(456) paged_layers_hnd = [ torch.randn(2, num_blocks, num_heads, block_size, head_size, dtype=dtype).to( device ) for _ in range(num_layers) ] engine_kv_format_hnd = ops.EngineKVFormat.NL_X_TWO_NB_NH_BS_HS d2h_chunks_hnd = _alloc_chunks( (2, num_layers, chunk_tokens, hidden_dim), num_chunks ) ops.multi_layer_block_kv_transfer( paged_layers_hnd if use_tensor_list else torch.tensor( [layer.data_ptr() for layer in paged_layers_hnd], dtype=torch.uint64, device=device, ), d2h_chunks_hnd if use_tensor_list else [c.data_ptr() for c in d2h_chunks_hnd], torch.tensor(block_ids, dtype=torch.int64, device=device), torch.device(device), ops.TransferDirection.D2H, shape_desc, chunk_tokens, engine_kv_format_hnd, 0, ) paged_h2d_hnd = [torch.zeros_like(layer) for layer in paged_layers_hnd] ops.multi_layer_block_kv_transfer( paged_h2d_hnd if use_tensor_list else torch.tensor( [layer.data_ptr() for layer in paged_h2d_hnd], dtype=torch.uint64, device=device, ), d2h_chunks_hnd if use_tensor_list else [c.data_ptr() for c in d2h_chunks_hnd], torch.tensor(block_ids, dtype=torch.int64, device=device), torch.device(device), ops.TransferDirection.H2D, shape_desc, chunk_tokens, engine_kv_format_hnd, 0, ) for i in range(num_layers): orig = paged_layers_hnd[i].cpu() recon = paged_h2d_hnd[i].cpu() results[f"hnd_layer{i}"] = torch.stack([orig, recon]) assert torch.allclose(orig, recon, atol=1e-6), ( f"HND Layer {i} round-trip mismatch" ) # --- FlashInfer HND per-layer --- torch.manual_seed(567) paged_layers_fi_hnd = [ torch.randn(num_blocks, 2, num_heads, block_size, head_size, dtype=dtype).to( device ) for _ in range(num_layers) ] engine_kv_format_fi_hnd = ops.EngineKVFormat.NL_X_NB_TWO_NH_BS_HS d2h_chunks_fi_hnd = _alloc_chunks( (2, num_layers, chunk_tokens, hidden_dim), num_chunks ) ops.multi_layer_block_kv_transfer( paged_layers_fi_hnd if use_tensor_list else torch.tensor( [layer.data_ptr() for layer in paged_layers_fi_hnd], dtype=torch.uint64, device=device, ), d2h_chunks_fi_hnd if use_tensor_list else [c.data_ptr() for c in d2h_chunks_fi_hnd], torch.tensor(block_ids, dtype=torch.int64, device=device), torch.device(device), ops.TransferDirection.D2H, shape_desc, chunk_tokens, engine_kv_format_fi_hnd, 0, ) paged_h2d_fi_hnd = [torch.zeros_like(layer) for layer in paged_layers_fi_hnd] ops.multi_layer_block_kv_transfer( paged_h2d_fi_hnd if use_tensor_list else torch.tensor( [layer.data_ptr() for layer in paged_h2d_fi_hnd], dtype=torch.uint64, device=device, ), d2h_chunks_fi_hnd if use_tensor_list else [c.data_ptr() for c in d2h_chunks_fi_hnd], torch.tensor(block_ids, dtype=torch.int64, device=device), torch.device(device), ops.TransferDirection.H2D, shape_desc, chunk_tokens, engine_kv_format_fi_hnd, 0, ) for i in range(num_layers): orig = paged_layers_fi_hnd[i].cpu() recon = paged_h2d_fi_hnd[i].cpu() results[f"flashinfer_hnd_layer{i}"] = torch.stack([orig, recon]) assert torch.allclose(orig, recon, atol=1e-6), ( f"FlashInfer HND Layer {i} round-trip mismatch" ) # --- MLA per-layer --- torch.manual_seed(789) mla_hidden = 16 paged_layers_mla = [ torch.randn(num_blocks, block_size, mla_hidden, dtype=dtype).to(device) for _ in range(num_layers) ] shape_desc_mla = ops.PageBufferShapeDesc() shape_desc_mla.nl = num_layers shape_desc_mla.nb = num_blocks shape_desc_mla.bs = block_size shape_desc_mla.nh = 1 shape_desc_mla.hs = mla_hidden shape_desc_mla.element_size = dtype.itemsize shape_desc_mla.kv_size = 1 engine_kv_format_mla = ops.EngineKVFormat.NL_X_NB_BS_HS d2h_chunks_mla = _alloc_chunks((num_layers, chunk_tokens, mla_hidden), num_chunks) ops.multi_layer_block_kv_transfer( paged_layers_mla if use_tensor_list else torch.tensor( [layer.data_ptr() for layer in paged_layers_mla], dtype=torch.uint64, device=device, ), d2h_chunks_mla if use_tensor_list else [c.data_ptr() for c in d2h_chunks_mla], torch.tensor(block_ids, dtype=torch.int64, device=device), torch.device(device), ops.TransferDirection.D2H, shape_desc_mla, chunk_tokens, engine_kv_format_mla, 0, ) paged_h2d_mla = [torch.zeros_like(layer) for layer in paged_layers_mla] ops.multi_layer_block_kv_transfer( paged_h2d_mla if use_tensor_list else torch.tensor( [layer.data_ptr() for layer in paged_h2d_mla], dtype=torch.uint64, device=device, ), d2h_chunks_mla if use_tensor_list else [c.data_ptr() for c in d2h_chunks_mla], torch.tensor(block_ids, dtype=torch.int64, device=device), torch.device(device), ops.TransferDirection.H2D, shape_desc_mla, chunk_tokens, engine_kv_format_mla, 0, ) for i in range(num_layers): orig = paged_layers_mla[i].cpu() recon = paged_h2d_mla[i].cpu() results[f"mla_layer{i}"] = torch.stack([orig, recon]) assert torch.allclose(orig, recon, atol=1e-6), ( f"MLA Layer {i} round-trip mismatch" ) # --- SGLang MLA flat per-layer --- torch.manual_seed(890) paged_layers_sglang_mla = [ torch.randn(num_blocks * block_size, 1, mla_hidden, dtype=dtype).to(device) for _ in range(num_layers) ] engine_kv_format_sglang_mla = ops.EngineKVFormat.NL_X_NBBS_ONE_HS d2h_chunks_sglang_mla = _alloc_chunks( (num_layers, chunk_tokens, mla_hidden), num_chunks ) ops.multi_layer_block_kv_transfer( paged_layers_sglang_mla if use_tensor_list else torch.tensor( [layer.data_ptr() for layer in paged_layers_sglang_mla], dtype=torch.uint64, device=device, ), d2h_chunks_sglang_mla if use_tensor_list else [c.data_ptr() for c in d2h_chunks_sglang_mla], torch.tensor(block_ids, dtype=torch.int64, device=device), torch.device(device), ops.TransferDirection.D2H, shape_desc_mla, chunk_tokens, engine_kv_format_sglang_mla, 0, ) paged_h2d_sglang_mla = [ torch.zeros_like(layer) for layer in paged_layers_sglang_mla ] ops.multi_layer_block_kv_transfer( paged_h2d_sglang_mla if use_tensor_list else torch.tensor( [layer.data_ptr() for layer in paged_h2d_sglang_mla], dtype=torch.uint64, device=device, ), d2h_chunks_sglang_mla if use_tensor_list else [c.data_ptr() for c in d2h_chunks_sglang_mla], torch.tensor(block_ids, dtype=torch.int64, device=device), torch.device(device), ops.TransferDirection.H2D, shape_desc_mla, chunk_tokens, engine_kv_format_sglang_mla, 0, ) for i in range(num_layers): orig = paged_layers_sglang_mla[i].cpu() recon = paged_h2d_sglang_mla[i].cpu() results[f"sglang_mla_layer{i}"] = torch.stack([orig, recon]) assert torch.allclose(orig, recon, atol=1e-6), ( f"SGLang MLA Layer {i} round-trip mismatch" ) # --- Cross-layer NHD --- torch.manual_seed(101) paged_cross_nhd = torch.randn( num_blocks, num_layers, 2, block_size, num_heads, head_size, dtype=dtype, ).to(device) engine_kv_format_cross_nhd = ops.EngineKVFormat.NB_NL_TWO_BS_NH_HS d2h_chunks_cross_nhd = _alloc_chunks( (2, num_layers, chunk_tokens, hidden_dim), num_chunks ) ops.multi_layer_block_kv_transfer( paged_cross_nhd if use_tensor_list else torch.tensor( [paged_cross_nhd.data_ptr()], dtype=torch.uint64, device=device ), d2h_chunks_cross_nhd if use_tensor_list else [c.data_ptr() for c in d2h_chunks_cross_nhd], torch.tensor(block_ids, dtype=torch.int64, device=device), torch.device(device), ops.TransferDirection.D2H, shape_desc, chunk_tokens, engine_kv_format_cross_nhd, 0, ) paged_h2d_cross_nhd = torch.zeros_like(paged_cross_nhd) ops.multi_layer_block_kv_transfer( paged_h2d_cross_nhd if use_tensor_list else torch.tensor( [paged_h2d_cross_nhd.data_ptr()], dtype=torch.uint64, device=device ), d2h_chunks_cross_nhd if use_tensor_list else [c.data_ptr() for c in d2h_chunks_cross_nhd], torch.tensor(block_ids, dtype=torch.int64, device=device), torch.device(device), ops.TransferDirection.H2D, shape_desc, chunk_tokens, engine_kv_format_cross_nhd, 0, ) orig = paged_cross_nhd.cpu() recon = paged_h2d_cross_nhd.cpu() results["cross_nhd"] = torch.stack([orig, recon]) assert torch.allclose(orig, recon, atol=1e-6), "Cross-layer NHD mismatch" # --- Cross-layer HND --- torch.manual_seed(202) paged_cross_hnd = torch.randn( num_blocks, num_layers, 2, num_heads, block_size, head_size, dtype=dtype, ).to(device) engine_kv_format_cross_hnd = ops.EngineKVFormat.NB_NL_TWO_NH_BS_HS d2h_chunks_cross_hnd = _alloc_chunks( (2, num_layers, chunk_tokens, hidden_dim), num_chunks ) ops.multi_layer_block_kv_transfer( paged_cross_hnd if use_tensor_list else torch.tensor( [paged_cross_hnd.data_ptr()], dtype=torch.uint64, device=device ), d2h_chunks_cross_hnd if use_tensor_list else [c.data_ptr() for c in d2h_chunks_cross_hnd], torch.tensor(block_ids, dtype=torch.int64, device=device), torch.device(device), ops.TransferDirection.D2H, shape_desc, chunk_tokens, engine_kv_format_cross_hnd, 0, ) paged_h2d_cross_hnd = torch.zeros_like(paged_cross_hnd) ops.multi_layer_block_kv_transfer( paged_h2d_cross_hnd if use_tensor_list else torch.tensor( [paged_h2d_cross_hnd.data_ptr()], dtype=torch.uint64, device=device ), d2h_chunks_cross_hnd if use_tensor_list else [c.data_ptr() for c in d2h_chunks_cross_hnd], torch.tensor(block_ids, dtype=torch.int64, device=device), torch.device(device), ops.TransferDirection.H2D, shape_desc, chunk_tokens, engine_kv_format_cross_hnd, 0, ) orig = paged_cross_hnd.cpu() recon = paged_h2d_cross_hnd.cpu() results["cross_hnd"] = torch.stack([orig, recon]) assert torch.allclose(orig, recon, atol=1e-6), "Cross-layer HND mismatch" # --- SGLang MHA flat (NBBS) --- torch.manual_seed(303) pbs = num_blocks * block_size paged_sglang_nbbs = [ [ torch.randn(pbs, num_heads, head_size, dtype=dtype).to(device) for _ in range(num_layers) ], [ torch.randn(pbs, num_heads, head_size, dtype=dtype).to(device) for _ in range(num_layers) ], ] engine_kv_format_sglang_nbbs = ops.EngineKVFormat.TWO_X_NL_X_NBBS_NH_HS d2h_chunks_sglang_nbbs = _alloc_chunks( (2, num_layers, chunk_tokens, hidden_dim), num_chunks ) ops.multi_layer_block_kv_transfer( paged_sglang_nbbs if use_tensor_list else torch.tensor( [t.data_ptr() for t in paged_sglang_nbbs[0]] + [t.data_ptr() for t in paged_sglang_nbbs[1]], dtype=torch.uint64, device=device, ), d2h_chunks_sglang_nbbs if use_tensor_list else [c.data_ptr() for c in d2h_chunks_sglang_nbbs], torch.tensor(block_ids, dtype=torch.int64, device=device), torch.device(device), ops.TransferDirection.D2H, shape_desc, chunk_tokens, engine_kv_format_sglang_nbbs, 0, ) paged_h2d_sglang_nbbs = [ [torch.zeros_like(t) for t in group] for group in paged_sglang_nbbs ] ops.multi_layer_block_kv_transfer( paged_h2d_sglang_nbbs if use_tensor_list else torch.tensor( [t.data_ptr() for t in paged_h2d_sglang_nbbs[0]] + [t.data_ptr() for t in paged_h2d_sglang_nbbs[1]], dtype=torch.uint64, device=device, ), d2h_chunks_sglang_nbbs if use_tensor_list else [c.data_ptr() for c in d2h_chunks_sglang_nbbs], torch.tensor(block_ids, dtype=torch.int64, device=device), torch.device(device), ops.TransferDirection.H2D, shape_desc, chunk_tokens, engine_kv_format_sglang_nbbs, 0, ) for kv in range(2): for i in range(num_layers): orig = paged_sglang_nbbs[kv][i].cpu() recon = paged_h2d_sglang_nbbs[kv][i].cpu() results[f"sglang_nbbs_kv{kv}_l{i}"] = torch.stack([orig, recon]) assert torch.allclose(orig, recon, atol=1e-6), ( f"SGLang NBBS kv={kv} layer={i} mismatch" ) # --- SGLang MHA block (NB_BS) --- torch.manual_seed(404) paged_sglang_nb = [ [ torch.randn(num_blocks, block_size, num_heads, head_size, dtype=dtype).to( device ) for _ in range(num_layers) ], [ torch.randn(num_blocks, block_size, num_heads, head_size, dtype=dtype).to( device ) for _ in range(num_layers) ], ] engine_kv_format_sglang_nb = ops.EngineKVFormat.TWO_X_NL_X_NB_BS_NH_HS d2h_chunks_sglang_nb = _alloc_chunks( (2, num_layers, chunk_tokens, hidden_dim), num_chunks ) ops.multi_layer_block_kv_transfer( paged_sglang_nb if use_tensor_list else torch.tensor( [t.data_ptr() for t in paged_sglang_nb[0]] + [t.data_ptr() for t in paged_sglang_nb[1]], dtype=torch.uint64, device=device, ), d2h_chunks_sglang_nb if use_tensor_list else [c.data_ptr() for c in d2h_chunks_sglang_nb], torch.tensor(block_ids, dtype=torch.int64, device=device), torch.device(device), ops.TransferDirection.D2H, shape_desc, chunk_tokens, engine_kv_format_sglang_nb, 0, ) paged_h2d_sglang_nb = [ [torch.zeros_like(t) for t in group] for group in paged_sglang_nb ] ops.multi_layer_block_kv_transfer( paged_h2d_sglang_nb if use_tensor_list else torch.tensor( [t.data_ptr() for t in paged_h2d_sglang_nb[0]] + [t.data_ptr() for t in paged_h2d_sglang_nb[1]], dtype=torch.uint64, device=device, ), d2h_chunks_sglang_nb if use_tensor_list else [c.data_ptr() for c in d2h_chunks_sglang_nb], torch.tensor(block_ids, dtype=torch.int64, device=device), torch.device(device), ops.TransferDirection.H2D, shape_desc, chunk_tokens, engine_kv_format_sglang_nb, 0, ) for kv in range(2): for i in range(num_layers): orig = paged_sglang_nb[kv][i].cpu() recon = paged_h2d_sglang_nb[kv][i].cpu() results[f"sglang_nb_kv{kv}_l{i}"] = torch.stack([orig, recon]) assert torch.allclose(orig, recon, atol=1e-6), ( f"SGLang NB kv={kv} layer={i} mismatch" ) # --- skip_prefix_n_blocks > 0 --- torch.manual_seed(505) skip_n = 2 paged_layers_skip = [ torch.randn(2, num_blocks, block_size, num_heads, head_size, dtype=dtype).to( device ) for _ in range(num_layers) ] engine_kv_format_nhd = ops.EngineKVFormat.NL_X_TWO_NB_BS_NH_HS # With skip=2, effective blocks start at index 2. # Object 0 occupies flat indices [0, blocks_per_chunk), skipping first 2. # Object 1 occupies flat indices [blocks_per_chunk, 2*blocks_per_chunk). d2h_chunks_skip = _alloc_chunks( (2, num_layers, chunk_tokens, hidden_dim), num_chunks ) ops.multi_layer_block_kv_transfer( paged_layers_skip if use_tensor_list else torch.tensor( [layer.data_ptr() for layer in paged_layers_skip], dtype=torch.uint64, device=device, ), d2h_chunks_skip if use_tensor_list else [c.data_ptr() for c in d2h_chunks_skip], torch.tensor(block_ids, dtype=torch.int64, device=device), torch.device(device), ops.TransferDirection.D2H, shape_desc, chunk_tokens, engine_kv_format_nhd, skip_n, ) paged_h2d_skip = [torch.zeros_like(layer) for layer in paged_layers_skip] ops.multi_layer_block_kv_transfer( paged_h2d_skip if use_tensor_list else torch.tensor( [layer.data_ptr() for layer in paged_h2d_skip], dtype=torch.uint64, device=device, ), d2h_chunks_skip if use_tensor_list else [c.data_ptr() for c in d2h_chunks_skip], torch.tensor(block_ids, dtype=torch.int64, device=device), torch.device(device), ops.TransferDirection.H2D, shape_desc, chunk_tokens, engine_kv_format_nhd, skip_n, ) # Blocks [skip_n:num_blocks] should round-trip at their original positions for layer_idx in range(num_layers): for kv in range(2): orig_blocks = paged_layers_skip[layer_idx][kv, skip_n:num_blocks] recon_blocks = paged_h2d_skip[layer_idx][kv, skip_n:num_blocks] key = f"skip_l{layer_idx}_kv{kv}" results[key] = torch.stack([orig_blocks.cpu(), recon_blocks.cpu()]) assert torch.allclose(orig_blocks, recon_blocks, atol=1e-6), ( f"Skip mismatch at layer={layer_idx} kv={kv}" ) # Skipped blocks [0:skip_n] should remain zero for layer_idx in range(num_layers): for kv in range(2): skipped = paged_h2d_skip[layer_idx][kv, :skip_n] assert torch.all(skipped == 0), ( f"Skipped blocks not zero at layer={layer_idx} kv={kv}" ) assert torch.all(d2h_chunks_skip[0][:, :, : skip_n * block_size] == 0), ( "Skipped D2H chunk region should remain zero" ) # --- Non-sequential block_ids --- torch.manual_seed(515) paged_layers_permuted = [ torch.randn(2, num_blocks, block_size, num_heads, head_size, dtype=dtype).to( device ) for _ in range(num_layers) ] generator = torch.Generator(device="cpu").manual_seed(616) permuted_block_ids = torch.randperm(num_blocks, generator=generator).tolist() d2h_chunks_permuted = _alloc_chunks( (2, num_layers, chunk_tokens, hidden_dim), num_chunks ) ops.multi_layer_block_kv_transfer( paged_layers_permuted if use_tensor_list else torch.tensor( [layer.data_ptr() for layer in paged_layers_permuted], dtype=torch.uint64, device=device, ), d2h_chunks_permuted if use_tensor_list else [c.data_ptr() for c in d2h_chunks_permuted], torch.tensor(permuted_block_ids, dtype=torch.int64, device=device), torch.device(device), ops.TransferDirection.D2H, shape_desc, chunk_tokens, engine_kv_format_nhd, 0, ) paged_h2d_permuted = [torch.zeros_like(layer) for layer in paged_layers_permuted] ops.multi_layer_block_kv_transfer( paged_h2d_permuted if use_tensor_list else torch.tensor( [layer.data_ptr() for layer in paged_h2d_permuted], dtype=torch.uint64, device=device, ), d2h_chunks_permuted if use_tensor_list else [c.data_ptr() for c in d2h_chunks_permuted], torch.tensor(permuted_block_ids, dtype=torch.int64, device=device), torch.device(device), ops.TransferDirection.H2D, shape_desc, chunk_tokens, engine_kv_format_nhd, 0, ) for i in range(num_layers): orig = paged_layers_permuted[i].cpu() recon = paged_h2d_permuted[i].cpu() results[f"permuted_nhd_layer{i}"] = torch.stack([orig, recon]) assert torch.allclose(orig, recon, atol=1e-6), ( f"Permuted NHD Layer {i} round-trip mismatch" ) # --- Multi-chunk (num_blocks > blocks_per_chunk) --- torch.manual_seed(606) num_blocks_mc = 12 paged_layers_mc = [ torch.randn(2, num_blocks_mc, block_size, num_heads, head_size, dtype=dtype).to( device ) for _ in range(num_layers) ] shape_desc_mc = ops.PageBufferShapeDesc() shape_desc_mc.nl = num_layers shape_desc_mc.nb = num_blocks_mc shape_desc_mc.bs = block_size shape_desc_mc.nh = num_heads shape_desc_mc.hs = head_size shape_desc_mc.element_size = dtype.itemsize shape_desc_mc.kv_size = 2 num_chunks_mc = num_blocks_mc // blocks_per_chunk # 3 chunks d2h_chunks_mc = _alloc_chunks( (2, num_layers, chunk_tokens, hidden_dim), num_chunks_mc ) block_ids_mc = list(range(num_blocks_mc)) ops.multi_layer_block_kv_transfer( paged_layers_mc if use_tensor_list else torch.tensor( [layer.data_ptr() for layer in paged_layers_mc], dtype=torch.uint64, device=device, ), d2h_chunks_mc if use_tensor_list else [c.data_ptr() for c in d2h_chunks_mc], torch.tensor(block_ids_mc, dtype=torch.int64, device=device), torch.device(device), ops.TransferDirection.D2H, shape_desc_mc, chunk_tokens, engine_kv_format_nhd, 0, ) paged_h2d_mc = [torch.zeros_like(layer) for layer in paged_layers_mc] ops.multi_layer_block_kv_transfer( paged_h2d_mc if use_tensor_list else torch.tensor( [layer.data_ptr() for layer in paged_h2d_mc], dtype=torch.uint64, device=device, ), d2h_chunks_mc if use_tensor_list else [c.data_ptr() for c in d2h_chunks_mc], torch.tensor(block_ids_mc, dtype=torch.int64, device=device), torch.device(device), ops.TransferDirection.H2D, shape_desc_mc, chunk_tokens, engine_kv_format_nhd, 0, ) for i in range(num_layers): orig = paged_layers_mc[i].cpu() recon = paged_h2d_mc[i].cpu() results[f"multi_chunk_l{i}"] = torch.stack([orig, recon]) assert torch.allclose(orig, recon, atol=1e-6), ( f"Multi-chunk Layer {i} round-trip mismatch" ) return results def scenario_record_drain_event(ops: Any, device: str) -> dict[str, torch.Tensor]: """Test record_event_on_stream / drain_recorded_events contracts. Verified backend-agnostic: native c_ops uses cudaLaunchHostFunc on the default stream (ptr=0), which fires synchronously after device_sync; the fallback enqueues immediately with time.time(). Both paths satisfy every assertion below. """ ops.drain_recorded_events() # clear residual global state assert ops.drain_recorded_events() == [] ops.record_event_on_stream( 0, "mp.store.start", "sess-1", {"device": "cuda:0"}, {"count": 10} ) ops.record_event_on_stream( 0, "mp.store.end", "sess-1", {"device": "cuda:0"}, {"count": 10} ) device_sync(device) result = ops.drain_recorded_events() assert len(result) == 2 # Each element is (event_type_name, session_id, timestamp, str_meta, int_meta) name0, sid0, ts0, str_meta0, int_meta0 = result[0] name1, sid1, ts1, str_meta1, int_meta1 = result[1] assert name0 == "mp.store.start" assert name1 == "mp.store.end" assert sid0 == "sess-1" assert sid1 == "sess-1" assert ts0 > 0.0 assert ts1 >= ts0 # timestamps must be monotonically non-decreasing assert str_meta0 == {"device": "cuda:0"} assert int_meta0 == {"count": 10} assert str_meta1 == {"device": "cuda:0"} assert int_meta1 == {"count": 10} # Drain clears the buffer assert ops.drain_recorded_events() == [] # Multiple records on the default stream (ptr=0) must all enqueue. for i in range(5): ops.record_event_on_stream(0, f"mp.test.{i}", f"sess-{i}", {}, {"idx": i}) device_sync(device) events = ops.drain_recorded_events() assert len(events) == 5 for i, (name, sid, ts, str_meta, int_meta) in enumerate(events): assert name == f"mp.test.{i}" assert sid == f"sess-{i}" assert int_meta == {"idx": i} assert ts > 0.0 return {"record_drain_event": torch.tensor([1], dtype=torch.int32)} # ========================================== # 3. Registry # ========================================== # cover pybind list in csrc/pybind.cpp SCENARIO_REGISTRY = { "transfer_direction_enum": scenario_transfer_direction_enum, "multi_layer_kv_transfer": scenario_multi_layer_kv_transfer, "multi_layer_kv_transfer_unilateral": scenario_multi_layer_kv_transfer_unilateral, "multi_layer_block_kv_transfer": scenario_multi_layer_block_kv_transfer, "single_layer_kv_transfer": scenario_single_layer_kv_transfer, "single_layer_kv_transfer_sgl": scenario_single_layer_kv_transfer_sgl, "load_and_reshape_flash": scenario_load_and_reshape_flash, "reshape_and_cache_back_flash": scenario_reshape_and_cache_back_flash, "lmcache_memcpy_async": scenario_lmcache_memcpy_async, "encode_fast_new": scenario_encode_fast_new, "decode_fast_new": scenario_decode_fast_new, "decode_fast_prefsum": scenario_decode_fast_prefsum, "calculate_cdf": scenario_calculate_cdf, "rotary_embedding_k_fused": scenario_rotary_embedding_k_fused, "alloc_free_pinned_ptr": scenario_alloc_free_pinned_ptr, "alloc_free_pinned_numa_ptr": scenario_alloc_free_pinned_numa_ptr, "alloc_free_numa_ptr": scenario_alloc_free_numa_ptr, "alloc_free_shm_pinned_ptr": scenario_alloc_free_shm_pinned_ptr, "get_gpu_pci_bus_id": scenario_get_gpu_pci_bus_id, "record_drain_completion": scenario_record_drain_completion, "dispatcher_integration": scenario_dispatcher_integration, "record_drain_event": scenario_record_drain_event, } # ========================================== # 4. Test functions pytest sees # ========================================== class TestScenarios: """Test class to ensure test_scenario runs before test_compare. **Execution Order Guarantee:** By grouping tests in a class and using alphabetically ordered method names (test_1_scenario, test_2_compare), pytest will execute all scenario tests before any compare tests, ensuring _results dict is fully populated. """ @pytest.mark.parametrize("name,fn", list(SCENARIO_REGISTRY.items())) def test_1_scenario( self, backend: tuple, name: str, fn: Any, ) -> None: """Run a single scenario with a specific backend configuration. Each (scenario, backend) pair is a separate pytest test case, giving per-scenario per-backend failure reporting without subprocess indirection. Results are stored in the module-level _results dict for later comparison by test_2_compare. """ backend_id, ops, device = backend result = fn(ops, device) if result is not None: _results[(name, backend_id)] = result @pytest.mark.parametrize("name", list(SCENARIO_REGISTRY.keys())) def test_2_compare(self, name: str) -> None: """Compare results across backends for a single scenario. When multiple backends ran (CUDA available), asserts that python fallback equivalents produce numerically identical results to cuda_ops. When only one backend ran (no CUDA), simply verifies results were stored. This test runs after test_1_scenario due to alphabetical ordering of method names within the TestScenarios class. """ available = [p.values[0][0] for p in _BACKEND_PARAMS] # list of backend_ids backend_results = { bid: _results[(name, bid)] for bid in available if (name, bid) in _results } assert len(backend_results) >= 1, ( f"{name}: no results collected — were all test_1_scenario tests skipped?" ) if len(backend_results) < 2: # Only one backend available (no CUDA); just verify results exist return # Collect all result keys across all backends for this scenario all_keys: set[str] = set() for res in backend_results.values(): all_keys.update(res.keys()) base_bid = next(iter(backend_results)) # first available backend as reference for key in sorted(all_keys): base_val = backend_results[base_bid].get(key) if base_val is None: continue for bid, res in backend_results.items(): if bid == base_bid: continue val = res.get(key) if val is None: pytest.fail( f"{name}/{key}: backend '{bid}' has no result " f"(reference backend '{base_bid}' does)" ) continue if isinstance(val, torch.Tensor): v_current = val.detach().cpu().float() v_base = base_val.detach().cpu().float() if not torch.allclose(v_current, v_base, rtol=1e-4, atol=1e-4): max_diff = (v_current - v_base).abs().max().item() pytest.fail( f"{name}/{key}: '{bid}' vs '{base_bid}' mismatch, " f"max diff = {max_diff:.2e}" ) else: if val != base_val: pytest.fail( f"{name}/{key}: '{bid}'={val} != '{base_bid}'={base_val}" ) # ========================================== # Allocation page alignment # ========================================== # # Rust raw-block backend with O_DIRECT requires page-aligned buffer # pointers; CUDA path gets this for free via cudaHostAlloc, and the # non-CUDA fallback in lmcache.non_cuda_equivalents shall mirror the same # guarantee. _PINNED_ALLOC_SIZES = [1, 4095, 4096, 8192, 1024 * 1024] @pytest.mark.parametrize("size", _PINNED_ALLOC_SIZES) def test_alloc_pinned_ptr_is_page_aligned(size: int) -> None: page_size = os.sysconf("SC_PAGESIZE") ptr = _py_ops.alloc_pinned_ptr(size) try: assert ptr != 0 if ptr % page_size != 0: raise AssertionError( f"alloc_pinned_ptr({size}) returned non-page-aligned ptr " f"{hex(ptr)} (page size {page_size})" ) # Touch every byte in the requested region through the raw pointer # to confirm the registered view covers the full requested size # (an undersized view would corrupt adjacent memory or segfault). buf = (ctypes.c_uint8 * size).from_address(ptr) for i in range(size): buf[i] = (i & 0xFF) ^ 0xA5 for i in range(size): assert buf[i] == ((i & 0xFF) ^ 0xA5) finally: _py_ops.free_pinned_ptr(ptr)