ARTICLE DETAIL

资讯详情

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

大模型推理可观测性实战:Token消耗与延迟追踪

大模型推理可观测性实战:Token消耗与延迟追踪 大模型推理服务的成本账单十有八九是从感觉最近有点慢和这个月 API 账单怎么又超了这两句话开始失控的。我见过太多团队模型部署上线跑得挺欢一问单次推理平均消耗多少 Token、P99 延迟是多少、哪个环节是瓶颈全员沉默。问题不在于大家不想管而在于大模型的推理链路天然是个黑盒——输入一段 prompt输出一段文本中间发生了什么、花了多少资源默认状态下你什么都看不到。这篇内容就是聊怎么把这个黑盒拆开把每一次推理的 Token 消耗和延迟都追踪清楚让可观测性真正落到大模型服务上。不管你是刚把模型跑起来想加点监控的新手还是已经在生产环境被成本和性能两头夹击的老手这里面的思路和实操细节都能直接拿去用。1. 为什么大模型的可观测性和传统服务完全不是一回事1.1 传统 APM 在大模型场景下的三个盲区做过微服务监控的人第一反应通常是接个 APM 不就完了请求量、响应时间、错误率老三样。但把这套直接套到大模型推理上你会发现它几乎什么都测不准。第一个盲区是计量单位对不上。传统服务的一次请求是个相对固定的成本单位一次数据库查询和一次缓存读取资源消耗量级差不多。但大模型的一次推理请求输入 50 个 Token 和输入 8000 个 Token成本可能差上百倍。APM 只告诉你这个接口被调用了 1000 次但完全不知道这 1000 次里消耗了多少算力。第二个盲区是延迟的构成被掩盖了。传统接口的响应时间基本就是处理时间但大模型推理的延迟要拆成好几段请求排队等待、prefill 阶段处理输入 prompt、decode 阶段逐个生成输出 Token、以及网络传输。一个 3 秒的响应可能是 prefill 花了 0.5 秒、decode 花了 2.4 秒、排队 0.1 秒也可能是排队就占了 2 秒。这两种情况的优化方向完全不同但 APM 给你的就是一个笼统的 3 秒。第三个盲区是流式输出让响应时间失去意义。大模型服务普遍用流式返回streaming第一个 Token 到达的时间和最后一个 Token 到达的时间可能差好几秒。用户真正感知的快慢其实是首 Token 延迟TTFTTime To First Token而传统 APM 记录的是整个请求的完成时间这个数字对流式场景几乎没有参考价值。我踩过的坑早期给一个推理服务接监控盯着平均响应时间 2.8 秒觉得还行结果用户投诉卡得要死。后来才发现平均 2.8 秒里有一半请求是首 Token 就要等 2 秒以上用户盯着空白屏幕等两秒体感就是卡。平均值骗死人。1.2 Token 才是大模型服务的计费原子理解大模型可观测性核心要转变一个观念Token 是比请求更基础的计量单位。一次推理请求的算力消耗大致正比于输入 Token 数加上输出 Token 数。输入部分prefill可以并行处理算力消耗相对可控输出部分decode是逐个 Token 串行生成的每个 Token 都要完整跑一遍前向计算所以输出 Token 的边际成本远高于输入 Token。这也是为什么各家 API 的定价里输出 Token 通常比输入 Token 贵好几倍。这意味着如果你只统计请求数你根本不知道成本花在哪。同样 1000 次请求A 场景是短问答平均输入 100 Token、输出 50 TokenB 场景是长文档摘要平均输入 6000 Token、输出 800 Token后者的成本可能是前者的几十倍。没有 Token 级别的统计成本优化就是盲人摸象。1.3 可观测性要回答的四个核心问题把需求收敛一下大模型推理的可观测性本质上要能回答四个问题花了多少每次推理消耗的输入 Token、输出 Token 分别是多少累计成本是多少快不快首 Token 延迟TTFT、每 Token 生成时间TPOT、端到端总延迟分别是多少稳不稳延迟的分布是什么样的P50、P95、P99 各是多少有没有长尾哪里慢延迟到底花在排队、prefill 还是 decode 上瓶颈在哪个环节这四个问题回答清楚了成本控制和性能优化才有抓手。下面几节就围绕怎么把这四个问题落地展开。2. 推理链路上到底有哪些可观测的埋点位置2.1 从请求进入到响应返回的完整时间线要埋点先得把推理请求的完整生命周期画出来。一个典型的大模型推理请求时间线大致是这样的请求到达网关记录请求进入时间戳 T0排队等待请求进入推理引擎的调度队列等待被分配计算资源记录开始处理时间戳 T1Prefill 阶段引擎处理输入 prompt计算 KV Cache记录 prefill 完成时间戳 T2Decode 阶段逐个生成输出 Token每个 Token 生成时记录时间戳直到生成结束时间戳 T3响应返回结果通过网络返回给调用方记录 T4有了这条时间线各个关键指标就能算出来了指标计算方式含义排队延迟T1 - T0请求在队列里等了多久Prefill 延迟T2 - T1处理输入 prompt 的耗时首 Token 延迟 TTFT第一个输出 Token 时间 - T0用户感知的开始响应时间每 Token 时间 TPOT(T3 - 首Token时间) / 输出Token数生成速度端到端延迟T4 - T0完整请求耗时这张表是整个可观测性体系的地基。很多团队只测了端到端延迟结果优化时完全不知道从哪下手就是因为缺了中间这几个分段指标。2.2 网关层、引擎层、应用层各自能拿到什么不同层级能采集到的信息是不一样的得分工明确。网关层能拿到的是最外层的视角请求的完整耗时、调用方标识、请求的输入输出内容如果允许记录、HTTP 状态码。这一层适合做全链路追踪的入口给每个请求打一个 trace_id贯穿整个链路。网关层拿不到引擎内部的细节但它是唯一能同时看到请求进来和响应出去的地方。推理引擎层是信息最丰富的地方。以 vLLM 这类主流推理引擎为例它内部有调度器、有 KV Cache 管理、有 prefill 和 decode 的分离能拿到排队时间、prefill 耗时、每个 Token 的生成时间、显存占用、批处理batch的大小等。这一层的数据最接近真相但需要引擎本身暴露这些指标或者通过它的 metrics 接口采集。应用层是业务逻辑所在的地方能拿到的是业务语义这次推理属于哪个业务场景、对应哪个用户、是哪个功能触发的。应用层的数据要和引擎层的数据通过 trace_id 关联起来才能回答哪个业务场景最费 Token这种问题。实操建议trace_id 的生成放在网关层通过请求头一路透传到引擎层和应用层。别小看这个 ID没有它三层的数据就是三座孤岛关联不起来。2.3 流式场景下埋点的特殊处理流式输出给埋点带来的最大麻烦是响应不是一个点而是一个过程。你不能等请求结束了才记录那样首 Token 延迟就丢了。正确的做法是在流式返回的每个 chunk 上打时间戳。具体来说在应用层接收流式响应时对第一个 chunk 记录 TTFT对每个后续 chunk 记录到达时间最后算出 TPOT。这里有个细节网络传输的抖动会影响 chunk 到达时间的准确性如果追求精确最好在引擎层直接记录 Token 生成时间应用层记录的作为参考。另一个坑是流式响应的中断。用户可能中途取消请求或者网络断了。这种情况下已经生成的 Token 是算成本的引擎已经消耗了算力但请求没有正常结束。埋点时要能识别这种部分完成的状态否则成本统计会漏掉这部分。3. Token 消耗统计从粗放到精确的三种做法3.1 用引擎自带的分词器做精确计数最准确的做法是用推理引擎实际使用的分词器tokenizer来数 Token。因为 Token 的切分方式直接决定了计数结果用错分词器数字就对不上。以 HuggingFace 的 transformers 库为例加载模型对应的 tokenizer 后可以这样计数from transformers import AutoTokenizer tokenizer AutoTokenizer.from_pretrained(your-model-path) def count_tokens(text): # 输入 Token 计数 input_ids tokenizer.encode(text, add_special_tokensTrue) return len(input_ids) # 输出 Token 计数同理对生成的文本做 encode这里有个容易忽略的点add_special_tokens参数。有些模型会在输入前后加特殊标记如 BOS、EOS这些标记也算 Token算成本时要包含进去。不同模型的默认行为不一样得对着模型文档确认。对于输出 Token更准确的做法是直接拿引擎返回的生成 Token 数而不是对输出文本重新 encode。因为 decode 过程中可能有特殊处理比如跳过某些 Token重新 encode 的结果可能和实际生成的不一致。3.2 用 tiktoken 类工具做快速估算如果不想加载完整的分词器比如在网关层做轻量统计可以用 tiktoken 这类独立的计数工具。它的优点是轻量、快不需要加载模型。import tiktoken # 注意不同模型用的编码方式不同要选对 enc tiktoken.get_encoding(cl100k_base) def estimate_tokens(text): return len(enc.encode(text))但这里必须强调tiktoken 的计数是估算不是精确值。它用的是 OpenAI 系列的编码方式如果你跑的是 Llama、Qwen 这些模型分词方式不一样计数会有偏差。偏差有多大英文文本通常在 5% 以内中文文本可能到 10% 甚至更多因为中文的 Token 切分差异更大。所以 tiktoken 适合做实时监控和趋势观察不适合做精确的计费依据。计费还是得用引擎的真实计数。3.3 输入输出 Token 分开统计的必要性前面提过输入和输出 Token 的成本不一样所以统计时必须分开。很多监控系统图省事只记一个总 Token 数这在成本分析时基本没用。分开统计后你能看出很多有价值的信息。比如某个业务场景输入 Token 特别大说明 prompt 里塞了太多上下文可以考虑做 prompt 压缩或者检索增强来减少输入某个场景输出 Token 特别大说明模型话太多可以考虑在 prompt 里加约束或者调整生成参数如 max_tokens。统计维度用途优化方向输入 Token 总量评估 prompt 成本prompt 压缩、上下文裁剪、缓存复用输出 Token 总量评估生成成本约束输出长度、调整生成参数输入/输出比值判断场景类型比值高说明是理解类任务比值低说明是生成类任务一个反直觉的发现我们有个客服问答场景一开始以为成本大头在输出毕竟要生成回答结果统计下来输入 Token 是输出的 8 倍。原因是每次请求都把整个知识库片段塞进 prompt。后来改成先检索再拼接输入 Token 直接降了 70%。4. 延迟拆解把慢这个模糊感受变成具体数字4.1 首 Token 延迟为什么比总延迟更重要在流式场景下用户对快慢的感知几乎完全取决于首 Token 延迟TTFT。道理很简单只要第一个字出来了用户就知道系统在工作后面的字一个个蹦出来即使慢一点体感也是在正常输出。但如果第一个字迟迟不出来用户面对的就是一片空白超过两三秒就会开始怀疑是不是卡了。这就导致一个很实际的现象一个 TTFT 是 0.5 秒、总延迟 8 秒的请求用户体感可能比一个 TTFT 是 3 秒、总延迟 4 秒的请求要快。因为前者很快就给了反馈后者让人干等了 3 秒。所以监控指标里TTFT 的优先级要高于总延迟。优化时也要优先优化 TTFT哪怕牺牲一点总延迟也值得。降低 TTFT 的手段包括减少输入 prompt 长度prefill 更快、优化调度策略减少排队、使用更快的硬件或量化模型。4.2 Prefill 和 Decode 的耗时占比分析把延迟拆成 prefill 和 decode 两段后你会发现不同场景的瓶颈完全不同。Prefill 主导的场景输入很长、输出很短。比如文档分类、长文本问答。这类场景的耗时几乎全在 prefill 上因为要处理几千个输入 Token。优化方向是减少输入长度、用更高效的 attention 实现、或者对输入做缓存相同前缀的 prompt 可以复用 KV Cache。Decode 主导的场景输入短、输出长。比如内容生成、代码补全。这类场景 prefill 很快但 decode 要一个个 Token 生成输出越长越慢。优化方向是提高 decode 速度比如用投机采样speculative decoding、调整批处理策略。两者都重的场景长输入长输出比如长文档摘要。这种最难优化两头都要抓。实测中我建议把 prefill 耗时和 decode 耗时分别打点做成两个独立的指标。这样一眼就能看出当前负载的瓶颈在哪。如果 prefill 耗时占比超过 60%说明输入处理是瓶颈如果 decode 占比超过 70%说明生成速度是瓶颈。4.3 排队延迟最容易被忽视的隐形杀手排队延迟是最容易被忽视的因为它不在推理本身里而是发生在请求等待被处理的过程中。但在高并发场景下排队延迟往往是最大的那块。想象一下推理引擎的并发处理能力是有限的受显存和算力限制当同时到达的请求超过这个能力多出来的请求就得排队。如果引擎每秒能处理 10 个请求突然来了 50 个那就有 40 个要排队排在最后的可能要等好几秒。排队延迟的监控要点队列长度当前有多少请求在等待这是最直接的信号排队时间分布不能只看平均要看 P95、P99因为长尾排队对用户体验伤害最大排队与并发的关联把排队时间和当前并发数放一起看能看出引擎的容量拐点在哪踩坑记录有次线上突然大量超时查了半天推理本身没问题最后发现是上游一个批量任务瞬间打了几百个请求进来全堵在队列里。如果早点监控队列长度设个告警这事根本不会发生。4.4 用直方图而不是平均值来看延迟分布这一条是血泪教训延迟监控绝对不能用平均值。延迟的分布是典型的长尾分布大部分请求很快少数请求很慢。平均值会被大量快请求拉低掩盖掉长尾问题。比如 100 个请求95 个是 1 秒5 个是 10 秒平均值是 1.45 秒看起来挺好但那 5 个 10 秒的请求对应的用户已经在骂人了。正确的做法是用直方图histogram记录延迟分布然后看分位数P50中位数一半请求比它快代表典型体验P9595% 的请求比它快代表大多数用户的体验上限P9999% 的请求比它快代表最差的那批用户的体验在大模型场景下P99 往往比 P50 高好几倍。如果 P50 是 1 秒、P99 是 8 秒说明有 1% 的请求体验极差这批请求值得单独排查。Prometheus 的 histogram 类型就是干这个的配置好分桶bucket后可以直接算出各个分位数。分桶的设置要根据实际延迟范围来定比如 TTFT 的分桶可以设成 0.1、0.25、0.5、1、2、5 秒。5. 把指标串起来一套可落地的监控方案5.1 指标采集Prometheus 客户端埋点实操落地监控Prometheus 是目前最主流的选择。核心是在代码里埋点暴露 metrics 接口让 Prometheus 定期抓取。先定义好要采集的指标。大模型推理场景我建议至少定义这几个from prometheus_client import Counter, Histogram, Gauge # 计数器累计 Token 消耗 input_tokens_total Counter( llm_input_tokens_total, Total input tokens consumed, [model, scene] # 按模型和业务场景区分 ) output_tokens_total Counter( llm_output_tokens_total, Total output tokens generated, [model, scene] ) # 直方图延迟分布 ttft_seconds Histogram( llm_ttft_seconds, Time to first token, [model, scene], buckets[0.1, 0.25, 0.5, 1.0, 2.0, 5.0, 10.0] ) e2e_latency_seconds Histogram( llm_e2e_latency_seconds, End to end latency, [model, scene], buckets[0.5, 1.0, 2.0, 5.0, 10.0, 30.0, 60.0] ) # 仪表当前状态 queue_length Gauge( llm_queue_length, Current queue length, [model] )埋点位置很关键。Token 计数在推理完成后记录TTFT 在第一个 Token 返回时记录端到端延迟在请求完全结束时记录。标签label的设计要克制别加太多维度否则指标基数爆炸Prometheus 扛不住。model和scene这两个维度通常够用了。5.2 用 Grafana 搭一个能一眼看懂的面板指标采集上来后得有个地方看。Grafana 面板的设计原则是让异常一眼可见。我习惯把面板分成三块第一块是成本概览今天累计消耗的输入/输出 Token、按场景拆分的 Token 消耗排行、预估成本。这块用数字面板stat和柱状图让成本一目了然。第二块是性能概览TTFT 的 P50/P95/P99 曲线、端到端延迟的分位数、当前队列长度。这块用时间序列图看趋势。第三块是异常排查延迟的直方图分布、按模型拆分的延迟对比、错误率。这块用于出问题时快速定位。面板上要设好阈值线比如 TTFT 的 P95 超过 2 秒就变黄超过 5 秒就变红。这样值班的人扫一眼就知道有没有问题。5.3 告警规则哪些指标越界必须立刻知道告警不能太多多了就是狼来了最后没人看。大模型推理场景我建议只对这几类设告警告警项触发条件严重程度TTFT P95 过高5 分钟内 P95 3 秒高队列积压队列长度持续 50高错误率上升5 分钟内错误率 5%高Token 消耗异常小时级消耗环比增长 200%中单请求 Token 超限单次输入 模型上限的 90%低Token 消耗异常这条特别有用能及时发现是不是有程序在疯狂调用或者是不是 prompt 被改坏了导致输入暴涨。我们有一次就是靠这条告警发现一个测试脚本忘了关半夜跑了几十万次请求。5.4 采样与存储别让监控本身成为负担大模型推理的请求量可能很大如果每个请求都完整记录存储成本会很高。这时候要做采样。采样策略有两种头部采样和尾部采样。头部采样是按比例随机采比如 10% 的请求记录完整详情。尾部采样是先收集所有请求的概要等请求结束后根据结果决定是否记录详情——比如只记录慢请求和错误请求的完整信息。对于大模型场景我推荐尾部采样因为慢请求和错误请求才是排查问题的关键快请求记录那么多没意义。具体做法是所有请求都记录基础指标Token 数、延迟但只有满足条件延迟超过阈值、发生错误的请求才记录完整的 prompt 和输出内容。存储方面指标数据时序数据放 Prometheus日志数据请求详情放 Loki 或 Elasticsearch追踪数据trace放 Jaeger 或 Tempo。三者通过 trace_id 关联形成完整的可观测性体系。6. 几个真实场景下的排查与优化案例6.1 案例一Token 消耗突然翻倍问题出在 prompt 拼接有段时间成本监控告警Token 消耗环比涨了一倍多但请求量没变。查下来发现是某个业务场景的输入 Token 暴涨。顺着 trace_id 找到具体请求对比历史记录发现 prompt 里多了一大段内容。再查代码是上游一个检索模块改了逻辑原本返回 3 个文档片段改成了返回 10 个而且没做长度限制。结果每次请求的输入从平均 800 Token 涨到了 2500 Token。修复很简单给检索结果加长度上限超出的截断。但如果没有 Token 级别的监控这个问题可能要等到月底看账单才发现那时候钱已经花出去了。这个案例的教训是Token 监控要能下钻到具体请求。光有总量指标不够出问题时得能定位到是哪个请求、哪个场景、哪段内容导致的。6.2 案例二P99 延迟飙升根因是批处理策略另一个案例是延迟问题。TTFT 的 P50 一直很稳在 0.4 秒左右但 P99 突然从 2 秒涨到了 8 秒。平均值几乎没变所以一开始没人注意直到有用户投诉。排查过程先看队列长度正常再看 prefill 耗时正常最后看 decode发现长输出的请求 decode 特别慢。深入查引擎配置发现批处理策略是来者不拒一个长输出请求和一堆短请求混在一个 batch 里短请求早就生成完了但整个 batch 要等长请求生成完才能释放导致短请求被拖累。修复方案是引入更细粒度的调度把长输出和短输出的请求分开批处理。改完后 P99 从 8 秒降到了 2.5 秒。这个案例说明P99 的异常往往指向调度和资源分配问题而不是单个请求本身的问题。只看平均值永远发现不了。6.3 案例三用缓存把重复 prompt 的 prefill 成本降下来第三个案例是优化。我们发现有个场景大量请求的 prompt 前缀是相同的比如都带同一段系统提示词但每次都要重新做 prefill浪费算力。解决方案是用前缀缓存prefix caching。原理是如果两个请求的 prompt 前缀相同那么这段前缀的 KV Cache 可以复用不用重新计算。主流推理引擎如 vLLM都支持这个特性开启后相同前缀的 prefill 耗时能大幅降低。开启前缀缓存后这个场景的 TTFT 从平均 1.2 秒降到了 0.3 秒效果立竿见影。但要注意前缀缓存会占用显存如果显存紧张要权衡缓存大小和并发能力。这里有个细节前缀缓存对 prompt 的格式敏感。如果系统提示词里带了时间戳或者随机 ID那每次前缀都不一样缓存就失效了。所以设计 prompt 时要把不变的部分放前面变化的部分放后面。7. 落地这套方案时最容易踩的几个坑7.1 指标基数爆炸标签不是越多越好Prometheus 的指标基数cardinality是个隐形炸弹。每个不同的标签组合都会生成一条独立的时间序列标签维度一多序列数量指数级增长Prometheus 内存直接爆掉。大模型场景最容易犯的错是把user_id、request_id这种高基数标签加到指标上。一个请求一个 ID几百万请求就是几百万条序列必炸。正确做法是指标只加低基数标签如 model、scene、status高基数的信息放到日志或 trace 里通过 trace_id 关联。指标负责看趋势日志负责查细节各司其职。7.2 埋点位置不对在错误的地方测延迟埋点位置错了测出来的数字就是错的。常见的错误有在应用层测 TTFT应用层收到第一个 chunk 的时间包含了网络传输时间比引擎实际生成第一个 Token 的时间要晚。如果网络抖动大这个数字会很不准。在网关层测 prefill 耗时网关层根本看不到 prefill 和 decode 的分界测不出来。在异步回调里测端到端延迟如果用了异步框架回调执行的时间可能和请求实际完成的时间有偏差。原则是谁产生的数据就在谁那里测。引擎内部的数据在引擎层测网络的数据在网关层测业务的数据在应用层测。7.3 流式响应的 Token 计数别在最后才数流式响应下如果等所有 Token 生成完再计数会有两个问题一是实时性差监控面板要等请求结束才更新二是如果请求中断计数就丢了。更好的做法是边生成边计数。每生成一个 Token计数器加一。这样即使请求中断已经生成的 Token 也被统计到了。实现上可以在流式处理的循环里累加计数请求结束时把总数记录到指标里。7.4 监控数据的存储成本该省的省该留的留监控数据不是留得越久越好。原始指标数据高精度通常只留 15 天到 1 个月之后降采样成低精度数据比如 5 分钟粒度长期保留。请求详情日志含 prompt 和输出涉及隐私和存储成本通常只留 7 天且要做脱敏。这里有个合规提醒记录 prompt 和输出内容时要注意用户隐私和数据安全。敏感信息要脱敏或者干脆不记录内容只记录 Token 数和延迟。具体留什么要根据业务场景和合规要求来定。8. 从能看见到能优化可观测性的下一步把 Token 和延迟监控起来只是第一步。真正的价值在于用这些数据驱动优化。有了 Token 数据你可以做成本归因哪个业务场景最费钱、哪个用户的调用量最大、哪类请求的性价比最低。有了延迟数据你可以做容量规划当前配置能扛多少并发、什么时候需要扩容、扩容的瓶颈在哪。更进一步可以把这些指标接入自动扩缩容系统。当队列长度持续超过阈值自动增加推理实例当负载降下来自动缩容。这样既保证性能又不浪费资源。我在实际项目里的体会是可观测性建设最忌讳一步到位、追求大而全。先埋最核心的几个指标Token 数、TTFT、端到端延迟把面板搭起来让团队养成看数据的习惯然后再逐步细化。一开始就搞几十个指标、十几个面板最后往往没人看白费功夫。最后分享一个实用的小技巧给每个业务场景设一个Token 预算比如客服问答场景每天不超过 100 万 Token。监控系统实时对比消耗和预算接近预算就告警。这样能把成本控制从事后看账单变成事中干预效果立竿见影。
返回列表