认识 vLLM
当你向 ChatGPT 提问时,背后发生了什么?vLLM 就是那个让大模型飞速回答的引擎。
你每天都在用,只是不知道它的名字
想象一下:你在手机上跟 AI 聊天,输入一句话,它几乎瞬间就开始回答。这段"瞬间"的背后,有一个叫做 vLLM 的系统在疯狂运转。
vLLM 是一个推理引擎——它的任务就是把训练好的大语言模型(比如 LLaMA、Qwen)部署到服务器上,让成千上万的用户可以同时和模型对话。
训练是让模型"上学"——喂给它海量文本,让它学会语言规律。这个过程可能花费数百万美元和几个月时间。推理是训练完成后,模型开始"工作"——回答用户的问题。vLLM 只负责推理这个环节,但这一环节的效率直接决定了用户体验和成本。
为什么不能直接用模型?
你可能会想:模型训练好了,直接让它回答不就行了?问题在于——大模型太大了,太慢了,太贵了。
模型巨大
一个 70B(700 亿参数)的模型需要约 140GB 显存。一张顶级显卡只有 80GB,放不下一个模型。
生成很慢
模型一次只生成一个 token,如果不做优化,每秒可能只吐出几个字。
GPU 极贵
一张 NVIDIA A100 显卡售价超过 10 万元人民币。如果每个用户独占一张卡,成本是天文数字。
让一张 GPU 同时服务尽可能多的用户,让每个用户的等待时间尽可能短。一句话:又快又省。
一个请求的旅程
当你在聊天框里输入"什么是量子计算?"并点击发送,数据经历了这样的旅程:
启动 vLLM 只需要一行命令
这就是 vLLM 如此受欢迎的原因之一——你不需要写复杂的代码,一条命令就能启动一个高性能推理服务:
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Llama-2-7b-chat-hf \
--tensor-parallel-size 2 \
--max-model-len 4096
启动 vLLM 的 API 服务器(兼容 OpenAI 的接口格式)…
加载 Meta 公司的 LLaMA-2 7B 对话模型…
使用 2 张 GPU 并行处理(把模型分到两张卡上)…
最多支持 4096 个 token 的上下文长度。
启动后,vLLM 就在 http://localhost:8000 上监听请求了。你完全可以用调用 OpenAI API 一样的方式来调用它。
检测你的理解
vLLM 的主要职责是什么?
为什么不能直接运行大模型,而需要 vLLM 这样的推理引擎?
核心架构
vLLM 内部有哪些"角色"?它们如何协作?认识系统的"演员表"。
vLLM 的演员表
一个好的系统就像一部电影——每个角色各司其职。vLLM 有五个核心角色:
前台接待员——接收用户请求,返回模型回答。兼容 OpenAI 的 API 格式。
交通指挥——决定哪些请求先上 GPU、哪些排队等候。它像一个精明的项目经理。
核心计算引擎——把模型的数学运算安排到 GPU 上执行。真正的"干活的人"。
记忆管家——管理模型的"草稿纸"空间,让多用户的草稿纸互不冲突。
聪明的工作安排——把长文本拆成小块,跟其他请求混在一起处理,提高 GPU 利用率。
全局架构一览
点击架构图中的任意组件,了解它的职责:
用户侧
vLLM 核心
硬件层
当请求来了——角色们的对话
让我们"偷听"一下 vLLM 内部角色们在处理一个请求时的对话:
代码中的调度器核心逻辑
调度器是 vLLM 最精妙的组件之一。以下是其核心调度逻辑的简化版本:
class Scheduler:
def schedule(self):
# 调度器每次循环调用这个方法
scheduler_outputs = []
for seq_group in self.waiting:
if self._check_memory(seq_group):
self._allocate(seq_group)
scheduler_outputs.append(
seq_group)
return scheduler_outputs
定义一个调度器类……
核心调度方法——每个循环周期调用一次……
(注释说明了调用时机)
准备一个空列表来收集本轮被批准的请求……
遍历所有正在排队等待的请求……
检查:GPU 显存够不够放下这个请求的 KV Cache?……
如果够,就为它分配 KV Cache 物理页……
把它加入本轮的执行列表……
返回所有获准进入 GPU 的请求列表。
架构理解测试
在 vLLM 中,谁负责决定一个请求何时可以被送上 GPU 处理?
KV Cache 管理器的主要职责是什么?
内存与性能优化
vLLM 最厉害的创新——PagedAttention,一个来自操作系统课本的灵感。
GPU 显存的碎片化噩梦
要理解 PagedAttention 为什么伟大,先得理解它解决了什么问题。
想象你经营一个停车场——但车的大小各不相同。有的只需要 2 个车位,有的需要 50 个。问题是:你不知道一辆车进来时会占多少车位(因为生成的文本长度不确定)。
传统做法是给每辆车预留最大可能的空间。结果?大部分车位空着,但已经"名花有主",别人用不了。
在 vLLM 出现之前,推理框架的显存利用率通常只有 20-40%。也就是说,一张 80GB 的 A100 显卡,实际可能只利用了 16-32GB,剩下的 48GB 被浪费在"预留但没用"的空间上。
PagedAttention:来自操作系统的灵感
这个问题的解法,计算机科学家在 1960 年代就找到了——虚拟内存和分页。
每个页可以存放固定数量(比如 16 个)token 的 KV 值。不再为每个请求预留一大块连续空间。
每个请求有一个页表,记录"我的第 1 页存在物理页 #7,第 2 页存在物理页 #23"。页不需要连续!
生成新 token 时才分配新页。请求结束后,所有页立刻回收,可以被其他请求复用。零浪费。
这个设计直接将显存利用率从 20-40% 提升到 接近 100%。同样一张 GPU,vLLM 可以服务 2-4 倍的用户。这就是 vLLM 能在 2023 年横空出世、迅速成为业界标准的原因。
PagedAttention 在代码中的实现
vLLM 的 KV Cache 管理器实现了一个类似操作系统内存管理器的页表系统:
class BlockSpaceManager:
def allocate(self, seq_group):
num_blocks = math.ceil(
seq_group.num_tokens / self.block_size)
for i in range(num_blocks):
block = self.free_blocks.pop()
self.block_tables[seq_id].append(
block)
return self.block_tables[seq_id]
块空间管理器——相当于操作系统的内存管理器……
分配方法——为一个请求分配所需的缓存块……
算一下需要多少个块:总 token 数 ÷ 每块的容量,向上取整……
(续上行的计算)
逐个分配需要的块……
从空闲块池里取出一个块……
记录到这个请求的页表里——"第 i 页指向物理块 block"……
(续上行的记录操作)
返回这个请求的完整页表。
连续批处理:让 GPU 永远不闲着
PagedAttention 解决了"显存不够用"的问题。但还有另一个问题:GPU 的计算能力没有被充分利用。
传统静态批处理
凑齐一批请求一起送 GPU。但 batch 里最长的请求完成之前,其他已经完成的请求还在等待,白白浪费 GPU 时间。
vLLM 连续批处理
每当一个请求生成完毕,立刻从等待队列拉一个新请求进来填补空位。GPU 的每一轮计算都是"满载"状态——没有空闲、没有等待。
连续批处理让 vLLM 的 吞吐量比传统方案提升 2-4 倍。结合 PagedAttention 的显存优化,vLLM 的整体效率可以达到传统方案的 4-10 倍。
优化策略理解测试
PagedAttention 的核心思想借鉴了哪个操作系统概念?
连续批处理(Continuous Batching)比静态批处理好在哪?
请求处理全流程
从你按下"发送"到 AI 开始回答,数据在 vLLM 内部经历了什么?完整追踪一次请求的一生。
推理的两个阶段
大模型的推理过程分为两个截然不同的阶段——就像读书和写文章是两种完全不同的脑力活动:
Prefill 阶段(预填充)
"读书"——把用户的 prompt(可能是一段很长的文本)一次性通过模型,计算出所有 token 的 KV Cache。这一步是计算密集型的,但只做一次。就像读完整篇材料后,知识已经记在脑子里了。
Decode 阶段(逐 token 生成)
"写文章"——基于已经算好的 KV Cache,每次生成一个新的 token。每生成一个 token 都要访问之前所有的 KV 值。这一步是访存密集型的,速度受限于 GPU 显存带宽。
因为它们对硬件的需求完全不同。Prefill 需要 GPU 的计算能力(大量的矩阵乘法),Decode 需要显存带宽(快速读取 KV Cache)。理解这一点,你就能更好地选择 GPU 型号和配置参数。
一个完整请求的数据流追踪
让我们追踪一个请求从进入到结束的完整旅程。点击"下一步"逐步观看:
Decode 循环的核心代码
在 vLLM 中,生成的核心是一个循环——每一步生成一个 token,直到满足停止条件:
while seq_group.is_finished() is not True:
scheduler_outputs = scheduler.schedule()
output = model_executor.execute_model(
scheduler_outputs)
for seq in output:
seq.append_token(seq.last_token)
if has_eos_token(output):
break
只要这个请求还没生成完毕,就一直循环……
调度器决定这一轮哪些请求可以进入 GPU(包含正在生成的和新进入的)……
把本轮所有请求打包交给 GPU 执行……
(续上行的 GPU 执行调用)
遍历 GPU 返回的结果,每个请求都有一个新 token……
把新 token 追加到这个请求的序列末尾……
如果生成了结束符(表示模型觉得回答完了)……
跳出循环,这个请求处理完毕。
模型太大怎么办?——张量并行
70B 参数的模型有 140GB,一张 A100 只有 80GB。vLLM 用张量并行来解决这个问题:
比如一个 4096×4096 的矩阵,用 4 张 GPU,每张只需存储 4096×1024——四分之一的大小。
输入数据复制到每张卡上,各卡并行做矩阵乘法。互不干扰,同时进行。
每层计算完后,GPU 之间通过高速通信(如 NVLink)交换结果,拼出完整输出。
请求流程理解测试
Prefill 阶段和 Decode 阶段的核心区别是什么?
如果模型太大放不进一张 GPU,vLLM 的张量并行是怎么解决的?
实战部署与调试
掌握了原理之后,如何在实际项目中部署 vLLM?遇到问题怎么排查?
生产环境的启动参数
前面我们看到过最简启动命令。在实际生产中,你需要更多参数来调优性能和资源使用:
python -m vllm.entrypoints.openai.api_server \
--model Qwen/Qwen2.5-72B-Instruct \
--tensor-parallel-size 4 \
--gpu-memory-utilization 0.90 \
--max-model-len 8192 \
--max-num-seqs 256 \
--enable-prefix-caching
启动 vLLM 的 OpenAI 兼容 API 服务器……
加载阿里通义千问 72B 对话模型……
用 4 张 GPU 做张量并行(模型分到 4 张卡上)……
使用每张 GPU 90% 的显存(留 10% 给系统开销)……
最大上下文长度 8192 个 token……
最多同时处理 256 个并发请求……
开启前缀缓存,相同前缀的请求可以复用 KV Cache。
gpu-memory-utilization 决定你敢用多少显存(设太低浪费资源,设太高可能崩溃)。max-model-len 越大,支持的长文本越长,但显存消耗也越多。max-num-seqs 控制并发上限。这三个参数的平衡是性能调优的核心。
用代码调用 vLLM——和调用 OpenAI 一模一样
vLLM 的 API 完全兼容 OpenAI 的接口,这意味着你可以用任何支持 OpenAI 的 SDK 来调用它:
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:8000/v1",
api_key="EMPTY")
response = client.chat.completions.create(
model="Qwen/Qwen2.5-72B-Instruct",
messages=[{"role": "user",
"content": "什么是量子计算?"}])
导入 OpenAI 的 Python SDK……
创建客户端,指向本地 vLLM 服务器……
把地址改为 vLLM 的地址(而不是 OpenAI 的云端地址)……
API key 随便填(本地部署不需要认证)。
调用聊天补全接口,和调用 OpenAI 的方式完全一样……
指定模型名称(需要和启动时加载的模型一致)……
传入对话消息列表——这里是用户的一个问题……
(续上行消息内容)
这是一个聪明的工程决策。市面上几乎所有 AI 应用都使用 OpenAI 的 API 格式。vLLM 直接兼容这个格式,意味着你可以把 vLLM 当作 OpenAI 的"平替"——把 base_url 从 api.openai.com 改成 localhost:8000,其他代码一行不用改。
遇到问题怎么办?——常见场景排查
以下是你在使用 vLLM 时最可能遇到的问题,以及如何判断和解决:
OOM(显存溢出)
症状:服务崩溃,日志显示 CUDA out of memory。排查:降低 gpu-memory-utilization(比如从 0.95 降到 0.85),减小 max-model-len,或减少 max-num-seqs。本质上是显存不够用了。
响应速度变慢
症状:初期很快,随着并发用户增多,延迟急剧上升。排查:用 /metrics 端点查看 gpu_cache_usage。如果接近 1.0,说明 KV Cache 快满了——考虑加 GPU 或降低并发上限。
首 token 延迟高
症状:用户发送请求后,第一个字要等很久才出现。排查:Prefill 阶段是瓶颈。开启 Chunked Prefill(分块预填充),让长 prompt 的处理与其他请求混合执行,减少等待。
用指标说话——vLLM 的监控端点
vLLM 内置了 Prometheus 监控指标,你可以通过 /metrics 端点查看系统的实时状态:
gpu_cache_usage_perc
KV Cache 使用率——接近 1.0 意味着快满了,需要扩容或降低并发
avg_generation_throughput
平均每秒生成的 token 数——核心性能指标,越高越好
num_requests_running
当前正在 GPU 上处理的请求数——如果经常等于 max-num-seqs,说明瓶颈在并发
num_requests_waiting
排队等待的请求数——如果这个数字持续增长,说明系统处理不过来了
e2e_request_latency_seconds
端到端延迟——从请求进入到完成的总时间,用户体验的直接指标
实战场景测试
你的 vLLM 服务突然出现 CUDA out of memory 错误。以下哪组操作最可能解决问题?
你有一个已经使用 OpenAI API 的应用。迁移到 vLLM 自部署需要改多少代码?
🎉 恭喜!你已经掌握了 vLLM 的核心原理
让我们回顾一下这段旅程中学到的关键知识:
高性能大模型推理引擎,让训练好的模型高效、低成本地服务用户。
API 服务器、调度器、模型执行器、KV Cache 管理器、分块预填充——各司其职,协同工作。
借鉴操作系统虚拟内存的分页思想,把显存利用率从 20-40% 提升到接近 100%。
Prefill 处理输入,Decode 逐 token 生成。连续批处理让 GPU 始终满载。
掌握关键启动参数、OpenAI 兼容接口、常见问题排查和性能监控。
现在你已经理解了 vLLM 的工作原理。试着在自己的项目中部署 vLLM,用 Prometheus 监控指标观察 PagedAttention 和连续批处理的实际效果。当你需要向 AI 编程工具描述需求时,可以精确地说"我要开启前缀缓存"、"需要张量并行度 4"、"gpu-memory-utilization 设为 0.9"——这些精确的词汇会让你的指令效率倍增。