Skip to content

[Performance] 建立流式体验 SLO,并消除 Akasha 首尾固定延迟 #377

Description

@kachofugetsu09

背景

“单位时间平均吐得多”只能描述吞吐量,不能单独代表丝滑。平均值会把中途长停顿抹平;真正的体感来自首字延迟、相邻可见更新间隔、展示 backlog、帧稳定性,以及最后一个 delta 到 composer 恢复的尾延迟。

本轮修复已在 #376 建立共享 grapheme projector、terminal durable barrier 和全链路 telemetry。本 Issue 只跟踪剩余的架构级延迟,不把它们塞回同一个高风险 PR。

参考口径:

“丝滑”验收定义

以 grapheme cluster 而非 UTF-16/code point 计数;以下为 Akashic 的 Silky Goodput SLO:

  1. send → turn.started committed:P95 ≤ 100ms。
  2. first delta received → next rAF ready:≤ 1 个显示帧;不得等待 thinking 完结后才开始展示。
  3. 流式开始后:P95 可见 lag ≤ 100ms(400 grapheme/s 输入),任意停顿 < 100ms。
  4. 正常追赶容量 ≥ 600 grapheme/s;任意滚动 1s 展示量 ≤ 600,避免隐藏页恢复时爆发。
  5. thinking 与 answer 都不能饥饿;answer 首字不能等待完整 thinking backlog。
  6. authoritative terminal 立即刷完;terminal committed → composer ready P95 ≤ 100ms。
  7. 60/90/120/144Hz 结果等价;不拆 Unicode grapheme;Frame jank 不回归。

Silky Goodput = 活跃滚动窗口中同时满足上述 SLO 的窗口比例。裸 CPS/TPS 作为容量指标保留,但不能代替该结果。

已测基线

Provider ABBA

同一 deepseek-v4-flash、high effort、Akasha 开启;DeepSeek official 与 OpenCode Go 各 warmup 后 ABBA ×5,各 10 个 measured turn。所有 turn 的 input/session/model_binding 三处选择一致且 completed。

阶段 DeepSeek official OpenCode Go
first delta mean 2576ms 3680ms
Akasha query mean 1474ms 1461ms
provider raw TTFT mean 1047ms 2168ms
last delta → final mean 1618ms 1666ms
Akasha turn commit mean 1507ms 1588ms

结论:

  • 首卡有约 1.46–1.47s 的 Akasha 固定成本。
  • OpenCode Go 相比 official 多出的约 1.10s 几乎全部来自 provider raw TTFT。
  • 尾卡不是 provider:Akasha commit 占 tail 约 93%–95%。

Pixel 7

候选 APK + 候选 WebUI preview 的真实 turn:

  • send ack committed:45ms
  • turn.started committed:78ms
  • first thinking UI:2965ms
  • first answer UI:3774ms
  • terminal committed:5608ms
  • composer ready:5668ms
  • WebView terminal dispatched:5682ms
  • 285 frames;jank 3(1.05%);P95 10ms;missed vsync 0

端侧从收到首字到 UI/WebView 只需十几到几十毫秒;“卡很久才弹出卡片”的主要等待发生在 Akasha + provider 之前,不是 Room 或 Compose。

根因与语义边界

首段

before_turn → Akasha query 当前会等待上一轮后台 graph publication,再做 query/embed。等待约 1.14–1.20s,query runtime 约 0.17–0.20s,embed 约 0.10–0.12s。

不能直接删除 publication fence:它保证上一轮记忆对下一轮可见,属于语义合同。

尾段

TurnCommitted → Akasha turn_commit 同步 durable stage 后才允许核心 terminal;现状约 1.5–1.6s,主要成本来自 sparse stage/全 source 扫描。

不能先发 terminal 再异步丢下 durable stage:这会改变“完成”所代表的记忆持久化语义。

分阶段方案

A. 不改语义的性能修复

  • 将 sparse stage 改为按 session/turn 增量维护,避免每轮扫描完整 source。
  • 为 publication fence 记录 waited_for_turn_id/publication_span_id,区分真正 graph publish 与排队。
  • 保持 turn commit 的 durable barrier,但把可并行的 embed/stage 工作前移或增量化。
  • 建立 Akasha query/commit 的 benchmark fixture:冷/热、小/大 session、带/不带前序 publication。
  • 以相同 SessionDB write set、query result 与 graph publication identity 做语义等价 Gate。

B. 首屏反馈

  • turn.started 后 100ms 内展示克制的空态/“正在思考”反馈,不等待首 thinking delta 才创建卡片。
  • 该状态不能伪造模型已开始输出,也不能写入权威消息正文。
  • Web 与 Mobile 共用状态语义;Material 3 token,不新增独立视觉体系。

C. 持续回归

  • 自动产出 send/provider/Akasha/Room/WebView/terminal 分段报告。
  • 60/90/120/144Hz projector 仿真作为 PR Gate。
  • Android Macrobenchmark/FrameTimingMetric + Pixel 手工正式链;正式 workspace 测试只作为明确标记的手工证据,隔离 Gate 仍使用独立包名/workspace。
  • 报告 Silky Goodput、P95 lag、max stall、terminal-to-composer;禁止只报平均 CPS。

完成标准

  • Akasha query 固定部分 P95 至少下降 50%,且上一轮记忆可见性合同不变。
  • Akasha turn commit P95 至少下降 50%,SessionDB/Akasha write set 与恢复行为一致。
  • Pixel 首字前 100ms 内有真实 turn 状态反馈。
  • terminal committed → composer ready P95 ≤ 100ms。
  • Silky Goodput 在 60/90/120/144Hz 与 Pixel 7 上通过;无 grapheme 拆分、终态晚 delta、重复 terminal 或正式数据清理。
  • 性能改动拆成可独立回滚的 PR,不进行一次性 WebUI 全仓重写。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions