nano-blocks
nano-vllm/nanovllm/engine/block_manager.py · bb823b3 · L26–120 · 固定 commit 上游
这是固定版本源码的只读摘录;可在线阅读,也可通过上游链接查看原文件。
26class BlockManager:
27
28 def __init__(self, num_blocks: int, block_size: int):
29 self.block_size = block_size
30 self.blocks: list[Block] = [Block(i) for i in range(num_blocks)]
31 self.hash_to_block_id: dict[int, int] = dict()
32 self.free_block_ids: deque[int] = deque(range(num_blocks))
33 self.used_block_ids: set[int] = set()
34
35 @classmethod
36 def compute_hash(cls, token_ids: list[int], prefix: int = -1):
37 h = xxhash.xxh64()
38 if prefix != -1:
39 h.update(prefix.to_bytes(8, "little"))
40 h.update(np.array(token_ids).tobytes())
41 return h.intdigest()
42
43 def _allocate_block(self) -> int:
44 block_id = self.free_block_ids.popleft()
45 block = self.blocks[block_id]
46 assert block.ref_count == 0
47 if block.hash != -1 and self.hash_to_block_id.get(block.hash) == block_id:
48 del self.hash_to_block_id[block.hash]
49 block.reset()
50 self.used_block_ids.add(block_id)
51 return block_id
52
53 def _deallocate_block(self, block_id: int):
54 assert self.blocks[block_id].ref_count == 0
55 self.used_block_ids.remove(block_id)
56 self.free_block_ids.append(block_id)
57
58 def can_allocate(self, seq: Sequence) -> int:
59 h = -1
60 num_cached_blocks = 0
61 num_new_blocks = seq.num_blocks
62 for i in range(seq.num_blocks - 1):
63 token_ids = seq.block(i)
64 h = self.compute_hash(token_ids, h)
65 block_id = self.hash_to_block_id.get(h, -1)
66 if block_id == -1 or self.blocks[block_id].token_ids != token_ids:
67 break
68 num_cached_blocks += 1
69 if block_id in self.used_block_ids:
70 num_new_blocks -= 1
71 if len(self.free_block_ids) < num_new_blocks:
72 return -1
73 return num_cached_blocks
74
75 def allocate(self, seq: Sequence, num_cached_blocks: int):
76 assert not seq.block_table
77 h = -1
78 for i in range(num_cached_blocks):
79 token_ids = seq.block(i)
80 h = self.compute_hash(token_ids, h)
81 block_id = self.hash_to_block_id[h]
82 block = self.blocks[block_id]
83 if block_id in self.used_block_ids:
84 block.ref_count += 1
85 else:
86 block.ref_count = 1
87 self.free_block_ids.remove(block_id)
88 self.used_block_ids.add(block_id)
89 seq.block_table.append(block_id)
90 for i in range(num_cached_blocks, seq.num_blocks):
91 seq.block_table.append(self._allocate_block())
92 seq.num_cached_tokens = num_cached_blocks * self.block_size
93
94 def deallocate(self, seq: Sequence):
95 for block_id in reversed(seq.block_table):
96 block = self.blocks[block_id]
97 block.ref_count -= 1
98 if block.ref_count == 0:
99 self._deallocate_block(block_id)
100 seq.num_cached_tokens = 0
101 seq.block_table.clear()
102
103 def can_append(self, seq: Sequence) -> bool:
104 return len(self.free_block_ids) >= (len(seq) % self.block_size == 1)
105
106 def may_append(self, seq: Sequence):
107 if len(seq) % self.block_size == 1:
108 seq.block_table.append(self._allocate_block())
109
110 def hash_blocks(self, seq: Sequence):
111 start = seq.num_cached_tokens // self.block_size
112 end = (seq.num_cached_tokens + seq.num_scheduled_tokens) // self.block_size
113 if start == end: return
114 h = self.blocks[seq.block_table[start - 1]].hash if start > 0 else -1
115 for i in range(start, end):
116 block = self.blocks[seq.block_table[i]]
117 token_ids = seq.block(i)
118 h = self.compute_hash(token_ids, h)
119 block.update(h, token_ids)
120 self.hash_to_block_id[h] = block.block_id