尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

GLM-5.2 推理优化实战:LMDeploy 量化与 RAG 部署全指南

GLM-5.2 推理优化实战:LMDeploy 量化与 RAG 部署全指南 GLM-5.2 跑起来并不难难的是在有限的显存里把吞吐和首 token 延迟压到“能正经用”的水平。我自己在项目里踩了不少坑最后选定的方案是 LMDeploy引擎用 TurboMind权重做 W4A16 量化KV Cache 再开 INT8整体下来显存占用能降到 FP16 方案的 1/3 左右并发吞吐提升两倍也不夸张。这篇文章就把我这套基于 LMDeploy 的 GLM-5.2 推理优化实践完整拆开讲适合手里有单卡或多卡 GPU、想让对话模型跑得更快更省的读者参考。配套的 RAG 场景我也一起讲现在很多人问 lmdeploy 部署 bge-large-zh-v1.5 怎么搞我实际项目的做法是对话模型和向量模型分开部署但调用链必须无缝衔接。我会把整条链路串起来从模型转换、服务启动到调优参数全部给可复现的命令和配置。1. GLM-5.2 推理的瓶颈到底在哪1.1 生成式推理的两段式耗时Prefill 和 Decode先别急着调参得明白大模型推理为什么慢。GLM-5.2 这类自回归模型生成一句话不是一个模型做一次预测就完事而是每个 token 都要经历一遍前向计算上一个 token 的输出作为下一个 token 的输入。这个过程天然被分成两段第一段叫 Prefill也就是用户输入 prompt 后模型一次性处理整个上下文。这一步是典型的“计算密集型”GPU 的算力利用率能跑得很高耗时主要花在处理大量 token 的并行计算上。第二段叫 Decode也就是模型逐个生成新 token 的过程。每生成一个 token 都要读取模型全部权重还要读写不断增长的 KV Cache这一步是典型的“访存密集型”受显存带宽限制非常明显。怎么理解这个差异你可以把 Prefill 想象成“把整本材料一次性看完”而 Decode 是“每写一个字都要重新翻一遍书”。所以 decode 阶段想变快光堆算力没用瓶颈在显存带宽而 Prefill 想变快依赖算力和并行策略。LMDeploy 的 TurborMind 引擎就是同时针对这两段的特性做优化。1.2 显存都被谁吃掉了权重、激活与 KV Cache要优化先要知道显存去了哪里。一次推理过程中显存占用主要来自三块模型权重、中间激活值、KV Cache。模型权重这项大家都熟悉FP16 下 7B 参数需要约 14GBINT4 量化后能压到约 4GB。激活值是前向计算过程中的临时张量规模和 batch size、序列长度强相关。KV Cache 则是 Transformer 推理特有的缓存记录已经计算过的 Key 和 Value避免每生成一个新 token 都重新算历史 token 的 K、V。KV Cache 的显存计算公式可以写成KV Cache 字节数 2K和V × 层数 × 注意力头数 × head_dim × 序列长度 × batch size × 每元素字节数拿一个约 7B 规模的 GLM-5.2 做例子假设 32 层、32 个注意力头、head_dim 128在 batch size 为 8、序列长度 4096、FP162字节的情况下2 × 32 × 32 × 128 × 4096 × 8 × 2 17,179,869,184 字节约 16GB。这个数字很吓人因为你模型权重才 14GBKV Cache 却吃掉 16GB两者加一起普通 24GB 显卡已经喘不过气了。如果序列长度拉到 8192KV Cache 直接翻倍到 32GB。这也是为什么很多人发现单卡跑 GLM-5.2 只能用小 batch稍微并发一高就 OOM。明白了显存的账本下面要说的所有优化手段基本都在围绕“压缩权重”“压缩 KV Cache”“提高显存复用效率”这三件事展开。2. LMDeploy 凭什么能把速度提上来2.1 TurboMind 引擎与 Continuous BatchingLMDeploy 的核心加速引擎叫 TurboMind它和普通的一次处理一个 batch 的方式不同用的是 Continuous Batching也就是连续批处理。传统静态批处理的做法是把请求攒够一批再同时跑谁最慢大家就一起等谁一批中有请求已经生成了结束符但同批其他请求还在跑显存资源就被空占。Continuous Batching 把“一个请求”当作调度单位而不是把“一个 batch”当作单位。GPU 上每个时刻都在跑不同状态的请求有的在 Prefill有的在 Decode有的刚刚结束新请求随时可以插入空出的位置。这样 GPU 的利用率能保持在一个较高水位。用生活化的话说传统方式像公共食堂必须凑够一桌人才开饭Continuous Batching 像快餐店谁来了都能点阿姨一边炒这个菜一边顺手给另一个客人打饭。对于对话、客服这类请求长短不一、到达时间随机的场景连续批处理带来的吞吐提升非常明显。2.2 PagedAttention把 KV Cache 做成“按页管理”KV Cache 在显存里怎么存放直接决定显存碎片率。早期的推理框架会为每个请求预先分配一段连续显存大小按最大可能序列长度来算。结果就是绝大多数请求根本用不到那么长内存被白白预留请求还没跑完显存就满了。LMDeploy 实现了 PagedAttention 的思路核心类似操作系统的虚拟内存分页管理。KV Cache 不再要求物理连续而是拆成固定大小的块按需分配、按需释放。请求用完的块可以回收给下一个请求长上下文和短请求的显存占用差距被抹平。这个设计对 GLM-5.2 这种需要长上下文能力的模型尤其重要因为序列越长KV Cache 越占显存分页管理能明显减少浪费。2.3 量化方案怎么选W4A16、KV Cache INT8LMDeploy 最实用的加速手段还是量化。它主推 W4A16也就是权重用 4bit 存储激活保持 16bit。为什么不把激活也量化成 4bit因为激活值的动态范围大量化后精度损失对生成质量影响明显而权重相对稳定4bit 量化在大多数任务上能把精度损失压到可接受范围同时显存和带宽压力大幅下降。权重从 14GB 降到 4GB 左右decode 阶段需要搬运的数据量少了一大截生成速度自然就上去了。KV Cache 也能量化。LMDeploy 支持--quant-policy 4开启 INT8 KV Cache支持--quant-policy 8开启 INT4 KV Cache。量化后 KV Cache 显存直接减半或再减半在长上下文、高并发场景下效果显著。代价是极端长文本下精度有轻微波动但我实际体验下来日常对话、知识问答、摘要提取这类任务几乎感知不到差异。3. 完整实操LMDeploy 部署 GLM-5.2 并调优3.1 环境准备和模型下载我的环境是 Ubuntu 22.04、CUDA 12.1、Python 3.10、单张 A100 80G读者用 3090、4090 也完全没问题显存小就把 batch size 和上下文长度调低即可。先创建虚拟环境并安装 LMDeployconda create -n lmdeploy python3.10 -y conda activate lmdeploy pip install lmdeploy建议装最新版本量化选项和模型支持更新很快。如果网络下载模型不方便建议直接用 ModelScope 拉取命令是pip install modelscope modelscope download --model ZhipuAI/glm-5.2-chat --local_dir /models/GLM-5.2-chat下载完成后先跑一次推理确认模型本身没问题再开始优化。不要跳过这一步我见过有人直接量化、直接部署最后效果不如预期排查了半天才发现是模型文件不完整。3.2 离线转换AWQ 权重量化与 KV Cache 设置LMDeploy 的离线量化工具叫lmdeploy lite它集成了 AWQ 算法。AWQ 的核心理念是不是所有权重通道对精度影响都一样它通过少量校准数据统计出通道的重要程度量化时对重要通道做更精细的处理。相比简单 round-to-nearestAWQ 能在 4bit 下保留更多精度。先把 GLM-5.2 量化成 AWQ 格式lmdeploy lite auto_awq /models/GLM-5.2-chat \ --work-dir /models/GLM-5.2-chat-awq \ --calib-dataset ptb \ --calib-samples 128量化完成后再转换部署所需的模型格式lmdeploy convert /models/GLM-5.2-chat-awq \ --model-format awq \ --group-size 128 \ --work-dir /models/GLM-5.2-workspace这一步生成的 workspace 目录就是后面启动服务用的模型目录。如果你的显存很充裕不想量化也可以直接跳过这一步后面启动服务时用--model-format hf指定原模型。不过我的建议是既然用了 LMDeployAWQ 这个收益一定要吃下INT4 权重配合 KV Cache INT8 后单卡能干的活会宽裕很多。3.3 启动 OpenAI 兼容服务关键参数逐个说清LMDeploy 提供 OpenAI 兼容的服务接口业务代码可以直接用 openai SDK 调用。启动命令如下lmdeploy serve api_server /models/GLM-5.2-workspace \ --model-name GLM-5.2-chat \ --model-format awq \ --quant-policy 4 \ --cache-max-entry-count 0.5 \ --max-batch-size 64 \ --max-prefill-tokens 2048 \ --session-len 8192 \ --tp 1 \ --server-port 8080这些参数我逐个说一下--model-name是暴露给调用方的模型名后面请求里的 model 字段要填它。--model-format awq表示使用 AWQ 量化模型。--quant-policy 4表示 KV Cache 用 INT8。想开 INT4 就改成--quant-policy 8。--cache-max-entry-count是 KV Cache 最多占显存的比例默认 0.8。我压到 0.5给权重和激活留余量避免并发波动导致 OOM。--max-batch-size控制连续批处理的最大 batch调越大吞吐越高但显存压力和调度开销也会涨。--session-len是最大上下文长度按业务需求设。--tp是张量并行卡数单卡就写 1两张卡写 2。有个容易被忽略的点--max-prefill-tokens。它决定了 Prefill 阶段一次性最多处理多少输入 token长文档场景如果预填很长这个值设太小会导致 Prefill 被切分成很多次整体延迟变大。官方默认值偏保守我一般会调成 2048 或 4096。3.4 不同显存容量下的部署参考由于 GLM-5.2 的参数量大概在 7B 级别不同显存的卡都有可行的配置。我把自己的参数参考整理成了表格显卡显存推荐方案可参考配置RTX 3090 / 409024GBAWQ KV INT8batch 16session-len 4096cache-max-entry-count 0.4A100 / A800 40G40GBAWQ KV INT8batch 32session-len 8192cache-max-entry-count 0.5A100 / H800 80G80GBAWQ KV INT8/INT4batch 64session-len 16384cache-max-entry-count 0.6多卡环境2×24GBAWQ TP2tp 2batch 64session-len 8192这组配置不是死参数如果你的请求平均长度很短可以把 cache-max-entry-count 调高一些换更大吞吐如果场景是长文档问答建议把 session-len 拉高同时调低 batch size。总之量化给显存腾出来的空间要用在业务最需要的地方。4. 配合 bge-large-zh-v1.5把检索链路也一起优化4.1 为什么对话模型要配本地向量模型单纯把 GLM-5.2 跑得快只是第一步。在真实项目里我发现大多数场景都需要私有知识库否则模型只能靠训练时学到的通用知识回答很多内部资料答不出来还会一本正经地说错。为了解决这个问题我在项目里引入了 RAG先根据用户问题在知识库里检索相关片段再把这些片段拼到 prompt 里让 GLM-5.2 生成答案。RAG 的核心检索依赖 embedding 模型中文场景我最常用的就是 bge-large-zh-v1.5。它的特点是中文语义理解效果好、维度不高、检索召回率靠谱而且权重体积适中单卡轻松跑。很多人看到 lmdeploy 部署 bge-large-zh-v1.5 这个用法以为 embedding 模型也要塞进 TurboMind 引擎里加速我实际测下来并不是最优解下面具体说。4.2 我的部署方式对话模型和向量模型分开跑LMDeploy 的 TurboMind 主要针对自回归生成模型做深度优化而 bge 这类双向编码模型的任务逻辑完全不同它是一次性把文本编码成一个向量没有 Prefill 和 Decode 之分。所以我不会强行把 bge 塞进同一个 model 服务里而是把它单独部署一个 Embedding 服务再和 GLM-5.2 的推理服务互相调用。这样部署有几个好处一是两个服务的显存、并发参数可以独立调检索服务打满时不影响生成服务的吞吐二是 bge 模型可以替换升级不用重启主流程三是定位问题的时候容易定位不用在一堆日志里做减法。如果你看到的“lmdeploy 部署 bge-large-zh-v1.5”指的是让它和 LMDeploy 服务走同一条 HTTP 链路那本质上也是这个思路LLM 服务管生成embedding 服务管向量化中间用统一 API 格式接起来。我给 bge-large-zh-v1.5 单独做了一个 OpenAI 兼容的 embedding 服务用 FastAPI 包装。核心代码很简单from fastapi import FastAPI from pydantic import BaseModel from sentence_transformers import SentenceTransformer app FastAPI() model SentenceTransformer(/models/bge-large-zh-v1.5) class EmbeddingRequest(BaseModel): input: str | list[str] class EmbeddingResponse(BaseModel): object: str data: list[dict] model: str app.post(/v1/embeddings, response_modelEmbeddingResponse) def embeddings(req: EmbeddingRequest): texts req.input if isinstance(texts, str): texts [texts] vectors model.encode(texts, normalize_embeddingsTrue) data [ {object: embedding, index: i, embedding: vec.tolist()} for i, vec in enumerate(vectors) ] return {object: list, data: data, model: bge-large-zh-v1.5}启动命令uvicorn embed_server:app --host 0.0.0.0 --port 8001注意normalize_embeddingsTrue一定要加bge 模型官方推荐做余弦相似度检索归一化之后点积等价于余弦相似度后面向量检索计算会简单很多。4.3 检索增强后的调用链路与性能要点整个链路我这样串用户输入先请求 bge 服务拿 query 向量去向量库里找最相关的几段文本然后组装成 system prompt再请求 LMDeploy 的 GLM-5.2 服务得到最终回答。示例代码如下import requests from openai import OpenAI query 公司报销制度是什么样的 query_vec requests.post( http://127.0.0.1:8001/v1/embeddings, json{input: query} ).json()[data][0][embedding] docs vector_db.search(query_vec, top_k3) context \n.join([d[content] for d in docs]) client OpenAI( base_urlhttp://127.0.0.1:8080/v1, api_keynot-used ) resp client.chat.completions.create( modelGLM-5.2-chat, messages[ {role: system, content: f请根据以下资料回答问题\n{context}}, {role: user, content: query} ], temperature0.2, max_tokens1024 ) print(resp.choices[0].message.content)这个链路的性能重点有两个。第一bge 服务建议用 fp16 精度不要在 embedding 模型上盲目量化向量质量直接影响检索召回率召回错了上下文再快也没意义。第二向量库的召回不要一次取太多片段我一般取 3 到 5 段每段控制在 200 到 500 字prompt 太长会挤占 GLM-5.2 的输出上下文和 KV Cache。5. 常见问题与排查技巧实录5.1 量化后回答质量下降AWQ 量化后如果感觉回答质量下降优先检查校准数据集。--calib-samples默认值偏小我建议收集一批跟你业务场景接近的中文文本作为校准语料。比如你的场景是法律咨询就用法律条文和问答记录做校准。校准数据相关性越高量化后精度损失越小。另外--group-size 128比--group-size 32的显存占用更小但精度略差一般选 128 是平衡点。5.2 显存不足直接 OOMOOM 先别急着减小模型按顺序排查这几项先看--cache-max-entry-count这个值太高是 OOM 的第一大原因再看--session-len很多请求如果根本没用到长上下文可以往下压再看--max-batch-size并发波动时把 batch 调小一档立刻见效。实际项目里我习惯把 cache-max-entry-count 控制在 0.5 以下宁愿损失一点吞吐也要避免一个长请求进来就把整卡内存打爆。5.3 并发上来后首 token 延迟变高并发增大导致首 token 变慢通常是 Prefill 阶段算力被打满或者--max-prefill-tokens设置太小导致预填切分次数太多。我一般把--max-prefill-tokens调到 4096同时确认--max-batch-size没有设置到一个请求就把所有 block 占满的程度。如果是多卡环境检查--tp是否生效以及显存是否均衡分布经常出现两张卡负载不一致的情况。5.4 接口调用报 model 不存在LMDeploy 启动后调用接口如果提示 model 不存在十有八九是--model-name的参数和客户端请求里的 model 字段没对齐。这个参数不是模型文件名是给服务暴露出来的别名客户端填哪个启动命令里就要写哪个。另外如果量化后目录结构和原始目录不一致也可能出现模型加载偏移的问题建议用我前面的流程先 lite 量化再用 convert 转出 workspace。5.5 bge 检索效果差如果 bge-large-zh-v1.5 部署好了但检索结果不对先看句子长度。bge 模型对超长文本效果一般超过 512 token 的内容建议先分段再编码检索时取分段后的结果。再看是否需要加 instruction。bge-large-zh 在检索场景对 query 侧使用“为这个句子生成表示以用于检索相关文章”前缀会有提升但如果你做的是非对称检索还需要结合自己的场景测试。最省事的做法是像我一样用 sentence-transformers 封装normalize_embeddingsTrue保证向量尺度一致这一步最容易忽略。6. 关于这套优化我最后分享几个心得前前后后调过很多次最深的体会是推理优化不是把参数堆高就完事先想清楚你的业务是长文档还是短对话、并发是稳定还是突发、显存是富余还是紧张再去选择量化策略和 batch 配置。LMDeploy 这套方案给我最大的收益是省心AWQ 量化、KV Cache 量化、连续批处理都是开箱即用不需要手写 CUDA kernel也不用自己管显存分配。如果你打算从零复现建议按这个顺序先原模型跑通再 AWQ 量化再开 KV Cache INT8最后再调并发参数。每一步都记录显存和吞吐这样你就能清楚知道每一项优化到底贡献了多少后面出了问题也容易回退排查。检索链路也一样bge-large-zh-v1.5 单独部署、单独测试确认向量质量没问题再接进 GLM-5.2 的生成链路。最后再分享一个小技巧生产环境可以把两个服务的启动参数写进 systemd 或者容器编排配置每次变更后保留一份参数快照。我在调参时经常对比不同cache-max-entry-count和max-batch-size的组合记录下来的数据比感觉靠谱得多。性能和质量的平衡点永远需要用你真实的业务流量去测不要直接照搬别人文章里的参数。
返回列表