ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

AI模型推理延迟监控实战:从指标拆解到全链路可观测体系

AI模型推理延迟监控实战:从指标拆解到全链路可观测体系 我是一个经常跟模型推理打交道的人老实说“AI 模型推理延迟监控”这件事大部分团队一开始都是不放在心上的。模型上线跑起来功能正常偶发一次超时大家第一反应是网络抖动直到线上反馈“AI 客服转人工率飙升”、“推荐刷不出来”时才手忙脚乱地翻日志。这种被动局面我经历得太多了所以想把这几年在推理延迟监控上踩过的坑、攒下的经验沉淀成一份方案讲清楚。这套东西适合谁正在做模型部署、AI应用开发、算法工程的团队也包括一个人要扛起“算法后端运维”的独立开发者。它解决的问题不复杂用一套可观测的体系把“模型到底慢在哪”变成不用猜、不用吵、看一眼就知道答案的事。传统的Web接口监控我们都很熟但推理服务有个天然差异它的延迟构成极其复杂模型输入预处理、排队等待、显存调度、前向计算、输出解码、网络回传每一段都可能成为瓶颈而且瓶颈会随着流量特征、输入数据分布甚至同一批请求的内容动态漂移。不把监控粒度拆到推理内部你只能看到“接口慢了”但永远不知道是“模型算了太久”还是“请求在排队”。这篇文章不讲空理论我会按实际的落地顺序来先讲清楚延迟指标到底该怎么拆解、拆到多细才够用再给出一套可以照着搭的监控体系包含工具选型和关键配置然后复盘一次真实的线上延迟突刺排查过程最后补充一些文档里不会写的细节和经验。1. 延迟上去了最先懵的总是算法和运维先说一个特别典型的场景。某个周五下午运营反馈“AI 审核一直转圈”你打开监控大盘一看接口P99延迟从800ms飙到了4s。算法看了一眼说模型没问题单卡测试延迟正常运维查了CPU、内存、带宽都说没打满后端说请求量没有突增。四个人都有自己的数据但谁的数据都解释不了问题。这种“三不管”状态就是推理延迟监控缺失的典型症状。传统监控工具解决不了推理延迟问题原因在于它们面向的是通用服务器指标。CPU使用率、内存占用、网络流量这些指标反映的是系统资源状态但它们和“模型推理到底花了多久”之间隔着一层。比如GPU利用率很高时延迟确实可能升高但GPU利用率只有30%时延迟也可能飙升——因为瓶颈可能在CPU侧的预处理队列也可能在显存带宽争抢甚至在同一张卡上跑了别的任务的kernel挤压。只看资源率解决不了“延迟构成”的问题。这套监控方案的核心思路是按延迟发生的阶段做端到端拆解。把一次完整的模型推理请求从用户发起到最后返回切分成一段一段可独立观察的时间区间每段对应一个系统组件或计算阶段。这样哪一段慢通过数据一眼就能定位不用再靠开会讨论。下面这张表是我在实际框架里常用的延迟拆解维度不同模型类型会略有调整但思路是一致的延迟阶段起始点结束点典型瓶颈网络接入客户端发出请求服务端接收完请求网关、负载均衡、DNS排队等待请求进入队列推理进程从队列取出并发度配置、堆积策略预处理取出请求完成tokenize/图像缩放等CPU算力、Python开销模型前向输入张量拷贝至设备前向算子执行完毕GPU算力、显存带宽、算子效率后处理拿到原始输出完成解码/过滤/格式化CPU逻辑、迭代次数网络回传服务端发送响应客户端收到响应网络带宽、序列化大小拆到这一粒度后延迟监控就从“黑盒”变成了“白盒”。排查问题时也不需要四个人开会对齐信息了直接看每一段的耗时占比就能锁定责任边界。这个思路不只是针对大模型小到单机部署的BERT分类模型大到分布式的LLM推理集群都适用。区别只在于某几段的分法颗粒度不同而已。1.1 为什么不能只盯接口平均耗时很多团队规划的监控方案第一步就是统计接口平均耗时、最大耗时、QPS。这个思路本身没错但放在推理场景下远远不够。有两个原因第一平均耗时在长尾分布下基本没有参考价值。推理延迟的分布通常带有明显的长尾特征大多数请求可能在300ms完成但5%的请求会落在1.5s以上。平均值被高频的“快请求”拉得很低掩盖了真实用户体验的劣化。这也是我在所有监控配置里都以P95、P99为核心指标、平均值只做参考的原因。第二同一个接口的延迟在不同输入下差异极大。普通Web接口的响应时间相对稳定但推理服务不一样。一个OCR识别接口处理10个字符的图片和处理5000个字符的图片延迟可能差出20倍一个大模型对话请求生成长度不同总耗时天然不同。如果不区分输入特征或者按batch维度拆解指标监控的价值会大打折扣。这点在后面告警规则设计里还有展开这里先埋个伏笔。1.2 推理延迟监控和常规监控的关键区别我把这两年的经验总结成一句话常规监控回答“系统有没有病”推理延迟监控回答“这一秒模型干了多少活、卡在了哪个环节”。前者偏资源层后者偏行为层。在落地上有三个关键区别需要特别留意必须感知模型状态。推理进程的特殊性在于它依赖GPU资源。同一张卡上可能同时存在训练任务或者其他的推理实例显存、算力都是共享的。监控方案必须能看到GPU级别的事件比如kernel占用、显存分配、温度、功耗否则你连模型真实计算时间都测不准确。必须关联业务上下文。至少要将模型名称、模型版本、输入长度、批大小等作为标签和指标一起上报。没有这些上下文延迟异常根本无法回溯到是“数据分布变了”还是“模型代码变了”。必须分输入特征统计。建议在监控设计初期就把“延迟直方图按模型维度的分组能力”做进去。语言类模型按“输入token数区间”分桶视觉类模型按“输入分辨率/目标数量”分桶这样告警才能避免误报。2. LLM场景下的特殊延迟指标首Token时间和Token间延迟如果你的模型是LLM或者对话式AI那延迟监控还得多一个心眼。传统单轮推理的“端到端延迟”指标在流式输出的LLM场景里会严重失真。用户感知到的“慢”不是“全部生成完花了多久”而是“第一个字什么时候出来”和“字与字之间等多久”这两个体验指标。先说首Token时间TTFTTime To First Token。它定义的是从请求到达服务端到流式响应中第一个token生成的耗时。在现在的LLM推理架构里TTFT主要由三部分构成prefill阶段的计算时间、排队等待显存/调度器的时间、网络链路的首包耗时。prefill阶段要并行处理所有输入的注意力计算对算力需求极高所以TTFT直接反映模型服务的初始响应能力。以一个7B模型为例如果输入prompt有1000个token在普通单卡A10上prefill阶段通常需要几百毫秒这个数字会成为用户感知“反应快不快”的第一来源。再来看Token间延迟TBTTime Between Tokens也叫ITL。它衡量的是生成阶段相邻两个token输出之间的时间间隔。这一步对应的是decode阶段每生成一个token都要跑一次完整的前向计算。TBT越长用户感受到的“一个字蹦一个字”的卡顿感越明显。实际体感上TBT小于50ms时几乎流畅超过150ms就明显觉得“输出有点费劲”。我见过不少团队的监控方案里压根没有这两个指标只有平均生成时长。于是出现一种奇怪现象监控显示总延迟正常但用户一直在投诉“回复不出来”。原因就是总延迟被少数的快速短回复平均掉了而长篇幅回复的生成过程密密麻麻铺满了卡顿。把TTFT和TBT拆出来才能量化用户真实体验。2.1 流式与非流式场景的指标取舍是不是所有LLM场景都需要同时统计TTFT和TBT也不是取决于接口的输出方式非流式接口一次性返回完整结果可以继续用端到端延迟但建议内部拆成“prefill耗时”和“decode总耗时”两个子指标。这样至少能判断慢是由于输入计算还是输出长度造成的。流式接口核心指标变为TTFT和TBT。端到端延迟反而意义不大因为用户其实边看边等体验锚点完全不同。混合模式常见于同一个推理服务同时支撑聊天补全和离线批处理。建议打不同的标签区分两类请求各自维护一套延迟指标不要让它们混在一个直方图里统计。有一个实操细节值得记录埋点位置很关键特别是流式场景里“首个token生成完毕”这个时机一定是在stream的第一次yield处记录而不是在整个response对象构造完成时记录。很多人因为把TTFT记在了请求入口和出口的时差上结果把队列等待网络耗时也算进了TTFT导致指标看着高但排查不出问题。2.2 输出长度归一化更公平的横向对比跨模型、跨版本对比延迟时我强烈建议引入“归一化延迟”指标否则一定会被数据骗。最简单的方案是记录每个请求的输入token数、输出token数和各自的耗时然后计算prefill吞吐 输入token数 / prefill耗时单位 tokens/sdecode吞吐 输出token数 / decode耗时单位 tokens/s这两个指标非常有用。假设一次A/B测试中版本B的端到端延迟比版本A高了30%但版本B平均生成了2倍长的回复那它decode吞吐反而可能更高说明模型本身效率并没有下降只是生成了更多内容。不归一化就直接对比延迟得出“版本B变慢”的结论是典型的误判场景。在监控平台上我通常会把直方图的标签组合做成这样model_name、model_version、input_range按token区间分桶、output_range、batch_size。查询粒度足够细做回归测试时才能快速定位“到底是哪类输入的延迟劣化了”。3. 监控搭建实战从OpenTelemetry到Prometheus的完整链路方案落地我不建议从头发明轮子。目前最适合大多数团队的组合是OpenTelemetry做链路埋点和指标收集Prometheus做指标存储和告警计算Grafana做可视化大盘。这套链路的好处是解耦清晰、生态成熟、后面换组件也方便而且全部开源没有License成本。3.1 埋点设计侵入性低别把业务代码搞乱先说你最关心的埋点。我要强调一个原则埋点是观察逻辑不要和业务逻辑混在一起。如果你的每个推理函数里塞满了计时器和指标上报的样板代码那监控还没搭完业务代码已经看不下去了。我习惯的做法是写一个装饰器或者中间件把计时的逻辑统一收口业务函数只保留正常的推理代码。以Python服务为例一个通用装饰器长这样import time import functools from opentelemetry import metrics meter metrics.get_meter(inference.monitor) histogram meter.create_histogram( nameinference.request.duration, descriptionEnd-to-end inference latency, units, boundaries[0.01, 0.05, 0.1, 0.25, 0.5, 1.0, 2.0, 4.0, 8.0], ) def observe_inference(model_name, model_version, stagee2e): def decorator(func): functools.wraps(func) async def wrapper(*args, **kwargs): start time.perf_counter() try: return await func(*args, **kwargs) finally: duration time.perf_counter() - start labels {model_name: model_name, model_version: model_version, stage: stage} histogram.record(duration, attributeslabels) return wrapper return decorator observe_inference(model_namechat-7b, model_versionv2) async def chat_completion(prompt): # 实际的推理逻辑 ...注意boundaries参数它定义了直方图的桶边界直接决定P95、P99计算的准确性。对于常规推理接口延迟从10ms到8s的指数分布基本能覆盖绝大多数场景。如果你们服务的延迟量级明显不同比如全部在微秒级那边界就要相应调小。对于TTFT和TBT这两个LLM特有的阶段建议拆成独立的histogram不要塞进通用的inference.request.duration。因为它们的语义不同混合统计会导致分位数计算完全失真。命名上我一般用inference.ttft.seconds和inference.tbt.seconds在Grafana里单独建面板展示。3.2 中间件思路拦截FastAPI应用的每个推理请求如果你用的是FastAPI这类异步框架我更推荐写HTTP中间件来做链路埋点这样连改动业务函数都省了。一个典型的中间件流程是请求进来时记录开始时间调用时从Header提取request_id和model_name响应返回时计算耗时并把状态码、模型名、输入长度等一并记入指标。这里有一个关键点中间件方案适合端到端延迟的观察但它看不到模型内部各阶段的耗时。如果你要拆解TTFT、prefill耗时这些内部指标还是得在推理引擎内部埋点。成熟的方案是在推理框架如vLLM、Triton中开启它们内置的metrics导出接口然后让Prometheus直接抓取。以vLLM为例它原生暴露/metrics端点里面有vllm:time_to_first_token_seconds和vllm:time_per_output_token_seconds这两个指标直接对接Prometheus就能拿到不需要自己写埋点代码。Triton Inference Server则提供了nv_inference_request_duration_us等指标覆盖了request的完整生命周期。一个常见的误区是开了框架原生metrics后就不再自己做分层埋点。其实两者互相补充——框架metrics解决“模型引擎内部怎么看”自主埋点解决“如何跟业务语义对齐”比如按照我们自己的model_version标签或input_range标签去切片统计。建议都做但注意标签命名保持统一避免两套数据在Grafana里没法关联。3.3 指标存储和查询配置Prometheus的关键参数Prometheus抓取推理指标时有一点和普通Web服务不太一样推理延迟的直方图数据量会比普通接口大得多因为维度多model_name、version、stage、input_range等而且直方图的bucket数量直接放大sample数量。我见过有人把10个bucket、5个标签的直方图以1秒间隔抓取结果一个月后单机Prometheus存储直接爆炸。所以有两个配置项要提前想清楚scrape_interval推理监控的指标不建议低于15s。实时性要求没那么苛刻P99连续多个周期异常才告警的话15s和5s在体感上没什么区别但数据量差了3倍。retention原始高基数指标保留7天就够了更长的历史需求通过recording rule预先降采样后再保留。比如把每小时的P95、P99算好单独存成一个低基数指标保留90天做趋势分析。查询时最常用的PromQL就两条。第一条计算端到端P95延迟按模型维度分组histogram_quantile(0.95, sum by (le, model_name, model_version) ( rate(inference_request_duration_seconds_bucket[5m]) ) )第二条计算流式场景下的TTFT P99histogram_quantile(0.99, sum by (le, model_name) ( rate(inference_ttft_seconds_bucket[5m]) ) )这里的sum by (le, ...)太重要了千万不能省。因为直方图数据在采集端就会带上全部标签不按le加维度汇总的话histogram_quantile会分别计算每个标签组合的分位数结果自然是错的表现在图上就是一条水平线或者跳变的锯齿线。3.4 告警规则设计不要让告警变成狼来了告警规则是整个监控方案里最考验经验的部分。推理服务的延迟天然会有波动输入长度的变化、并发请求量的波动都会导致延迟抬升。如果阈值设得太死一天到晚都在误报团队很快就对告警免疫了设得太松又起不到提前发现问题的作用。我目前在用的规则分三档告警级别触发条件持续周期响应预期Warning端到端P95延迟 基线×1.510分钟以上排查趋势不一定要立即处理Critical端到端P99延迟 基线×35分钟以上立即介入可能存在资源瓶颈PageTTFT或TBT连续5分钟超过阈值5分钟以上用户可感知的服务劣化这里有个很关键的细节基线不是拍脑袋定的是通过历史数据算出来的动态基线。比如用过去7天同一时段、同一模型的P95数据作为基准线。直接写死“P952s”这种静态阈值一旦输入分布变了、模型版本换了阈值立刻失效。Prometheus里可以用record来定期计算基线groups: - name: inference_baseline.rules interval: 30m rules: - record: inference:request_duration:p95:baseline7d expr: | histogram_quantile(0.95, sum by (le, model_name) ( rate(inference_request_duration_seconds_bucket[7d] offset 7d) ) )查询当前偏差时直接让当前P95除以这条基线比值超过1.5或者3.0再触发告警既灵敏又不用频繁调参。另外还有一个建议告警务必带上模型名称和版本标签这样钉钉或企业微信机器人推送时就能看到“chat-7b v2的P95延迟比上周高了2.2倍”排查的人一上来就有方向。4. 一次线上推理延迟突刺的完整排查链路光有监控方案还不够关键要看遇到问题时怎么用它排查。这里回顾一次发生在我们环境里的真实故障不是少数派的特例而是非常有代表性的“显存碎片化”问题。某个周三下午4点半告警突然弹出OCR服务的P99延迟从稳定的300ms跳到1.2s持续上升。第一反应是流量问题但看一眼QPS曲线和平时段落完全相同。打开Grafana按前面说的延迟拆解面板逐段看数据发现网络接入、预处理、后处理每段时间都是平的唯独“模型前向”这一段从80ms涨到了900ms。这就锁定了问题在GPU计算侧业务代码和网络链路先排除。接着看框架metrics面板GPU利用率竟然只有35%远没有打满显存占用倒是接近上限。这里就有意思了——按常理利用率不高时延迟不应该这么高。继续翻显存指标发现空闲显存显示还有6GB但模型申请一块连续的2GB buffer时偶尔会失败要等释放才能勉强分配。这明显是显存碎片化的症状总空闲量够但连续的大块显存不足。顺着这个方向检查发现当天上午有其他团队在同一台物理机上调度过一个临时的训练任务。训练结束后显存被释放但分配过的块没有完全归还底层驱动导致CUDA的显存分配器里碎片越来越多。推理进程的缓存池在碎片化的显存里来回找地址前向计算的时间就莫名其妙地被拖长了。排查到这里锁定根因同卡混布导致的显存碎片污染。处理方案分两步走先立即把推理任务迁到另一台干净的GPU节点P99延迟马上回落到320ms再在长期方案里规定推理卡和训练卡分离部署禁止跨团队混用GPU资源。整个排查过程从告警到根因确认只花了不到20分钟全程没有开会就靠监控面板和指标维度一根链条拉到底。事后我复盘时有个很深的感受如果没有分阶段延迟监控这个问题找起来会极其痛苦——CPU、内存、带宽都没瓶颈GPU利用率也不高传统监控方案里根本没有任何一个指标能直接指向“显存碎片”。而有了“模型前向”这个阶段的耗时指标一切就显得顺理成章。4.1 排查时高价值的几块面板经历了这次故障我调整了Grafana的dashboard布局现在常用的核心面板有这么几块分段延迟堆叠面积图按模型名区分把预处理、排队、前向、后处理、网络各段时间堆叠起来。一眼看出哪个阶段是当前主要耗时方。TTFT/TBT双轴趋势图一条线表示首Token时间的变化另一条表示Token间延迟。判断LLM服务是“响应慢”还是“生成慢”。GPU多维面板把利用率、显存使用、温度、功耗放在一起并叠加显存碎片事件标记。发现利用率低但延迟高时优先怀疑资源争抢或碎片问题。输入/输出长度分布热力图观察线上请求的输入特征是否有突变。这在“找不到任何资源瓶颈但延迟却升高了”的场景下特别管用因为大概率是输入数据分布变了。核心原则就一条排查路径要能顺着面板逐层下钻。从总览看到异常点击某个模型名称能跳到该模型的分段延迟再点击某个阶段能关联到对应的GPU或者日志。点击深挖的次数越少排查效率越高。4.2 定位根因时的常见误判别被表面的高延迟带偏排查过程中最容易犯的错就是看到延迟高就直接怀疑“模型计算量大”。实际上推理延迟突刺的原因远不止这一个。按我的经验大致可以分为这么几类根因方向典型表现怎么确认资源争抢GPU利用率高但同卡有其他任务的kernel穿插查看真机的GPU进程列表、时间线分析工具显存碎片利用率不高但连续显存不足监控空闲显存与可分配大块显存的差值输入特征漂移平均输入长度徒增模型计算量变大输入长度分布热力图队列堆积请求在排队阶段耗时明显上涨观察并发数和每个请求的queue time冷启动/缓存击穿周期性出现首次请求高延迟后回落观察延迟曲线是否有明显周期后处理反压输出结果过大序列化传输耗时猛增看网络回传阶段耗时占比每种情况在分段延迟面板上都有独特的“指纹”。比如队列堆积时排队耗时和GPU利用率几乎同步上涨显存碎片时前向耗时上涨但利用率不动输入漂移时预处理和模型前向的涨幅与输入长度增长正相关。平时多积累这类对应关系排查时效率会高很多。建议大家把每次故障的截图、指标特征、根因结论沉淀成一个内部wiki半年下来就是团队排查的经验库。5. 监控方案落地后我还会盯着这几件事方案跑通只是第一步真正让监控持续产生价值的是几个容易被忽略的运维细节。我在这里专门整理一下都是实际使用中反复踩过的坑和积累的习惯。5.1 采样与全量之间的平衡很多人问过我要不要全量采样。实话说推理延迟监控不需要全量统计每个请求。对于高QPS的OCR、向量化这类服务全量采集直方图数据成本很高但对结果的影响微乎其微。我可以接受只做1%或5%的采样只要采样是均匀随机且标签维度完整P95算出来和全量统计的误差通常不超过5%。真正需要全量的是“错误监控”和“请求轨迹追踪”那些涉及钱的异常不能漏。具体做法上我会在网关层对request_id做hash取模命中采样范围的就注入一个x-otel-sample: true的Header全链路所有埋点组件都识别这个标记再决定是否上报细粒度指标。这样采样决策是全局一致的不会出现前端记了后端没记的情况。5.2 标签基数的控制这是Prometheus用户最容易踩的坑但放到本文最后讲是因为它往往在监控上线一段时间后才爆发。推理监控天然高基数模型名、版本、输入范围、输出范围、请求来源、batch_size每个标签多几个取值组合起来就是爆炸式增长。当标签组合数超过十万Prometheus的查询响应时间和存储占用都会呈指数恶化。控制手段有几条只给真正需要分开查询的维度加标签。能通过日志回溯的上下文就不要放进指标标签里。把连续值变成离散桶。输入token数不要直接作为标签值上报而是先映射成input_0_100、input_100_500这样的桶一个来减少基数一个也让后续的查询语义更清晰。边界值可以按业务特征来定比如短对话、长文档、超长上下文。不用的旧版本标签及时下线。模型迭代后不要让历史版本还在继续上报指标既省存储也避免干扰当前版本的基线计算。5.3 监控与容量规划的结合最后说一个监控指标带来的额外收益。延迟监控跑通后我们意外收获了一份容量规划的基准数据——因为各个阶段的耗时指标本质上就是服务处理能力的标尺。举个例子模型A在batch_size从1涨到8时前向耗时只增加了40%说明算力还有余量模型B在batch_size从1涨到4时前向耗时已经翻了2.5倍说明这个模型对batch非常敏感扩并发时要特别小心。这些结论在平时很难靠感觉判断但监控数据能直接给出答案。我现在已经习惯每个季度拉一次历史延迟数据结合QPS走势给每个模型重新计算一次“建议最大并发数”和“建议batch_size上限”。这个动作在模型版本升级后尤其重要新版本的算力特性往往和旧版本差异很大不通过监控数据校准直接沿用旧的资源配置很容易导致线上延迟劣化或者资源浪费。如果你正准备给推理服务搭监控我的建议是从最小闭环开始先只接框架自带metrics加一个端到端延迟中间件画一个分段延迟面板再配一条P95告警。这一套一天就能跑通。跑通后你会很快体会到延迟监控不再是“出了问题才翻的数据”而是日常做模型调优、容量规划、资源预算时离不开的尺子。
返回列表