☰
首字很快,用户仍嫌慢:把 TTFT、ITL 与成功率放在一张账上
2026/10/25 8:36:25 网站建设 项目流程

一条回答首字 100 毫秒返回,后面却每隔半秒才出一个字;另一条首字稍慢,随后连续输出。只看 TTFT,前者更漂亮,读起来却未必更舒服。再把超时请求从报表里删掉,剩下的曲线还能更好看,但用户不会少遇到一次失败。

10 月 16 日 vLLM 的 Kimi K3 优化文章专门讨论了正确性与跨配置性能验证。性能补丁需要回答它对哪些输入、哪些批次有效,而不是只展示最快的一点。官方工程文章

时间戳要从同一条请求取

TTFT 是从约定起点到首个输出 Token 的时间;ITL 是相邻输出 Token 的间隔。起点可以放在客户端发起、网关接收或引擎入队,但报表必须写清楚,不能把不同起点的数据直接比较。流式网络分块也不一定逐 Token 对齐;把 HTTP chunk 当 Token,会得到另一种指标。

数据建议保留的字段容易造成的误读
请求ID、开始、状态、结束失败没有首字,于是被消失
输出流首个Token、各Token时间或块边界一个chunk被当成一个Token
负载输入长度、输出长度、并发、到达率不同工作量被平均到一起
缓存命中长度、冷热状态热缓存数字被当作冷启动

从三条教学记录算起

下面全部时间都是人为构造的毫秒数,用来检查统计口径,不是线上性能。程序显式使用 nearest-rank 分位数,避免不同库默认插值规则造成差异。

import math from statistics import mean def percentile(values, p): if not values or not 0 < p <= 1: raise ValueError("empty data or invalid percentile") xs = sorted(values) if any(not math.isfinite(x) for x in xs): raise ValueError("non-finite sample") return xs[math.ceil(p * len(xs)) - 1] rows = [ {"start": 0, "tokens": [100, 120, 180], "ok": True}, {"start": 0, "tokens": [300, 340], "ok": True}, {"start": 0, "tokens": [], "ok": False}, ] ok = [r for r in rows if r["ok"]] ttft = [r["tokens"][0] - r["start"] for r in ok] itl = [b-a for r in ok for a, b in zip(r["tokens"], r["tokens"][1:])] assert ttft == [100, 300] and itl == [20, 60, 40] print("TTFT mean/p99:", mean(ttft), percentile(ttft, 0.99)) print("ITL mean/p99:", mean(itl), percentile(itl, 0.99)) print("success:", len(ok), "/", len(rows))

两条成功请求的平均 TTFT 是 200 毫秒,nearest-rank P99 是 300 毫秒,成功率是 2/3。这里只有两个成功样本,所谓 P99 实际上落在最大值上,不能用它描述稳定的尾延迟。样本数、窗口和置信程度应与指标一起展示。

Python statistics 文档说明了均值和分位统计的不同用途,也提醒 NaN 等值可能破坏排序结果。坏样本应该单独计数和调查,不能静默吞掉后宣称数据更干净。官方文档

把成功率放回决策

比较两个方案时,至少同时看满足延迟目标的成功请求数、完整输出量和总资源时间。若方案通过更早拒绝请求降低延迟,报表要显示拒绝率;若通过截断输出增加吞吐,要显示完成条件变了。否则统计优化会伪装成系统优化。

还要拆开 Prefill 主导的长输入、Decode 主导的长输出,以及多轮缓存命中场景。全局一个平均数无法告诉我们哪种用户变快了。这里没有运行模型、网关或浏览器;离线代码只验证三条记录的统计计算,真实流式采集与集群负载仍需单独验证。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询