尧图精选

qwen-code daemon 内存预算解析与观测(PR A):先给内存一个可信的分母

🕒 发布时间:2026/9/14 3:57:38 📁 来源:尧图网络
qwen-code daemon 内存预算解析与观测PR A先给内存一个可信的分母【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code本文围绕 qwen-code 的qwen servedaemon 内存预算解析与观测工作PR A对应实施计划docs/plans/2026-07-31-daemon-memory-budget-pr1.md与其设计文档展开讲解 daemon 如何把“可用内存”解析成一个唯一、可信的分母并通过limits.memory/runtime.memory如实上报为后续 child-capacity 策略提供设计依据。读完本文你将掌握--memory-budget-mb的完整算术模型、configured 与 effective 分离的原因、为什么不划分/不应用/不拒绝份额以及这一改动如何在“不改变任何子进程启动参数”的前提下落地并被端到端测试验证。背景daemon 的内存能力模型没有分母要理解这个 PR 的价值需要先看清 daemon 的现状。设计文档 2026-07-31-daemon-capacity-model-and-memory-bounds.md 给出了三个关键结论。daemon 不是单一进程在http-bridge模式下ServeModedaemon 会为每个 workspace runtime 预热一个qwen --acp子进程同一 runtime 内的多个 session 通过connection.newSession()复用该子进程daemon 根进程通过 HTTP/SSE 承载 ACP NDJSON 协议。因此多 workspace 稳态内存的大头在子进程聚合 RSS上而不是 daemon 根进程的 JS 堆。maxSessions文档中引用的“每 session 约 30–50 MB RSS”实际上消耗在子进程内部。这意味着对根进程堆做字节账本既观察不到、也约束不了、更拒绝不了子进程的内存。三个能力旋钮互不关联现有代码中决定 daemon 内存消耗的三个旋钮各自独立推导没有任何代码对它们做对账旋钮推导方式位置注册 workspace 数固定常量25packages/acp-bridge/src/channel-control-timeouts.ts总 session 数maxSessionsPerWorkspace × workspaceCountpackages/cli/src/serve/run-qwen-serve.ts每子进程 V8 堆max(min(宿主内存的 50%, 16 GB), V8 默认值)packages/acp-bridge/src/spawnChannel.ts其中第三个旋钮最致命。spawnChannel.ts 的getAcpMemoryArgs()计算出一个目标值后缓存在模块级变量里应用到每一个被派生的子进程——它是宿主的“分数”而不是任何东西的“份额”。而且其中的 raise-only 守卫targetMB currentLimitMB才输出--max-old-space-size导致当目标值小于派生方 daemon 自身heap_size_limit时 flag 被整体丢弃子进程静默继承 V8 默认值。实测在 3.4 GB 宿主上目标 1747 MB 低于 daemon 自身限制 1795 MBflag 被丢弃子进程实际上限是 1795 MB而 32 GB 宿主上默认约 4 GB、目标是 16384 MBflag 正常输出。其结果就是32 GB 宿主被允许 25 × 16 GB3.4 GB 宿主被允许 25 × ~1.8 GB——无论哪种情况都大约超卖 12 倍而这个守卫今天唯一的作用是“抬高上限”永远不会“压低上限”。能测内存却说不清“多不多”DaemonMetricsRing已经每 5 秒采样一次rssBytes、heapUsedBytes、cpuPercent、eventLoopLagP99Ms写入 180 桶的环形缓冲15 分钟历史并单飞轮询 primary ACP 子进程的 RSS30 秒陈旧悬崖GET /daemon/status可以全部返回。但 daemon 缺少一个可除的分母没有 cgroup 读取、没有heap_size_limit、没有比值、没有压力分级、没有内存派生的 issue code也没有任何limits.*内存字段。Core 包里的MemoryPressureMonitor什么都算但computeEffectiveMemoryLimit()是私有方法daemon 从不构造那个类。用设计文档的原话说“daemon 能说出自己用了多少字节却说不清这是多还是少。”本 PR 的目标与全局约束实施计划 2026-07-31-daemon-memory-budget-pr1.md 把目标收敛得非常克制把 daemon 的内存分母解析出来并如实上报不改变任何子进程的启动参数。目的是让后续的 child-capacity 策略有一个可设计的依据而不是自己再发明一个分母。围绕这个目标文档明确列出了六条全局约束它们是这个 PR 的灵魂不划分、不应用、不拒绝。见设计文档“Why the share is not applied”按注册数划分在 32 GB / 25 注册 / 仅 primary 存活时会把那个子进程从 16384 MB 砍到 614 MB26.7 倍而且因为有 512 MB 下限8 GB 宿主上 25 个子进程仍会授权 12800 MB 对 3687 MB 的池子。代价大且拿不到聚合上界。configured 与 effective 必须分离。effective 封顶在解析出的 cgroup/宿主内存。派生预算低于最小值时不向上钳位——早先版本曾这么做768 MB 宿主报出 1024 MB 预算会污染观测层的一切比值。改报insufficientMemory。--max-old-space-size不是 RSS 边界不含 Buffer / native / young gen / channel worker / MCP 子孙。任何基于它的策略都是子进程堆策略。应用份额是兼容性变更即使不拒绝任何请求——它改变子进程的 GC 与 OOM 行为不能当作 reporting only 交付。不加 capability tagdaemon_status已是 baseline新增可选字段不属于CONDITIONAL_SERVE_FEATURES。不引入 core 依赖computeEffectiveMemoryLimit()是MemoryPressureMonitor的 private 方法抽取会触发 maintainer 门禁。整体架构与文件结构架构上改动呈“一条链路、零处消费”的形态acp-bridge 新增纯算术模块daemon-memory-budget.ts无 I/O宿主内存可注入产出DaemonMemoryBudgetrunQwenServe在解析完 workspace 输入后调用一次挂到opts.daemonMemoryBudget仅供状态面读取daemon-status把静态事实放limits.memory、动态计数放runtime.memoryspawnChannel.ts完全不动。计划文档给出的文件结构路径已转换为仓库根相对路径包文件改动acp-bridgesrc/daemon-memory-budget.ts新增DaemonMemoryBudget、resolver、legacyChildCeilingMb、recommendedChildShareMbacp-bridgesrc/daemon-memory-budget.test.ts新增算术表、effective 封顶、insufficientMemory、建议份额上界acp-bridgepackage.json./daemonMemoryBudgetsubpath exportacp-bridgesrc/spawnChannel.ts不改动clisrc/commands/serve.ts--memory-budget-mb声明 范围校验 透传clisrc/serve/fast-path.tsNUMBER_OPTIONS一行与 yargs 的 parity 有契约测试强制clisrc/serve/types.tsmemoryBudgetMb/daemonMemoryBudget修订maxSessions文档段clisrc/serve/run-qwen-serve.tsboot 解析 面包屑bootstrap 占位状态补memory: nullclisrc/serve/daemon-status.tslimits.memory静态事实 runtime.memory活跃计数与建议份额cli三个*.test.ts预算上报、live 计数、flag 解析、boot 校验sdksrc/daemon/types.tsDaemonStatusReport.limits.memory?docsdaemon 17/20、qwen-serve.md、protocolflag 行 limits.memory/runtime.memory字段说明预算算术一个分母一次解析预算的全部算术在 daemon-memory-budget.ts 的resolveDaemonMemoryBudget()中一次完成。计划文档给出的核心公式availableMemoryMb cgroup limit, else os.totalmem() 封顶在宿主总量 configuredBudgetMb --memory-budget-mb ?? floor(availableMemoryMb * 0.5) effectiveBudgetMb min(configuredBudgetMb, availableMemoryMb) rootReserveMb min(clamp(floor(effectiveBudgetMb * 0.1), 256, 1024), effectiveBudgetMb) childPoolMb effectiveBudgetMb - rootReserveMb legacyChildCeilingMb min(floor(availableMemoryMb * 0.5), 16384) insufficientMemory effectiveBudgetMb 1024各常量的源码定义daemon-memory-budget.ts顶部DEFAULT_MEMORY_BUDGET_FRACTION 0.5未传 flag 时预算取可用内存的 50%MIN_MEMORY_BUDGET_MB 1024/MAX_MEMORY_BUDGET_MB 1048576--memory-budget-mb的合法范围MIN_CHILD_HEAP_MB 512/MAX_CHILD_HEAP_MB 16384每子进程份额的钳位上下界ROOT_RESERVE_FRACTION 0.1MIN_ROOT_RESERVE_MB 256MAX_ROOT_RESERVE_MB 1024根进程保留额LEGACY_CHILD_HEAP_FRACTION 0.5与spawnChannel.ts中硬编码的 0.5 镜像对应注释明确要求同步更新。可用内存探测cgroup 优先、哨兵钳位detectAvailableMemoryMb() 优先读取process.constrainedMemory()libuv 已封装 cgroup v1/v2并将其钳位到宿主总量——因为 cgroup v1/v2 可能把“无限制”报成巨大哨兵值而非“无限制”。只有当受限值大于 0 且小于宿主总量时才认定是constrained来源否则回退host来源。这个availableMemorySource字段非常重要在 cgroup 下它是 OOM killer 真正执行的限制比值才有意义在裸机上它只是整机内存rssRatio只能当作压力下界来读。configured 与 effective为什么必须分开两个值在两个方向上都会分叉合并它们会得到一个机器无法兑现的分母显式预算大于宿主被向下封顶。例如 2 GB 宿主机上显式传1048576configuredBudgetMb保持 flag 值effectiveBudgetMb变成 2048。派生预算低于最小值不向上钳位。768 MB 宿主会得到configured effective 384并置insufficientMemory: true——早先版本把它钳到 1024 上报等于发明了一个宿主无法支撑的分母会污染观测层的一切比值。过小的宿主是一个观测事实insufficientMemory不是凭空造内存的许可。测试 daemon-memory-budget.test.ts 中专门有一条“never reports a budget the host cannot back”遍历 256 到 32768 MB 的宿主尺寸断言effectiveBudgetMb availableMemoryMb并断言小宿主上insufficientMemory为真且不向上钳位。recommendedChildShareMb只上报不应用recommendedChildShareMb(budget, n) 计算“如果池子按 n 份划分每份是多少”min(clamp(floor(childPoolMb / n), 512, 16384), legacyChildCeilingMb)。注释明确说明它是被上报、从不被应用的并且存在两个语义陷阱最终Math.min(share, legacyChildCeilingMb)会让小宿主上的建议份额跌破 512 MB 下限测试can sit below the per-child floor when the legacy ceiling is lower锁定了这一行为它存在的意义就是暴露registered 与 live 之间的差距同一份 32768 MB 预算按 25 个注册数除得到 614 MB按 1 个活跃子进程除得到 15360 MB测试exposes the registered-vs-live gap精确断言了这两个数字。为什么“划分、应用”份额行不通计划文档和设计文档花了大量篇幅论证这一点这是整个 PR“只观测不干预”的决定性理由注册不等于分配。workspace runtime 惰性派生子进程channelIdleTimeoutMs默认为 0“立即杀通道”休眠的 secondary 根本没有子进程只有预热的 primary 是例外。按注册数除有真实代价且买不到任何东西。32 GB 宿主、25 个注册、仅 primary 存活时按注册数划分会把该子进程从 16384 MB 砍到 614 MB——26.7 倍的削减来自 24 个不占内存的注册同时每子进程 512 MB 下限让划分后的份额总和仍然超过池子8 GB 宿主 25 个子进程授权 12800 MB 对 3687 MB 池。动态注册没有稳定的计数。boot 时计数漏掉后来的 workspace重算无法收缩运行中子进程的 V8 堆当前注册数惩罚休眠 workspace。按 live 子进程数除得到的上限又依赖派生顺序依然没有聚合上界。应用份额是兼容性变更即使不拒绝任何请求它也会改变子进程的 GC 与 OOM 行为不能当作 reporting only 交付。真正的控制手段是派生时刻的准入以并发存活子进程数为键并明确“下一个子进程超出池子时怎么办”——这需要本 PR 观测数据支撑所以被推迟而不是靠猜。CLI 接入与 boot 解析flag 声明与范围校验serve.ts 中--memory-budget-mb声明为 number 类型描述文本完整说明了语义daemon 进程树的总内存预算MB未设置时按 cgroup 受限内存或宿主内存的 50% 派生两种情况都封顶在解析出的可用内存不改变任何qwen --acp子进程的规格如今唯一的消费者是自适应 live-journal 增长——有效预算 5% 的 daemon 级池上限 1024 MB有效预算低于 1024 MB 最小预算时为 0、增长禁用在 daemon status 的limits.memory下上报旁边还有建模的每子进程分区limits.memory.childHeap必须是[1024, 1048576]内的整数。校验逻辑在 daemon-memory-budget.tsisValidMemoryBudgetMb要求安全整数且落在[1024, 1048576]normalizeMemoryBudgetMb对越界值抛TypeError。测试覆盖了0, -1, 1.5, NaN, 1023, MAX1全部非法形态。boot 时run-qwen-serve.ts在 workspace 上限校验之后一旦越界即整体拒绝启动。fast-path parityfast-path.ts 的NUMBER_OPTIONS增加一行[memoryBudgetMb, memory-budget-mb]映射并在 L241-L242 做同样的isValidMemoryBudgetMb校验。计划文档特别强调这一行与 yargs 的 parity 有契约测试强制防止两条解析路径漂移。boot 解析与 stderr 面包屑run-qwen-serve.ts#L3815-L3854 是接缝位置显式 workspace 上限校验之后、构造 bridge 之前调用一次resolveDaemonMemoryBudget({ budgetMb: opts.memoryBudgetMb })挂到opts.daemonMemoryBudget随后当budgetSource flag或insufficientMemory时向 stderr 写一条面包屑formatMemoryBudgetStderr 负责格式化派生预算在足够大的宿主上静默避免噪音派生live-journal 自适应增长池serveJournalGrowthPoolMb在算子显式钉住--max-journal-events/--max-journal-bytes时返回 0显式配置优先否则取journalGrowthPoolMb——有效预算的 5%、上限 1024 MB、不得大于 root reserve 之后的余量、insufficientMemory时直接禁用返回 0。这是 PR A 交付时预算的唯一运行时消费者一场 turn 扇出大量并发 subagent 时session 的 journal 上限可以在 daemon 级共享池内按需增长每 session 硬上限 256 MiB保留两份 journal 的堆开销约为增长额的 2 倍而不是静默截断 live replay 窗口。契约测试run-qwen-serve.test.ts 中对应有三类 boot 级测试rejects a budget below the documented minimummemoryBudgetMb: 512时runQwenServe整体 rejectwrites a stderr line when the budget comes from the flag真实启动后断言 stderr 包含memory budgetwrites no stderr line for a derived budget on a sufficient host钉住 32 GB 宿主后断言不打印防止守卫变成无条件噪音derives an adaptive journal growth pool into every bridge16 GiB 宿主下用 4096 MB flag 与派生 8192 MB 的差异证明 daemon 确实消费了 flag。状态面输出limits.memory 与 runtime.memorydaemon-status.ts 中limits.memory承载静态事实DaemonMemoryBudget的全部字段configuredBudgetMb、effectiveBudgetMb、budgetSource、availableMemoryMb、availableMemorySource、rootReserveMb、childPoolMb、legacyChildCeilingMb、insufficientMemory以及enforced: false——这个必需字段用来在线上区分“功能存在”与“子进程真的被预算限定”runtime.memory承载动态计数activeAcpChildrenchannel存活非 dying的受管 ACP 子进程数。实现上刻意用isChannelLive()谓词而非 active-state 的list()因为list()会漏掉替换中或被阻塞的 workspace——恰好在准入策略绝不能当作空闲容量的窗口里少报子进程registeredWorkspaces注册数含 draining/transitioning/blocked明确标注不是 live 子进程数两个建议份额recommendedChildShareMb(budget, registeredWorkspaceCount)与recommendedChildShareMb(budget, activeAcpChildCount)——二者之间的差距就是上报的意义聚合 child RSS 与sampled计数与activeAcpChildCount同一次同步遍历中求和await一旦插入两者就会描述不同瞬间静默吞掉来去之间的子进程——这正是sampled字段存在的理由源码注释对此有专门警告。SDK 镜像在 sdk-typescript/src/daemon/types.tslimits.memory与runtime.memory均为可选、增量字段旧 daemon 对新客户端、新 daemon 对旧客户端都可解析runtime.memory.children的文档还点明聚合 RSS 既是高估按进程求和重复计算共享页也是下界每个子进程只自报自身进程MCP 子孙与 channel worker 缺失并明确“不是 daemon 进程树的内存”。验证从纯算术单测到真实 daemon E2E计划文档的验证策略分为四层全部可以在当前仓库中找到对应实现1. 算术单测注入宿主内存纯算术无 I/O。daemon-memory-budget.test.ts 的resolveDaemonMemoryBudget算术表availableconfiguredeffectivereservepoollegacy ceiling3276816384163841024153601638416384819281928197373819281924096409640936874096349417471747256149117472048102410242567681024并断言effectiveBudgetMb availableMemoryMb两个方向都不虚报、rootReserveMb effectiveBudgetMb小宿主上保留额不得大于预算本身、建议份额在子进程数上单调不增、跨 768 MB 到 32 GB 宿主尺寸建议份额不超过legacyChildCeilingMb、cgroup 哨兵值被钳位detectAvailableMemoryMb的一组 mock 测试覆盖 cgroup 优先、v1 无限哨兵、无限制回退、cgroup 等于宿主视为无约束四种情况。2. 回归测试预算派生的上限即使低于派生方自身堆限制也必须输出。计划文档称这是“第一个改动中最重要的测试”。getAcpMemoryArgs()只在目标值超过派生方 daemon 自身heap_size_limit时才输出--max-old-space-sizespawnChannel.ts#L320-L332预算派生的份额通常低于该限制天真实现会静默丢弃 flag、让超卖回归。回归测试必须断言低于测试进程自身堆上限的值仍会输出 flag。3. 断言没有任何子进程启动参数被改变。既有 spawn 测试套件原样通过getAcpMemoryArgs本阶段未动。4. E2E真实启动 daemon 并 GET/daemon/status。run-qwen-serve.test.ts 中测试真实 boot daemon 并断言limits.memory非空且enforced必须为falseeffectiveBudgetMb availableMemoryMbmodeled.childPoolMb effectiveBudgetMb且非负runtime.memory.registeredWorkspaces、childRssCoverage active_children、sampled activeAcpChildren、sampled 0时rssBytes 0且无陈旧年龄压力块pressure.ratio max(rssRatio, heapRatio)。计划文档还记录了真实输出确认封顶的案例3.4 GB 机器上传--memory-budget-mb 4096effective 降到 3494 并打印说明。不在本 PR 的后续工作计划文档明确列出三项后续其中部分在当前仓库中已经落地后文以现状为准说明聚合 child RSS 与压力分级Part 2本 PR 只采样 primary 子进程覆盖全部 workspace 子进程与 channel worker 需要扩展 metrics sampler。→ 当前仓库中runtime.memory.pressurelevel/ratio/source 六个原始数值--memory-pressure-mode off|observe与runtime.memory.children聚合 RSS sampled已经落地见 19-observability.md子进程老年代峰值测量另见 2026-08-18-acp-child-peak-old-generation-measurement.md。child-capacity 策略按并发存活子进程数在 spawn 期准入并明确“下一个子进程超出池子时怎么办”。实现时注意spawnChannel.ts的 raise-only 判断会把偏小的份额整个丢掉回归测试必须断言低于测试进程自身堆上限的值仍会输出 flag。→ 当前仓库已出现--child-heap-mode off|observe与 child-heap-policy.tslimits.memory.childHeapmode、maxConcurrentChildren、perChildCeilingMb、refusals仍保持“建模但不应用”固定分区、准入封顶refusals只衡量准入压力而非上限充足性。聚合配额设计文档 Part 4对保留环、队列、缓存、并发大操作的按 workspace 与进程级计数器在数据结构实际变更处维护。演进记录与陷阱备忘计划文档的“备注”记录了这条设计路线被现实修正的两轮过程值得任何做资源管理的人引以为戒早先版本曾在 workspace 数超预算时 boot 失败并在注册处返回 409跑既有测试套件当场被推翻——注册不等于分配。随后改成“划分份额但不拒绝”在 #8051 上被进一步指出仍是同一个分母错误且划分本身就已是行为变更。当前形态是这两轮的结果只解析、只上报。spawnChannel.ts上一版曾加过可选maxOldSpaceSizeMB参数和 C1 回归测试本 PR 已整体回退——没有消费者的参数就是未接线的基础设施。这个陷阱记录在设计文档和 #8182 中。最后回到本 PR 的兼容性承诺设计文档「Compatibility」一节当前代码均可验证交互式 CLI、IDE companion、direct-embed bridge 路径不变仍派生单子进程并保留宿主派生的上限Part 1 不改变任何子进程启动参数唯一新增的 boot 行为是拒绝越界--memory-budget-mb与显式设置/宿主过小时的一条 stderr 面包屑workspace 注册、持久化恢复、POST /workspaces均不变limits.memory与runtime.memory在 SDK 镜像中可选、增量旧 daemon 对新客户端完全可解析。这份“观测先行、策略后置、分母可信”的克制正是它能够安全落地并被端到端测试锁定的原因。【免费下载链接】qwen-codeAn open-source AI coding agent that lives in your terminal.项目地址: https://gitcode.com/GitHub_Trending/qw/qwen-code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →