如果你的LLM服务线上延迟高、吞吐低、GPU还闲得发慌,大概率不是模型太烂,是你的推理框架没选对。
今天聊一个硬核话题:vLLM凭什么成为LLM生产部署的事实标准。不是背概念,是从底层问题出发,拆解它到底解决了什么、怎么解决、线上怎么用。
我会按这个顺序来:
- 三个要命的瓶颈——vLLM之前的世界有多惨
- vLLM三板斧——PagedAttention、Continuous Batching、Tensor Parallelism
- 四层架构拆解——从API到GPU发生了什么
- 线上部署实操——Docker单机到K8s集群
- Ollama vs vLLM怎么选——别选错
- 六道高频面试题——能答上来才算真懂
这玩意儿不是玄学,是工程。
一、三个要命的瓶颈:vLLM之前的世界有多惨
先说结论:传统LLM推理框架的GPU显存利用率只有24%左右。你没看错,花了十几万买的A100,76%的显存在空转。
瓶颈1:KV Cache显存浪费
大模型推理时,每个请求都需要一块连续的显存空间存KV Cache(Key-Value缓存)。问题在于——传统框架按最大可能长度预分配显存。
打个比方:你去餐厅吃饭,服务员直接给你留一个100人的大包间,不管你实际就3个人。结果餐厅明明能接待50桌,实际只能接待5桌。
具体数字更扎心:一个13B模型在A100上,传统框架预分配KV Cache占掉约80GB显存,但实际平均利用率只有24%。剩下的76%全在"占着茅坑不拉屎"。
瓶颈2:请求排队等批
传统Static Batching的逻辑是:凑够一批请求→一起推理→等最长的那个生成完→才能处理下一批。
这就跟坐公交车一样:车上有50个座位,上来3个人,司机说"等坐满再开"。然后你就等着,等到50个人凑齐了才发车。最惨的是如果你那批里有个生成2000 token的长请求,整个batch都得等它写完。
瓶颈3:单GPU装不下大模型
一个70B模型FP16需要约140GB显存,单张A100只有80GB。传统框架基本就傻了——装不下就是装不下,你只能量化压缩或者换小模型。
这三个问题叠加的结果:GPU算力本身不是瓶颈,显存管理和调度才是。GPU在空转等内存,而不是在算东西。
二、vLLM三板斧:每一招都直击要害
第一斧:PagedAttention--虚拟内存分页管理
核心思想:把KV Cache的连续显存分配改成按需分页分配。
操作系统怎么管内存的?虚拟内存+分页。你申请1GB内存,OS不会真给你分配1GB物理内存,而是给你一堆虚拟页,用到哪页分配哪页。
vLLM把同样的思路搬到了GPU上:
- 把KV Cache切成固定大小的Block(通常每Block存16个token的KV)
- 物理显存按需分配,用到才给
- 逻辑Block和物理Block通过Block Table映射
- 一个请求的KV Cache不需要连续存储
效果:显存利用率从24%飙到96%。同一张GPU能同时服务的请求数翻了4倍。
还有个意外收获:Block级别的共享让Prefix Caching变得很自然。多个请求共享同一个system prompt,那段KV Cache只需要存一份,所有请求共享。这在实际场景中省的不是一点半点。
第二斧:Continuous Batching--请求动态进出
核心思想:不等一批凑齐,不等一批跑完。请求随时进随时出。
Static Batching的问题在于"木桶效应"--最长的请求决定整批的时间。如果一批50个请求,49个生成了20 token就完事,最后1个要生成2000 token,那49个请求的GPU资源在等待期间全浪费了。
Continuous Batching的做法:
- 每次iteration只处理当前有活干的请求
- 某个请求生成完了立刻退出batch
- 新请求随时加入batch
- GPU从不闲着
效果:吞吐量提升2-4倍。延迟反而更低了,因为短请求不用等长请求。
第三斧:Tensor Parallelism--多GPU切分
核心思想:一张卡装不下大模型,那就把模型切成块分到多张卡上。
不是按Layer切(那是Pipeline Parallelism),而是按Tensor切。具体来说,矩阵乘法 A×B 可以按列切分B:
- GPU 1 算 A×B₁
- GPU 2 算 A×B₂
- 最后All-Reduce合并结果
这样每张卡只需要存模型的一部分,70B模型用2-4张A100就能跑了。虽然通信开销增加了一些延迟,但换来的是能跑大模型。
三者关系:PagedAttention省显存->更多请求能同时塞进去->Continuous Batching让这些请求高效调度->Tensor Parallelism让大模型也能跑。三板斧组合起来,GPU才真正忙起来。
三、四层架构拆解:从API到GPU发生了什么
vLLM的架构很清晰,四层各司其职:
┌─────────────────────────────────────┐
│ API Server (FastAPI) │ ← OpenAI兼容接口
│ /v1/chat/completions, /v1/engines │
├─────────────────────────────────────┤
│ Engine │
│ ├ Scheduler (调度器) │ ← Continuous Batching调度
│ └ KV Cache Manager (分页管理) │ ← PagedAttention
├─────────────────────────────────────┤
│ Worker │
│ ├ Model Runner (模型执行) │
│ └ PagedAttention CUDA Kernel │ ← 底层算子
├─────────────────────────────────────┤
│ Hardware │
│ GPU(s) / NVLink / 多卡 │ ← Tensor Parallelism
└─────────────────────────────────────┘
API Server层
FastAPI写的,对外暴露OpenAI兼容的REST接口。你之前用OpenAI SDK写的代码,把base_url从https://api.openai.com改成http://your-vllm-server:8000就能跑,业务代码零改动。
这跟Ollama的/v1/chat/completions一样的设计思路--降低迁移成本。
Engine层
这是vLLM的大脑,两个核心组件:
Scheduler:决定每次iteration跑哪些请求。它维护一个waiting队列和running队列,根据显存余量动态调度。新请求进waiting,有显存了拉到running,生成完了踢出去。这就是Continuous Batching的具体实现。
KV Cache Manager:管理Block的分配和回收。每个请求有一个Block Table记录它的逻辑Block映射到哪些物理Block。请求结束了,Block立刻回收给下一个请求用。这就是PagedAttention的实现。
Worker层
每个Worker对应一张GPU。Model Runner负责跑模型的前向传播,PagedAttention CUDA Kernel是专门写的底层算子,直接操作Block的KV Cache,不走PyTorch的注意力机制。
这层是性能的关键--自己写CUDA Kernel而不是用PyTorch原生的注意力,才能精确控制显存访问模式。
Hardware层
多GPU通过NVLink互联,Tensor Parallelism在这层生效。单卡就是普通的GPU推理。
四、线上部署实操:从Docker单机到K8s集群
Docker单机部署
线上最快的方式,几个关键参数:
docker run --gpus all \
--shm-size 8g \
-v /data/models:/models \
-p 8000:8000 \
vllm/vllm-openai:latest \
--model /models/qwen2-7b \
--tensor-parallel-size 2 \
--max-model-len 4096
三个容易踩的坑:
--gpus all:透传所有GPU。如果只想要部分GPU,用--gpus '"device=0,1"'指定。--shm-size 8g:默认64MB太小,PyTorch多进程共享内存会爆。给8GB基本够。- 模型挂载用PVC:别把模型打包进镜像,一个13B模型镜像就几十GB,构建一次要命。挂载到容器里秒级启动。
K8s集群部署
生产环境真正用的,核心是三个资源对象:
nvidia-device-plugin:让K8s能调度GPU。没这个,K8s不知道GPU是什么资源。装了之后,Pod配置里写nvidia.com/gpu: 2就能申请2张GPU。
PVC共享模型:多个Pod共享同一个模型存储,避免每个节点都下载一份。用ReadWriteMany的存储类(比如NFS或CephFS)。
HPA自动扩缩容:这个最有意思。HPA不能只看CPU/内存利用率(GPU推理Pod的CPU使用率很低),要看请求队列长度。vLLM暴露了/metrics端点,里面有vllm:num_requests_waiting指标。基于这个指标扩缩容:队列长了加Pod,空闲了缩Pod。
另外要配启动探针,vLLM冷启动加载模型需要30秒到几分钟,默认探针太激进会杀Pod。initialDelaySeconds: 120给足加载时间。
Ollama vs vLLM:别选错
这两个不是竞争关系,是开发环境 vs 生产环境的区别:
| 维度 | Ollama | vLLM |
|---|---|---|
| 定位 | 本地开发调试 | 生产环境高吞吐 |
| 显存管理 | 简单预分配 | PagedAttention分页 |
| 批处理 | Static Batching | Continuous Batching |
| 多GPU | 不支持 | Tensor Parallelism |
| 启动速度 | 快(秒级) | 慢(加载模型30s+) |
| API兼容 | OpenAI兼容 | OpenAI兼容 |
| 业务代码改动 | 零 | 零 |
关键点:两者API完全兼容。开发用Ollama,上线切vLLM,只需要改一个base_url。这才是好的架构设计--接口统一,实现可换。
我本地用Ollama跑Qwen3:8B和GLM4:9B做开发调试,线上用vLLM跑同款模型服务真实流量。业务代码用的是LangChain4j的ChatModel接口,底层切换完全透明。
五、线上真实场景:公司里到底怎么用的
场景1:高并发聊天服务
某互联网公司用Qwen2-72B做客服聊天,日均100万次对话。用vLLM + 4张A100 80GB:
- Tensor Parallelism=4,72B模型FP16约144GB,4卡分摊每卡36GB
- PagedAttention让同时服务200+并发会话(传统框架只能服务50个)
- Continuous Batching让P99延迟从800ms降到200ms
- 峰值QPS约500,GPU利用率保持在85%以上
场景2:批量推理任务
某数据公司每晚跑10万条数据抽取任务。之前用Transformers库串行跑,8小时。换vLLM后:
- Continuous Batching自动批处理,不用手动凑batch
- 2小时跑完,快了4倍
- KV Cache复用:所有任务用同一个system prompt,Prefix Caching命中率高
场景3:多模型A/B测试
K8s上起多个vLLM Pod,每个Pod跑不同模型。流量通过Service按比例分发:
- Pod 1: Qwen2-7B(80%流量,成本低)
- Pod 2: Qwen2-72B(20%流量,高质量)
- ModelRouter根据任务复杂度路由
- 这跟我之前手敲的ModelSwitcher设计思路一模一样,只是从本地Ollama换成了vLLM集群
场景4:成本优化
vLLM最狠的不是性能提升,是同等硬件能服务更多用户。某创业公司从Transformers切换到vLLM:
- 同样4张A100
- 之前最大并发50,vLLM后最大并发200
- 不用加机器,服务能力翻了4倍
- 每月省下2张A100的云费用约¥3万
这就是vLLM的价值--不是让GPU算得更快,是让GPU不闲着。
六、六道高频面试题:能答上来才算真懂
Q1: PagedAttention为什么能提升显存利用率?
答:传统框架按最大序列长度预分配连续显存,实际平均利用率24%。PagedAttention把KV Cache切成固定大小的Block,按需分配,用到哪页给哪页,逻辑Block通过Block Table映射到物理Block。利用率提升到96%,同一张GPU能服务的并发请求数翻了4倍。
加分项:提到Prefix Caching--共享system prompt的多个请求只存一份KV Cache。
Q2: Continuous Batching和Static Batching有什么区别?
答:Static Batching凑满一批才跑,等最长的请求生成完才能处理下一批,存在木桶效应。Continuous Batching每次iteration只处理当前活跃请求,生成完的立刻退出,新请求随时加入,GPU不闲着。吞吐提升2-4倍,短请求延迟更低。
加分项:提到iteration级别的调度--每生成一个token就是一次iteration,Scheduler每次都重新决定哪些请求参与。
Q3: Tensor Parallelism和Pipeline Parallelism有什么区别?
答:
- Tensor Parallelism:按矩阵列切分权重矩阵,多张卡同时算同一层的不同部分,结果All-Reduce合并。通信开销在层内。
- Pipeline Parallelism:按Layer切分模型,不同层放在不同卡上,数据像流水线一样经过各卡。通信开销在层间。
- vLLM主要用Tensor Parallelism,因为它更适合低延迟场景。Pipeline Parallelism更常见于训练。
Q4: vLLM和Ollama有什么区别?线上怎么选?
答:Ollama定位本地开发调试,简单预分配显存,Static Batching,不支持多GPU。vLLM定位生产环境,PagedAttention+Continuous Batching+Tensor Parallelism。两者API都兼容OpenAI格式,业务代码只需改base_url。开发用Ollama,上线用vLLM。
加分项:提到Ollama启动快(秒级),vLLM启动慢(加载模型30s+),所以K8s部署要配启动探针。
Q5: K8s部署vLLM,HPA基于什么指标扩缩容?
答:不能只看CPU/内存,GPU推理Pod的CPU使用率很低。应该看vLLM暴露的/metrics端点中的vllm:num_requests_waiting(等待队列长度)。队列长了加Pod,空闲了缩Pod。另外要配置启动探针initialDelaySeconds: 120以上,因为vLLM冷启动加载模型需要时间。
Q6: 为什么说"vLLM解决的是显存瓶颈不是算力瓶颈"?
答:GPU推理时,大部分时间GPU不是在算,是在等数据从显存搬过来。传统框架的显存管理太粗放,导致很多显存空转,能同时服务的请求数很少,GPU算力闲置。vLLM通过PagedAttention把显存利用率从24%拉到96%,更多请求能同时进来,Continuous Batching让这些请求高效调度,GPU才真正忙起来。所以瓶颈不在算力,在显存管理和调度效率。
总结:三句话记住vLLM
- PagedAttention解决显存浪费--分页管理,利用率24%→96%
- Continuous Batching解决调度低效--请求动态进出,吞吐×2-4
- Tensor Parallelism解决模型太大--多卡切分,70B也能跑
核心认知:vLLM不是让GPU算得更快,是让GPU不闲着。显存瓶颈非算力瓶颈,这个认知在面试时说出来,面试官会眼前一亮。
线上实操口诀:开发用Ollama,上线用vLLM,API兼容只改base_url,Docker单机够用,K8s看队列扩缩容。
下一篇我们会聊LLM可观测性--线上跑了vLLM之后怎么监控Token消耗、延迟分布、质量指标,别等用户投诉才发现问题。