fix(GATE-SNAPSHOT-CONTENT-PIN): pin the Nemotron checkpoint by CONTENT, not by "this revision was downloaded here once" (#569) - #579
Merged
Conversation
…T, not by "this revision was downloaded here once" (#569) `parity::Nemotron35LightningSnapshot()` gated the staged `hf download --local-dir` tree on the EXISTENCE of `.cache/huggingface/trees/<revision>.json`. That file records that the revision was fetched into this directory at some point. It does not record that the bytes now on disk are that revision, and the two come apart. DEMONSTRATED (RED, against this header at c6b240e): a directory holding a DIFFERENT model's config.json -- `{"architectures": ["LlamaForCausalLM"]}` -- whose every `.cache/huggingface/download/<file>.metadata` named revision `bbbb...`, resolved. The manifest was an empty `touch`ed file and a decoy manifest beside it changed nothing. W6 of `.agents/specs/nemotron-h-model.md` (#517) is a token-exact gate that resolves through this function, and a token gate is precisely the instrument that cannot see a substituted checkpoint: the wrong model reads as a correctness regression in whatever changed last. THE `trees/` QUESTION, ANSWERED. `hf download --local-dir` does NOT delete a superseded manifest. huggingface_hub 1.7.2 (this box) has no `trees` code at all; the writer is `_tree_cache.py`, added in 1.23.0/1.24.0, which is what is installed on the gate host where the NAS copy was fetched. Its module docstring states the design outright -- "Because a commit hash is immutable, its tree listing never changes and can be cached forever WITHOUT ANY INVALIDATION LOGIC" -- and `write_tree_cache` only `os.replace`s `<commit>.json`. `trees` appears five times in the whole library, all reads and path joins; nothing removes one. So manifests ACCUMULATE, and the worst case is the real case: after a re-download at a later revision the pinned revision's manifest is still there, vouching for bytes that are no longer it. `test_hf_snapshot_pinning` stages exactly that. THE FIX. Sweep the per-file download sidecars instead. Line 1 of `.cache/huggingface/download/<file>.metadata` is that file's `commit_hash` (`huggingface_hub/_local_folder.py` module docstring and `read_download_metadata`), and it does not accumulate: `file_download.py` calls `write_download_metadata(..., commit_hash=...)` on all four outcomes, including the `local_metadata.etag == etag` early return that downloads nothing, so it always names the revision the bytes were fetched as. On the NAS copy all 69 sidecars read 29f2d174..., in exact bijection with the 69 staged files. SCOPE: EVERY staged file, not just config.json. The goldens depend on the WEIGHTS. `hf download <repo> <shard> --revision B` into a tree fetched at A leaves a correct config beside wrong shards, which is the substitution that matters and the one a config-only check cannot see. 69 sidecar reads of ~130 bytes against a 20.1 GiB checkpoint the gate is about to load is not a trade-off. The check is symmetric -- a file with NO sidecar refuses too, or substituting a shard would cost one `rm`. The manifest check is REPLACED, not kept as an extra term: it can no longer refuse anything the sweep does not already refuse, while it CAN refuse a good tree fetched by a huggingface_hub older than 1.23, which writes sidecars and no manifest. Preserved unchanged: `CHECKPOINT_ROOT` as the default path, `VT_NEMOTRON35_SNAPSHOT` as the explicit override that wins over it and is deliberately NOT revision-gated (naming ONE directory is the different-checkpoint run), and a set-but-wrong override refusing rather than falling back. The resolver now returns the reason it refused through an optional `why` out-param; `test_modelopt_mixed_precision_checkpoint` prints it verbatim in its exit-77 banner, so the skip names WHICH file recorded WHICH revision. The no-argument spelling is unchanged, so `test_nemotron_h_scaffold` is untouched. The four sibling accessors were checked and NOT changed. They resolve `models--org--name/snapshots/<rev>/`, where the revision is the directory name and every entry is a symlink into `blobs/<etag>`; a re-download at a new revision creates a NEW snapshot directory rather than replacing bytes under the old one. Verified on the gate host, where that repo holds both @890bdef7 and @ccdaab7e side by side. Genuinely a different situation, and widening this change to them would be unevidenced. IMP-MUTATE, four mutations, no survivors: disabling the wrong-revision refusal (3 cases RED), deleting the missing-sidecar refusal (1 RED -- it needed the MESSAGE asserted, since falling through to the wrong-revision branch also refuses), replacing the sweep with a config.json-only check (3 RED), and deleting the vacuous-tree refusal (1 RED, reachable via a config.json that is a DIRECTORY). Header restored byte-for-byte, md5 807c9171c3529fa300e6f7729d6a57d4. Evidence: test_hf_snapshot_pinning 11 cases / 42 assertions, Status: SUCCESS. Header compiled in a real build, not only swept: test_qwen36_paged_engine, test_qwen27_paged_engine, test_qwen36_weights, test_nemotron_h_scaffold and test_modelopt_mixed_precision_checkpoint all rebuilt clean, and the full Release `-Werror` build exits 0 -- `agent-preflight.sh` does not compile the parity TUs, and this header RED'd main today when an accessor was ordered above the `HfSnapshot` it called. Against the LIVE NAS checkpoint the arm resolves and passes 12181/12181. df -h /: 68G avail, 85% used. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
Picks up 51ec6be (BENCH-ASSERT-CLOCK-STATE, #543/#545). No conflict: that change touches tools/bench and .agents, this row touches tests/parity and the Nemotron checkpoint arm. Full Release build and ctest re-run on the merge result -- 403/403, 0 failed. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
localai-bot
pushed a commit
that referenced
this pull request
Aug 13, 2026
…pin gate and the intake record (#517) Fourth re-merge of this land-prep; `origin/main` `51ec6bed5` -> `751325460`. Both new commits are clean auto-merges and neither collides with W2: - `751325460` (#579) re-pins the Nemotron checkpoint by CONTENT in `tests/parity/hf_snapshot.h` and `tests/parity/test_hf_snapshot_pinning.cpp`. That is the same file this branch resolved in its FIRST merge, so it is re-checked rather than assumed: `git diff origin/main -- tests/parity/hf_snapshot.h` is EMPTY, i.e. main's version stands whole, and the branch contributes nothing to it. - `ce7071122` (#580) is a records-only intake placement. `.agents/specs/nemotron-h-model.md` is unchanged on main across this pair, so the hand-merged version from the previous commit still holds; re-verified block by block anyway -- 16 blocks, 13 BYTE-IDENTICAL to main, and the 3 that differ are W2's own (§2's one table row, the new §6a, §7 `Now`). The delta vs `origin/main` remains exactly this branch's eight W2 files, and `src/vt/cuda/cuda_moe.cu` and `vt/ops.h` are untouched by this merge, so the GPU-verified CUDA arm is still unchanged. FOLLOWING_AGENTS_PROTOCOL Refs #517. Refs #579. Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [Claude Code]
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.
Closes #569. Row:
MODEL-TEXT-nemotron-h-nemotron-hfor-causal-lm. Spec:.agents/specs/nemotron-h-model.md(W6, #517).What was wrong
parity::Nemotron35LightningSnapshot()pinned the stagedhf download --local-dircheckpoint on the existence of.cache/huggingface/trees/<revision>.json. That records "this revision was downloaded into this directory once", not "these bytes are that revision".RED, against the header at
c6b240ed:A different model, whose every download sidecar named a different revision, resolved — with the pinned manifest an empty
touched file and a decoy manifest beside it changing nothing. W6 is a token-exact gate that resolves through this function, and a token gate is exactly the instrument that cannot see a substituted checkpoint.The
trees/deletion question: ANSWERED — it does not deletehuggingface_hub1.7.2 (this box) has notreescode at all. The writer is_tree_cache.py, present in 1.23.0 / 1.24.0, which is what is installed on the gate host where the NAS copy was fetched. Its module docstring states the design:write_tree_cacheonlyos.replaces<commit>.json. The stringtreesappears five times in the whole library — all reads and path joins. Nothing removes one.So manifests accumulate, and the worst case is the real case: after a re-download at a later revision the pinned revision's manifest is still sitting there vouching for bytes that are no longer it. That case is staged and pinned by a test.
The fix
Sweep the per-file sidecars. Line 1 of
.cache/huggingface/download/<file>.metadatais that file'scommit_hash(_local_folder.pymodule docstring;read_download_metadataparses it back). Unlike the manifest it does not accumulate:file_download.pycallswrite_download_metadata(..., commit_hash=...)on all four outcomes — fresh download, copy out of the shared cache, LFS hash matched, and thelocal_metadata.etag == etagearly return that downloads nothing — so it always names the revision the bytes were fetched as. Verified on the NAS copy: 69 sidecars, all29f2d174…, in exact bijection with the 69 staged files (including.gitattributes).Scope: every staged file, not just
config.jsonThe goldens depend on the weights.
hf download <repo> <shard> --revision Binto a tree fetched at A leaves a correct config beside wrong shards — the substitution that matters, and the one a config-only check cannot see. 69 sidecar reads of ~130 bytes against a 20.1 GiB checkpoint the gate is about to load is not a trade-off. The check is symmetric: a file with no sidecar refuses too, or substituting a shard would cost onerm.The manifest check is replaced, not kept as an extra AND-term: it can no longer refuse anything the sweep does not already refuse, while it can refuse a good tree fetched by a
huggingface_hubolder than 1.23, which writes sidecars and no manifest.Preserved unchanged
CHECKPOINT_ROOTas the default path;VT_NEMOTRON35_SNAPSHOTas the explicit override that wins over it and is deliberately not revision-gated (naming ONE directory is the different-checkpoint run the escape exists for); a set-but-wrong override refusing rather than falling back. Each is pinned by a subcase.Loud skip
The resolver returns the reason it refused through an optional
whyout-param, andtest_modelopt_mixed_precision_checkpointprints it verbatim in its exit-77 banner, so the skip names which file recorded which revision instead of "not staged". The no-argument spelling is unchanged, sotest_nemotron_h_scaffoldis untouched.Sibling accessors: checked, NOT changed
Qwen27NvfP4Snapshot,Qwen27nFp8TowerSnapshot,Qwen36A3bNvfP4Snapshot,Qwen27DFlashDraftSnapshotresolvemodels--org--name/snapshots/<rev>/, where the revision is the directory name and every entry is a symlink intoblobs/<etag>. A re-download at a new revision creates a new snapshot directory rather than replacing bytes under the old one — verified on the gate host, wheremodels--unsloth--Qwen3.6-27B-NVFP4holds@890bdef7and@ccdaab7eside by side, each a directory of symlinks. Genuinely a different situation; widening this change to them would be unevidenced.IMP-MUTATE — 4 mutations, 0 survivors
config.json-onlyThe missing-sidecar mutant initially survived: falling through to the wrong-revision branch also refuses (
""never equals the pin), so only the message changed. The message is now asserted, and aconfig.jsonthat is a directory makes the vacuous-tree branch reachable, which is what catches the fourth. Header restored byte-for-byte afterwards,md5 807c9171c3529fa300e6f7729d6a57d4.Evidence
agent-preflight.shdoes not compile the parity TUs, and this header RED'dmaintoday (#546/#551/#556) on accessor ordering, so the header was compiled in a real build rather than only swept:test_hf_snapshot_pinningStatus: SUCCESS!test_qwen36_paged_engine,test_qwen27_paged_engine,test_qwen36_weights,test_nemotron_h_scaffold,test_modelopt_mixed_precision_checkpoint-Werrorbuildctest(full)test_modelopt_mixed_precision_checkpointSKIPPED (exit 77) with noCHECKPOINT_ROOTscripts/check-snapshot-pins.py,--self-test,tests/scripts/test_check_snapshot_pins.pytests/tools/test_online_gate_server_binary.pyscripts/agent-preflight.sh --stageddf -h /beside every number above:447G total, 68G avail, 85% used.Owed, out of this row's authority
Authority for this task excluded
docs/and.agents/. #569 is therefore not yet linked in the.agents/roadmap_v1.mdissue table or in.agents/specs/nemotron-h-model.md; preflight does not demand it, and it is left to the operator rather than edited here. No public-doc surface is owed — no row changed lifecycle state.🤖 Generated with Claude Code