2、vLLM基础:vLLM简介、安装配置、基本推理流程

2.1 vLLM是什么:从推理痛点说起

在大模型服务化部署中,吞吐量低与显存浪费是两大核心痛点。传统推理框架(如简单的Transformers pipeline)在服务多用户并发请求时,每个请求独立申请KV Cache(键值缓存),导致显存碎片化严重,且无法有效复用已计算的历史状态。vLLM(Very Large Language Model Serving Engine)正是为解决这些问题而生的高性能推理引擎,由加州大学伯克利分校开发,核心创新在于PagedAttention技术。

PagedAttention借鉴操作系统虚拟内存的分页思想,将KV Cache划分为固定大小的块(Block),每个块可以存储若干个Token的键值向量。这些块在显存中不必连续存放,而是通过块表(Block Table)进行映射。这一设计带来了三个直接收益:

  • 显存利用率接近100%:不再需要预留连续大块显存,碎片化空间可被任意请求的任意块使用。
  • 高效共享:多个序列(如并行采样、Beam Search)可以共享相同的KV块,减少重复计算。
  • 动态增长:KV Cache按需分配,请求结束后立即释放,支持更长上下文。
核心指标速览:vLLM在相同硬件条件下,相比HuggingFace Transformers的Naive实现,吞吐量通常可提升2~4倍,在长序列场景下优势更明显。其吞吐量以Tokens/s(每秒生成的Token数)衡量,而非仅看单请求延迟。
vLLM 核心架构与推理流程 传统方式:连续显存分配 请求A:连续KV Cache(预留大量空白) 请求B:连续KV Cache(碎片无法利用) 显存碎片区(浪费) PagedAttention:分页块管理 块1 块2 块3 块4 块5 块6 块表(Block Table) 请求A: 块1 → 块3 → 块6(逻辑连续,物理离散) 请求B: 块2 → 块4(共享块1? 否,独立) 优化 vLLM 基本推理流程(离线批量推理) 1. 初始化引擎 LLM(model, tensor_parallel_size) 2. 准备输入 prompts 列表 / tokenize 3. 批量调度 Continuous Batching 4. 并行解码 PagedAttention 管理KV 5. 输出收集 outputs 列表 / decode 关键点:vLLM 将“显存管理”与“调度策略”解耦,调度器(Scheduler)决定每步迭代执行哪些序列, 而 PagedAttention 负责高效读写KV块,两者配合实现高吞吐。

2.2 安装配置:三步完成环境准备

vLLM的安装非常直接,推荐使用Python 3.9~3.12环境,并优先使用CUDA 11.8或12.1+的GPU驱动。以下以最常用的pip方式为例:

# 1. 创建虚拟环境(推荐)
conda create -n vllm_env python=3.11 -y
conda activate vllm_env

# 2. 安装vLLM(自动拉取匹配的torch、transformers等依赖)
pip install vllm

# 3. 验证安装
python -c "from vllm import LLM; print('vLLM版本:', LLM.__module__)"
硬件要求:vLLM需要NVIDIA GPU且显存不低于8GB(运行7B模型量化版)。若使用多卡,可通过tensor_parallel_size参数指定并行度。CPU推理目前支持有限,不建议生产环境使用。

安装完成后,建议先运行一个最小化冒烟测试,确认框架能正确加载模型并完成一次推理:

from vllm import LLM, SamplingParams

# 初始化引擎(首次运行会下载模型权重)
llm = LLM(model="facebook/opt-125m", dtype="float16")

# 构造采样参数
sampling_params = SamplingParams(temperature=0.8, top_p=0.95, max_tokens=64)

# 执行推理
outputs = llm.generate(["Hello, my name is"], sampling_params)
print(outputs[0].outputs[0].text)

如果上述代码能正常输出一段文本,说明vLLM已正确安装并能够调用GPU资源。常见的安装问题包括:CUDA版本不匹配、显存不足(OOM)、模型下载超时等,可参考下表排查:

现象 可能原因 解决方案
ImportError: libcuda.so.1 CUDA驱动未安装或路径未配置 安装NVIDIA驱动,并设置LD_LIBRARY_PATH
RuntimeError: CUDA out of memory 模型过大或批量参数过高 降低max_num_seqs或使用dtype="float16"
ValueError: The model's max seq len 模型上下文长度限制 设置max_model_len参数(如4096)

2.3 基本推理流程:从单请求到批量

vLLM的推理流程可以抽象为“初始化 → 调度 → 执行 → 输出”四个阶段。理解这一流程,是后续调优max_num_seqs、max_num_batched_tokens等参数的基础。

  1. 初始化阶段:创建LLM实例时,vLLM会加载模型权重、构建KV Cache池(根据gpu_memory_utilization参数预留显存)、初始化调度器。
  2. 调度阶段:当调用generate()传入多个prompts时,调度器将请求拆分为序列(Sequence),并决定每步迭代(Iteration)执行哪些序列。默认采用连续批处理(Continuous Batching)策略——某个序列生成结束后,立即插入新序列,而非等待整个批次完成。
  3. 执行阶段:每步迭代中,vLLM对当前批次的所有序列执行一次前向传播(Forward Pass),生成下一个Token的概率分布,并通过采样参数(temperature、top_p等)选择具体Token。PagedAttention在此阶段负责读写KV块。
  4. 输出阶段:当所有序列达到max_tokens或生成结束符(EOS)时,vLLM将Token序列解码为文本,返回RequestOutput对象列表。
关键理解:vLLM的generate()是同步阻塞的,即调用后等待所有结果返回。若需异步流式输出,应使用AsyncLLMEngine(后续章节详述)。批处理大小并非由用户直接指定,而是由调度器根据当前显存余量、max_num_seqs上限和max_num_batched_tokens上限动态决定。

2.4 案例:对比单请求与批量请求的吞吐差异

为了直观感受vLLM的批处理优势,我们用一个简单实验对比串行处理与批量处理的耗时。假设有8条不同的提示词,每条期望生成128个Token:

import time
from vllm import LLM, SamplingParams

llm = LLM(model="meta-llama/Llama-2-7b-chat-hf", dtype="float16")

prompts = [
    "Explain quantum computing in simple terms.",
    "Write a Python function to check if a number is prime.",
    # ... 共8条不同提示词
] * 8  # 共64条

sampling_params = SamplingParams(temperature=0.7, max_tokens=128)

# 方式1:串行(每次只发1条)
start = time.time()
for p in prompts[:8]:
    llm.generate([p], sampling_params)
serial_time = time.time() - start

# 方式2:批量(一次发64条)
start = time.time()
llm.generate(prompts, sampling_params)
batch_time = time.time() - start

print(f"串行8条耗时: {serial_time:.2f}s")
print(f"批量64条耗时: {batch_time:.2f}s")
print(f"吞吐提升: {64 / batch_time / (8 / serial_time):.1f}x")

实际运行中,批量64条的耗时通常仅比串行8条多30%~60%,但处理的请求数却是8倍,因此整体吞吐量提升显著。这正是max_num_seqs参数存在的意义——允许调度器一次性容纳更多序列,充分利用GPU并行计算能力。

2.5 常见误区与复盘

误区1:认为max_num_seqs越大越好。虽然增大该值可提升吞吐,但会占用更多KV Cache显存。若设置过大,可能导致部分请求因显存不足而被拒绝(表现为ValueError: Requested max_num_seqs ... exceeds available)。正确做法是结合gpu_memory_utilization(默认0.9)预留的KV Cache总量,估算可容纳的最大序列数。
误区2:忽略max_model_len对KV Cache的影响。vLLM在初始化时按max_model_len为每个序列预留最大KV空间。若该值设置过大(如32K),即使实际请求很短,也会导致KV Cache池被“虚占”,降低有效并发数。建议根据业务实际最长上下文设置,而非直接使用模型支持的最大值。
误区3:混淆“吞吐量”与“单请求延迟”。vLLM优化目标是提升整体吞吐(Tokens/s),而非降低单个请求的首Token延迟(TTFT)。在批处理场景下,单个请求的响应时间可能略有增加,但系统整体服务能力大幅提升。调优时应明确业务优先指标:若追求低延迟,应减小max_num_seqs;若追求高吞吐,则应适当增大。