Retry a learned artifact's pickle once, after a collection - #142
Open
amburger66 wants to merge 1 commit into
Open
Retry a learned artifact's pickle once, after a collection#142amburger66 wants to merge 1 commit into
amburger66 wants to merge 1 commit into
Conversation
Saving learned NSRTs and GNN weights intermittently dies with `TypeError: cannot pickle '_abc._abc_data' object` -- the C-level cache behind an abstract base class. It has blocked three PRs on three different shards this week and it predates all of them: the same nine-test cluster reproduces on unmodified master. WHAT IS ESTABLISHED Two call sites, both reached from the CI tracebacks: nsrt_learning_approach.py :114 (`pkl.dump(self._nsrts, f)`) and gnn_approach.py:207 (`pkl.dump(info, f)`). Every failure seen so far funnels through one of them. On CI it is DETERMINISTIC for a given test set: shard 8 failed twice on the same single test, and shard 6 twice on the same three, across re-runs of untouched jobs. Which shard is hit moves with pytest-split's packing, which is why adding tests to an unrelated PR appears to "cause" it. Locally it is stochastic -- 3 of 12 identical runs, with code, test order and PYTHONHASHSEED all fixed. The likely difference is that CI containers are uniform while a developer machine is not. WHAT IS NOT ESTABLISHED The root cause. An `_abc_data` holds WEAK references, so whether dill trips over one plausibly turns on collection timing -- the one thing that still varies with everything else pinned. That is the hypothesis this mitigation is aimed at, and it is a hypothesis: it is stated here rather than dressed up as a diagnosis. If the failures stop, that is also the evidence for it. If they do not, it rules the hypothesis out, which is worth knowing too. Ruled out along the way, none of them the cause: pytest-randomly (not installed -- `-p no:randomly` is a silent no-op here), execution order (progress lines match character-for-character between a red and a green run), hash randomisation (PYTHONHASHSEED=0 is exported), and any single polluting test (a bisection appeared to find one, then the control showed removing its neighbours worked equally well -- that bisection was invalid, having used single runs to measure a 25% event). WHY IT IS SHAPED THIS WAY Serialising to bytes before writing, rather than retrying into the handle: a dump that raises part-way has already written a prefix, and appending a retry to that leaves a corrupt file which only fails at LOAD time -- much worse than the error being fixed. A test pins the file being empty after a persistent failure. Any TypeError is retried, not just the `_abc_data` one. Matching on the message would break silently when it is reworded, and an object that is genuinely unpicklable fails the second time too and raises exactly as before -- so the broad catch costs one wasted attempt and hides nothing. Two tests, both mutation-checked: removing the retry fails the first and not the second, and dumping straight into the handle fails both. Full local gates: mypy clean, pylint 10.00/10, yapf/docformatter/isort applied. The previously failing shard passes locally, though a single local run is weak evidence at a 25% rate -- CI is the real test of this, and is why it is worth landing to find out.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Validated by a controlled experiment
The green CI on this branch alone would NOT have been evidence: this PR adds two
tests, which changes
pytest-split's packing -- the same mechanism that movedthe failure between shards 8, 5 and 6 across #138/#139/#140. A green run was
therefore consistent with "the retry worked" and with "the cluster relocated".
So the fix was tested against a fixed packing. Branch
control-pickle-fixisPR #138's exact failing head (
901696d) plus only the production half of thisPR -- the helper and its two call sites, no test files touched, so the test
set, the packing and shard 6's contents are byte-identical to the run that
failed.
test_oracle_samplers,test_main,test_skip_initial_testSame tree, same tests, same packing; the retry is the only variable.
This also promotes the hypothesis to a finding. The body below describes the
weak-reference/collection-timing story as an untested guess, which is what it
was when written. A
gc.collect()plus one retry converting a twice-reproducedfailure into two clean runs is what that hypothesis predicts, and nothing else
in the diff could produce it. The root cause -- which object graph reaches an
_abc_data, and why -- is still not identified, and this remains a mitigationrather than a repair.
control-pickle-fixis an experiment branch marked "Not for merge"; it shouldbe deleted once this lands.
Saving learned NSRTs and GNN weights intermittently dies with
TypeError: cannot pickle '_abc._abc_data' object. It has blocked three PRs onthree different shards this week (#138, #139, #140) and it predates all of them
-- the same nine-test cluster reproduces on unmodified
master.What is established
Two call sites, both reached from the CI tracebacks:
nsrt_learning_approach.py:114(pkl.dump(self._nsrts, f)) andgnn_approach.py:207(pkl.dump(info, f)). Every failure seen so far funnelsthrough one of them.
On CI it is deterministic for a given test set: shard 8 failed twice on the
same single test, shard 6 twice on the same three, across re-runs of untouched
jobs. Which shard is hit moves with
pytest-split's packing, which is whyadding tests to an unrelated PR looks like it "causes" the failure.
Locally it is stochastic -- 3 of 12 identical runs, with code, execution
order and
PYTHONHASHSEEDall fixed. The likely difference is that CIcontainers are uniform and a developer machine is not.
What is NOT established
The root cause. An
_abc_dataholds weak references, so whether dilltrips over one plausibly turns on collection timing -- the one thing that still
varies with everything else pinned. That is the hypothesis this mitigation
targets, and it is a hypothesis; I would rather say so than dress it up.
If the failures stop, that is the evidence for it. If they do not, it rules
the hypothesis out, which is worth knowing too. That is the main argument for
landing this: it converts an unfalsifiable annoyance into a decided question.
Ruled out along the way, none of them the cause:
pytest-randomly-p no:randomlyis a silent no-op herePYTHONHASHSEED=0is exported for every runThat last one deserves a note: the bisection was invalid, because it used
single runs to measure a 25% event. Measuring the rate first would have shown
each step had a ~75% chance of a spurious pass.
Why the fix is shaped this way
Serialise to bytes, then write -- rather than retry into the open handle. A
dump that raises part-way has already written a prefix, and appending a retry to
that leaves a corrupt file which only fails at load time, long after the run
that produced it could have been repeated. A test pins the file being empty
after a persistent failure.
Catch any
TypeError, not just the_abc_dataone. Matching on the messagewould break silently when it is reworded, and an object that is genuinely
unpicklable fails the second time too and raises exactly as before -- so the
broad catch costs one wasted attempt and hides nothing.
Tests
Two, both mutation-checked:
mypy clean, pylint 10.00/10, yapf/docformatter/isort applied. The previously
failing shard passes locally -- though at a 25% local rate a single clean run is
weak evidence, which is exactly why CI is the test that matters here.
Unrelated thing noticed
Running the suite rewrites
tests/datasets/mock_vlm_datasets/ice_tea_making__demo+labelled_atoms__manual__1.txtin the working tree. It is a test writing into the repo, it nearly rode along in
this commit twice, and it is not fixed here.