@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 130d6c9514
Fork 0

A high-throughput and memory-efficient inference and serving engine for LLMs

Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git

Synced Nov 10, 02:45 UTC

[ROCm][Perf] Enable shuffle kv cache layout and assembly paged attention kernel for `AiterFlashAttentionBackend` (#29887)

Signed-off-by: ganyi <ygan@amd.com>

Pleaplusone committed Jan 15, 2026 at 23:29 UTC 130d6c9514856cb5a152329f0382d60ff6e8d97e
3 files changed +311 -84
vllm/_aiter_ops.py
+7
@@ -833,6 +833,7 @@ class rocm_aiter_ops:
833 _FMOE_ENABLED = envs.VLLM_ROCM_USE_AITER_MOE
834 _MLA_ENABLED = envs.VLLM_ROCM_USE_AITER_MLA
835 _MHA_ENABLED = envs.VLLM_ROCM_USE_AITER_MHA
836 + _SHUFFLE_KV_CACHE_ENABLED = envs.VLLM_ROCM_SHUFFLE_KV_CACHE_LAYOUT
837 _TRITON_UNIFIED_ATTN_ENABLED = envs.VLLM_ROCM_USE_AITER_UNIFIED_ATTENTION
838 # TODO: Consolidate under _LINEAR_ENABLED
839 _FP8BMM_ENABLED = envs.VLLM_ROCM_USE_AITER_FP8BMM
@@ -859,6 +860,7 @@ class rocm_aiter_ops:
860 cls._FMOE_ENABLED = envs.VLLM_ROCM_USE_AITER_MOE
861 cls._MLA_ENABLED = envs.VLLM_ROCM_USE_AITER_MLA
862 cls._MHA_ENABLED = envs.VLLM_ROCM_USE_AITER_MHA
863 + cls._SHUFFLE_KV_CACHE_ENABLED = envs.VLLM_ROCM_SHUFFLE_KV_CACHE_LAYOUT
864 cls._TRITON_UNIFIED_ATTN_ENABLED = envs.VLLM_ROCM_USE_AITER_UNIFIED_ATTENTION
865 cls._FP8BMM_ENABLED = envs.VLLM_ROCM_USE_AITER_FP8BMM
866 cls._FP4_GEMM_DYNAMIC_QUANT_ASM = envs.VLLM_ROCM_USE_AITER_FP4_ASM_GEMM
@@ -906,6 +908,11 @@ class rocm_aiter_ops:
908 def is_mha_enabled(cls) -> bool:
909 return cls._AITER_ENABLED and cls._MHA_ENABLED
910
911 + @classmethod
912 + @if_aiter_supported
913 + def is_shuffle_kv_cache_enabled(cls) -> bool:
914 + return cls._AITER_ENABLED and cls._SHUFFLE_KV_CACHE_ENABLED
915 +
916 @classmethod
917 @if_aiter_supported
918 def is_triton_unified_attn_enabled(cls) -> bool:
vllm/envs.py
+5
@@ -128,6 +128,7 @@ if TYPE_CHECKING:
128 VLLM_ROCM_FP8_PADDING: bool = True
129 VLLM_ROCM_MOE_PADDING: bool = True
130 VLLM_ROCM_CUSTOM_PAGED_ATTN: bool = True
131 + VLLM_ROCM_SHUFFLE_KV_CACHE_LAYOUT: bool = False
132 VLLM_ENABLE_V1_MULTIPROCESSING: bool = True
133 VLLM_LOG_BATCHSIZE_INTERVAL: float = -1
134 VLLM_DISABLE_COMPILE_CACHE: bool = False
@@ -1018,6 +1019,10 @@ environment_variables: dict[str, Callable[[], Any]] = {
1019 "VLLM_ROCM_CUSTOM_PAGED_ATTN": lambda: (
1020 os.getenv("VLLM_ROCM_CUSTOM_PAGED_ATTN", "True").lower() in ("true", "1")
1021 ),
1022 + # Whether to use the shuffled kv cache layout
1023 + "VLLM_ROCM_SHUFFLE_KV_CACHE_LAYOUT": lambda: (
1024 + os.getenv("VLLM_ROCM_SHUFFLE_KV_CACHE_LAYOUT", "False").lower() in ("true", "1")
1025 + ),
1026 # Custom quick allreduce kernel for MI3* cards
1027 # Choice of quantization level: FP, INT8, INT6, INT4 or NONE
1028 # Recommended for large models to get allreduce
vllm/v1/attention/backends/rocm_aiter_fa.py
+299 -84
@@ -7,6 +7,7 @@ from typing import ClassVar
7
8 import torch
9
10 +from vllm._aiter_ops import rocm_aiter_ops
11 from vllm.attention.layer import Attention
12 from vllm.config import VllmConfig, get_layers_from_vllm_config
13 from vllm.logger import init_logger
@@ -30,7 +31,6 @@ from vllm.v1.kv_cache_interface import AttentionSpec
31
32 _PARTITION_SIZE_ROCM = 256
33 _CP_TOKENS_PER_ITER_ROCM = 32 * 1024
33 -
34 if current_platform.is_rocm():
35 import aiter
36
@@ -52,7 +52,7 @@ if current_platform.is_rocm():
52 cu_seqlens_kv_ptr, # [num_batches + 1]
53 token_to_batch_ptr, # [max_cum_tokens]
54 seq_start_ptr, # [num_batches]
55 - k_scale_ptr,
55 + k_scale_ptr, # [1] / [num_blocks, num_kv_heads, page_size]
56 v_scale_ptr,
57 num_heads,
58 head_size,
@@ -64,13 +64,15 @@ if current_platform.is_rocm():
64 BLOCK_SIZE: tl.constexpr,
65 ):
66 token_id = tl.program_id(0)
67 + head_id = tl.program_id(1)
68 col_offsets = tl.arange(0, BLOCK_SIZE)
68 - if DEQUANT:
69 - k_scale = tl.load(k_scale_ptr)
70 - v_scale = tl.load(v_scale_ptr)
69
72 - key_ptr_offset = key_ptr + token_id * head_size * num_heads
73 - value_ptr_offset = value_ptr + token_id * head_size * num_heads
70 + key_ptr_offset = (
71 + key_ptr + token_id * head_size * num_heads + head_id * head_size
72 + )
73 + value_ptr_offset = (
74 + value_ptr + token_id * head_size * num_heads + head_id * head_size
75 + )
76 batch_idx = tl.load(token_to_batch_ptr + token_id)
77 batch_start = tl.load(seq_start_ptr + batch_idx)
78 token_start = tl.load(cu_seqlens_kv_ptr + batch_idx)
@@ -89,24 +91,54 @@ if current_platform.is_rocm():
91 key_cache_ptr
92 + block_id * num_heads * head_size * PAGE_SIZE
93 + slot_id * num_heads * head_size
94 + + head_id * head_size
95 )
96 value_cache_ptr_offset = (
97 value_cache_ptr
98 + block_id * num_heads * head_size * PAGE_SIZE
99 + slot_id * num_heads * head_size
100 + + head_id * head_size
101 )
98 -
99 - for i in tl.range(0, head_size * num_heads, BLOCK_SIZE):
100 - mask = (col_offsets + i) < head_size * num_heads
101 - k_reg = tl.load(key_cache_ptr_offset + col_offsets + i, mask=mask)
102 - v_reg = tl.load(value_cache_ptr_offset + col_offsets + i, mask=mask)
103 - if DEQUANT:
104 - k_dtype = k_reg.dtype
105 - v_dtype = v_reg.dtype
106 - k_reg = (k_reg.to(tl.float32) * k_scale).to(k_dtype)
107 - v_reg = (v_reg.to(tl.float32) * v_scale).to(v_dtype)
108 - tl.store(key_ptr_offset + col_offsets + i, k_reg, mask=mask)
109 - tl.store(value_ptr_offset + col_offsets + i, v_reg, mask=mask)
102 + k_reg = tl.load(key_cache_ptr_offset + col_offsets)
103 + v_reg = tl.load(value_cache_ptr_offset + col_offsets)
104 + if DEQUANT:
105 + k_scale = tl.load(k_scale_ptr)
106 + v_scale = tl.load(v_scale_ptr)
107 + k_dtype = k_reg.dtype
108 + v_dtype = v_reg.dtype
109 + k_reg = (k_reg.to(tl.float32) * k_scale).to(k_dtype)
110 + v_reg = (v_reg.to(tl.float32) * v_scale).to(v_dtype)
111 + tl.store(key_ptr_offset + col_offsets, k_reg)
112 + tl.store(value_ptr_offset + col_offsets, v_reg)
113 +
114 + elif CACHE_FORMAT == "SHUFFLE":
115 + # for kv cache layout as
116 + # K: [num_blocks, num_head, head_dim // x, page_size, x]
117 + # V: [num_blocks, num_head, page_size // x, head_dim, x]
118 + key_cache_ptr_offset = (
119 + key_cache_ptr
120 + + block_id * num_heads * head_size * PAGE_SIZE
121 + + head_id * head_size * PAGE_SIZE
122 + + slot_id * x
123 + )
124 + value_cache_ptr_offset = (
125 + value_cache_ptr
126 + + block_id * num_heads * head_size * PAGE_SIZE
127 + + head_id * head_size * PAGE_SIZE
128 + + (slot_id // x) * head_size * x
129 + + slot_id % x
130 + )
131 + k_reg_offset = col_offsets // x * PAGE_SIZE * x + col_offsets % x
132 + v_reg_offset = col_offsets * x
133 + k_reg = tl.load(key_cache_ptr_offset + k_reg_offset)
134 + v_reg = tl.load(value_cache_ptr_offset + v_reg_offset)
135 + if DEQUANT:
136 + k_scale = 1.0
137 + v_scale = 1.0
138 + k_reg = k_reg.to(tl.float32) * k_scale
139 + v_reg = v_reg.to(tl.float32) * v_scale
140 + tl.store(key_ptr_offset + col_offsets, k_reg)
141 + tl.store(value_ptr_offset + col_offsets, v_reg)
142
143 def cp_mha_gather_cache(
144 key_cache: torch.Tensor,
@@ -123,17 +155,14 @@ if current_platform.is_rocm():
155 kv_cache_layout: str,
156 total_tokens: int,
157 ):
126 - assert kv_cache_layout in ["v0", "NHD", "HND"], (
127 - "kv_cache_layout only support v0, NHD, HND"
158 + assert kv_cache_layout in ["NHD", "SHUFFLE"], (
159 + "kv_cache_layout only support NHD, SHUFFLE"
160 )
161 head_dim = key.shape[2]
130 - x = 0
162 + x = 16 // key_cache.element_size()
163 # assert dequant is True, "Currently, we only support "\
164 # "gather cache with dequant"
165 # For k cache layout: [num_blocks, num_heads, page_size, head_dim]
134 - assert kv_cache_layout == "NHD", (
135 - "ROCM_AITER_FA_BACKEND Only support NHD kv cache layout for now"
136 - )
166 assert head_dim == key_cache.shape[3], (
167 "We assume your kv cache layout is [num_blocks, "
168 "page_size, num_heads, head_dim], but got otherwise"
@@ -141,7 +170,7 @@ if current_platform.is_rocm():
170 page_size = key_cache.shape[1]
171 num_heads = key_cache.shape[2]
172
144 - grid = lambda meta: (total_tokens,)
173 + grid = lambda meta: (total_tokens, num_heads)
174 cp_mha_gather_cache_kernel[grid](
175 key_cache,
176 value_cache,
@@ -163,6 +192,112 @@ if current_platform.is_rocm():
192 BLOCK_SIZE=head_dim,
193 )
194
195 + @triton.jit
196 + def reshape_and_cache_shuffle_kernel(
197 + key_ptr, # [num_tokens, num_kv_heads, head_size]
198 + value_ptr, # [num_tokens, num_kv_heads, head_size]
199 + key_cache_ptr, # [num_blocks, num_kv_heads, head_size // x, block_size, x]
200 + value_cache_ptr, # [num_blocks, num_kv_heads, block_size // x, head_size, x]
201 + slot_mapping_ptr, # [num_tokens]
202 + k_scale_ptr, # [num_blocks, num_kv_heads, block_size]
203 + v_scale_ptr, # [num_blocks, num_kv_heads, block_size]
204 + x,
205 + k_stride0,
206 + v_stride0,
207 + block_size,
208 + head_size,
209 + num_kv_heads,
210 + BLOCK_SIZE: tl.constexpr,
211 + QUANT: tl.constexpr,
212 + IS_FNUZ: tl.constexpr,
213 + ):
214 + tid = tl.program_id(0)
215 + head_id = tl.program_id(1)
216 + offset = tl.arange(0, BLOCK_SIZE)
217 + src_offset_k = tid * k_stride0 + head_id * head_size
218 + src_offset_v = tid * v_stride0 + head_id * head_size
219 + slot_id = tl.load(slot_mapping_ptr + tid)
220 + if slot_id < 0:
221 + return
222 + block_id = slot_id // block_size
223 + block_offset = slot_id % block_size
224 + dst_offset = (
225 + block_id * num_kv_heads * head_size * block_size
226 + + head_id * head_size * block_size
227 + )
228 + dst_k_shuffle_offset = (
229 + dst_offset + offset // x * block_size * x + block_offset * x + offset % x
230 + )
231 + dst_v_shuffle_offset = (
232 + dst_offset
233 + + block_offset // x * head_size * x
234 + + offset * x
235 + + block_offset % x
236 + )
237 + k_val = tl.load(key_ptr + src_offset_k + offset)
238 + v_val = tl.load(value_ptr + src_offset_v + offset)
239 + if QUANT:
240 + k_scale = 1.0
241 + v_scale = 1.0
242 + k_dtype = key_cache_ptr.type.element_ty
243 + v_dtype = value_cache_ptr.type.element_ty
244 + k_val = (k_val.to(tl.float32) / k_scale).to(k_dtype)
245 + v_val = (v_val.to(tl.float32) / v_scale).to(v_dtype)
246 + tl.store(key_cache_ptr + dst_k_shuffle_offset, k_val)
247 + tl.store(value_cache_ptr + dst_v_shuffle_offset, v_val)
248 +
249 + def reshape_and_cache_shuffle_triton(
250 + key: torch.Tensor,
251 + value: torch.Tensor,
252 + key_cache: torch.Tensor,
253 + value_cache: torch.Tensor,
254 + slot_mapping: torch.Tensor,
255 + kv_cache_dtype: str,
256 + k_scales: torch.Tensor,
257 + v_scales: torch.Tensor,
258 + ):
259 + num_tokens = slot_mapping.shape[0]
260 + _, num_kv_heads, head_size = key.shape
261 + num_blocks, block_size, _, _ = key_cache.shape
262 + x = 16 // key_cache.element_size()
263 + k_cache_template = torch.empty(
264 + [num_blocks, num_kv_heads, head_size // x, block_size, x],
265 + dtype=key_cache.dtype,
266 + device="meta",
267 + )
268 + v_cache_template = torch.empty(
269 + [num_blocks, num_kv_heads, block_size // x, head_size, x],
270 + dtype=value_cache.dtype,
271 + device="meta",
272 + )
273 + new_key_cache = key_cache.view_as(k_cache_template)
274 + new_value_cache = value_cache.view_as(v_cache_template)
275 + QUANT = False
276 + if kv_cache_dtype.startswith("fp8"):
277 + QUANT = True
278 + grid = (
279 + num_tokens,
280 + num_kv_heads,
281 + )
282 + reshape_and_cache_shuffle_kernel[grid](
283 + key,
284 + value,
285 + new_key_cache,
286 + new_value_cache,
287 + slot_mapping,
288 + k_scales,
289 + v_scales,
290 + x,
291 + key.stride(0),
292 + value.stride(0),
293 + block_size,
294 + head_size,
295 + num_kv_heads,
296 + BLOCK_SIZE=head_size,
297 + QUANT=QUANT,
298 + IS_FNUZ=current_platform.fp8_dtype() == torch.float8_e4m3fnuz,
299 + )
300 +
301
302 logger = init_logger(__name__)
303
@@ -253,6 +388,11 @@ class AiterFlashAttentionMetadata:
388 common_prefix_len: int
389 total_tokens: int
390
391 + # Only for fp8 shuffle layout kv cache, we allocate kv_scale for each layer
392 + # since we might integrate per token quant for kv cache in the future.
393 + k_scale: dict[str, torch.Tensor] | None
394 + v_scale: dict[str, torch.Tensor] | None
395 +
396
397 class AiterFlashAttentionMetadataBuilder(
398 AttentionMetadataBuilder[AiterFlashAttentionMetadata]
@@ -303,6 +443,7 @@ class AiterFlashAttentionMetadataBuilder(
443 dtype=self.model_config.dtype,
444 device=device,
445 )
446 + self.scale = torch.tensor([1.0], dtype=torch.float, device=self.device)
447
448 def build_for_cudagraph_capture(
449 self, common_attn_metadata: CommonAttentionMetadata
@@ -325,7 +466,27 @@ class AiterFlashAttentionMetadataBuilder(
466 common_attn_metadata,
467 decode_threshold=self.reorder_batch_threshold,
468 )
328 -
469 + # Allocate scales for fp8 shuffle kv cache with shuffle_kv_cache enabled
470 + if (
471 + rocm_aiter_ops.is_shuffle_kv_cache_enabled()
472 + and self.scale.numel() == 1
473 + and self.vllm_config.cache_config.cache_dtype.startswith("fp8")
474 + ):
475 + layers = get_layers_from_vllm_config(self.vllm_config, Attention)
476 + first_layer_name = [k for k in layers][0]
477 + kv_cache_shape = (
478 + self.vllm_config.compilation_config.static_forward_context[
479 + first_layer_name
480 + ]
481 + .kv_cache[0]
482 + .shape
483 + )
484 + num_blocks = kv_cache_shape[1]
485 + self.scale = torch.ones(
486 + [num_blocks, self.num_heads_kv, self.block_size],
487 + dtype=torch.float32,
488 + device=self.device,
489 + )
490 (
491 num_decodes,
492 num_extends,
@@ -507,6 +668,8 @@ class AiterFlashAttentionMetadataBuilder(
668 use_cascade=use_cascade,
669 common_prefix_len=common_prefix_len,
670 total_tokens=self.total_tokens,
671 + k_scale=self.scale,
672 + v_scale=self.scale,
673 )
674 return attn_metadata
675
@@ -548,7 +711,6 @@ class AiterFlashAttentionBackend(AttentionBackend):
711 ) -> tuple[int, ...]:
712 if block_size % 16 != 0:
713 raise ValueError("Block size must be a multiple of 16.")
551 -
714 return (2, num_blocks, block_size, num_kv_heads, head_size)
715
716
@@ -630,7 +792,7 @@ class AiterFlashAttentionImpl(AttentionImpl):
792 cu_seqlens_kv=swa_cu_seqlens,
793 token_to_batch=swa_token_to_batch,
794 seq_starts=swa_seq_starts,
633 - dequant=False,
795 + dequant=self.kv_cache_dtype.startswith("fp8"),
796 kv_cache_layout="NHD",
797 total_tokens=swa_total_tokens,
798 )
@@ -668,8 +830,8 @@ class AiterFlashAttentionImpl(AttentionImpl):
830 min_seqlen_q: int,
831 block_table: torch.Tensor,
832 slot_mapping: torch.Tensor,
671 - k_scale: float,
672 - v_scale: float,
833 + k_scale: torch.Tensor,
834 + v_scale: torch.Tensor,
835 ):
836 if self.sliding_window[0] != -1:
837 self.extend_for_sliding_window(
@@ -725,8 +887,10 @@ class AiterFlashAttentionImpl(AttentionImpl):
887 cu_seqlens_kv=cu_seqlens_kv[chunk_idx],
888 token_to_batch=token_to_batch[chunk_idx],
889 seq_starts=chunk_starts[chunk_idx],
728 - dequant=False,
729 - kv_cache_layout="NHD",
890 + dequant=self.kv_cache_dtype.startswith("fp8"),
891 + kv_cache_layout="SHUFFLE"
892 + if rocm_aiter_ops.is_shuffle_kv_cache_enabled()
893 + else "NHD",
894 total_tokens=total_token_per_batch[chunk_idx],
895 )
896
@@ -823,6 +987,9 @@ class AiterFlashAttentionImpl(AttentionImpl):
987 # key and value may be None in the case of cross attention. They are
988 # calculated once based on the output from the encoder and then cached
989 # in KV cache.
990 + if self.kv_cache_dtype.startswith("fp8"):
991 + key_cache = key_cache.view(current_platform.fp8_dtype())
992 + value_cache = value_cache.view(current_platform.fp8_dtype())
993 if (
994 self.kv_sharing_target_layer_name is None
995 and key is not None
@@ -835,21 +1002,31 @@ class AiterFlashAttentionImpl(AttentionImpl):
1002 # key[:num_actual_tokens] and value[:num_actual_tokens] because
1003 # the reshape_and_cache_flash op uses the slot_mapping's shape
1004 # to determine the number of actual tokens.
838 -
839 - torch.ops._C_cache_ops.reshape_and_cache_flash(
840 - key,
841 - value,
842 - key_cache,
843 - value_cache,
844 - attn_metadata.slot_mapping,
845 - self.kv_cache_dtype,
846 - layer._k_scale,
847 - layer._v_scale,
848 - )
849 -
850 - if self.kv_cache_dtype.startswith("fp8"):
851 - key_cache = key_cache.view(current_platform.fp8_dtype())
852 - value_cache = value_cache.view(current_platform.fp8_dtype())
1005 + if rocm_aiter_ops.is_shuffle_kv_cache_enabled():
1006 + # We may calculate per token quant scale in
1007 + # reshape_and_cache_shuffle_triton which might differ from
1008 + # vllm's style when shuffle layout is used.
1009 + reshape_and_cache_shuffle_triton(
1010 + key,
1011 + value,
1012 + key_cache,
1013 + value_cache,
1014 + attn_metadata.slot_mapping,
1015 + self.kv_cache_dtype,
1016 + attn_metadata.k_scale,
1017 + attn_metadata.v_scale,
1018 + )
1019 + else:
1020 + torch.ops._C_cache_ops.reshape_and_cache_flash(
1021 + key,
1022 + value,
1023 + key_cache,
1024 + value_cache,
1025 + attn_metadata.slot_mapping,
1026 + self.kv_cache_dtype,
1027 + layer._k_scale,
1028 + layer._v_scale,
1029 + )
1030
1031 # decode:extend:prefill
1032 query = query[:num_actual_tokens]
@@ -902,6 +1079,11 @@ class AiterFlashAttentionImpl(AttentionImpl):
1079 extend_keys = key[extend_tokens_slice]
1080 extend_values = value[extend_tokens_slice]
1081 extend_outputs = output[extend_tokens_slice]
1082 + k_scale = layer._k_scale
1083 + v_scale = layer._v_scale
1084 + if rocm_aiter_ops.is_shuffle_kv_cache_enabled():
1085 + k_scale = attn_metadata.k_scale
1086 + v_scale = attn_metadata.v_scale
1087 self.extend_forward(
1088 attn_metadata=attn_metadata,
1089 query=extend_querys,
@@ -920,14 +1102,17 @@ class AiterFlashAttentionImpl(AttentionImpl):
1102 slot_mapping=attn_metadata.slot_mapping[
1103 num_decodes : num_decodes + num_extends
1104 ],
923 - k_scale=layer._k_scale,
924 - v_scale=layer._v_scale,
1105 + k_scale=k_scale,
1106 + v_scale=v_scale,
1107 )
1108
1109 # calculate for decodes
1110 if num_decodes > 0:
1111 assert attn_metadata.decode_metadata is not None
1112 if self.sliding_window[0] != -1:
1113 + assert not rocm_aiter_ops.is_shuffle_kv_cache_enabled(), (
1114 + "Sliding window with shuffle layout is not supported yet."
1115 + )
1116 from aiter.ops.triton.unified_attention import (
1117 unified_attention,
1118 )
@@ -957,41 +1142,71 @@ class AiterFlashAttentionImpl(AttentionImpl):
1142 )
1143 return
1144 assert attn_metadata.decode_metadata is not None
960 - _, num_heads, head_size = query.shape
961 - nbytes_per_qo_elem = torch.finfo(query.dtype).bits // 8
962 - num_seqs = attn_metadata.seq_lens.shape[0]
963 - max_num_partitions = (
964 - attn_metadata.max_seq_len + _PARTITION_SIZE_ROCM - 1
965 - ) // _PARTITION_SIZE_ROCM
966 -
967 - workspace_buffer = torch.empty(
968 - (num_seqs * num_heads * max_num_partitions * head_size)
969 - * nbytes_per_qo_elem
970 - + 2 * (num_seqs * num_heads * max_num_partitions) * 4,
971 - dtype=torch.uint8,
972 - device=output.device,
973 - )
1145
975 - torch.ops.aiter.paged_attention_v1(
976 - output[:num_decode_tokens],
977 - workspace_buffer,
978 - query[:num_decode_tokens],
979 - key_cache,
980 - value_cache,
981 - self.scale,
982 - attn_metadata.block_table[:num_decodes],
983 - attn_metadata.query_start_loc[:num_decodes],
984 - attn_metadata.seq_lens[:num_decodes],
985 - attn_metadata.max_seq_len,
986 - self.alibi_slopes,
987 - self.kv_cache_dtype,
988 - "NHD",
989 - self.logits_soft_cap,
990 - layer._k_scale,
991 - layer._v_scale,
992 - None,
993 - _PARTITION_SIZE_ROCM,
994 - )
1146 + if rocm_aiter_ops.is_shuffle_kv_cache_enabled():
1147 + num_blocks, block_size, num_kv_heads, head_size = key_cache.shape
1148 + x = 16 // key_cache.element_size()
1149 + k_cache_template = torch.empty(
1150 + [num_blocks, num_kv_heads, head_size // x, block_size, x],
1151 + dtype=key_cache.dtype,
1152 + device="meta",
1153 + )
1154 + v_cache_template = torch.empty(
1155 + [num_blocks, num_kv_heads, block_size // x, head_size, x],
1156 + dtype=value_cache.dtype,
1157 + device="meta",
1158 + )
1159 + new_key_cache = key_cache.view_as(k_cache_template)
1160 + new_value_cache = value_cache.view_as(v_cache_template)
1161 + aiter.pa_fwd_asm(
1162 + Q=query[:num_decode_tokens],
1163 + K=new_key_cache,
1164 + V=new_value_cache,
1165 + block_tables=attn_metadata.block_table[:num_decodes],
1166 + context_lens=attn_metadata.seq_lens[:num_decodes],
1167 + block_tables_stride0=attn_metadata.block_table[
1168 + :num_decodes
1169 + ].stride(0),
1170 + K_QScale=attn_metadata.k_scale,
1171 + V_QScale=attn_metadata.v_scale,
1172 + out_=output[:num_decode_tokens],
1173 + )
1174 + else:
1175 + _, num_heads, head_size = query.shape
1176 + nbytes_per_qo_elem = torch.finfo(query.dtype).bits // 8
1177 + num_seqs = attn_metadata.seq_lens.shape[0]
1178 + max_num_partitions = (
1179 + attn_metadata.max_seq_len + _PARTITION_SIZE_ROCM - 1
1180 + ) // _PARTITION_SIZE_ROCM
1181 +
1182 + workspace_buffer = torch.empty(
1183 + (num_seqs * num_heads * max_num_partitions * head_size)
1184 + * nbytes_per_qo_elem
1185 + + 2 * (num_seqs * num_heads * max_num_partitions) * 4,
1186 + dtype=torch.uint8,
1187 + device=output.device,
1188 + )
1189 +
1190 + torch.ops.aiter.paged_attention_v1(
1191 + output[:num_decode_tokens],
1192 + workspace_buffer,
1193 + query[:num_decode_tokens],
1194 + key_cache,
1195 + value_cache,
1196 + self.scale,
1197 + attn_metadata.block_table[:num_decodes],
1198 + attn_metadata.query_start_loc[:num_decodes],
1199 + attn_metadata.seq_lens[:num_decodes],
1200 + attn_metadata.max_seq_len,
1201 + self.alibi_slopes,
1202 + self.kv_cache_dtype,
1203 + "NHD",
1204 + self.logits_soft_cap,
1205 + layer._k_scale,
1206 + layer._v_scale,
1207 + None,
1208 + _PARTITION_SIZE_ROCM,
1209 + )
1210 else:
1211 raise NotImplementedError(
1212 "Cascade attention is not implemented for ROCM AITER"