Skip to content

Image session permanent 503: re-sending 48.6 MiB outbound body every round (fold blocked by cadence, archived images sti | 图片会话永久 503:每轮重发 48.6 MiB 出站体(fold 被 cadence 挡住,已归档图片仍在 wire 上) #3909

Open

Description

@yanhuojiyusheng

环境

  • bili 0.1.201:~/downloads/node-v24.20.0-linux-x64/lib/node_modules/billion-context(pid 2442676,127.0.0.1:8787,<state>/proxy-origin 指向它)
  • bili 0.1.202:~/.pi/agent/npm/node_modules/billion-context(pid 2333255,127.0.0.1:18802,instance json 里 "lane":"pi")
  • 客户端:pi(native plugin 车道,lane=pi)
  • 上游:magpie http://127.0.0.1:3425/v1 → api.commandcode.ai(Cloudflare;/etc/hosts 把该域名固定到 172.66.201.178)
  • 模型:group/sol(contextWindow: 500000,maxTokens: 393216)
  • 会话:01a14d1c-bffc-706d-badb-86931fd725ac

现象

不是某一轮失败,而是这个会话从此永久失败。客户端看到:

Command Code: A Worker script configured by the website owner exceeded its resource limits (CPU time or memory) and was terminated. (503: you can retry your request)

bili.log 里每一轮都是同一组:

request: 4829 msgs, inbound=115.1MiB, outbound=48.6MiB, local=3752ms
forward POST → http://127.0.0.1:3425/v1/chat/completions
stream silent 15000ms — holding client with SSE keep-alive comments past its undici body timeout (#1647)
client disconnected mid-stream after 474900ms (2125 bytes written to client)
network failure with no usage report — armed emergency shrink with local estimate 738022.4007379536 tokens

会话体量

  • jsonl:120.3 MB / 4807 条消息
  • content block 统计:image 95.63 MB(113 张内联 PNG,均值 846 KB,最大 1.50 MB)、text 17.35 MB、toolCall 2.97 MB、thinking 1.49 MB

bili 已经降采样过这些图([image-rescale] image/png m09856 2000x457/521KB -> 1600x366/336KB 一类日志),出站仍有 48.6 MiB。

已排除「上游随机抖动」

直连 magpie 复现(同 model,同 prompt 结构):

  • 45 张内联 PNG,请求体 52.0 MB → HTTP=503 time=219.3s,body 与客户端看到的完全一致
  • 对照 A:24 MB 纯文本体 → 55 s 400 context_length_exceeded,无 503
  • 对照 B:48 MB 纯文本体 → 53 s 400 context_length_exceeded,无 503

⇒ 触发量级是图片像素/解码 CPU,不是裸字节数;上游 Cloudflare Worker 的 CPU/内存配额被撑爆。
同一时刻另一个会话(01a15778-0765-77da-8351-c61385816e85)走同一链路 outbound=233.5KiB、cache hit 97%,完全正常 → 客户端 / 代理 / 路由都没坏。

我判断是 bili 侧缺陷的三点

1. 待折叠量早就够了,fold 永远不触发

nudge idle: usage=70% (263160/375000), growth=6578/22500 (ref=256582, interval=50000), pendingT1=51457/50000 model=group/sol,
  reason="growth 6578 < floor 22500, ready: T1 51457, blocked: T1 (cadence)"

pendingT1=51457 已越过阈值 50000,但 growth 6578 < floor 22500 且 blocked: T1 (cadence)。图片不参与文本折叠,于是每轮全量重发;而维持 48.6 MiB 的请求又一直 503,token 增长永远上不去 → 死锁,emergency shrink 也只能空转。

2. 已归档的像素没有从 wire 上摘除

<state>/retrieve/img/01a14d1c-bffc-706d-badb-86931fd725ac/ 已有 163 个文件 / 105 MB(说明 #2640 的归档路径在工作),但出站体里仍有 ~46 MiB 图片。对图片密集会话,"归档了但照样重发"等于没省。

3. acp-drift 覆盖缺口让折叠静默退化为整段重发

[acp-drift] fold coverage mismatch: 5454/6441 covered message id(s) present in resent history — 987 covered id(s) missing from resent history

每次重启 / 客户端截断后都出现,且没有任何 rebuild 动作,fold 状态实际上是"部分失效 = 全量重发"。

另外两点(同会话,独立问题)

  • reasoning-pair-violated: 47 assistant tool-call message(s) lack reasoning_content while 169 carry it —— 严格回传上游(thinking 模式)会 400
  • 版本分叉:0.1.201 与 0.1.202 同以 lane=pi 身份运行,同一 sessionId 的日志在两个版本间交织([v=0.1.201] … request: 4829 msgs 后紧跟 [v=0.1.202] … client disconnected mid-stream),03:38:52 还出现同一会话被两个进程重复 cold-loaded

复现步骤

  1. 在一个会话里累积 ~100 张 1 MB 级 PNG 截图(我这边是移动端 / 浏览器截图走查)
  2. 反复重启客户端,让 fold 覆盖漂移(acp-drift)
  3. 继续对话:outbound 稳定在 ~48 MiB,上游 503,客户端约 8 分钟超时后 abort

期望

  1. 出站体要有字节护栏:超过阈值时强制折叠 / 继续降采样,或 fail-fast 给出可诊断错误,而不是每轮烧 4–8 分钟再超时
  2. 已归档到 retrieve/img/ 的图片不应再出现在 wire 上
  3. pendingT1 已超阈值且出站体异常巨大时,cadence / floor 应让位(现在 ready: T1 51457, blocked: T1 (cadence))
  4. acp-drift 覆盖缺口应触发 rebuild,而不是静默整段重发
  5. (可选)为图片密集会话提供可配置的「历史图片保留窗口」,弥补 建议:compress.stripImages 默认开启——否则带图消息不可压缩会形成只升不降的输入地板撑爆上下文 #2607 移除 stripImages 后的空缺

补充:这是极端但真实的负载(~100 张截图 ≈ 一次移动端 UI 走查的量),期望的是优雅降级,而不是会话永久卡死。

Activity

  1. changed the title [-]图片会话永久 503:每轮重发 48.6 MiB 出站体(fold 被 cadence 挡住,已归档图片仍在 wire 上)[/-] [+]Image session permanent 503: re-sending 48.6 MiB outbound body every round (fold blocked by cadence, archived images sti | 图片会话永久 503:每轮重发 48.6 MiB 出站体(fold 被 cadence 挡住,已归档图片仍在 wire 上)[/+] on Oct 20, 2026
  2. ranxianglei commented on Oct 20, 2026

    @ranxianglei
    Owner

    🤖 Powered by ework · qwen3.8-27b

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

    查重:不重复。 按失败签名(HTTP chat/completions 车道 · 图片密集 · 出站体恒定 ~48 MiB · 上游 Worker 资源上限 503 · fold 被 cadence 挡住)检索 open+closed issue 与 PR,无同签名条目。最近邻但机制不同:

    标签: bug · p1 · pi · scope/wiring(HTTP 车道会话完全不可用且无自愈路径 = 核心承诺失灵;落点在 host 侧出站管线,故 wiring)。


    人话版:到底发生了什么

    这个会话攒了 ~100 张 1MB 级 PNG 截图,token 占用才 70%(图片 token 轻),但出站体每轮恒定 ~48.6 MiB(图片字节重)。于是:折叠永远不触发(够不着触发线)→ 图片永远留在 wire 上 → 每轮都把 48.6 MiB 原样发给上游 → 上游 Cloudflare Worker 被图片解码的 CPU/内存撑爆回 503 → 客户端等 ~8 分钟超时 abort → 下一轮又重来。死锁,且没有任何一条恢复路径能把它拉回来。 你的对照实验(同样 48MB 纯文本 → 干净 400,不 503)已经钉死了:触发量级是图片像素/解码成本,不是裸字节数。

    三步审核结论

    ① 复现/查证(master 代码核实) —— 你把三点判断全写对了方向,但第 2 点其实是第 1 点的下游症状,第 3 点在这里基本是旁观者:

    • (1) fold 被 cadence 挡住 = 成立且是主因。 pendingT1=51457 > 50000 说明待折叠质量够了,但内核 nudge 决策要同时满足质量阈值和增长节奏(growth 6578 < floor 22500),节奏门把注入挡了(blocked: T1 (cadence))。图片 token 轻,token 占用停在 70%,growth 长期上不去 → 节奏门永远关着。
    • (2) 「归档了但照样重发」= 误诊,是 (1) 的症状。 读代码(src/image-restore.ts、src/server/handle.ts、src/session.ts:548):像素只有在所在消息范围被折叠时才会从出站副本摘除(Archive-then-fold: default folding for archivable media (Closes #2607, Fixes #2609) #2640);你在 retrieve/img/ 看到的 163 个文件是入站索引时的落盘通道(ingest-time spill,decompress-shared.ts:283),不是「折叠摘除」的证据。因为从没折叠过,像素自然一直在 wire 上。修好 (1)/字节护栏,这条自动消失,不需要单独改。
    • (3) acp-drift 覆盖缺口 = 此处基本无关。 那行 [acp-drift] fold coverage mismatch(prepare-openai.ts:189)代码里定位就是纯观测日志("fix: rescue context-overflow turns in-request instead of arming a rescue that never comes (#1195) #1328 overflow rescue 的可观测补充"),本身不触发 rebuild 也不该触发——覆盖 id「missing from resent history」意味着客户端自己把这些消息删了/截断了(它们本就不在 wire 上,没东西可重建)。按 KDD feat: self-update (auto + manual) #11 顺序为中心的对账,个别 cell 失配只作废那个 cell,不会级联成整段 unfold。真正让东西折不掉的是 (1) 的节奏门,不是漂移。要不要针对「客户端截断后残留失效 fold」单独排查可以另开,但它不是这次死锁的根因。

    ② 层次定位(根因) —— 一句话:压缩管线在 token 空间做折叠决策,而这条链路的拒绝方(Cloudflare Worker)按图片解码成本判定;中间缺一层「字节空间护栏」。 #3706 已经为 WS 车道补了这层(wsOutboundBytes + shedding),唯独 HTTP 车道没有——它只有一个 5 GiB 硬拒绝上限(checkOutboundBody/limitsOutboundBytes),48.6 MiB 远远够不着,畅通无阻。第三层:网络失败武装的 emergency shrink 是 token estimate,被 usage 锚点压住(#3701 已确认这是防 ghost-overcount 的有意设计),所以 token 空间的恢复手段对「字节过大」天然无效。

    ③ 方案评估 —— 你的期望 1(字节护栏)打到根因,方向认可;期望 2 是 (1) 的下游不用单修;期望 3(cadence/floor 让位)我能做到但不建议,理由见下;期望 4 见上面 (3);期望 5 是新配置项、可选。


    需要你拍板的:HTTP 车道的护栏怎么加

    核心是一个产品/config-surface 决策(新增或泛化一个默认字节预算 = 默认行为变更,按配置面纪律需你先点头)。三个方案:

    A. 把 #3706 的字节预算泛化到 HTTP(推荐) B. 图片载荷感知预算 C. 只对 503 反应
    触发 发送边界测最终序列化字节,> 阈值即 shedding 只统计出站里的图片字节/像素,> 阈值即 shedding 收到 Worker 资源上限 503 才 shedding+有界重发
    复用 直接扩 #3706 的 egress-shed(owner 已审过的机器) 新计量逻辑 借 #3706 的反应式重发骨架
    精准度 粗(裸字节;48MiB 文本会被同门槛管到) 高(直击像素-CPU 失败模式,不误伤大文本) 最高但被动
    风险 低(与 #3706 同构,那边 CI 已验过) 中(新逻辑别误判非图片大块) 中高(503 归因不可靠,可能限流/瞬时;首次仍烧满超时)
    opt-out 阈值设 0 关闭,回旧行为 同左 无需

    我的推荐:A 打底。 理由:#3706 的 shedding 本来就是「从最旧的非保护尾部图片开始归档为占位注记(保留 mNNNNN 引用、原件可恢复)→ 仍超再走 jimp 降采样阶梯」,这恰好直接砍掉像素-CPU 成本,不需要另造像素计量器;跨车道统一、有 opt-out、CI 已验证过同型改动。关于期望 3(cadence 让位)我建议不做:字节 shedding 已经把出站体压下去了,没必要再去动折叠节奏那条精心设计的纪律(KDD #8 平直 50K + #3006 净收益地板,都是防零收益折叠循环的)。若你仍希望「pendingT1 已就绪 + 出站异常巨大时 cadence 让位」,请当第二个独立变更单列评审,别和护栏混在一个 PR。

    结构化八问(决策请求)

    1. 是什么问题:图片密集会话在 HTTP chat/completions 车道永久 503——token 才 70%,出站体每轮恒定 ~48.6 MiB,上游 Worker 被图片解码撑爆,客户端 ~8 分钟超时后 abort,会话从此卡死无自愈。
    2. 什么原因导致:三层叠加——(a) 折叠/nudge 全在 token 空间跑,图片 token 轻字节重,70% 占用 + growth<floor 时 fold 永不触发、pendingT1 超阈值也被 cadence 挡;(b) HTTP 车道没有字节护栏([PR] fix: WS-lane egress byte budget with image shedding for close-1009 oversize requests (#3701) #3706 只覆盖 WS,HTTP 只有 5GiB 硬拒绝上限,够不着);(c) emergency shrink 是 token estimate,被 usage 锚点压住(有意设计),对字节过大无效。
    3. 影响范围与严重程度(分选项):受影响 = HTTP 车道 + 单会话图片累积到几十 MB 的真实负载(移动端/浏览器截图走查)。严重度 p1(会话完全不可用、无自愈)。A/B:> 阈值的图片会话从「永久 503 死循环」变「自动降载后恢复」,代价是当前在阈值附近正常工作的会话其较旧图片保真度下降(可恢复),大文本会话 A 会被同门槛管到 / B 不受影响。C:未收到 503 前行为完全不变。
    4. 怎么修复(推荐 A):把 [PR] fix: WS-lane egress byte budget with image shedding for close-1009 oversize requests (#3701) #3706 的 egress-shed 从「WS-only」泛化到 HTTP 车道——发送边界测最终序列化字节,超预算先归档受保护尾部之外的最旧图片为占位注记(保留 mNNNNN 引用可恢复),仍超走既有 jimp 阶梯;relay 预发送与 loop 内部重请求共用同一 helper。不动 kernel、不动折叠 cadence。
    5. 回归风险(分选项):A 极小(纯新增 HTTP 分支,WS 车道逐字不变,HTTP < 阈值零变化;唯一风险是默认阈值选错误伤合法大会话 → 保守值 + opt-out 兜底);B 中(新计量须防误判非图片大块文本);C 中高(503 归因不可靠,有误触发重发风险,且被动——首次仍烧满超时)。
    6. 是否破坏缓存:不破坏。shedding 只改出站副本([PR] fix: WS-lane egress byte budget with image shedding for close-1009 oversize requests (#3701) #3706/Egress image downscale: shrink big images before sending to the model, originals stay local and self-readable | 重做图片降采样(简化版):发模型前压大图、原图留本地不动、模型被告知尺寸后可自读原图 #3400 先例),会话状态消息本体不动,prefix-cache 语义不变。
    7. 是否改了设计方向:没有转向,是把 [PR] fix: WS-lane egress byte budget with image shedding for close-1009 oversize requests (#3701) #3706 已确立的「字节空间恢复路径」补到它当时明确排除的 HTTP 车道,不引入新的独立机制层。
    8. 是否引入新配置:是。倾向复用现有字段语义——要么把 network.wsOutboundBytes 泛化为通用名(WS+HTTP 共用),要么新增 network.httpOutboundBytes;默认值对齐 [PR] fix: WS-lane egress byte budget with image shedding for close-1009 oversize requests (#3701) #3706 量级(10 MiB 起,可下调),0=关闭回旧行为;CONFIGURATION.md 中英随生成器同步。这是 config-surface 变更,按规约需你拍板后才实现。

    拍板点(三选一 + 两个子项): ① 选 A / B / C?② 配置项命名(泛化 wsOutboundBytes vs 新增 httpOutboundBytes)?③ 默认阈值取多少(建议 10 MiB 起)?④ 要不要顺带加「reactive-to-503 有界重发」(我倾向不加,理由见 C 的风险)?


    另外两点(你标注为独立问题)——我会拆出去单独跟,不塞进本 PR

    • reasoning-pair-violated(47 条 assistant tool-call 缺 reasoning_content 而 169 条带 → thinking 模式严格回传会 400):不同症状 + 不同机制(reasoning 配对校验),拆独立 issue。
    • 版本分叉 / 双实例 split-brain(全局 0.1.201 与 pi-native 0.1.202 同时以 lane=pi 服务同一 sessionId,日志交织、同一会话被两进程重复 cold-load):不同失败域(安装拓扑/多实例态竞争),拆独立 issue。

    这两条我随后各开一个带 来源: #3909 标记的 issue 走各自 triage,保持 #3909 聚焦 503 死锁这一族。

    下一步: 上述护栏方向你拍板后我立即开工(我会先把工作区 rebase 到当前 master——它已含 #3706 的 egress-shed.ts,本分支现在停在 v0.1.201、落后 280 提交)。在你拍板前我不擅自动 config surface / 默认行为。

  3. ranxianglei commented on Oct 20, 2026

    @ranxianglei
    Owner

    🤖 Powered by ework · qwen3.8-27b

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

    跟进:上一条回复里承诺的两条独立问题已各自立项(均带 来源: #3909 标记 + 查重声明,保持 #3909 聚焦 503 死锁这一族):

    ⚠️ 透明披露一个操作失误:我在验证「本环境能否建 issue」时,用一条探针 POST 误建了一个空 issue #3921(标题 probe-do-not-file)。该环境 API 支持建 issue 但不支持删除(DELETE 返回 405),我已把它关闭并改标为「误建探针,请忽略」。无实际内容,不影响任何工作;在此如实报告,不藏着。

    当前状态 / 下一步:#3909 主问题(HTTP 车道图片会话永久 503 死锁)的护栏方案仍等你拍板——① A(泛化 #3706 字节预算到 HTTP,推荐)/ B(图片载荷感知)/ C(只对 503 反应)?② 配置项命名?③ 默认阈值?④ 要不要 reactive-to-503 重发?拍板后我先把工作区 rebase 到当前 master(已含 #3706 的 egress-shed.ts)再动手实现。在此之前不动 config-surface / 默认行为。

  4. ranxianglei commented on Oct 20, 2026

    @ranxianglei
    Owner

    🤖 Powered by ework · qwen3.8-27b

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

    补充一条对上一条决策请求的精确化(我按承诺读了当前 master v0.1.202 的真实代码核对 #3706 机制,发现一个会影响你选项 ② 的细节):

    1. Option A 确认可落地。 当前 master 的 src/egress-shed.ts 正是我描述的那套:两档 shedding——(A) 把受保护尾部(最近 8 条 top-level entry)之外的最旧图片归档为占位注记(原件留在会话存储 + 入站索引,read_original_image / decompress({imageRef}) 仍可达,客户端自身历史不动),(B) 剩余图走 jimp maxDim 阶梯 [1024,768,512,384];只改出站副本、绝不改 kernel 状态(与 Egress image downscale: shrink big images before sending to the model, originals stay local and self-readable | 重做图片降采样(简化版):发模型前压大图、原图留本地不动、模型被告知尺寸后可自读原图 #3400 同纪律),且对同一输入确定性产出稳定字节(保 prefix-cache)。而且 src/server/relay.ts 里调用点已经存在(现在只是被 gate 挡成 passthrough)——所以「泛化到 HTTP」是个外科手术式的小改动,不是新机制。

    2. 但有个坑,直接影响选项 ②(配置命名):[PR] fix: WS-lane egress byte budget with image shedding for close-1009 oversize requests (#3701) #3706 的 WS 门被刻意收窄——代码注释写明它排除 dsh-inline(一个 managed-but-HTTP transport,实测能正常送 16 MiB,[Bug] Jimp is active, but image accumulation still triggers Responses WS 1009; duplicate request size not reduced (v0.1. | [Bug] Jimp 已生效,图片累积仍触发 Responses WS 1009;重复请求未缩小(v0.1.200) #3701 Finding 1 明确说它绝不能 shed)。所以「把所有 HTTP 车道一刀切纳入 shedding」会错误地开始裁 dsh-inline。正确做法是给一个 transport 类别谓词,只覆盖出问题的非托管直连 HTTP chat/completions relay 车道(你的 pi→magpie→Cloudflare Worker 这条),保持 dsh-inline 继续豁免。

    3. 据此我把选项 ② 的倾向改明确:新增独立的 network.httpOutboundBytes,而不是把 wsOutboundBytes 泛化共用——WS 与 HTTP 的尺寸容忍度、豁免集合都不同(WS 更紧且要排 dsh-inline;HTTP 能扛更大)。默认值 ③ 建议落在 10–16 MiB(只要低于本例 48.6 MiB 就会触发;HTTP 比 WS 容忍大,取上沿可少误伤合法中大会话)——具体由你定。

    结论不变:仍等你拍板 ① A/B/C(我推荐 A)② 命名(现推荐新增独立 httpOutboundBytes)③ 默认阈值 ④ 要不要 reactive-to-503(我倾向不加)。拍板前我不动 config-surface / 默认行为;工作树干净、无仓库改动。

  5. added a commit that references this issue on Oct 20, 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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions