news 2026/10/5 8:36:14

大模型推理可观测性实战:Token统计、TTFT/TPOT延迟归因与成本核算

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
大模型推理可观测性实战:Token统计、TTFT/TPOT延迟归因与成本核算

1. 大模型推理可观测性到底在解决什么问题

1.1 从一次线上告警说起

去年下半年,我负责维护的一个内部推理服务突然收到告警:P99 延迟从 1.8 秒飙到 11 秒,但 GPU 利用率、显存占用、请求 QPS 三个指标全都正常。运维同学第一反应是网络抖动,查了半小时没结果。最后把单次请求的输入 Token 数和输出 Token 数打出来对比,才发现问题:某个上游业务把系统提示词从 200 Token 悄悄改到了 3000 Token,导致 prefill 阶段计算量暴涨,而 decode 阶段因为输出很短,整体 QPS 没变化,GPU 利用率自然看不出异常。

这件事让我彻底意识到一个问题:传统服务的可观测性三件套(Metrics、Logging、Tracing)直接套到大模型推理上是不够用的。CPU 密集型服务的瓶颈在指令数,而大模型推理的瓶颈在 Token——输入多少 Token、输出多少 Token、每个 Token 花多少毫秒、KV Cache 命中多少,这些才是真正决定成本和延迟的变量。

所谓大模型日志与可观测性,说白了就是给每一次推理请求建立一份"体检报告":这次请求吃了多少 Token、首 Token 延迟(TTFT)多少、每 Token 输出间隔(TPOT)多少、总耗时多少、命中了多少缓存、花了多少钱。没有这份报告,你既不知道钱花在哪,也不知道延迟卡在哪。

1.2 谁需要这套东西

这套东西不是只有大厂才需要。我梳理了一下,下面几类角色都绕不开:

  • 推理服务开发者:要定位延迟毛刺、优化吞吐,必须知道时间花在 prefill 还是 decode。
  • 平台/运维同学:要按业务线分摊 GPU 成本,必须按 Token 用量计费。
  • 算法/微调同学:要评估微调后模型是否"变啰嗦"了,必须对比微调前后的输出 Token 分布。
  • 业务方:要知道一次对话大概烧多少钱,才能决定要不要上大模型。

我见过太多团队,模型部署起来了,接口也能调通,但一问"你们一次请求平均多少 Token、P99 延迟多少",没人答得上来。这种状态下做容量规划和成本控制,基本靠拍脑袋。

1.3 核心指标先定义清楚

在动手之前,必须把指标口径统一,否则后面数据全是乱的。我按重要性排了个序:

指标英文/缩写含义为什么重要
首 Token 延迟TTFT从请求发出到第一个 Token 返回的时间直接决定用户"卡不卡"的体感
单 Token 输出间隔TPOT / ITLdecode 阶段平均每个 Token 的生成时间决定输出"流不流畅"
输入 Token 数prompt_tokens本次请求的输入长度决定 prefill 计算量和成本
输出 Token 数completion_tokens本次生成的输出长度决定 decode 总时长和成本
总 Token 数total_tokens输入+输出计费基准
端到端延迟E2E Latency整个请求的总耗时用户感知的总时间
吞吐Throughput每秒处理的 Token 数/请求数决定要几张卡
KV Cache 命中率cache hit rate前缀缓存复用比例直接影响 TTFT 和成本

提示:TTFT 和 TPOT 一定要分开统计。很多团队只统计端到端延迟,结果优化时完全不知道该动 prefill 还是 decode,这是最常见的坑。

2. 日志埋点方案怎么设计才不返工

2.1 埋点位置的选择逻辑

埋点位置决定了你能拿到什么数据。我一般分三层埋:

第一层:网关层(Gateway)。在请求进入推理服务之前记录请求 ID、用户 ID、业务线、时间戳、原始 prompt 长度。这一层的好处是能拿到"用户视角"的延迟,包括排队时间。很多团队漏掉排队时间,导致明明服务内部很快,用户却觉得很慢。

第二层:推理引擎层。这是核心。以 vLLM 为例,它本身提供了丰富的 metrics,通过/metrics端点暴露 Prometheus 格式的数据。关键指标包括vllm:time_to_first_token_seconds、vllm:time_per_output_token_seconds、vllm:num_requests_running、vllm:gpu_cache_usage_perc等。如果你用的是别的推理引擎,思路一样,找它暴露的 metrics 端点。

第三层:应用层。在业务代码里记录这次请求的业务语义,比如是哪个功能模块、用户是否点了"重新生成"、是否命中了缓存。这一层是纯业务数据,推理引擎给不了。

三层数据靠一个全局唯一的request_id串起来。这个 ID 必须在网关层生成,然后一路透传到推理引擎和业务层。

2.2 为什么不用纯日志而要用指标+日志组合

我踩过一个坑:一开始所有数据都往日志里打,结果一天几百万条日志,ES 集群直接扛不住,查询还慢。后来改成"指标走 Prometheus,明细走日志"的组合:

  • 高频、聚合类数据(QPS、延迟分位数、Token 总量)走 Prometheus,采样成本低,聚合查询快。
  • 低频、明细类数据(单次请求的完整 Token 明细、异常请求的完整上下文)走日志,用于事后排查。

这个分工的逻辑是:你不可能对每一次请求都做全量明细存储,成本扛不住;但聚合指标又无法回答"这次异常请求到底发生了什么"。所以两者必须配合。

2.3 采样策略:全量还是抽样

这里有个反直觉的结论:Token 用量和延迟的聚合指标必须全量统计,但明细日志可以抽样。

原因很简单:Token 用量是计费依据,少统计一次就少收一次钱,必须全量。而明细日志主要用于排查,抽样 10% 已经足够发现规律性问题。我的做法是:

  • 聚合指标:100% 全量上报。
  • 明细日志:正常请求采样 5%~10%,异常请求(延迟超阈值、报错)100% 全量记录。

这样既控制了存储成本,又保证了排查时不会"恰好没采到"。

3. 核心指标采集的实操细节

3.1 TTFT 和 TPOT 到底怎么算

这两个指标看着简单,实际算起来有讲究。TTFT 是"请求发出到第一个 Token 到达",但"请求发出"这个时间点从哪算?从网关收到请求算,还是从推理引擎开始计算算?

我的建议是两个都算,分别叫"用户侧 TTFT"和"引擎侧 TTFT"。两者之差就是排队和调度时间。如果用户侧 TTFT 远大于引擎侧,说明瓶颈在排队,该扩容了;如果两者接近但都很大,说明瓶颈在 prefill 计算,该优化模型或加算力了。

TPOT 的计算公式是:

TPOT = (E2E Latency - TTFT) / (completion_tokens - 1)

注意分母是completion_tokens - 1,因为第一个 Token 的时间已经算在 TTFT 里了。这个细节很多人搞错,导致 TPOT 偏小。

3.2 用代码把埋点串起来

下面是我实际项目里用的一段 Python 埋点代码,基于 OpenAI 兼容接口,思路通用:

import time import uuid from prometheus_client import Histogram, Counter TTFT_HIST = Histogram('llm_ttft_seconds', 'Time to first token', buckets=[0.1, 0.25, 0.5, 1, 2, 5, 10]) TPOT_HIST = Histogram('llm_tpot_seconds', 'Time per output token', buckets=[0.005, 0.01, 0.02, 0.05, 0.1, 0.5]) TOKEN_COUNTER = Counter('llm_tokens_total', 'Total tokens', ['type', 'biz']) def call_llm(prompt, biz_line): request_id = str(uuid.uuid4()) start = time.perf_counter() first_token_time = None output_tokens = 0 # 流式调用,逐 Token 计时 for chunk in stream_chat(prompt): if first_token_time is None: first_token_time = time.perf_counter() TTFT_HIST.observe(first_token_time - start) output_tokens += 1 end = time.perf_counter() if output_tokens > 1: tpot = (end - first_token_time) / (output_tokens - 1) TPOT_HIST.observe(tpot) TOKEN_COUNTER.labels(type='completion', biz=biz_line).inc(output_tokens) return request_id

这段代码的关键点:用time.perf_counter()而不是time.time(),因为前者是单调时钟,不受系统时间调整影响;流式调用才能精确拿到 TTFT,非流式调用只能拿到总时间。

3.3 输入 Token 数怎么准确获取

输入 Token 数最好直接用推理引擎返回的usage.prompt_tokens,不要自己用 tokenizer 估算。原因有两个:一是不同模型的 tokenizer 不一样,自己算容易错;二是推理引擎返回的是实际参与计算的 Token 数,包含了 chat template 拼接后的系统提示词,这才是真实成本。

如果你用的是 vLLM,它会在响应的usage字段里返回准确的prompt_tokens和completion_tokens。如果引擎不返回,那就只能自己用对应模型的 tokenizer 算,但要记得把 chat template 也算进去。

注意:很多团队统计 Token 时只算用户输入的文本,漏掉了系统提示词和对话历史。一个多轮对话场景,系统提示词加历史可能有几千 Token,漏算会导致成本统计严重偏低。

4. 延迟归因:把毛刺拆到具体环节

4.1 延迟到底花在哪几个阶段

一次推理请求的延迟可以拆成这几段:

  1. 网络传输:请求从客户端到网关的时间。
  2. 排队等待:请求在推理引擎队列里等 GPU 的时间。
  3. Prefill 阶段:处理输入 Token,生成 KV Cache。
  4. Decode 阶段:逐个生成输出 Token。
  5. 回传:Token 流式返回客户端的时间。

大部分团队只统计了 3+4,忽略了 1、2、5。但实际排查中,排队时间(第 2 段)经常是延迟毛刺的元凶——当并发请求超过 GPU 处理能力时,请求会在队列里堆积,TTFT 暴涨。

4.2 用滑动窗口做延迟平滑

延迟数据天然抖动大,直接看瞬时值会被噪声干扰。我一般用滑动窗口做平滑,窗口大小取 1 分钟或 5 分钟。这里涉及一个"滑动窗口滤波器"的思路:对窗口内的延迟数据取分位数(P50、P95、P99),而不是平均值。

为什么不用平均值?因为平均值会被极端值拉偏,而且掩盖了长尾问题。用户体感差往往是因为 P99 高,而不是平均值高。一个服务平均延迟 500ms 但 P99 是 10 秒,用户体验就是"时不时卡一下",平均值完全反映不出来。

4.3 一个真实的延迟归因案例

回到开头那个案例,我最后是怎么定位的?把延迟按阶段拆开后发现:

阶段正常请求异常请求
排队20ms25ms
Prefill180ms8500ms
Decode1200ms1300ms
回传50ms55ms

Prefill 从 180ms 涨到 8500ms,其他阶段几乎没变。这就锁定了问题在输入 Token 数。再对比输入 Token 分布,发现异常请求的输入 Token 从平均 300 涨到了 3200。根因就是上游改了系统提示词。

如果没有分阶段埋点,只看端到端延迟,你永远不知道是哪个环节出了问题。

5. 成本核算与 Token 计费落地

5.1 按 Token 计费的实现思路

Token 计费的核心是"谁用了多少 Token"。实现上分两步:

第一步:采集。每次请求记录user_id、biz_line、prompt_tokens、completion_tokens。这些数据在推理引擎层就能拿到。

第二步:聚合。按用户、按业务线、按天做聚合,算出总 Token 数,再乘以单价。

这里有个细节:输入和输出的单价通常不一样(输出更贵,因为 decode 更耗算力)。所以计费时要分开算:

cost = prompt_tokens * input_price + completion_tokens * output_price

5.2 缓存命中怎么算钱

现在很多推理引擎支持前缀缓存(Prefix Caching),命中的部分不需要重新计算 prefill,成本更低。但计费时要不要给用户打折?我的做法是:按实际计算量计费,缓存命中的部分按折扣价。

具体实现上,vLLM 会返回num_cached_tokens之类的字段,用prompt_tokens - cached_tokens作为实际计费的输入 Token 数。这样既公平,也能激励业务方复用相同的前缀(比如固定的系统提示词)。

5.3 成本异常告警

光统计不够,还要能告警。我设了几条规则:

  • 单次请求 Token 数超过阈值(比如 8000)触发告警,防止有人误传超长文本。
  • 单业务线日 Token 用量环比增长超过 50% 触发告警,防止异常调用。
  • 单用户日 Token 用量超过配额触发限流。

这几条规则帮我拦下过好几次事故,最典型的一次是某个测试脚本忘了加循环退出条件,一晚上烧了几百万 Token。

6. 常见问题与排查技巧实录

6.1 排查速查表

现象可能原因排查方向
TTFT 高但 TPOT 正常输入 Token 太多或排队严重看 prompt_tokens 分布和队列长度
TTFT 正常但 TPOT 高decode 阶段算力不足或 batch 太大看 GPU 利用率和 batch size
端到端延迟高但引擎指标正常网络回传慢或客户端处理慢看网关到客户端的耗时
Token 统计和引擎对不上漏算系统提示词或 chat template对比 usage 字段和自算值
延迟毛刺周期性出现定时任务抢占资源或缓存失效对齐毛刺时间和任务调度时间
缓存命中率突然下降前缀被改动或缓存被清检查系统提示词是否变更

6.2 几个我踩过的坑

坑一:用平均值看延迟。前面说过,平均值会掩盖长尾。我现在的习惯是 P50、P95、P99 一起看,任何一个异常都要查。

坑二:Token 统计口径不统一。网关层算一次、引擎层算一次、业务层又算一次,三个数对不上。后来统一规定:以推理引擎返回的 usage 为准,其他层只做透传,不做二次计算。

坑三:日志里打了完整 prompt。这既是隐私风险,也是存储灾难。后来改成只打 prompt 的哈希值和长度,需要排查时再根据 request_id 去受控的地方取原文。

坑四:忘了统计失败请求。失败的请求也消耗了资源(比如 prefill 算完了但 decode 报错),如果不统计,成本会漏算。现在我把失败请求也纳入 Token 统计,只是标记状态为 failed。

6.3 一个容易被忽略的指标:队列等待时间

很多推理引擎的 metrics 里没有直接暴露"排队时间",但你可以用当前时间 - 请求入队时间来算。这个指标在扩容决策上非常关键:如果排队时间持续大于 TTFT,说明瓶颈在并发能力,加卡比优化模型更有效。

我一般会设一条规则:排队时间 P95 超过 500ms 就触发扩容评估。这条规则比看 GPU 利用率靠谱得多,因为 GPU 利用率高不代表排队严重(可能 batch 打得好),利用率低也不代表不排队(可能调度有问题)。

7. 可视化看板怎么搭才有用

7.1 看板分层原则

看板不是指标越多越好,我一般分三层:

  • 总览层:QPS、P99 延迟、Token 总量、成本。给管理层看,一眼知道健康度。
  • 诊断层:TTFT/TPOT 分位数、排队时间、缓存命中率、各业务线 Token 分布。给开发和运维看,用于定位问题。
  • 明细层:单请求的完整链路追踪。给排查具体问题时用。

三层之间可以下钻:总览发现异常,点进诊断层看哪个环节,再点进明细层看具体请求。

7.2 告警阈值怎么定

阈值不能拍脑袋,要基于历史数据。我的做法是:先跑两周收集基线,然后按 P99 的 1.5 倍设告警阈值。比如历史 P99 延迟是 2 秒,那告警阈值设 3 秒。这样既能捕捉异常,又不会因为正常波动频繁误报。

另外,告警要分级:P99 超过 1.5 倍发 warning,超过 3 倍发 critical。不同级别走不同的通知渠道,避免告警疲劳。

8. 我个人的几点实操体会

这套可观测性体系我从零搭到能用,前后迭代了三四个月。最大的体会是:不要一上来就追求大而全。我一开始想做一个覆盖所有指标的完美系统,结果拖了很久没上线。后来改成"先上 TTFT、TPOT、Token 数三个核心指标,能跑起来再逐步加",两周就上线了,后面再慢慢补。

第二个体会是:指标口径一定要在团队内对齐。我们曾经因为"延迟从哪算起"这个问题争论了一周,最后发现大家说的根本不是一回事。现在我们的做法是,所有指标定义写进文档,新人入职第一件事就是读这份文档。

第三个体会是:可观测性本身也有成本。埋点、存储、查询都要花钱。所以要有取舍,高频聚合指标全量,明细日志抽样,异常请求全量。这个平衡点需要根据业务量慢慢调。

最后一个建议:如果你现在还没做任何 Token 和延迟统计,别想着一步到位。先在最外层加一个简单的计时和 Token 计数,把数据跑起来,哪怕只是打到日志里。有了数据,你才知道下一步该优化什么。没有数据的时候,所有的优化都是猜。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/5 8:36:13

Android 本地存储进阶:Jetpack DataStore 从原理到实战避坑指南

做 Android 几年,本地存储这块我算是把所有方案都折腾过一遍:最早是 SharedPreferences,后面项目大了、数据多了,明显感觉它越来越吃力。两年前我们团队把主力 App 的存储层从 SharedPreferences 整体迁到了 Jetpack DataStore&am…

作者头像 李华
网站建设 2026/10/5 8:35:35

Python正则表达式从入门到实战:核心逻辑与常见坑一次讲透

正则表达式这东西,不少Python开发者学了三年还是记不住,每次用到都得现查手册。原因很简单:零散地看语法、背元字符,没有真正理解它的设计思路。我自己从搞爬虫、做日志分析、清洗脏数据一路走过来,深感正则不是背出来…

作者头像 李华
网站建设 2026/10/5 8:32:41

JSP+MySQL宠物诊所系统:真实业务闭环与部署避坑指南

简介:本资源是一套完整的基于JSP与SQL Server(或兼容数据库)开发的宠物诊所管理系统毕业设计项目,面向计算机专业本科生及Web开发初学者,解决宠物医疗场景下的用户管理、宠物档案、预约挂号、诊疗记录等信息化管理需求…

作者头像 李华
网站建设 2026/10/5 8:32:11

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

大模型推理服务的账单和性能问题,往往不是"模型不行",而是"看不见"。我见过太多团队在本地跑得好好的模型,一上生产就出现响应忽快忽慢、Token 消耗远超预算、GPU 利用率忽高忽低的情况,排查起来全靠猜。问题…

作者头像 李华
网站建设 2026/10/5 8:29:28

Hadoop分布式存储架构实战:气象海量小文件与HDFS优化策略

简介:一份面向计算机科学与技术、软件工程等专业本科毕业生的Hadoop方向学士学位论文,围绕气象数据的分布式存储展开研究。论文从Hadoop架构入手,系统讲解HDFS分布式文件系统与MapReduce计算模型,并结合气温、湿度、风速等多源气象…

作者头像 李华
网站建设 2026/10/5 8:29:08

AI落地别急着取代人:增强式AI才是可靠路线

最近在带几个AI落地项目,接触了不少团队。我注意到一个现象:大家开会时最常问的问题不是“AI能给业务带来什么增量”,而是“这活儿以后是不是不用人干了”。几乎每一轮讨论到最后都会绕到同一个地方——哪些岗位会被替换,哪些环节…

作者头像 李华