当前位置: 首页 > news >正文

Day14 | vLLM——把训完的模型搬上线的“印钞机“

苦猿的大模型日记 · Day14 · vLLM——把训完的模型搬上线的"印钞机"-帮普通人把AI学进简历系列

前言:模型训完了,发上去一推理,你傻了

你刚把一个 GRPO 训完的 reasoning 模型权重 push 到服务器,满怀激动地用 transformers 起了个 FastAPI 服务,发了个测试请求——

8 秒。

一条请求 8 秒。你以为网络卡了,又发一条。

还是 8 秒。

你寻思"可能并发能好点",开了 10 个并发一起打。

显卡直接OOM

你盯着nvidia-smi那条上蹿下跳的曲线发呆——A100,一张一天 800 块钱的卡,GPU 利用率 8%

老板这时候路过你工位,问了一句你这辈子都不想听的话:

「这卡的钱,公司能报销吗?」

那一刻我才明白一个事——

训完一个模型,离它能上线,中间还隔着一整座山。

我当年卡在这座山上卡了大半年。后来遇到一个工具,把吞吐量干到了原来的二十多倍——

它叫 vLLM。

今天我把它讲透。


PART 01:transformers 起服务——你以为能用,其实是"老头乐"

很多人觉得:模型推理嘛,不就是model.generate()加一层 FastAPI 包一下?

我先把这套"原始方案"的代码贴出来,你看看眼熟不眼熟——

from fastapi import FastAPI from transformers import AutoModelForCausalLM, AutoTokenizer app = FastAPI() model = AutoModelForCausalLM.from_pretrained( "Qwen/Qwen3-8B-Instruct", device_map="auto", torch_dtype="auto", ) tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen3-8B-Instruct") @app.post("/chat") def chat(prompt: str): inputs = tokenizer(prompt, return_tensors="pt").to("cuda") output = model.generate(**inputs, max_new_tokens=512) return {"response": tokenizer.decode(output[0])}

跑起来,单条请求是能出结果的。

但你只要往上面压并发,三个致命问题立刻爆出来——

第一个毛病:逐 token 生成,没有批处理意识。

transformers 的generate()是一个完整的"端到端"调用,进来一个请求就生成完才放你走。第二个请求来了,对不起,排队。你的显卡里其实 99% 的算力在睡大觉。

第二个毛病:KV cache 一次性占用,10 个并发就 OOM。

LLM 推理时,每生成一个 token 都要回头查前面所有 token 的 Key/Value 中间状态——这就是KV cache。问题在于:transformers 给每个请求预分配一整块连续显存,按最长可能长度算。

你 8B 模型,max length 8192,一个请求就能吃掉 6GB 显存。10 个并发一起进来,A100 80GB 都扛不住。

第三个毛病:GPU 利用率个位数。

不是显卡不行,是算法层就没为推理优化。transformers 是为训练设计的,不是为 serving 设计的。你拿它做推理,等于拿超跑去送外卖——一次送一份,还堵在路口。

下面这张图是我自己实测的,同样一张 A100、同一个 Qwen3-8B——

方案单卡 QPSGPU 利用率
transformers + FastAPI~38%
vLLM**~75****92%**

24 倍。

不是优化几个点的事,是数量级的差距。

那 vLLM 到底干了什么?


PART 02:vLLM 的"印钞机"原理——PagedAttention + Continuous Batching

vLLM 是 2023 年 UC Berkeley 发的论文,标题就一个词——Efficient Memory Management for Large Language Model Serving with PagedAttention

但说实话,论文那套术语我看着都头大。我用大白话给你翻译一下——

vLLM 的核心就两件事:把显存榨干,把批处理做活。

第一件:PagedAttention——把显存从"包场"改成"按需点菜"

传统 KV cache 的问题,一句话——

给每个请求包场,不管它坐不坐得满。

一个请求声明 "我可能要 8192 个 token",显存就真给它留 8192 个位置。最后它可能只生成了 800 个 token,剩下 7000 多个位置白白占着

PagedAttention 干的事,你可以类比成操作系统的虚拟内存——

把 KV cache 切成固定大小的 block(比如每 16 个 token 一块),按需分配。

你要 800 token?好,给你 50 个 block。
你要 8192?好,给你 512 个 block。
不够?再补。

而且这些 block 在物理显存里可以不连续,靠一张映射表串起来。

这一个改动,显存利用率从 ~30% 拉到 ~95%

同样是 80GB 显存,能塞下的并发请求数直接翻了三倍。

第二件:Continuous Batching——批处理不再"队头阻塞"

传统 batching 的逻辑是——

一批 8 个请求同时进来,等最慢那个生成完,才能进下一批

第一个请求 50 token 就答完了,最后一个要生成 800 token——前面那位得在显存里干等

Continuous Batching 的逻辑是——

每一个生成步(step)都重新组 batch。谁生成完了,立刻走人,新请求立刻补进来。

传统批处理: [A B C D] ────────────► 全部结束,下一批 ↑ 队头阻塞 连续批处理: [A B C D] [A B C _] ← D 结束走人,E 加入 [A B C E] [_ B C E] ← A 也走了,F 来了 [F B C E]

每一步 GPU 都在满负荷干活,没有一个 token 在干等。

这一个改动,吞吐量再提 8-14 倍

两件事叠在一起,就是开头那张表里24 倍的来源。

到这里你大概明白了——vLLM 不是什么"小优化",是把推理这件事重新设计了一遍

那实战怎么用?下面 5 分钟带你跑起来。


PART 03:5 分钟把模型跑起来——vLLM 实操

vLLM 的设计哲学很粗暴——一行命令起服务

第一步:安装

pip install vllm

注意 vLLM 只支持 Linux,Windows 用户得用 WSL2 或者直接租 Linux 服务器。

第二步:一行 CLI 启动服务

vllm serve Qwen/Qwen3-8B-Instruct \ --gpu-memory-utilization 0.9 \ --max-model-len 8192 \ --tensor-parallel-size 1 \ --enable-prefix-caching

启动完,你会在终端看到一行:

INFO: Uvicorn running on http://0.0.0.0:8000

服务就起来了,对外暴露的是 OpenAI 兼容 API——这意味着你之前调 OpenAI 的代码,把base_url改一下就能直接用。

第三步:客户端调用

curl 版本:

curl http://localhost:8000/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "Qwen/Qwen3-8B-Instruct", "messages": [{"role": "user", "content": "你好,介绍一下你自己"}] }'

Python 用 OpenAI SDK 版本(最推荐):

from openai import OpenAI client = OpenAI( base_url="http://localhost:8000/v1", api_key="dummy", # vLLM 本地不校验 key ) resp = client.chat.completions.create( model="Qwen/Qwen3-8B-Instruct", messages=[{"role": "user", "content": "用一句话解释 PagedAttention"}], ) print(resp.choices[0].message.content)

跑通之后,你再去nvidia-smi看一眼——

GPU 利用率从 8% 直接拉到 90%+。

关键参数怎么调(不是流水账)

这四个参数你必须懂,不懂就是面试必挂——

--gpu-memory-utilization:vLLM 能用多少显存。

默认 0.9,意思是"这张卡的 90% 给我"。显存够就拉到 0.95,吞吐还能再涨一截;显存紧张(机器上还跑别的任务)就降到 0.7。我一般直接拉满。

--max-model-len:模型最大上下文长度。

越大越吃显存——KV cache 是按这个长度预计算的。Qwen3-8B 默认支持 32K,但你 RAG 场景真用得到 32K 吗?大部分场景8192 足够,砍一半显存能多塞一倍并发

--tensor-parallel-size:多卡切分。

TP=2 就是把模型横切两半,一张卡存一半权重。只有当单卡塞不下整个模型时才用——单卡能塞下就别开,TP 通信开销反而拖慢速度。

--enable-prefix-caching:相同前缀共享 KV。

这个我建议你无脑开。RAG 场景里几十条 query 都引用同一份文档,那份文档的 KV cache 第一次算完之后所有后续请求直接复用——延迟砍一半,吞吐翻倍。Agent 工具调用场景同理,system prompt 是固定的,能省一大笔。

参数讲完,下一个问题——怎么知道你的服务真的"调好了"?

得压测。


PART 04:压测与调优——同样的卡,吞吐差 3 倍

vLLM 自带一个压测脚本,路径在vllm/benchmarks/benchmark_throughput.py。直接跑:

python -m vllm.benchmarks.benchmark_throughput \ --model Qwen/Qwen3-8B-Instruct \ --backend vllm \ --num-prompts 1000 \ --request-rate 10

输出会告诉你平均每个请求的延迟整体吞吐量

你该盯的三个信号

信号 1:GPU 显存利用率

nvidia-smi持续高位(90%+)才是好事。如果只有 40%,说明 vLLM 没吃饱——大概率是gpu-memory-utilization调低了,或者请求 QPS 不够。

信号 2:请求延迟分布(P50 / P99)

P99 飙高说明有请求在排队。要么是 batch 满了,要么是 max-model-len 设太短导致频繁截断重算。

信号 3:KV cache 命中率

开了 prefix caching 之后,vLLM 日志会打印cache hit rate。低于 30% 说明你的请求前缀不够集中——RAG 场景低于这个数,检查一下 system prompt 是不是每个请求都不一样。

调优经验表(实测,不是 GPT 编的)

场景调什么为什么
显存够,想榨吞吐`--gpu-memory-utilization 0.95`留 5% 给临时计算就够,再多就是浪费
显存紧张 OOM砍 `--max-model-len`,别砍 batch砍 batch 会让 continuous batching 失效
长 prompt + RAG必开 `--enable-prefix-caching`共享文档 KV,延迟减半
多卡机器优先 TP,不要 DPDP 要自己写调度器,vLLM 的 TP 是开箱即用
高并发短输出调高 `--max-num-seqs`batch size 上限,默认 256,可以拉到 512

调到一份能跑出75 QPS / P99 800ms的配置,A100 单卡,Qwen3-8B——这就够交付大部分在线场景了。

但有个事我得提醒你——

vLLM 不是万能的。


PART 05:什么场景该用 vLLM,什么场景别用

我跟你说实话——vLLM 解决的是"高并发在线服务"问题,不是所有推理场景都该上它。

这些场景,vLLM 是真神

1. 在线 chat / API 服务——多用户高并发,吞吐量直接关系到成本。

2. RAG 系统——长 prompt、固定文档前缀,prefix caching 直接起飞。

3. Agent / Function Calling——system prompt 长、tool 描述固定,每次调用复用 KV。

4. 批量推理任务——一晚上跑完几十万条数据,continuous batching 让你提前下班。

这些场景,别用 vLLM

1. 单用户本地玩。

你只是想在自己笔记本上跑个模型玩玩——用 Ollama,不要用 vLLM。vLLM 是为多并发设计的,单用户场景它的优势完全用不上,反而吃满你显存。

2. 极致延迟敏感(单请求 < 100ms)。

vLLM 优化的是吞吐量,不是单请求延迟。如果你做的是同声传译、实时语音这种 100ms 内必须出第一个 token 的场景,vLLM 反而不一定比 transformers 快——因为它要组 batch,会有几毫秒的等待。

3. 自定义架构 / 非标准注意力。

vLLM 支持的模型有列表(GitHub 上一个models.md),你的模型如果改了 attention 结构(比如自定义的 sparse attention),vLLM 大概率不认——这时候只能 transformers 自己起。

一个最常被问到的误区

「vLLM 和量化啥关系?是不是开了 vLLM 模型就变小了?」

不是。

量化是把模型权重从 fp16 压成 int8/int4(用 AWQ、GPTQ 这些工具),目的是让模型本身变小

vLLM 是个推理服务框架,负责把模型高效地跑起来——你给它什么精度的模型,它就跑什么精度。

两者的关系是叠加,不是替代——你可以给 vLLM 喂一个 AWQ 量化过的模型,显存占用再砍一半,吞吐量再翻一倍。

vllm serve TheBloke/Qwen3-8B-Instruct-AWQ \ --quantization awq \ --gpu-memory-utilization 0.9

到这里 vLLM 讲得差不多了。最后我给你留一个判断——


结尾:训完的模型不上线,等于没训

我见过太多团队卡在这一步——

SFT 跑通了,对齐跑通了,权重也存好了,然后扔在硬盘里三个月没人上线

为什么?

因为上线推一把,发现 A100 烧不起。

训出来的模型不会自己跑到生产环境。得有人把它背上去——选好框架、调好参数、压测、监控、扩容。

vLLM 就是那把背篓。

我自己的体感是——

SFT 决定你的模型有多聪明,vLLM 决定它能在几张卡上聪明得下去。

前一件大家都在卷,论文一周一篇;后一件没人在意,但它是真烧钱。一张 A100 一天 800 块,调好 vLLM 一年能给公司省一套首付。

这种活,老板不会单独派给你——你得自己懂、自己上、自己认领。

这就是简历上"模型推理优化经验"那一行的来源。

下一篇我们继续——上线的模型跑得慢,到底怎么定位瓶颈?是 GPU 算力不够、还是 IO 卡了、还是请求 pattern 有问题?我们聊LLM 推理性能分析

— END —

苦猿 · 帮普通人把 AI 学进简历

http://www.jsqmd.com/news/1133347/

相关文章:

  • OASIS Ring智能戒指:Vibe Coding新宠,能否成为AI交互最佳硬件?
  • 5分钟看懂MetaBMC:为什么它是服务器管理的未来?
  • STM32驱动WS2812 LED灯带的实战指南
  • STM32与Si4732打造高性能数字收音机方案
  • 【JAVA毕设源码分享】基于Web的商品预购平台的设计与实现(程序+文档+代码讲解+一条龙定制)
  • 明日方舟智能助手实战指南:5个核心技巧告别手动肝日常
  • Si4732与MKV44F128VLH16在数字音频处理中的协同应用
  • 2026最新vibe coding常用工具平替深度对比实测
  • PIC32MX与M24C04-R的I2C EEPROM存储方案实现
  • AnythingLLM:企业级私有知识库的高性能可扩展架构解决方案
  • OpenCV实战:从零搭建实时人脸识别系统,附完整代码与避坑指南
  • N_m3u8DL-RE流媒体下载:3个实用技巧轻松搞定在线视频保存
  • NBTExplorer:Minecraft数据编辑的终极免费可视化工具
  • LP5812与PIC18F4682实现RGB LED灯光控制方案
  • 终极指南:如何安全解锁《原神》60帧限制,畅享144Hz极致体验
  • GTA5线上小助手:从新手到高手的洛圣都自由之旅
  • AI编程助手实战指南:从工具本质到高效协作,程序员如何驾驭AI提效
  • STM32与WS2812B实现动态光效系统开发指南
  • STM32与KMX63实现6DOF运动追踪与人机交互设计
  • TPS65263三路降压转换器与PIC18LF26K80的电源管理系统设计
  • PIC18F47K42驱动WS2812B LED的嵌入式开发指南
  • XMATCH函数核心原理与高阶实战:精准定位、双向搜索与动态匹配
  • 网页逆向实战:MD5、SHA、HMAC加密算法识别与破解指南
  • PUBG罗技鼠标宏终极指南:5分钟实现完美压枪控制
  • EM3080-W条码扫描模块与STM32L496AG嵌入式系统设计
  • Si4732与TM4C129EKCPDT实现高保真音频系统设计
  • 时间序列预测基线模型:3个场景下朴素预测法(Naive)的适用性分析
  • STM32与74HC32实现高效按键管理的硬件与软件方案
  • 为什么NBTExplorer是Minecraft数据编辑的终极解决方案?
  • PIC32与WS2812智能LED控制方案详解