You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat(aorlta): export a training checkpoint as a PEFT adapter - #612
A training run saves its LoRA only as a raw torch.save checkpoint. A score run, tools/merge_base_adapter.py and peft all take a PEFT adapter directory, so there was no way to use a trained model outside its run. This adds the missing step.
writes adapter_model.safetensors (the fp32 tensors, same bits) and adapter_config.json (the trainer's r 16 / alpha 32, no dropout, no bias);
reads the output back bit for bit, and records the input and output hashes in export-manifest.json.
A failed read-back stays in DIR.partial. The README's "Run outputs" section shows export, then score or merge.
The test (heavy, CPU) trains one wired iteration, exports the checkpoint, and merges it with the existing merge tool. The merged export must be exactly, bit for bit, the weights the engines were served, and peft must load the checkpoint's tensors. A tampered checkpoint or an existing output is refused. Two mutants (a scaled B, a skipped manifest check) are caught.
Limit: a run with sync.stochastic_rounding served stochastically rounded weights, so a nearest-rounded merge is close to them, not equal. On MI350X: I exported a real checkpoint from a 3-iteration training run of the HIP-kernel example (#613) and scored it with model.adapter_dir. The score run merged it, served it, and the landing check was PROVEN on 4 engines. That run also found the safetensors file written owner-only (0600) beside a 0644 config; it now takes the umask's mode.
tools/export_adapter.py writes a checkpoint's fp32 LoRA latent as a PEFT adapter
directory (adapter_model.safetensors and adapter_config.json, the trainer's r and
alpha), after checking the checkpoint against its manifest, and reads its output
back bit for bit. That closes the loop from a training run to a score run
(model.adapter_dir), to tools/merge_base_adapter.py, and to peft.
The test trains one wired iteration on CPU, exports its checkpoint, and checks that
the merged export is exactly the weights the engines served, and that peft loads
the checkpoint's tensors.
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
The exporter can overwrite a concurrently created output directory, and reopening trainer.pt across verification, loading, and hashing can export unverified tensors or record a mismatched input digest.
… owner-only
safetensors writes its file owner-only, so an export of a real checkpoint on
MI350X was mode 0600 beside a 0644 config: unreadable to anyone it is shared
with.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
The export publication step can replace a concurrently created output directory, violating the non-overwrite guarantee instead of refusing the collision and preserving the partial export.
Limit merged-weight equality claims to the tested CPU fixture
aorlta/README.md:256
A GPU-trained checkpoint is not guaranteed to reproduce the served bits: training merges on the base tensors' device, but merge_base_adapter.py merges on CPU. Different fp32 accumulation orders can change the final bf16 rounding even without stochastic rounding. Please limit the merged-weight equality claim to the tested CPU fixture and distinguish it from bitwise preservation of the adapter tensors.
The reason will be displayed to describe this comment to others. Learn more.
Review summary
Verdict: fix-before-ship
The export shape is right: manifest check, fp32 bits unchanged, read-back, and a failed read-back left in .partial. The manifest can still certify a different file from the one that was loaded.
Findings: 0 High · 1 Medium · 0 Low
Copilot's other note, that rename replaces an empty directory created after the existence check, is a real POSIX race. It does not publish a passing export of the wrong tensors; the three reads of trainer.pt can. I am not asking to block on the rename.
The reason will be displayed to describe this comment to others. Learn more.
[Medium]trainer.pt is read three times, and the manifest hashes a different read from the one that was loaded.
verify_checkpoint hashes the file, torch.load reads it again, and line 139 hashes it a third time. A replace between those reads exports tensors the manifest check never saw, with pass: true and a sha256 of some other bytes.
Fix: read the file once, check that digest against the checkpoint manifest, torch.load those bytes, and record that same digest. Copilot flagged this too.
The reason will be displayed to describe this comment to others. Learn more.
Fixed in 7258101 as you suggested: trainer.pt is read once, those bytes must match the checkpoint manifest's entry, they're loaded with torch.load(BytesIO(...)), and the same digest goes into the export manifest. A test replaces the file before, together with its manifest, and after the check.
…e or with main
- Every GPU run needs the aorta checkout (AORLTA_AORTA_ROOT): the import pre-flight loads two of its backends
whatever the profile, and exits 99 without it. Its own row, and out of the stand-in-only environment line.
- The score run and the training run are two submissions; give the training command.
- min_scored_fraction is over the candidates handed to the reward service, not every chain generated (also in the
README's correctness table).
- A scored_fraction_below_threshold stop writes no summary for the failing iteration: read stop.detail.policy and
stop.detail.unsettled in run_result.json.
- Name the pull requests that add the worked examples (#613), plugins (#611) and the adapter exporter (#612) where a
passage depends on them; drop hipk.run.gpus as if it were a config key; a checkpoint is not a PEFT adapter.
…e export and compare tools come with #612 and #613
- The README's iteration diagram drew every parse rejection with a turn left as a repair turn. A repair happens only
when the parser sets a rejection reason (the worked examples' parsers do); standin.parse sets none, so its reply
with no code block ends the chain (rollout/chain.py, no_code_block).
- The score-run paragraph names tools/export_adapter.py (#612) and tools/eval_compare.py (#613) as additions.
- The aorta checkout row says #611 makes the import pre-flight load aorta's backends only for profiles that use them.
…ut that appeared meanwhile
- trainer.pt was read three times: by the checkpoint's manifest check, by torch.load, and for the manifest's
sha256. A file replaced between reads was exported unchecked, with pass: true and the hash of other bytes. It is
now read once; those bytes must match the checkpoint manifest's entry, are loaded from memory, and their digest is
the one recorded.
- The output was checked at entry only, and a rename replaces an empty directory on POSIX. Publication now reserves
<out> by creating it (fails if anything is there), then renames over only that reservation; a refused export is
left in <out>.partial, exit 2.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Human judgment is needed to reconcile the documentation’s bitwise-reproducibility claims and CPU-only limitations with cross-device rounding differences and reported GPU validation.
@amd-vivekag your point is addressed in 7258101: trainer.pt is read once, checked against the manifest, loaded from those bytes, and that digest recorded. Could you take another look?
* docs(aorlta): a getting-started guide, and the README facts a new user needs first
docs/GETTING-STARTED.md puts the steps in order: what a run needs (and what is
untested: fewer GPUs, other GPUs, other models), the CPU tests as CI runs them,
the site file and the job's environment, a score run before a training run,
measured times, reading the outputs, the settings people change, and what each
of the job's exit codes and the common run stops mean.
The README now says the test suite must not run with -n auto on a many-core
machine, that only the Qwen3.5 family works as it stands, that the stand-in
dataset is not published and the example pool is too small to run, and that
the fake components do not read the configured pool or checks.
* docs(aorlta): point the guide's checkpoint note at the README's run outputs
* docs(aorlta): GPU counts and memory, models and checkpoints, getting help, and a pointer from the aorta README
GETTING-STARTED.md: how to place engines, trainer ranks and checks on 1, 2, 4 or
8 GPUs of one node, measured peak memory per GPU (labelled estimates beside
them), how to share a node, how to switch models and what limits that today,
and where checkpoints and adapters go. The README gains a Getting help section
(open an issue with the new AORLTA template, label aorlta; docs feedback as
docs: issues). The top-level README points to aorlta/ as early and evolving.
* docs(aorlta): diagrams of one iteration, of a score run and export, and of the GPU layout
* docs(aorlta): wider boxes in the README diagrams, so they read without scrolling
* docs(aorlta): getting started, where the guide disagreed with the code or with main
- Every GPU run needs the aorta checkout (AORLTA_AORTA_ROOT): the import pre-flight loads two of its backends
whatever the profile, and exits 99 without it. Its own row, and out of the stand-in-only environment line.
- The score run and the training run are two submissions; give the training command.
- min_scored_fraction is over the candidates handed to the reward service, not every chain generated (also in the
README's correctness table).
- A scored_fraction_below_threshold stop writes no summary for the failing iteration: read stop.detail.policy and
stop.detail.unsettled in run_result.json.
- Name the pull requests that add the worked examples (#613), plugins (#611) and the adapter exporter (#612) where a
passage depends on them; drop hipk.run.gpus as if it were a config key; a checkpoint is not a PEFT adapter.
* docs(aorlta): the stand-in parser ends a reply with no code block; the export and compare tools come with #612 and #613
- The README's iteration diagram drew every parse rejection with a turn left as a repair turn. A repair happens only
when the parser sets a rejection reason (the worked examples' parsers do); standin.parse sets none, so its reply
with no code block ends the chain (rollout/chain.py, no_code_block).
- The score-run paragraph names tools/export_adapter.py (#612) and tools/eval_compare.py (#613) as additions.
- The aorta checkout row says #611 makes the import pre-flight load aorta's backends only for profiles that use them.
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
The README’s merged-weight equality claim must be limited to the tested CPU setup with matching thread counts, since other device or thread configurations can alter final bf16 rounding.
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
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.
A training run saves its LoRA only as a raw
torch.savecheckpoint. A score run,tools/merge_base_adapter.pyand peft all take a PEFT adapter directory, so there was no way to use a trained model outside its run. This adds the missing step.tools/export_adapter.py --ckpt RUN_DIR/ckpt/iter-<n> --out DIR:adapter_model.safetensors(the fp32 tensors, same bits) andadapter_config.json(the trainer's r 16 / alpha 32, no dropout, no bias);export-manifest.json.A failed read-back stays in
DIR.partial. The README's "Run outputs" section shows export, then score or merge.The test (heavy, CPU) trains one wired iteration, exports the checkpoint, and merges it with the existing merge tool. The merged export must be exactly, bit for bit, the weights the engines were served, and peft must load the checkpoint's tensors. A tampered checkpoint or an existing output is refused. Two mutants (a scaled B, a skipped manifest check) are caught.
Limit: a run with
sync.stochastic_roundingserved stochastically rounded weights, so a nearest-rounded merge is close to them, not equal. On MI350X: I exported a real checkpoint from a 3-iteration training run of the HIP-kernel example (#613) and scored it withmodel.adapter_dir. The score run merged it, served it, and the landing check was PROVEN on 4 engines. That run also found the safetensors file written owner-only (0600) beside a 0644 config; it now takes the umask's mode.