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

- 姓名
- 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-V3 或 Claude 3.5 Sonnet 这样的大型模型时,显存效率直接决定了成本。通过 n1n.ai 平台,开发者可以利用这些经过深度优化的后端,而无需亲自处理复杂的 GPU 调度逻辑。
vLLM 架构概览
在深入代码之前,我们需要理解 vLLM 的三层架构:
- 用户面向层 (User-Facing Layer):包含
LLM类、OpenAI 兼容的 API 服务器以及 gRPC 接口。 - 引擎层 (Engine Layer):负责输入处理(分词)、将数据传递给核心引擎并格式化输出。
- 引擎核心 (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, ...])。这在处理复杂任务流时非常有用。
总结
- PagedAttention 通过块状显存管理彻底解决了 KV 缓存浪费问题。
- LLM 类 是 vLLM 的“门面”,它隐藏了底层复杂的调度逻辑。
- 连续批处理 极大地提升了 GPU 的吞吐能力,是 vLLM 性能领先的关键。
想要在生产环境中体验极致的推理速度吗?欢迎访问 n1n.ai 获取高性能 API 支持。
Get a free API key at n1n.ai