最近手头接了个部署任务,要把 GLM-4.7-Flash 接到我们自己的服务里。以前习惯了直接用脚本加载模型跑推理,这次并发一上来就拉胯,显存管理、请求排队各种问题接踵而至。折腾一圈之后,还是用上了 vLLM,才算真正把推理服务稳定跑起来。这篇文章就完整记录下我这次的实践经验,从框架选型、环境配置到最终压测上线,每一步都有对应的理解和踩坑记录,希望对正在做同样事情的你有参考价值。
先说下背景,GLM-4.7-Flash 属于轻量级对话模型,参数量不大,但“轻量”不代表“好部署”,真正上生产环境后,吞吐、并发、显存占用这些问题全靠推理后端去解决。我最初图省事,直接用了最简单的 Python 加载方式,结果就是单请求延时可观,多请求一来直接显存溢出,服务动不动就挂。换成 vLLM 之后,同样一张显卡,吞吐量提升了不止一个量级。
这篇文章我会把部署链路拆成五个部分讲:为什么选 vLLM、环境准备、模型获取与启动、性能调优和压测、最后是常见坑的排查手册。无论你是刚接触推理部署的初学者,还是准备替换掉现有推理方案的老手,整个过程都能直接照着操作一遍,不用再做额外的知识拼凑。
1. 为什么用 vLLM 来扛 GLM-4.7-Flash:思路与选型
1.1 GLM-4.7-Flash 的部署痛点
先说模型本身。GLM-4.7-Flash 是一类面向高吞吐场景设计的生成式语言模型,具备对话、通用指令理解等能力。部署这种模型的难点主要不在模型架构多复杂,而在推理时动态申请显存这件事非常不可控。Transformer 模型推理时要为每个请求存储 KV Cache(键值缓存),传统推理框架通常会预先分配一个固定的最大长度,比如 2048 或者 4096 token,意味着不管实际生成了多少 token,显存都按最大值占着。一路请求还好,并发一高,普通加载方式基本撑不住。
实际测试中,我用裸 PyTorch 代码加载 GLM-4.7-Flash,8 张并发请求就把一张 24GB 的显卡打满了,而且每请求延迟还在不断上升,最后直接 OOM 崩溃。这其实不是模型本身的问题,而是推理过程的显存管理策略太落后。所有大模型部署到生产环境,本质上都要解决三个核心问题:怎么省显存、怎么提高吞吐、怎么降低单请求延迟。vLLM 正好是这三个问题的综合解。
1.2 主流推理框架怎么选
当前开源的推理方案不止 vLLM 一个,还有 TGI(Text Generation Inference)、TensorRT-LLM 等,我把自己实际用过和调研过的方案放在一起对比过,各有侧重,适合的场景也不太一样。我整理成了一张表,方便你按自己手头的情况判断。
| 推理方案 | 核心优势 | 劣势 | 适合场景 |
|---|---|---|---|
| 裸 PyTorch / transformers | 简单直接,无额外依赖 | 显存浪费严重,吞吐低下 | 单人或极低并发,快速验证效果 |
| vLLM | PagedAttention 显存管理,连续批处理,兼容性好 | 部分自定义算子需要对应版本支持 | 大多数生产环境,高并发服务 |
| TGI | 与 HF 生态深度融合,加载模型方便 | 显存利用率和极限吞吐通常不及 vLLM | 已有 HF 推理栈,想快速迁移 |
| TensorRT-LLM | 性能极致,支持定点量化 | 模型转换流程复杂,调试成本高 | 有专门推理优化资源,追求极限硬件性能 |
我最后选 vLLM,主要看中了它的 PagedAttention 显存管理机制。简单类比一下,传统推理框架预分配显存的方式相当于一个人提前订了好多停车位,不管车停没停满,位置都占着,高峰期其他车想停也没位置。PagedAttention 则是按需分配、动态调度的思路,就像共享停车场,每个请求用多少就给多少,结束即释放,显存利用率自然上去了。
1.3 vLLM 的核心机制:为什么值得学
vLLM 能大幅提升吞吐,除了显存优化,还有两个关键机制。第一个是 Continuous Batching(连续批处理),传统推理过程要等一个批次全部生成完了再接收新请求,vLLM 则是只要有请求完成,空出来的位置立刻分配给下一个请求,调度粒度非常细,GPU 就一直处于满负荷运转状态。第二个是算子层面的优化,比如对 Attention 计算做了融合和并行化处理,减少了 kernel 启动开销。
这套机制对 GLM-4.7-Flash 这类开源模型兼容很好,也符合我预期要并发服务的真实场景,所以在选型阶段我没有再纠结太多,直接确定了 vLLM 作为主线方案。值得提醒的是,vLLM 版本迭代比较快,不同版本对模型架构的支持细节有差异,部署前务必确认当前 vLLM 版本对该模型架构的具体支持情况,避免版本不兼容导致启动失败。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 部署前准备:硬件、系统与基础环境搭建
2.1 显存到底怎么估算才准确
部署前先算一笔账。显存占用主要由两部分构成,一部分是模型权重本身,一部分是推理过程中动态生成的 KV Cache。
以 GLM-4.7-Flash 的大致参数量为基础来估算,如果用 BF16 精度加载,每个参数约占 2 字节,那么 70 亿参数级别对应的权重显存约为 14GB。KV Cache 的大小则跟并发数、最大序列长度、层数、注意力头数都有关,通常先按每请求预留 2 到 4GB 的余量来规划。
我实际使用的是单张 24GB 显存显卡,加载 BF16 权重后剩余空间足够支撑中低并发场景。如果你的显卡只有 16GB,建议直接考虑 INT8 或 INT4 量化来降低权重占用,否则很容易出现加载完模型就逼近显存上限的尴尬情况。可以简单记一条经验公式:
最低显存需求 ≈ 权重占用 + 最大并发数 × 单请求 KV Cache 估算值 + 推理框架自身开销
这个公式虽然没有精确到单个 block 的粒度,但足够用来做选型前的容量规划。
2.2 环境版本搭配不当就是灾难
只要是部署过开源推理项目的人都有这种经验,版本适配才是整个流程里最隐形的时间杀手。我这次部署使用的环境组合是:
- 操作系统:Ubuntu 22.04 LTS
- GPU 驱动:CUDA 12.1 及以上版本
- Python 版本:3.10
- PyTorch 版本:2.1.0(与 vLLM 官方预编译包匹配)
- vLLM 版本:0.4.x 系列
不建议直接用系统自带的 Python 3.8 或 3.9 硬测,很多 vLLM 的预编译 wheel 包对 Python 版本有明确限制。更建议用 Conda 或 venv 单独建一个干净环境,避免把服务器上的系统环境搞乱。还有一个非常容易被忽略的点是磁盘空间,模型文件本身十几 GB,但下载过程会先写缓存,再复制到目标目录,磁盘至少预留模型体积 2 倍以上的空间,否则下载到一半磁盘写满,连排查都难。
2.3 vLLM 安装与安装后的自检
安装 vLLM 本身并不复杂,只要环境匹配,一条命令就能完成:
bash复制pip install vllm
如果是中国大陆网络环境,可以加上国内镜像源,速度会快很多:
bash复制pip install vllm -i https://pypi.tuna.tsinghua.edu.cn/simple
安装完成后不要急着下载模型,立刻跑一个自检命令,确认 vLLM 能正常调用 GPU 资源:
bash复制python -c "from vllm import LLM; print('vllm imported successfully')"
假如 CPU 版本和 GPU 版本混装,或者其他包依赖冲突,这一步就会直接报错,趁早发现能少走很多弯路。vLLM 启动时还会自动检测 GPU 的 CUDA capability。若显卡算力过低,vLLM 可能直接放弃运行,这类情况下只能考虑降低 vLLM 版本或更换推理方案,硬等没有意义。
3. 模型获取与启动服务:从下载到API上线
3.1 模型文件下载与目录组织
模型权重通常从主流的模型托管平台拉取。GLM-4.7-Flash 这类的模型仓库中一般包含模型权重、分词器、配置文件等。我习惯先建一个单独目录存放模型文件,避免和代码仓库混在一起,目录结构大概是这样的:
bash复制mkdir -p /data/models/glm-4.7-flash
cd /data/models/glm-4.7-flash
建议在模型托管平台上找对应的仓库地址,直接克隆或者按照规范下载全部文件。有一点要注意,实际部署前应先确认仓库中的文件是否完整,尤其是 config.json 和分词器相关文件。曾经我遇到过只下载了权重文件漏掉了配置,vLLM 启动时直接报错找不到模型结构,回头重新补文件又得下载半天,纯粹是在浪费生命。
3.2 启动命令与核心参数:每个参数都要理解
拿到模型文件之后,vLLM 的启动命令是核心一环。我用的是一条标准命令:
bash复制python -m vllm.entrypoints.openai.api_server \
--model /data/models/glm-4.7-flash \
--served-model-name glm-flash \
--tensor-parallel-size 1 \
--gpu-memory-utilization 0.90 \
--max-model-len 4096 \
--dtype bfloat16
逐项解释下每个参数的含义,因为理解参数才能调参:
--model指定模型路径,本地路径或模型托管平台上的仓库 ID 均可。--served-model-name是服务对外暴露的模型名,客户端请求时需要保持一致,可以随意取个好记的名字,不必和原始模型完全相同。--tensor-parallel-size指定张量并行的显卡数,单卡部署就是 1,多卡就填显卡数。这里的并行切分由框架自动完成,无需手动改模型。--gpu-memory-utilization是显存利用率上限,vLLM 会预留这个比例之外的显存给 CUDA 上下文和其他开销。我习惯设置 0.90 左右,给系统留出 10% 的余量。--max-model-len表示支持的最大上下文长度,包含输入和输出 token 总数。值设得太大,KV Cache 预留就多,能同时处理的并发请求数会变少;设得太小,长文档场景又会直接报错。需要根据实际业务权衡。--dtype bfloat16指定加载权重和计算的数据类型。多数现代 GPU 对 bf16 支持良好,如果显卡不支持 bf16,改用 float16 也行。
服务启动后,vLLM 会打印出模型加载日志、显存使用情况以及监听端口,默认端口是 8000。看到类似“Starting vLLM API server”的日志时,说明服务已经进入等待请求状态。
3.3 用一条命令验证服务可用性
服务起来之后,先用 curl 发一个最简单的补全请求看下完整链路是否正常:
bash复制curl http://localhost:8000/v1/completions \
-H "Content-Type: application/json" \
-d '{
"model": "glm-flash",
"prompt": "你好,介绍一下你自己",
"max_tokens": 128
}'
返回里带 choices 字段并且内容合理,就代表整个调用链已经通了。这里给大家一个非常实用的建议:启动命令做成一个 shell 脚本,参数用环境变量替代,不同模型、不同显存设置之间切换会非常省事,不至于每次改参数都要重新翻历史命令。
4. 性能调优与压测实录:参数怎么调才够稳
4.1 吞吐量和延迟怎么同时照顾
部署完成后,紧接着面临的就是性能问题。我先后做了两轮压测,第一轮直接用默认参数跑,第二轮微调参数后对比,数据差异很明显。
第一轮压测我是用 Python 脚本并发发送 20 个请求,每请求设置 max_tokens=256,模型并行度为 1,最终测得的吞吐量大约在 420 tokens/s,整体表现已经很不错了。但仔细分析后发现,max-num-seqs(最大并发序列数)默认值在实际场景中并没有完全撑满,于是我在启动命令里追加了 --max-num-seqs 256,并针对性调整了 --max-model-len 2048,让每次迭代能容纳更多的请求参与批处理。
第二轮压测后,吞吐量提升到接近 600 tokens/s,单请求延迟几乎没有明显劣化。这说明一个道理,性能调优本质上是并发数和显存占用之间的平衡。max-num-seqs 调大,参与连续批处理的请求就更多,GPU 利用率更饱和,但单个请求能分到的显存空间会缩小,极端情况下可能导致部分请求因显存不足而被拒绝。gpu-memory-utilization 也可以适当调低留出余量,但调得过低相当于主动放弃性能,建议以 0.85 到 0.92 区间为基准来微调。
4.2 量化方案怎么选,AWQ 还是 FP8
如果显存比较紧张,或者想要继续提升吞吐,量化是一个绕不开的话题。vLLM 可以加载 GPTQ 和 AWQ 等量化后的模型。我个人建议优先考虑 AWQ 量化格式,在大模型场景中 AWQ 的精度损失通常很小,同时能显著降低权重显存占用。
以 GLM-4.7-Flash 为例,4-bit 量化后的权重文件相比 BF16 能减少约 70% 左右的显存占用,相当于原来本来放不下的卡也能跑起来了。不过量化过程中需要准备校准数据集,这一步会稍微有点门槛。如果只是想快速验证效果,可以先试 FP8 或者 INT8 量化,处理流程更省事。我的建议是,先以 BF16 完成全流程部署、确保链路通畅,再根据显存瓶颈决定是否切量化,千万不要一上来就尝试量化,否则到时候排查问题都不知道该看模型问题还是部署问题。
4.3 并发压测脚本与关键指标解读
部署阶段我一直用一个简单的 Python 并发脚本,通过线程池模拟并发请求,统计成功率、平均延迟和吞吐量。核心逻辑大概是这样:
python复制import json
import time
import urllib.request
from concurrent.futures import ThreadPoolExecutor
URL = "http://localhost:8000/v1/completions"
DATA = {
"model": "glm-flash",
"prompt": "用一句话解释什么是大语言模型",
"max_tokens": 256,
}
def send_request():
req = urllib.request.Request(
URL,
data=json.dumps(DATA).encode("utf-8"),
headers={"Content-Type": "application/json"},
)
start = time.time()
with urllib.request.urlopen(req, timeout=60) as resp:
resp.read()
return time.time() - start
with ThreadPoolExecutor(max_workers=20) as pool:
latencies = list(pool.map(lambda _: send_request(), range(20)))
avg_latency = sum(latencies) / len(latencies)
print(f"平均延迟: {avg_latency:.2f}s")
print(f"总请求数: {len(latencies)}")
压测时重点关注三个指标:吞吐量(tokens/s)、首 token 延迟和端到端延迟。有一个比较常见的误区是只盯着首 token 延迟看,这其实只能代表用户感受到的“打字速度”,真正决定服务容量的是连续批处理下的整体吞吐量。生产负载建议直接用压测工具打流量,别用自己手写的脚本硬锁死,条件允许的话可以逐步提升并发,观察服务在哪一层开始报错或延迟暴涨,这个临界点就是当前配置下的容量上限。
5. 常见问题排查与避坑手册
5.1 显存溢出和显卡求救信号
部署过程中最常见的报错就是 CUDA OOM(显存溢出),尤其并发压力一大,显存瞬间爆掉。遇到这类问题,首先用 nvidia-smi 查看显存占用情况,确认是模型权重占了大头,还是 KV Cache 占用太高。如果是权重占用太多,就考虑量化;如果是 KV Cache 导致的,就检查 --max-model-len 和 --max-num-seqs 是否设置得太大。
还有一类情况非常隐蔽,vLLM 启动时显示显存还有空闲,但发送请求后立刻 OOM。这往往是 gpu-memory-utilization 设得太高,导致 CUDA 上下文和运行时没有足够预留空间,请求进来后一分配 KV Cache 就崩。把该参数降到 0.90 甚至 0.85,通常能解决问题。
5.2 模型加载失败与版本兼容性
如果你启动 vLLM 时遇到某个算子或模块无法加载的报错,第一反应别去质疑代码,先检查 vLLM 版本和模型发布时适配的版本是否一致。vLLM 更新频繁,新版本可能引入了一些破坏性变更。我遇到过跑官方示例完全正常、换成本地模型就报错的场景,最后发现是模型配置文件中某个注意力头相关字段与 vLLM 版本解析逻辑不兼容,升级了 vLLM 后问题迎刃而解。所以有个经验分享给大家:部署前先去 vLLM 官方版本说明里查一下当前版本支持的模型架构列表,而不是等报错之后再去翻 GitHub Issues。
5.3 推理结果出现乱码或重复内容
这类问题在生成类任务里不算罕见。排查思路是,先排除输入侧编码问题,尤其注意中文输入是否按照模型预期的方式完成分词;再排查生成参数,比如 temperature 和 top_p 设置得过高,容易导致上下文不连贯或重复。重复内容通常并不是显存或框架问题,而是采样设置不合理。可以顺手把 ignore_eos 这类参数梳理一遍,多数场景都能定位到根因。有一点特别重要,假如量化模型出现了明显的输出质量下降,优先考虑是否校准集和实际业务数据分布差异过大,重新做一次量化往往比改生成参数更有效。
5.4 服务起来了,但外网调不通
有时候本地调用一切正常,换一台机器就连接不上。这类问题大多出在服务监听地址或防火墙配置。vLLM 默认监听 0.0.0.0,一般不会有问题,但要注意安全组和防火墙是否放行了 8000 端口。如果是容器部署,端口映射也要单独检查。还有一个很容易踩的坑是,服务监听的是 IPv6 地址而不是 IPv4,客户端访问时就会出现“无法连接”或“超时”的情况,连接不上时可以先在服务端本机用 curl 确认端口正常,再逐步排查网络路径。
6. 最后再分享一点实测体会
整套部署流程走下来,我最深的感受是:vLLM 的性能上限并不是靠单一参数决定的,而是取决于你是否理解了模型特点、显存余量、并发模型和生成参数之间的约束关系。GLM-4.7-Flash 本身底子很好,但真正把它用顺手的,还是推理框架的选型和对运行参数的持续打磨。从裸 PyTorch 到 vLLM,我的服务承载能力提升非常可观,且稳定性大幅提升。最后建议大家在上生产前多做几轮压测,找出临界点再配置告警。如果手头正打算把某个大模型接进自己的服务,这篇文章中的流程应该能帮你少踩不少坑,祝你一次跑通。
