docs/e2e-tests/2026-05-21-qwen-0.15.11-default-heap-oom-stress-report.md
日期:2026-05-21
本报告记录了针对 Qwen Code 0.15.11 最新本地构建的一轮默认 heap 压测。
这轮测试的目标是验证:在不人为降低内存上限的情况下,当前代码是否还能复现
issue 中提到的长会话 OOM,以及在更极端的大输出场景下还有没有新的风险。
本轮覆盖三个模型:
pai/glm-5qwen3.6-plusDeepSeek/deepseek-v4-pro测试分为两部分:
| 项目 | 值 |
|---|---|
| 分支 | codex/memory-investigation-draft-pr |
| Commit | c161e0aa4 |
| CLI | 本地 dist/cli.js |
| CLI 版本 | 0.15.11 |
| Node 默认 heap limit | 4144 MiB |
NODE_OPTIONS | 未设置 |
显式 --max-old-space-size | 未设置 |
runner ulimit | runner 未设置 |
| 配置模式 | 临时复制 ~/.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 并发测试一共执行了:
719,094,118 reported total tokens这部分没有复现任何传统 V8 heap OOM 特征:
JavaScript heap out of memoryReached heap limitIneffective mark-compacts near heap limitAllocation failed真实长任务阶段最高 process-tree RSS 为 874.7 MiB,最高 root-process RSS 为
219.1 MiB。这说明在默认 heap 下,当前代码没有轻易复现原 issue 中那种长任务
跑挂的 heap OOM。
第二阶段 amplified stdout 压测更激进。它一共执行了 18 个 payload attempt,
覆盖三个模型和 128 MiB 到 2048 MiB 的 foreground stdout payload。
结果是:
1536 MiB payload。5964.7 MiB,出现在 qwen3.6-plus
的 1536 MiB payload。2048 MiB payload 时,出现了一个新的 extreme large-output failure。2048 MiB 的结果:
pai/glm-5:exit=1,stdout 为空,没有标准 OOM 文本。qwen3.6-plus:exit=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 修复失败。
每个模型 worker 都复用同一个 session,不断 --resume。每一轮要求 Qwen Code:
agent tool call;runner 每秒采样 process-tree RSS,没有设置任何额外 heap cap。
这部分在观察到内存比较稳定后用 SIGTERM 主动停止,以便切换到第二阶段的
amplified stdout 压测。因此表里的 SIGTERM 不是 OOM。
| Model | Worker turns | Total tokens | Agent calls | Tool calls | Peak tree RSS | Peak root RSS | Last exit | OOM |
|---|---|---|---|---|---|---|---|---|
pai/glm-5 | 9 | 444,614,704 | 36 | 362 | 874.7 MiB | 217.4 MiB | SIGTERM | no |
qwen3.6-plus | 7 | 101,425,927 | 17 | 346 | 862.7 MiB | 219.1 MiB | SIGTERM | no |
DeepSeek/deepseek-v4-pro | 7 | 173,053,487 | 24 | 148 | 864.5 MiB | 213.8 MiB | SIGTERM | no |
| Total / max | 23 | 719,094,118 | 77 | 856 | 874.7 MiB | 219.1 MiB | - | no |
| Model | Turn | Exit | Timed out | OOM | Peak tree RSS | Peak root RSS | Total tokens | Agent calls | Tool calls |
|---|---|---|---|---|---|---|---|---|---|
DeepSeek/deepseek-v4-pro | 1 | 0 | no | no | 709.1 MiB | 167.3 MiB | 5,565,147 | 4 | 37 |
DeepSeek/deepseek-v4-pro | 2 | 0 | no | no | 674.5 MiB | 118.8 MiB | 13,989,721 | 4 | 29 |
DeepSeek/deepseek-v4-pro | 3 | 0 | no | no | 734.1 MiB | 148.0 MiB | 22,621,542 | 4 | 24 |
DeepSeek/deepseek-v4-pro | 4 | 0 | no | no | 771.1 MiB | 107.5 MiB | 33,470,249 | 4 | 22 |
DeepSeek/deepseek-v4-pro | 5 | 0 | no | no | 864.5 MiB | 212.9 MiB | 43,540,313 | 4 | 19 |
DeepSeek/deepseek-v4-pro | 6 | 0 | no | no | 807.6 MiB | 167.9 MiB | 53,866,515 | 4 | 17 |
DeepSeek/deepseek-v4-pro | 7 | SIGTERM | no | no | 785.1 MiB | 213.8 MiB | n/a | n/a | n/a |
pai/glm-5 | 1 | SIGTERM | yes | no | 742.8 MiB | 170.5 MiB | 17,071,519 | 4 | 142 |
pai/glm-5 | 2 | 0 | no | no | 874.7 MiB | 217.4 MiB | 27,438,727 | 4 | 60 |
pai/glm-5 | 3 | 0 | no | no | 699.7 MiB | 102.1 MiB | 35,627,222 | 4 | 38 |
pai/glm-5 | 4 | 0 | no | no | 796.0 MiB | 194.0 MiB | 44,130,101 | 4 | 23 |
pai/glm-5 | 5 | 0 | no | no | 743.4 MiB | 152.1 MiB | 50,465,979 | 4 | 26 |
pai/glm-5 | 6 | 0 | no | no | 714.9 MiB | 125.2 MiB | 56,357,372 | 4 | 18 |
pai/glm-5 | 7 | 0 | no | no | 694.5 MiB | 96.6 MiB | 64,047,037 | 4 | 20 |
pai/glm-5 | 8 | 0 | no | no | 756.0 MiB | 136.8 MiB | 71,891,505 | 4 | 15 |
pai/glm-5 | 9 | SIGTERM | no | no | 755.7 MiB | 157.3 MiB | 77,585,242 | 4 | 20 |
qwen3.6-plus | 1 | 0 | no | no | 735.1 MiB | 153.1 MiB | 3,890,508 | 4 | 83 |
qwen3.6-plus | 2 | 0 | no | no | 702.4 MiB | 142.5 MiB | 4,300,186 | 1 | 9 |
qwen3.6-plus | 3 | 0 | no | no | 862.7 MiB | 219.1 MiB | 8,635,953 | 4 | 88 |
qwen3.6-plus | 4 | SIGTERM | yes | no | 685.8 MiB | 106.5 MiB | n/a | n/a | n/a |
qwen3.6-plus | 5 | 0 | no | no | 610.5 MiB | 93.1 MiB | 40,191,337 | 4 | 87 |
qwen3.6-plus | 6 | 0 | no | no | 723.6 MiB | 121.9 MiB | 44,407,943 | 4 | 79 |
qwen3.6-plus | 7 | SIGTERM | no | no | 810.4 MiB | 116.0 MiB | n/a | n/a | n/a |
这是本轮里最能说明原始 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。
第二阶段故意放大 shell-output 路径压力。每个模型、每个 payload size 都要求
parent session 和并发 agents 运行前台 shell 命令,输出大量 x 到 stdout:
node -e "const chunk='x'.repeat(1024*1024); for (let i=0; i<N; i++) process.stdout.write(chunk)"
Payload size:
128 MiB256 MiB512 MiB1024 MiB1536 MiB2048 MiB这是 synthetic stress,不是正常代码审查工作负载。它的作用是验证极大前台 stdout 是否还会把 Qwen Code 推入危险内存路径。
| Model | Payload | Exit | Timed out | OOM detector | Peak tree RSS | Peak root RSS | Largest process RSS | Agent calls | Total tokens | Fatal text |
|---|---|---|---|---|---|---|---|---|---|---|
DeepSeek/deepseek-v4-pro | 128 MiB | 0 | no | no | 1503.9 MiB | 20.4 MiB | 1379.7 MiB | 10 | 2,142,198 | no |
DeepSeek/deepseek-v4-pro | 256 MiB | 0 | no | no | 2635.3 MiB | 41.1 MiB | 2467.0 MiB | 9 | 4,430,876 | no |
DeepSeek/deepseek-v4-pro | 512 MiB | 0 | no | no | 4103.5 MiB | 39.0 MiB | 3941.0 MiB | 7 | 6,862,342 | no |
DeepSeek/deepseek-v4-pro | 1024 MiB | 0 | no | no | 5638.8 MiB | 19.2 MiB | 5541.0 MiB | 15 | 12,771,536 | no |
DeepSeek/deepseek-v4-pro | 1536 MiB | 0 | no | no | 4281.6 MiB | 109.3 MiB | 3936.0 MiB | 5 | 14,471,839 | no |
DeepSeek/deepseek-v4-pro | 2048 MiB | SIGTERM | no | no | 4660.4 MiB | 41.1 MiB | 4527.6 MiB | n/a | 0 | yes |
pai/glm-5 | 128 MiB | 0 | no | no | 1160.1 MiB | 41.7 MiB | 1026.6 MiB | 4 | 443,778 | no |
pai/glm-5 | 256 MiB | 0 | no | no | 1709.4 MiB | 24.9 MiB | 1573.2 MiB | 4 | 856,902 | no |
pai/glm-5 | 512 MiB | 0 | no | no | 2528.1 MiB | 38.8 MiB | 2351.7 MiB | 4 | 1,285,019 | no |
pai/glm-5 | 1024 MiB | 0 | no | no | 4477.6 MiB | 41.1 MiB | 4343.1 MiB | 4 | 1,727,941 | no |
pai/glm-5 | 1536 MiB | 0 | no | no | 5941.4 MiB | 41.1 MiB | 5808.5 MiB | 4 | 2,185,419 | no |
pai/glm-5 | 2048 MiB | 1 | no | no | 4634.6 MiB | 49.6 MiB | 4493.1 MiB | n/a | 0 | no |
qwen3.6-plus | 128 MiB | 0 | no | no | 977.0 MiB | 93.0 MiB | 638.4 MiB | 4 | 796,217 | no |
qwen3.6-plus | 256 MiB | 0 | no | no | 1508.2 MiB | 25.0 MiB | 1425.3 MiB | 4 | 1,601,828 | no |
qwen3.6-plus | 512 MiB | 0 | no | no | 2338.2 MiB | 32.7 MiB | 2232.3 MiB | 4 | 2,571,448 | no |
qwen3.6-plus | 1024 MiB | 0 | no | no | 4183.8 MiB | 40.8 MiB | 4001.0 MiB | 4 | 3,555,603 | no |
qwen3.6-plus | 1536 MiB | 0 | no | no | 5964.7 MiB | 41.1 MiB | 5831.7 MiB | 4 | 4,366,032 | no |
qwen3.6-plus | 2048 MiB | 1 | no | no | 4134.8 MiB | 41.1 MiB | 4001.4 MiB | n/a | 0 | no |
本机 Node heap limit 是约 4144 MiB,但 process-tree RSS 最高达到约
5964.7 MiB。这不矛盾:
5 月 18 日 maintainer benchmark 里,普通任务下 Qwen process-tree RSS 峰值大多在
942.5 MiB 到 1006.6 MiB。这些数据主要说明:当时 Qwen Code 的任务期
footprint 明显高于 Claude Code。
5 月 19 日 runtime diagnostics report 进一步拆出了两条线:
本轮 5 月 21 日数据进一步支持这个拆分:
0.9 GiB 以下,没有 hit heap
OOM;5.96 GiB,并在 2048 MiB
触发另一类 V8 string-length fatal。因此,与原 issue 里的传统 long-session heap OOM 相比,当前构建明显更稳。但这不 等于 Qwen Code 已经没有任何 large-output memory 风险。
本轮新发现的问题不是原来的 heap OOM,而是 multi-GiB foreground stdout 下的 string/decoding fatal:
Fatal error in , line 0
Check failed: i::kMaxInt >= len.
...
v8::String::NewFromOneByte
node::StringBytes::Encode
node::encoding_binding::BindingData::DecodeUTF8
触发条件:
DeepSeek/deepseek-v4-pro2048 MiB4660.4 MiB4527.6 MiBSIGTERM,因为 fatal 输出已经捕获后,剩余子进程仍在高 CPU
空转,被手动终止。pai/glm-5 和 qwen3.6-plus 在 2048 MiB 也失败,表现为 stdout 为空、
exit code 1,但 stderr 没有捕获到 V8 fatal stack。
这是一个真实的 robustness 问题,但触发条件是 multi-GiB foreground stdout, 不是正常代码审查任务。它也不能证明当前 long-session heap-pressure 修复失败。
本轮没有证据表明 2048 MiB stdout failure 是当前 memory PR 引入的回归。
原因:
2048 MiB payload 出现;128 MiB 到
1536 MiB 都能完成。建议把它作为 dedicated large-output follow-up:更早 stream / spool / hard-cap foreground shell output,避免在内存里构造 multi-GiB JS string。除非当前 PR 的目标 明确包含“任意 multi-GiB 前台 stdout 都必须可处理”,否则不建议把它作为当前 PR 的 blocker。
最新本地 0.15.11 构建在 issue 报告的 long-session heap OOM 方向上明显更好。
基于当前默认 heap 压测结果,可以认为本 PR 已经基本解决此前遇到的
long-session heap OOM 复现路径。
在默认 Node heap 下,真实长任务 + 多 agent review loop 没有在
pai/glm-5、qwen3.6-plus、DeepSeek/deepseek-v4-pro 三个模型上复现传统
V8 heap OOM。
synthetic foreground stdout 压测仍能把 process-tree RSS 推得很高。当前构建在
三模型上都撑过了 1536 MiB payload,最高成功 tree RSS 是 5964.7 MiB。
仍然存在一个独立的极端 large-output 问题:2048 MiB stdout 附近,Qwen Code
可能在输出 JSON 结果前失败;DeepSeek case 捕获到了 V8 string-length fatal。
这个新发现重要,但更像是后续 large-output robustness 问题,不应直接作为 long-session heap-pressure mitigation 的 blocker。
建议 PR 评论里只放精简摘要,完整数据放本文档:
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.