[Bugfix][ROCm] Select page-aligned kernel blocks for pooled indexers so the block table addresses storage pages (GLM-5.3-Flash) (#59412)

Signed-off-by: vllmellm <vllm.ellm@embeddedllm.com>
This commit is contained in:
yhcheong (vllmellm)
2026-10-08 14:44:58 +00:00
committed by GitHub
parent 2f7f05c256
commit 273e099693
2 changed files with 178 additions and 24 deletions
@@ -0,0 +1,135 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""CPU tests for storage-block kernel-block selection in prepare_kernel_block_sizes."""
from types import SimpleNamespace
import pytest
import torch
from vllm.config import VllmConfig, set_current_vllm_config
from vllm.models.glm5next.common.attention import Glm5NextIndexerCache
from vllm.v1.attention.backend import AttentionBackend, MultipleOf
from vllm.v1.attention.backends.mla import indexer as indexer_mod
from vllm.v1.attention.backends.mla.indexer import (
DeepseekV32IndexerBackend,
KpoolTailBackend,
)
from vllm.v1.attention.backends.mla.rocm_aiter_mla_sparse import (
ROCMAiterMLASparseBackend,
)
from vllm.v1.kv_cache_interface import MLAAttentionSpec
from vllm.v1.worker.utils import prepare_kernel_block_sizes
pytestmark = pytest.mark.cpu_test
# ``index_kpool`` of zai-org/GLM-5.3-Flash: one indexer state pools 4
# compressed states (hence storage_block_size = page * 4).
INDEX_KPOOL = 4
# The hybrid KDA/mamba manager floors at TP8..TP1; 640 is the geometry of the
# original report ("the 640-token block table").
TP_FLOOR_MANAGER_BLOCKS = [640, 1152, 2176, 4352]
def _mock_rocm_platform(monkeypatch: pytest.MonkeyPatch) -> None:
# Platform predicates are mutually exclusive in production. Override both
# so these ROCm policy tests do not inherit CUDA capabilities from the CI
# host running them (same approach as test_deepseek_v4_rocm_adaptive).
monkeypatch.setattr(indexer_mod.current_platform, "is_cuda", lambda: False)
monkeypatch.setattr(indexer_mod.current_platform, "is_rocm", lambda: True)
def _kpool_storage_spec(manager_block: int) -> MLAAttentionSpec:
"""Real Glm5NextIndexerCache spec for the given cache block size."""
with set_current_vllm_config(VllmConfig()):
cache = Glm5NextIndexerCache(
head_dim=128,
dtype=torch.bfloat16,
prefix="model.layers.0.indexer.k_cache_probe",
cache_config=SimpleNamespace(block_size=manager_block),
index_kpool=INDEX_KPOOL,
)
spec = cache.get_kv_cache_spec(VllmConfig())
assert isinstance(spec, MLAAttentionSpec)
return spec
def _prepare(
spec: MLAAttentionSpec, backends: list[type[AttentionBackend]]
) -> list[int]:
kv_cache_config = SimpleNamespace(
kv_cache_groups=[SimpleNamespace(kv_cache_spec=spec)]
)
attn_groups = [[SimpleNamespace(backend=backend) for backend in backends]]
return prepare_kernel_block_sizes(kv_cache_config, attn_groups)
@pytest.mark.parametrize("manager_block", TP_FLOOR_MANAGER_BLOCKS)
def test_prepare_uses_storage_block_for_the_kpool_group(
monkeypatch: pytest.MonkeyPatch, manager_block: int
):
"""The bug: on ROCm, kpool groups must get 128/256 - not the 640-4352
manager block - as their kernel block size.
Manager-granular selection is what made the block table address manager
blocks while the kpool writer and the index-cache gather read pool pages,
aliasing every cache column past the request's row.
"""
_mock_rocm_platform(monkeypatch)
spec = _kpool_storage_spec(manager_block)
expected_storage = 256 if manager_block % 256 == 0 else 128
assert spec.storage_block_size == expected_storage
selected = _prepare(
spec,
[ROCMAiterMLASparseBackend, DeepseekV32IndexerBackend, KpoolTailBackend],
)
assert selected == [expected_storage]
assert selected != [manager_block]
# The hybrid block-table split stays integral.
assert manager_block % selected[0] == 0
def test_prepare_falls_back_when_storage_block_is_not_supported():
"""No storage block, or a storage block the backends reject (e.g. the
CUDA indexer's exact ``[64]`` vs a 128-token storage block): selection is
`select_common_block_size`'s answer, unchanged from no-fix behavior.
"""
class Fixed64Backend:
@staticmethod
def get_supported_kernel_block_sizes():
return [64]
@staticmethod
def get_name() -> str:
return "FIXED64"
# Storage block the group's backend does not accept -> backend vote (64).
spec = _kpool_storage_spec(640)
assert spec.storage_block_size == 128
assert _prepare(spec, [Fixed64Backend]) == [64]
# No storage block at all (any non-kpool MLA model) -> backend vote.
plain_spec = MLAAttentionSpec(
block_size=640,
num_kv_heads=1,
head_size=128,
dtype=torch.bfloat16,
)
assert plain_spec.storage_block_size is None
assert _prepare(plain_spec, [Fixed64Backend]) == [64]
# ...and with backends that accept the manager block, the manager wins,
# exactly as on main.
class AcceptAllBackend:
@staticmethod
def get_supported_kernel_block_sizes():
return [MultipleOf(1)]
@staticmethod
def get_name() -> str:
return "ACCEPTALL"
assert _prepare(plain_spec, [AcceptAllBackend]) == [640]
+43 -24
View File
@@ -327,6 +327,30 @@ class AttentionGroup:
self.get_metadata_builder().update_draft_decode_metadata(metadata)
def _block_size_is_supported(
backends: list[type[AttentionBackend]], block_size: int
) -> bool:
"""Check if the block size is supported by all backends.
An exact ``int`` declaration must match exactly; a ``MultipleOf``
declaration accepts any multiple of its base.
"""
for backend in backends:
is_supported = False
for supported_size in backend.get_supported_kernel_block_sizes():
if isinstance(supported_size, int):
if block_size == supported_size:
is_supported = True
elif isinstance(supported_size, MultipleOf):
if block_size % supported_size.base == 0:
is_supported = True
else:
raise ValueError(f"Unknown supported size: {supported_size}")
if not is_supported:
return False
return True
def select_common_block_size(
kv_manager_block_size: int,
backends: list[type[AttentionBackend]],
@@ -348,27 +372,7 @@ def select_common_block_size(
ValueError: If no valid block size found.
"""
def block_size_is_supported(
backends: list[type[AttentionBackend]], block_size: int
) -> bool:
"""Check if the block size is supported by all backends."""
for backend in backends:
is_supported = False
for supported_size in backend.get_supported_kernel_block_sizes():
if isinstance(supported_size, int):
if block_size == supported_size:
is_supported = True
elif isinstance(supported_size, MultipleOf):
if block_size % supported_size.base == 0:
is_supported = True
else:
raise ValueError(f"Unknown supported size: {supported_size}")
if not is_supported:
return False
return True
if block_size_is_supported(backends, kv_manager_block_size):
if _block_size_is_supported(backends, kv_manager_block_size):
return kv_manager_block_size
# MultipleOf constraints also accept the manager size if they accept a divisor.
@@ -381,7 +385,7 @@ def select_common_block_size(
}
for size in sorted(candidates, reverse=True):
if block_size_is_supported(backends, size):
if _block_size_is_supported(backends, size):
return size
raise ValueError(
f"No common block size for {kv_manager_block_size} ("
@@ -493,9 +497,24 @@ def prepare_kernel_block_sizes(
# This is an attention backend that supports virtual block splitting.
kv_manager_block_size = kv_cache_group.kv_cache_spec.block_size
group_backends = [g.backend for g in attn_groups[kv_cache_gid]]
selected_kernel_size = select_common_block_size(
kv_manager_block_size, group_backends
storage_block_size = (
kv_cache_spec.storage_block_size
if isinstance(kv_cache_spec, MLAAttentionSpec)
else None
)
if storage_block_size is not None and _block_size_is_supported(
group_backends, storage_block_size
):
# Storage-block specs (e.g. the GLM-5.3-Flash kpool indexer
# cache) address the cache in pool pages, and every other
# consumer (cache views, metadata builders, hisparse) already
# uses storage_block_size as the kernel block. Fall back to the
# backend vote when the group's backends do not accept it.
selected_kernel_size = storage_block_size
else:
selected_kernel_size = select_common_block_size(
kv_manager_block_size, group_backends
)
kernel_block_sizes.append(selected_kernel_size)
elif isinstance(kv_cache_spec, MambaSpec):
# This is likely Mamba or other non-attention cache, no splitting.