4、模型加载与推理基础:使用vLLM加载模型并执行文本生成

4.1 核心原理:从模型权重到可服务状态

在深入使用 vLLM 之前,需要先理解其加载模型与执行推理的底层逻辑。vLLM 并非简单地将 Hugging Face Transformers 的模型包装一层 API,而是通过 PagedAttention 和 Continuous Batching 机制,对 KV Cache 进行块级管理,从而在推理过程中实现高吞吐与低显存浪费。

当调用 LLM 类或启动 OpenAI 兼容服务器 时,vLLM 会经历以下关键阶段:

  • 权重加载:从本地磁盘或 Hugging Face Hub 读取模型权重,支持 safetensors 格式,并按张量并行度(tensor_parallel_size)切分到多张 GPU。
  • 图编译与优化:将模型的计算图转换为 CUDA Graph,减少内核启动开销,并预分配 KV Cache 的显存池。
  • 请求调度器:维护一个等待队列和运行队列,采用连续批处理策略,每步迭代动态决定哪些序列继续生成、哪些序列被抢占或终止。
  • 采样与输出:根据 temperature、top_p 等参数从 logits 中采样,逐步生成 token,直到遇到 EOS 或达到 max_tokens 限制。

下图展示了 vLLM 从加载到生成的核心数据流与状态管理逻辑:

模型权重加载 safetensors / bin tensor_parallel 切分 CUDA Graph 编译 算子融合 & 内核优化 KV Cache 显存池预分配 调度器初始化 等待队列 / 运行队列 连续批处理策略 vLLM 推理引擎 PagedAttention 块管理 KV Cache 动态映射 迭代级调度 投机采样 / 并行采样 每步迭代动态选择序列 文本生成输出 采样参数:temperature top_p / top_k / repetition 流式输出 / 非流式 EOS 停止 / max_tokens 返回 prompt + generated_text 用户请求 prompt + 采样参数 新请求进入调度队列

理解这张图的关键在于:vLLM 将显存管理从“静态预留”变为“动态分页”,因此多个请求可以共享同一块 GPU 显存,而不会因为某个序列的 KV Cache 增长导致其他序列被阻塞。

4.2 方法流程:离线批量推理与在线服务

vLLM 提供两种使用方式:离线批量推理(适合脚本、测试、数据处理)和 在线 OpenAI 兼容服务(适合生产环境 API 部署)。下面分别介绍其标准流程。

4.2.1 离线批量推理

使用 LLM 类是最直接的方式。核心代码如下:

from vllm import LLM, SamplingParams

# 1. 初始化模型(加载权重 + 构建 KV Cache)
llm = LLM(
    model="Qwen/Qwen2.5-7B-Instruct",
    tensor_parallel_size=1,        # 单卡推理
    gpu_memory_utilization=0.85,   # 使用 85% 显存
    max_model_len=8192,            # 最大序列长度
    trust_remote_code=True
)

# 2. 配置采样参数
sampling_params = SamplingParams(
    temperature=0.7,
    top_p=0.9,
    max_tokens=512,
    stop=["</s>", "<|im_end|>"]
)

# 3. 构造输入(支持多轮对话模板)
prompts = [
    "请解释什么是PagedAttention?",
    "写一段Python代码实现快速排序。"
]

# 4. 执行推理
outputs = llm.generate(prompts, sampling_params)

# 5. 解析结果
for output in outputs:
    prompt = output.prompt
    generated_text = output.outputs[0].text
    print(f"Prompt: {prompt}\n生成: {generated_text}\n")

关键参数说明:

参数 作用 建议值
gpu_memory_utilization 控制 KV Cache 可用的显存比例 0.80 ~ 0.90(留出激活值空间)
max_model_len 限制输入+输出的最大 token 数 根据模型训练长度设置
tensor_parallel_size 多卡张量并行度 1(单卡)或 2/4(多卡)
dtype 权重精度 auto 或 bfloat16

4.2.2 在线 OpenAI 兼容服务

启动服务只需一行命令:

python -m vllm.entrypoints.openai.api_server \
    --model Qwen/Qwen2.5-7B-Instruct \
    --served-model-name qwen2.5-7b \
    --port 8000 \
    --gpu-memory-utilization 0.85 \
    --max-model-len 8192

然后通过 HTTP 请求调用:

curl http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "qwen2.5-7b",
    "messages": [
      {"role": "user", "content": "用一句话介绍vLLM"}
    ],
    "temperature": 0.3,
    "max_tokens": 128
  }'

返回的 JSON 中包含 choices[0].message.content 即为生成文本。在线模式的优势在于:支持流式输出(stream=True)、多用户并发、以及自动的请求排队与批处理。

4.3 案例实战:加载 Qwen2.5 并完成多轮对话

下面演示一个完整的案例:使用 vLLM 加载 Qwen2.5-7B-Instruct,并模拟多轮对话场景。该案例展示了如何结合 tokenizer.apply_chat_template 构造符合模型要求的输入格式。

from vllm import LLM, SamplingParams
from transformers import AutoTokenizer

# 加载 tokenizer(用于对话模板)
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-7B-Instruct")

# 初始化 vLLM
llm = LLM(model="Qwen/Qwen2.5-7B-Instruct", gpu_memory_utilization=0.85)

# 构造多轮对话
messages = [
    {"role": "system", "content": "你是一个乐于助人的AI助手。"},
    {"role": "user", "content": "请解释什么是KV Cache?"},
    {"role": "assistant", "content": "KV Cache是Transformer推理时缓存Key和Value矩阵的技术,用于避免重复计算。"},
    {"role": "user", "content": "那PagedAttention如何优化它?"}
]

# 应用对话模板
prompt = tokenizer.apply_chat_template(
    messages,
    tokenize=False,
    add_generation_prompt=True
)

# 生成回复
sampling_params = SamplingParams(temperature=0.7, top_p=0.9, max_tokens=256)
outputs = llm.generate([prompt], sampling_params)

print(outputs[0].outputs[0].text)

运行结果示例:

PagedAttention将KV Cache划分为固定大小的块(block),
通过块表(block table)动态映射逻辑位置到物理显存地址。
这样不同序列可以共享显存块,减少碎片化浪费,
同时支持高效的并行采样和抢占调度。
实践建议:如果显存不足,优先降低 gpu_memory_utilization 至 0.7,而不是减小 max_model_len。因为 KV Cache 是动态分配的,降低利用率会减少缓存容量,但能保证模型正常加载。

4.4 常见误区与复盘

在实际使用中,以下问题出现频率最高,需要特别注意:

  • 误区一:忽略对话模板。直接拼接用户输入而不使用 apply_chat_template,会导致模型生成质量下降,甚至产生格式混乱。vLLM 的 LLM 类不会自动应用对话模板,必须手动处理。
  • 误区二:max_tokens 设置过大。当 max_tokens 加上输入长度接近 max_model_len 时,vLLM 会直接报错或截断。建议预留 10% 的 token 余量。
  • 误区三:多卡推理时忘记设置 tensor_parallel_size。如果使用多张 GPU 但未指定并行度,vLLM 默认只使用单卡,导致显存溢出。
  • 误区四:混淆 gpu_memory_utilization 与 max_model_len。前者控制显存分配比例,后者控制序列长度上限。两者独立,但共同影响可并发处理的请求数。
排障提示:如果遇到 CUDA out of memory,首先检查 nvidia-smi 确认显存占用。若 vLLM 已占用 85% 仍溢出,说明模型权重或激活值超出预期,可尝试:
① 降低 gpu_memory_utilization 至 0.7;
② 使用 --dtype float16 强制半精度;
③ 启用 --enforce-eager 关闭 CUDA Graph(牺牲部分性能)。

复盘一个典型故障:某次部署中,用户反馈并发请求时吞吐骤降。排查发现 max_num_seqs(默认 256)设置过高,导致调度器频繁抢占。将 max_num_seqs 调整为 64 后,吞吐恢复稳定。这说明 vLLM 的性能调优需要结合具体负载特征,并非参数越大越好。