Description
版本
master c26c31ace(2026-11-11)。已发布的 0.1.213 不受影响:它在冷加载时对齐,没有延后的标记。这个标记是 #5608-D 带进来的(提交 2071e9fe7,在 PR #5609 里合入)。
现在的行为
#5608-D 把一次性的 ref/账本对齐(#4316)从冷加载挪到了会话下一次成功的折叠。折叠本来就会打断 provider 前缀,所以对齐带来的字节变化搭这个请求一起走;用 src/persist.ts 冷加载路径上(afterLoad)那段注释的话说,"sessions that never fold pay nothing ever"。
applyRanges(src/stream.ts)为此置上标记 session.pendingLedgerAlign = true,位置紧跟在提交 kernel 返回的状态(ctx.session.state = res.state)之后。这在后面的 r.blocksCreated === 0 分支之前,所以两类什么都没折叠的调用也会置上它:
- 被拒绝的 compress,回执
[Compression FAILED: …](gate:too-small、gate:cannot-anchor、gate:already-compressed、gate:none-resolved、gate:summary-length、gate:other); - Compression conflict banner (bili v0.1.205, node v24.19.0, win32-x64) #4521 说的 no-op 那一类,回执
[Compression no-op: nothing was folded and nothing failed — …]。
下一次存盘会消费这个标记(consumePendingLedgerAlign → maybeRestampToLedger → restampRefsToLedger)。被拒绝时,这次存盘就发生在同一次 applyRanges 调用里,经由 recordCompressFailure → markDirty → scheduleSave。no-op 时是会话的下一次存盘。
于是对齐跑在一次什么都没折叠的调用上,下一个请求发出去的字节就变了:
- 引用已经都在各自账本位置上(ref = 行 seq + 1)的会话:这一遍保留所有引用(
kept=N restamped=0 rehomed=0),但仍然把timestampsHighWaterRefSeq抬到最高的引用号,此前渲染过的每个<acp>标签都丢掉time=属性。 - 引用偏离了账本位置的会话(比如有一条受保护的消息占了一个存储行、却不占引用号):这一遍还会给偏离的引用重新编号。这正是 # Cache hit collapses to 1–12% for a whole generation, then the very next request returns to 100% — repeated 15× in one day #5608-D 推迟掉的那次重编号,现在发生在一次拒绝上,而不是一次重启上。
两种情况下,出站字节都从第一条带标签的消息起就变了,provider 前缀缓存从那条消息往后都未命中,而这次调用的回执报的是失败或 no-op。对齐是一次性的,所以每个会话只发生一次:在第一次走到提交那一步的 compress 调用上,不管它结果如何。
在提交之前就返回的拒绝(参数解析失败、gate:no-visible-context、gate:coverage-regression、gate:no-net-shrink 等)不受影响。
这和 src/persist.ts 里 consumePendingLedgerAlign 处写下的约定("No fold ⇒ marker never set ⇒ pure legacy no-op")以及 #5797 的 D 项(不 fold 的会话永久保留旧号,零代价)相矛盾。
复现
宿主层面,不联网。一个 9 轮、开着时间戳的会话,经 SessionStore 存盘、行在统一存储里,然后调一次 applyRanges、存一次盘,再对同一段历史跑第二次 processTurn:
| 会话 | compress 调用 | 对齐日志行 | 引用 | 字节变了的带标签消息 | 带 time= 的标签 |
|---|---|---|---|---|---|
| 引用都在账本位置上 | 被拒绝(gate:too-small) |
kept=9 restamped=0 rehomed=0 blocked=0 superseded=0 |
不变 | 9 / 9 | 9 → 0 |
| 引用都在账本位置上 | no-op(范围在保护区里) | 同上 | 不变 | 9 / 9 | 9 → 0 |
| 第三条消息受保护,它后面的六个引用都比账本位置低一位 | 被拒绝(gate:too-small) |
kept=2 restamped=6 rehomed=0 blocked=1 superseded=6 |
m00003–m00008 → m00004–m00009 | 8 / 8 | 8 → 0 |
| 同上 | no-op | 同上 | 同上 | 8 / 8 | 8 → 0 |
第一行里第一个标签前后的样子:
<acp tokens="752" type="text" time="…">m00001</acp>
<acp tokens="752" type="text">m00001</acp>
只在创建了块之后才置标记时,四行都是:没有对齐日志行、引用不变、0 条消息变化、每个标签都保留 time=。
前两行就是 PR 里的测试文件(tests/compress-refused-ledger-align.test.ts)固定下来的。后两行来自同一夹具的一个本地变体(一条消息受保护)。
端到端:在 master c26c31ace 上起一个代理(插件车道,带 x-bili-plugin: dsh,Anthropic wire,compress 经插件工具端点发出,一段合成对话,上游是本机回环上的假服务):
before compress : tags with time=19 / all tags=19
compress result : refused | [Compression FAILED: Total compressible content too small (408 chars across 1 range(s), min 5000). …
right after : tags with time=0 / all tags=19
拒绝之后那个请求有 33 条出站消息,之前是 31 条(多了这次调用和它的回执),和上一个请求的第一处不同在出站消息 0。代理日志里有 [persist] ledger-aligned refs for <session>: kept=55 restamped=0 rehomed=0 blocked=0 superseded=0 (#4316 PR3/3)。拿已发布的 0.1.213 包做的对照:被拒绝的调用之后 17 个标签里 17 个仍带 time=,每个请求都是下一个请求的前缀。
影响
- 谁:master 上第一次走到提交那一步的 compress 调用没有创建块的任何会话。
applyRanges是共用的提交路径:代理的 compress 循环和流改写、插件工具端点、外部摘要 compress、预检折叠都走它。关了消息时间戳(compress.messageTimestamps: false或BILI_MESSAGE_TIMESTAMPS=0)的会话,标签上本来就没有time=:引用都在账本位置上时对齐照样会跑,但出站字节不变;引用偏离了的仍然会被重新编号。 - 代价:每个会话一次,从第一条带标签的消息起前缀缓存未命中,而那时什么都没折叠。引用偏离了的会话,模型引用的编号也在那时被重排(旧号经 superseded 表拒绝,和任何一次对齐之后一样)。此前渲染过的消息上
time=属性没了,之后也不会回来。 - 没有丢东西,也没有折错。这是一次缓存代价和一处显示变化。
提议的修法
在 r.blocksCreated === 0 的那几处返回之后再置标记,和其他只在成功时做的记账(clearCompressFailures、clearCompressLoopStreak)放在一起。创建了块的 compress 照旧置上。只动宿主侧(src/stream.ts);PR 随后就发。
这个改动管不到的:搭着真折叠跑的对齐,在一个引用都没挪的时候仍然会抬高水位线。抬高发生在 restampRefsToLedger(kernel/src/refs.ts)里,所以我会另外提一个 kernel 授权申请。
Activity
[-][Bug][compress] 被拒绝或 no-op 的 compress 也会触发一次性的 ref/账本对齐:什么都没折叠,下一个请求却改写了所有标签[/-][+][Bug][compress] A rejected or no-op compress also triggers a one-time ref/ledger alignment: nothing was folded, yet the | [Bug][compress] 被拒绝或 no-op 的 compress 也会触发一次性的 ref/账本对齐:什么都没折叠,下一个请求却改写了所有标签[/+]ranxianglei commentedon Nov 12, 2026
该issue已按根因归类纳入 [kernel-compression] 根因汇总 #6265,由汇总单统一跟踪处理;本单不再单独跟进,故关闭。如认为归类有误或问题仍未解决,请直接回复,会重新打开。
ranxianglei commentedon Nov 12, 2026
状态更正(自动化脚本误操作): 本单此前被「纳入根因聚合」流程误关闭, 现重新打开。机器人工作已停, 本单由根因聚合线程统一跟踪, 根因修复落地后请手动关闭或在本单回复「close」。
[automated: re-open after bot-stop-only policy]
ranxianglei commentedon Nov 12, 2026
重新打开:此前被归类聚合脚本误关。按 owner 口径,纳入根因聚合的 issue 只停机器人、不关闭,由根因聚合线程统一跟踪,根因修复落地后统一关闭。
(reopened: absorbed issues must not be closed, only bot work stopped — owner policy 2026-11-12)
ranxianglei commentedon Nov 12, 2026
(更正)此前按"清理"口径误关闭;按 owner 口径纠正:被根因聚合纳入的 issue 只停机器人、不关闭。本 issue 重新打开,机器人工作已停。
ranxianglei commentedon Nov 12, 2026
(ai: vllm-qwen/qwen3.8-27b)
查重结论:本单已按根因归类纳入 [kernel-compression] 根因汇总 #6265(owner 2026-11-12 口径),由汇总线程统一跟踪,本单不单独跟进。
状态确认:
根因修复与关闭统一由汇总线程 #6265 处理,本单不再采取行动。