Repository navigation
Conversation
Review: PR #2252 — Trie keys()/release() 递归改迭代
Summary本次 PR 将
真实调用链核实(逐个 rg/读源码确认):
另注意:这是 trace 前缀存储(增量 tokenize 缓存)路径,与推理侧 KV cache affinity 分属不同路径,本 PR 与缓存复用命中率无关,不涉及实测缓存复用。 验证证据(作者提供,只读核实):
Main Flowchart before this PRgraph TD
subgraph SessionServer["SessionServer (xtuner/v1/rl/rollout/session_server.py)"]
SS_search["store.search.remote(session_id, prompt_text, filter_none=True)"]
SS_insert["store.insert.remote(session_id, prefix, TokenizedSegment)"]
end
subgraph AgentLoops["Agent Loops (export 消费方)"]
AL_sb["agent_in_sandbox_loop:337 export_training_trace.remote"]
AL_lh["agent_in_localhost_loop:258 export_training_trace.remote"]
end
subgraph RolloutTraceStoreActor["RolloutTraceStore (ray actor)"]
RTS_insert["insert(session_id, key, value) → Trie.insert"]
RTS_search["search(session_id, text) → Trie.search"]
RTS_keys["keys(session_id) → Trie.keys"]
RTS_release["release(session_id, key=None): pop session → Trie.release"]
RTS_release_sessions["release_sessions(session_ids) → release(session_id)"]
RTS_export["export_training_trace(session_id, prompt_text)"]
end
subgraph TrieKeys["Trie.keys() interface (before)"]
TK_collect["递归 _collect(node, path: List[str])<br/>path + [token]; ''.join(path) 输出<br/>深度 == 链深 → RecursionError"]
end
subgraph TrieRelease["Trie.release(key=None) interface (before)"]
TR_sub["递归 _free_subtree(node)<br/>先递归子节点 → _free_ray_refs(value) → children.clear()<br/>深度 == 链深 → RecursionError, 显式 free 中断"]
end
subgraph TrieReleasePartial["Trie.release(key) 部分释放 (before, 未改)"]
TP_locate["前缀定位 best_prefix 匹配"]
TP_prune["自底向上剪枝空值节点"]
end
subgraph CleanupPaths["清理调用方"]
RT["rl_trainer._release_trace_sessions / _release_all_trace_sessions"]
RB["replay_buffer → release_and_discard_rollout_groups"]
PU["produce_utils → release_and_discard_rollout_groups"]
end
subgraph TrieSearch["Trie.search() interface (未改)"]
T_search["迭代最长前缀查找"]
end
RTS_search --> T_search
TP_locate --> TP_prune
SS_search --> RTS_search
SS_insert --> RTS_insert
AL_sb --> RTS_export
AL_lh --> RTS_export
RTS_export -->|"trie.search(prompt_text, filter_none=True)"| T_search
RTS_export -->|"trie.keys() 仅 mismatch 诊断"| TK_collect
RT --> RTS_release_sessions
RB --> RTS_release_sessions
PU --> RTS_release_sessions
RTS_release_sessions --> RTS_release
RTS_release -->|"trie.release(None)"| TR_sub
RTS_release -->|"trie.release(key), 无现存调用方"| TP_locate
RTS_keys --> TK_collect
Main Flowchart after this PR修改点(高亮): graph TD
subgraph SessionServer["SessionServer (写入方, 未改)"]
SS_search["store.search.remote(...)"]
SS_insert["store.insert.remote(...)"]
end
subgraph AgentLoops["Agent Loops (export 消费方, 未改)"]
AL_sb["agent_in_sandbox_loop export_training_trace.remote"]
AL_lh["agent_in_localhost_loop export_training_trace.remote"]
end
subgraph RolloutTraceStoreActor["RolloutTraceStore (ray actor, 未改)"]
RTS_insert["insert → Trie.insert"]
RTS_search["search → Trie.search"]
RTS_keys["keys → Trie.keys"]
RTS_release["release(session_id, key=None): pop session → Trie.release"]
RTS_release_sessions["release_sessions → release"]
RTS_export["export_training_trace"]
end
subgraph TrieKeys["Trie.keys() interface (after)"]
TK_stack["显式栈迭代 DFS:<br/>stack: List[(TreeNode, str 前缀)]"]:::changed
TK_order["reversed(children.items()) 压栈<br/>弹出顺序 == 递归插入序前序"]:::changed
end
subgraph TrieRelease["Trie.release(key=None) interface (after)"]
TR_stack["两相栈 (node, visited: bool) 后序迭代"]:::changed
TR_order["visited=True 时: _free_ray_refs(value) → value=None → children.clear()<br/>顺序与递归版一致"]:::changed
end
subgraph TrieReleasePartial["Trie.release(key) 部分释放 (after, 未改)"]
TP_locate["前缀定位 best_prefix 匹配"]
TP_prune["release('ab') 仅清 ab 值并保留 abc 子节点;<br/>后续 release('abc') 再自底向上剪枝无值空节点"]
end
subgraph CleanupPaths["清理调用方 (未改)"]
RT["rl_trainer 释放路径"]
RB["replay_buffer / produce_utils 丢弃路径"]
end
subgraph TrieSearch["Trie.search() interface (未改)"]
T_search["迭代最长前缀查找"]
end
RTS_search --> T_search
TP_locate --> TP_prune
SS_search --> RTS_search
SS_insert --> RTS_insert
AL_sb --> RTS_export
AL_lh --> RTS_export
RTS_export --> T_search
RTS_export --> TK_stack
RT --> RTS_release_sessions
RB --> RTS_release_sessions
RTS_release_sessions --> RTS_release
RTS_release -->|"trie.release(None)"| TR_stack
RTS_release --> TP_locate
RTS_keys --> TK_stack
TK_stack --> TK_order
TR_stack --> TR_order
classDef changed fill:#ffe082,stroke:#e65100,stroke-width:2px;
文字说明(流程 ↔ 修改对应):
核心原理
核心实现与对应的单测
抽象与信息隐藏评估(按 improve-codebase-architecture 词汇,范围限定本 Trie 修复,不做无关候选重构)
新增功能守则非新增功能(行为等价修复),R1–R3 trivially 满足:既有类( 公开接口的线性业务流程评估
单测建议现有 2 个用例已覆盖:超递归上限深度、释放顺序精确断言、空 key(root value)、部分释放剪枝、重复 release 幂等。无强制补充项。 其他 issues本 PR diff 未发现待修问题。 特别澄清两点(防止误读):
VerdictAPPROVE — 本 PR 改动:Critical: 0 / Warning: 0 / Nit: 0。 代码正确:遍历顺序、释放顺序、剪枝语义均与旧实现等价并有测试锁定;栈帧字符串前缀优化正确且省一次 join;作者验证证据(8 passed、changed-file pre-commit、mypy/pydantic 沙箱外通过)与源码审阅结论一致。未运行 GPU 训练,远端 CI 结果以实际为准。 |
Delivery scope and retained independent reviewThe exact code head remains The author reports GLM-5.3-Flash compatibility in the integrated work. This is a model-independent Python trie traversal fix; the statement does not claim a new full-size GLM training run on this exact standalone branch or a KV-cache/performance benchmark. The retained exact-branch evidence remains the deep-chain failure reproduction, CPU tests and scope recorded in the original review. The new Agent-lifecycle core #2297 has an overlapping iterative traversal implementation and different request-aware shared-reference cleanup. Review the overlap before landing both; do not overwrite the entire trace-store file or force-free shared R3 references. Original #2257 is unchanged. |
Root Cause
Long multi-turn traces can create a trie deeper than Python's recursion limit. Both
Trie.keys()and full-treeTrie.release()recursively traverse this tree, so exporting or cleaning up a long trace can raiseRecursionErrorand interrupt training.Fix
Use explicit stacks for key enumeration and full-tree cleanup. Preserve insertion-order DFS for keys and child-before-parent cleanup order. Single-key release, session identifiers, inference routing, and KV-cache configuration are unchanged.
Test Plan
RecursionErrorin both methods on the base revision; both complete with this fix.tests/rl/test_trace_store.py: 8 passed, no skips, including a local CPU Ray actor.Final Review
Full final review report, including before/after flowcharts.
Real Kimi CLI subprocesses reviewed the change with the original
xt-reviewprompt. The initial key-path suggestion was implemented and re-reviewed. Final code verdict: APPROVE, with no outstanding findings introduced by this PR. The published report identifies the author's diagram corrections and excludes an unchanged docstring observation from PR findings under the repository's review-scope rule.Reviewed head:
8c7b266dfc996c9700b3826524da24e60fb87328. Session persistence was verified. Session identifiers are recorded in the report.Agent RL delivery and model scope
This fix removes a Python recursion-depth limit in model-independent trace storage. The author has confirmed GLM-5.3-Flash compatibility in the integrated Agent work. This statement is separate from the exact-branch validation above: this update does not claim a new full-size GLM training or KV-cache benchmark on this standalone head.
The new Agent-lifecycle core #2297 contains overlapping iterative traversal and request-aware shared-reference ownership. Reconcile that overlap before landing both; replacing the complete trace-store file would change ownership behavior. Original #2257 remains the pre-split reference.
Developer guide: feature/PR map, diagrams, scripts and validation scope.
Keep Draft. See the live Checks tab for current CI state; the historical final-review comment records the check state at its publication time. No code or head revision was changed by this description update.