Back to Qwen Code

Qwen Code 0.15.11 默认 Heap OOM 压测报告

docs/e2e-tests/2026-05-21-qwen-0.15.11-default-heap-oom-stress-report.md

0.16.019.3 KB
Original Source

Qwen Code 0.15.11 默认 Heap OOM 压测报告

日期:2026-05-21

测试范围

本报告记录了针对 Qwen Code 0.15.11 最新本地构建的一轮默认 heap 压测。 这轮测试的目标是验证:在不人为降低内存上限的情况下,当前代码是否还能复现 issue 中提到的长会话 OOM,以及在更极端的大输出场景下还有没有新的风险。

本轮覆盖三个模型:

  • pai/glm-5
  • qwen3.6-plus
  • DeepSeek/deepseek-v4-pro

测试分为两部分:

  1. 真实长任务、多 agent 并发 review 循环。
  2. amplified foreground stdout 压测,即用大规模前台 shell stdout 放大 tool-output 路径压力。

测试环境

项目
分支codex/memory-investigation-draft-pr
Commitc161e0aa4
CLI本地 dist/cli.js
CLI 版本0.15.11
Node 默认 heap limit4144 MiB
NODE_OPTIONS未设置
显式 --max-old-space-size未设置
runner ulimitrunner 未设置
配置模式临时复制 ~/.qwen,并隔离 QWEN_RUNTIME_DIR
MCP / 正常配置尽量按复制后的正常配置加载

注意:这里的 CLI 版本显示为 0.15.11,是因为 package version 尚未 bump。 实际测试对象是 commit c161e0aa4 下本地编译出的 dist/cli.js,不是 PATH 里的全局 qwen 可执行文件。

本轮没有修改全局 Qwen 配置。原始 runtime artifacts 在:

  • .qwen/runtime-bench/2026-05-20T13-51-58-731Z-oom-stress
  • .qwen/runtime-bench/2026-05-20T15-20-37-790Z-oom-amplified

注意:本轮里 env-center MCP server 启动失败,但其他内置工具和部分 MCP/child process 仍然加载。因此这些结果代表当前本地环境,不是完全 stripped 的 --bare 环境。

核心结论

最新本地构建在 issue 最关心的“长会话 V8 heap OOM”路径上表现明显更好。 基于这轮默认 heap、多模型、多 agent、长任务压测,可以认为本 PR 对此前遇到的 long-session heap OOM 问题已经基本解决,至少在当前复现维度下已经不能再复现 原始 heap OOM。

真实长任务、多 agent 并发测试一共执行了:

  • 23 个 worker turn
  • 719,094,118 reported total tokens
  • 77 次 agent tool call
  • 856 次总 tool call

这部分没有复现任何传统 V8 heap OOM 特征:

  • JavaScript heap out of memory
  • Reached heap limit
  • Ineffective mark-compacts near heap limit
  • Allocation failed

真实长任务阶段最高 process-tree RSS 为 874.7 MiB,最高 root-process RSS 为 219.1 MiB。这说明在默认 heap 下,当前代码没有轻易复现原 issue 中那种长任务 跑挂的 heap OOM。

第二阶段 amplified stdout 压测更激进。它一共执行了 18 个 payload attempt, 覆盖三个模型和 128 MiB2048 MiB 的 foreground stdout payload。

结果是:

  • 三个模型都成功跑过 1536 MiB payload。
  • 最高成功 process-tree RSS 是 5964.7 MiB,出现在 qwen3.6-plus1536 MiB payload。
  • 2048 MiB payload 时,出现了一个新的 extreme large-output failure。

2048 MiB 的结果:

  • pai/glm-5exit=1,stdout 为空,没有标准 OOM 文本。
  • qwen3.6-plusexit=1,stdout 为空,没有标准 OOM 文本。
  • DeepSeek/deepseek-v4-pro:出现 V8 fatal: Check failed: i::kMaxInt >= len,栈在 v8::String::NewFromOneByte / node::StringBytes::Encode / DecodeUTF8

这个新问题不是原 issue 中的传统 long-session heap OOM。它更像是 multi-GiB foreground stdout 被解码/构造成 JS string 时触发的 V8 字符串长度 限制或大输出处理问题。建议作为 large-output follow-up 跟踪,而不是把它当作 当前长会话 heap-pressure 修复失败。

Phase 1:真实长任务、多 Agent 并发压测

测试形态

每个模型 worker 都复用同一个 session,不断 --resume。每一轮要求 Qwen Code:

  • 进行只读代码审查和代码搜索;
  • 在同一轮中并发启动至少 4 个 agent tool call;
  • 重点检查 chat history、compaction、subagent runtime、non-interactive streaming、provider adapters 等 memory 相关区域;
  • 保留足够详细的最终回答,让 session history 自然增长。

runner 每秒采样 process-tree RSS,没有设置任何额外 heap cap。

这部分在观察到内存比较稳定后用 SIGTERM 主动停止,以便切换到第二阶段的 amplified stdout 压测。因此表里的 SIGTERM 不是 OOM。

汇总结果

ModelWorker turnsTotal tokensAgent callsTool callsPeak tree RSSPeak root RSSLast exitOOM
pai/glm-59444,614,70436362874.7 MiB217.4 MiBSIGTERMno
qwen3.6-plus7101,425,92717346862.7 MiB219.1 MiBSIGTERMno
DeepSeek/deepseek-v4-pro7173,053,48724148864.5 MiB213.8 MiBSIGTERMno
Total / max23719,094,11877856874.7 MiB219.1 MiB-no

分轮结果

ModelTurnExitTimed outOOMPeak tree RSSPeak root RSSTotal tokensAgent callsTool calls
DeepSeek/deepseek-v4-pro10nono709.1 MiB167.3 MiB5,565,147437
DeepSeek/deepseek-v4-pro20nono674.5 MiB118.8 MiB13,989,721429
DeepSeek/deepseek-v4-pro30nono734.1 MiB148.0 MiB22,621,542424
DeepSeek/deepseek-v4-pro40nono771.1 MiB107.5 MiB33,470,249422
DeepSeek/deepseek-v4-pro50nono864.5 MiB212.9 MiB43,540,313419
DeepSeek/deepseek-v4-pro60nono807.6 MiB167.9 MiB53,866,515417
DeepSeek/deepseek-v4-pro7SIGTERMnono785.1 MiB213.8 MiBn/an/an/a
pai/glm-51SIGTERMyesno742.8 MiB170.5 MiB17,071,5194142
pai/glm-520nono874.7 MiB217.4 MiB27,438,727460
pai/glm-530nono699.7 MiB102.1 MiB35,627,222438
pai/glm-540nono796.0 MiB194.0 MiB44,130,101423
pai/glm-550nono743.4 MiB152.1 MiB50,465,979426
pai/glm-560nono714.9 MiB125.2 MiB56,357,372418
pai/glm-570nono694.5 MiB96.6 MiB64,047,037420
pai/glm-580nono756.0 MiB136.8 MiB71,891,505415
pai/glm-59SIGTERMnono755.7 MiB157.3 MiB77,585,242420
qwen3.6-plus10nono735.1 MiB153.1 MiB3,890,508483
qwen3.6-plus20nono702.4 MiB142.5 MiB4,300,18619
qwen3.6-plus30nono862.7 MiB219.1 MiB8,635,953488
qwen3.6-plus4SIGTERMyesno685.8 MiB106.5 MiBn/an/an/a
qwen3.6-plus50nono610.5 MiB93.1 MiB40,191,337487
qwen3.6-plus60nono723.6 MiB121.9 MiB44,407,943479
qwen3.6-plus7SIGTERMnono810.4 MiB116.0 MiBn/an/an/a

Phase 1 解读

这是本轮里最能说明原始 long-session OOM 已明显改善的数据。

这组测试比 5 月 18 日的小 PR review / code navigation 更重:它包含更多 --resume、更多 subagent activity、更大的 reported token 量和更多 tool call。 但 process-tree RSS 始终低于 0.9 GiB,也没有出现传统 V8 heap OOM。

这不能证明所有用户 OOM 都不可能再发生,但至少说明当前构建在默认 heap 下, 已经无法轻易复现 issue 中那类长会话 heap-pressure OOM。

Phase 2:Amplified Foreground Stdout 压测

测试形态

第二阶段故意放大 shell-output 路径压力。每个模型、每个 payload size 都要求 parent session 和并发 agents 运行前台 shell 命令,输出大量 x 到 stdout:

bash
node -e "const chunk='x'.repeat(1024*1024); for (let i=0; i<N; i++) process.stdout.write(chunk)"

Payload size:

  • 128 MiB
  • 256 MiB
  • 512 MiB
  • 1024 MiB
  • 1536 MiB
  • 2048 MiB

这是 synthetic stress,不是正常代码审查工作负载。它的作用是验证极大前台 stdout 是否还会把 Qwen Code 推入危险内存路径。

Payload 结果

ModelPayloadExitTimed outOOM detectorPeak tree RSSPeak root RSSLargest process RSSAgent callsTotal tokensFatal text
DeepSeek/deepseek-v4-pro128 MiB0nono1503.9 MiB20.4 MiB1379.7 MiB102,142,198no
DeepSeek/deepseek-v4-pro256 MiB0nono2635.3 MiB41.1 MiB2467.0 MiB94,430,876no
DeepSeek/deepseek-v4-pro512 MiB0nono4103.5 MiB39.0 MiB3941.0 MiB76,862,342no
DeepSeek/deepseek-v4-pro1024 MiB0nono5638.8 MiB19.2 MiB5541.0 MiB1512,771,536no
DeepSeek/deepseek-v4-pro1536 MiB0nono4281.6 MiB109.3 MiB3936.0 MiB514,471,839no
DeepSeek/deepseek-v4-pro2048 MiBSIGTERMnono4660.4 MiB41.1 MiB4527.6 MiBn/a0yes
pai/glm-5128 MiB0nono1160.1 MiB41.7 MiB1026.6 MiB4443,778no
pai/glm-5256 MiB0nono1709.4 MiB24.9 MiB1573.2 MiB4856,902no
pai/glm-5512 MiB0nono2528.1 MiB38.8 MiB2351.7 MiB41,285,019no
pai/glm-51024 MiB0nono4477.6 MiB41.1 MiB4343.1 MiB41,727,941no
pai/glm-51536 MiB0nono5941.4 MiB41.1 MiB5808.5 MiB42,185,419no
pai/glm-52048 MiB1nono4634.6 MiB49.6 MiB4493.1 MiBn/a0no
qwen3.6-plus128 MiB0nono977.0 MiB93.0 MiB638.4 MiB4796,217no
qwen3.6-plus256 MiB0nono1508.2 MiB25.0 MiB1425.3 MiB41,601,828no
qwen3.6-plus512 MiB0nono2338.2 MiB32.7 MiB2232.3 MiB42,571,448no
qwen3.6-plus1024 MiB0nono4183.8 MiB40.8 MiB4001.0 MiB43,555,603no
qwen3.6-plus1536 MiB0nono5964.7 MiB41.1 MiB5831.7 MiB44,366,032no
qwen3.6-plus2048 MiB1nono4134.8 MiB41.1 MiB4001.4 MiBn/a0no

为什么 RSS 会超过 Node Heap Limit

本机 Node heap limit 是约 4144 MiB,但 process-tree RSS 最高达到约 5964.7 MiB。这不矛盾:

  • RSS 包含 V8 heap、external buffers、native allocations、loaded modules、 child processes,以及不计入 old-space heap 的内存。
  • amplified 阶段的最高峰通常出现在 child Node process,而不是 root wrapper。
  • 所以 root RSS 可以维持较低,同时 process-tree RSS 很高。

与历史报告的对比

5 月 18 日 maintainer benchmark 里,普通任务下 Qwen process-tree RSS 峰值大多在 942.5 MiB1006.6 MiB。这些数据主要说明:当时 Qwen Code 的任务期 footprint 明显高于 Claude Code。

5 月 19 日 runtime diagnostics report 进一步拆出了两条线:

  • startup/config RSS:常由正常配置和 MCP child process 推高;
  • long-session heap OOM:更偏向 history / compaction / clone pressure。

本轮 5 月 21 日数据进一步支持这个拆分:

  • 真实长任务、多 agent、长 session 场景稳定在 0.9 GiB 以下,没有 hit heap OOM;
  • amplified stdout 可以把 process-tree RSS 推到 5.96 GiB,并在 2048 MiB 触发另一类 V8 string-length fatal。

因此,与原 issue 里的传统 long-session heap OOM 相比,当前构建明显更稳。但这不 等于 Qwen Code 已经没有任何 large-output memory 风险。

新发现:极端 Foreground Stdout Fatal Path

本轮新发现的问题不是原来的 heap OOM,而是 multi-GiB foreground stdout 下的 string/decoding fatal:

text
Fatal error in , line 0
Check failed: i::kMaxInt >= len.
...
v8::String::NewFromOneByte
node::StringBytes::Encode
node::encoding_binding::BindingData::DecodeUTF8

触发条件:

  • Model:DeepSeek/deepseek-v4-pro
  • Payload:2048 MiB
  • Peak tree RSS:4660.4 MiB
  • Largest process RSS:4527.6 MiB
  • runner 记录 exit:SIGTERM,因为 fatal 输出已经捕获后,剩余子进程仍在高 CPU 空转,被手动终止。

pai/glm-5qwen3.6-plus2048 MiB 也失败,表现为 stdout 为空、 exit code 1,但 stderr 没有捕获到 V8 fatal stack。

严重程度

这是一个真实的 robustness 问题,但触发条件是 multi-GiB foreground stdout, 不是正常代码审查任务。它也不能证明当前 long-session heap-pressure 修复失败。

是否是本 PR 引入?

本轮没有证据表明 2048 MiB stdout failure 是当前 memory PR 引入的回归。

原因:

  • 失败路径是 foreground shell stdout decode / string construction。
  • 原 issue 路径是 long-session history、compaction、clone pressure。
  • 本轮没有做同 payload 的 pre-PR baseline,因此不能归因成 regression。
  • 该 failure 只在刻意极端的 2048 MiB payload 出现;128 MiB1536 MiB 都能完成。

建议把它作为 dedicated large-output follow-up:更早 stream / spool / hard-cap foreground shell output,避免在内存里构造 multi-GiB JS string。除非当前 PR 的目标 明确包含“任意 multi-GiB 前台 stdout 都必须可处理”,否则不建议把它作为当前 PR 的 blocker。

结论

  1. 最新本地 0.15.11 构建在 issue 报告的 long-session heap OOM 方向上明显更好。 基于当前默认 heap 压测结果,可以认为本 PR 已经基本解决此前遇到的 long-session heap OOM 复现路径。

  2. 在默认 Node heap 下,真实长任务 + 多 agent review loop 没有在 pai/glm-5qwen3.6-plusDeepSeek/deepseek-v4-pro 三个模型上复现传统 V8 heap OOM。

  3. synthetic foreground stdout 压测仍能把 process-tree RSS 推得很高。当前构建在 三模型上都撑过了 1536 MiB payload,最高成功 tree RSS 是 5964.7 MiB

  4. 仍然存在一个独立的极端 large-output 问题:2048 MiB stdout 附近,Qwen Code 可能在输出 JSON 结果前失败;DeepSeek case 捕获到了 V8 string-length fatal。

  5. 这个新发现重要,但更像是后续 large-output robustness 问题,不应直接作为 long-session heap-pressure mitigation 的 blocker。

建议发到 PR 的评论摘要

建议 PR 评论里只放精简摘要,完整数据放本文档:

markdown
I reran default-heap stress tests on the latest local build with
`pai/glm-5`, `qwen3.6-plus`, and `DeepSeek/deepseek-v4-pro`.

No `NODE_OPTIONS`, `--max-old-space-size`, or runner `ulimit` was used. The
local Node heap limit was about 4144 MiB.

Results:

- Realistic long-session + multi-agent review loop: 23 worker turns,
  ~719M reported total tokens, 77 agent calls, 856 total tool calls.
  No traditional V8 heap OOM was reproduced. Peak process-tree RSS was
  874.7 MiB; peak root RSS was 219.1 MiB.
- Amplified stdout stress: 18 payload attempts across 128 MiB -> 2048 MiB.
  All three models completed through 1536 MiB payloads without traditional
  heap OOM. Highest successful process-tree RSS was 5964.7 MiB.
- At 2048 MiB foreground stdout, an extreme large-output failure remains.
  DeepSeek captured a V8 fatal `Check failed: i::kMaxInt >= len` stack in
  `String::NewFromOneByte` / `StringBytes::Encode` / `DecodeUTF8`.

Conclusion: this PR appears to have effectively addressed the previously
observed long-session heap OOM reproduction path under default heap. The
2048 MiB stdout failure is a separate large-output/string-limit robustness issue
and should be tracked as a follow-up rather than treated as the same
long-session heap OOM regression.