[Bugfix][KV Cache] Fix incremental multimodal block hashing (#51694)
Signed-off-by: Jellow <49915976+CZT0@users.noreply.github.com> Signed-off-by: Jellow <dvdx@foxmail.com>
Jellow committed
Sep 25, 2026 at 06:38 UTC
e6c07ea5763bada5da789ed437851ee1526fd5a8
2 files changed
+41
-6
tests/v1/core/test_kv_cache_utils.py
+31
@@ -1018,6 +1018,37 @@ def test_request_block_hasher(hash_fn):
1018
assert block_hashes[1] == hash_fn((block_hashes[0], (3, 4, 5), (("hash2", 0),)))
1019
1020
1021
+@pytest.mark.parametrize("hash_fn", [sha256, sha256_cbor])
1022
+def test_request_block_hasher_incremental_append_with_multiple_mm_features(hash_fn):
1023
+ mm_positions = [
1024
+ PlaceholderRange(offset=4, length=2),
1025
+ PlaceholderRange(offset=6, length=1),
1026
+ ]
1027
+ incremental = make_request(
1028
+ request_id="incremental",
1029
+ prompt_token_ids=list(range(7)),
1030
+ block_size=4,
1031
+ hash_fn=hash_fn,
1032
+ mm_positions=mm_positions,
1033
+ mm_hashes=["A", "B"],
1034
+ )
1035
+ incremental.append_output_token_ids(7)
1036
+ fresh = make_request(
1037
+ request_id="fresh",
1038
+ prompt_token_ids=list(range(8)),
1039
+ block_size=4,
1040
+ hash_fn=hash_fn,
1041
+ mm_positions=mm_positions,
1042
+ mm_hashes=["A", "B"],
1043
+ )
1044
+
1045
+ expected_second_hash = hash_fn(
1046
+ (incremental.block_hashes[0], (4, 5, 6, 7), (("A", 0), ("B", 2)))
1047
+ )
1048
+ assert incremental.block_hashes[1] == expected_second_hash
1049
+ assert incremental.block_hashes == fresh.block_hashes
1050
+
1051
+
1052
@pytest.mark.parametrize("hash_fn", [sha256, sha256_cbor])
1053
def test_hash_tokens_different_mm_input(hash_fn):
1054
request1 = make_request(
vllm/v1/core/kv_cache_utils.py
+10
-6
@@ -15,6 +15,7 @@ from typing import TYPE_CHECKING, Any, NamedTuple, NewType, TypeAlias, cast, ove
15
from vllm import envs
16
from vllm.config import VllmConfig
17
from vllm.logger import init_logger
18
+from vllm.multimodal.utils import get_mm_features_in_window
19
from vllm.utils.hashing import xxhash, xxhash_cbor
20
from vllm.utils.math_utils import cdiv, round_up
21
from vllm.utils.mem_utils import format_gib
@@ -845,12 +846,15 @@ def get_request_block_hasher(
846
return []
847
848
curr_mm_idx = 0
848
- if start_token_idx > 0:
849
- # Set curr_mm_idx = -1 to indicate the last mm input.
850
- # Note that since we reach to this branch only when the block is
851
- # completed with generated tokens, we only need to consider the
852
- # last mm input.
853
- curr_mm_idx = -1
849
+ mm_features = request.mm_features
850
+ if start_token_idx > 0 and mm_features:
851
+ last_mm_pos = mm_features[-1].mm_position
852
+ if last_mm_pos.offset + last_mm_pos.length > start_token_idx:
853
+ curr_mm_idx, _ = get_mm_features_in_window(
854
+ mm_features,
855
+ start_token_idx,
856
+ start_token_idx + hash_block_size,
857
+ )
858
859
prev_block_hash_value = (
860
request.block_hashes[-1] if request.block_hashes else None