Skip to content

image_full 无法被正确调用:同一张图每轮重复降采样(38 次)、restore 提示注入 38 次,模型只能猜 ref → 每轮一次失败+重发,缓存命中从 75% 掉到 28% #3266

Closed

Description

@nsdbjh

环境

  • billion-context 0.1.197(Windows / npm 全局),bili codex lane,protocol=responses,模型 gpt-6.1-sol
  • 会话:01a134c1-9f0e-7863-9ad6-e6237bd181f7
  • 配置:compress.stripImages=true、compress.imageCompression.enabled=true(现已由用户关闭以止血)

现象

会话里连续出现红色失败回执(用户截图):

[ACP] image_full FAILED: no downscaled image is recorded for m04963 — it was passed through untouched or never carried an image
[ACP] image_full FAILED: no downscaled image is recorded for m04974 — it was passed through untouched or never carried an image
[ACP] image_full FAILED: ref m04986 does not exist in this session

证据

1)只有一张图被降采样,却被重复降采样 38 次、提示也注入 38 次

16:05:16 [acp-image] m04327: image/png 214575B→~3410B (1105→105 tok, saved 141165B)
16:05:16 [inject] image-full restore note appended as trailing user turn (234 chars)
16:06:11 [acp-image] m04327: image/png 214575B→~3410B …
16:06:11 [inject] image-full restore note appended as trailing user turn (234 chars)
16:07:11 …(同一 ref、同一张图,反复)

统计(当前 bili.log 窗口):[acp-image] m04327 = 38 次;image-full restore note appended = 38 次;成功 image_full = 0 次。

2)模型调用的 ref 全是它自己的近期消息(在"猜")

16:04:20 [acp-loop] round 1: 2 call(s): image_full({"ref": "m05020"}) |
16:04:46 image_full m05026: rejected (no downscaled image is recorded …)
16:05:33 image_full m05033: rejected
16:06:34 image_full m05040: rejected
16:07:26 image_full m05047: rejected
16:08:24 [acp-loop] round 1: 2 call(s): image_full({"ref": "m05051"}) |

ref 每轮 +7 递增,典型"顺着最新 ref 往后试"。DB 交叉核对(sessions/index.db 的 messages 表):

被调用的 ref 实际角色/类型 是否图片消息
m04963 / m04974 / m04986 assistant / tool-call ❌
m05026 tool / tool-result ❌
m05033 / m05040 / m05047 同上(模型自己的近邻消息) ❌
正确的目标 m04327(用户贴图,被降采样的那一张) ✅

3)代价:每轮一次失败就重发整条请求

每次 rejection 都跟一条 [acp-loop] round 1: proxy tool executed; re-requesting so the model sees the result;面板 缓存命中率 当前 28.2%(近 10 次平均 75.2%)——每轮重发都是几十 k token 的实付。

判断

  • no downscaled image is recorded = 该 ref 存在但不是图片消息;ref does not exist in this session = 纯猜测的 ref。两种都是"模型不知道正确 ref"的表现。
  • 正确 ref(m04327)每轮都在被降采样、note 也每轮注入,说明 note 没有起到指引作用。两个可能:
    1. note 在转发上游时被"临时内容剥离"逻辑一起剥掉(日志里同时存在 stripped N ACP status marker line(s) from incoming history (issue #1029));
    2. note 送到了但模型忽略,改用自己的近邻 ref(ref 递增行为支持这一解释)。
  • 无论哪种,缺少失败护栏:现在会无限重复"注入 note → 模型猜 ref → 失败 → 重发"。

Ask

  1. 失败护栏:同一 session 内 image_full 连续失败 ≥2 次后,停止注入 restore note(或在 note 里明确"本会话当前没有可还原的图片"),避免无限猜 ref。
  2. 提示可达性可诊断:确认 restore note 不会被 incoming-history 的临时标记剥离;若被剥离,打一条 debug 日志(现在从日志无法判断 note 是否到了上游)。
  3. 降采样应 memoize:同一 ref + 同一内容不应每轮重算(本例 38 次 × 214KB→3.4KB),按 (ref, content_hash) 缓存即可,省 CPU 也省日志体积。
  4. 失败回执里给出正确 ref:rejection 文案可以带上"本会话可还原的 ref 列表(或:无)",让模型一次命中,而不是靠猜。

1. 唯一被降采样的图片:m04327(重复 38 次)

[acp-image] m04327: image/png 214575B→~3410B (1105→105 tok, saved 141165B/0 tok)
[inject] image-full restore note appended as trailing user turn (234 chars)

同一对日志在 16:05:16 / 16:06:11 / 16:07:11 … 每轮重复。

当前 bili.log 计数:

计数项 次数
[acp-image] m04327:(降采样) 38
image-full restore note appended(提示注入) 38
成功的 image_full(非 FAILED 的 result) 0

被降采样的 ref 分布(去重后只有一个):

m04327  38

2. 模型的 image_full 调用序列(ref 在猜)

16:04:20  [acp-loop] round 1: 2 call(s): image_full({"ref": "m05020"}) |
16:04:46  [acp-image] image_full m05026: rejected ([image_full FAILED: no downscaled image is recorded for m05026 — it was passed through untouched or never carried an image])
16:04:46  [acp-loop] round 1: 1 call(s): image_full({"ref":"m05026"})
16:04:46  [acp-loop]   → image_full result: [image_full FAILED: no downscaled image is recorded for m05026 …]
16:05:33  [acp-image] image_full m05033: rejected (…same…)
16:06:34  [acp-image] image_full m05040: rejected (…same…)
16:07:26  [acp-image] image_full m05047: rejected (…same…)
16:08:24  [acp-loop] round 1: 2 call(s): image_full({"ref": "m05051"}) |

用户侧截图里的三条(同一模式的更早片段):

[ACP] image_full FAILED: no downscaled image is recorded for m04963 — it was passed through untouched or never carried an image
[ACP] image_full FAILED: no downscaled image is recorded for m04974 — it was passed through untouched or never carried an image
[ACP] image_full FAILED: ref m04986 does not exist in this session

失败 ref 汇总(重启后 16:04–16:08):m04999 / m05004 / m05020 / m05026 / m05033 / m05040 / m05047(各 1 次,另有 m05051 再次发起)。每次失败都跟一条 proxy tool executed; re-requesting。

3. DB 交叉核对(~/.local/share/billion-context/sessions/index.db,messages 表)

select ref, role, kind from messages where session_id=? and ref in (...)
ref role kind 结论
m04963 assistant tool-call 模型自己的工具调用,非图片
m04974 assistant tool-call 同上
m04986 assistant tool-call 同上
m05026 tool tool-result 上一次失败的回执,非图片
m04327 (被降采样的那一张,用户贴图) — 正确目标

→ 模型引用的全是它自己近邻的消息 ref;正确 ref(m04327)在提示里给了 38 次,但从未被采用。

4. 代价(面板数据)

指标 值
缓存命中率(当前) 28.2%
缓存命中率(近 10 次平均) 75.2%
最近一条助手消息 输入 198 / 缓存读取 103,296 / 缓存写入 0 / 输出 355
单轮耗时 71m47s(会话累计)、首字延迟 50.8s

每次 rejection 都触发一次整请求重发,且重发时前缀已变(re-requesting so the model sees the result),命中率因此塌陷。

5. 采集命令(只读)

$f="$env:USERPROFILE\.local\state\billion-context\bili.log"

# 降采样与提示注入计数
(Get-Content $f | Where-Object { $_ -match "\[acp-image\] (m\d+):" }).Count
(Get-Content $f | Where-Object { $_ -match "image-full restore note appended" }).Count

# image_full 失败 ref 分布
Get-Content $f | Where-Object { $_ -match "image_full (m\d+): rejected" } |
  ForEach-Object { if ($_ -match "image_full (m\d+): rejected") { $matches[1] } } | Group-Object

# 调用序列
Get-Content $f | Where-Object { $_ -match "image_full" } | Select-Object -Last 20
# DB 核对被调用 ref 的真实角色
python3 - <<'EOF'
import sqlite3
con=sqlite3.connect("file:$HOME/.local/share/billion-context/sessions/index.db?immutable=1", uri=True)
for ref in ("m04963","m04974","m04986","m05026","m04327"):
    print(ref, list(con.execute(
        "select role,kind from messages where session_id=? and ref=?",
        ("01a134c1-9f0e-7863-9ad6-e6237bd181f7", ref))))
EOF

Activity

  1. ranxianglei commented on Oct 15, 2026

    @ranxianglei
    Owner

    🤖 Powered by ework · qwen3.8-27b

    (ai: vllm-qwen/qwen3.8-27b)

    查重: 不重复。 全库检索(含已关闭)只有本 issue 涉及 image_full;相关的 ranxianglei/acp-kernel#353 是该功能的原始设计 issue(另一个仓库、功能立项性质),不是同一问题的重复报告。

    版本核对: 你跑的是 v0.1.197。我核对了 v0.1.197 → master 之间所有触碰图像压缩路径的提交——没有一项改动这个功能(期间只有持久化 fsync 自愈 #3157/#3163 等无关修复),所以以下分析直接适用于你的构建。

    审核结论(三步 triage)

    ① 复现/查证 — 问题真实存在,且比表面更深一层。

    • 「restore 提示不带 ref」是代码事实:src/image-compress.ts 的 imageFullTrailingNote() 只把数量传给 kernel 的 buildImageFullSystemNote(count),提示原文就是 "N image(s) were reduced … call image_full with that message's ref"——没有任何 ref。模型只能从自己最近见过的 ref 往前猜(+7 递增正是它在按消息序号试错)。你列的 4 点里第 2 点是根因,其余都是它的下游症状。
    • 「每轮重复降采样 38 次」我做了机制排查:现有单测(tests/image-compress.test.ts)证明同一个 session 对象内重复收到原始图只会降采样一次(有指纹缓存);codex 本地 auto-compact 的 rebase 路径也会抬高 ref 水位线,不可能保持 m04327 不变。所以 38× 意味着你的环境里每轮拿到的 session 状态不是同一个(状态丢失或 session id 变化——Windows + AV 场景下持久化文件读写受阻是这个仓库已知的一类问题,[bug] 会话状态写入缺 fsync:内核崩溃后留下 size 正常但 VDL=0 的全零状态文件,loadSync 永久抛错 → 502 acp-proxy failure 且不自愈(含 7 处 tmp+rename 写路径盘点) #3157/fix: fsync state writers before rename + self-heal corrupt session state (#3157) #3163 正是为此后补的,但那是 v0.1.197 之后才合入的)。但现有日志无法区分这两种情况——[acp-image] m04327: ... 这行日志不带 session id,这是可诊断性缺陷,我会一并修。
    • 另发现一个同机制家族的相邻缺陷:进程重启后(shrink 记录随 state 恢复、但主机侧指纹缓存在内存里丢了),代码会判定「已降采样过」而跳过该图,结果原始 214KB 大图从此每轮原样发上游,功能静默失效。和你的现象不同但同根(状态与内存缓存的生命周期不一致),会一起修。

    ② 层次判断: 表面现象 = 模型猜 ref 失败循环 + 缓存命中率崩;根本原因 = (a) restore 提示/拒绝回执都不携带可操作信息(ref 列表),(b) 降采样记账对状态重置不鲁棒且不可诊断。你的 4 点建议全部打在正确层次上,我按此实现。

    ③ 方案评估 — 采纳你的 4 点,全部在宿主侧(src/)修,不动 kernel:

    1. 连续 ≥2 次 image_full 失败后停止注入 restore 提示(成功即重新武装)——防提示注入空转;
    2. 提示行和 [inject] 日志带上 ref 列表/session id——可诊断性;
    3. 降采样幂等性加固:用「原始字节大小 == shrink 记录的 originalBytes」识别原始图,替代纯内存指纹——重启/重载后不再重复计算也不再漏缩;
    4. restore 提示和每次拒绝回执都列出当前可恢复的 ref(或明确说 none)——模型第一次就能命中。

    开始实现,完成后开 PR(Closes #3266)并在本帖回复链接。

  2. nsdbjh commented on Oct 15, 2026

    @nsdbjh
    Author

    关键新证据:宿主侧根本没有这个工具

    codex 的 stderr(由宿主 Nest 侧捕获,CodexJsonRpcClient,level=40):

    15:33:54  codex stderr: ERROR codex_core::tools::router: error=unsupported call: image_full
    15:34:45  …同上
    15:35:23  …同上
    15:37:06  …同上
    15:37:56  …同上
    (16:01–16:11 继续同样报错)
    

    今日累计 45 次,与 bili 侧每次 image_full({"ref": …}) 一一对应 → image_full 不在宿主(codex lane)的工具表里,工具路由直接判 unsupported。​

    计数口径说明:正文里的 38 是当时窗口的计数;以下是同一份 bili.log 的最新累计(分"死前 / 重启后"两段)——
    降采样 66(唯一目标始终是 m04327)/ restore note 43 / 被拒 22(死前 13:m04800…m04986;重启后 9:m04999…m05062),仍在增长。

    判断:三重叠加

    # 环节 证据
    ① bili 注入 restore note,要求模型调用 image_full image-full restore note appended as trailing user turn × 43
    ② 宿主没有注册该工具 error=unsupported call: image_full × 45
    ③ bili 自己的回执说"没有降采样记录" no downscaled image is recorded for m05xxx … × 22

    三者叠加 → 模型无论怎么调都拿不到原图 → 换 ref 再试 → 无限循环。

    代价

    每次 rejection 都跟一条 [acp-loop] round 1: proxy tool executed; re-requesting so the model sees the result,即整条请求重发;面板缓存命中率由近 10 次平均 75.2% 掉到当前 28.2%,每轮重发都是几十 k token 的实付。

    背景(非因果结论)

    死前 22.5 分钟里 13 次失败重发 + 42 次重降采样,每轮都要重新投影该会话的 20.8 MiB / 5,053 条历史。该循环与随后的一次进程异常退出是否有因果关系未证实(无 WER、无 dump、无异常栈,相关证据已被启动方式的 stderr 截断销毁),此处仅作为"循环不只是浪费 token"的背景提供。

    新增两条 Ask(优先级高于正文那四条)

    1. 注入 restore note 前,先确认目标 lane 的工具表里真的有 image_full——宿主已经在报 unsupported call,此时注入只会制造死循环。
    2. 该 lane 不支持时不要注入 note,改为纯文本说明"本会话图片已降采样、无法还原",让模型彻底放弃这条路径。

    (正文原 4 条 Ask 仍然有效:失败护栏 / 提示可达性可诊断 / 降采样 memoize / 回执给出正确 ref。)

    用户侧止血(已做)

    compress.imageCompression.enabled → false:不再降采样、不再注入 restore note,该类失败立即消失(代价:图片按原尺寸进上下文)。

  3. ranxianglei commented on Oct 15, 2026

    @ranxianglei
    Owner

    🤖 Powered by ework · qwen3.8-27b

    (ai: vllm-qwen/qwen3.8-27b)

    这条新证据把死循环的最后一环钉死了——问题比「模型猜错 ref」更根本:restore 通道在 codex lane 上结构性不成立。

    机制分析(已对照 codex 源码与测试套件核实)

    error=unsupported call: image_full 不是无害噪音。codex 的工具路由器对未知工具的处理是(codex-rs/core/src/tools/registry.rs,且其自身测试 compact.rs / search_tool.rs 明确断言了这个行为):给该调用合成一条 function_call_output = "unsupported call: image_full",写进自己的会话历史,并在下一次请求里原样重发。

    于是同一次 image_full 调用在两份历史里留下两条互相矛盾的记录:

    视角 调用结果
    bili 服务端(acp-loop 执行后 re-request,模型实际看到的) no downscaled image is recorded for m05xxx …
    codex 持久历史(下次请求重发的) unsupported call: image_full

    后果链:① 模型永远拿不到干净的成功——即使猜对 ref,codex 的历史里这次调用仍记为 unsupported;② 每次尝试都在上游缓存行与客户端重发历史之间制造一个永久分叉点 → 前缀缓存命中率持续衰减(75.2%→28.2%);③ 每轮一次失败 + 整条 re-request,几十 k token 实付。你面板里的三个现象(note×43、host error×45、rejection×22)就是这一条链的三个观测面。

    结论与方案更新

    你的两条新 Ask 判定正确,升为最高优先。更新后的修复(全部宿主侧,不动 kernel):

    1. lane 门控(新 Ask fix: keep system prompt byte-stable for prefix cache #1/fix: address all code review findings (condense, routing, session-id, dead code) #2):responses+codex lane 上不再注入 image_full 工具声明,restore 提示改为纯文本「本会话 N 张图已降采样、无法还原原分辨率,请基于降采样图继续」——让模型彻底放弃该路径。降采样本身保留(省 token 是主价值),只撤掉走不通的还原承诺。
    2. 原 Ask feat: OpenAI Responses API support (/v1/responses) #4:restore 提示与每次拒绝回执列出当前可恢复的 ref 列表(或明确 none)——在 restore 可用的 lane(anthropic/openai/google)上模型第一次就能命中。
    3. 原 Ask fix: keep system prompt byte-stable for prefix cache #1:连续 ≥2 次 image_full 失败后停止注入 restore 提示(成功即重新武装)。
    4. 原 Ask Use kernel renderTags:"text-only" + forbid tag echoing in prompt #3 + 可诊断性:降采样幂等性加固(用 shrink 记录里的原始字节大小识别原始图,替代纯内存指纹——进程重启后不再重复计算、也不再出现「记录还在但指纹丢了 → 原图从此直发上游」的静默失效);[acp-image] 日志补 session id——你那 66 次重复降采样到底是状态丢失还是 session id 变化,下个版本一眼可辨。

    另注:acp_retrieve(CCR)在 codex lane 上有同样的结构风险(同为 bili 注入工具、宿主无法路由),但 CCR 关断涉及占位符替换语义,属行为决策,本次不动,我在 issue 里单列等你判断。

    开始实现,完成后开 PR。

  4. ranxianglei commented on Oct 15, 2026

    @ranxianglei
    Owner

    🤖 Powered by ework · qwen3.8-27b

    (ai: vllm-qwen/qwen3.8-27b)

    修复已提交:PR #3270 — #3270 (分支 2026-10-15_image-full-lane-gate,已 rebase 到最新 master,typecheck+build+定向测试本地全绿,全量回归以 CI 为准)。全部改动在宿主侧(src/),未动 kernel。以下按八问作答:

    1. 是什么问题
    codex lane 上 image_full 的「还原通道」结构性不可用:bili 向模型承诺了一个宿主根本路由不到的工具。模型每轮按提示调用 → 宿主报 unsupported call 并把合成错误写进自己的重发历史 → bili 服务端又执行一次并回执拒绝 → 整条请求重发。提示里只有计数没有 ref,模型只能从自己最近的消息往前猜(你的 +7 递增模式),猜错必然,于是死循环 + 缓存命中率 75%→28%。「同一张图每轮重复降采样」是叠加的第二机制(见 2)。

    2. 什么原因导致的
    三层:① 能力错配——bili 注入的工具声明只对「能路由该工具的宿主」成立;codex 的工具路由器对未知工具不是静默忽略,而是合成一条 function_call_output="unsupported call: ..." 写进会话历史并原样重发(已对照 codex-rs/core/src/tools/registry.rs 源码及其自身测试核实)。这使每次尝试在客户端历史与上游缓存行之间制造一个永久分叉点,且即使猜对 ref,codex 历史里记的仍是 unsupported——该 lane 上还原路径从根上走不通。② 提示缺信息——restore note 只给计数不给 ref 列表(kernel 的 buildImageFullSystemNote(count) 设计如此),模型无依据只能猜。③ 重载后状态不一致——进程重启后持久化的 shrink 记录还在、内存指纹表没了,守卫判定「有记录但指纹未知」→ 跳过压缩 → 原始字节从此直发上游,功能静默关闭且每轮日志看似仍在活动。

    3. 影响范围与严重程度
    所有 responses+codex lane 且开了 compress.imageCompression.enabled 并降采样过截图的会话:每轮实付几十 k token 的重发 + 缓存命中持续衰减 + 模型永远拿不到原图(功能性失效,不只是浪费)。其他 lane(anthropic/openai/google)不受门控影响,仅提示/回执文案增强。你已用 enabled=false 止血,与本 PR 不冲突(关掉后两者都不发生)。

    4. 怎么修复的

    • 降采样与还原通道分离门控:新增 per-request stamp(handle() 内,与既有 storeEffectiveImageCompression 同纪律)。responses+codex lane:不注入 image_full 工具声明,尾部提示降级为纯文本「N 张图已降采样、本会话无法还原原分辨率,请基于降采样图继续,不要调用 image_full」——让模型彻底放弃这条路径(对应你新增 Ask fix: keep system prompt byte-stable for prefix cache #1/fix: address all code review findings (condense, routing, session-id, dead code) #2)。降采样本身保留(省 token 的主价值不受影响)。执行侧保持 armed:偶发调用被正常执行无害且有益。
    • 提示与失败回执列出可恢复 ref(原 Ask feat: OpenAI Responses API support (/v1/responses) #4):note 从裸计数改为列出当前真正可恢复的 ref(上限 20 + "+N more";已还原的、ref 已被剪除的不列——列了只会喂拒绝),按编号排序保证字节稳定。每次失败的回执同样附带列表或明确的「本会话无可恢复图片」。
    • 连续失败护栏(原 Ask fix: keep system prompt byte-stable for prefix cache #1):连续 ≥2 次失败后 armed 提示自抑制,直到一次成功重置(streak 随会话 stats 持久化),抑制时打一次 warn。
    • 重载后尺寸识别(原 Ask Use kernel renderTags:"text-only" + forbid tag echoing in prompt #3 的机制面):shrink 记录里有原始字节大小;keep-smaller 规则保证降采样产物严格更小,所以尺寸精确匹配 = 原始载荷无歧义 → 重启后再次见到原图会重新压缩,不再直发、不重复建记录/日志。(memoize 本身已有 encode cache,38× 的根因是上面的状态丢失,不是缺 memo。)
    • 可诊断性(原 Ask fix: address all code review findings (condense, routing, session-id, dead code) #2):[acp-image] info 行补 session id——「同 ref 反复降采样」下个版本一眼可辨是状态丢失还是 session id 变化。提示可达性部分结论:note 是出站 ephemeral user message,客户端永不回传,incoming-history 剥离路径碰不到它(你日志里 note×43 即证据),无需改。

    5. 是否有回归问题
    逐项审过:既有 image 测试 14 例未改断言全绿;wire-contract golden 未动(工具 schema 没变);plugin mode 本就排除(stamp 条件含 !pluginMode,同旧行为);未 stamp 的路径默认 armed = 完全保持 #3266 前行为;master 新合入的 lazy-metadata proxy(#3050)对新 metadata 写入透明安全(set 触发物化,且 stamp 每请求覆盖)。PR 正文附了 7 行「旧→新」行为变更表。

    6. 是否破坏缓存
    不新增破坏:note 字节跨轮稳定(排序 ref、固定上限);护栏抑制反而移除了一个随失败变化的元素;重载会话的 wire 字节从「原尺寸直发」变「重新降采样」是一次性的有意修正(表 f 行)。

    7. 是否改了设计方向
    轻微转向:#1095 的单一 enabled 开关拆成两个正交门控(降采样通道 / 还原通道),因为「宿主能否路由该工具」是 per-host 属性而非功能开关。遗留一项待你决策:acp_retrieve(CCR)在 codex lane 上有同样的结构风险(同为 bili 注入、宿主无法路由),但关断涉及占位符替换语义,属行为决策,本 PR 不动——要不要同样按 host 门控,等你拍板。

    8. 是否引入新配置
    无。config surface 不涉及;新增的只是内部 per-request stamp 和 stats 字段 imageFullFailStreak(持久化统计,非配置项)。

    判定:可以进入 review/合并流程;CI 已在推送的 head 上跑,红了我会定向排查。

  5. added a commit that references this issue on Oct 15, 2026
    a483bfe
  6. nsdbjh commented on Oct 15, 2026

    @nsdbjh
    Author

    追加一条观察:成功还原后,同一张图又立刻被重新降采样

    (日志时间为 UTC;会话 01a134c1-9f0e-7863-9ad6-e6237bd181f7)

    image_full 成功那次的回执明确承诺"本会话内保持全分辨率":

    08:11:23  [acp-loop] → image_full result: restored original-resolution image(s) for m04327 (1 image);
              full resolution applies for the rest of this session.
    

    但同一张图在前后几秒内仍在被降采样:

    08:11:04  [acp-image] m04327: image/png 214575B→73410B (1105→1105 tok, saved 141165B/0 tok)
    08:11:23  → restored original-resolution image(s) for m04327        ← 成功
    08:12:21  [acp-image] m04327: image/png 214575B→73410B …             ← 又缩回去了
    08:13:22  [acp-image] m04327: image/png 214575B→73410B …
    

    → 回执承诺的 "full resolution applies for the rest of this session" 没有真正生效。​

    这是长期现象,不是一次抖动

    统计项(会话累计) 值
    imageShrunkCount 136(其中绝大多数是同一个 ref m04327)
    imageFullRestores 10
    imageBytesSaved 13,679,124 B
    imageTokensSaved 0
    每轮 [acp-usage] img-saved=**0tok**/787KB x37、0tok/13359KB x136 …(全部 0 tok)
    [acp-image] 行的 token 对(92/92 条) 1105→1105 tok(从未变过)

    两个结论

    ① 重复降采样是纯浪费。​ 当前 maxDimension: 1600 对 1440×900 的截图不触发缩放,只做重新编码:字节 214KB→73KB,但 token 恒为 1105,一个没省(imageTokensSaved=0 且每轮 img-saved=0tok 即证据)。反复做的只有编码 CPU 与日志体积。

    ② "已还原"状态没有保住。​ 客户端每轮重发的是原始字节,bili 每轮重新看到未压缩的图;若"本会话已还原(full-res armed)"是按请求/内存态保存,就会在折叠重投影或进程重启后丢失,于是反复走"重新压缩"。这与本 PR 修的"重载后尺寸识别"(shrink 记录还在、内存指纹表丢了)属于同一类状态丢失。

    Ask(可选,不阻塞本 PR)

    1. 把"本会话已还原"按 (ref, content_hash) 持久化,并在下次遇到同一原始字节时短路:不再重新降采样、不再重复写 [acp-image] 日志。
    2. 若短期不做,建议在 [acp-image] 行里区分情形(如 first shrink / re-shrink after restore)——本 PR 刚加的 session id 已让"状态丢失 vs session id 变化"可分,这一条能把"是不是还原状态没保住"也变成一眼可辨。
  7. added 2 commits that reference this issue on Oct 15, 2026
    bfd56d3
    fbc0b1d
  8. ranxianglei commented on Oct 15, 2026

    @ranxianglei
    Owner

    🤖 Powered by ework · qwen3.8-27b

    (ai: vllm-qwen/qwen3.8-27b)

    这条观察很关键——它把「重复降采样」和「restore 承诺不生效」并成了同一个状态问题。逐条回应:

    ① 重复降采样是纯浪费 —— 确认。
    1105→1105 tok 证明 maxDimension=1600 对 1440×900 根本没触发缩放,每轮只做了重新编码(214KB→73KB,token 一个没省)。PR 新增的尺寸识别(重载后按记录的 originalBytes 认出原始字节)把这类情况收敛为:同一进程生命周期内只重编码一次,之后字节稳定、不再刷日志。跨进程重启仍会各重编码一次(编码缓存是内存态),属可接受代价。

    ② 「已还原」没保住 —— 机制上有两条路:

    • 还原标记(imageFullRestored)和降采样记录同在会话状态体里、一起持久化,普通进程重启不会丢;
    • 但你日志里的组合特征——每轮都重新看到未压缩原图,且连「首次创建降采样记录」的日志都反复出现(38→66 次全是同一个 m04327)——指向每轮拿到的都是全新/丢状态的会话对象:状态活不过一轮,还原标记和降采样记录一起丢,两个症状同源。PR 已给 [acp-image] 行加上 [sessionId] 前缀,升级后一眼可辨:sessionId 每轮变 = 会话身份在漂;不变而其他状态重置 = 状态没活过一轮。
    • 另一条会主动清还原标记的路径是 native-compaction rebase(会同时清还原列表和降采样记录)。「本会话保持全分辨率」目前只在两次 rebase 之间成立;要不要让用户的全分辨率诉求跨 rebase 粘住是个设计取舍,我没擅自扩大本 PR 范围——你若认为值得保证,我在本 issue 补一条子项跟踪(同机制家族,不单开 issue)。

    你侧的止血(enabled=false)与 PR 方向一致:codex lane 上本来就该保留降采样、撤掉永远走不通的 restore 承诺。

    PR 最新状态:#3270 已更新到 fbc0b1d。CI 红的 6 个 test 分片完成分诊:4 个是存储快照的元数据摘要漂移(真实回归,已修复——无条件打戳是新行为,四个 storage-render 快照按既有先例重新生成);2 个是 Windows 跑机时序抖动(断言零差异、对应 Ubuntu 分片通过、本地复跑全绿,证据见 PR 评论)。另补了两处:失败计数跨重启持久化(此前重启会悄悄丢掉「≥2 次连续失败即停注提示」的护栏)、四条 wire 的工具注入改为「降采样通道激活 且 宿主可路由」双门控。等本轮 CI 全绿即可合并验证。

  9. ranxianglei commented on Oct 15, 2026

    @ranxianglei
    Owner

    🤖 Powered by ework · qwen3.8-27b

    (ai: vllm-qwen/qwen3.8-27b)

    本轮收工状态汇总(细节见上方各条回复):

    1. 修复已交付:PR https://github.com/ranxianglei/billion-context/pull/3270(分支 2026-10-15_image-full-lane-gate,head fbc0b1daf)。覆盖本 issue 全部 6 条 Ask:codex lane 不再注入 image_full/restore 提示(改纯文本「不可还原」)、note 与失败回执列出真实可还原 ref、连续 ≥2 次失败自动停注(成功复位、streak 随会话持久化)、重载后按 originalBytes 尺寸识别原始字节重新降采样(不再永久直通)、[acp-image] 日志带 [sessionId]。
    2. 验证:本地 typecheck/build + 定向套件全绿(含新增 tests/issue3266-image-full-lane-gate.test.ts 7 例);CI 41/43 lane 绿,唯二红 shard 为 windows-latest/node22 定时抖动(零断言差异、本地复跑绿、与 diff 无关子系统),已在 PR 评论逐条给出证据。
    3. 后续:已触发该 run 失败 job 重跑(run 38722934773,in_progress),转绿即全绿可合;抖动模式另立跟踪 issue windows-latest/node22 CI runner 上反复出现活体 HTTP/fs 定时抖动(4 个测试文件、2 轮 CI) #3279(4 个测试文件 × 2 轮 CI 的实例表),不占本 PR。
    4. 留给 owner 的一个设计问题:native-compaction rebase 会清掉 imageFullRestored,「本次会话保持全分辨率」的承诺跨 rebase 是否要粘住——本 PR 未扩大范围,如需跟踪请在本 issue 留言。

    合并由你操作;重跑结果如有异常我会跟进。

  10. added 8 commits that reference this issue on Oct 15, 2026
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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions