最近好几个团队都在问我同一个问题:大模型应用跑通了,功能也上线了,可为什么流量一起来,GPU账单和延迟同时失控?我每次都会反问一句:你是不是所有请求都丢给同一个最大最强的模型了?十有八九回答是肯定的。这种做法效果最稳,但也是成本爆炸的根源。把不同难度、不同延迟要求的请求,分流到不同规格的模型、不同精度的副本、不同位置的算力上,这就是混合推理(Hybrid Inference)的思路。这篇文章我会把我做资源优化与性能调优过程中总结的架构设计、关键路径和排查链路完整写下来,适合正在搭AI原生应用推理服务、或者觉得当前推理链路太贵的读者参考。
混合推理看起来像“大模型+小模型”的简单拼装,但真正落地会发现它牵扯请求分级、路由策略、缓存设计、批处理调度、弹性扩缩等一系列问题。任何一个环节拍脑袋决定,都会让系统在观测指标上表现得一团糟。下面我从底层逻辑讲到具体操作,最后把实测中踩过的三个大坑完整复盘一遍。
1. 为什么AI应用开始“混着跑”:单一架构撑不住的三个现实信号
1.1 信号一:请求难度方差太大,一刀切等于浪费算力
大多数AI原生应用的线上请求呈典型的“2-8分布”:约80%的请求是轻量任务,比如摘要、分类、改写、简短问答和意图识别;只有20%是长上下文推理、复杂工具调用、多步规划这类重任务。但很多团队从一开始就不做区分,所有请求统一走同一个超大参数模型。
这么做的代价非常直白:
- 成本维度:高算力卡的单位时间成本是普通算力卡的十几倍,大模型生成100个token的成本可能是小模型的百倍以上。
- 延迟维度:简单任务在超大模型里也要经历全量注意力计算,首token延迟轻松飙到好几百毫秒,用户体验毫无必要地变差。
- 容量维度:简单请求占用了昂贵算力的并发额度,真正需要大模型的复杂请求反而排队等待,导致整体吞吐上不去。
我在一个负载测试项目里看过这样的数据:简单摘要请求占线上流量72%,却消耗了约50%的昂贵算力时长。如果把这些请求中的60%迁移到小模型上,高算力池的负载可以直接下降三成,复杂请求的排队时间也会显著缩短。这不是靠增加机器解决的,而是靠分流。
1.2 信号二:能力峰值冗余与常态过载并存
单一模型架构还有一个很拧巴的现象:日常流量下昂贵算力大量闲置,而一旦遇到热点事件、大促、集中刷新这类峰值流量,复杂请求暴增,整个模型池又来不及快速扩容,延迟直接崩掉。
这背后的本质是“能力的峰度”和“资源的弹性”错配。大模型的巅峰能力是为了极少数的复杂请求准备的,但如果把它放在所有请求的必经之路上,就相当于让全城所有车辆都走一条八车道高速,而普通小路全部废弃。混合推理把轻量请求挡在小模型和低成本算力上,相当于把高速路让给真正的重型车辆,轻车上普通道路,整个路网效率自然就上来了。
1.3 信号三:上下文复用让“相似请求”占比越来越高
AI原生应用和传统API调用有一个明显差别:大量请求共享相同的上下文片段。典型场景包括:
- 同一个用户多轮会话中的历史消息,每次重新全量编码;
- 系统级提示词(system prompt)在所有请求中完全一致;
- RAG场景下,同一段知识库文本被大量用户反复提问时来回传入。
如果所有请求都从零开始对完整上下文做注意力计算,等于每次把同样的内容算了一遍又一遍。混合推理体系通常会配合分层缓存和前缀复用机制,把这些重复计算直接截掉。这也是单一模型单链路结构很难做好的优化点——因为它缺少“数据面拆分”的抓手。
2. 混合推理的决策层设计:从请求进来到模型响应,中间发生了什么
2.1 请求分级:先把流量分出层次,才知道该往哪条路走
混合推理的第一个关键动作不是在模型层,而是在网关层做请求分级。分级不是简单地按关键词匹配,而是结合业务规则和模型判断。比较通用的分级特征包括:
| 分级维度 | 典型判断方式 | 示例 |
|---|---|---|
| 任务类型 | 意图识别模型或规则 | 分类任务、摘要任务、代码修复、长文创作 |
| 输入长度 | tokenizer 后长度 | 短上下文(<1K)、中上下文(1K-4K)、长上下文(>4K) |
| 延迟SLA | 请求头或用户等级 | 在线交互要求 <500ms,后台任务可容忍 >5s |
| 能力要求 | 是否需要工具调用、外部知识、长链推理 | 需要工具调用则直接路由大模型 |
| 语义复杂度 | 小模型置信度 | 简单问答置信度>0.95 直接返回,否则升级 |
分级可以用硬规则(比如“无工具调用的短文本分类请求直接进小模型”),也可以用软规则(小模型先算一遍,输出置信度低再升级)。我更推荐“规则为主、模型为辅”的混合路由:规则保证可解释性,模型兜底处理规则覆盖不到的长尾情况。
提示:路由层最怕“死路由”。如果一条请求被判定进小模型后永远没有升级通道,那么误判带来的幻觉或答非所问就会直接砸在用户体验上。一定要在路由设计里留一条“动态升级”的路:小模型置信度低、超时或触发了安全校验,都要有明确的重定向机制。
2.2 多级模型池的规格划分与运行时隔离
我把混合推理的模型池分成三级,这三级不是拍脑袋分的,而是按“参数量级 × 量化精度 × 部署位置”三个维度组合出来的:
| 模型层级 | 参数量级参考 | 量化方式 | 典型场景 | 吞吐特征 | 成本特征 |
|---|---|---|---|---|---|
| L0 端侧/轻量 | 1B-4B | INT8/INT4 | 打招呼、短文本分类、简单问答 | 极高,支持高并发 | 几乎可以忽略 |
| L1 线上小模型 | 7B-14B | INT8/FP16 | 摘要、改写、实体抽取、简单QA | 高,单卡可承载大流量 | 数十倍低于大模型 |
| L2 大模型 | 70B以上 | FP16/INT8量化 | 复杂推理、工具调用、长文生成 | 低,需要多卡或高算力卡 | 成本与延迟都最高 |
特别强调一下隔离问题:三个层级的模型不要共用同一个服务实例,否则小模型会被大模型的显存占用和调度抢资源。比较稳妥的做法是每个层级的模型池独立部署,队列独立、批处理独立、缓存独立。它们之间只通过路由网关通信。
2.3 级联队列与超时逃逸:延迟链路上的暗雷
多级模型带来一个单模型架构没有的麻烦——级联队列。一个请求可能先在小模型排队,处理超时后又升级到大模型,又排一次队。如果不做控制,用户感受到的延迟不是“小模型的延迟+大模型的延迟”,而是“两个队列的排队时间叠加”。
因此网关必须具备三个能力:
- 延迟预算:从入口就给每个请求分配一个总延迟预算,比如简单请求300ms、复杂请求3s。预算内允许一次升级,超过预算则直接降级返回或超时终止。
- 逃逸条件:小模型在什么情况下升级?我建议同时满足“置信度低于阈值”和“剩余预算充足”才升级,避免每个超时请求都无脑往大模型甩,把大模型变成重试垃圾桶。
- 队列状态感知:网关在路由时应该参考下游队列长度,而不是只看模型种类。如果L1队列积压严重,可以在流量入口就直接分流到L2,或者触发扩容。
这三个能力缺失任何一个,混合推理在低峰期看不出问题,高峰一冲就散架。
3. 资源优化的四条关键路径:模型规格、缓存裁剪、批处理与弹性扩缩容
3.1 模型规格选择:用“够用”的模型代替“最强”的模型
模型选型是资源优化里性价比最高的一步,也最容易被跳过。很多团队直接选最大模型,理由是“省事、效果有保障”。但推理成本是持续性的,只要模型不下线,每一笔流量都在为当初的省事买单。
我的建议是建立一个任务维度的评测集,把线上请求按任务类型拆成若干子集,然后逐个测试候选模型的评测分数。判断标准很简单:在评测分数下降不超过2-3个点的前提下,尽可能选择最小的模型。实际项目里,我把摘要任务从L2级别降到L1级别,评测分只掉了2分,单次请求成本下降62%,P95延迟下降约35%。这个收益不需要改任何架构,只需要换一条路由规则。
选型时还要注意一个细节:小模型对提示词的敏感度通常高于大模型。同样一个任务,换一个措辞,大模型可能无所谓,小模型的表现可能波动很大。所以降级后务必做提示词的适配测试,必要时把提示词模板也调整一遍。
3.2 KV Cache的裁剪与量化:从显存里挤出可用容量
推理过程中,Transformer每一层都要保存历史token的Key和Value矩阵,这部分显存占用和序列长度线性相关。在线服务里,KV Cache往往会占掉整个显存的一半以上,大模型尤其明显。混合推理体系里不同模型规格差异大,KV Cache的管理也要分开做。
这里有几个立竿见影的手段:
- 启用分页显存管理机制:让KV Cache按块分配,减少碎片和浪费,避免显存利用率忽高忽低。
- 对KV Cache做量化:从FP16降到INT8,显存占用直接减半,精度损失在多数任务上可控。但建议先在评测集上验证,不要直接上生产。
- 配置合理的最大序列长度和回收策略:很多服务把max_seq_len调得过大,结果大量显存被预留却根本用不上。按业务实际输入分布设置上限,超长请求单独走特殊通道。
我见过一个项目把max_seq_len从32K降到8K,配合KV Cache量化,单实例承载的并发数直接翻了1.6倍,而实际业务里超过8K上下文的请求占比不到0.3%。这类“保守配置”反而释放了大量可用容量。
3.3 连续批处理与同质合流:吞吐提升的引擎
静态批处理的做法是等请求攒够一批再一起推理,吞吐上去了但延迟很容易不可控。连续批处理(continuous batching)允许新请求插入到正在执行的批次里,边生成边入队,能显著提高GPU利用率,同时保持单请求延迟稳定。
但在混合推理里,我对批处理有一个额外的建议:做“同质合流”。把同一规格模型、相近输入长度范围、相近生成长度预期的请求合到一个batch里。为什么?因为一个batch的生成速度取决于最慢的那条序列。一个短问题和一个长文章生成请求混在同一个batch里,生成长度长的那条会拖住整批,短请求白白陪跑。把长度差异过大的请求分开,整体吞吐反而更高。
同质合流会让批处理策略更复杂,但收益很可观。我在模拟项目X里做过对比:同样流量下,启用同质合流后,P95延迟下降了28%,GPU算力的单位时间产出增加了接近40%。
3.4 弹性扩缩容与温冷算力分离
混合推理的算力池不能一视同仁。轻量任务所在的服务实例应该具备秒级扩缩容能力,见流量高峰就快速拉起;重任务所在的高算力池则要保持相对稳定,避免频繁扩缩带来的调度开销和模型加载时间。
扩缩容触发条件建议同时看两个指标:
- 队列堆积:网关层各队列的平均等待时间超过阈值;
- GPU利用率:连续几分钟利用率高于80%,同时存在排队则扩容。
还要注意缩容方向不能太激进。推理服务扩一个实例需要重新加载模型权重,冷启动可能花几十秒甚至几分钟。如果你用“利用率低了就立刻缩容”,下一个高峰来临时扩容跟不上,服务直接被压垮。我的经验是至少保留20%-30%的冗余容量,宁可利用率低一点,也要保证缓冲空间。
4. 性能调优的度量体系:延迟、吞吐与成本三元组的取舍实战
4.1 先建指标,再谈优化:没有度量就没有收敛
混合推理的调优比单模型复杂,因为它牵涉路由、多级队列、缓存、批处理多个环节。我建指标时一定拆到最细,至少包含以下维度:
| 指标类型 | 具体指标 | 用途 |
|---|---|---|
| 延迟 | 首token延迟(TTFT),端到端延迟,P50/P95/P99 | 判断用户体验与排队情况 |
| 吞吐 | 每秒请求数(RPS),每秒生成token数,缓存命中率 | 判断系统承载能力与冗余计算 |
| 成本 | 单次请求成本,每百万token成本,高算力池利用率 | 判断资源花费是否合理 |
| 路由 | 各层模型流量占比,升级率,超时逃逸率 | 判断分级策略是否在正常工作 |
我调试时有一个固定的排查顺序:先看P95延迟,延迟异常则看队列平均等待时间;队列没问题就看缓存命中率;缓存没问题就看路由升级率。换句话说,先定位“慢在哪一层”,再决定“调什么参数”。如果一上来就乱调批处理参数,往往治标不治本。
4.2 参数调节顺序:不要一上来就动batch size
很多资料喜欢把max_batch_size和max_wait_tokens放在调优第一步,但我踩过几次坑后总结了更适合混合推理的调节顺序:
- 先放宽客户端超时和重试策略,把超时重试风暴排除掉(这部分我后面会展开);
- 再调批处理窗口,每次只动max_batch_size或max_wait_tokens中的一个,观察P95和吞吐的变化;
- 确认批处理稳定后,再调KV Cache预留与显存分配,把OOM率压到0;
- 最后才动路由策略,比如升级阈值、逃逸条件和各层流量占比。
为什么要这个顺序?因为批处理和缓存的问题会放大路由问题的表象。如果路由升级率正常、但批处理窗口过大导致延迟飙升,你可能会误判成“小模型能力不行”,然后错误地把流量切到大模型,成本瞬间翻倍却没能解决问题。
4.3 延迟预算拆解:把看不见的排队摊在明面上
一次请求的总延迟由这些部分构成:
- 网络往返RTT;
- 网关排队时间;
- tokenizer编码时间;
- 模型首token延迟(包含预填充阶段);
- 逐个token的生成时间;
- 输出解析与反序列化时间。
其中网络、tokenizer、反序列化通常是百毫秒内的固定开销,变化最大的是网关排队和模型延迟。实际调优中最重要的判断是:模型计算延迟占比高,说明算力吃紧;排队延迟占比高,说明并发调度有问题,不一定是模型慢。
混合推理里我会给每个路由通道设一个延迟预算。以简单请求为例,总预算500ms:网络100ms、排队50ms、模型计算250ms、生成与解析100ms。当监控显示排队超过100ms,就说明该通道的并发或批处理设置需要调整。预算拆解的意义在于,把“慢”这个笼统的感受变成可操作的定位结论。
5. 实测中踩过的坑:从路由命中率暴跌到超时重试风暴的完整排查链路
5.1 坑一:路由置信度阈值设太高,升级率暴涨,小模型形同虚设
现象是某系统上线一周后,成本没降反升,P95延迟也在涨。我第一反应是查升级率——结果从预设的5%涨到了30%。也就是说大量请求在小模型跑了一遍后又被升级到L2,小模型形同虚设,还白白多付了一次推理开销。
完整排查链路如下:
- 看路由层指标:升级率从5%涨到30%,命中率却正常,直接锁定路由策略异常;
- 看小模型单独延迟:单独压测P95正常,排除小模型本身性能问题;
- 追日志看升级原因:发现大量升级请求的路由judgment逻辑命中了“置信度低”条件,但置信度低的原因是用户问的是情绪类、讽刺类问题,小模型输出时置信度天然偏低;
- 找到根因:路由规则把所有置信度<0.95的请求都升级,而这类请求恰好占比高;
- 解决:给简单任务开一条“中档模型通道”,置信度在0.8-0.95之间的请求先走中档模型,置信度<0.8才走L2;同时把小模型的提示词增加了一个“情感判断”前缀,帮助置信度稳定。
修复后升级率从30%回落到7%,成本恢复预期,P95延迟还下降了约12%。这个坑给我们的教训是:置信度阈值不是越高越安全,它需要和线上请求的真实分布对齐,否则就是拿大模型给分类器兜底。
5.2 坑二:动态字段击穿前缀缓存,缓存命中率只有3%
另一个项目的现象很典型:显存持续上涨,服务频繁OOM,观测面板上缓存命中率只有3%。当时我怀疑是缓存逻辑没有生效,追查后发现问题出在缓存key的构造方式上。
完整排查链路如下:
- 看缓存命中率:3%,基本等于没缓存;
- 看缓存key的拼接逻辑:发现prompt构造时把session_id、用户ID这类动态字段拼在了系统提示词之前;
- 验证根因:同一个知识库问题,两个不同用户请求的prompt前缀完全不同,token级前缀缓存永远无法命中;
- 解决:对prompt做规范化,系统提示词固定在首段,动态字段(session_id、用户信息)统一放入显性分隔符之后,并在送入缓存key计算前先剥离;
- 额外优化:在提示词级缓存之外,叠加一层语义缓存,完全相同的业务意图直接返回历史结果。
修复后缓存命中率从3%涨到了47%,显存占用降了一半,OOM消失。这个坑提醒了我一个通用原则:前缀缓存的命中和prompt的结构化程度强相关,而AI原生应用的prompt经常是字符串拼接拼出来的,不规范化,上层优化全是空谈。
5.3 坑三:客户端超时设置比P99还短,重试风暴把服务打挂
这个事故印象特别深刻。某系统某天下午请求成功率突然掉到50%,GPU利用率冲到100%,但新的有效推理请求其实并不多。看面板第一眼像算力不够,实际是客户端调度出了问题。
完整排查链路如下:
- 看服务端监控:P99延迟约280ms,看起来不算差;
- 看客户端配置:超时时间300ms,比P99还短,等于服务端每100个请求里有1个会超时;
- 看重试策略:超时后客户端会重试2次,每次重试都会把请求重新塞进队列,而重试请求又会遇到同样的问题;
- 验证风暴机制:GPU利用率接近100%但有效产出很低,说明大量算力在处理超时重试的重复请求,正常请求反而排不上队;
- 解决:客户端超时从300ms放宽到1200ms,重试策略改为指数退避加随机抖动,同时在网关层加了熔断逻辑,当某个通道错误率超过阈值时自动暂停转发;
- 后续验证:一周内无同类事故,P95延迟反而稳定在250ms附近。
这个坑本质上不是模型慢,而是调用方和服务方的时间假设不一致。混合推理里多级路由本来就会引入额外排队,客户端超时参数必须基于实测P99而不是想当然的“越快越好”。
6. 我现在用的推荐配置与后续可扩展方向
6.1 三级模型体系配置参考
结合前面这些经验,我现在给新项目做混合推理时通常直接采用这套配置:
- L0/端侧或轻量通道:处理打招呼、简单分类、短文本匹配等请求,量化到INT8,走CPU或低端算力即可,不设重试;
- L1/线上小模型通道:承载约70%的流量,包括摘要、改写、结构化抽取、简单QA,量化到INT8,独立队列和批处理,缓存优先;
- L2/大模型通道:承载能力最强也最贵的那部分流量,包括长文推理、工具调用、代码生成、多步规划,保持FP16或INT8高质量运行,并设置严格的逃逸保护。
网关层统一负责请求分级、延迟预算、动态升级和重试熔断。每个通道的监控面板分开,路由升级率、各层占比、缓存命中率必须单独成图。
6.2 性价比极高的几个优化动作
如果团队时间有限,按性价比排序,我建议先做这几件事:
- prompt规范化加前缀缓存:把动态字段彻底从公共前缀里拆出来,缓存命中率通常能提升30个百分点以上,这是投入产出比最高的一步;
- 简单任务改走轻量通道:先按任务类型和输入长度各切10%流量验证效果,确认质量不降再逐步放量;
- 批处理做同质合流:把长短请求分开合批,照顾P95延迟和吞吐的平衡;
- 客户端重试改成指数退避加抖动:这一步既不花钱又不改动模型,却能在尖峰流量下保住可用性。
6.3 后续可以继续扩展的方向
这套体系稳定后,可以继续加一层更细的能力:
- 全链路可观测性:给每个请求打上“路由标签”,记录它走了哪个通道、是否升级、升级原因是什么、实际生成了多少token。没有这些标签,你永远只能猜测流量分布,而不是看到流量分布。
- 语义缓存:从token级缓存升级到“同一业务含义直接返回历史结果”,对重复性业务(比如客服问题、报表查询)特别有效,但需要加一层相似度判断和结果新鲜度控制。
- Agent场景下的分层推理:复杂Agent任务里,简单工具调用和中途步骤不一定要实时调用大模型,可以用规则模板或轻量模型替代,只保留最终聚合和关键决策走大模型。
最后再分享一点个人体会。混合推理本质上不是“用谁替代谁”,而是在所有请求进来之前,先想清楚这个请求到底需要多强的模型才能让用户满意。我们团队初期把大量精力花在调大模型效果上,后来调整方向,先做请求分级和模型瘦身,整体用户体验反而变好了,成本还降了一大截。建议你从两件事开始:把线上日志按请求难度分一下类,给重复前缀加一层缓存,跑一周看指标变化,收益会比想象中直观很多。