背景
“单位时间平均吐得多”只能描述吞吐量,不能单独代表丝滑。平均值会把中途长停顿抹平;真正的体感来自首字延迟、相邻可见更新间隔、展示 backlog、帧稳定性,以及最后一个 delta 到 composer 恢复的尾延迟。
本轮修复已在 #376 建立共享 grapheme projector、terminal durable barrier 和全链路 telemetry。本 Issue 只跟踪剩余的架构级延迟,不把它们塞回同一个高风险 PR。
参考口径:
“丝滑”验收定义
以 grapheme cluster 而非 UTF-16/code point 计数;以下为 Akashic 的 Silky Goodput SLO:
send → turn.started committed:P95 ≤ 100ms。
first delta received → next rAF ready:≤ 1 个显示帧;不得等待 thinking 完结后才开始展示。
- 流式开始后:P95 可见 lag ≤ 100ms(400 grapheme/s 输入),任意停顿 < 100ms。
- 正常追赶容量 ≥ 600 grapheme/s;任意滚动 1s 展示量 ≤ 600,避免隐藏页恢复时爆发。
- thinking 与 answer 都不能饥饿;answer 首字不能等待完整 thinking backlog。
- authoritative terminal 立即刷完;
terminal committed → composer ready P95 ≤ 100ms。
- 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 全仓重写。
背景
“单位时间平均吐得多”只能描述吞吐量,不能单独代表丝滑。平均值会把中途长停顿抹平;真正的体感来自首字延迟、相邻可见更新间隔、展示 backlog、帧稳定性,以及最后一个 delta 到 composer 恢复的尾延迟。
本轮修复已在 #376 建立共享 grapheme projector、terminal durable barrier 和全链路 telemetry。本 Issue 只跟踪剩余的架构级延迟,不把它们塞回同一个高风险 PR。
参考口径:
“丝滑”验收定义
以 grapheme cluster 而非 UTF-16/code point 计数;以下为 Akashic 的 Silky Goodput SLO:
send → turn.started committed:P95 ≤ 100ms。first delta received → next rAF ready:≤ 1 个显示帧;不得等待 thinking 完结后才开始展示。terminal committed → composer readyP95 ≤ 100ms。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。结论:
Pixel 7
候选 APK + 候选 WebUI preview 的真实 turn:
端侧从收到首字到 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. 不改语义的性能修复
waited_for_turn_id/publication_span_id,区分真正 graph publish 与排队。B. 首屏反馈
turn.started后 100ms 内展示克制的空态/“正在思考”反馈,不等待首 thinking delta 才创建卡片。C. 持续回归
完成标准
terminal committed → composer readyP95 ≤ 100ms。