From 34f7168ce3eadc1757d9b43e4088df18dd78e75b Mon Sep 17 00:00:00 2001 From: andsonder Date: Sat, 2 Nov 2024 22:38:00 +0800 Subject: [PATCH] [Doc] add block manager NaiveBlockAllocator part --- .../04_block_manager_part1.md | 493 ++++++++++++++++++ ...b64f6c50a135922d773f80eb59a4375dd3152e.png | Bin 0 -> 51419 bytes 2 files changed, 493 insertions(+) create mode 100644 docs/16_vllm_source_code/04_block_manager_part1.md create mode 100644 docs/16_vllm_source_code/images/171265232857bb86e4f7b5ee3ab64f6c50a135922d773f80eb59a4375dd3152e.png diff --git a/docs/16_vllm_source_code/04_block_manager_part1.md b/docs/16_vllm_source_code/04_block_manager_part1.md new file mode 100644 index 0000000..29b61f2 --- /dev/null +++ b/docs/16_vllm_source_code/04_block_manager_part1.md @@ -0,0 +1,493 @@ +# vLLM BlockManager - NaiveBlockAllocator + + +在上一篇博客中,我们深入解析了 vLLM 调度器的调度策略,了解了它如何通过任务分配、优先级排序、抢占等机制在高并发请求环境下提升性能。而这一切的背后,内存管理的效率至关重要。调度器必须在 CPU 和 GPU 之间灵活分配内存,以支持生成任务的顺利执行。这一任务的核心正是由 **BlockSpaceManager** 来完成。 + +在 vLLM 中,`BlockSpaceManager` 和 `BlockAllocator` 共同承担了生成过程中内存分配、动态调整和缓存管理的职责。它们直接影响到高效处理 `waiting`、`running` 和 `swapped` 三个状态队列中的请求:如何在不同阶段为任务分配内存资源,如何优化 GPU 和 CPU 间的数据交换,如何避免内存瓶颈。这些都是 `BlockSpaceManager` 需要解决的问题。 + +本篇博客将介绍 `BlockSpaceManager` 的设计和实现,聚焦其在不同状态队列中如何分配和管理内存资源。需要注意的是,`BlockAllocator` 目前支持多种类型的内存分配器。**在这里,我们将重点介绍 `NaiveBlockAllocator` 的实现,不涉及更为复杂的 `PrefixCachingBlockAllocator`。** + +:::note + +本系列的代码基于 vLLM 的 0.6.3 版本介绍 + +::: + + +## 1. BlockSpaceManager 的架构概览 + +在 vLLM 系统中,BlockSpaceManager 负责为调度器的请求提供动态内存管理,其架构设计旨在实现高效的 GPU/CPU 内存分配和切换。为了支持调度器在不同请求状态下(如 waiting、running、swapped)的内存需求,BlockSpaceManager 采用了 BlockAllocator 模块对物理内存进行细化管理。NaiveBlockAllocator 是 BlockAllocator 的一种实现,提供了基础的内存块分配和管理能力,适用于不需要复杂缓存机制的场景。 + +### 1.1 BlockSpaceManager 管理策略 + +BlockSpaceManager 的管理策略围绕三个核心目标展开:**分配、动态扩展、和交换内存**。在调度器处理请求的过程中,不同状态的请求会对内存提出不同的需求。BlockSpaceManager 通过 BlockAllocator 将内存资源拆分为小块(blocks),并灵活地将这些块分配到 waiting、running 和 swapped 状态队列中的请求。 + +- **分配**:当请求进入 waiting 状态时,BlockSpaceManager 创建并分配一个初始 BlockTable,用于预填充所需的内存。 +- **动态扩展**:对于 running 状态的请求,在生成新 token 时,需要追加更多内存块以支持解码阶段。BlockAllocator 动态扩展 BlockTable,确保请求可以在 GPU 内存中进行连续解码。 +- **交换**:当 GPU 内存资源不足时,部分 running 队列的请求将转移至 swapped 队列。BlockSpaceManager 通过 NaiveBlockAllocator 将内存块从 GPU 移至 CPU,腾出 GPU 空间以供高优先级任务使用。 + +### 1.2 各组件的角色 + +在 BlockSpaceManager 中,内存块的分配与管理主要由以下几个关键组件协作完成: + +- **BlockAllocator**:提供底层的内存块分配接口,包括块的分配、扩展、和交换。NaiveBlockAllocator 是该接口的一种简单实现,通过一系列的块管理方法(如 `allocate_immutable_blocks`, `append_slots`, `swap_out` 等)来支持基本的内存操作。 +- **BlockTable**:表示一个请求的内存块表,用于记录和追踪分配给特定 Sequence 或 SequenceGroup 的物理块。BlockTable 是内存管理的核心数据结构,提供了内存块的分配和回收机制。 +- **Sequence 和 SequenceGroup**:代表了请求中的一系列 token 序列。Sequence 是基本的序列单位,而 SequenceGroup 则是多个 Sequence 的集合。当一个请求进入调度器时,BlockSpaceManager 会为 SequenceGroup 创建一个或多个 BlockTable,用于存储序列所需的内存块。 +- **Block**:Block 是内存的基本单位。在 NaiveBlockAllocator 中,Block 表示一个固定大小的内存区域,用于存储 token IDs。在解码阶段,每当需要生成新 token 时,BlockSpaceManager 会在 BlockTable 中追加新的 Block。 + +## 2. 内存分配与管理机制 + +### 2.1 调度器何时使用 BlockAllocator + +在 **vLLM 调度器**中,`BlockAllocator` 作为内存管理的核心模块,负责为生成任务中的不同阶段提供动态的内存块分配。具体来说,`BlockAllocator` 在 `prefill`(预填充)和 `decode`(解码)两个阶段起到关键作用。这两个阶段对应着生成过程中任务状态的变化,而 `BlockAllocator` 则根据这些变化为 `waiting` 队列和 `running/swapped` 队列中的请求提供所需的内存支持。 + +**在 prefill 阶段**,调度器**在请求进入生成任务前会预分配初始内存。这个阶段主要用于将请求的初始 token 加载到 GPU 内存中**,以便模型启动生成任务。此时,调度器会调用 `BlockAllocator` 的 `allocate` 方法,为 `waiting` 队列中的 `seq_group` 分配初始化的物理内存块,确保序列能够加载其初始数据。这一步对于模型生成任务的平稳启动至关重要。 + +接下来是 **decode 阶段**,这个阶段**用于请求生成 token 的过程**。随着生成的逐步进行,请求所需的内存量会动态增加。此时,调度器会调用 `append_slots` 方法,为 `running` 或 `swapped` 队列中的序列动态追加内存块,确保生成过程有足够的内存支撑。解码阶段对内存的需求较为灵活,因此 `BlockAllocator` 必须确保每个序列在需要时能获得足够的内存,避免生成过程的中断。 + + +### 2.2 BlockTable 的内存分配 + +在 **vLLM** 中,`BlockTable` 是 `BlockSpaceManager` 内存管理架构的核心数据结构,承担了管理和存储序列 token 数据的职责。在具体实现中,`BlockTable` 利用 `_blocks` 列表维护实际的内存块分配,通过与 `NaiveBlockAllocator` 的交互完成内存的动态分配、扩展和释放。 + +#### `BlockTable` 在内存管理中的角色 + +`BlockTable` 的作用**类似于操作系统的页表**。每个请求的 token 数据被分割成独立的逻辑块,这些块通过 `BlockTable` 进行存储和管理,使得 vLLM 可以高效地处理生成任务中的内存需求。例如,当一个新的 `SequenceGroup` 在 `prefill` 阶段进入 `waiting` 队列时,`BlockTable` 负责在 `NaiveBlockAllocator` 的帮助下分配足够的物理块,以存储初始提示的 KV 缓存。随后在 `decode` 阶段,`BlockTable` 动态扩展以存储新生成的 token。 + +这种架构不仅节省了内存,还提高了生成任务的并行处理能力。不同的请求可以通过块表的管理共享有限的物理内存资源,从而支持更大的批量生成任务。 + +![picture 0](images/171265232857bb86e4f7b5ee3ab64f6c50a135922d773f80eb59a4375dd3152e.png) + + +#### `_blocks` 列表:内存块的核心管理结构 + +`_blocks` 列表是 `BlockTable` 的核心数据结构,用于存储实际分配的内存块。`_blocks` 中的每个元素都是一个 `Block` 实例,代表了一个逻辑存储块,包含一定数量的 token 数据。当一个 `Sequence` 需要扩展存储空间时,`BlockTable` 会通过 `NaiveBlockAllocator` 动态分配新的内存块,并将这些块添加到 `_blocks` 中。 + +以下是 `BlockTable` 中 `_blocks` 列表的结构与管理机制的关键代码: + +```python +class BlockTable: + def __init__(self, block_size: int, block_allocator: DeviceAwareBlockAllocator, ...): + self._block_size = block_size + self._allocator = block_allocator + self._blocks: BlockList = BlockList(_blocks or []) + ... +``` + +在这里,`_blocks` 初始化为空列表,代表一个新请求的存储空间。随着生成任务的执行,`_blocks` 列表会动态扩展,新增的物理块由 `block_allocator` 分配。例如,`allocate` 方法通过调用 `_allocate_blocks_for_token_ids` 分配所需的内存块,并将这些块更新到 `_blocks` 中。 + +#### `NaiveBlockAllocator` 如何支持 `BlockTable` 的动态扩展 + +`BlockTable` 通过 `NaiveBlockAllocator` 进行内存分配和扩展,确保每个生成任务在需要时获得合适的内存空间。具体来说,当需要为 `SequenceGroup` 分配新的 KV 缓存空间时,`NaiveBlockAllocator` 会根据请求所需的 token 数量计算需要的内存块数量并逐步分配。以下代码展示了 `allocate` 方法如何使用 `NaiveBlockAllocator` 动态分配存储空间: + +```python +def allocate(self, token_ids: List[int], device: Device = Device.GPU) -> None: + blocks = self._allocate_blocks_for_token_ids(prev_block=None, token_ids=token_ids, device=device) + self.update(blocks) + ... +``` + +在上述代码中,`allocate` 方法调用 `_allocate_blocks_for_token_ids`,根据 token 数量计算需要的块数并分配。分配完成后,通过 `update` 将这些块加载到 `_blocks` 列表中。 `_allocate_blocks_for_token_ids` 我们等会再详细介绍。 + +#### `BlockTable` 内存扩展与回收机制 + +随着生成任务的执行,`BlockTable` 中的 `_blocks` 列表会动态增加新的块,为生成的 token 提供存储空间。而当一个 `Sequence` 生成任务完成后,`BlockTable` 会调用 `free` 方法释放所占用的内存块,为后续任务腾出空间。每个物理块在完成使用后,通过 `NaiveBlockAllocator` 归还到内存池中,供其他请求复用。这种设计能够高效利用 GPU 内存,提高生成任务的吞吐量。 + +通过 `BlockTable` 的分层管理和 `NaiveBlockAllocator` 的动态分配机制,vLLM 实现了内存资源的高效利用,满足了多任务并发处理的需求。 + + +### 2.3 BlockTable 内存管理方法解析 + +在 `BlockTable` 中,内存管理方法负责高效分配和管理内存块,以支持不同阶段的请求需求。这些方法在 `NaiveBlockAllocator` 的协作下,通过动态分配和扩展策略,使调度器可以灵活应对序列生成过程中的存储需求。以下对主要内存管理方法进行深入解析: + +#### 预分配存储空间:等待序列的内存准备 + +`allocate` 方法用于预先分配存储空间,尤其是针对 `waiting` 队列中序列的 `prefill` 操作。此方法会根据初始提示长度和生成任务所需的 KV 缓存大小,通过 `_allocate_blocks_for_token_ids` 为每个序列分配一个或多个块,并将这些块更新到 `_blocks` 列表中。 + +```python +def allocate(self, token_ids: List[int], device: Device = Device.GPU) -> None: + blocks = self._allocate_blocks_for_token_ids(prev_block=None, token_ids=token_ids, device=device) + self.update(blocks) + self._num_full_slots = len(token_ids) +``` + +`allocate` 首先调用 `_allocate_blocks_for_token_ids` 根据 `token_ids` 的数量和块大小动态计算需要的块数,并逐一分配。分配完成后,这些块被添加到 `_blocks` 中。`_num_full_slots` 则更新当前分配的 token 数量,用于后续内存操作的参考。 + +#### 动态扩展:解码阶段的内存追加 + +在 `decode` 阶段,`running` 和 `swapped` 队列中的序列需要不断生成新的 token。`append_token_ids` 允许动态扩展 `BlockTable`,为新生成的 token 分配空间。通过追加 token 数据到现有块或者分配新块,该方法确保 decode 阶段的内存需求得到满足。 + +```python +def append_token_ids(self, token_ids: List[int], num_lookahead_slots: int = 0, num_computed_slots: Optional[int] = None) -> None: + # 确保分配足够的空余空间 + self.ensure_num_empty_slots(num_empty_slots=len(token_ids) + num_lookahead_slots) + + # 获取当前块表的起始索引,分块存储 token_ids + first_block_idx = self._num_full_slots // self._block_size + token_blocks = self._chunk_token_blocks_for_append(token_ids) + + # 将每块 token 数据存入对应的块 + for i, token_block in enumerate(token_blocks): + self._blocks.append_token_ids(first_block_idx + i, token_block) + + self._num_full_slots += len(token_ids) +``` + +`append_token_ids` 通过 `ensure_num_empty_slots` 检查是否有足够空间以存储 `token_ids` 和指定的 `lookahead` 数据,确保无中断的生成过程。随后,`_chunk_token_blocks_for_append` 方法将 `token_ids` 分为块大小的子集,并逐一追加到 `BlockTable` 的 `_blocks` 列表中。最终 `_num_full_slots` 更新为当前存储的总 token 数。 + +#### 空闲槽位检查与动态分配 + +`ensure_num_empty_slots` 确保当前 `BlockTable` 中的空闲槽位足够容纳新的 token 数据。在不足的情况下,方法会调用 `NaiveBlockAllocator` 动态分配新块。 + +```python +def ensure_num_empty_slots(self, num_empty_slots: int) -> None: + # 当前已有空闲空间满足需求,直接返回 + if self._num_empty_slots >= num_empty_slots: + return + + # 计算需要的新增槽位并分配新块 + slots_to_allocate = num_empty_slots - self._num_empty_slots + blocks_to_allocate = cdiv(slots_to_allocate, self._block_size) + + for _ in range(blocks_to_allocate): + self._blocks.append(self._allocator.allocate_mutable_block(prev_block=self._blocks[-1], device=Device.GPU)) +``` + +在此方法中,`_num_empty_slots` 表示当前剩余的空闲槽位。如果空闲空间不足,则计算所需的新槽位数量并通过 `NaiveBlockAllocator` 逐块分配,使得 `BlockTable` 满足新的存储需求。 + +#### 计算资源需求与空间分配 + +在 `prefill` 和 `decode` 阶段,`BlockTable` 利用辅助方法评估所需资源量并动态分配内存,确保资源分配的准确性。`get_num_required_blocks` 方法在分配初期帮助确定所需的最小块数,而 `get_num_blocks_touched_by_append_slots` 则在扩展阶段计算追加操作影响的块数。 + +```python +@staticmethod +def get_num_required_blocks(token_ids: List[int], block_size: int, num_lookahead_slots: int = 0) -> int: + return cdiv(len(token_ids) + num_lookahead_slots, block_size) +``` + +在动态扩展阶段,`get_num_blocks_touched_by_append_slots` 方法提供了精确的块数计算,用于指导调度器在 decode 阶段合理分配内存资源。 + +```python +def get_num_blocks_touched_by_append_slots(self, token_ids: List[int], num_lookahead_slots: int) -> int: + num_token_ids = len(token_ids) + num_lookahead_slots + first_chunk_size = self._block_size - (self._num_full_slots % self._block_size) + num_token_blocks = 1 + math.ceil((num_token_ids - first_chunk_size) / self._block_size) + return num_token_blocks +``` + +这两种计算方法为调度器的资源分配提供了精确的数据支撑,提升了系统的内存利用效率。 + +### 2.4 调度阶段的内存分配流程 + +在 vLLM 的内存分配架构中,不同阶段的请求需要不同的内存分配策略来保证生成任务的流畅性。BlockSpaceManager 通过 `allocate` 和 `append_slots` 等方法来管理这些内存块的分配和扩展,以适应 `waiting`、`running、`swapped` 队列中请求的实际需求。 + +#### 内存分配初始化:`waiting` 队列的处理 + +当一个请求被加入 `waiting` 队列时,我们需要先为其分配基本的内存空间。这时,调度器会调用 `allocate` 方法为其分配预填充(`prefill`)阶段所需的内存块。 + +`allocate` 方法的任务是将请求的 token 映射到内存块中。它会调用 `_allocate_blocks_for_token_ids` 方法,将一批 token 按需分配到一个或多个块中。这样,`waiting` 队列中的每个请求都能在 `prefill` 阶段拥有足够的内存。 + +```python +def allocate(self, token_ids: List[int], device: Device = Device.GPU) -> None: + # 通过内部方法为 token 分配物理块 + blocks = self._allocate_blocks_for_token_ids(prev_block=None, token_ids=token_ids, device=device) + self.update(blocks) + self._num_full_slots = len(token_ids) +``` + +`allocate` 方法做了两件关键的事情: + +1. **块分配**:它调用 `_allocate_blocks_for_token_ids`,为传入的 `token_ids`(即请求的 token 序列)分配所需的物理内存块。 +2. **更新块表**:分配的块会被添加到 `BlockTable` 中,确保后续访问能找到这些 token 数据所在的内存块。 + +```python +def _allocate_blocks_for_token_ids(self, prev_block: Optional[Block], token_ids: List[int], device: Device) -> List[Block]: + blocks: List[Block] = [] + for cur_token_ids in chunk_list(token_ids, self._block_size): + # 满块直接分配为 immutable(不可变)块 + if len(cur_token_ids) == self._block_size: + blocks.extend(self._allocator.allocate_immutable_blocks(prev_block, block_token_ids=[cur_token_ids], device=device)) + else: + # 不满块分配为 mutable(可变)块 + block = self._allocator.allocate_mutable_block(prev_block=prev_block, device=device) + block.append_token_ids(cur_token_ids) + blocks.append(block) + prev_block = blocks[-1] + return blocks +``` + +在这段代码中,每个块的分配逻辑如下: + +- **满块分配**:如果 `token_ids` 刚好填满一个块,则直接分配为不可变块。 +- **不满块分配**:若 token 数据无法填满一个完整的块,则分配为可变块,以便后续可以继续添加数据。 + +当 `allocate` 完成时,`waiting` 队列中的请求已经获得了 `prefill` 阶段所需的内存,并且 `BlockTable` 已记录所有分配的内存块。 + + +#### 解码阶段的动态扩展:`running` 和 `swapped` 队列 + +在进入 `decode` 阶段后,请求会被移动到 `running` 或 `swapped` 队列中。在这一阶段,生成的 token 数据量是动态增加的,因此 `BlockTable` 需要支持内存的动态扩展,这时就会用到 `append_slots` 方法。 + + +`append_slots` 方法通过 `append_token_ids` 将新的 token 数据追加到已有的内存块中,并在空间不足时动态分配新的块。这样可以保证 `running` 和 `swapped` 队列中的请求在解码过程中能够获得持续的内存支持。 + +```python +def append_slots(self, seq: Sequence, num_lookahead_slots: int) -> List[Tuple[int, int]]: + block_table = self.block_tables[seq.seq_id] + # 动态追加 token 到现有块中,不足时自动扩展 + block_table.append_token_ids(token_ids=block_table.get_unseen_token_ids(seq.get_token_ids()), num_lookahead_slots=num_lookahead_slots) + new_cows = self.block_allocator.clear_copy_on_writes() + return new_cows +``` + +`append_slots` 方法的主要逻辑是: + +1. **获取未分配的 token**:`get_unseen_token_ids` 会筛选出还未追加到 `BlockTable` 的 token 数据。 +2. **追加或扩展**:调用 `append_token_ids` 方法将这些新 token 追加到现有内存块中。若空间不足,`append_token_ids` 会自动扩展新的块以容纳新增的 token。 + + +在 `append_token_ids` 方法内部,`ensure_num_empty_slots` 会检查是否有足够的空余槽位,若不足则调用 `NaiveBlockAllocator` 分配新的块: + +```python +def append_token_ids(self, token_ids: List[int], num_lookahead_slots: int = 0) -> None: + # 确保有足够的空余槽位,动态追加块 + self.ensure_num_empty_slots(len(token_ids) + num_lookahead_slots) + first_block_idx = self._num_full_slots // self._block_size + token_blocks = self._chunk_token_blocks_for_append(token_ids) + for i, token_block in enumerate(token_blocks): + self._blocks.append_token_ids(first_block_idx + i, token_block) + self._num_full_slots += len(token_ids) +``` + +在这里,`ensure_num_empty_slots` 会根据需要追加新的内存块,并逐块存储新生成的 token。对于解码阶段,这种动态扩展机制确保了 `running` 和 `swapped` 队列能够随时应对新增的生成内容。 + +综上所述,`BlockTable` 和 `NaiveBlockAllocator` 在不同的调度阶段各有分工: + +- 在 `waiting` 队列的 `prefill` 阶段,`allocate` 方法预分配必要的内存。 +- 在 `running` 和 `swapped` 队列的 `decode` 阶段,`append_slots` 动态扩展以满足实时生成的 token 数据。 + +这种分配机制在保证内存使用效率的同时,兼顾了扩展性,使得 vLLM 能够更高效地管理资源,从而提高解码过程的吞吐量和响应速度。 + + +## 3. 交换(Swap)机制与缓存策略 + +### 3.1 交换机制的应用场景 + +在 vLLM 的生成任务中,GPU 内存资源通常十分宝贵,因此需要高效管理以确保模型推理和生成的顺利进行。交换机制(Swap)在这种场景下显得尤为重要。交换机制的核心思想是,当 GPU 内存资源不足时,将低优先级任务或当前未活跃的任务暂时从 GPU 转移至 CPU。 + +在代码层面上,交换的背景和应用场景可以进一步解释为: + +1. 资源不足时的调度优先级:在 `can_swap_in` 和 `can_swap_out` 的实现中,BlockSpaceManager 会通过检测当前 GPU 的空闲内存块数与设定的 watermark 水位值来判断是否需要交换任务。这并不是简单的“低优先级转移”,而是基于实际的内存占用情况做出的动态调整。只有在内存紧张时,才会调用 `swap_out` 方法将任务转移至 CPU。 +2. 多步调度触发的动态交换:`swap_in` 和 `swap_out` 的调用并不是直接基于任务的活跃性,而是根据任务在 running 和 swapped 队列中的状态。对于不活跃或暂时未进行解码的序列,调度器会优先尝试转移至 CPU,从而释放 GPU 内存给当前运行的任务。这里的状态转换是通过 `SequenceStatus` 管理的,而非直接依据任务优先级。 + +### 3.2 swap_in 和 swap_out 实现与状态管理 + +在 vLLM 的内存管理中,`swap_in` 和 `swap_out` 是两个关键方法,用于在 GPU 和 CPU 间交换内存,确保 GPU 有足够的空间处理当前任务,而不影响已经存在的任务状态。`swap_in` 负责将任务从 CPU 转移回 GPU,以便继续运行;而 `swap_out` 则将暂时不需要处理的数据从 GPU 转移到 CPU,释放 GPU 内存给其他任务。下面我们逐步剖析 `swap_in` 和 `swap_out` 的代码实现细节,理解其如何维护内存的连续性和数据一致性。 + +#### `swap_out`:释放 GPU 内存 + +当 GPU 内存紧张时,调度器通过 `swap_out` 方法将一部分内存块移至 CPU,以释放 GPU 资源。具体来说,`swap_out` 的流程如下: + +1. **找到需要转移的内存块**:`swap_out` 会遍历传入的序列组 `seq_group` 中的每个序列(`seq`),并通过其在 `BlockTable` 中的内存块列表获取该序列所占用的所有 GPU 内存块。 + +2. **执行交换操作**:`swap_out` 调用 `block_allocator.swap` 将 GPU 内存块的内容复制到 CPU 内存中,并记录下交换前后的内存块 ID 对应关系。为了确保一致性,它更新了 `BlockTable` 中的内存块信息。 + +3. **释放 GPU 内存**:当所有的内存块都成功转移到 CPU 后,`swap_out` 将 GPU 上对应的内存标记为可用状态,以便新任务分配。`swap_out` 通过调用 `free` 方法在 `BlockAllocator` 中释放这些块。 + +在代码中,`swap_out` 的大致实现如下: + +```python +def swap_out(self, seq_group: SequenceGroup) -> List[Tuple[int, int]]: + physical_block_id_mapping = [] + for seq in seq_group.get_seqs(status=SequenceStatus.RUNNING): + blocks = self.block_tables[seq.seq_id].blocks + if len(blocks) == 0: + continue + + seq_swap_mapping = self.block_allocator.swap( + blocks=blocks, src_device=Device.GPU, dst_device=Device.CPU) + + # 更新块表以反映内存交换后的状态 + self.block_tables[seq.seq_id].update(blocks) + + # 记录交换后的物理内存块 ID 映射 + seq_physical_block_id_mapping = { + self.block_allocator.get_physical_block_id(Device.GPU, gpu_block_id): + self.block_allocator.get_physical_block_id(Device.CPU, cpu_block_id) + for gpu_block_id, cpu_block_id in seq_swap_mapping.items() + } + + physical_block_id_mapping.extend(list(seq_physical_block_id_mapping.items())) + + return physical_block_id_mapping +``` + + +- `seq_group.get_seqs(status=SequenceStatus.RUNNING)` 会筛选出在 `running` 状态的序列。 +- `self.block_allocator.swap` 实现了内存块在 GPU 和 CPU 间的真正转移,将 GPU 上的块内容拷贝到 CPU 并返回交换后的块映射关系。 +- 通过 `update` 方法,`BlockTable` 更新每个序列在交换后使用的块 ID,确保块表的状态始终与当前的内存布局一致。 + +#### `swap_in`:将数据转移回 GPU + +当某个任务需要在 GPU 上继续运行时,调度器会调用 `swap_in` 方法,将任务的内存块从 CPU 转移回 GPU。流程如下: + +1. **获取需要的 CPU 内存块**:`swap_in` 方法会遍历 `swapped` 状态的序列,找到对应的 CPU 内存块。 + +2. **将内存块转移回 GPU**:类似于 `swap_out`,`swap_in` 使用 `block_allocator.swap` 将 CPU 内存块重新映射到 GPU 上,同时记录下转移前后的块 ID 映射关系。 + +3. **更新块表信息**:`BlockTable` 中的内存块信息会随之更新,以反映新分配到的 GPU 内存地址。 + +`swap_in` 的核心代码如下: + +```python +def swap_in(self, seq_group: SequenceGroup) -> List[Tuple[int, int]]: + physical_block_id_mapping = [] + for seq in seq_group.get_seqs(status=SequenceStatus.SWAPPED): + blocks = self.block_tables[seq.seq_id].blocks + if len(blocks) == 0: + continue + + seq_swap_mapping = self.block_allocator.swap( + blocks=blocks, src_device=Device.CPU, dst_device=Device.GPU) + + # 刷新块表中的块信息 + self.block_tables[seq.seq_id].update(blocks) + + # 记录物理块 ID 的映射 + seq_physical_block_id_mapping = { + self.block_allocator.get_physical_block_id(Device.CPU, cpu_block_id): + self.block_allocator.get_physical_block_id(Device.GPU, gpu_block_id) + for cpu_block_id, gpu_block_id in seq_swap_mapping.items() + } + + physical_block_id_mapping.extend(list(seq_physical_block_id_mapping.items())) + + return physical_block_id_mapping +``` + +#### 状态一致性和内存管理的结合 + +`swap_in` 和 `swap_out` 的设计关键在于保证内存状态的一致性。`swap_in` 会确保转回 GPU 的块映射是最新的,而 `swap_out` 则及时释放不需要的 GPU 内存。 + +### 3.3 watermark 策略 + +简单来说,watermark 是一个内存使用的下限。它帮助系统在 GPU 内存即将耗尽前提前做出反应,而不是等到内存用光时再仓促处理。这样,系统可以更加从容地将低优先级任务的数据转移到 CPU,以便将宝贵的 GPU 资源腾出来供更紧急的任务使用。 + +在 `SelfAttnBlockSpaceManager` 中,watermark 的值通过构造函数传递,表示一个占总 GPU 内存比例的阈值。例如: + +```python +self.watermark = watermark # 表示 GPU 总块数的阈值比例 +self.watermark_blocks = int(watermark * num_gpu_blocks) # 转化为实际块数 +``` + +假设 `watermark` 被设置为 0.1,且 GPU 上有 1000 个内存块,那么 `watermark_blocks` 的值为 100。当 GPU 的剩余内存块数量低于 100 时,系统会考虑触发交换操作。 + +在判断是否需要交换时,`can_swap_in` 和 `can_swap_out` 两个方法分别会检查当前的内存使用情况,并将 watermark 作为一个重要的参考条件。 后面我们将详细介绍这两个方法的实现。 + +通过设置 `watermark`,系统可以避免频繁且耗时的内存交换操作。换句话说,`watermark` 让系统在内存充足时尽量保持任务在 GPU 上执行,而不是频繁地在 GPU 和 CPU 间来回交换数据,从而减少了不必要的数据传输开销。这一策略对于高效地利用 GPU 资源尤为重要,因为频繁的交换操作会阻碍 GPU 的计算能力,降低整体吞吐量。 + +此外,`watermark` 也减少了 GPU 上“最后一刻”分配内存的风险。通过提前判断并触发交换,系统能确保高优先级任务在内存紧张的情况下优先获得 GPU 资源,从而提高任务的响应速度。 + +### 3.4 内存判定逻辑:can_swap_in 和 can_swap_out + +在 `BlockAllocator` 中,`can_swap_in` 和 `can_swap_out` 这两个方法是实现交换机制的核心。它们通过判断当前内存状态来决定是否可以进行数据交换,从而在内存不足时有效地管理资源。接下来,我们通过代码逐步深入分析它们的实现逻辑。 + +#### can_swap_in:判断序列是否可以从 CPU 交换到 GPU + +当某个任务进入调度流程并准备从 CPU 移入 GPU 内存时,`can_swap_in` 会先检查 GPU 是否有足够的空间来容纳需要交换的块。这个方法的核心逻辑是计算出交换所需的块数量,然后与当前 GPU 可用的内存块数进行比较。若空间充足,才允许进行 `swap_in` 操作。 + +```python +def can_swap_in(self, seq_group: SequenceGroup, num_lookahead_slots: int) -> AllocStatus: + """ + 判断指定的序列组是否可以交换到 GPU 上。 + + Args: + seq_group (SequenceGroup): 需要交换到 GPU 的序列组。 + num_lookahead_slots (int): 需要提前分配的槽位数量(在推测性解码中使用)。 + + Returns: + AllocStatus: 表示分配状态的枚举,可能的值为 OK、LATER 或 NEVER。 + """ + # 调用私有方法 _can_swap 来判断是否满足交换条件 + return self._can_swap(seq_group, Device.GPU, SequenceStatus.SWAPPED, num_lookahead_slots) +``` + +`can_swap_in` 的实现逻辑其实依赖于 `_can_swap` 方法,这个方法会先计算该序列在 GPU 上需要的总块数,然后结合当前 GPU 的空闲块数来判断是否能满足需求。 + +#### can_swap_out:判断是否可以将序列从 GPU 交换到 CPU + +在资源紧张的情况下,需要将某些在 GPU 上执行的任务换出至 CPU,以释放 GPU 空间。`can_swap_out` 方法会检查是否有足够的 CPU 内存来存储该任务的数据,以决定能否将其交换出 GPU。 + +```python +def can_swap_out(self, seq_group: SequenceGroup) -> bool: + """ + 判断是否可以将指定的序列组从 GPU 交换到 CPU。 + + Args: + seq_group (SequenceGroup): 要交换的序列组。 + + Returns: + bool: 若可以交换出返回 True,否则返回 False。 + """ + # 通过 _can_swap 方法确定交换状态是否为 OK + alloc_status = self._can_swap(seq_group, Device.CPU, SequenceStatus.RUNNING) + return alloc_status == AllocStatus.OK +``` + +#### 代码中的关键步骤:_can_swap + +我们来看 `_can_swap` 方法的核心部分,理解如何判断交换状态,`_can_swap` 的关键步骤如下: + +1. **计算触及的块数**:在 `_can_swap` 中,通过遍历 `seq_group` 中的每个序列,计算其在交换后的完整块需求。 +2. **检查水位线限制**:该逻辑利用 `watermark_blocks` 设置的水位线来避免频繁交换,确保只有当 GPU 块数足够时才执行交换。 +3. **返回状态**:根据可用块数返回 `AllocStatus.OK`(可交换),`AllocStatus.LATER`(稍后再尝试),或 `AllocStatus.NEVER`(不可能交换)。 + + +```python +def _can_swap(self, seq_group: SequenceGroup, device: Device, status: SequenceStatus, num_lookahead_slots: int = 0) -> AllocStatus: + """ + 检查指定序列组在指定设备上是否满足交换条件。 + + Args: + seq_group (SequenceGroup): 需要检查的序列组。 + device (Device): 目标设备(GPU 或 CPU)。 + status (SequenceStatus): 当前序列状态。 + num_lookahead_slots (int): 需要提前分配的槽位数量。 + + Returns: + AllocStatus: 返回分配状态,可能的值为 OK、LATER 或 NEVER。 + """ + # 计算需要的完整块数 + num_blocks_touched = 0 + blocks = [] + + for seq in seq_group.get_seqs(status=status): + block_table = self.block_tables[seq.seq_id] + if block_table.blocks: + # 计算触及块数,包括未分配的附加槽位 + num_blocks_touched += block_table.get_num_blocks_touched_by_append_slots( + block_table.get_unseen_token_ids(seq.get_token_ids()), num_lookahead_slots) + blocks.extend(block_table.blocks) + + # 检查设备上是否有足够的空闲块以满足交换需求 + num_blocks_touched += self.block_allocator.get_num_full_blocks_touched(blocks, device=device) + watermark_blocks = self.watermark_blocks if device == Device.GPU else 0 + + # 确认是否满足交换条件 + if self.block_allocator.get_num_total_blocks(device) < num_blocks_touched: + return AllocStatus.NEVER + elif self.block_allocator.get_num_free_blocks(device) - num_blocks_touched >= watermark_blocks: + return AllocStatus.OK + else: + return AllocStatus.LATER +``` + +## 4. 总结 + +在这篇文章中,我们深入解析了 `BlockSpaceManager` 的架构与核心功能,展示了其在内存分配、动态扩展以及交换机制中的重要作用。`BlockSpaceManager` 类似于一种轻量的内存管理器,帮助生成任务在有限的内存资源下实现高效运行。通过 `BlockAllocator` 的配合,`BlockSpaceManager` 能够在任务的不同阶段智能分配和调整内存块,确保请求在预填充(prefill)、解码(decode)和交换(swap)阶段拥有足够的资源。后面我们还会介绍 PrefixCachingBlockAllocator 的实现细节。 + +## 参考文献 + +1. https://zhuanlan.zhihu.com/p/700780161 +2. https://arxiv.org/pdf/2309.06180 +3. https://github.com/vllm-project/vllm/blob/main/vllm/core/block_manager.py \ No newline at end of file diff --git a/docs/16_vllm_source_code/images/171265232857bb86e4f7b5ee3ab64f6c50a135922d773f80eb59a4375dd3152e.png b/docs/16_vllm_source_code/images/171265232857bb86e4f7b5ee3ab64f6c50a135922d773f80eb59a4375dd3152e.png new file mode 100644 index 0000000000000000000000000000000000000000..20fc79520869d003a8f004145f1e60de22b780a2 GIT binary patch literal 51419 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%0Awp#HcE$^CX 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