ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

vLLM 完整上手:从显存优化到压测部署的实战指南

vLLM 完整上手:从显存优化到压测部署的实战指南 很多人第一次认真看 vLLM都是在本地把模型跑起来之后被现实教育了一顿单条请求还行并发一上来吞吐直接塌显存明明还剩几个 G 却提示放不下上下文一长就要排队等十几秒。我当初也是被这些问题逼着从别的推理方案迁到 vLLM 的前后折腾了差不多两个礼拜踩的坑足够写好几篇。这篇是快速上手系列的第四篇不打算再重复vLLM 是什么这种入门话术而是把从环境准备、安装选型、启动参数、服务化调用、压测到排错的完整链路一次讲透让你看完能自己把模型跑起来并且清楚每一步为什么这么干。这篇适合三类人刚接手 GPU 服务器、准备把模型做成服务的后端同学被并发和显存问题折腾过、想换推理引擎的算法同学还有只是想在本机快速验证一个开源模型效果的开发者。不需要你提前读过 vLLM 源码但至少要会基本的 Linux 命令行和 Python 环境操作。1. 先把 vLLM 优化的那点事说透动手之前花十分钟理解 vLLM 在优化什么后面看参数和报错都会顺很多。它解决的核心问题就两个显存怎么用得更省请求怎么排得更满。这两件事听起来平淡但恰恰是决定一个推理服务能不能扛住真实流量的关键。1.1 KV Cache 为什么会把显存吃成筛子大模型生成文本是逐 token 吐出来的。每生成一个 token都要用到前面所有 token 的 Key 和 Value 向量为了不重复计算框架会把这些向量缓存下来这就是 KV Cache。问题在于早期实现习惯给每个请求预分配一整块连续显存按最大长度来算。你请求实际只用了 500 个 token但显存里按 8192 长度预留的那块地已经占住了剩下的空间别的请求进不来。这和早年电脑内存管理一个毛病程序申请内存时按最大需求预留实际用不到那么多但别人也用不了这块地结果就是显存利用率可能只有三成剩下七成全是空洞。并发一多显存很快见底只能排队甚至拒绝请求。1.2 PagedAttention 和连续批处理到底做了什么vLLM 的两个招牌机制正好对应上面两个问题。PagedAttention 借鉴的是操作系统虚拟内存分页的思路把 KV Cache 切成固定大小的块block逻辑上连续、物理上可以散落。一个请求用完的块会立刻归还给公共池子下一个请求马上能接手。这样一来显存碎片基本被消掉实测里同样的卡能塞下的并发数往往能翻两三倍。连续批处理解决的则是调度问题。传统批处理要等一个批次里所有请求都生成完才能开始下一批。假如批次里有条请求要生成 2000 个 token其他请求早就生成完了也只能干等着占坑。连续批处理改成按迭代调度哪个请求生成完了就立刻退出批次队列里等待的请求马上补进来。GPU 在 decode 阶段的利用率因此被拉到很高。顺带说一句prefill处理输入提示和 decode逐 token 生成两个阶段的资源特性完全不同。前者是算力密集后者是访存密集。vLLM 里有个 chunked prefill 的选项就是把超长的 prefill 拆成小块和 decode 混在一起跑避免长提示把 decode 请求饿死。这个参数在长文本场景下很有用后面启动参数那节会细说。1.3 上手前先想清楚这三件事第一你的模型架构 vLLM 支持吗。主流架构各类 Transformer 解码器、部分多模态结构、嵌入模型基本都在支持列表里但一些魔改过的模型或者自研结构可能要自己写适配别上来就硬跑。第二显存够不够。模型权重的显存占用是可以提前估的参数量乘以每个参数的字节数。bf16 精度下7B 模型大约 14 GB13B 大约 26 GB70B 大约 140 GB。这只是权重还要留出 KV Cache 和激活值的空间。如果你只有一张 24 GB 的卡老老实实跑 7B 级别的模型别硬上 13B。第三你打算怎么用。只是本地验证还是对外提供接口。前者可以省掉服务化的步骤后者要提前规划端口、鉴权、日志和监控。这两种用法在启动命令上差别不大但在参数调优上完全是两个方向。2. 环境准备与安装三条路线怎么选安装这一步看起来简单实际上是最容易卡人的地方。vLLM 对 CUDA 版本、PyTorch 版本、Python 版本都有要求选错路线能让你在依赖地狱里泡一整天。2.1 硬件、驱动与系统前提Linux 是首选Ubuntu 20.04 和 22.04 用得最多。显卡方面N 卡从 Volta 架构之后基本都能跑但要注意显存至少得能放下模型。驱动版本建议 525 以上配 CUDA 12.x。查驱动很简单nvidia-smi输出的右上角会显示当前驱动支持的 CUDA 版本。注意这里显示的是驱动能支持的最高版本不是你已经安装的版本。真正的运行时 CUDA 版本可以用nvcc --version查但如果走 pip 安装路线nvcc 其实不一定需要因为 PyTorch 会自带编译好的 CUDA 运行时。Python 版本建议 3.10 或 3.11。3.12 有时候会遇到某些依赖没有预编译轮子需要现场编译很耗时。用 conda 或者 venv 建一个干净环境别在系统 Python 上直接装这是我踩过的第一个坑。提示环境里如果之前装过别的推理框架务必新建独立环境。不同框架对 PyTorch 和 CUDA 的版本要求经常冲突混装的结果往往是两个都跑不起来。2.2 pip 安装、源码编译、容器镜像三条路线对比路线适用场景优点缺点pip 安装快速验证、版本不挑一条命令几分钟搞定只能装发布版本无法改源码源码编译需要改代码、用最新特性灵活性最高编译耗时依赖问题多容器镜像生产部署、环境隔离环境完全一致省心镜像体积大定制略麻烦新手强烈建议从 pip 开始。命令就一行pip install vllm它会自动拉取匹配的 PyTorch 和 CUDA 相关依赖。装完先验证一下python -c import vllm; print(vllm.__version__)能正常打印版本号说明基础环境没问题。这时候千万别急着去跑模型先把版本号记下来后面排查问题、对照文档都要用到它。vLLM 迭代速度很快不同版本之间的参数名和默认值都可能有变化遇到对不上的情况第一反应应该是确认版本而不是怀疑自己命令写错了。2.3 源码构建到底要多久怎么少踩坑需要改源码或者用未发布特性时才走这条路。先克隆仓库然后pip install -e .构建耗时跟机器配置强相关。我实测过的几台机器16 核 CPU 加机械硬盘的云主机从零编译带 CUDA 扩展差不多花了 40 分钟32 核加 NVMe 的机器15 到 20 分钟如果 CPU 核数少于 8做好等一个小时的准备。想加速有几个办法。一是设置并行编译线程数比如MAX_JOBS16让编译用满 CPU。二是把torch之类的重依赖提前用 pip 装好避免从源码编译。三是务必挂载 NVMe 磁盘机械盘上的编译速度能慢一倍以上因为编译过程有大量小文件读写。常见的失败原因显存被其他进程占着导致编译时的 CUDA 检测失败nvcc版本和 PyTorch 要求的 CUDA 版本不一致磁盘空间不足源码编译过程中临时文件能吃掉十几 GB。2.4 Windows 环境下能做什么坦白讲vLLM 的官方支持以 Linux 为主。Windows 上直接 pip 安装大概率会卡在依赖上尤其是需要编译 CUDA 扩展的部分。可行的替代路径有三条用 WSL2 装 Ubuntu 子系统体验和原生 Linux 基本一致显卡也能直通用 Docker Desktop 拉官方镜像或者干脆放弃 Windows在局域网里找台 Linux 机器跑服务Windows 上只做客户端调用。如果你只是想在 Windows 桌面上快速体验模型用本地的桌面推理工具更合适没必要跟 vLLM 较劲。vLLM 的定位是服务端高吞吐不是桌面端一键启动这个定位差异决定了它对环境的要求更高。3. 启动第一个模型命令解析与执行顺序环境好了接下来启动模型。这一步是整个教程的核心我把命令拆开讲顺便把启动时的执行顺序捋一遍理解了顺序出问题时你就知道该看哪段日志。3.1 最简启动命令逐段拆解vllm serve Qwen/Qwen2.5-7B-Instruct \ --host 0.0.0.0 \ --port 8000 \ --gpu-memory-utilization 0.9 \ --max-model-len 8192vllm serve是服务化入口启动后会拉起一个兼容 OpenAI 协议的 HTTP 服务。模型名可以直接写 HuggingFace 上的仓库 ID框架会自动下载如果已经下载到本地写本地路径也行能省掉下载时间。--host 0.0.0.0表示监听所有网卡方便局域网其他机器访问。安全考量上如果这台机器暴露在不可信网络里务必加上鉴权或者用防火墙限制来源别裸着端口。--gpu-memory-utilization 0.9表示允许 vLLM 使用 90% 的显存剩下的留给系统和其他进程。这个值不是越高越好调到 0.95 以上往往会因为驱动自身的开销导致启动失败。--max-model-len是单条请求的最大长度包括输入和输出。这个值直接决定 KV Cache 单块的大小调得越大能并发处理的请求数越少。3.2 vLLM 启动时的执行文件顺序理解启动顺序遇到卡住的情况能快速定位。整体路径大致是这样的命令行入口解析参数把EngineArgs收集起来。初始化引擎创建LLMEngine离线或AsyncLLMEngine在线服务。加载模型权重按分片策略分配到各张卡上。探测显存余量计算能开多少 KV Cache 块然后预分配。启动 worker 进程和调度器准备好请求队列。启动 API server监听端口开始接收请求。日志上你会看到明显的分段。刚启动时打印的是配置信息然后进入权重加载这一步如果模型是第一次下载会明显卡住能看到进度条。权重加载完之后会打印一段 KV Cache 的空间统计最后出现监听端口的提示这时候服务才算真正就绪。很多人误以为看到下载进度条就是快好了实际上后面还有显存探测和缓存分配两个阶段。如果模型权重加载完卡住很久没动静八成是在做显存分配或者编译 CUDA 图。3.3 六个必须搞懂的启动参数参数作用常见取值调优建议--gpu-memory-utilization显存使用上限比例0.8 到 0.92留足余量别贴满--max-model-len单请求最大长度按业务定越短并发越高--tensor-parallel-size张量并行卡数1、2、4、8等于使用的卡数--max-num-seqs单批最大请求数默认即可显存紧张时调小--dtype权重精度auto、bfloat16卡支持就用 bf16--enable-chunked-prefill拆分长提示处理长文本场景开需配合调参重点说下显存估算。拿一张 24 GB 的卡跑 7B 的 bf16 模型举例权重占约 14 GB--gpu-memory-utilization 0.9意味着可用 21.6 GB减去权重还剩 7.6 GB 左右再扣掉激活值和 CUDA 图的开销实际能给 KV Cache 的大概 6 GB 出头。单 token 的 KV Cache 占用可以这样估2K 和 V × 层数 × KV 头数 × 头维度 × 精度字节数以 32 层、8 个 KV 头、头维度 128 的模型为例bf16 下每 token 约 128 KB。6 GB 大约能放下 4.7 万个 token 的缓存。这个数字决定了你同时能服务多少请求、每条能开多长上下文发布会前一定要算一遍别等上线了才发现并发上不去。3.4 多卡张量并行与显存切分模型放不下单卡时加--tensor-parallel-size。4 张卡就写 4vllm serve Qwen/Qwen2.5-72B-Instruct \ --tensor-parallel-size 4 \ --gpu-memory-utilization 0.9张量并行是把每一层的权重按维度切碎分散到不同卡上计算时通过通信把结果拼起来。它的好处是每张卡只需要放一部分权重坏处是卡间通信频繁对带宽要求高。所以张量并行数最好选能被注意力头数整除的值通常是 2 的幂次1、2、4、8。多卡启动时你会看到一条熟悉的日志类似vllm is using nccl某个版本号。这说明框架在用 NCCL 做卡间通信走的是 pynccl 这层封装。单卡启动时不会打印这条所以第一次看到不用慌它是正常信息。真正需要警惕的是通信超时或者初始化失败那种情况通常和卡间拓扑、驱动版本相关我在第七节会展开。4. 服务化与压测把接口跑通再谈性能模型起来了接口能调通只算完成一半。你得知道它在你的业务压力下能扛多少这就要靠压测。4.1 OpenAI 兼容接口启动与调用vLLM 服务默认就提供兼容 OpenAI 的接口路径是/v1/chat/completions和/v1/completions。用官方的 Python 客户端直接调from openai import OpenAI client OpenAI( base_urlhttp://localhost:8000/v1, api_keyEMPTY ) resp client.chat.completions.create( modelQwen/Qwen2.5-7B-Instruct, messages[{role: user, content: 用一句话解释什么是张量并行}], temperature0.7, max_tokens256 ) print(resp.choices[0].message.content)api_key这里随便填因为本地服务默认不校验。如果要对局域网开放务必配置鉴权参数把接口保护起来。这一步很多人在内网测试时省掉了结果服务器一暴露就出事。4.2 vllm bench serve 压测实操vLLM 自带压测工具不用另外写脚本。命令形态是这样vllm bench serve \ --backend openai-chat \ --base-url http://localhost:8000 \ --model Qwen/Qwen2.5-7B-Instruct \ --dataset-name random \ --num-prompts 200 \ --request-rate 10--num-prompts是总请求数--request-rate是每秒发起的请求数。想测极限吞吐就把请求速率调高观察服务什么时候开始出现排队想测不同压力下的延迟表现可以做一组梯度测试比如从每秒 2 个请求逐步加到 20 个记录每档的延迟指标。压测前有几个注意点。一是压测客户端的机器别和服务跑在同一台否则抢资源数据不准。二是先跑一轮短时间的预热让 CUDA 图编译和缓存全部就位再开始正式计数。三是记录的时候把输入输出长度固定住不然结果没法横向对比。4.3 TTFT、TPOT、吞吐量怎么读压测结果里最关键的是三个指标指标含义反映的问题优化方向TTFT首 token 延迟输入处理速度调小 batch、开 chunked prefillTPOT每 token 输出延迟逐字生成速度检查显存带宽和 batch 大小总吞吐每秒输出 token 数整体服务能力提高并发、调大 batchTTFT 高通常说明 prefill 阶段排队严重尤其是长输入场景。TPOT 高则多半是 decode 阶段 batch 太大导致显存带宽打满。这两个指标经常是此消彼长的关系为了吞吐把 batch 调大TPOT 就会上去为了延迟把 batch 调小吞吐又下来。所以调优的本质是在你的业务能接受的延迟范围内把吞吐推到最高而不是追求单项指标极值。5. 落地场景用 vLLM 部署 BGE-M3 嵌入模型很多人以为 vLLM 只能跑生成模型其实较新版本已经支持嵌入模型和多模态模型这一节专门说嵌入模型这条线。5.1 生成模型与嵌入模型启动差异生成模型和嵌入模型的输出结构完全不同前者是逐 token 生成的文本流后者是固定维度的向量。所以启动时要用--task参数区分vllm serve BAAI/bge-m3 \ --task embed \ --port 8001 \ --gpu-memory-utilization 0.6--task embed告诉框架按嵌入模式初始化不再分配生成用的缓存结构。注意显存利用率可以调低一些嵌入模型通常不需要那么大的缓存空间。版本兼容性要留意。嵌入支持是在特定版本之后才完善的如果启动时报参数不认识先确认自己的版本号。查版本用前面说的那条命令别凭记忆猜。较新的版本例如 0.28.0 这类对嵌入和多模态的支持已经比较成熟但每次大版本更新都可能调整行为升级前建议先在测试环境跑一遍。5.2 BGE-M3 部署实测与参数设置BGE-M3 是个多语言嵌入模型支持稠密、稀疏和混合多种检索方式在中文检索场景里用得很多。部署步骤和生成模型基本一致拉起来之后调用from openai import OpenAI client OpenAI(base_urlhttp://localhost:8001/v1, api_keyEMPTY) resp client.embeddings.create( modelBAAI/bge-m3, input[今天天气怎么样, 外面下雨了] ) print(len(resp.data[0].embedding))实测下来单卡跑 BGE-M3 的推理速度很快瓶颈通常在文本预处理和网络传输上。如果你的检索服务 QPS 要求高可以把--max-num-seqs调大一些配合批处理请求能显著提高吞吐。5.3 接口返回结构与业务对接返回的是标准的向量列表每条输入对应一个embedding字段。对接向量数据库时要注意两点一是维度必须和数据库建表时一致改模型等于重建索引二是归一化方式要和当初建库时保持一致有的检索方案要求向量做了 L2 归一化用余弦相似度计算有的直接用内积。这两点搞混了检索结果会莫名其妙变差而且很难查出来原因。注意换嵌入模型是件大事不是重启服务那么简单。新旧模型的向量空间不兼容混用会导致检索结果完全错乱。上线前一定要准备好重建全部索引的方案。6. 工具选型vLLM 和其他方案怎么选市面上推理方案不少知道各自适合什么场景能帮你少走弯路。6.1 和 SGLang 的差异SGLang 和 vLLM 都属于高吞吐服务框架定位有重叠。SGLang 在结构化输出、复杂提示编排方面有自己的优势它的前端 DSL 对多轮对话和约束解码支持得比较顺手vLLM 的生态更成熟模型支持列表更长工具链压测、量化、多模态更完整。选择上我一般这么看如果你的业务里大量涉及严格的 JSON 输出、复杂的提示模板组合SGLang 值得试如果是通用的大模型服务、模型种类杂、团队更熟悉标准接口vLLM 更稳。两者都支持 OpenAI 协议理论上可以互切但线上的 KV Cache 管理和调度策略不一样性能表现会有差异最好用自己的真实流量各测一轮再定。6.2 和桌面端推理工具的差别本地桌面推理工具主打开箱即用图形界面点点鼠标就能加载模型适合个人开发者快速体验和做演示。但它们的设计目标是单机、少量用户并发能力有限显存管理也没针对多请求优化。vLLM 反过来没有图形界面配置都在命令行和参数里学习曲线更陡但换来的是高并发下的吞吐优势和更精细的资源控制。选哪个取决于你的使用场景自己电脑上试试效果桌面工具够了要搭一个多人访问的服务还是得用 vLLM。6.3 什么情况下不该用 vLLM场景一你只需要偶尔跑一两次推理没有并发需求。这种情况下装 vLLM 的环境成本不划算用更轻量的方案更省事。场景二你的模型架构比较特殊vLLM 不支持而你又没有精力去改适配代码。硬上只会浪费时间。场景三你的硬件是消费级显卡并且显存很小。vLLM 虽然在显存利用上做得不错但基础开销摆在那里8 GB 以下的卡跑起来会很勉强。场景四你需要的是低延迟的单条交互比如语音助手这种即时响应场景。vLLM 的强项是吞吐不是极限低延迟这种场景可以考虑其他方案。7. 常见问题与排查技巧实录这部分是我这两年攒下来的实战记录基本覆盖了新手会遇到的绝大多数问题。7.1 显存不足与 OOM 排查顺序报显存不足时别急着换卡按这个顺序查第一看是不是别的进程占着显存。nvidia-smi一下如果发现残留进程先清掉。有时候上一次启动的服务没退干净显存还挂着新进程自然起不来。第二调低--gpu-memory-utilization。从 0.9 降到 0.85 甚至 0.8试试能不能起来。显存这东西留有余地比榨干更稳。第三缩小--max-model-len。这是最立竿见影的一招长度从 8192 降到 4096KV Cache 需求直接减半。第四量化。用 AWQ 或 GPTQ 的量化版本权重显存能降到原来的四分之一左右代价是精度略有损失。对多数业务场景来说这个损失可以接受。第五加卡用张量并行把权重摊开。7.2 NCCL 报错与多卡通信问题多卡启动时通信相关的报错最让人头疼。常见的有卡间通信超时、初始化失败、版本不匹配。排查思路先确认所有卡都能被正常识别nvidia-smi -L列出全部卡数量对不上说明有卡掉了或者驱动有问题。然后确认卡的拓扑nvidia-smi topo -m看卡间是走 NVLink 还是 PCIe走 PCIe 的情况下张量并行的效率会低不少并行数不宜太大。再就是版本问题。多卡通信依赖 NCCL而 NCCL 版本又是跟着 CUDA 和 PyTorch 走的。日志里会打印当前用的版本遇到通信异常先把这个版本记下来去框架的 issue 里搜一下有没有已知问题。升级或者降级 NCCL 通常能解决一部分诡异报错。7.3 版本与依赖冲突这一类问题的典型症状是命令照着文档写参数全对但就是报参数不存在或者模块导入失败。原因几乎都是版本对不上vLLM 更新很快网上教程的版本和你本地的可能差了好几个小版本。处理办法是养成看版本的习惯。启动前先确认框架版本、PyTorch 版本、CUDA 版本三个值然后去对应版本的文档里查参数。别拿旧教程硬套新版本也别拿新文档配旧版本。依赖冲突则优先用干净虚拟环境解决能省掉大量排查时间。7.4 常见问题速查表现象可能原因处理方式启动即报显存不足余量不够或残留进程调低利用率、清进程加载模型卡住不动网络慢或磁盘慢检查下载源、换本地路径服务起来了但请求超时请求长度超出上限调大 max-model-len多卡启动报通信错误拓扑或 NCCL 版本问题查拓扑、对齐版本嵌入接口报参数错误版本不支持 task 参数升级或确认版本吞吐远低于预期请求速率不足或 batch 太小调大并发、看压测曲线首 token 延迟很高长提示阻塞开 chunked prefill8. 我在实际部署中总结的几条经验第一条永远先跑通再调优。我见过太多人一上手就研究各种调度参数结果环境都没装利索。按顺序来环境装好、模型单卡跑通、接口调通、压测有基线然后才谈优化。跳步的结果是出了问题不知道是哪一层的问题。第二条把每次启动的参数记下来。我习惯在项目里放一个serve.sh把最终用的启动命令固化进去包括版本号和参数。换机器、重装环境时直接跑脚本能省掉大量回忆和试错的时间。同时留一份参数变更记录方便回溯为什么某个值调成了现在这样。第三条压测数据要留档。每次改参数之后跑一轮压测把 TTFT、TPOT、吞吐三个值和对应参数一起记在表格里。调优的过程本质上是在做对比实验没有基线数据的话你根本判断不出改动到底是变好了还是变差了。最后分享一个我踩过的坑日志里的警告不要一概忽略。有一类警告是关于显存利用率和缓存分配的看着不起眼但它往往在提示你当前配置已经接近极限流量再涨一点就会出问题。上线前把日志从头到尾读一遍比出事之后翻日志要省事得多。
返回列表