Skip to content

Snapshot constructor receivers generally without invoking application getters - #157

Merged
Essoz merged 2 commits into
mainfrom
codex/constructor-receiver-snapshot
Oct 27, 2026
Merged

Essoz merged 2 commits into
mainfrom
codex/constructor-receiver-snapshot

Conversation

@Essoz

@Essoz Essoz commented Oct 26, 2026 •

Copy link
Copy Markdown
Collaborator

TrainCheck serializes selected API arguments before calling the wrapped function. For a class's __init__, ordinary introspection can execute properties or dynamic lookup on a partially initialized receiver. This caused the reproduced OpOverloadPacket recursion, but the unsafe assumption applies to other constructors too.

This PR now handles constructor receivers generally. Class traversal explicitly identifies initializers; there is no operator-type special case or function-name heuristic. Before the initializer runs, record receiver identity and native stored state without invoking application properties, custom descriptors, dynamic lookup, or custom metaclass lookup. Preserve the other arguments, receiver identity, application exceptions, and PRE/POST pairs.

Representation and compatibility

  • Capture native instance dictionaries, metaclass class namespaces, and populated inherited/private slots. Dictionary entries and same-named slots remain separate, with each slot's declaring type retained.
  • _TRAINCHECK_CONSTRUCTOR_SNAPSHOT explicitly records before_init / stored_attributes_only, unavailable dictionary storage, unset slots, and requested-but-unavailable fields.
  • Exact built-in scalars retain values; nonfinite floats have explicit tags. Containers, tensors, and other objects are tagged identity/type references, not recursive content snapshots or tensor hashes.
  • Reinitialization and base-class initializers use the same representation. Positional and explicit keyword receivers work; a missing receiver reaches the original callable's TypeError. A native callable without a signature supports positional receivers without guessing a keyword name.
  • Constructor receivers bypass proxy-marker scans and unproxying on the dumping path. Ordinary arguments and non-constructor calls retain existing serialization.
  • This deliberately changes constructor receiver encoding. Existing invariants that depended on introspected constructor fields need recollection and activation validation. Native extension state without exposed storage descriptors and replaced slot descriptors are outside coverage; no atomic snapshot during concurrent mutation is promised.

The full contract is in docs/constructor-snapshots.md.

Validation at 98fed5058bfc0c712c0e9a717a52c1bc1c224666

  • 30 focused constructor regressions pass on Python 3.10.16 / PyTorch 2.2.2. Cover hostile getters/metaclasses, dictionary overrides, slots and collisions, metaclass initialization, reinitialization, keyword/missing receivers, malformed metadata/signatures, reader compatibility, state/RNG preservation, and event multiplicity.
  • The actual-instrumenter regression fails on previous PR head 86cbfc297cb27bac4a99359224cf21d48ecdf581 by executing a property before the ordinary class initializer runs; the same test passes after this change. It does not depend on calling a new wrapper interface.
  • Full local suite: 255 pass, two fail. Both failures reproduce on the previous head: test_traincheck_internal_attr_hidden and test_inject_eval_loop.
  • Changed-file pinned pre-commit hooks pass. Configured mypy passes all 78 production files. The new snapshot module and constructor tests pass stronger typing with installed Torch types (--check-untyped-defs --disallow-untyped-defs --disallow-any-generics --follow-imports=silent). The legacy tracer has 28 → 21 errors under those stronger options, with none added; dependency bodies are not fully checked.
  • Whole-tree Ruff and isort pass. Whole-tree Black reports the same three pre-existing files before and after: config/config.py, instrumentor/proxy_wrapper/hash.py, and instrumentor/proxy_wrapper/subclass.py under traincheck/. These unrelated formatting changes are not included.

Rechecked the real two-step tiny Transformer CLI using the prior signed #154–#157 composition 035f7f1976c5d84bd01f5d96bb095fb07c9cc059 with this commit's two production files overlaid and SHA-256 verified:

Mode Complete API pairs VAR records Constructor snapshots
Ordinary 478 72 40
Selective 2 48 0
Full 814 72 96

All three instrumented runs exactly match plain losses, parameters, gradients, cached mask, and Torch CPU RNG; cover all 12 parameter paths; and complete two active, passing invariant decisions each. No incomplete/duplicate API pairs or duplicate raw records. Every observed initializer PRE receiver has exactly one explicit snapshot. API/VAR counts match the prior composition; the snapshot counts reflect the generalized representation. This is correctness validation, not a tracing-speed improvement claim, and was CPU-only.

Reproduce local checks with python -m pytest -q tests/test_constructor_snapshot.py, python -m pytest -q tests, and the pinned hooks for the five changed files. Exact commands, raw traces, harnesses, source hashes, before/after failures, and coverage reports are retained at /Users/matt/gitrepos/traincheck/outputs/constructor-generalization-20261027/, especially validation.json and cli/validation.json.

All commits are signed. Targets main directly; production changes are independent of #154, while the composed Transformer check uses its subclass assignment fix. No PR has been merged.

@Essoz Essoz changed the title Snapshot uninitialized operator constructor receivers without dynamic getters Snapshot constructor receivers generally without invoking application getters Oct 27, 2026
@Essoz
Essoz merged commit 6dccb47 into main Oct 27, 2026
0 of 2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant