omp packages

oh-my-tps0.3.3

Tiny live TTFT and TPS readouts for the Pi coding agent.

enderliquid Published 10 days ago MIT ~675 downloads/mo

Untested on omp

This is a pi package. No one has reported whether it works on omp yet. If you try it, add your result below.

Did it work for you?

Result

One report per person. A new report replaces your old one.

Reports 0

No reports yet.

Readme

Oh My TPS

English | 简体中文

安装

npm package

pi install npm:oh-my-tps

Git repository

pi install git:github.com/EnderLiquid/oh-my-tps

功能速览

oh-my-tps 只做一件事: 给 Pi TUI 加一组实时速度读数,测量 LLM 首字延迟和输出速度。

  • τ:TTFT,首个 token 到达前等了多久,单位秒
  • Δ:TPS,每秒输出多少 token

显示效果:

τ0.8 Δ48.6

就这么多。

十个字符的空间,开箱即用的体验。

感兴趣可以继续往下看,但到这里你其实已经会用了。

读数详解

你会在 TUI 底部状态区域看到这样的读数:

τ0.8 Δ48.6
τ1.1 Δ49.7L
τ0.8A Δ52.4A

这里后缀的含义是:

  • A:平均值(Average)。TTFT 和 TPS 分别维护自己的历史样本。
  • L:上一轮有效的最终 TPS(Last),仅用于 Δ。

可以这样理解:

  • τ0.8 Δ48.6:响应正在流式传输,TTFT 约为 0.8 秒,并且已经产生了当前有效的实时 TPS。
  • τ1.1 Δ49.7L:请求已经发出,但还没有收到首个 token;TTFT 正在计时,Δ 暂时显示上一轮最终有效 TPS。
  • τ0.8A Δ52.4A:当前处于空闲状态,显示近期 TTFT 和 TPS 的平均值。

流式传输时的 Δ 仍可能显示为上一轮 TPS、平均 TPS 或未知值,直到实时 TPS 满足首次计算条件。响应结束后的 Δ 优先使用服务提供方返回的 usage.output;只有没有可用的 usage.output 时,才使用最后一次有效的实时 TPS。本轮没有有效最终 TPS 时,最终状态回退到本轮开始前的上一轮 TPS,再回退到平均 TPS。

配置

默认开箱即用。要更改读数颜色,创建 ~/.pi/agent/oh-my-tps.json:

{
  "builtinRenderer": {
    "color": "muted"
  }
}

同时,插件支持自行绘制并替换内置读数。做法是把状态数据发布到 Pi 的事件总线,实现测量与渲染解耦,内置读数只是其中一个事件订阅者。你可以关闭内置读数,订阅同一份数据并渲染到任何位置。详见 CONFIGURATION.zh-CN.md。

原理说明

下面这部分面向希望了解插件原理的用户。

状态机

内部分为四个阶段:

  1. 等待阶段:请求已发出,等待首个 token
  2. 流式输出阶段:首个 token 已到达,响应正在流式输出
  3. 结算阶段:响应结束,已完成本轮 TPS 的结算尝试
  4. 空闲状态:当前没有正在处理的服务提供方请求

示例:

空闲 τ… Δ? (还没有历史样本)
    -> 等待 τ0.2 Δ? (等待首个 token,每200ms更新τ)
    -> 流式输出 τ1.3 Δ? (首个 token 可能来自思考,τ已锁定,实时 TPS 尚未就绪)
    -> 流式输出 τ1.3 Δ51.0 (新的非思考增量到达,实时 TPS 已满足观察条件)
    -> 结算 τ1.3 Δ52.0 (本轮有效最终 TPS 来自 `usage.output` 或实时 TPS 回退值)
    -> 空闲 τ1.3A Δ52.0A
    -> 等待 τ0.2 Δ52.0L (优先显示上一轮有效最终 TPS)

τ 的来源

TTFT 的定义是:从请求发出到首个 token 到达所等待的时间。

插件使用首个携带 token 的非空流式增量作为可观测信号。以下事件在 delta.length > 0 时会触发 TTFT:

  • text_delta
  • thinking_delta
  • toolcall_delta

以下事件不会触发 TTFT:

  • text_start
  • thinking_start
  • toolcall_start
  • 空增量以及其他元数据事件

收到首个有效增量后,插件把它与请求开始时间的差值锁定为本轮 TTFT。

因此:

  • 等待阶段的 τ 会一直增加;
  • 首个正文、思考或工具调用 token 到达后,进入流式输出阶段,τ 就锁定;
  • thinking_start 等元数据不会结束等待,但非空 thinking_delta 会结束等待;
  • 如果服务提供方把思考内容加密并且不传输思考增量,插件只能测量首个可观察 token 的 TTFT,无法获得隐藏思考阶段内部的时间轴。

TTFT 样本与 TPS 样本独立维护。只要首个 token 到达,本轮就可以贡献 TTFT 平均值,即使本轮最终没有有效 TPS。

实时 Δ 的来源

服务提供方不会持续告诉 Pi “刚刚又生成了多少个 token”,所以实时 TPS 只能在本地估算。当前实现使用非思考增量的滚动队列:

  • 纳入非空的 text_delta;
  • 纳入非空的 toolcall_delta;
  • 排除所有 thinking_delta,包括思考摘要。

默认窗口是最近 5 秒。每当新的非思考增量到达时,插件会:

  1. 将原始增量和到达时间加入队列;
  2. 移除窗口之外的旧增量;
  3. 按到达顺序拼接窗口内的增量;
  4. 使用 tokenx 估算拼接内容的 token 数;
  5. 用窗口内 token 数除以观察时长,得到新的实时 TPS。

公式可以表示为:

实时 TPS = 最近窗口内的非思考增量估算 token
           /
           min(5秒,首个非思考增量后的观察时长)

从首个非思考增量到第一次计算实时 TPS,至少需要观察 2 秒。这个等待时间用于避免流式刚开始时分母过小,或者服务提供方一次性发送初始积压内容而产生异常高值。

实时 TPS 是最近窗口内正文和工具参数的传输速率,只在新的非思考增量到达时重算,不使用后台定时器。

最终 Δ 的来源

响应结束时,插件按以下顺序结算 TPS。

1. 服务提供方返回的 usage.output

当服务提供方返回有限正数 usage.output,并且从首个非空的正文、思考或工具调用增量到响应结束至少经过 2 秒时,使用:

最终 TPS = usage.output
           /
           (响应结束时间 - 首个有效内容增量时间)

usage.output 通常是服务提供方报告的真实输出 token 数,可能包含推理 token。因此,这个来源的分子可能包含思考 token,分母则从首个可观察的内容增量开始。

对于加密思考模型,隐藏思考可能在首个可观察增量之前已经发生。客户端无法知晓这段隐藏推理时长,因此 usage.output 来源的最终 TPS 可能略高于真实值。

2. 实时 TPS 回退值

只有当服务提供方没有可用的 usage.output 时,才使用本轮最后一次有效的实时 TPS:

最终 TPS = 本轮最后一次有效的实时 TPS

这个来源不包含思考,只反映正文和工具参数在最近窗口中的输出速率。

如果服务提供方返回了可用的 usage.output,但从首个有效内容增量到响应结束的时长不足 2 秒,插件不会改用实时 TPS 回退值;本轮没有有效最终 TPS。

实时值与最终值存在偏差的原因

1. token 估算是启发式的

tokenx 不是精确 tokenizer,而是一个轻量、偏启发式的估算库。它的优势是小而快,适合实时 UI 刷新。代价也很明确:它不是为所有模型都精确对齐而设计的。

tokenx 的设计与基准测试更偏向 GPT tokenizer / 英文文本 场景。当接入其他模型家族的 LLM,或者输出内容包含非英文字符时,偏差往往会更大一些。

2. 流式输出节奏不均匀

模型输出不是严格按“每个 token 匀速到达”展示给 UI 的。实际过程中还会受到这些因素影响:

  • 服务提供方自己的 SSE / chunk 刷新策略;
  • Pi 发布事件的节奏;
  • 思考、工具调用、正文混在一起时的内容结构变化。

3. 两种 TPS 的统计范围不同

实时 TPS 和 usage.output 来源的最终 TPS 的定义本来就不同:

  • 实时 TPS 只统计最近窗口内的非思考正文和工具调用;
  • usage.output 来源的最终 TPS 使用服务提供方报告的输出 token,并统计从首个正文、思考或工具调用增量到响应结束的时长;
  • 实时 TPS 不包含思考,而 usage.output 来源的最终 TPS 可能包含推理 token。

所以二者即使都没有估算误差,也不一定相等。实时值反映当前响应末段的输出节奏,最终值反映本轮可观察流式阶段的整体结果。

平均值 A

当前实现分别维护最近最多 5 条 TTFT 样本和 TPS 样本:

  • 首个 token 到达后,TTFT 就可以写入 TTFT 历史;
  • 只有本轮产生有效最终 TPS,才会写入 TPS 历史;
  • 使用服务提供方 usage.output 计算出的最终 TPS,以及实时 TPS 回退值来源的最终 TPS,都会纳入平均 TPS;
  • 因此一轮请求可能贡献 TTFT,但不贡献 TPS。

A 表示近期有效样本的平均值,不保证 TTFT 平均值和 TPS 平均值来自完全相同的请求集合,也不保证平均 TPS 中每条样本都具有相同的 token 统计范围。

数据参考指导

经验上可以这样看:

  • τ:参考价值很高,适合观察请求延迟;
  • 最终 / 平均 Δ:适合观察近期整体输出速度表现;
  • 实时 Δ:适合观察当前正文或工具参数的实时输出速度;

插件适用范围

  • 适用于为 LLM 速度与延迟提供粗略量化参考
  • 适用于快速发现长会话中某次明显偏慢的请求
  • 不适用于严格的模型性能对比与基准测试

许可证

MIT License