# SPDX-License-Identifier: Apache-2.0 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project import torch from vllm.v1.kv_cache_interface import FullAttentionSpec, KVQuantMode from vllm.v1.worker.gpu.attn_utils import _reshape_kv_cache from vllm.v1.worker.utils import AttentionGroup class FakeFlashAttentionBackend: @staticmethod def get_kv_cache_shape( num_blocks: int, block_size: int, num_kv_heads: int, head_size: int, cache_dtype_str: str = "auto", ) -> tuple[int, ...]: return (num_blocks, 2, block_size, num_kv_heads, head_size) @staticmethod def get_kv_cache_stride_order( include_num_layers_dimension: bool = False, ) -> tuple[int, ...]: assert not include_num_layers_dimension return (0, 1, 2, 3, 4) class FakeHNDFlashAttentionBackend(FakeFlashAttentionBackend): @staticmethod def get_kv_cache_stride_order( include_num_layers_dimension: bool = False, ) -> tuple[int, ...]: assert not include_num_layers_dimension return (0, 1, 3, 2, 4) def test_reshape_padded_flash_attention_kv_cache_strides_by_page(): num_blocks = 3 spec = FullAttentionSpec( block_size=16, num_kv_heads=1, head_size=2, dtype=torch.float32, page_size_padded=384, ) assert spec.real_page_size_bytes == 256 raw_tensors = { "layer": torch.zeros(spec.page_size_bytes * num_blocks, dtype=torch.int8) } attn_groups = [ AttentionGroup( backend=FakeFlashAttentionBackend, layer_names=["layer"], kv_cache_spec=spec, kv_cache_group_id=0, ) ] kv_cache = _reshape_kv_cache( attn_groups, raw_tensors, "auto", [spec.block_size], {}, )["layer"] assert kv_cache.shape == (num_blocks, 2, 16, 1, 2) assert kv_cache.stride(0) == spec.page_size_bytes // 4 assert kv_cache.stride(1) == spec.real_page_size_bytes // 2 // 4 assert kv_cache[1, 0].storage_offset() == spec.page_size_bytes // 4 assert ( kv_cache[1, 1].storage_offset() == (spec.page_size_bytes + spec.real_page_size_bytes // 2) // 4 ) def test_reshape_padded_hnd_flash_attention_kv_cache_strides_by_page(): num_blocks = 3 spec = FullAttentionSpec( block_size=16, num_kv_heads=3, head_size=2, dtype=torch.float32, page_size_padded=1024, ) assert spec.real_page_size_bytes == 768 raw_tensors = { "layer": torch.zeros(spec.page_size_bytes * num_blocks, dtype=torch.int8) } attn_groups = [ AttentionGroup( backend=FakeHNDFlashAttentionBackend, layer_names=["layer"], kv_cache_spec=spec, kv_cache_group_id=0, ) ] kv_cache = _reshape_kv_cache( attn_groups, raw_tensors, "auto", [spec.block_size], {}, )["layer"] assert kv_cache.shape == (num_blocks, 2, 16, 3, 2) assert kv_cache.stride(0) == spec.page_size_bytes // 4 assert kv_cache.stride(1) == spec.real_page_size_bytes // 2 // 4 assert kv_cache.stride(2) == 2 assert kv_cache.stride(3) == spec.block_size * spec.head_size assert kv_cache[1, 0].storage_offset() == spec.page_size_bytes // 4 assert ( kv_cache[1, 1].storage_offset() == (spec.page_size_bytes + spec.real_page_size_bytes // 2) // 4 ) assert ( kv_cache[1, 1, 3, 2].storage_offset() == ( spec.page_size_bytes + spec.real_page_size_bytes // 2 + 3 * spec.head_size * 4 + 2 * spec.block_size * spec.head_size * 4 ) // 4 ) class FakeDiffKVBackend: @staticmethod def get_kv_cache_shape( num_blocks: int, block_size: int, num_kv_heads: int, head_size: int, cache_dtype_str: str = "auto", ) -> tuple[int, ...]: return (num_blocks, block_size, num_kv_heads, head_size * 2) @staticmethod def get_kv_cache_stride_order( include_num_layers_dimension: bool = False, ) -> tuple[int, ...]: assert not include_num_layers_dimension return (0, 1, 2, 3) def test_reshape_padded_diff_kv_cache_does_not_infer_kv_dim(): num_blocks = 3 spec = FullAttentionSpec( block_size=16, num_kv_heads=1, head_size=2, dtype=torch.float32, page_size_padded=384, ) raw_tensors = { "layer": torch.zeros(spec.page_size_bytes * num_blocks, dtype=torch.int8) } attn_groups = [ AttentionGroup( backend=FakeDiffKVBackend, layer_names=["layer"], kv_cache_spec=spec, kv_cache_group_id=0, ) ] kv_cache = _reshape_kv_cache( attn_groups, raw_tensors, "auto", [spec.block_size], {}, )["layer"] assert kv_cache.shape == (num_blocks, 16, 1, 4) assert kv_cache.stride(0) == spec.page_size_bytes // 4 assert kv_cache.stride(1) == 4 class FakePerTokenScaleBackend: @staticmethod def get_kv_cache_shape( num_blocks: int, block_size: int, num_kv_heads: int, head_size: int, cache_dtype_str: str = "auto", ) -> tuple[int, ...]: return (num_blocks, 2, block_size, num_kv_heads, head_size + 4) @staticmethod def get_kv_cache_stride_order( include_num_layers_dimension: bool = False, ) -> tuple[int, ...]: assert not include_num_layers_dimension return (0, 1, 2, 3, 4) def test_reshape_padded_quantized_kv_cache_preserves_scale_stride(): num_blocks = 3 spec = FullAttentionSpec( block_size=16, num_kv_heads=1, head_size=4, dtype=torch.int8, kv_quant_mode=KVQuantMode.INT8_PER_TOKEN_HEAD, page_size_padded=384, ) assert spec.real_page_size_bytes == 128 assert spec.page_size_bytes == 384 raw_tensors = { "layer": torch.zeros(spec.page_size_bytes * num_blocks, dtype=torch.int8) } attn_groups = [ AttentionGroup( backend=FakePerTokenScaleBackend, layer_names=["layer"], kv_cache_spec=spec, kv_cache_group_id=0, ) ] kv_cache = _reshape_kv_cache( attn_groups, raw_tensors, "int8_per_token_head", [spec.block_size], {}, )["layer"] assert kv_cache.shape == (num_blocks, 2, 16, 1, 8) assert kv_cache.stride(0) == spec.page_size_bytes assert kv_cache.stride(1) == 16 * 1 * 8 assert kv_cache[1, 1].storage_offset() == spec.page_size_bytes + 16 * 1 * 8