Decompose _tokenize_chat_view into a staged _ChatViewTokenizer - #997
Conversation
|
Independent consumer review by Hayek and his review delegate; posted by Jarvis at Brad's request. Validation below describes the reviewer's work. Reviewed d0f718d against 3ad9a5f (all 12 commits). No blocking findings in this refactor. I did not author this change.
Validation limit: #996 is a separate PR, not part of this head or its canonical CI. I ran its named cases, not the separate 776-call trace corpus; that opt-in tracer excludes raising/private-worker calls and reports previously unknown tests without failing. The changed code stays in the existing module, so there is no added package/import manifest to ship. Hosted quality checks passed; package-install and GPU execution jobs were skipped. No GPU/performance or exhaustive semantic-equivalence claim. Warning/traceback frames move into the stage methods. Head/base were rechecked unchanged after review. This disposition does not cover subsequent integration with the separate native-token correctness stack. |
Mechanical, behavior-preserving step of the _tokenize_chat_view decomposition: the moved code is a verbatim slice of the original with shared locals renamed to instance attributes. Verified by an AST-equivalence check of the moved slice against a632f8d.
Mechanical, behavior-preserving step of the _tokenize_chat_view decomposition: the moved code is a verbatim slice of the original with shared locals renamed to instance attributes. Verified by an AST-equivalence check of the moved slice against a632f8d.
Mechanical, behavior-preserving step of the _tokenize_chat_view decomposition: the moved code is a verbatim slice of the original with shared locals renamed to instance attributes. Verified by an AST-equivalence check of the moved slice against a632f8d.
Mechanical, behavior-preserving step of the _tokenize_chat_view decomposition: the moved code is a verbatim slice of the original with shared locals renamed to instance attributes. Verified by an AST-equivalence check of the moved slice against a632f8d.
Mechanical, behavior-preserving step of the _tokenize_chat_view decomposition: the moved code is a verbatim slice of the original with shared locals renamed to instance attributes. Verified by an AST-equivalence check of the moved slice against a632f8d.
Mechanical, behavior-preserving step of the _tokenize_chat_view decomposition: the moved code is a verbatim slice of the original with shared locals renamed to instance attributes. Verified by an AST-equivalence check of the moved slice against a632f8d.
Mechanical, behavior-preserving step of the _tokenize_chat_view decomposition: the moved code is a verbatim slice of the original with shared locals renamed to instance attributes. Verified by an AST-equivalence check of the moved slice against a632f8d.
Mechanical, behavior-preserving step of the _tokenize_chat_view decomposition: the moved code is a verbatim slice of the original with shared locals renamed to instance attributes. Verified by an AST-equivalence check of the moved slice against a632f8d.
Mechanical, behavior-preserving step of the _tokenize_chat_view decomposition: the moved code is a verbatim slice of the original with shared locals renamed to instance attributes. Verified by an AST-equivalence check of the moved slice against a632f8d.
Mechanical, behavior-preserving step of the _tokenize_chat_view decomposition: the moved code is a verbatim slice of the original with shared locals renamed to instance attributes. Verified by an AST-equivalence check of the moved slice against a632f8d.
Mechanical, behavior-preserving step of the _tokenize_chat_view decomposition: the moved code is a verbatim slice of the original with shared locals renamed to instance attributes. Verified by an AST-equivalence check of the moved slice against a632f8d.
_PrefixChatRenderCache holds the bound method _render_normalized_text, whose __self__ is the tokenizer instance, so instance -> cache -> bound method -> instance kept every per-call state object (renders, masks, caches, replacements) alive until the cyclic collector ran. The original closure only captured the immutable inputs and the frame was refcount-freed on return. Deleting the cache attribute in a finally restores deterministic freeing: 5 -> 0 instances alive after return with gc disabled, 0 unreachable objects.
d0f718d to
b9f9da8
Compare
Summary
Decomposes
_tokenize_chat_viewinsrc/art/trajectories/_tokenize.py— a single 1,713-line function with 14 nested closures, 215 local variables, 146ifs and nesting depth 9 — intoclass _ChatViewTokenizer._tokenize_chat_viewkeeps its exact signature and becomes a 19-line wrapper that builds a fresh instance and callsrun().raw_render,render_normalized_text,render_text,segmented_render,render,probe_render,part_ids,source_prompt_tokens,source_output_tokens,source_matches_context,locations,canonical_render_to_rendered,canonical_span_to_rendered,differing_span) became methods with verbatim bodies.run():__init__(setup),_render_messages,_render_canonical_masks,_substitute_exact_prefix,_translate_masks,_prepare_span_search,_prove_marked_bounds,_prove_probed_bounds,_tokenize_exact_length_stops,_collect_replacements,_assemble. Each is a verbatim slice of the original with only local→attribute renames.self.attributes rather than grouped dataclasses: the state does not partition by lifecycle (renderedis written in three stages,direct_boundsin two,stop_maskis mutated in place late,segmentedis read via late binding), so any grouping would split co-mutated names. Introducing dataclasses is a cheap follow-up if wanted._tokenize.pyrather than a new module because three tests monkeypatch_tokenizemodule globals that the body resolves at call time (_tokenize_exact_projected_chat_history,_history_matches_projection,_cached_tokenizer); a move would have made those silent no-ops.run()deletesself.prefix_render_cachein afinallyso the render cache's bound-method reference does not form aself → cache → method → selfcycle; per-call state is refcount-freed exactly as the closure version was (verified with gc disabled: 0 instances alive after return, 0 unreachable objects on collect)._tokenize_chat_view: 1,713 → 19 lines._tokenize.py: 6,981 → 7,125 lines (class is 1,837 lines afterruff format; largest method_collect_replacementsat 566 lines — the natural seams for a second pass are a per-message context object with eight sub-methods, and five stages in_prove_marked_bounds).Equivalence evidence
ast.dumpthat every method body equals the original slice after normalizingself.X → X, that every original statement is covered exactly once, and that closure signatures are['self'] + original.symtablefinds no unresolved names.refactor/tokenize-golden-harness, Add a byte-exact golden regression harness for trajectory tokenization #996) merged on top — 776 tracedtokenize_historycalls across 279 existing tests plus 13 named cases covering every "preserve … boundaries" fix since August —ART_TOKENIZE_GOLDEN=check pytest tests/unit/trajectories: 756 passed, at the first commit and at HEAD.is not Nonechange is inert (TokenizedHistoryis a pydantic model with no__bool__/__len__, and the helper applies the original truthiness test internally); no closure had default arguments ornonlocal; no error path embeds frame names; nothing pickles the instance; perf unchanged within ±5% across five cases including the real Qwen3-0.6B tokenizer. Its one Medium (the reference cycle) is fixed in the last commit. Bugbot found no bugs.tests/unit/trajectories: 742 passed;ruff check,ruff format --check,ty checkclean. Rebased onto3ad9a5f8d.One cosmetic change: the
UserWarningfrom_warn_prefix_retokenization(stacklevel=3) now attributes to_ChatViewTokenizer.runinstead of_tokenize_history; both frames are internal to_tokenize.py.