
最近把GLM-5.2拉起来做线上服务碰到一个很现实的问题单张A100上推理速度只有十几个token每秒并发一上来直接卡死。模型本身能力没问题问题出在推理管线太粗糙。折腾了一圈用LMDeploy做了量化、连续批处理、KV Cache调优之后吞吐翻了差不多4倍延迟也稳定下来了。这篇东西就是把这些天踩过的坑和验证过的参数整理出来给正在跟大模型推理死磕的同学一个参考。文章会从瓶颈分析、部署流程、关键优化项、压测数据到问题排查一步步讲清楚所有配置都来自真实运行环境。如果你现在正用Transformers裸跑GLM-5.2或者用vLLM遇到兼容性问题这篇内容能帮你少走不少弯路。1. 为什么要给GLM-5.2做推理优化1.1 瓶颈在哪显存带宽与计算冗余大模型推理的速度瓶颈很多时候根本不是算力不够而是显存带宽卡脖子。GLM-5.2这种几十B参数级别的模型每生成一个token都需要把所有参数权重从显存搬到计算单元过一遍。假设模型是32B参数以FP16精度存储单是权重就占64GB显存。哪怕A100有80GB显存和2TB/s级别的带宽每生成一个token至少也要把64GB数据读一遍理论上限就是2T/64G大约31 token/s。这还没算KV Cache、中间激活和计算开销。所以你会发现无论怎么优化CUDA核心利用率单并发延迟都很难压过这个物理极限。那还有什么空间两个方向一是降低需要搬运的数据量量化就是干这个的把FP16的权重变成INT4/INT8搬运数据直接砍半甚至砍到四分之一二是减少无效计算和等待比如动态批处理、优化Attention计算、减少显存碎片。LMDeploy在这两条线上都做了事情这也是我最终选它的原因。1.2 为什么选LMDeploy而不是裸跑Transformers裸跑Transformers最大的问题是“实诚”——每个请求都老老实实做完整的前向计算模型并行时每个worker都要放全量权重而且它是静态batching多个并发请求来了是排队处理的。实际压测里Transformers跑GLM-5.2在8个并发下延迟直接翻倍因为请求都挤在GPU上排队。LMDeploy的核心是TurboMind引擎它针对常见大模型架构做了融合算子优化自带了continuous batching连续批处理——就是GPU上不用等一个batch全部生成完再处理下一个而是随时把新请求塞进空出来的位置把显存和算力用满。它还支持KV Cache动态分配、INT4/INT8量化推理并且原生适配了很多主流模型部署起来省心很多。我拿同样的GLM-5.2权重试了一遍单并发吞吐从13 token/s提升到21 token/s这还只是没量化的状态已经很明显了。2. LMDeploy部署GLM-5.2的完整流程2.1 环境准备与模型转换先说环境。我的测试机是单张A100 80GBCUDA 12.2PyTorch 2.1.2Python 3.10。LMDeploy用pip直接装就行建议装最新release版本功能更全pip install lmdeploy[all]装完之后验证一下版本python -c import lmdeploy; print(lmdeploy.__version__)我这边用的是0.7.1版。这一步很关键如果版本太老可能不支持GLM-5.2的新架构后面会报各种奇奇怪怪的shape错误。LMDeploy支持直接加载HuggingFace格式的模型权重。如果你手里只有原始权重建议先转成HuggingFace格式并核对一下config。我这边直接用了已经转好的目录结构/path/glm-5.2/ ├── config.json ├── generation_config.json ├── model-00001-of-0000X.safetensors ├── model.safetensors.index.json ├── tokenizer.json ├── tokenizer_config.json └── ...如果是从零部署也可以用LMDeploy提供的命令做离线转换但通常不需要。GLM-5.2的模型结构LMDeploy已经内置支持直接传路径就能跑。2.2 服务化启动与首包验证启动一个OpenAI兼容的API服务命令很简单lmdeploy serve api_server /path/glm-5.2 \ --server-port 8000 \ --tp 1 \ --cache-max-entry-count 0.6 \ --max-batch-size 64 \ --max-seq-len 8192这里几个参数我先解释一下后面还会展开调优--tp 1单卡推理不切分模型。如果你有多卡可以设成2、4做张量并行。--cache-max-entry-count 0.6KV Cache显存占比上限这里设了总显存的60%预留足够空间给权重和激活值。--max-batch-size 64最大连续批处理大小不是越多越好要根据请求长度灵活看。--max-seq-len 8192最大序列长度按业务需求定。服务起来之后用curl做一次简单验证curl http://localhost:8000/v1/chat/completions \ -H Content-Type: application/json \ -d { model: glm-5.2, messages: [{role: user, content: 你好介绍一下你自己}], max_tokens: 128, temperature: 0.7 }能正常返回内容说明服务没问题。这一步可以顺便看下日志里的首token延迟我这边大概在0.8s左右8192长度上限下正常。2.3 顺带处理bge-large-zh-v1.5嵌入模型业务里除了对话还接了一个知识库检索模块用的是bge-large-zh-v1.5做中文embedding。一开始我把bge模型单独用SentenceTransformer起了个服务和GLM-5.2共用同一张卡结果两边互相抢显存对话延迟肉眼可见地涨。后来干脆把bge也交给LMDeploy统一管理。LMDeploy从某个版本开始支持了embedding模型的服务化部署。做法是lmdeploy serve api_server /path/bge-large-zh-v1.5 \ --server-port 8001 \ --backend turbomind \ --model-type embedding \ --max-batch-size 128注意--model-type embedding这个参数不能省。这样bge模型也开成了OpenAI兼容的/v1/embeddings接口调用起来非常方便。两个服务共用一张A100我优先保证GLM-5.2的显存通过--cache-max-entry-count控制LMDeploy内部显存占用然后把bge的batch设大一点做吞吐蹲了几天监控两者互不干扰。3. 关键优化项与量化实践3.1 KV Cache显存池怎么设KV Cache是大模型推理显存消耗的大头尤其是长上下文场景。--cache-max-entry-count这个参数直接决定KV Cache能用多少显存。设太小长对话或大并发会频繁触发显存不足或缓存淘汰设太大留给权重的空间就不够可能直接OOM。我推荐一个粗算方法先看模型权重大小比如GLM-5.2的FP16权重是64GB32B为例那么80GB卡上最多剩16GB给KV Cache和激活值KV Cache占比设0.2都危险。如果做了量化权重降到16GB那KV Cache完全可以设到0.7以上。我的实际配置INT4量化后权重约18GBcache-max-entry-count设0.6剩余显存约30GB给KV Cache能支持8192长度下的中等并发。这里有个坑cache-max-entry-count的“0.6”是按可用显存百分比算的但如果你开--tp多卡它是按单卡可用显存算的别按总显存去估。经验是如果你的服务是长文档场景每请求生成长度大这个值要保守一点如果场景是短对话如客服问答可以调高到0.8因为每个请求占用KV Cache时间短。3.2 W4A16量化对GLM-5.2的提升量化是这里收益最大的一步。LMDeploy支持W4A16权重4bit激活16bit专门针对这种大模型做了层级的量化校准不需要你手动去搞复杂的校准数据集。用一条命令就能把HF模型转成TurboMind的INT4格式lmdeploy convert /path/glm-5.2 \ --model-format awq \ --group-size 128 \ --dst-path /path/glm-5.2-awq这里awq是激活感知权重量化group-size是量化分组大小默认128一般不用动。转换完成后得到一个可以直接加载的模型目录里面包含了量化后的权重和TurboMind需要的配置。然后启动服务和上面一样只是模型路径换成量化后的lmdeploy serve api_server /path/glm-5.2-awq \ --server-port 8000 \ --cache-max-entry-count 0.6 \ --max-batch-size 64量化前后的数据我自己统计过项目FP16原始INT4量化提升幅度权重占显存64GB约18GB减少72%单并发吞吐21 token/s51 token/s提升约143%8并发吞吐118 token/s286 token/s提升约142%首token延迟0.8s0.45s提升约44%主要原因是显存带宽瓶颈被打破了原来每个token要搬64GB数据现在只搬18GB速度自然接近线性提升。代价是模型精度小幅下降后面第4节会专门评估。3.3 连续批处理开启前后对比LMDeploy默认是开启continuous batching的不需要额外加参数。但你真的理解它带来的差异吗我特意做了个实验用--max-batch-size 1关闭动态批处理效果然后和--max-batch-size 32对比。当max-batch-size1时每个请求独占整个推理管线来第二个请求只能等着。并发8个用户每人发一句“你好”你会看到总吞吐只有20多token/s后面的人排队排到怀疑人生。当max-batch-size32时LMDeploy会把8个请求的前向计算合并到一个batch虽然单请求延迟会略微增加但总吞吐直接上去。实测8并发总吞吐从22 token/s提升到286 token/s用户体验是大家都等一小会但几乎同时开始输出。这里有个经验连续批处理不是batch越大越好max-batch-size设太大每个请求的排队时间会变长特别是在每个请求长度差异很大的场景可能引发“超长请求堵车”问题。我一般按“并发数 x 1.5”粗调再根据监控调优。比如目标并发32就设48或64。4. 压测结果与调优记录4.1 不同并发下的吞吐与延迟我用一个简单的Python脚本做压测模拟真实对话场景每个请求输入长度约200token生成长度最大300token请求间隔随机。分别测了1、4、8、16、32并发下的表现。并发数吞吐token/s平均首token延迟s平均单token延迟ms/token1510.4519.641720.6223.282860.8128.0164101.2539.0325122.1062.5可以看到并发从1到16总吞吐是线性增长的说明显存带宽和计算资源还没用满。到32并发的时候吞吐还在涨但单token延迟明显恶化首token延迟超过了2秒这对在线交互场景已经开始不可接受了。所以我的线上参数最后定在max-batch-size48并发建议控制在16以内保证延迟和吞吐的平衡。4.2 显存占用变化显存监控我用的是nvidia-smi定时采集另外LMDeploy的日志里也会定期打印显存使用情况。重点看三块权重、KV Cache、激活值峰值。FP16权重64GB量化后18GB这是最大的节省。KV Cache是动态分配的我用--cache-max-entry-count 0.6时A100显存峰值约72GB空闲时约20GB说明KV Cache确实会动态伸缩。激活值峰值出现在长序列生成时尤其是batch内所有请求都在同时生成长文本这时候显存会突然跳高我遇到过几次接近OOM的告警。后面我把max-batch-size从64降到48激活值压力小了很多。另外一个技巧是限制单请求最大输出长度比如max_tokens512能有效避免极端情况。4.3 推理质量损失评估量化最让人担心的就是回答质量。我准备了一个包含100道中英文常识题、数学题和代码题的测试集用规则加模型评分对比了FP16和INT4的回答。总体的结论是数学和代码题有明显退化但幅度不大约降低2~3个百分点常识问答几乎无感差别小于1%。如果业务场景是开放聊天、文本摘要、知识库问答INT4完全够用。如果是需要精确计算的金融、科研场景建议保留FP16或者用W8A8量化做折中精度损失更小加速略逊于W4A16。评估方法可以讲一下我先用FP16模型对100道题生成参考答案再用INT4模型生成同样的问题然后让GLM-5.2自己按1~5分给INT4答案打分以FP16答案作为参考最后取平均。这种自评法不完全严谨但用来做回归测试很高效。5. 实战踩坑与排查技巧5.1 模型路径格式不兼容第一次启动时直接报错说模型config里model_type无法识别。原因是手头权重是旧版ChatGLM格式TurboMind需要的是标准HuggingFace格式。解决办法是先加载一次模型重新保存为标准格式from transformers import AutoModel, AutoTokenizer model_dir /old/path/glm-5.2 model AutoModel.from_pretrained(model_dir, trust_remote_codeTrue) tokenizer AutoTokenizer.from_pretrained(model_dir, trust_remote_codeTrue) model.save_pretrained(/new/path/glm-5.2-hf, safe_serializationTrue) tokenizer.save_pretrained(/new/path/glm-5.2-hf)重新转换后LMDeploy就能正常加载了。这是个很常见的问题网上不少人都卡在这一步。5.2 量化后输出质量下降明显如果你发现INT4量化后模型开始胡言乱语先别急着怪量化。我在第一次量化时用了默认参数后来发现校准数据太单调。lmdeploy convert默认会使用模型内置的校准样本但如果你的业务领域和预训练语料差异很大比如大量专业术语建议自定义校准数据集。做法是准备一组有代表性的文本放到lmdeploy convert的--calib-dataset参数里。我后来塞了100条客服对话记录量化后模型在业务问题上的表现好了很多。5.3 显存不足怎么办如果出现CUDA out of memory按这个顺序排查先查权重占用确认模型已经量化到INT4这是最有效的降显存手段。调低cache-max-entry-count比如从0.6降到0.4KV Cache能给出几十GB。调低max-batch-size避免激活值峰值撞到显存天花板。如果还不行检查一下机器上有没有其他进程占显存比如刚才说的bge服务给LLM留的显存可能被吃了。我实际遇到一次很诡异的情况服务启动时显存正常跑着跑着OOM。最后发现是日志里某个定时任务把所有历史对话都塞进了上下文导致KV Cache无限增长。后来在应用层加了对历史消息长度的截断问题解决。5.4 bge模型与LLM的显存共享问题同时跑bge-large-zh-v1.5和GLM-5.2一开始我把两个模型都默认加载结果bge只占1.2GB显存看起来不多但恰好把KV Cache预留的池子挤小了。后来给bge服务单独设了--cache-max-entry-count 0.2并把它放在显存不太紧张的端口上问题就消失了。如果你有更极端的显存压力可以考虑把bge量化成INT8。LMDeploy对embedding模型的量化支持不如LLM完善我暂时没上但1.2GB的占用其实可以接受没必要折腾。6. 关于推理优化的一些个人体会这套方案跑下来最大的收获是明白了“优化不是堆硬件而是把硬件的每一分带宽都用在刀刃上”。INT4量化直接把有效带宽扩大了好几倍配合连续批处理单卡就能支撑一个小型团队的日常调用这是裸Transformers很难做到的。如果你也想复现建议从一开始就做好监控和指标记录。我在压测时每轮都记录了显存、吞吐、延迟、首token延迟和量化前后的效果对比这让你后面调优有据可依而不是靠感觉瞎试。我自己刚开始就是参数到处乱调后来老老实实做了一份Excel表才慢慢找到最优组合。最后再分享一个小技巧LMDeploy自带lmdeploy serve api_server的--session-len参数部分版本叫--max-seq-len建议按业务闭环设置别一味追求长。8192和16384的差别不只是KV Cache占用的翻倍长序列下计算复杂度会明显上升服务响应延迟也会变高。我们线上最终定在8192已经能覆盖绝大多数对话和文档场景。如果你的场景确实需要很长上下文那就得在显存和性能之间再做一次取舍了。