fix(lora): reject incomplete native adapter checkpoints - #2733
Conversation
Validate adapter names and shapes before copying weights or restoring optimizer progress, so a corrupt shard cannot resume training on a fresh or partially loaded adapter.
jhinpan
left a comment
There was a problem hiding this comment.
I reviewed the current head (8d08be274f0479f2e4ecc8e0c68de2b5ab31d4c6) and exercised the native load path on an AMD Instinct MI355X.
Blocker — dtype and non-finite tensors are still accepted.
_validate_native_adapter_state checks keys and shapes, but not dtype or finiteness. In the GPU probe, a same-shape float16 tensor was silently converted into a float32 parameter, while both NaN and Inf were copied into the live model. The function then reports a successful load, so a later training-state restore can proceed from poisoned weights.
Please validate the complete shard before any copy:
- the loaded object and every value are tensors;
- exact key set;
- exact shape and dtype;
torch.isfinite(...).all()for floating/complex tensors.
No model parameter or training state should be mutated until every local check succeeds.
Additional correctness gap — virtual-pipeline chunks can collide by parameter name.
expected is flattened into dict[name, param]. VPP chunks number their local layers independently, so two chunks can expose the same adapter name. The later entry overwrites the earlier one; this change then copies only into the retained parameter, leaving another chunk at fresh initialization. Key validation by (chunk_index, name), or explicitly reject duplicate names before save/load.
Stack integration requirement. When #2516 is rebased, local read/strict validation must be separated from mutation: every rank reads and validates, ranks reach a global error consensus, and only then do they copy parameters and reload optimizer masters. Also update these tests for #2516's new args parameter rather than resolving the conflict by taking its permissive load loop.
jhinpan
left a comment
There was a problem hiding this comment.
Follow-up review on the unchanged head (8d08be274f0479f2e4ecc8e0c68de2b5ab31d4c6): one additional correctness gap was found after excluding the first review's findings.
An empty model and an empty native shard are accepted as a successful adapter load.
_validate_native_adapter_state treats expected == {} and state_dict == {} as an exact match. load_lora_adapter then returns (True, iteration), and the caller logs that the LoRA adapter loaded successfully. I reproduced the current behavior directly: an empty adapter_megatron_rank0.pt with a model exposing no names matched by _is_adapter_param_name returned (True, None).
This is reachable through mismatched model predicates: is_lora_model accepts any parameter name containing adapter, while _is_adapter_param_name accepts only lora_ or exposed .adapter.(linear_in|linear_out) names. Multi-LoRA parameters such as ...adapters.0.linear_in.weight satisfy the first predicate but not the second, so the generic adapter export path can write an empty native shard.
Once #2516 is stacked, this vacuous success can also restore optimizer/scheduler/iteration even though no adapter weight was restored.
Please reject both an empty expected adapter set and an empty native shard, with a diagnostic that distinguishes “the model has no adapter parameters” from “the checkpoint contains no adapter tensors.”
Extend native adapter validation to dtype and finiteness so a truncated or NaN-poisoned shard cannot resume training.
|
Thanks — the dtype/finiteness blocker and the empty-adapter follow-up are both fixed; two of the other points I'd like to push back on. Adopted. Not adopted here — VPP name collisions. Real, but not fixable in this function: Not adopted here — global failure consensus. Agreed that a rank-local raise leaves peers in the next collective, but the same is true of the pre-existing No longer applicable — the #2516 stacking note. #2516 has been narrowed and no longer touches the native-load site or adds an Validation: 69 focused fast tests; full One thing worth a maintainer opinion: strict dtype equality means a shard written under a different precision flag no longer loads, where the old |
Part of #2705.
Problem
Native LoRA resume copies the intersection of saved and current parameter names, then restores optimizer and progress state. An empty, partial, shape-incompatible or numerically corrupt shard can therefore resume training on fresh or mixed adapter weights.
Change
Require the native shard to match every expected adapter name, shape and dtype, with no unexpected names and no non-finite values, before copying any weight or loading training state. A model that exposes no adapter parameters at all is rejected too: otherwise an empty shard matches it on every other check and the load reports success.
Validation
384tensors each; optimizer, scheduler and data cursor restored, rollout 30 completed two optimizer steps, anditer_30was savedNotes for reviewers
Two related gaps are deliberately left for follow-ups, since neither is fixable inside this validation function:
dict[name, tensor], so the shard format itself would have to become chunk-keyed.is_lora_modelaccepts any name containingadapter, while_is_adapter_param_nameaccepts onlylora_or exposed.adapter.(linear_in|linear_out)names. Reconciling the two predicates is a separate change; the new empty-model rejection turns the resulting mismatch into a clear error instead of a silent no-op load.