4. vLLM核心概念
核心原理:LLMEngine 如何驱动一次推理
vLLM 的运行时核心是 LLMEngine,它并不直接暴露给上层 API 用户,而是作为调度、显存管理、推理执行的“总控中枢”。一次完整的请求生命周期可以概括为:客户端提交请求 → Engine 接收并解析 → 调度器分配序列 → 连续批处理组织 级计算 → 采样器按参数生成输出 → 返回结果。理解 LLMEngine 的关键在于:它把“一个请求”拆解为“多个 生成步骤”,并在每一步动态调整批内序列。
LLMEngine 内部维护了一个调度状态机。每个请求被转换为一个或多个 Sequence(序列),每个序列拥有独立的 流和 KV cache 空间。调度器在每个迭代步(iteration)决定哪些序列参与计算,而 BlockManager 负责为这些序列分配或释放物理 KV block。这种设计使得 vLLM 能够实现迭代级调度,而非传统的请求级调度。
SamplingParams:控制生成行为的参数集合
SamplingParams 是 vLLM 中定义生成策略的核心数据结构。它不参与模型计算,却直接决定输出的多样性、确定性和长度。以下是最常用的参数及其作用:
| 参数 | 类型 | 作用说明 | 典型值 |
|---|---|---|---|
temperature |
float | 控制概率分布的锐度。越低越确定,越高越随机 | 0.1 ~ 1.0 |
top_p |
float | 核采样:累积概率超过该值的 被截断 | 0.9 |
top_k |
int | 仅保留概率最高的 k 个 参与采样 | 50 |
max_tokens |
int | 生成的最大 数(含 prompt) | 512 / 1024 |
stop |
list[str] | 遇到这些字符串时停止生成 | ["\n\n", "END"] |
presence_penalty |
float | 对已出现 施加惩罚,鼓励话题多样性 | 0.0 ~ 1.0 |
frequency_penalty |
float | 按 出现频率成比例惩罚,减少重复 | 0.0 ~ 1.0 |
在代码中,SamplingParams 通常与请求一同提交:
from vllm import SamplingParams
params = SamplingParams(
temperature=0.7,
top_p=0.9,
max_tokens=256,
stop=["</s>", "\n\n"],
presence_penalty=0.2,
frequency_penalty=0.1
)
outputs = llm.generate(["请解释什么是连续批处理"], params)
print(outputs[0].outputs[0].text)
temperature 设为 0 或极低值(如 0.01),此时 vLLM 会走贪心解码路径,避免采样开销;在需要多样性的场景(如对话、创意写作)再调高 temperature 并配合 top_p 使用。
请求调度:从排队到执行的决策逻辑
vLLM 的调度器采用优先级 + 连续批处理策略。每个序列有状态:WAITING(等待 prefill)、RUNNING(正在 decode)、SWAPPED(被抢占,KV 已换出到 CPU)。调度器在每个迭代步执行以下逻辑:
- 检查 running 序列:是否已完成(达到 max_tokens 或命中 stop);若完成则释放其 KV block。
- 检查 waiting 队列:若当前显存有足够空闲 block,则将 waiting 序列加入本轮 prefill。
- 抢占决策:若新序列优先级更高但显存不足,则对低优先级 running 序列执行抢占(swap 或 abort)。
- 组织 batch:将本轮所有参与计算的序列(prefill + decode)打包成一个 batch,送入模型执行。
这种调度方式的关键优势在于:prefill 和 decode 可以在同一个 batch 中混合执行。传统框架中,prefill 阶段计算量巨大而 decode 阶段每次只生成一个 ,若将两者分开处理,GPU 会在 decode 阶段严重利用率不足。vLLM 通过 PagedAttention 将 KV cache 分块管理,使得不同序列可以共享同一个 batch 的不同 block,从而让 GPU 始终处理“足够多”的 。
连续批处理:吞吐提升的核心机制
连续批处理(Continuous Batching)也被称为迭代级批处理(iteration-level batching)。它与传统静态批处理的本质区别在于:
- 静态批处理:一批请求必须全部完成后才能开始下一批。若某个请求生成了 1000 个 ,而其他请求只生成 50 个,GPU 必须等待最慢的请求,造成大量计算间隙。
- 连续批处理:每个 生成步骤结束后,立即检查是否有请求完成、是否有新请求等待。完成的请求退出,新请求加入,batch 的组成在每个 step 都可能变化。
这种机制带来的直接收益是:GPU 的利用率被显著拉平。在真实负载下,vLLM 相比传统静态批处理可实现 2~4 倍的吞吐提升,且请求的 P99 延迟更稳定。以下是一个简化的对比实验:
静态批处理吞吐:约 850 tokens/s
vLLM 连续批处理吞吐:约 2100 tokens/s
提升幅度:2.47 倍
案例:通过 Engine 核心参数优化吞吐
在实际部署中,我们可以通过调整 LLMEngine 的初始化参数来进一步优化吞吐。以下是一个典型的服务端配置示例:
from vllm import LLM, SamplingParams
llm = LLM(
model="meta-llama/Llama-3-8B-Instruct",
tensor_parallel_size=2, # 使用 2 张 GPU
gpu_memory_utilization=0.90, # 允许使用 90% 显存作为 KV cache
max_num_seqs=256, # 最大并发序列数
max_num_batched_tokens=8192, # 每个 batch 最大 数
enable_prefix_caching=True, # 启用前缀缓存
swap_space=4, # CPU 换出空间(GB)
)
# 模拟混合负载:短请求 + 长请求
short_params = SamplingParams(temperature=0.1, max_tokens=64)
long_params = SamplingParams(temperature=0.8, max_tokens=512)
prompts = ["什么是注意力机制?"] * 100 + ["请详细解释 Transformer 架构"] * 20
outputs = llm.generate(prompts, [short_params if len(p) < 20 else long_params for p in prompts])
在这个案例中,max_num_seqs 控制调度器最多同时跟踪的序列数,max_num_batched_tokens 则限制每个迭代步送入 GPU 的 总量。合理设置这两个参数可以避免显存溢出,同时保证 batch 足够大以充分利用 GPU 算力。
误区与复盘:常见配置陷阱
当请求的 max_tokens 远大于实际输出长度时,调度器会为每个序列预留完整的 KV cache 空间,导致可并发序列数下降。建议根据业务分布设置合理的 max_tokens,或使用 vLLM 的
--max-model-len 配合请求级 max_tokens 动态控制。
虽然更高的显存利用率意味着更大的 KV cache,但若设置超过 0.95,可能导致模型权重加载失败或 CUDA OOM。建议从 0.85 开始逐步调优,并监控实际显存峰值。
当 temperature=0 时,vLLM 会使用贪心解码,无需进行随机采样,可减少约 5%~10% 的延迟。若业务允许,优先使用贪心解码;若必须使用随机采样,建议将 top_k 限制在 50 以内以减少候选 的排序开销。
复盘一个真实案例:某团队部署 7B 模型时,将 max_num_seqs 设为 512,但实际显存只能容纳约 200 个序列的 KV cache。调度器频繁触发抢占,导致大量序列被 swap 到 CPU,性能反而下降 40%。将 max_num_seqs 调整为 192 后,吞吐恢复并超出预期。这说明调度参数必须与显存容量匹配,而非越大越好。
理解 LLMEngine 的调度机制和连续批处理原理,是优化 vLLM 推理性能的基础。在实际调优时,建议先通过 /metrics 端点观察 vllm:num_requests_running 和 vllm:num_requests_waiting 指标,若 waiting 长期大于 0,说明并发上限设置过低;若 running 长期接近上限且 GPU 利用率不足,则需检查 batch 数是否过小。通过监控指标与调度参数的配合调整,才能找到最优工作点。