diff --git a/docs/zh/docs/blogs/2025/inside-vllm.md b/docs/zh/docs/blogs/2025/inside-vllm.md index 8a823ded..634da453 100644 --- a/docs/zh/docs/blogs/2025/inside-vllm.md +++ b/docs/zh/docs/blogs/2025/inside-vllm.md @@ -72,7 +72,7 @@ if __name__ == "__main__": 在此示例中,我们做两件事: 1. 实例化一个引擎 -2. 调用 `generate` 从给定的提示词中采样 +2. 调用 `generate` 从给定的 Prompt 中采样 让我们从分析构造函数开始。 @@ -100,7 +100,7 @@ if __name__ == "__main__": 2. `waiting` 和 `running` 队列 3. KV-cache 管理器:[分页注意力的核心](https://arxiv.org/abs/2309.06180) -KV-cache 管理器维护一个 `free_block_queue`。这是所有可用 KV-cache 块形成的池(通常有几十万块,具体取决于显存大小和块大小)。在分页注意力期间,这些块作为索引结构,将 Token 映射到其计算的各个 KV-cache 块上。 +KV-cache 管理器维护一个 `free_block_queue`。这是所有可用 KV-cache block 形成的池(通常有几十万个 block,具体取决于显存大小和 block 大小)。在分页注意力期间,这些 block 作为索引结构,将 Token 映射到其计算的各个 KV-cache block 上。  @@ -110,7 +110,7 @@ KV-cache 管理器维护一个 `free_block_queue`。这是所有可用 KV-cache !!! tip - 标准 Transformer 层([非 MLA](https://arxiv.org/abs/2405.04434))的块大小计算公式为: + 标准 Transformer 层([非 MLA](https://arxiv.org/abs/2405.04434))的 block 大小计算公式为: 2 (key/value) * `block_size`(默认=16) * `num_kv_heads` * `head_size` * `dtype_num_bytes`(例如 bf16 为 2) @@ -123,7 +123,7 @@ KV-cache 管理器维护一个 `free_block_queue`。这是所有可用 KV-cache - 根据请求的 `gpu_memory_utilization`(例如 0.8 是总显存的 80%)验证是否有足够的显存 - 设置分布式配置(DP/TP/PP/EP 等) - 实例化一个 `model_runner`(持有采样器、KV-cache 以及前向计算缓冲区,如 `input_ids`、`positions` 等) - - 实例化一个 `InputBatch` 对象(持有 CPU 端前向计算缓冲区、KV-cache 索引的块表、采样元数据等) + - 实例化一个 `InputBatch` 对象(持有 CPU 端前向计算缓冲区、KV-cache 索引的 block 表、采样元数据等) 2. 加载模型: @@ -135,7 +135,7 @@ KV-cache 管理器维护一个 `free_block_queue`。这是所有可用 KV-cache 3. 初始化 KV-cache: - 获取每层的 KV-cache 规格。历史上这总是 `FullAttentionSpec`(同质 Transformer),但对于混合模型(滑动窗口、Transformer/SSM 类 Jamba)会更复杂(参见 [Jenga](https://arxiv.org/abs/2503.18292)) - - 执行一次虚拟/分析前向计算并获取 GPU 内存快照,以计算可用显存中能容纳多少 KV-cache 块 + - 执行一次虚拟/分析前向计算并获取 GPU 内存快照,以计算可用显存中能容纳多少 KV-cache block - 分配、调整形状并绑定 KV-cache 张量到注意力层 - 准备注意力元数据(例如将后端设置为 FlashAttention),以供前向计算时内核使用 - 除非提供 `--enforce-eager`,否则对每个预热批次大小执行一次虚拟运行并捕获 CUDA 图。CUDA 图将整个 GPU 工作序列记录为 DAG。在后续前向计算中,我们直接启动/重放预先构建的图,从而减少内核启动开销并改善延迟。 @@ -146,14 +146,14 @@ KV-cache 管理器维护一个 `free_block_queue`。这是所有可用 KV-cache ### `generate` 函数 -第一步是验证并将请求送入引擎。对于每个提示词: +第一步是验证并将请求送入引擎。对于每个 Prompt: 1. 创建唯一请求 ID 并记录到达时间 -2. 调用输入预处理器,将提示词分词并返回一个字典,包含 `prompt`、`prompt_token_ids` 和 `type`(text、tokens、embeds 等) +2. 调用输入预处理器,将 Prompt 分词并返回一个字典,包含 `prompt`、`prompt_token_ids` 和 `type`(text、tokens、embeds 等) 3. 将这些信息打包进 `EngineCoreRequest`,添加优先级、采样参数和其他元数据 4. 将请求传入引擎核心,它会将请求包装为 `Request` 对象并将状态设置为 `WAITING`。然后该请求被加入调度器的 `waiting` 队列(如果是先来先服务(FCFS),则追加;如果是按优先级,则使用堆插入(heap-push)。) -此时,引擎已被喂入数据,执行可以开始。在同步引擎示例中,这些初始提示词是唯一处理的请求,没有机制在运行中注入新请求。相比之下,异步引擎支持此特性(即[连续批处理](https://www.usenix.org/conference/osdi22/presentation/yu)):每步结束后,会同时考虑新旧请求。 +此时,引擎已被喂入数据,执行可以开始。在同步引擎示例中,这些初始 Prompt 是唯一处理的请求,没有机制在运行中注入新请求。相比之下,异步引擎支持此特性(即[连续批处理](https://www.usenix.org/conference/osdi22/presentation/yu)):每步结束后,会同时考虑新旧请求。 !!! tip @@ -163,7 +163,7 @@ KV-cache 管理器维护一个 `free_block_queue`。这是所有可用 KV-cache 1. 调度:选择本步要运行的请求(解码和/或(分块)预填充) 2. 前向计算:运行模型并采样 Token -3. 后处理:将采样的 Token ID 添加到每个 `Request`,反分词,并检查停止条件。如果请求完成,清理(例如将 KV-cache 块返回 `free_block_queue`)并提前返回输出 +3. 后处理:将采样的 Token ID 添加到每个 `Request`,反分词,并检查停止条件。如果请求完成,清理(例如将 KV-cache block 返回 `free_block_queue`)并提前返回输出 !!! note "停止条件为:" @@ -188,7 +188,7 @@ KV-cache 管理器维护一个 `free_block_queue`。这是所有可用 KV-cache 推理引擎主要处理两类工作负载: -1. **预填充请求** - 对所有提示词 Token 执行一次前向计算。这类请求通常是 **计算受限** 的(阈值取决于硬件和提示词长度)。在末尾,我们从最后一个 Token 的概率分布中采样一个 Token。 +1. **预填充请求** - 对所有 Prompt Token 执行一次前向计算。这类请求通常是 **计算受限** 的(阈值取决于硬件和 Prompt 长度)。在末尾,我们从最后一个 Token 的概率分布中采样一个 Token。 2. **解码请求** - 仅对最近的 Token 执行前向计算。之前的所有 KV 向量已经缓存。这类请求是 **内存带宽受限** 的,因为我们仍然需要加载所有大语言模型权重(以及 KV-cache)才能计算一个 Token。 !!! tip @@ -205,21 +205,21 @@ V1 调度器可以在同一步中混合处理两类请求,这得益于更智 之后,它处理来自 `waiting` 队列的预填充请求: -1. 获取已计算块的数量(如果禁用前缀缓存则返回 0)。 +1. 获取已计算 block 的数量(如果禁用前缀缓存则返回 0)。 2. 调用 KV-cache 管理器的 `allocate_slots` 函数。 3. 将请求从 waiting 弹出并移动到 running,设置状态为 `RUNNING`。 4. 更新 Token 预算。 接下来看看 `allocate_slots` 的工作: -1. **计算块数** - 确定需要分配多少新的 KV-cache 块(`n`)。每块默认存储 16 个 Token。例如,一个预填充请求有 17 个新 Token,则需要 `ceil(17/16) = 2` 块。 -2. **检查可用性** - 如果管理器的池中没有足够的块,则提前退出。根据请求类型(解码或预填充),引擎可能尝试重新计算抢占(V0 支持交换抢占),通过调用 `kv_cache_manager.free` 将低优先级请求的 KV 块释放回块池,或者跳过调度继续执行。 -3. **分配块** - 通过 KV-cache 管理器的协调器,从块池(前文提到的 `free_block_queue` 双向链表)获取前 `n` 块。存入 `req_to_blocks` 字典,将每个 `request_id` 映射到其 KV-cache 块列表。 +1. **计算 block 数** - 确定需要分配多少新的 KV-cache block(`n`)。每 block 默认存储 16 个 Token。例如,一个预填充请求有 17 个新 Token,则需要 `ceil(17/16) = 2` 个 block。 +2. **检查可用性** - 如果管理器的池中没有足够的 block,则提前退出。根据请求类型(解码或预填充),引擎可能尝试重新计算抢占(V0 支持交换抢占),通过调用 `kv_cache_manager.free` 将低优先级请求的 KV block 释放回 block 池,或者跳过调度继续执行。 +3. **分配 block** - 通过 KV-cache 管理器的协调器,从 block 池(前文提到的 `free_block_queue` 双向链表)获取前 `n` 个 block。存入 `req_to_blocks` 字典,将每个 `request_id` 映射到其 KV-cache block 列表。 - +