
我现在看到大模型项目规模化上线所有人都会遇到同一个奇怪现象模型训练好了指标也准本地推理也很快一放到服务端真并发就变慢或者开始反复 OOM。我很少怀疑模型本身而是先排查推理链路。这篇文章想把我实际部署大模型推理服务的完整思路从头到尾过一遍重点记录哪些优化真正值得做哪些其实是看起来唬人、收益有限的花活。标题虽然叫人工智能大模型高效推理与部署技术实战但真正要掌握的核心不是某个现成 API 的参数而是理解生成过程本身。这篇文章适合做后端、平台、算法工程的开发者也适合被交办把模型上线提速的运维同学。我拿一个内部的知识问答项目举例。模型是一个 7B 参数的纯文本生成模型原本用原来训练时代的推理方式直连 GPU 做了简单服务上线后首字延迟 4 秒多并发稍微到 20 就出现排队和显存溢出。后来经过量化、显存规划、调度策略调整和压测调参稳定在几十路并发下运行首字延迟降到 1 秒以内而且显存高峰期没有再爆过。整个过程没有用什么高深理论都是能直接落地的常规手段下面拆开说。1. 推理慢在哪先分清三类瓶颈想优化推理先得搞清楚一个请求从进来到底经历了什么。大模型生成过程和传统深度学习模型完全不一样它不是一个前向就结束而是先处理整段输入再一个 token 一个 token 地生成。这两个阶段分别叫预填充和解码瓶颈性质完全不同绝大多数调优失误都是因为把它们混在一起谈。1.1 预填充阶段算力密集不是显存密集预填充阶段是用户输入进来的第一段处理。模型要看到完整的输入序列然后做并行计算为每一个输入位置生成中间状态也就是后面的 KV Cache。这个阶段计算量非常大尤其是输入很长的时候大规模矩阵乘跑满 GPU 计算核心延迟主要由算力决定。比如一个 7B 模型输入 512 个 token 的文本预填充阶段要做的矩阵计算量可能接近生成阶段很多倍。这个阶段通常响应时间在后端统计里体现为首字延迟的一部分。之前很多团队喜欢把所有优化都压在服务框架上但输入超长时预填充本身的计算量摆在那里不压它不行。1.2 解码阶段内存带宽才是天花板等预填充算完模型进入逐 token 自回归解码。这一步每生成一个新 token都要把模型全部权重重新读一遍同时也要反复读写不断增长的 KV Cache。单看一次计算量不大真正浪费时间的是权重读取。打个比方预填充像是整页抄课文处理器一直在动笔解码阶段像查字典虽然每个字都认识但要不停翻整本词典才能写出下一个字。对于 7B 模型权重如果用 FP16 存储大约是 14GB每生成一个 token 都要把这 14GB 从显存搬到计算单元。哪怕显存带宽是 2TB/s 的级别光搬运权重就要好几个毫秒这还只是纯搬运时间。所以解码阶段提升空间非常有限谁能让权重快速复用、谁减少冗余搬运谁就是赢家。1.3 两类瓶颈对部署策略的直接启示从这里就能理解为什么普通 HTTP 请求单次推理的模式不适合大模型在线服务它在预填充阶段让 GPU 算得飞快但一到解码阶段 GPU 算力大量闲置带宽也喂不饱整卡利用率非常难看。反过来如果系统能同时处理多个请求让一个请求在解码等待带宽的时候另一个请求在利用算力整体的吞吐就能明显提升。这就是后面要说的批处理和调度的理论基础。先看一张瓶颈对照表后面所有优化手段都会围绕这几行来阶段主要瓶颈典型现象优先优化方向预填充显存带宽外的计算能力首字延迟随输入长度快速上升压缩计算量、控制超长输入解码显存带宽与权重搬运生成速度稳定但受限批处理、权重量化、KV Cache 复用整体服务显存容量与调度策略并发一高就 OOM 或排队显存规划、连续批处理、超时策略服务端性能优化说穿了就是围绕这三行做文章。2. 量化落地不是无脑 INT8好多文章一上来就喊量化到 INT4实际动手才发现效果完全看模型和数据集。量化不是把所有权重简单转成低精度格式那么简单缩位之后的目标格式、缩放因子怎么定、校准集怎么选每步都会影响最终效果。量化做得好的项目能省一半甚至更多的显存做差了直接让生成结果胡言乱语。2.1 权重量化的实测边界我先从最稳妥的权重量化开始。常见思路是把 FP16 权重缩到 INT8 或 INT4并且按一组权重共享一个缩放因子。实践中效果比较好的做法是分块缩放通常每 64 或 128 个参数共享一个缩放因子精度损失明显低于一整层共用一个缩放因子的粗暴方式。在我部署的 7B 模型上INT8 权重量化几乎无损显存从 14GB 左右压到 7GB 多生成质量在测试集上差异可以忽略。INT4 则能压到 4GB 左右但某些专业领域的输出开始出现重复词和事实性错误。所以我的建议是如果显存不是极度紧张先用 INT8真需要 INT4也要保留原版模型做 A/B 对比别全量切换。2.2 激活量化的隐藏难点只量化权重简单把激活值也量化才是容易翻车的地方。激活值在不同 token、不同位置上分布差异很大某些位置会出现离群值导致量化误差被放大。这就需要准备一个和真实业务接近的校准数据集尽量覆盖长文本、短文本、多轮对话等场景然后在模型跑校准数据时统计激活值的分布范围再据此决定缩放因子。我自己踩过的一个坑是不做校准直接用一个默认缩放表上线后发现一个完全和量化无关的领域开始输出乱码。后来重新用真实问答数据校准问题才消失。如果你没有充足校准数据就不要强行做激活量化收益通常没有权重量化高风险却大得多。2.3 KV Cache 量化显存大户不能漏很多人算显存时只看到模型权重等跑起来才意识到 KV Cache 增长有多快。KV Cache 是预填充阶段生成的中间矩阵生成每个 token 都要往里追加数据。它的大小和层数、注意力头数、序列长度、并发数成正比。上下文一旦长KV Cache 会占掉比权重还多的显存。KV Cache 也能量化通常做法是降低它的存储精度比如从 FP16 降到 INT8减少一半占用。这一步相对安全因为 KV Cache 的价值主要存在于注意力计算中恢复精度空间相对小所以量化后质量下降通常不明显。但要注意KV Cache 量化和激活量化类似也需要考虑按通道或按头的缩放不能简单一个缩放因子糊上去。量化选择建议用一张表快速对比量化对象主要收益精度风险推荐场景模型权重直接降权重占用低风险INT4 需评估显存紧张必做激活值提升计算效率高风险需校准数据充分的项目KV Cache大幅省显存中低风险长上下文服务必须考虑3. 高效调度的隐藏战场显存编排与批处理策略量化把模型变小以后下一道坎是调度。同一个推理服务同时收多个请求时GPU 到底怎么分配计算资源和显存直接影响吞吐和延迟。很多人只知道加并发但错误调度方式会让并发越高、效率越低甚至互相拖累。3.1 静态批处理的浪费逻辑早期的一种做法是把多个请求攒成一个大批次前向计算时一起处理这就是静态批处理。听起来很合理但它的明显缺陷在于一批请求必须一起开始、一起结束。比如一个批次里有 3 个请求其中一个已经生成完毕另外两个还在慢慢解码那这个 GPU 的算力还会留给已经结束的请求位置形成空洞。如果用动态批处理请求按 token 粒度被调度计算谁有新 token 要生成谁就上计算单元已经完成的请求立刻让出位置。这就像食堂打饭静态批处理是十个人一起坐下有人吃完也不能走要等最慢的人吃完才一起收桌动态批处理是吃完一个走一个新来的立刻坐空位。实际对比下来动态批处理的吞吐能提升明显尤其在请求长度差异大的场景。3.2 连续批处理与逐 token 调度现在主流推理服务基本都是连续批处理思路服务端维护一个等待队列GPU 每个计算步骤动态决定这一轮处理哪些请求的下一页 token。这样一来每个请求都在按自己的节奏推进短的先结束长的继续跑。GPU 算力和显存不会因为一个慢请求而整体空转。连续批处理对服务框架的显存管理提出了更高要求。因为每个请求需要的 KV Cache 是动态增长的系统得能动态分配显存空间。实际工程里一般会引入类似分页的机制把显存分成小块按需分配给不同请求而不是每个请求预留一整块。这能大幅减少显存浪费让我在相同显存下扛住了更多并发。3.3 前缀缓存多轮对话和重复上下文的救星知识问答、客服对话这类场景用户经常基于同一份背景资料反复提问。每次请求都要重新预填充一遍相同的长文本太浪费算力。前缀缓存就是针对这一点把输入序列的中间状态按内容哈希缓存起来如果新请求的前缀和之前的某次请求相同直接复用缓存的 KV Cache跳过预填充计算。这个优化在短对话场景收益一般但在长文档问答里收益极大。我见过一个项目把平均首字延迟从 3 秒多压到不到 1 秒几乎全靠前缀缓存命中了相同的系统提示词和用户背景资料。接入成本不高却是很容易被忽略的性价比之王。4. 部署实战从量化到服务化的完整链路前面的原理讲得再多最终都要落到一条完整的部署流程上。我以某知识问答系统为例把从模型压缩到服务上线的完整链路走一遍。下面这些步骤都基于我们实际可行的常规方案不是某个特殊环境专属的魔法。4.1 第一步模型压缩与校准准备先确定量化目标和校准集。项目里输入主要是中长文档和问题我准备了一组大约几百条样本的小校准集尽量包含长文档、短问答、多轮上下文三种类型。然后做权重量化优先 INT8通过验证集对比确认生成质量没有明显劣化后再决定是否继续压 INT4。当时显存还够用所以停在了 INT8。做这一步时一定要记录每个量化版本在标准测试样本上的输出结果哪怕是肉眼对比也行。之后如果线上出现质量问题至少能快速判断是不是压缩引的锅。4.2 第二步推理引擎配置接下来需要在推理引擎里做好几件事。第一是设置最大并发批次引擎不是越大越好它受制于显存和输入长度上限我通常先设一个偏保守的值再通过压测逐步往上加。第二是开启连续批处理和前缀缓存这两个默认开关不一定在要在配置里显式打开。第三是把 KV Cache 分配成预占的显存池避免运行时反复申请释放显存造成碎片。模型权重也从加载即显存常驻改成类似内存映射加载的方式减少冷启动时间和内存复制。经过这一步同一个显存卡能容纳的并发请求数量明显上升原来 20 并发就爆显存现在能稳定跑更多。4.3 第三步服务接口与并发控制推理引擎就绪之后还需要一个对外服务层。这里我坚持用流式返回模型每生成一页 token 就通过网络推给前端用户看到的首字没变但体验上感觉生成速度更快。流式响应同时能降低前端等待焦虑也让后端不用等整个回答生成完才占用网络连接。服务层要做两个重要控制一是并发上限超过引擎承载能力就去排队而不是无脑涌入二是超时与重试超时后立刻熔断避免请求卡住把连接池占满。重试要加次数上限防止风暴式重试把所有资源打垮。这一步看着简单线上稳定性却基本靠它兜底。4.4 第四步监控与日志部署完成不等于结束。我至少会盯三个指标首字延迟、生成速度、显存峰值。这三个指标对应前面说的预填充、解码、显存容量三大瓶颈。服务层还要记录请求输入长度和输出长度分布辅助判断调度是否需要进一步调优。监控面板一旦出现显存持续上涨但不回落基本就是 KV Cache 没释放或显存碎片在堆积需要马上处理。这个信号比响应时间变慢更早暴露问题。5. 压测与调参用数据说话部署链路搭好后绝不能直接上线先压测。压测不是为了得出一张好看的报表而是为了找到服务在真实负载下的性能拐点。很多人上线后才发现响应时间像过山车就是因为没提前测出那个拐点在哪。5.1 核心指标别只看 QPS大模型服务的性能指标和常规接口不一样。除了 QPS更关键的是首字延迟和生成速度前者决定用户等待第一句话多久后者决定整体回复速度。业内一般还会看尾部延迟 p99因为大模型推理天然波动大平均数好看没意义最慢的那 1% 请求才是用户体验崩塌的根源。我压测时固定输入长度和输出长度分别测不同并发下的表现。低并发时首字延迟通常稳定高并发时首字延迟会明显上升因为新请求要等显存和算力让出来。关键是要找到延迟还能接受和吞吐尽量高的平衡点。5.2 一次典型的调参过程我用一种通用的调参流程来说明先固定并发数为 1测单请求基准延迟确定服务质量上限再把并发逐步抬高每次倍增并观察延迟和显存当 p99 延迟开始剧烈上升时就降回前一档作为线上限速值。最大批处理大小也是一个重要的旋钮。开太大长请求会塞满 KV Cache 池短请求反而进不来开太小GPU 算力又有闲置。我习惯从 32 开始向上向下各测几轮看哪一档吞吐高且 p99 稳。某些服务里把动态批处理等待窗口调大一点能把同时到达的请求合并成批次对短请求吞吐有明显帮助但会让单个请求多等一小段时间要忍受首字延迟小幅上升。5.3 压测结果如何反推配置压测结束后一定要形成一张对照表记录不同并发下的表现哪怕只有几行数字上线后被问为什么限制并发时也有据可依。我自己常带一张简化版并发数首字延迟 p50首字延迟 p99生成速度显存峰值结论10.8s1.0s24 token/s8GB基准161.2s2.5s280 token/s12GB可接受322.1s5.8s420 token/s15GB延迟超标这里数字只做示例但能清楚看出吞吐和延迟之间的跷跷板。线上我通常选择延迟还在可接受范围内的最高吞吐档位而不是单追求吞吐最高毕竟用户等不起。6. 上线后最容易踩的几个坑最后这部分是最想写的因为很多坑只有真正上线跑一段时间才会暴露。调参时看着一切正常流量一进来就原形毕露。这里列几个高频问题每个我都在实际项目里遇到过。6.1 显存碎片与长时间运行后的隐忧服务连续跑几天以后显存占用会缓慢上升最后突然 OOM。最常见的元凶是显存碎片。原因在于 KV Cache 和临时计算缓冲区频繁申请、释放显存被切得七零八落新的大块申请反而失败。解决办法不是简单重启而是在启动时就做显存预分配把 KV Cache 池、权重区、激活区提前圈好运行中尽量从池子里取。如果条件允许周期性低峰时段重新加载模型也能有效清理碎片但这不是根治手段。监控里出现显存只涨不降就要怀疑碎片了。6.2 动态长度与 padding 浪费输入长度参差不齐时很多服务会统一 padding 到批次内最长长度。结果就是短请求被拉长计算浪费算力。如果用的是连续批处理框架这个问题会好很多因为不再强制同步批次大小。但如果框架里某些算子仍然要求固定形状那会在内部悄悄做 padding性能一样受影响。排查方法是在压测时故意混入长短差距很大的请求观察生成的 token 吞吐有没有明显下滑。下滑就说明调度层有强制对齐有必要升级到支持动态形状的算子。传统框架里这一步是无解的只能在新框架里解决。6.3 长尾请求与队头阻塞在线服务里一个特别慢的请求会把后续请求都堵住这就是队头阻塞。大模型里更常见的是某个输出特别长的请求占住了一大批显存让短请求反复排队。应对思路是给不同类别的请求设不同优先级和超时普通问答给一个输出长度上限超了就强行截断内部高优请求走单独队列。截断操作要温和最好在自然句子边界截不要硬切。6.4 模型热更新与回滚模型迭代不可避免要替换版本。最忌讳的是直接停掉所有旧模型实例再起新实例这会让线上出现一段空窗期。稳妥做法是新版本先灰度上线流量逐步切换发现问题立刻切回旧版本。前后端协议也要做到兼容至少保证老请求不会打到新模型上产生格式错误。我做这些项目的最大体会是大模型高效推理与部署不是一个调一个参数就完事的活计而是把量化、显存规划、调度策略、压测方法和线上监控串成一条链。每个环节单独看都不复杂合在一起就能显著改变服务的稳定性。平时我还会保留一份压测脚本和显存趋势截图团队里出现性能问题争议时直接把数据贴出来比争论更有说服力。如果你刚接手类似任务别急着调参先把首字延迟、生成速度、显存峰值三个指标记录下来然后从量化开始一步步做多半很快能看到效果。