news 2026/9/10 19:01:28

vLLM部署实战:从环境配置到性能调优,快速跑通大模型服务

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
vLLM部署实战:从环境配置到性能调优,快速跑通大模型服务

第一次在8G显存的卡上跑vllm,我的感受是:原来开源大模型部署可以这么省事。以前用transformers自己写推理脚本,一个请求进来基本独占整张卡,第二个请求只能排队,GPU利用率低到让人心疼。换成vllm之后,同样一张显卡,吞吐直接上了一个台阶,多路并发请求也能扛住,输出速度还快。这篇教程是给想快速把大模型服务跑起来的朋友准备的,从环境准备、启动命令、指标判断到常见坑位,我会把vllm部署这件事讲清楚,目标是你花半天时间就能把自己的模型服务跑透。

1. 先把话说清楚:vllm到底替你解决了什么

1.1 没有vllm的时候,自建模型服务为什么又慢又贵

很多人第一次接触vllm,是在部署Qwen、InternLM这类开源模型的时候。为什么大家都推荐用它?因为按照传统的transformers推理逻辑,一次请求从进入到返回,整条链路是串行的:先做prefill(预填充,把用户的输入一次性算完),再一步步做decode(逐个token生成),一个请求占着整张卡,第二个请求只能排队等待。更浪费的是,显存里给KV cache预留的空间,通常按模型最大序列长度去分配。用户实际只输入几十个token,你却给它留了几千个token的位置,显存利用率自然上不去。

vllm做的第一件事,就是连续批处理(continuous batching)。大白话解释:它不再等一个请求完全跑完才开始下一个请求,而是在每一步推理时,动态地把几个请求拼进同一个batch,一起forward。就像以前出租车一车只拉一个乘客,到站就空车回去;现在改成拼车,有人上下车,车子始终在拉人。GPU是同一个GPU,但有效计算密度提升了好几个量级。

1.2 PagedAttention和显存分页:为什么多出来的KS Cache能变成吞吐

vllm另一个核心设计是PagedAttention。这个机制解决的是KV cache碎片化的问题。它把KV cache拆成固定大小的block,像操作系统给进程分配内存页一样,需要多少就分配多少,不要求一整块连续的显存空间。这个思路直接吃掉了传统推理框架最吃亏的部分——大量显存被闲置浪费。

有人会问,KV cache省下来之后,好处是什么?答案是:省下来的显存可以放大batch。batch越大,GPU的算力利用率越高,每秒钟能产出的token总量越大。这也是为什么同样一张卡,用vllm部署能把吞吐翻好几倍的原因。快速上手阶段你不需要把PagedAttention的源码吃透,但心里要有这个概念:vllm的显存管理方式决定了它会优先吃满显存来放大batch,所以后面提到的--gpu-memory-utilization参数会非常重要。

顺带说一句,如果你准备深入看vllm代码,可以从LLMEngineSchedulerWorker这三层入手。Scheduler负责决定哪些请求进batch,Worker负责真正的模型前向计算,LLMEngine把整个调度循环串起来。快速上手不要求你懂这些,但如果后面遇到性能问题,知道堆栈在哪一层会帮你省很多事。

2. 环境准备:版本、依赖和模型权重一个都不能错

2.1 先确认Python、CUDA和PyTorch的对应关系

vllm对环境的要求不复杂,但版本一旦错位,启动时会有一堆莫名其妙的报错。我建议安装前先执行三条命令确认环境:

python --version nvcc --version nvidia-smi

我的经验是:Python用3.10到3.12之间,CUDA用12.1及以上,PyTorch用2.x版本,这套组合在vllm上踩坑最少。nvidia-smi显示的是驱动支持的CUDA版本,nvcc --version显示的是编译工具链的CUDA版本,两个不一定一致,vllm实际上更关心运行时能不能找到匹配的驱动和算子库。只要你驱动够新,通常不会有问题。

如果你机器上已经装了其他深度学习环境,强烈建议用venv或者conda单独给vllm开一个环境,不要直接在base环境里装。我以前图省事直接装,结果跟另一个项目的torch版本互相打架,最后重装了系统环境才恢复,这个成本真的没必要。

2.2 用pip装vllm,还是自己编译一份

大多数情况下,一条pip命令就能搞定:

pip install vllm

pip会自动拉取匹配的PyTorch、flash-attention等依赖。装完可以跑一下python -c "import vllm; print(vllm.__version__)"确认安装正常。

那什么时候需要从源码编译?一般是两种情况:你要改vllm源码做二次开发,或者你需要跑的模型架构在官方release版本里还没支持,需要用到最新的主分支。源码编译会花不少时间,我自己在一台16核机器上编译过一次,大概用了四十多分钟,如果机器核数少,一个多小时也很正常。所以搜到"vllm构建需要多长时间"这样的问题,我的回答是:能pip就pip,别折腾编译,除非你确实有源码级的需求。

2.3 模型权重:用ModelScope下载更省心

vllm本身不直接提供模型权重,你需要先把模型下到本地。以通义千问Qwen3-8B为例,用ModelScope的命令行工具下起来很方便:

pip install modelscope modelscope download --model Qwen/Qwen3-8B --local_dir ./models/Qwen3-8B

--local_dir指定本地保存目录,模型下载完会包含config.json、tokenizer文件、模型权重等完整内容。vllm在用--model指定路径时,会去读取这个目录下的模型配置。我习惯下载完先看一眼目录结构,确认权重文件是完整的一个整体,而不是中间断掉只剩一堆临时文件。

有些朋友习惯直接用Hugging Face上的模型路径,比如Qwen/Qwen3-8B,vllm也支持,但如果你的网络环境不太好,还是先用ModelScope下到本地再加载,能省掉很多等待时间。

3. 第一次启动:一条命令跑通Qwen3-8B

3.1 推荐启动命令和各参数含义

vllm提供了OpenAI兼容的HTTP服务。打开终端,进到模型目录所在环境,执行:

vllm serve ./models/Qwen3-8B \ --served-model-name qwen3-8b \ --host 0.0.0.0 \ --port 8000 \ --gpu-memory-utilization 0.85 \ --max-model-len 8192 \ --max-num-seqs 32

这里每个参数都不是随便填的,我拆开解释:

  • --served-model-name是暴露给调用方的模型名称,可以随便起,但调用时用的model字段必须跟它一致。
  • --host 0.0.0.0表示允许其他机器访问,本地调试也可以用127.0.0.1
  • --gpu-memory-utilization 0.85表示允许vllm最多使用85%的显存。剩下15%留给模型权重、CUDA context和系统开销。如果你的显卡显存比较紧张,可以调低到0.75;如果显存富余,可以调高到0.9以上。这个参数调不好,后续很容易OOM。
  • --max-model-len 8192是最大上下文长度。设得越大,KV cache预留越激进,能同时服务的请求数就越少。要根据实际业务需求来定,不是越大越好。
  • --max-num-seqs 32是单个batch最多容纳的请求数。调大能提升吞吐,但会增加显存压力。

启动后看到类似Application startup complete之类的日志,并且没有报错,就说明服务起来了。

3.2 用OpenAI兼容接口做冒烟测试

服务起来之后,用curl直接发一条chat请求:

curl http://localhost:8000/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3-8b", "messages": [{"role": "user", "content": "用一句话介绍你自己"}], "max_tokens": 128 }'

正常情况下,会返回一个JSON,里面有choices数组和usage字段。usage里的prompt_tokenscompletion_tokens分别表示输入和输出的token数,第一次调通时建议看一眼这两个数字,能帮你对模型的实际token开销建立感觉。

如果你习惯用Python,可以用openai库来调:

from openai import OpenAI client = OpenAI(base_url="http://localhost:8000/v1", api_key="EMPTY") resp = client.chat.completions.create( model="qwen3-8b", messages=[{"role": "user", "content": "讲个冷笑话"}], max_tokens=128, ) print(resp.choices[0].message.content)

api_key随便填一个占位字符串就行,vllm默认不做鉴权。生产环境要加鉴权的话,可以在前面套一层网关,或者用vllm支持的API key配置。

3.3 启动失败时的快速排查

第一次启动大概率会遇到点问题,最常碰到的几个:

  • 显存不足:报CUDA OOM。这时把--gpu-memory-utilization调低,或者把--max-model-len调小,再或者换一个量化版模型。
  • 模型目录填错:vllm会提示找不到config.json。确认--model指向的是模型权重所在目录,而不是上一级目录。
  • 模型需要trust_remote_code:部分模型架构不在vllm内置支持列表里,需要加--trust-remote-code参数。极个别小众模型还要准备自定义代码。我一般建议优先用主流模型,省心。

4. 别凭感觉优化:先盯住TTFT、TPOT和吞吐量

4.1 三个核心指标先搞清楚

服务跑通之后,最重要的不是马上调整各种参数,而是先搞清楚系统当前处于什么水平。大模型推理服务最常看三个指标:

  • TTFT(Time To First Token):从请求发出到收到第一个token的时间。这个值决定了用户感知的响应速度,通常目标在几百毫秒到一两秒之间。
  • TPOT(Time Per Output Token):生成每一个token的平均耗时。这个值决定了文字输出的流畅度,如果一秒钟只能蹦出一个token,用户会觉得卡。
  • 吞吐量:每秒能生成的token总数。对于并发调用场景,这个值决定了系统能同时服务多少用户。

这三个指标互相制约。比如把--max-num-seqs调大,吞吐通常会上升,但如果batch太大,每个请求分到的算力变少,TTFT就可能变长。所以优化前一定要先明确:你更在意首字延迟,还是在意单位时间能处理的请求总量。

4.2 用vllm bench serve自己压一把

vllm自带了一个压力测试工具vllm bench serve,可以在不写任何压测脚本的情况下,直接对服务发起一批请求:

vllm bench serve \ --model ./models/Qwen3-8B \ --served-model-name qwen3-8b \ --num-prompts 100 \ --max-model-len 8192

跑完之后,会输出一组统计结果,包括请求成功率、平均TTFT、平均生成速率等等。我第一次压的时候,结果里TTFT的波动很大,后来发现是因为首轮请求触发了显存和CUDA graph初始化,后面就顺了。所以拿到压测数据后,先剔除前几条预热请求,再看整体分布,不要只看平均值,最好关注P50和P95。

没有现成压测工具时,也可以自己写循环用curl多发几次请求,记录每个请求的耗时和输出长度,手动算一个粗略的吞吐。虽然不够严谨,但用来判断参数调整前后的趋势变化是够用的。

4.3 用nvidia-smi观察GPU是否真的在卖力干活

压测的时候,并行开一个窗口跑nvidia-smi -l 1,每秒刷新一次GPU利用率。如果GPU利用率始终在90%以上,说明算力吃满了;如果利用率不高,但请求已经堆积,说明瓶颈不在算力,而在调度或CPU prefill环节,这时要考虑调大--max-num-seqs或者检查CPU内存是否足够。

还有一点容易被忽略:vllm启动时会打印KV cache pool的相关信息,大致内容是会估算KV cache占了多少显存、最大并发是多少。这些日志不是给你看着玩的,当你想调大并发时,先回看这段输出,它会告诉你当前配置下理论上的并发上限,省得你盲调。

5. Windows、embedding和版本差异:实测踩过的三个坑

5.1 Windows上到底能不能跑vllm

这个问题被问过很多次,包括我最早也踩过。结论是:原生Windows不是vllm的官方主战场,部分算子编译和依赖在Windows上会遇到不少麻烦,强行用不是不行,但会让你把大量时间耗在环境折腾上。

如果你只有Windows机器,最省心的办法是装WSL2,在WSL里跑一个Ubuntu环境,然后把vllm装进去。NVIDIA驱动在Windows下装了之后,WSL2里可以直接透传CUDA,显卡能正常调用。模型权重可以在Windows侧用ModelScope下载,摆在共享目录里,WSL里直接加载这个路径就行。

我在Windows上跑通过一次,过程不能说痛苦,但确实比Linux多花了不少时间。如果你是新手,建议直接找一台Linux服务器,或者用云GPU镜像,先把vllm的主要流程跑通,再考虑Windows的兼容问题。

5.2 用vllm启动bge-m3这类embedding模型

vllm不是只能部署对话模型,它也能跑embedding模型。以BAAI/bge-m3为例:

vllm serve BAAI/bge-m3 --task embedding --port 8001

注意这里的--task embedding,不指定的话,vllm会按默认的生成模型逻辑去处理,很可能报错。模型起来后,通过/v1/embeddings接口获取向量:

curl http://localhost:8001/v1/embeddings \ -H "Content-Type: application/json" \ -d '{"model": "BAAI/bge-m3", "input": "你好"}'

碰到启动失败时,先确认两件事:一是模型架构是否在vllm的支持列表里,像bge系列这类比较主流的没问题,小众的embedding模型则不一定;二是你用的vllm版本是否完善。如果版本太老,对embedding任务的支持可能不完整,升级一下版本往往能解决。

5.3 版本升级带来的行为差异,尤其要注意chunk_size这类隐藏参数

vllm的版本更新速度很快,但新版本不一定在所有场景下都更省心。有人反馈过chunk_size相关的行为在特定版本里表现异常,比如使用chunked prefill时显存分配不符合预期,或者在某些并发条件下性能明显回退。这类问题并不一定是bug,也可能是默认值变了,导致你的启动命令实际行为和之前不一样。

我的建议是:生产环境把vllm版本固定下来,不要随手升级。新版本可以先在测试环境跑一遍压测,对比关键指标,确认没问题再切换。很多问题看似是模型问题,查到最后其实是版本行为差异。

6. 从跑通到跑好:缓存命中、引擎选型和边缘设备

6.1 prefix caching:让重复前缀不再白算

如果你观察过真实业务,会发现很多请求是有公共前缀的:比如多轮对话里前几轮历史几乎一样,比如批量做评测时所有请求共享同一大段system prompt,又比如代码补全时用户把前面几百行代码都塞进上下文。传统推理方式下,每个请求都要重新计算这些前缀的KV cache,浪费大量算力。

vllm提供了Prefix Caching机制,加了--enable-prefix-caching之后,相同前缀的KV cache会被复用。如果某个请求的前缀跟之前算过的匹配,这部分就不再重复计算。对多轮对话、Few-shot评测、文档问答这类场景,收益非常明显。我在实测中遇到过一次缓存命中率很高但显存压力变大的情况,原因是缓存也需要占额外显存。所以开启前要看好显存余量,尤其当模型本身已经塞得很满时,缓存带来的收益可能被显存紧张冲抵掉。

6.2 vllm和sglang怎么选

sglang和vllm经常被放在一起对比,它们解决的是同一类问题。sglang在RadixAttention上做得比较激进,对前缀复用的调度粒度更细,在某些复杂Prompt和结构化生成场景下,性能可以比vllm更极致。vllm的优势则在于生态成熟、接口标准、社区庞大,遇到问题时更容易找到答案。

我个人的选型经验是:如果团队刚起步,优先用vllm,因为它能让你用最短时间把服务稳定跑起来;如果后续发现延迟或吞吐确实不够,再花时间对比一下sglang,用压测数据说话。不要一开始就迷信"哪个更快"的论调,多数场景下两者差距并没有传说中那么大。

6.3 Jetson这类边缘设备上的vllm

话题回到边缘设备。Jetson Thor这类设备上也能跑vllm,官方提供了对应的容器镜像,但边缘设备显存和算力都有限,不适合直接上几十B的大模型。我见过比较常见的做法是:在边缘设备上跑7B以下的小模型,开低一点的并发,配合量化把显存占用压下来,效果还不错。边缘部署相比服务器端的难点不在vllm本身,而在环境尺寸、功耗和可维护性,所以快速上手阶段可以先不用深挖。

7. 最后再说几句我的实际操作心得

这篇教程写到这里,核心链路已经完整了:环境准备、模型下载、启动服务、压测验证、常见坑位。最后分享几个我实际跑项目时沉淀下来的习惯。

第一,先小后大。第一次接触vllm,不要一上来就部署一个27B的大模型,先用1.5B或者3B这种小模型把整个链路跑通,确认接口、日志和压测方法都没问题,再换大模型。这一步能帮你过滤掉非常多不必要的变量。

第二,启动日志值得仔细读。vllm启动时打印的显存估算、KV cache allocation、CUDA graph初始化信息,不是无用输出。有一次我发现服务性能下降,回头查启动日志,发现--max-model-len被某次配置误改大,KV cache余量明显变小,问题一下就定位了。

第三,固定版本、固定参数、固定压测方法。版本升级前先跑一遍压测,参数调整后记录前后指标,压测时用同样的请求集。把这个习惯保持住,你会发现自己排查问题的速度快很多。快速上手从来不是靠背诵命令,而是靠建立一套自己的验证流程。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 18:56:34

SMT车间智能ESD防护闸机设计与应用

1. 项目概述:当SMT车间遇上智能ESD防护闸机 在SMT(表面贴装技术)车间里,静电就像个看不见的杀手。去年我们产线就发生过一起典型案例:某批次主板在测试阶段出现不明原因的信号干扰,追溯发现是操作员未规范释…

作者头像 李华
网站建设 2026/9/10 18:56:01

Rust egui窗口配置全攻略:从NativeOptions到ViewportBuilder实战

我在Rust桌面应用里用egui写小工具也有段时间了,每次新建项目都要重新配一遍窗口:大小、标题、全屏、图标、渲染器、垂直同步……这些参数统一由eframe::NativeOptions管理。今天这篇就直接给一份“窗口配置小抄”,把常见窗口形态的配置方式都…

作者头像 李华
网站建设 2026/9/10 18:55:50

2026国产电脑监控软件选型指南:信创适配与合规实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 18:55:37

ClickHouse实时数据立方体构建与优化实战

1. 为什么选择ClickHouse构建实时数据立方体第一次接触ClickHouse是在三年前的一个电商大促监控项目,当时需要实时分析每分钟千万级的用户行为数据。传统MySQL在写入时就已崩溃,而Hadoop生态的方案又无法满足亚秒级响应需求。当我用单机版ClickHouse轻松…

作者头像 李华
网站建设 2026/9/10 18:55:30

SpEL表达式注入攻防:从反射逃逸到安全加固与扩展实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华