深入浅出 vLLM:User API 详解与 PagedAttention 原理

作者
  • avatar
    姓名
    Nino
    职业
    Senior Tech Editor

大语言模型(LLM)的推理过程本质上是受 GPU 显存限制(Memory-bound)而非计算能力限制。当模型生成文本时,它需要为每个处于活动状态的请求存储键值缓存(KV Cache),即注意力机制产生的中间计算结果。传统的推理实现通常会为每个请求预分配最大可能的序列长度,这导致了巨大的资源浪费——通常有 60-80% 的 GPU 显存在“空转”。vLLM 的出现彻底改变了这一现状。

在本次教程中,我们将深入研究 vLLM 架构的第一层:User API。对于寻求稳定、高速 LLM API 的开发者和企业,n1n.ai 提供了卓越的聚合服务,让您能够轻松调用经过此类技术优化的顶级模型。

PagedAttention:显存管理的革命

vLLM 的核心创新在于 PagedAttention。它不再为每个请求分配巨大的连续缓冲区,而是将 GPU 显存划分为固定大小的块(默认每块 16 个 token)。这些块根据需要动态分配,类似于操作系统利用“页”来管理虚拟内存。这种机制实现了近乎完美的显存利用率,并使吞吐量比 HuggingFace Transformers 提升了 2 到 4 倍。

在部署如 DeepSeek-V3Claude 3.5 Sonnet 这样的大型模型时,显存效率直接决定了成本。通过 n1n.ai 平台,开发者可以利用这些经过深度优化的后端,而无需亲自处理复杂的 GPU 调度逻辑。

vLLM 架构概览

在深入代码之前,我们需要理解 vLLM 的三层架构:

  1. 用户面向层 (User-Facing Layer):包含 LLM 类、OpenAI 兼容的 API 服务器以及 gRPC 接口。
  2. 引擎层 (Engine Layer):负责输入处理(分词)、将数据传递给核心引擎并格式化输出。
  3. 引擎核心 (Engine Core):这是“大脑”所在,包含调度器(Scheduler)、执行器(Executor)和 KV 缓存管理器(BlockPool)。

今天我们的重点是第一层:vllm/entrypoints/llm.py 中的 LLM 类。

LLM 类的深度解析

LLM 类是进行离线批量推理的主要接口。它的设计理念是作为一个轻量级包装器,将配置参数封装后交给 LLMEngine 处理。

# vllm/entrypoints/llm.py (简化版)
class LLM:
    def __init__(
        self,
        model: str,
        *,
        tensor_parallel_size: int = 1,
        gpu_memory_utilization: float = 0.9,
        **kwargs,
    ) -> None:
        # 将参数打包进 EngineArgs
        engine_args = EngineArgs(
            model=model,
            tensor_parallel_size=tensor_parallel_size,
            gpu_memory_utilization=gpu_memory_utilization,
            **kwargs,
        )

        # 初始化引擎
        self.llm_engine = LLMEngine.from_engine_args(engine_args)

关键参数:gpu_memory_utilization

该参数默认值为 0.9。这意味着 vLLM 会占用 90% 的 GPU 显存用于 KV 缓存,预留 10% 给 PyTorch 的模型权重和激活值。如果您在尝试运行 OpenAI o3 级别的复杂推理模型且遇到显存不足,可以尝试将其调低至 0.8。

generate 方法与连续批处理 (Continuous Batching)

generate() 是执行推理的核心入口。vLLM 弃用了传统的静态批处理,采用了 连续批处理 技术。

def generate(
    self,
    prompts: PromptType | Sequence[PromptType],
    sampling_params: SamplingParams | None = None,
) -> list[RequestOutput]:
    # 验证并添加请求到引擎队列
    self._validate_and_add_requests(prompts, sampling_params)
    # 运行引擎循环
    outputs = self._run_engine()
    # 按请求 ID 排序返回,确保顺序与输入一致
    return sorted(outputs, key=lambda x: int(x.request_id))

_run_engine() 内部,是一个简单的 while 循环,不断调用 self.llm_engine.step()。每一次 step() 都会执行一轮调度和推理。由于不同请求的生成长度不同,有些请求会提前完成,sorted 操作确保了返回结果的顺序性。

采样参数 (SamplingParams) 的科学

每个请求都携带一个 SamplingParams 对象,用于控制 token 的选择策略。值得注意的是,vLLM 使用 msgspec 而非 Python 原生的 dataclass 进行序列化,因为 msgspec 的速度快 10-50 倍。在 n1n.ai 这样需要处理高并发请求的场景下,这种微小的优化能显著降低系统延迟。

from vllm import SamplingParams

# 示例配置
params = SamplingParams(
    temperature=0.7,
    max_tokens=512,
    stop=["<|end of sentence|>"]
)

专家提示:温度为零的情况

temperature=0 时,vLLM 会强制进入贪婪搜索(Greedy Decoding)模式。此时,top_p 会被设为 1.0,top_k 被设为 0。这种自动规范化防止了用户设置冲突参数导致推理行为异常。

进阶功能:Chat、Embedding 与结构化输出

现代 LLM 应用不仅仅是文本生成。vLLM 已经扩展支持了多种任务:

  • Chat 接口:自动应用聊天模板(如 Llama 3.1 或 DeepSeek-V3 的模板)。
  • Embedding (嵌入):为 RAG (检索增强生成) 提供高性能的向量化支持。
  • 结构化输出:结合 JSON Schema 确保模型输出符合特定的业务逻辑格式。

这些功能使得 vLLM 成为构建 LangChain 智能体或企业级 AI 系统的首选后端。如果您希望快速体验这些能力,n1n.ai 提供的统一 API 接口是最佳起点。

实战 Q&A

Q1: 为什么 _run_engine() 结束后要对输出进行排序? 答:因为连续批处理是非阻塞的。一个短请求(如“你好”)可能在 5 次迭代内完成,而一个长请求(如“写一篇论文”)可能需要 500 次。排序确保了 outputs[i] 始终对应 prompts[i]

Q2: 如何在单次调用中为不同提示词设置不同的参数? 答:您可以向 generate 方法传递一个 SamplingParams 列表。例如:llm.generate(prompts, [params1, params2, ...])。这在处理复杂任务流时非常有用。

总结

  1. PagedAttention 通过块状显存管理彻底解决了 KV 缓存浪费问题。
  2. LLM 类 是 vLLM 的“门面”,它隐藏了底层复杂的调度逻辑。
  3. 连续批处理 极大地提升了 GPU 的吞吐能力,是 vLLM 性能领先的关键。

想要在生产环境中体验极致的推理速度吗?欢迎访问 n1n.ai 获取高性能 API 支持。

Get a free API key at n1n.ai