之前帮某开发者的“模拟项目X”做大模型部署时,我在 GPU 服务器上对比过好几套推理方案,踩完一圈坑之后,现在手里的长期方案基本固定成了 LMDeploy。如果你手里已经有一个开源对话模型,正打算放进业务里做 API 服务,或者只是想在本地起一个能跑的演示环境,这篇博文会把部署链路完整拉通一遍:环境准备、模型转换、W4A16 量化、服务启动、客户端调用、性能测试,以及我在现场遇到过的各种坑。整个过程不依赖特定云厂商,一套流程在自己笔记本或服务器上都能复现。1. 为什么最终留下了 LMDeploy1.1 几种主流推理方案的取舍大模型部署这件事,市面上不缺方案。最早想省事,很多人会直接拿 Transformers 配合原生推理跑,好处是零成本,坏处也很明显:并发一上来,显存直接爆掉,速度也平平。之后我试过 vLLM,它的 PagedAttention 确实把显存利用率拉了上去,吞吐量非常好看,但遇到量化支持、batch 动态变化、小模型调优这些场景,参数一多就有点烦。LMDeploy 的特点在于它的定位很巧:不只是一个推理引擎,而是一整套“参数压缩 + 推理优化 + 服务化”的工具链。它默认支持 KV Cache 量化、W4A16 权重量化、连续批处理、CUDA Graph、自定义注意力算子,而且这些能力通过简单命令就能组合起来。我个人的感受是,它对中大型对话场景的默认配置就已经很合理,不需要为了跑起来去读大量源码或反复调参。1.2 LMDeploy 更让人省心的三个点第一个点是显存占用。一个几十亿参数的模型,FP16 原始权重就要占掉两位数 GB 的显存,而用 W4A16 量化后,权重部分能压到原来的四分之一左右,原本勉强放的下的卡可以腾出更多空间给 KV Cache 和并发请求。第二个点是部署链路的完整性,从模型转换到启动 API 服务,再到 C++ 运行时推理,一条命令走完,学起来非常直接。第三个点是它对 OpenAI 格式接口的兼容,业务方接入时几乎不需要改代码,原来的请求结构直接复用。提示:LMDeploy 不是一个“又一个套壳工具”,它底层换了自己的算子实现。所以同一个模型,在 Transformers 里跑和在 LMDeploy 里跑的显存曲线和延迟表现会有明显差异,这也是为什么值得单独写一篇部署实战的原因。2. 环境准备是“稳”字当头2.1 版本匹配怎么确认LMDeploy 对运行环境有明确要求,建议遵循官方文档的推荐秩序。我自己的经验是:先用 conda 新建一个 Python 3.10 环境,再把 PyTorch 装成 CUDA 12.x 对应的版本,最后安装 LMDeploy。这个顺序不能乱,因为 LMDeploy 在安装时会根据当前 PyTorch 的 CUDA 版本来编译或拉取匹配的算子库,如果先装 LMDeploy 再回头改 PyTorch,很容易出现 libcudart 找不到的诡异报错。具体确认驱动时,直接运行nvidia-smi看右上角 Driver 版本,再确认 PyTorch 里torch.cuda.is_available()是否为 True。很多人会忽略一个小细节:即使系统里装了显卡驱动,conda 环境里也不一定有对应的 CUDA 工具链,二者要分开看。2.2 安装与安装包自救在干净环境里执行以下命令即可完成基础安装:conda create -n lmdeploy python=3.10 -y conda activate lmdeploy pip install torch --index-url https://download.pytorch.org/whl/cu121 pip install lmdeploy安装完成后,用一条命令验证核心模块是否就位:python -c "import lmdeploy; print(lmdeploy.__version__)"如果你所在的内网环境无法直接访问 PyPI,提前准备离线安装包是更稳妥的办法。在外网机器上执行pip download lmdeploy -d ./lmdeploy_packages,把整个目录拷进内网,再在内网执行pip install --no-index --find-links=./lmdeploy_packages lmdeploy。我在几次现场部署中遇到最多的问题就是内网源同步不全,离线包一定要连依赖一起下载,只下主包会出现一堆缺依赖的连环报错。3. 先把模型转换这一件事做好3.1 转换命令与目录结构部署前,我习惯先把原始权重转换成 LMDeploy 的专有格式。这一步不是模型文件本身的格式变化,而是为了让推理引擎在加载时能直接读取量化后的权重和缓存布局,省去每次启动都现算量化参数的重复开销。命令行形式很直观:lmdeploy convert ./models/chat-7b \ --model-name chat-7b \ --dst-path ./workspace/chat-7b-w4其中./models/chat-7b是原始模型目录,正常应该包含config.json、tokenizer.model和分片权重文件。--model-name是给转换后格式做兼容标记的关键参数,不同模型架构要写对应的名字,比如标准对话模型填chat-7b,多模态模型再按场景调整,转换脚本会用它推断层数、头数、词表大小等细节。转换完成后,workspace/chat-7b-w4目录里会有config.json、model.bin等文件。你可以用du -sh看一眼总大小,通常这个目录的体积会明显小于原始 FP16 模型。3.2 W4A16 量化到底做了什么W4A16 的意思其实非常直白:把模型权重压到 4bit 存储,但在计算时仍然把权重反量化回 FP16 做矩阵乘,激活值全程保持 FP16。这样做的原因是推理时的显存瓶颈主要在权重本身,而计算精度受影响的更多是激活的累积误差。4bit 量化的基本逻辑是给一小块权重找一组 scale 和 zero point,把原始浮点数映射到 4bit 的整数范围内。比如对一个 16 个数的权重向量,取其中的最大值,算一个缩放系数,再把每个权重除以这个系数并四舍五入到整数。恢复时乘以同样的系数就能大致还原。因为 LMDeploy 默认会做分组量化,不是整层共用一个 scale,所以精度损失在对话场景中通常很小。量化命令默认走向的正是这种 W4A16 方案。如果你的部署场景对显存极度敏感,还可以考虑把 KV Cache 也量化成 4bit,但日常先把权重量化到位,收益就已经非常明显了。3.3 正确性验证量化完成后不要直接上服务,先跑一个快速验证脚本,用同样的 prompt 分别加载原始模型和量化模型,各生成一小段文本,人工目测一下语义和流畅度。IanN 我自己的经验是,如果量化后输出出现大面积乱码或者语义断裂,八成不是量化本身的问题,而是转换时的模型名写错导致兼容层匹配失败,重新校验目录和模型名之后多半能恢复。如果对速度指标感兴趣,还可以顺手记录一下原始模型和量化模型各自加载到 GPU 的耗时。量化模型的加载通常明显更快,因为要读取的字节数少了很多。4. 本地 dryrun 之后再谈启动服务4.1 notebook 里先把模型跑通我强烈建议在启动对外服务之前,先在 Python 里用 pipeline 接口做一次本地 dryrun。这一步的意义在于:把“模型能不能跑”和“服务通不通”两个变量分开排查。直接在 load 阶段就报错,基本都是权重目录或模型名问题;宁可在脚本里发现,也不要让线上服务启动到一半才暴露。from lmdeploy import pipeline, TurbomindEngineConfig engine_cfg = TurbomindEngineConfig( model_path="./workspace/chat-7b-w4", tp=1, cache_max_entry_count=0.5, ) pipe = pipeline(engine_cfg=engine_cfg) resp = pipe(["请用一句话介绍你自己"]) print(resp[0].text)如果这一步能正常输出,说明模型加载、推理算子和显存分配都过了关,可以继续下一步搭建服务。4.2 API 服务启动与关键参数LMDeploy 提供了和 OpenAI 接口风格一致的 API 服务,启动方式也足够简单:lmdeploy serve api_server \ ./workspace/chat-7b-w4 \ --model-name chat-7b \ --server-port 8000 \ --tp 1 \ --cache-max-entry-count 0.5这里几个参数我单独说明下。--tp 1表示单卡推理,如果你的权重要跨多张卡切分,改成对应的张数就行。--cache-max-entry-count 0.5这个参数直接决定了 KV Cache 在显存中的占用上限:0.5 表示最多允许 KV Cache 占用约一半显存,剩下的留给权重和计算。实际调优时我会根据并发需求反复调这个值,并发高就调大,留给其他程序的显存不够就调小。还有一个容易被忽略的--session-len参数,它控制单条会话的最大上下文长度。模型训练长度是几千 tokens,但部署时不一定需要给满,按业务需要截短一些可以省下不少 KV Cache 空间,对并发量提升非常友好。4.3 显存里悄悄占地方的几块部署时很多人只看权重体积,其实显存占用由好几块叠加而成:权重显存、KV Cache、激活值、临时计算缓冲。权重量化主要解决第一块,KV Cache 则由cache-max-entry-count和session-len共同调节。激活值和临时缓冲更依赖 batch 大小和输入长度,并发越高,这部分绝对值也会增大。理解了这个模型,遇到“显存明明够,跑起来却 OOM”的现场,就能快速定位到是 KV Cache 或临时缓冲的锅,而不是盲目去缩模型。5. 接业务之前先写一个请求客户端5.1 流式对话调用服务一旦跑起来,立刻用 curl 或 Python 客户端打一发请求,确认返回结构符合预期。我自己常用的一段流式调用代码大致长这样:import requests url = "http://127.0.0.1:8000/v1/chat/completions" payload = { "model": "chat-7b", "messages": [{"role": "user", "content": "写一段关于大模型部署的总结"}], "temperature": 0.7, "max_tokens": 512, "stream": True, } resp = requests.post(url, json=payload, stream=True) for line in resp.iter_lines(): if line: print(line.decode("utf-8"))非流式请求只需要把stream改成 False,返回体里的choices[0].message.content就是完整生成文本。前端对接时,优先把流式打开,首字返回时间会好看很多,用户感知上也更接近真实对话体验。5.2 并发基线的重要性单条请求通了之后,我还会顺手做一次小压测。不需要复杂的压测工具,用一段简单的 Python 脚本并发发十几个请求,观察有没有超时、报错和显存溢出就够了。这事的价值在业务接入前就能提前暴露显存配置上的问题,而不是等到业务方压力测试时才手忙脚乱。并发如果出现 OOM,最直接的调整是降低cache-max-entry-count,或者给服务加--max-batch-size限制同时处理的请求数。记住一个原则:宁可让请求排队,也不要让显存被打满后整个服务崩溃。排队只是延迟,崩溃则是可用性事故。6. 性能数据怎么测才不会骗人6.1 指标定义:首token、吞吐量、时延在汇报性能数据之前,先把指标口径说清楚。通常关注三个值:首 token 延迟、单请求完整时延、吞吐量。首 token 延迟指的是从请求发出去到模型返回第一个 token 的时间,它和输入长度、模型计算速度、排队时间强相关;完整时延是整段回答生成结束的时间;吞吐量则是系统稳定运行时每分钟或每秒能生成的 token 总数。这三个值不是相互独立的。压测时并发提高,吞吐量往往上升,但单请求的完整时延也会拉长,因为它们共享显存和算力资源。我看到过很多人只甩一个“吞吐量很高”的结论,却不说明并发数和卡数,这没有任何对照价值。6.2 一个小型压测脚本以下是一段足够用来做横向对比的脚本框架,你可以根据自己的字段调整:import time import requests from concurrent.futures import ThreadPoolExecutor url = "http://127.0.0.1:8000/v1/chat/completions" prompt = "用三句话解释什么是 KV Cache,语言要通俗。" def send_one(_): start = time.time() resp = requests.post(url, json={ "model": "chat-7b", "messages": [{"role": "user", "content": prompt}], "max_tokens": 128, }) cost = time.time() - start return cost, resp.status_code with ThreadPoolExecutor(max_workers=8) as pool: results = list(pool.map(send_one, range(40))) costs = [r[0] for r in results] errors = [r for r in results if r[1] != 200] print(f"平均时延: {sum(costs)/len(costs):.2f}s") print(f"最大时延: {max(costs):.2f}s") print(f"错误数: {len(errors)}")如果你想测“纯推理速度”,应该把 prompt 固定为某个长度,把max_tokens固定,并且保持并发数不变,这样出来的结果才能用于横向比较。如果只是随手测一测,prompt 忽长忽短,数据完全没有可比性。6.3 结果怎么解读我在某次对比测试里,用原始 FP16 模型和 W4A16 量化模型分别跑了同一批 100 条请求,量化后显存占用降到了原来的四成左右,首 token 延迟略有下降,完整生成速度基本持平。这个结果符合预期:量化降低了显存压力和 IO 压力,在线性层计算上因为重量化开销,速度不会简单提升,但整体稳定性反而更好。做性能汇报时,我建议固定测试条件写清楚:显卡型号、量化方式、并发数、prompt 平均长度、max_tokens。否则数据拿给旁观者看,根本没法判断是好是差。7. 部署现场最容易踩的坑7.1 显存不足与预分配显存问题在部署现场出现的频率最高。有一种常见情况是:启动 log 显示一切正常,但一进来并发请求就 OOM。排查思路很简单,先用nvidia-smi看进程实际占用,再调整cache-max-entry-count降低缓存预分配。如果模型是多卡跑的,还要确认是否真的做了张量并行,很多报错其实是权重被重复加载到了每张卡上。7.2 量化后质量波动量化后模型输出质量出现波动,先别急着回退到全精度。第一步确认量化分组数是否被正确识别,第二步用同样的 prompt 多测几轮,因为生成结果本身有随机性。质量波动如果持续存在,建议对照原始模型的输出长度和内容风格,判断到底是量化引入的偏差,还是 temperature 和 top_p 设置差异带来的。7.3 流式与并发的问题流式接口偶发断流,是并发场景下比较头疼的问题。遇到这种情况,先从客户端侧确认是不是超时时间设置太短导致的连接中断,再看服务端日志里有没有显存或过载告警。服务端负载过高时,流式连接也可能被强制断开,这种情况单纯调客户端没意义,要回到并发上限的调整上。7.4 环境类问题速查表现象常见原因处理建议启动报 CUDA 相关错误PyTorch 与驱动版本不匹配重装匹配版本,优先用 conda 管理 CUDA 工具链找不到模型配置文件原始权重目录不完整检查 config.json 和分片权重文件是否齐全服务启动成功但请求返回 404模型名或接口路径不匹配确认--model-name参数与实际模型名一致首 token 特别慢等待队列过长或温度控制异常缩小并发,检查请求是否走了热加载流程显存一直不释放多进程加载同一模型确认主进程只加载一次,子进程共享显存8. 最后分享几个实操习惯部署 LMDeploy 走完几轮之后,我自己养成了几个固定习惯。第一,每次拿到一个新模型,先转一版 W4A16 量化版,再做一版原始 FP16 对照,两边跑同一批 prompt,记录输出质量和显存占用,方便后续量化方案出问题时回溯。第二,服务参数统一写进启动脚本,尤其是cache-max-entry-count和session-len,不靠手动敲命令,避免不同环境配置漂移。第三,正式压测前先用小并发跑一轮,让显存预分配和 CUDA 图预热完成,再上真实压力,这样拿到的曲线才稳定。这些习惯不一定是最优解,但能让你在排障时节省大量时间,部署这件事,稳定比炫技重要得多。
之前帮某开发者的“模拟项目X”做大模型部署时,我在 GPU 服务器上对比过好几套推理方案,踩完一圈坑之后,现在手里的长期方案基本固定成了 LMDeploy。如果你手里已经有一个开源对话模型,正打算放进业务里做 API 服务,或者只是想在本地起一个能跑的演示环境,这篇博文会把部署链路完整拉通一遍:环境准备、模型转换、W4A16 量化、服务启动、客户端调用、性能测试,以及我在现场遇到过的各种坑。整个过程不依赖特定云厂商,一套流程在自己笔记本或服务器上都能复现。1. 为什么最终留下了 LMDeploy1.1 几种主流推理方案的取舍大模型部署这件事,市面上不缺方案。最早想省事,很多人会直接拿 Transformers 配合原生推理跑,好处是零成本,坏处也很明显:并发一上来,显存直接爆掉,速度也平平。之后我试过 vLLM,它的 PagedAttention 确实把显存利用率拉了上去,吞吐量非常好看,但遇到量化支持、batch 动态变化、小模型调优这些场景,参数一多就有点烦。LMDeploy 的特点在于它的定位很巧:不只是一个推理引擎,而是一整套“参数压缩 + 推理优化 + 服务化”的工具链。它默认支持 KV Cache 量化、W4A16 权重量化、连续批处理、CUDA Graph、自定义注意力算子,而且这些能力通过简单命令就能组合起来。我个人的感受是,它对中大型对话场景的默认配置就已经很合理,不需要为了跑起来去读大量源码或反复调参。1.2 LMDeploy 更让人省心的三个点第一个点是显存占用。一个几十亿参数的模型,FP16 原始权重就要占掉两位数 GB 的显存,而用 W4A16 量化后,权重部分能压到原来的四分之一左右,原本勉强放的下的卡可以腾出更多空间给 KV Cache 和并发请求。第二个点是部署链路的完整性,从模型转换到启动 API 服务,再到 C++ 运行时推理,一条命令走完,学起来非常直接。第三个点是它对 OpenAI 格式接口的兼容,业务方接入时几乎不需要改代码,原来的请求结构直接复用。提示:LMDeploy 不是一个“又一个套壳工具”,它底层换了自己的算子实现。所以同一个模型,在 Transformers 里跑和在 LMDeploy 里跑的显存曲线和延迟表现会有明显差异,这也是为什么值得单独写一篇部署实战的原因。2. 环境准备是“稳”字当头2.1 版本匹配怎么确认LMDeploy 对运行环境有明确要求,建议遵循官方文档的推荐秩序。我自己的经验是:先用 conda 新建一个 Python 3.10 环境,再把 PyTorch 装成 CUDA 12.x 对应的版本,最后安装 LMDeploy。这个顺序不能乱,因为 LMDeploy 在安装时会根据当前 PyTorch 的 CUDA 版本来编译或拉取匹配的算子库,如果先装 LMDeploy 再回头改 PyTorch,很容易出现 libcudart 找不到的诡异报错。具体确认驱动时,直接运行nvidia-smi看右上角 Driver 版本,再确认 PyTorch 里torch.cuda.is_available()是否为 True。很多人会忽略一个小细节:即使系统里装了显卡驱动,conda 环境里也不一定有对应的 CUDA 工具链,二者要分开看。2.2 安装与安装包自救在干净环境里执行以下命令即可完成基础安装:conda create -n lmdeploy python=3.10 -y conda activate lmdeploy pip install torch --index-url https://download.pytorch.org/whl/cu121 pip install lmdeploy安装完成后,用一条命令验证核心模块是否就位:python -c "import lmdeploy; print(lmdeploy.__version__)"如果你所在的内网环境无法直接访问 PyPI,提前准备离线安装包是更稳妥的办法。在外网机器上执行pip download lmdeploy -d ./lmdeploy_packages,把整个目录拷进内网,再在内网执行pip install --no-index --find-links=./lmdeploy_packages lmdeploy。我在几次现场部署中遇到最多的问题就是内网源同步不全,离线包一定要连依赖一起下载,只下主包会出现一堆缺依赖的连环报错。3. 先把模型转换这一件事做好3.1 转换命令与目录结构部署前,我习惯先把原始权重转换成 LMDeploy 的专有格式。这一步不是模型文件本身的格式变化,而是为了让推理引擎在加载时能直接读取量化后的权重和缓存布局,省去每次启动都现算量化参数的重复开销。命令行形式很直观:lmdeploy convert ./models/chat-7b \ --model-name chat-7b \ --dst-path ./workspace/chat-7b-w4其中./models/chat-7b是原始模型目录,正常应该包含config.json、tokenizer.model和分片权重文件。--model-name是给转换后格式做兼容标记的关键参数,不同模型架构要写对应的名字,比如标准对话模型填chat-7b,多模态模型再按场景调整,转换脚本会用它推断层数、头数、词表大小等细节。转换完成后,workspace/chat-7b-w4目录里会有config.json、model.bin等文件。你可以用du -sh看一眼总大小,通常这个目录的体积会明显小于原始 FP16 模型。3.2 W4A16 量化到底做了什么W4A16 的意思其实非常直白:把模型权重压到 4bit 存储,但在计算时仍然把权重反量化回 FP16 做矩阵乘,激活值全程保持 FP16。这样做的原因是推理时的显存瓶颈主要在权重本身,而计算精度受影响的更多是激活的累积误差。4bit 量化的基本逻辑是给一小块权重找一组 scale 和 zero point,把原始浮点数映射到 4bit 的整数范围内。比如对一个 16 个数的权重向量,取其中的最大值,算一个缩放系数,再把每个权重除以这个系数并四舍五入到整数。恢复时乘以同样的系数就能大致还原。因为 LMDeploy 默认会做分组量化,不是整层共用一个 scale,所以精度损失在对话场景中通常很小。量化命令默认走向的正是这种 W4A16 方案。如果你的部署场景对显存极度敏感,还可以考虑把 KV Cache 也量化成 4bit,但日常先把权重量化到位,收益就已经非常明显了。3.3 正确性验证量化完成后不要直接上服务,先跑一个快速验证脚本,用同样的 prompt 分别加载原始模型和量化模型,各生成一小段文本,人工目测一下语义和流畅度。IanN 我自己的经验是,如果量化后输出出现大面积乱码或者语义断裂,八成不是量化本身的问题,而是转换时的模型名写错导致兼容层匹配失败,重新校验目录和模型名之后多半能恢复。如果对速度指标感兴趣,还可以顺手记录一下原始模型和量化模型各自加载到 GPU 的耗时。量化模型的加载通常明显更快,因为要读取的字节数少了很多。4. 本地 dryrun 之后再谈启动服务4.1 notebook 里先把模型跑通我强烈建议在启动对外服务之前,先在 Python 里用 pipeline 接口做一次本地 dryrun。这一步的意义在于:把“模型能不能跑”和“服务通不通”两个变量分开排查。直接在 load 阶段就报错,基本都是权重目录或模型名问题;宁可在脚本里发现,也不要让线上服务启动到一半才暴露。from lmdeploy import pipeline, TurbomindEngineConfig engine_cfg = TurbomindEngineConfig( model_path="./workspace/chat-7b-w4", tp=1, cache_max_entry_count=0.5, ) pipe = pipeline(engine_cfg=engine_cfg) resp = pipe(["请用一句话介绍你自己"]) print(resp[0].text)如果这一步能正常输出,说明模型加载、推理算子和显存分配都过了关,可以继续下一步搭建服务。4.2 API 服务启动与关键参数LMDeploy 提供了和 OpenAI 接口风格一致的 API 服务,启动方式也足够简单:lmdeploy serve api_server \ ./workspace/chat-7b-w4 \ --model-name chat-7b \ --server-port 8000 \ --tp 1 \ --cache-max-entry-count 0.5这里几个参数我单独说明下。--tp 1表示单卡推理,如果你的权重要跨多张卡切分,改成对应的张数就行。--cache-max-entry-count 0.5这个参数直接决定了 KV Cache 在显存中的占用上限:0.5 表示最多允许 KV Cache 占用约一半显存,剩下的留给权重和计算。实际调优时我会根据并发需求反复调这个值,并发高就调大,留给其他程序的显存不够就调小。还有一个容易被忽略的--session-len参数,它控制单条会话的最大上下文长度。模型训练长度是几千 tokens,但部署时不一定需要给满,按业务需要截短一些可以省下不少 KV Cache 空间,对并发量提升非常友好。4.3 显存里悄悄占地方的几块部署时很多人只看权重体积,其实显存占用由好几块叠加而成:权重显存、KV Cache、激活值、临时计算缓冲。权重量化主要解决第一块,KV Cache 则由cache-max-entry-count和session-len共同调节。激活值和临时缓冲更依赖 batch 大小和输入长度,并发越高,这部分绝对值也会增大。理解了这个模型,遇到“显存明明够,跑起来却 OOM”的现场,就能快速定位到是 KV Cache 或临时缓冲的锅,而不是盲目去缩模型。5. 接业务之前先写一个请求客户端5.1 流式对话调用服务一旦跑起来,立刻用 curl 或 Python 客户端打一发请求,确认返回结构符合预期。我自己常用的一段流式调用代码大致长这样:import requests url = "http://127.0.0.1:8000/v1/chat/completions" payload = { "model": "chat-7b", "messages": [{"role": "user", "content": "写一段关于大模型部署的总结"}], "temperature": 0.7, "max_tokens": 512, "stream": True, } resp = requests.post(url, json=payload, stream=True) for line in resp.iter_lines(): if line: print(line.decode("utf-8"))非流式请求只需要把stream改成 False,返回体里的choices[0].message.content就是完整生成文本。前端对接时,优先把流式打开,首字返回时间会好看很多,用户感知上也更接近真实对话体验。5.2 并发基线的重要性单条请求通了之后,我还会顺手做一次小压测。不需要复杂的压测工具,用一段简单的 Python 脚本并发发十几个请求,观察有没有超时、报错和显存溢出就够了。这事的价值在业务接入前就能提前暴露显存配置上的问题,而不是等到业务方压力测试时才手忙脚乱。并发如果出现 OOM,最直接的调整是降低cache-max-entry-count,或者给服务加--max-batch-size限制同时处理的请求数。记住一个原则:宁可让请求排队,也不要让显存被打满后整个服务崩溃。排队只是延迟,崩溃则是可用性事故。6. 性能数据怎么测才不会骗人6.1 指标定义:首token、吞吐量、时延在汇报性能数据之前,先把指标口径说清楚。通常关注三个值:首 token 延迟、单请求完整时延、吞吐量。首 token 延迟指的是从请求发出去到模型返回第一个 token 的时间,它和输入长度、模型计算速度、排队时间强相关;完整时延是整段回答生成结束的时间;吞吐量则是系统稳定运行时每分钟或每秒能生成的 token 总数。这三个值不是相互独立的。压测时并发提高,吞吐量往往上升,但单请求的完整时延也会拉长,因为它们共享显存和算力资源。我看到过很多人只甩一个“吞吐量很高”的结论,却不说明并发数和卡数,这没有任何对照价值。6.2 一个小型压测脚本以下是一段足够用来做横向对比的脚本框架,你可以根据自己的字段调整:import time import requests from concurrent.futures import ThreadPoolExecutor url = "http://127.0.0.1:8000/v1/chat/completions" prompt = "用三句话解释什么是 KV Cache,语言要通俗。" def send_one(_): start = time.time() resp = requests.post(url, json={ "model": "chat-7b", "messages": [{"role": "user", "content": prompt}], "max_tokens": 128, }) cost = time.time() - start return cost, resp.status_code with ThreadPoolExecutor(max_workers=8) as pool: results = list(pool.map(send_one, range(40))) costs = [r[0] for r in results] errors = [r for r in results if r[1] != 200] print(f"平均时延: {sum(costs)/len(costs):.2f}s") print(f"最大时延: {max(costs):.2f}s") print(f"错误数: {len(errors)}")如果你想测“纯推理速度”,应该把 prompt 固定为某个长度,把max_tokens固定,并且保持并发数不变,这样出来的结果才能用于横向比较。如果只是随手测一测,prompt 忽长忽短,数据完全没有可比性。6.3 结果怎么解读我在某次对比测试里,用原始 FP16 模型和 W4A16 量化模型分别跑了同一批 100 条请求,量化后显存占用降到了原来的四成左右,首 token 延迟略有下降,完整生成速度基本持平。这个结果符合预期:量化降低了显存压力和 IO 压力,在线性层计算上因为重量化开销,速度不会简单提升,但整体稳定性反而更好。做性能汇报时,我建议固定测试条件写清楚:显卡型号、量化方式、并发数、prompt 平均长度、max_tokens。否则数据拿给旁观者看,根本没法判断是好是差。7. 部署现场最容易踩的坑7.1 显存不足与预分配显存问题在部署现场出现的频率最高。有一种常见情况是:启动 log 显示一切正常,但一进来并发请求就 OOM。排查思路很简单,先用nvidia-smi看进程实际占用,再调整cache-max-entry-count降低缓存预分配。如果模型是多卡跑的,还要确认是否真的做了张量并行,很多报错其实是权重被重复加载到了每张卡上。7.2 量化后质量波动量化后模型输出质量出现波动,先别急着回退到全精度。第一步确认量化分组数是否被正确识别,第二步用同样的 prompt 多测几轮,因为生成结果本身有随机性。质量波动如果持续存在,建议对照原始模型的输出长度和内容风格,判断到底是量化引入的偏差,还是 temperature 和 top_p 设置差异带来的。7.3 流式与并发的问题流式接口偶发断流,是并发场景下比较头疼的问题。遇到这种情况,先从客户端侧确认是不是超时时间设置太短导致的连接中断,再看服务端日志里有没有显存或过载告警。服务端负载过高时,流式连接也可能被强制断开,这种情况单纯调客户端没意义,要回到并发上限的调整上。7.4 环境类问题速查表现象常见原因处理建议启动报 CUDA 相关错误PyTorch 与驱动版本不匹配重装匹配版本,优先用 conda 管理 CUDA 工具链找不到模型配置文件原始权重目录不完整检查 config.json 和分片权重文件是否齐全服务启动成功但请求返回 404模型名或接口路径不匹配确认--model-name参数与实际模型名一致首 token 特别慢等待队列过长或温度控制异常缩小并发,检查请求是否走了热加载流程显存一直不释放多进程加载同一模型确认主进程只加载一次,子进程共享显存8. 最后分享几个实操习惯部署 LMDeploy 走完几轮之后,我自己养成了几个固定习惯。第一,每次拿到一个新模型,先转一版 W4A16 量化版,再做一版原始 FP16 对照,两边跑同一批 prompt,记录输出质量和显存占用,方便后续量化方案出问题时回溯。第二,服务参数统一写进启动脚本,尤其是cache-max-entry-count和session-len,不靠手动敲命令,避免不同环境配置漂移。第三,正式压测前先用小并发跑一轮,让显存预分配和 CUDA 图预热完成,再上真实压力,这样拿到的曲线才稳定。这些习惯不一定是最优解,但能让你在排障时节省大量时间,部署这件事,稳定比炫技重要得多。