选大模型推理方案,很多人习惯先看榜单、看单卡推理速度,但我做了大半年业务侧的大模型应用落地,最深的体会是:真正决定项目生死的不是峰值性能,而是推理稳定性。这里的稳定性不是指模型输出内容稳,而是服务层面的时延一致性、并发抗压能力和故障恢复能力。这篇内容,我就结合自己的压测和上线经验,聊聊怎么从业务落地的角度评估大模型推理能力,以及火山引擎这类托管推理平台在稳定性上的优势到底体现在哪里。
如果你的团队正在做大模型应用的选型和上线,或者已经部署了自建推理服务但经常被线上问题搞得焦头烂额,这篇文章应该能帮你少踩很多坑。我会先把“为什么稳定性比跑分更重要”这件事讲透,再拆解推理服务稳定性的关键技术点,然后给出一套可以直接落地的压测和监控方案,最后整理真实故障的排查记录。
1. 为什么业务落地首先看推理稳定性,而不是排行榜分数
1.1 排行榜分数是实验室环境下的“理想值”
大模型推理能力怎么比较?很多人第一反应是看排行榜:MMLU、Benchmark GLUE、推理评测集,或者说看单卡能跑多少 token/s。这些指标确实能反映模型的智力上限和推理引擎的优化水平,但它和真实业务运行之间隔着一层很厚的“理想化滤镜”。
实验室测分的时候,请求是干净的,context 长度是固定的,并发是可控的,GPU 是独占的。而真实业务里,请求长短参差不齐,高峰期和低峰期流量相差几倍甚至几十倍,模型可能和多个模型共享资源,网络也有抖动。这些因素叠加之后,榜单上那点性能差距往往会被更大规模的服务可靠性差异掩盖掉。
举个例子,我曾经压测过一个开源模型,单线程连续发请求,Token 生成速度非常漂亮,每秒能到 80 token 以上。但一旦把并发拉到 64,P95 时延直接从 1 秒飙到 12 秒,还伴随大量超时。这种表现放在聊天机器人里,用户最多觉得“卡”,但如果放在自动化客服、合同审查、报表生成这类业务里,就是事故——下游任务等不到结果,整个流程就会阻塞。
所以,我评估大模型推理能力时,会先问三个问题:
- 在目标并发下,时延分布是否稳定,而不是只测一次平均值。
- 在长尾流量冲击下,服务是优雅排队还是直接雪崩。
- 出故障后,恢复是分钟级还是需要人工重启部署。
这三个问题回答好了,才能谈业务落地。
1.2 业务场景中推理不稳定是真实的“事故源”
我在实际项目中遇到过不少“推理不稳定”的表现,它不一定是模型本身的问题,但最后都算在了“推理能力”的头上。比较典型的场景有这么几类。
第一类是突发流量导致的时延雪崩。早上九点上班,业务方定时推送几百份合同给大模型做抽取,服务接收到的请求瞬间翻倍。如果推理服务没有合理的队列机制、批处理容量上限和优雅降级策略,GPU 显存会被瞬时打爆,或者请求全部堆在等待队列里,然后连环超时。
第二类是共享资源引发的结果异常。多个模型部署在同一批 GPU 上,某个模型触发了一次长上下文请求,占用了大量显存和计算资源,其他模型的推理速度就被拖慢。这种“互相干扰”在监控图上看得清清楚楚:一个模型开始跑长文本任务,其他模型的 token 生成速度曲线一起往下掉。
第三类是实例重启造成连接中断。推理服务本身是长连接请求,一旦某个 GPU 卡出现 NVLink 错误、显存 ECC 报错,或者容器被杀掉,正在处理的所有请求都会失败。自建方案如果没有做快速的故障转移和请求重放,用户端感知到的就是大量 5xx 错误。
这类问题让我意识到,大模型推理能力不能只看“聪明不聪明”,更要看“扛不扛得住”。火山引擎这类云上推理平台之所以在稳定性上有优势,不是因为它的模型比别人强很多,而是因为它把服务层面的弹性伸缩、多副本容灾、故障自动恢复这些能力做成了托管能力,业务方不用自己从零搭建整个可靠性体系。
2. 推理稳定性背后,这几个技术细节值得深挖
2.1 动态批处理和显存管理如何影响稳定性
大模型推理的基础技术,这几年已经从静态批处理演进到动态批处理。静态批处理很好理解:攒够固定数量的请求再一起推理,比如每批最多 4 条,等到有 4 条才处理。这种方式在高并发且请求长度均匀的时候效率不错,但一旦请求长度偏差很大,短的请求等着长的请求跑完,整体时延就会被拖得很长。而动态批处理,或者说 Continuous Batching,是每个 token 生成完就立刻把这条请求挪走,空出来的计算槽位马上接新的请求,让 GPU 一直处于高利用率状态。
这个机制听起来很棒,但它对显存管理的稳定性要求极高。因为每条请求的 KV Cache 是动态增长的,如果预留显存过少,长请求一来就会出现显存溢出;如果预留过多,又会让能并发的请求数量变少,降低吞吐。很多推理框架处理不好这个平衡,表现就是服务运行几个小时后,随着显存碎片增多,OOM 频率越来越高,最后进程崩溃。
在自建推理服务时,我踩过几次 OOM 的坑,解决方案大多是在请求入口限制最大序列长度、设置批处理的最大 token 数上限、定期清理显存碎片。但这些方案都是“堵”而不是“疏”,治标不治本。而托管推理平台通常会在框架层面做更精细的显存分配,比如根据历史流量动态调整预留池大小,或者通过多实例负载均衡来避免单个实例被极端请求打爆。这也是为什么在业务压力比较大的时候,平台型推理服务的稳定性往往优于自己搭的裸框架。
2.2 首字延迟、尾字延迟和并发抖动的取舍
评估推理服务稳定性,最需要盯的两个指标是 TTFT 和 TPOT。TTFT 是首 Token 延迟,也就是从请求发出到第一个 Token 返回的时间;TPOT 是每个输出 Token 的时间,决定了生成速度。这两个指标直接决定了用户的“响应手感”。
问题在于,TTFT 和 TPOT 在并发压力下会和 GPU 调度方式强相关。有些推理框架为了最大化吞吐,会把新到的请求排队,优先处理已经在生成中的请求,导致 TTFT 在并发高的时候急剧变大。用户感受到的就是“点完发送后卡了很久才开始输出”,但在首 Token 出来之后速度又还行。另一种做法是牺牲一点吞吐来保证首 Token 快速响应,但这样在高峰期容易形成请求积压。
稳定性的本质,就是要在吞吐和时延之间找到一个不随流量变化而剧烈波动的平衡点。我在压测中会特别关注两条曲线:一条是 TTFT 的 P99,另一条是 TPOT 的标准差。如果 TPOT 的标准差很小,说明模型生成速度很稳;如果 TPOT 波动很大,说明计算资源经常被抢占,或者批处理策略不稳定。
火山引擎这类平台在做推理优化时,通常会在负载均衡层面把不同优先级的请求分离,比如实时交互请求和异步批量请求走不同的队列,避免“长尾任务拖垮短任务”。这点在业务落地时很重要,因为一个服务里往往同时存在实时问答和后台批量分析两类流量。自己写调度逻辑非常复杂,但平台托管通常已经把这层做进去了。
2.3 长上下文场景最容易暴露稳定性的短板
如果把普通短请求比作“轻松跑一公里”,长上下文请求就是“扛着几十公斤装备跑马拉松”。一个 2 万 token 的请求,KV Cache 占用可能比 20 个普通请求还多,计算耗时也会呈超线性增长。很多推理平台在短文本场景下表现亮眼,一旦丢过来几个长文档,就立刻露出短板。
长上下文场景常见的有:知识库问答时把整篇文档作为上下文、长代码文件解析、多轮对话历史累积。这些请求不仅消耗算力,还容易引发超时。我遇到过的一个典型问题是:某模型配置的最大上下文是 32K,但当请求长度接近这个上限时,TTFT 会莫名其妙地膨胀到 20 秒以上。原因是框架为了避免显存溢出,会提前做大量的内存整理和计算迁移,反而拖慢了响应速度。
在稳定性评估时,我建议一定要把长上下文请求融入压测流量。单独测短请求,模型再弱也看不出来;混入一定比例的长请求后,服务的真实水平就暴露了。平台侧对长上下文的优化通常体现在两点:一是高效的注意力算子,比如 PagedAttention 对 KV Cache 的分块管理;二是自动把超长请求拆分或降级,避免单个请求占满整个实例。火山引擎提供的大模型推理服务,对长上下文场景会有更明确的资源隔离和上限保护,这也是它在大模型知识库应用场景中比较容易被选中的原因。
3. 从压测到监控:搭建一套推理稳定性评估流程
3.1 先设计符合业务特征的压测场景
评估推理稳定性,第一步不是找最好的压测工具,而是先定义“什么样的流量算正常”。很多团队直接拿公开数据集跑一遍,或者用脚本随机生成一堆 prompt,这测出来的结果参考价值有限。正确的做法是从线上日志里抽样真实请求,统计出三个分布。
第一,请求长度分布。有多少请求是几十 token 的短问题,有多少是上千 token 的文档,最长能到多少。第二,请求频率分布。一天之内哪些时段是高峰,高峰 QPS 是多少,平峰又是多少。第三,输入输出比例。有些业务是短问长答,比如写作助手,输出可能有几千 token;有些业务是长文短答,比如抽取总结。输出 token 数量直接影响 TPOT 和总时延。
有了这三个分布,再设计压测脚本就有的放矢了。我常用的方式是写一个 Locust 脚本,按比例生成不同长度的 prompt,然后按线上 QPS 的 1 倍、2 倍、3 倍去打。这里注意,压测不能只有“目标 QPS”或“目标并发”这两个参数,必须同时带“请求长度分布”,否则很容易出现“并发 50 全短文本压测通过,但实际上线后因为长文本比例高导致一直超时”的情况。
一个简化版的压测脚本思路大致是这样:
from locust import HttpUser, task, between import random class LLMUser(HttpUser): wait_time = between(0.1, 0.5) @task(7) def short_chat(self): payload = { "model": "chat-model", "messages": [{"role": "user", "content": "你好,介绍一下你自己"}], "max_tokens": 100 } self.client.post("/chat", json=payload) @task(2) def long_ctx(self): content = "背景信息" * 2000 # 模拟长文档 payload = { "model": "chat-model", "messages": [{"role": "user", "content": content}], "max_tokens": 500 } self.client.post("/chat", json=payload)实际跑的时候,要开启 Locust 的响应时间统计,同时注意不要用 Locust 所在机器作为观测点而忽略了服务端本身的监控。
3.2 盯住这5个稳定性指标
压测结果出来后,不能只看平均时延,我每次都会整理一张指标表,只看五个数值。
第一个是 TTFT P99,首 Token 延迟的 99 分位。P99 比平均值更能反映长尾问题。第二个是端到端时延 P50 和 P95 的差距,差距越大说明越不稳定。第三个是错误率,包括 HTTP 5xx、超时和空响应,稳定性的底线是错误率不能随着并发升高而失控。第四个是 token 生成速率变异系数,也就是标准差除以平均值,看生成速度是否均匀。第五个是排队等待时间,如果这个值持续上涨,说明服务已经过载但机制上没有体现,接下来就是要出事故了。
很多平台会在监控页面上直接给出这些指标,比如火山引擎的推理服务监控,一般会展示调用量、时延分位数、TPM 吞吐和使用率等。自己搭建的话,至少要用 Prometheus 记录每个请求的开始时间、结束时间、输出 token 数,然后通过 Grafana 计算分位数。
对于阈值,我给一个经验范围,具体根据业务调整:
- TTFT P99 要小于 2 秒,聊天场景下超过 3 秒用户就会明显觉得卡。
- 端到端时延 P95 与 P50 的比值,最好小于 3 倍。如果超过 5 倍,说明服务在并发下已经进入了“时延崩溃”状态。
- 错误率在高并发测试期间不允许超过 0.5%。允许偶发超时,但不允许系统性报错。
- GPU 利用率波动的系数要小于 30%,否则说明批处理策略或负载均衡有问题。
还有一个很容易被忽略的指标:长时间运行的稳定性。压测跑 5 分钟看不出问题,跑 2 小时才能暴露显存泄漏、缓存逐渐膨胀、线程池被占满等问题。所以每个压测场景至少要持续 30 分钟以上,并且观察内存和显存曲线是否随测试时间稳步上升。
3.3 结合火山引擎推理服务做稳定性验证的几点心得
前面说的自建评估流程同样适用于评估托管推理平台。这两年我在火山引擎方舟这类推理服务上做过几次真实的业务验证,试过把自建的开源模型服务和平台的托管服务放在一起跑同样的压测流量,有一些很直观的感受。
一个感受是,平台上做弹性伸缩省了很多事。自建推理服务时,我需要提前预估高峰流量并手动扩容节点。分布式训练和推理经常会让你在凌晨被电话叫醒:“GPU 又满了,赶紧加节点”。而火山引擎的推理服务通常支持配置最小实例数和最大实例数,设定 CPU 或 GPU 利用率阈值后自动扩缩容。扩容过程中请求不会全部失败,因为有负载均衡器在做灰度切换,新实例健康检查通过后才会接入流量。
另一个感受是故障自动恢复的兜底策略。自建方案遇到进程崩溃,通常需要监控告警、人工登录服务器、查看日志、重启服务,平均恢复时间大概 15 分钟到半小时。平台托管服务一般会做自动重启和实例替换,恢复时间能压缩到几分钟甚至更短。虽然我们不应该依赖自动恢复来解决架构问题,但它确实能大幅降低业务的故障窗口。
还有一点是配额管理。平台会把单实例的并发上限、TPM、RPM 暴露成配额指标,超限时返回 429 而不是直接压垮节点。这种温和的限流方式,对业务方其实更友好。我在自建服务上也习惯给网关加上类似的限流逻辑,但自己做容易漏掉很多细节,比如不同模型的混合配额管理、请求排队和拒绝之间的边界等。
4. 实战中的典型故障与排查技巧
4.1 高并发下接口大面积超时,问题出在哪
有一回做即时通讯场景的智能摘要,上线第二个小时就收到告警:错误率到了 8%,大量请求超时。当时第一反应是模型被大流量打崩了,但点进监控一看,GPU 利用率才 45%,显存也没到 95%。这就很奇怪,资源没满,请求却超时。
后来查了服务和网关日志,发现超时的请求全部卡在“等待可用连接”阶段。原因是客户端用了连接池,并发量一上来,连接池默认大小只有 10,其余请求都在排队等连接。这其实不是推理服务的问题,而是客户端连接管理配置不合理。把连接池大小调大,同时加上读超时和连接超时的合理配置,错误率立刻降到 0.1% 以下。
这个故障很典型,也很有代表性。很多线上超时不是模型或者推理框架的问题,而是客户端、网关、DNS 解析、负载均衡器配置等外围环节导致的。排查步骤应该是自下而上:先看客户端日志是不是连接失败,再看网关是否健康,最后排查推理服务本身。千万不要一超时就重启推理实例,那样反而会掩盖真实原因。
4.2 长文本请求导致节点重启,怎么快速恢复
有一次压测,只要一发送超过 24K token 的长文本请求,某个推理实例就出现 CUDA OOM,然后容器被 K8s 杀掉重启。虽然这个请求本身失败,但更麻烦的是,重启期间其他运行中的请求全部断连,引起了一连串的 5xx。
这是长上下文场景最典型的稳定性事故。当时我们有两个处理方向:一是在模型服务里把 max input length 限制在 20K,超过就直接拒绝或截断。这个方案简单有效,但会牺牲业务价值。二是让长文本请求走单独部署的高显存实例,不让它和短文本请求共用资源。后来我们在火山引擎的推理服务上测试,平台可以根据模型部署实例分配不同的上下文长度上限,还支持在多副本之间做基于请求长度的路由。这样短请求走标配实例,长请求走专用大显存实例,互不干扰。
如果是在自建环境里排查这类问题,建议先看显存监控,确认 OOM 是渐进式的还是瞬时爆发的。渐进式说明存在 KV Cache 泄漏,瞬时爆发说明是单个长请求超过了预留显存。前者的解决方案是升级推理框架或开启显存碎片整理,后者的方案则是限制长度或者把长请求路由到专门的实例。
4.3 稳定性不代表输出质量,别把参数波动误判为故障
还有一个容易被误判的场景:模型跑着跑着,输出内容开始变短、变啰嗦,甚至偶尔回答“我不知道”。业务方觉得“模型推理不稳定”,要求切换平台。但我查了服务监控,时延和错误率都很正常,GPU 也没有异常。最后发现问题出在 prompt 里的 temperature 和 top_p 参数被另一个服务不小心改了。
大模型的输出本身带有随机性,这是正常现象。评估推理稳定性,衡量的应该是服务的可用性和性能,而不是模型输出的内容。你可以通过固定 seed、降低 temperature、增加 prompt 约束来让输出更可预测,但这不是推理服务“稳定性”的范畴。在排查时,一定要把服务监控和模型输出分开来记录,否则很容易出现“稳定性不好”的错误结论。
我在做压测时,会固定一批测试 prompt,把 temperature 设为 0,保存每次的输出结果。这样既能观测服务性能,又能记录模型输出的可重复性。如果输出差异很大,我会先检查采样参数和 prompt 版本,再考虑是模型的问题还是推理精度的问题。
4.4 一份实用的避坑清单
最后整理一份我在大模型推理稳定性实战中沉淀下来的避坑清单,都是真实踩过的坑。
- 不要在高并发压测的时候只用 GPU 利用率判断系统是否超负荷。GPU 利用率达到 100% 不代表吞吐最高,可能是在大量重计算或排队。
- 不要把 max_tokens 设得很大。max_tokens 是上限,但每次请求都会在生成到 max_tokens 时才释放连接。很多超时不是推理慢,而是每个请求都默认输出 4096 token,导致单个请求持续几十秒,挤占资源。
- 给推理服务配置超时时间时,要区分“连接超时”和“读取超时”。连接超时设短一点,快速失败;读取超时要根据模型生成的 max_tokens 估算,不能写死。
- 长上下文请求一定要单独统计时延,不要和普通请求混在一起算平均。否则平均时延很好看,长请求其实一直在超时。
- 压测必须包含“请求积压后再灌入流量”的场景。很多服务能在低负载下稳定运行,一旦队列已经积压,新请求会全部快速失败,这就是雪崩效应。
- 上线前至少做一次故障演练:手动杀掉一个推理实例,看服务是否能自动恢复、正在处理的请求是否会重试、监控告警是否准确触发。这一步最能检验平台稳定性。
大模型推理能力的评估,本质上是从“模型有多强”转向“服务有多稳”的过程。我自己在做业务选型时,会把稳定性权重放到性能权重之前,先保证服务不崩、不超时、不抖动,再去追求更快的 token 生成速度。火山引擎这类托管推理服务给到的价值,是让团队可以把故障恢复、弹性扩缩、长尾保护这些底层问题交给平台,把精力集中在业务逻辑和模型效果上。如果你是刚准备上线大模型应用,我建议别只拿一个榜单去比较,而是用真实业务流量去压测,看看在压力下谁还能稳住。稳住的,才是能陪你走完业务落地的那个。