--- title: vllm学习笔记(壹) date: 2025-09-06 23:00:00 # 发布日期和时间,格式:YYYY-MM-DD HH:MM:SS descriptionHTML: '学习笔记:ray介绍,vllm的作用和主要运行方式' tags: # 文章标签列表,用于分类和搜索 - AI - 笔记 sidebar: true # 是否显示侧边栏:true显示,false隐藏 readingTime: true # 是否显示阅读时间:true显示,false隐藏 sticky: 0 # 精选文章设置:值越大在首页展示越靠前,0表示不精选 recommend: true publish: false --- # vLLM学习笔记(壹) > **版本说明**: 本文基于 vLLM 0.2.7 版本进行分析 > **源码位置**: 相关代码主要位于 `vllm/engine/` 和 `vllm/worker/` 目录下 ## Ray框架介绍 ### Ray是什么 Ray是一个开源的分布式计算框架,专门为机器学习和AI工作负载设计。它提供了简单的API来构建和运行分布式应用程序。 ### Ray中的有状态和无状态 #### 无状态服务 - **特点**: 每次请求都是独立的,不依赖之前的状态 - **示例**: HTTP API服务,每个请求处理完就结束 #### 有状态(Worker)服务 - **特点**: 需要维护内部状态,请求之间有依赖关系 - **优势**: 可以缓存数据,避免重复计算 - **示例**: 数据库连接池,模型推理服务(需要保持模型在内存中) 在vLLM中,Worker节点就是典型的**有状态服务**,因为它们需要: - 在内存中保持加载的模型 - 维护KV Cache状态 - 跟踪正在处理的请求状态 ## vLLM的作用与价值 ### 主要作用 1. 专门优化大语言模型的推理速度 2. 通过PagedAttention技术显著减少内存占用 3. 支持动态批处理,提高GPU利用率 4. 支持多GPU和多节点部署 ### 核心优势 - **PagedAttention**: 将注意力机制的KV Cache分页管理,类似操作系统的虚拟内存 - **连续批处理**: 动态调整批大小,无需等待整个批次完成 - **零拷贝**: 减少不必要的数据复制操作 ### KV Cache显存分配机制 在传统的Transformer推理中,KV Cache需要预先分配连续的显存空间,这会造成大量的内存浪费。vLLM通过PagedAttention技术,将KV Cache分割成固定大小的block块进行管理。 #### Block分配原理 #### ```mermaid graph TB subgraph "GPU显存空间" subgraph "KV Cache Pool" B1["Block 1
16 tokens
状态: 已分配"] B2["Block 2
16 tokens
状态: 已分配"] B3["Block 3
16 tokens
状态: 空闲"] B4["Block 4
16 tokens
状态: 空闲"] B5["Block 5
16 tokens
状态: 已分配"] B6["Block 6
16 tokens
状态: 空闲"] end subgraph "模型权重" MW["Model Weights
固定占用"] end end subgraph "请求管理" R1["Request 1
Seq ID: 001
长度: 25 tokens"] R2["Request 2
Seq ID: 002
长度: 18 tokens"] end subgraph "Block映射表" BT["Block Table
Seq 001: [Block1, Block2]
Seq 002: [Block5]
Free: [Block3, Block4, Block6]"] end R1 --> B1 R1 --> B2 R2 --> B5 BT --> B1 BT --> B2 BT --> B5 style B1 fill:#ff9999,stroke:#333,stroke-width:2px,color:#000 style B2 fill:#ff9999,stroke:#333,stroke-width:2px,color:#000 style B5 fill:#ff9999,stroke:#333,stroke-width:2px,color:#000 style B3 fill:#99ff99,stroke:#333,stroke-width:2px,color:#000 style B4 fill:#99ff99,stroke:#333,stroke-width:2px,color:#000 style B6 fill:#99ff99,stroke:#333,stroke-width:2px,color:#000 style MW fill:#cce5ff,stroke:#333,stroke-width:2px,color:#000 style R1 fill:#fff2cc,stroke:#333,stroke-width:2px,color:#000 style R2 fill:#fff2cc,stroke:#333,stroke-width:2px,color:#000 style BT fill:#f0f0f0,stroke:#333,stroke-width:2px,color:#000 ``` #### 关键特性说明 1. **固定Block大小**: 每个Block通常包含16个token的KV Cache 2. **动态分配**: 根据序列长度动态分配所需的Block数量 3. **非连续存储**: Block在物理内存中可以不连续,通过映射表管理 4. **高效回收**: 请求完成后立即回收Block,供其他请求使用 #### 内存利用率对比 | 方案 | 内存预分配 | 实际使用率 | 浪费率 | |------|------------|------------|--------| | 传统方案 | 最大序列长度 | 20-30% | 70-80% | | PagedAttention | 按需分配 | 90-95% | 5-10% | #### Block管理的具体流程 ```python # 位置: vllm/core/block_manager.py class BlockManager: def allocate_blocks(self, sequence_length): """为序列分配所需的Block""" blocks_needed = math.ceil(sequence_length / self.block_size) allocated_blocks = [] for _ in range(blocks_needed): if self.free_blocks: block = self.free_blocks.pop() allocated_blocks.append(block) else: # 内存不足,触发抢占机制 self.preempt_sequences() return allocated_blocks def free_blocks(self, sequence_id): """释放序列占用的Block""" blocks = self.sequence_to_blocks[sequence_id] self.free_blocks.extend(blocks) del self.sequence_to_blocks[sequence_id] ``` 这种设计的优势: - **内存碎片化最小**: 固定大小的Block避免了内存碎片 - **动态扩展**: 序列可以根据需要动态申请更多Block - **共享机制**: 多个序列可以共享相同的prefix Block(如系统提示词) ## vLLM运作方式详解 ### 整体架构流程 ``` 用户请求 → LLMEngine → Scheduler → Workers → GPU推理 → 结果返回 ``` ### 详细运作步骤 1. `LLMEngine`接收用户的文本生成请求 2. `Scheduler`决定哪些请求可以被处理 3. 为请求分配GPU内存和计算资源 4. 多个`Worker`并行执行推理任务 5. 收集各Worker的输出并返回给用户 ## 调度器(Scheduler)详解 ### 调度器的核心职责 调度器是vLLM的"大脑",主要负责: #### 1. 请求管理 ```python # 位置: vllm/engine/llm_engine.py class LLMEngine: def __init__(self): self.scheduler = Scheduler(...) ``` #### 2. 资源调度策略 1. 跟踪可用的GPU内存 2. 决定哪些请求可以组成一个批次 3. 根据请求的优先级和到达时间排序 ### 调度算法核心逻辑 ```python # 简化的调度逻辑示例 def schedule_requests(self): # 1. 检查可用资源 available_memory = self.get_available_memory() # 2. 选择可执行的请求 executable_requests = [] for request in self.waiting_requests: if self.can_allocate(request, available_memory): executable_requests.append(request) # 3. 返回调度结果 return executable_requests ``` ## Worker的作用与机制 ### Worker的核心功能 Worker是vLLM的"执行者",每个Worker负责: #### 1. 模型加载与管理 ```python # 位置: vllm/worker/worker.py class Worker: def __init__(self): self.model_runner = ModelRunner(...) self.cache_engine = CacheEngine(...) ``` #### 2. 推理执行 - **前向传播**: 执行模型的前向计算 - **KV缓存管理**: 管理注意力机制的键值缓存 - **内存分配**: 为每个请求分配必要的内存空间 #### 3. 状态维护 - **请求状态跟踪**: 记录每个请求的处理进度 - **缓存状态管理**: 维护PagedAttention的页面状态 - **错误处理**: 处理推理过程中的异常情况 ### Worker的工作流程 1. **初始化**: 加载模型权重,初始化缓存引擎 2. **接收任务**: 从调度器接收批处理任务 3. **执行推理**: 并行处理批次中的所有请求 4. **返回结果**: 将推理结果返回给引擎 ## 关键代码文件位置 ### 主要源码文件结构 ``` vllm/ ├── engine/ │ ├── llm_engine.py # 主引擎,协调整个推理流程 │ └── async_llm_engine.py # 异步版本的引擎 ├── core/ │ ├── scheduler.py # 调度器核心逻辑 │ └── block_manager.py # 内存块管理器 ├── worker/ │ ├── worker.py # Worker基类实现 │ └── model_runner.py # 模型运行器 └── attention/ └── backends/ # PagedAttention实现 ``` ### 重要文件说明 - **`vllm/engine/llm_engine.py`**: 整个系统的入口点和协调中心 - **`vllm/core/scheduler.py`**: 实现了复杂的请求调度算法 - **`vllm/worker/worker.py`**: Worker的具体实现逻辑 - **`vllm/core/block_manager.py`**: PagedAttention的内存管理实现 ## 总结 vLLM通过Ray框架实现分布式推理,采用有状态的Worker设计来保持模型和缓存状态。其核心创新在于PagedAttention技术和智能调度系统,大幅提升了大语言模型的推理效率和资源利用率。