vLLM部署Qwen与GLM:显存优化与推理服务实战

发布时间:2026/10/21 22:32:10
vLLM部署Qwen与GLM:显存优化与推理服务实战 最近我把自己这边的推理服务从老的 Transformers 接口整体搬到了 vLLM 上主力就是 Qwen 和 GLM 这两个系列7B、9B、14B 都试过单卡、双卡也都搭过。整个过程不算复杂但坑不少而且很多坑不在启动命令本身在于参数搭配和对 vLLM 工作机制的理解上。这篇我打算把整个部署链路从头到尾捋一遍包括选型理由、环境准备、模型下载、启动服务、参数调优和排障记录。如果你正准备用 vLLM 跑 Qwen 或 GLM拿这篇文章当一份参考应该能少走不少弯路。1. 为什么选 vLLM三个绕不开的理由先说结论如果你的目标是开发调试、跑通一个 Demo那用 Transformers 的generate接口也没什么问题但如果是给团队内部或外部提供稳定的推理接口vLLM 几乎是现阶段最省心的选择。原因不只是“快”而是它从根上解决了两个问题显存浪费和吞吐调度。1.1 Transformers 做推理的最大瓶颈不在算力在显存带宽大模型的生成是自回归的每生成一个 token都要把完整的模型权重从显存搬到计算单元里过一遍。拿一个 7B 模型举例FP16 权重大概 14GB每生成一个 token这 14GB 数据就要被完整读一遍。所以实际速度瓶颈往往不是 GPU 算得多快而是显存带宽能搬多快。Transformers 的贪心解码在单请求场景下看着还行但一旦并发上来每个请求都独占一份权重搬运带宽GPU 算力吃不满显存倒是先爆了。vLLM 做的优化本质是“让每一轮前向计算同时处理更多请求”也就是连续批处理。把多个请求的 token 拼进同一个 batch一次权重搬运服务多个请求吞吐自然就上去了。1.2 PagedAttention 把显存浪费率压了下来自回归生成另一个脏大件是 KV Cache。每个请求在处理时都会把历史 token 的 Key 和 Value 缓存下来这部分显存是动态增长的。传统推理框架最常见的做法是预先分配一块定长的连续空间比如按最大长度 2048 给每个请求预留但实际请求可能只用了 200 token剩下的空间全浪费了。序列越长、并发越高这种浪费越夸张有些场景下有效利用率只有三四成。vLLM 的核心技术 PagedAttention 的思路很像操作系统的虚拟内存分页把 KV Cache 切成固定大小的块按需分配物理上不需要连续。这样一来显存利用率明显提升同一个卡上能跑的并发请求也多了一截。这是我选它的核心原因不是某个 Benchmark 数字好看而是同样的显存能装下更多同时在跑的会话。1.3 Continuous Batching 让单卡并发不再像挤牙膏传统静态 Batching 要等一整批请求全部结束后才释放资源慢请求会拖住快请求。vLLM 的 Continuous Batching 按迭代粒度做调度每一轮只把当前活跃请求的 token 放进去计算某个请求生成完了立刻移出新请求随时插进来。实际体验就是并发请求多了吞吐稳步上涨单个请求的延迟不会因为某一个长回复被整体拖垮。当然vLLM 也有它的适用边界。它主要面向推理服务不是训练平台虽然新版支持 LoRA 和简单微调但拿它当训练工具用并不合适。另外它对单卡显存比较敏感模型太大或者显存太小的时候需要搭配量化策略后面会专门讲。2. 环境准备显卡、CUDA、Python 版本一次配对vLLM 对环境的依赖比普通 Python 库更敏感经常有人在这里翻车。我之前就在 CUDA 和 PyTorch 版本不匹配上折腾了整整半天后来总结下来环境部分只要盯住三件事就能少踩大部分坑。2.1 显存需求先算清楚再买卡很多人拿到模型第一反应就是跑结果启动直接 OOM。显存需求其实可以提前粗算公式很简单模型权重显存 参数量 × 每个参数占用字节数FP16 / BF16 下每个参数占 2 字节7B 模型权重约 14GB9B 约 18GB14B 约 28GBINT8 量化每个参数占 1 字节INT4 / AWQ 量化大约 0.5 字节左右KV Cache 和激活值另算一般来说至少给 8GB 到 16GB 的余量基于这个算法我的建议是跑 7B 到 9B 级别的 FP16 模型24GB 显存最舒服16GB 显存要配合量化或者压缩上下文长度跑 14B 级别40GB 单卡或者双卡 24GB 才比较宽裕。GLM-4-9B 别看参数是 9BFP16 权重比 7B 大不少拿 16GB 卡硬跑很容易启动失败。2.2 一条命令装好全套 Python 环境我现在的标准做法是先用uv建独立虚拟环境再装 vLLM避免把系统 Python 搞乱uv venv vllm-env --python 3.11 source vllm-env/bin/activate uv pip install vllm如果没有uv直接用python -m venv加pip install vllm效果一样。Python 版本建议 3.10 或 3.11有的老项目用 3.8 也能装但没必要给自己找麻烦。vLLM 的依赖会连带装好 PyTorch、transformers、tokenizers 这些不需要单独手动装除非你已经有特定版本的 PyTorch 想要复用。GPU 驱动和 CUDA 这块我提一句vLLM 是编译好的 wheel它对 CUDA toolkit 的依赖不像想象中那么重关键在 NVIDIA 驱动版本要够新。一般建议驱动版本能支持 CUDA 12.x 或 11.8Ampere 以上架构问题不大。如果是比较老的 Maxwell、Pascal 卡vLLM 支持有限实际情况里我基本不建议强行跑。2.3 vLLM 版本与 PyTorch 的对应关系装 vLLM 时最容易忽略的是版本选择。它的版本迭代非常快不同版本对模型格式、启动参数和默认行为的处理都不一样。比如早期版本需要手动指定--use-v2-block-manager新版已经默认用 V1 引擎了有些老参数在新版里直接被移除照着旧教程敲命令会报错。我的建议是尽量装较新的正式版别贪图某一个特定版本稳定就不升级。vLLM 对 Qwen、GLM 这类新模型的支持都是靠新版本带进去的旧版本很可能不认识新模型的 config。拿到版本后最好看一眼对应 release notes确认一下serve命令的 CLI 参数有没有变化。这一条虽然听起来是废话但实际排障的时候非常管用。3. 模型下载Qwen、GLM 从仓库到本地目录有了环境下一步就是拿模型权重。很多人习惯直接让 vLLM 从远程仓库拉但我强烈建议先把权重下载到本地目录再让 vLLM 加载本地路径。原因很简单启动的时候再现场下载一旦网络抖动或者仓库超时服务就卡死在启动阶段本地权重可以反复重启、回滚版本排查问题方便得多。3.1 开源模型的目录结构怎么看不管是 Qwen 还是 GLM一个标准的开源模型仓库里通常包含这些文件config.json模型结构配置vLLM 靠它识别模型类型和层数model.safetensors或分片文件真正的权重tokenizer.json、tokenizer_config.json、tokenizer.model分词器generation_config.json生成参数默认值chat_template字段通常在tokenizer_config.json里决定对话时的输入拼装方式下载之后第一件事不是急着启动而是打开tokenizer_config.json确认里面有没有chat_template。这个字段很关键如果仓库是 base 基座版本而不是 Instruct/Chat 对话版本可能没有这个字段vLLM 会退回默认模板对话效果会明显变差。3.2 推荐走 ModelScope 拉权重国内环境下载 HuggingFace 仓库经常速度感人我这边团队基本都是走 ModelScope 拉权重稳定而且快。Python 里直接调用就行from modelscope import snapshot_download snapshot_download( Qwen/Qwen2.5-7B-Instruct, local_dir./models/Qwen2.5-7B-Instruct ) snapshot_download( zai-org/glm-4-9b-chat, local_dir./models/glm-4-9b-chat )注意这里的仓库 ID 可能会随着版本变化用的时候可以去 ModelScope 页面确认一下最新路径。local_dir参数我建议一定指定默认会下载到缓存目录路径很怪不利于后面写启动脚本。如果网络环境可以直接访问 HuggingFace也可以用huggingface-cli download拉同样的目录结构。两种方式得到的文件内容是一致的不用混合着下载避免分片文件不齐。3.3 本地模型路径验证清单下载完成后启动前花一分钟做下面几个检查能省不少事确认model.safetensors文件大小和仓库页面标称一致分片模型要确认所有分片都在确认config.json里model_type是 vLLM 认识的类型Qwen、GLM 都支持别下成 GGUF 之类的转换格式确认本地路径里没有中文、没有空格尤其是 Windows 环境如果是量化模型确认config.json里有quantization_config配置后面启动要传对应参数我见过有人把权重文件全部解压到一个带空格的目录然后启动时路径没加引号脚本一直报文件不存在。路径规范从一开始就要立好。4. 一条命令拉起 OpenAI 兼容服务环境有了模型下载好了现在可以进入正题。vLLM 提供了一套 OpenAI 兼容的 HTTP 接口这意味着你不需要额外写包装层很多现成的客户端和工具链可以直接对接。4.1 vllm serve 的关键参数以 Qwen2.5-7B-Instruct 为例我常用的启动命令是这样vllm serve ./models/Qwen2.5-7B-Instruct \ --served-model-name qwen2.5-7b \ --host 0.0.0.0 \ --port 8000 \ --max-model-len 8192 \ --gpu-memory-utilization 0.85GLM-4-9B-Chat 几乎一样换成目录和模型名即可vllm serve ./models/glm-4-9b-chat \ --served-model-name glm-4-9b \ --host 0.0.0.0 \ --port 8000 \ --max-model-len 8192 \ --gpu-memory-utilization 0.85这里逐个解释一下我为什么这样设参数--served-model-name是对外暴露的模型名客户端调用时model字段必须传这个名字。你可以随便起不一定要和仓库名一致。--host 0.0.0.0表示监听所有网卡这样同一局域网内其他机器也能访问。如果只本机访问用127.0.0.1更安全。--max-model-len是最大序列长度包括输入的 prompt 和生成的输出。这个参数直接影响了 KV Cache 的预留量后面专门讲。--gpu-memory-utilization表示 vLLM 最多使用多大比例的 GPU 显存默认是 0.9我习惯设成 0.85留一点余量给驱动和加载进程。启动后看到类似Starting vLLM API server on http://0.0.0.0:8000的日志说明服务已经起来了。4.2 用 curl 验证服务服务起来后先看模型列表curl http://localhost:8000/v1/models正常会返回 JSON里面包含你设置的模型名。然后发一个最基本的对话请求curl http://localhost:8000/v1/chat/completions \ -H Content-Type: application/json \ -d { model: qwen2.5-7b, messages: [ {role: user, content: 用一句话解释什么是KV Cache} ], max_tokens: 256, temperature: 0.7 }返回 JSON 里choices[0].message.content就是模型输出。这一步能通说明模型加载、分词、推理、接口链路都是好的。4.3 用 OpenAI SDK 调用有了 OpenAI 兼容接口客户端这边直接用常见的 OpenAI SDK把 base_url 指过去就行from openai import OpenAI client OpenAI( base_urlhttp://localhost:8000/v1, api_keyEMPTY ) response client.chat.completions.create( modelqwen2.5-7b, messages[ {role: user, content: 介绍一下连续批处理机制} ], max_tokens512, temperature0.7 ) print(response.choices[0].message.content)api_key随便填因为 vLLM 默认不开鉴权。如果要开启动参数里加--api-key指定密钥客户端传对应的值。我自己的体会是OpenAI 兼容接口带来的收益很直接团队里的业务代码可以完全忽略模型后端是什么只依赖一套标准协议。后面就算把模型从 Qwen 换成 GLM只要起一个新 vLLM 实例业务代码基本不用动。5. 线上稳定运行要盯的参数服务能跑通只是第一步真正要上线参数调优才是重头戏。以下这几个参数我几乎每次部署都要反复试任何一个设错都可能造成显存溢出、并发上不去、或者模型输出质量崩掉。5.1 --max-model-len 怎么设--max-model-len决定 vLLM 为每个请求预留多大缓存空间。Qwen2.5 系列官方支持 128K 上下文GLM-4 甚至支持到 128K但“支持”和“用起来不爆显存”是两码事。如果你直接设 128K在普通 24GB 卡上KV Cache 会吃掉大量显存能支持的并发数就变得很小。我的经验是按真实业务场景反推先统计一下线上请求的平均 prompt 长度和期望输出长度然后把这个数字乘 2 到 3 作为安全余量。比如你的业务平均输入 2000 token输出 1000 token那么设置 8192 已经非常充裕了完全没有必要设 32768。--max-model-len设小了也有代价请求超过就会报错。这个可以提前在客户端做长度校验或者在 nginx 层拦截超长请求别让异常流量打到模型端。5.2 --gpu-memory-utilization 别照抄 0.9很多人看到默认 0.9 就不管了这是线上 OOM 的一大来源。0.9 意味着 vLLM 会一次性申请 90% 的显存包括权重和 KV Cache 预分配。听起来没问题但 GPU 上还有 CUDA context、驱动库、加载进程的临时缓冲区这些都要占空间。申请到顶后一些浮点运算和临时 tensor 就没地方放了。我一般从 0.8 起步如果是老卡或者显存刚好卡着模型权重直接调到 0.7。判断标准是在高并发压测下连续跑 10 分钟观察nvidia-smi的显存使用曲线稳定且没有 spike 到 100% 才算合格。5.3 多卡时 tensor-parallel-size 要谨慎模型单卡放不下时可以用张量并行把权重切到多卡上vllm serve ./models/Qwen2.5-14B-Instruct \ --served-model-name qwen2.5-14b \ --tensor-parallel-size 2 \ --max-model-len 8192 \ --gpu-memory-utilization 0.85但这里有个很容易被忽略的点张量并行对卡间通信带宽非常敏感。两张卡之间如果有 NVLink 或 NVSwitch效果很好如果只是普通 PCIe 通信吞吐会明显打折。所以不是任何两台机器凑一起就能组双卡先确认硬件拓扑。另外--tensor-parallel-size必须小于等于卡数而且要求所有卡显存一致。我遇到过一张 24G 和一张 16G 的机器强行 TP2 也能起来但因为小的那张卡成了瓶颈整体性能反而不如单卡跑小模型。5.4 量化模型AWQ 和 FP8 的取舍显存不够时量化是最直接的解法。Qwen 和 GLM 都有官方或社区发布的 AWQ、GPTQ 量化版本。启动时指定量化方式vllm serve ./models/Qwen2.5-7B-Instruct-AWQ \ --served-model-name qwen2.5-7b-awq \ --quantization awq \ --dtype halfAWQ 4bit 能把 7B 模型的权重压到大约 4GB 左右16GB 老卡也能轻松跑。FP8 则是新版显卡上比较灵活的选择如果显卡不支持原生 FP8 计算vLLM 会退回软件模拟速度反而比 FP16 慢。我的取舍建议很简单7B 到 9B 这种中小规模模型如果显存还有空间别量化FP16 效果最稳14B 以上或者 24G 以下的卡优先试 AWQ输出质量影响通常可控。还有一点量化模型的chat_template一般和原版一致但如果发现量化版输出乱码、多出特殊符号先检查是不是下载了 base 模型而不是 chat 版本。5.5 前缀缓存和其他提速特性多轮对话场景里长上下文会有大量重复前缀比如系统提示词和前面的历史消息。vLLM 的--enable-prefix-caching就是干这个的相同前缀只需要计算一次后续请求直接复用 KV Cache。多轮对话和高并发场景下效果非常明显。新版本 vLLM 默认启用 V1 引擎和连续批处理调度一些老的调优参数已经不需要手动指定了。我的习惯是升级版本后先看日志里的引擎信息确认默认配置生效再决定要不要额外调参数避免拿旧思维设一些已经不生效的选项。6. 踩坑实录实测中的完整排查链路部署期间我大概踩了快十来个坑挑几个有代表性的说说完整排查过程。这些问题我相信很多同学迟早都会遇到。6.1 显存被打爆的排查过程现象启动命令敲下去vLLM 先加载权重然后直接报错退出。日志末尾出现No available memory for the cache blocks有时候是 CUDA OOM。第一次遇到我以为是模型权重太大直接把模型从 14B 换到 7B结果还是报错。后来用nvidia-smi一看发现显存被一个僵尸 Python 进程占掉了一半vLLM 根本没多少空间可用。杀掉进程后同一个命令就正常起来了。排查顺序建议是这样先看nvidia-smi确认显存是不是被其他进程占着看启动日志里 vLLM 打印的显存计算确认模型权重占多少、KV Cache 计划分配多少逐步调低--gpu-memory-utilization0.9 降到 0.8还不行就降到 0.7再把--max-model-len从 32768 降到 8192显存占用会立刻降下来这个问题的根因往往不是某一项参数而是几个参数叠加起来超过物理显存。记住一个原则vLLM 启动时申请的显存是权重加 KV Cache 的总和这两部分此消彼长想让权重多就要牺牲上下文长度或并发空间。6.2 输出重复、内容崩坏现象单卡 7B 模型跑简单问答没问题但稍微长一点的对话输出开始循环重复同一句话或者后半段夹杂大量换行和特殊标记。排查链路是这样的刚开始我以为是量化精度损失换回 FP16 模型问题还在。后来对比了官方 demo 的采样参数才发现问题出在temperature设置和惩罚参数上。vLLM 的推理接口是可控的如果业务代码把temperature设成 0模型在多轮长上下文下很容易陷入重复循环。我给团队定了一个基线temperature0.7、top_p0.8、frequency_penalty0.3、presence_penalty0.6跑大批测试用例后重复问题基本消失。另外上下文长度接近--max-model-len上限时模型输出质量也会明显下降这并不是模型坏了而是需要在应用层限制对话轮数。6.3 并发一高就大面积排队现象单请求延迟都很正常但用脚本压测同时来 20 个请求吞吐没有线性增长反而部分请求等了很久才返回。排查后发现vLLM 的 Continuous Batching 本身没问题问题出在--max-model-len设置得过大导致 KV Cache 预分配空间减少实际能同时并行的序列数被卡住了。日志里会有明显的排队等待信息。解决方案有两个方向一是把--max-model-len降到业务合理值给并发腾空间二是明确设置--max-num-seqs限制单次批处理的最大序列数避免极端情况下瞬间申请太多缓存。我建议上线前先用压测找一下这台 GPU 的饱和点比如并发 32、64、128 各跑一轮看延迟的 P99 在哪个档位开始失控然后反过来设置阈值。6.4 加载非对话版本导致输出格式怪现象用 GLM 的某个仓库地址下载模型调用接口后模型完全不按对话格式回话直接把用户的输入补全了一段或者拼命输出特殊标记。问题出在仓库选错。GLM 系列区分基础模型和对话模型需要下载带chat标识的版本。我在本地目录里检查了tokenizer_config.json发现里面没有完整的chat_template字段所以 vLLM 只能套默认模板拼装出来的 prompt 不符合预期。修复方法是换正确的仓库重新下载然后再次验证本地文件里pre_tokenizer_config.json、chat_template都在启动后先发一条最简单的“你好”测试。这个坑在 Qwen 系里相对少见因为 Qwen 的 Instruct 版本标识很明确但 GLM 那边很容易误拿基础版。6.5 OpenAI SDK 调用时报 404 或 400现象curl 测试都正常但用 OpenAI SDK 调用时返回404 model not found或400 invalid messages。前者几乎都是--served-model-name和客户端model字段不一致导致的。改法很简单要么改启动参数要么改请求里的 model 名两边对齐即可。后者一般是 messages 格式不规范比如role写成了system之外的自定义值或者content不是字符串而是数组。OpenAI 协议对消息格式有明确要求业务层传参之前要做一次校验。vLLM 兼容接口已经尽量宽松了但乱传字段还是会挂。7. 压测结论与上线建议部署调参都完成后上线前我习惯做一轮压测确认自己的参数配置真的能扛住预期流量而不是“看起来能跑”。7.1 简单压测脚本参考这个思路用 Python 并发触发一批请求统计成功率和耗时import concurrent.futures import time from openai import OpenAI client OpenAI(base_urlhttp://localhost:8000/v1, api_keyEMPTY) def run_one(i): start time.time() resp client.chat.completions.create( modelqwen2.5-7b, messages[{role: user, content: f这是第{i}个请求请简单回复}], max_tokens64, temperature0.7 ) latency time.time() - start return latency with concurrent.futures.ThreadPoolExecutor(max_workers32) as pool: latencies list(pool.map(run_one, range(128))) print(avg:, sum(latencies) / len(latencies)) print(p95:, sorted(latencies)[int(len(latencies) * 0.95)])这个脚本不严谨但足够观察趋势并发从 16 提升到 32 时平均延迟如果翻倍甚至更多说明服务接近瓶颈需要回炉调参或者换更大显存。7.2 上线前的检查清单我最终上线前会过一遍这份清单模型目录、启动脚本、模型名三者对应一致健康检查接口/health返回正常监控里能看到日志启动了--api-key或在前端加一层鉴权设了超时和重试策略避免客户端无限等用/metrics接入 Prometheus观察每秒请求数和 KV Cache 利用率确认--max-model-len不小于业务最长输入加最大输出否则线上会随机报错这些看起来琐碎但都是真实线上故障的高发点。尤其鉴权和超时这两条很多团队部署完就忘结果某个同事随手调接口把显存打满整个推理服务一起被拖垮。7.3 后续扩展方向vLLM 这套方案并不局限于某一两个模型。我在实际项目中还试过把多个模型各起一个实例用一层路由按业务模型名转发也试过在同样的实例上加载多个 LoRA 适配器一份权重服务多种场景。对于大部分团队来说vLLM 加上通用 OpenAI 客户端这套组合已经能把大模型推理这块做到“够用、稳定、可观测”。最后再分享一个小技巧升级 vLLM 之前先在测试目录把现网启动命令完整跑一遍确认新版本没有移除或改变关键参数再切流量。这个版本兼容问题几乎每个用过 vLLM 的人都会遇到一次提前验证能省掉一次尴尬的回滚。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询