Skip to content

Commit b3791c2

Browse files
authored
[BugFix]Get mamba cached block failed (vllm-project#11131)
### What this PR does / why we need it? - Fixes vllm-project#10998 Currently issue: block size: 1k chunk size: 4k condition 1: ``` full 0: 1k 2k 3k 4k(evict) -> hit 3k mamba 1: 4k -> hit 4k mamba 2: 4k -> hit 4k mamba 3: 4k -> hit 4k finally hit: 3k(min) problem: group 2, 3, 4 load 3k failed(-704) ``` condition 2: ``` full 0: 1k 2k 3k 4k 5k 6k 7k 8k 9k 10k 11k 12k(evict) -> hit 11k mamba 1: 4k(evict) 8k 12k -> hit 8k mamba 2: 4k 8k(evict) 12k -> hit 4k mamba 3: 4k 8k 12k -> hit 4k finally hit: 4k(min) problem: group 1 load 4k failed(-704) ``` After Fix: condition 1: ``` full 0: 1k 2k 3k 4k(evict) -> hit [1k 2k 3k] mamba 1: 4k -> hit [4k] mamba 2: 4k -> hit [4k] mamba 3: 4k -> hit [4k] finally hit: 0(max(intersection)) ``` condition 2: ``` full 0: 1k 2k 3k 4k 5k 6k 7k 8k 9k 10k 11k 12k(evict) -> hit [1k 2k 3k 4k 5k 6k 7k 8k 9k 10k 11k] mamba 1: 4k(evict) 8k 12k -> hit [8k] mamba 2: 4k 8k(evict) 12k -> hit [4k] mamba 3: 4k 8k 12k -> hit [4k] finally hit: 0(max(intersection)) ``` condition 2: ``` full 0: 1k 2k 3k 4k 5k 6k 7k 8k 9k 10k 11k 12k(evict) -> hit [1k 2k 3k 4k 5k 6k 7k 8k 9k 10k 11k] mamba 1: 4k 8k 12k -> hit [4k 8k] mamba 2: 4k 8k(evict) 12k -> hit [4k] mamba 3: 4k 8k 12k -> hit [4k] finally hit: 4k(max(intersection)) ``` - vLLM version: v0.23.0 - vLLM main: vllm-project/vllm@dc68bd8 --------- Signed-off-by: Qingsong Zhang <1640410765@qq.com>
1 parent b7b7f4e commit b3791c2

2 files changed

Lines changed: 107 additions & 44 deletions

File tree

tests/ut/distributed/ascend_store/test_pool_worker.py

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -50,28 +50,61 @@ def test_check_all_layers_exists_none(self):
5050
result = cls.check_all_layers_exists(None, [0, 0, 0], 3)
5151
self.assertEqual(result, [0])
5252

53-
def test_find_max_hit_index_found(self):
53+
def test_find_all_continuous_hit_positions_found(self):
5454
cls = self._make_worker_class()
5555
arr = [[1, 1, 0], [1, 0, 1]]
56-
result = cls.find_max_hit_index(None, arr, 3)
57-
self.assertEqual(result, 0)
56+
result = cls.find_all_continuous_hit_positions(arr, [16, 32, 48], 3, 48, 16)
57+
self.assertEqual(result, [16])
5858

59-
def test_find_max_hit_index_all_one(self):
59+
def test_find_all_continuous_hit_positions_all_one(self):
6060
cls = self._make_worker_class()
6161
arr = [[1, 1, 1], [1, 1, 1]]
62-
result = cls.find_max_hit_index(None, arr, 3)
63-
self.assertEqual(result, 2)
62+
result = cls.find_all_continuous_hit_positions(arr, [16, 32, 48], 3, 48, 16)
63+
self.assertEqual(result, [16, 32, 48])
6464

65-
def test_find_max_hit_index_first_pos(self):
65+
def test_find_all_continuous_hit_positions_first_pos(self):
6666
cls = self._make_worker_class()
6767
arr = [[0, 1], [1, 0]]
68-
result = cls.find_max_hit_index(None, arr, 3)
69-
self.assertEqual(result, -1)
68+
result = cls.find_all_continuous_hit_positions(arr, [16, 32], 2, 48, 16)
69+
self.assertEqual(result, [])
70+
71+
def test_find_all_continuous_hit_positions_empty(self):
72+
cls = self._make_worker_class()
73+
result = cls.find_all_continuous_hit_positions([], [], 0, 48, 16)
74+
self.assertEqual(result, [])
75+
76+
def test_find_all_discontinuous_hit_positions_all_tp_hits(self):
77+
cls = self._make_worker_class()
78+
arr = [[0, 0, 1, 0, 0, 1], [0, 0, 1, 0, 0, 1]]
79+
result = cls.find_all_discontinuous_hit_positions(arr, [16, 32, 48, 64, 80, 96], 6, 128, 16)
80+
self.assertEqual(result, [48, 96])
81+
82+
def test_find_all_discontinuous_hit_positions_some_tp_hits(self):
83+
cls = self._make_worker_class()
84+
arr = [[0, 0, 1, 0, 0, 1], [0, 0, 1, 0, 0, 0]]
85+
result = cls.find_all_discontinuous_hit_positions(arr, [16, 32, 48, 64, 80, 96], 6, 128, 16)
86+
self.assertEqual(result, [48])
87+
88+
def test_find_all_discontinuous_hit_positions_all_tp_hits_with_limits(self):
89+
cls = self._make_worker_class()
90+
arr = [[0, 0, 1, 0, 0, 1], [0, 0, 1, 0, 0, 1]]
91+
result = cls.find_all_discontinuous_hit_positions(arr, [16, 32, 48, 64, 80, 96], 6, 64, 16)
92+
self.assertEqual(result, [48])
93+
94+
def test_max_intersection_hit_position_single_group(self):
95+
cls = self._make_worker_class()
96+
hits = [[16, 32, 48]]
97+
self.assertEqual(48, cls._max_intersection_hit_position(hits))
98+
99+
def test_max_intersection_hit_position_empty_group(self):
100+
cls = self._make_worker_class()
101+
hits: list[list[int]] = []
102+
self.assertEqual(0, cls._max_intersection_hit_position(hits))
70103

71-
def test_find_max_hit_index_empty(self):
104+
def test_max_intersection_hit_position_multi_group(self):
72105
cls = self._make_worker_class()
73-
result = cls.find_max_hit_index(None, [], 0)
74-
self.assertEqual(result, -1)
106+
hits = [[16, 32, 48], [32, 48], [16, 32], [32, 48, 64]]
107+
self.assertEqual(32, cls._max_intersection_hit_position(hits))
75108

76109

77110
class TestKVPoolWorkerInit(unittest.TestCase):
@@ -84,6 +117,7 @@ def _make_vllm_config(self, kv_role="kv_producer", extra_config=None, block_size
84117
config.model_config.hf_text_config = MagicMock(spec=[]) # no index_topk
85118
config.model_config.get_num_layers.return_value = 32
86119
config.model_config.get_total_num_kv_heads.return_value = 8
120+
config.model_config.max_model_len = 1024
87121
config.parallel_config.data_parallel_rank = 0
88122
config.parallel_config.rank = 0
89123
config.parallel_config.pipeline_parallel_size = 1
@@ -461,6 +495,7 @@ def _make_config(self, kv_role="kv_producer", extra_config=None, block_size=16):
461495
config.model_config.model = "org/llama-7b"
462496
config.model_config.use_mla = False
463497
config.model_config.hf_text_config = MagicMock(spec=[])
498+
config.model_config.max_model_len = 1024
464499
config.model_config.get_num_layers.return_value = 2
465500
config.model_config.get_total_num_kv_heads.return_value = 1
466501
config.parallel_config.data_parallel_rank = 0

vllm_ascend/distributed/kv_transfer/kv_pool/ascend_store/pool_worker.py

Lines changed: 60 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ def __init__(
7878
hf_config = getattr(model_config, "hf_config", hf_text_config)
7979
self.hf_config = hf_text_config or hf_config
8080
self.compress_ratios = getattr(hf_text_config, "compress_ratios", None)
81+
self.max_model_len = model_config.max_model_len
8182
if self.compress_ratios is None:
8283
self.compress_ratios = getattr(hf_config, "compress_ratios", None)
8384
self.use_compress = self.compress_ratios is not None
@@ -1018,7 +1019,8 @@ def lookup_scheduler(
10181019
:return: An int indicating how many prefix tokens are cached.
10191020
"""
10201021
try:
1021-
hits = []
1022+
hits: list[list[int]] = []
1023+
max_hit_position = self.max_model_len
10221024
kv_cache_group_ids = kv_cache_group_ids or [0]
10231025
kv_cache_group_ids = self._get_lookup_gate_group_ids(kv_cache_group_ids)
10241026
for group_id in kv_cache_group_ids:
@@ -1081,32 +1083,22 @@ def lookup_scheduler(
10811083
multi_tp_keys[:3],
10821084
)
10831085
if group_id < len(self.group_uses_align_state) and self.group_uses_align_state[group_id]:
1084-
# mamba group with align mode will skip some null block, we must loop it in reverse order
1085-
for i in range(num_block - 1, -1, -1):
1086-
if (
1087-
all(values[i] == 1 for values in multi_tp_values)
1088-
and ends[i] % self.cache_transfer_granularity == 0
1089-
):
1090-
hits.append(ends[i])
1091-
break
1092-
else:
1093-
return 0
1086+
group_hits = self.find_all_discontinuous_hit_positions(
1087+
multi_tp_values, ends, num_block, max_hit_position, self.cache_transfer_granularity
1088+
)
10941089
else:
1095-
index = self.find_max_hit_index(multi_tp_values, num_block)
1096-
if index == -1:
1097-
return 0
1098-
else:
1099-
for hit_index in range(index, -1, -1):
1100-
if ends[hit_index] % self.cache_transfer_granularity == 0:
1101-
hits.append(ends[hit_index])
1102-
break
1103-
else:
1104-
return 0
1090+
group_hits = self.find_all_continuous_hit_positions(
1091+
multi_tp_values, ends, num_block, max_hit_position, self.cache_transfer_granularity
1092+
)
1093+
if not group_hits:
1094+
return 0
1095+
max_hit_position = min(max_hit_position, group_hits[-1])
1096+
hits.append(group_hits)
11051097
logger.debug(
11061098
"KV pool scheduler lookup group=%d keys=%d hit=%d token_len=%d",
11071099
group_id,
11081100
len(keys),
1109-
hits[-1],
1101+
max_hit_position,
11101102
token_len,
11111103
)
11121104
except Exception as e:
@@ -1116,14 +1108,26 @@ def lookup_scheduler(
11161108
e,
11171109
)
11181110
return 0
1111+
final_hits = self._max_intersection_hit_position(hits)
11191112
logger.debug(
1120-
"KV pool scheduler lookup final token_len=%d groups=%s hits=%s result=%d",
1113+
"KV pool scheduler lookup final token_len=%d groups=%s hit=%d",
11211114
token_len,
11221115
kv_cache_group_ids,
1123-
hits,
1124-
min(hits) if hits else 0,
1116+
final_hits,
11251117
)
1126-
return min(hits) if hits else 0
1118+
return final_hits
1119+
1120+
@staticmethod
1121+
def _max_intersection_hit_position(hits: list[list[int]]) -> int:
1122+
"""
1123+
For all attention groups, treat the position of the maximum common hit as the final hit position
1124+
"""
1125+
if not hits:
1126+
return 0
1127+
common_elements = set(hits[0]).intersection(*hits[1:])
1128+
if not common_elements:
1129+
return 0
1130+
return max(common_elements)
11271131

11281132
def check_all_layers_exists(self, res: list[int], num_layers: int) -> list[int]:
11291133
total_chunks = len(res) // num_layers
@@ -1137,13 +1141,37 @@ def check_all_layers_exists(self, res: list[int], num_layers: int) -> list[int]:
11371141

11381142
return result
11391143

1140-
def find_max_hit_index(self, arr, num_blocks: int):
1144+
@staticmethod
1145+
def find_all_discontinuous_hit_positions(
1146+
arr, ends, num_blocks: int, max_hit_position: int, cache_transfer_granularity: int
1147+
) -> list[int]:
1148+
"""
1149+
For mamba attn, there will be some uncached null blocks, we just collect all hit positions,
1150+
and use the last position as final hit position
1151+
"""
1152+
hits: list[int] = []
11411153
for i in range(num_blocks):
1142-
if any(row[i] != 1 for row in arr):
1143-
return i - 1
1144-
else:
1145-
# if arr is not empty, all hits, else no hits
1146-
return len(arr[0]) - 1 if arr else -1
1154+
if ends[i] > max_hit_position:
1155+
break
1156+
if all(row[i] == 1 for row in arr):
1157+
if ends[i] % cache_transfer_granularity == 0:
1158+
hits.append(ends[i])
1159+
return hits
1160+
1161+
@staticmethod
1162+
def find_all_continuous_hit_positions(
1163+
arr, ends, num_blocks: int, max_hit_position: int, cache_transfer_granularity: int
1164+
) -> list[int]:
1165+
hits: list[int] = []
1166+
for i in range(num_blocks):
1167+
if ends[i] > max_hit_position:
1168+
break
1169+
if all(row[i] == 1 for row in arr):
1170+
if ends[i] % cache_transfer_granularity == 0:
1171+
hits.append(ends[i])
1172+
else:
1173+
break
1174+
return hits
11471175

11481176
def get_kv_events(self) -> list[BlockStored]:
11491177
if self.enable_kv_events and self.kv_send_thread is not None:

0 commit comments

Comments
 (0)