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 2, 13:30 UTC
[Attention][AMD] Standardize kv layout to blocks first for AMD (#43660)
Signed-off-by: NickLucche <nlucches@redhat.com>
Nicolò Lucchesi committed
May 28, 2026 at 19:28 UTC
5b115bb8a33d72820075450ecefcd292607bfe57
6 files changed
+57
-44
vllm/platforms/rocm.py
+7
-3
@@ -362,6 +362,7 @@ def flash_attn_triton_available() -> bool:
362
def _get_backend_priorities(
363
use_mla: bool,
364
use_sparse: bool,
365
+ use_kv_connector: bool = False,
366
) -> list[AttentionBackendEnum]:
367
from vllm._aiter_ops import is_aiter_found_and_supported, rocm_aiter_ops
368
@@ -380,9 +381,11 @@ def _get_backend_priorities(
381
AttentionBackendEnum.TRITON_MLA,
382
]
383
383
- backends = [
384
- AttentionBackendEnum.ROCM_ATTN,
385
- ]
384
+ backends = []
385
+ # ROCM_ATTN uses (2, num_blocks, ...) KV cache layout which is
386
+ # incompatible with KV connectors that require blocks-first layout.
387
+ if not use_kv_connector:
388
+ backends.append(AttentionBackendEnum.ROCM_ATTN)
389
if rocm_aiter_ops.is_mha_enabled():
390
backends.append(AttentionBackendEnum.ROCM_AITER_FA)
391
if is_aiter_found_and_supported():
@@ -461,6 +464,7 @@ class RocmPlatform(Platform):
464
backend_priorities = _get_backend_priorities(
465
attn_selector_config.use_mla,
466
attn_selector_config.use_sparse,
467
+ attn_selector_config.use_kv_connector,
468
)
469
for priority, backend in enumerate(backend_priorities):
470
try:
vllm/v1/attention/backend.py
+7
@@ -240,6 +240,10 @@ class AttentionBackend(ABC):
240
def supports_batch_invariance(cls) -> bool:
241
return False
242
243
+ @classmethod
244
+ def supports_kv_connector(cls) -> bool:
245
+ return True
246
+
247
@classmethod
248
def supports_attn_type(cls, attn_type: str) -> bool:
249
"""Check if backend supports a given attention type.
@@ -283,6 +287,7 @@ class AttentionBackend(ABC):
287
attn_type: str,
288
use_non_causal: bool = False,
289
use_batch_invariant: bool = False,
290
+ use_kv_connector: bool = False,
291
) -> list[str]:
292
invalid_reasons = []
293
if not cls.supports_head_size(head_size):
@@ -319,6 +324,8 @@ class AttentionBackend(ABC):
324
invalid_reasons.append("non-causal attention not supported")
325
if use_batch_invariant and not cls.supports_batch_invariance():
326
invalid_reasons.append("batch invariance not supported")
327
+ if use_kv_connector and not cls.supports_kv_connector():
328
+ invalid_reasons.append("KV connector not supported")
329
combination_reason = cls.supports_combination(
330
head_size,
331
dtype,
vllm/v1/attention/backends/rocm_aiter_fa.py
+23
-35
@@ -225,6 +225,8 @@ if current_platform.is_rocm():
225
x,
226
k_stride0,
227
v_stride0,
228
+ k_cache_block_stride,
229
+ v_cache_block_stride,
230
block_size,
231
head_size,
232
num_kv_heads,
@@ -241,15 +243,17 @@ if current_platform.is_rocm():
243
return
244
block_id = slot_id // block_size
245
block_offset = slot_id % block_size
244
- dst_offset = (
245
- block_id * num_kv_heads * head_size * block_size
246
- + head_id * head_size * block_size
246
+ k_dst_offset = (
247
+ block_id * k_cache_block_stride + head_id * head_size * block_size
248
)
249
dst_k_shuffle_offset = (
249
- dst_offset + offset // x * block_size * x + block_offset * x + offset % x
250
+ k_dst_offset + offset // x * block_size * x + block_offset * x + offset % x
251
+ )
252
+ v_dst_offset = (
253
+ block_id * v_cache_block_stride + head_id * head_size * block_size
254
)
255
dst_v_shuffle_offset = (
252
- dst_offset
256
+ v_dst_offset
257
+ block_offset // x * head_size * x
258
+ offset * x
259
+ block_offset % x
@@ -280,18 +284,6 @@ if current_platform.is_rocm():
284
_, num_kv_heads, head_size = key.shape
285
num_blocks, block_size, _, _ = key_cache.shape
286
x = 16 // key_cache.element_size()
283
- k_cache_template = torch.empty(
284
- [num_blocks, num_kv_heads, head_size // x, block_size, x],
285
- dtype=key_cache.dtype,
286
- device="meta",
287
- )
288
- v_cache_template = torch.empty(
289
- [num_blocks, num_kv_heads, block_size // x, head_size, x],
290
- dtype=value_cache.dtype,
291
- device="meta",
292
- )
293
- new_key_cache = key_cache.view_as(k_cache_template)
294
- new_value_cache = value_cache.view_as(v_cache_template)
287
QUANT = False
288
if is_quantized_kv_cache(kv_cache_dtype):
289
QUANT = True
@@ -302,14 +294,16 @@ if current_platform.is_rocm():
294
reshape_and_cache_shuffle_kernel[grid](
295
key,
296
value,
305
- new_key_cache,
306
- new_value_cache,
297
+ key_cache,
298
+ value_cache,
299
slot_mapping,
300
k_scales,
301
v_scales,
302
x,
303
key.stride(0),
304
value.stride(0),
305
+ key_cache.stride(0),
306
+ value_cache.stride(0),
307
block_size,
308
head_size,
309
num_kv_heads,
@@ -485,7 +479,7 @@ class AiterFlashAttentionMetadataBuilder(
479
kv_cache_shape = self.vllm_config.compilation_config.static_forward_context[
480
first_layer_name
481
].kv_cache.shape
488
- num_blocks = kv_cache_shape[1]
482
+ num_blocks = kv_cache_shape[0]
483
self.scale = torch.ones(
484
[num_blocks, self.num_heads_kv, self.block_size],
485
dtype=torch.float32,
@@ -765,7 +759,7 @@ class AiterFlashAttentionBackend(AttentionBackend):
759
) -> tuple[int, ...]:
760
if block_size % 16 != 0:
761
raise ValueError("Block size must be a multiple of 16.")
768
- return (2, num_blocks, block_size, num_kv_heads, head_size)
762
+ return (num_blocks, 2, block_size, num_kv_heads, head_size)
763
764
@classmethod
765
def supports_compute_capability(cls, capability: DeviceCapability) -> bool:
@@ -1025,7 +1019,7 @@ class AiterFlashAttentionImpl(AttentionImpl):
1019
key: shape = [num_tokens, num_kv_heads, head_size]
1020
value: shape = [num_tokens, num_kv_heads, head_size]
1021
kv_cache: shape =
1028
- [2, num_blocks, block_size, num_kv_heads, head_size]
1022
+ [num_blocks, 2, block_size, num_kv_heads, head_size]
1023
attn_metadata: Metadata for attention.
1024
Returns:
1025
shape = [num_tokens, num_heads * head_size]
@@ -1053,7 +1047,7 @@ class AiterFlashAttentionImpl(AttentionImpl):
1047
# Whenever making a change in this method, please benchmark the
1048
# performance to make sure it does not introduce any overhead.
1049
num_actual_tokens = attn_metadata.num_actual_tokens
1056
- key_cache, value_cache = kv_cache.unbind(0)
1050
+ key_cache, value_cache = kv_cache.unbind(1)
1051
1052
if is_quantized_kv_cache(self.kv_cache_dtype):
1053
key_cache = key_cache.view(current_platform.fp8_dtype())
@@ -1278,18 +1272,12 @@ class AiterFlashAttentionImpl(AttentionImpl):
1272
max_logits = torch.empty_like(exp_sums)
1273
num_blocks, block_size, num_kv_heads, _ = key_cache.shape
1274
x = 16 // key_cache.element_size()
1281
- k_cache_template = torch.empty(
1282
- [num_blocks, num_kv_heads, head_size // x, block_size, x],
1283
- dtype=key_cache.dtype,
1284
- device="meta",
1275
+ new_key_cache = key_cache.reshape(
1276
+ num_blocks, num_kv_heads, head_size // x, block_size, x
1277
)
1286
- v_cache_template = torch.empty(
1287
- [num_blocks, num_kv_heads, block_size // x, head_size, x],
1288
- dtype=value_cache.dtype,
1289
- device="meta",
1278
+ new_value_cache = value_cache.reshape(
1279
+ num_blocks, num_kv_heads, block_size // x, head_size, x
1280
)
1291
- new_key_cache = key_cache.view_as(k_cache_template)
1292
- new_value_cache = value_cache.view_as(v_cache_template)
1281
k_qscale = (
1282
layer._k_scale
1283
if attn_metadata.k_scale is None
@@ -1378,7 +1366,7 @@ class AiterFlashAttentionImpl(AttentionImpl):
1366
kv_cache: torch.Tensor,
1367
slot_mapping: torch.Tensor,
1368
):
1381
- key_cache, value_cache = kv_cache.unbind(0)
1369
+ key_cache, value_cache = kv_cache.unbind(1)
1370
1371
# key and value may be None in the case of cross attention. They are
1372
# calculated once based on the output from the encoder and then cached
@@ -1446,7 +1434,7 @@ class AiterFlashAttentionImpl(AttentionImpl):
1434
kv_cache: torch.Tensor,
1435
layer_slot_mapping: torch.Tensor,
1436
):
1449
- key_cache, value_cache = kv_cache.unbind(0)
1437
+ key_cache, value_cache = kv_cache.unbind(1)
1438
flash_layout = True
1439
1440
is_fp8_kv_cache = is_quantized_kv_cache(self.kv_cache_dtype)
vllm/v1/attention/backends/rocm_aiter_unified_attn.py
+5
-5
@@ -74,7 +74,7 @@ class RocmAiterUnifiedAttentionBackend(RocmAttentionBackend):
74
) -> tuple[int, ...]:
75
if block_size % 16 != 0:
76
raise ValueError("Block size must be a multiple of 16.")
77
- return (2, num_blocks, block_size, num_kv_heads, head_size)
77
+ return (num_blocks, 2, block_size, num_kv_heads, head_size)
78
79
@staticmethod
80
def use_cascade_attention(*args, **kwargs) -> bool:
@@ -153,7 +153,7 @@ class RocmAiterUnifiedAttentionImpl(RocmAttentionImpl):
153
key: shape = [num_tokens, num_kv_heads, head_size]
154
value: shape = [num_tokens, num_kv_heads, head_size]
155
kv_cache: shape =
156
- [2, num_blocks, block_size, num_kv_heads, head_size]
156
+ [num_blocks, 2, block_size, num_kv_heads, head_size]
157
attn_metadata: Metadata for attention.
158
Returns:
159
shape = [num_tokens, num_heads * head_size]
@@ -194,7 +194,7 @@ class RocmAiterUnifiedAttentionImpl(RocmAttentionImpl):
194
layer,
195
)
196
197
- key_cache, value_cache = kv_cache.unbind(0)
197
+ key_cache, value_cache = kv_cache.unbind(1)
198
199
softmax_scale = self.scale
200
if is_quantized_kv_cache(self.kv_cache_dtype):
@@ -243,7 +243,7 @@ class RocmAiterUnifiedAttentionImpl(RocmAttentionImpl):
243
# For encoder attention,
244
# we use direct Q, K, V tensors without caching
245
return
246
- key_cache, value_cache = kv_cache.unbind(0)
246
+ key_cache, value_cache = kv_cache.unbind(1)
247
248
# Reshape the input keys and values and store them in the cache.
249
ops.reshape_and_cache_flash(
@@ -276,7 +276,7 @@ class RocmAiterUnifiedAttentionImpl(RocmAttentionImpl):
276
# For encoder attention,
277
# we use direct Q, K, V tensors without caching
278
return
279
- key_cache, value_cache = kv_cache.unbind(0)
279
+ key_cache, value_cache = kv_cache.unbind(1)
280
flash_layout = True
281
282
is_fp8_kv_cache = is_quantized_kv_cache(self.kv_cache_dtype)
vllm/v1/attention/backends/rocm_attn.py
+6
@@ -208,6 +208,12 @@ class RocmAttentionBackend(AttentionBackend):
208
def supports_non_causal(cls) -> bool:
209
return True
210
211
+ @classmethod
212
+ def supports_kv_connector(cls) -> bool:
213
+ # ROCM_ATTN uses (2, num_blocks, ...) KV cache layout which is
214
+ # incompatible with KV connectors that require blocks-first layout.
215
+ return False
216
+
217
forward_includes_kv_cache_update: bool = False
218
219
@staticmethod
vllm/v1/attention/selector.py
+9
-1
@@ -31,6 +31,7 @@ class AttentionSelectorConfig(NamedTuple):
31
attn_type: str = AttentionType.DECODER
32
use_non_causal: bool = False
33
use_batch_invariant: bool = False
34
+ use_kv_connector: bool = False
35
36
def __repr__(self):
37
return (
@@ -45,7 +46,8 @@ class AttentionSelectorConfig(NamedTuple):
46
f"use_per_head_quant_scales={self.use_per_head_quant_scales}, "
47
f"attn_type={self.attn_type}, "
48
f"use_non_causal={self.use_non_causal}, "
48
- f"use_batch_invariant={self.use_batch_invariant})"
49
+ f"use_batch_invariant={self.use_batch_invariant}, "
50
+ f"use_kv_connector={self.use_kv_connector})"
51
)
52
53
@@ -80,6 +82,11 @@ def get_attn_backend(
82
else:
83
block_size = None
84
85
+ kv_transfer_config = vllm_config.kv_transfer_config
86
+ use_kv_connector = (
87
+ kv_transfer_config is not None and kv_transfer_config.is_kv_transfer_instance
88
+ )
89
+
90
attn_selector_config = AttentionSelectorConfig(
91
head_size=head_size,
92
dtype=dtype,
@@ -93,6 +100,7 @@ def get_attn_backend(
100
attn_type=attn_type or AttentionType.DECODER,
101
use_non_causal=vllm_config.attention_config.use_non_causal,
102
use_batch_invariant=envs.VLLM_BATCH_INVARIANT,
103
+ use_kv_connector=use_kv_connector,
104
)
105
106
return _cached_get_attn_backend(