fix(transformers): gate _tie_weights_nemo on tie_word_embeddings flag#2942
Merged
Conversation
_tie_weights_nemo re-tied lm_head.weight to the input embedding for any model exposing _nemo_tied_weights_keys, without consulting config.tie_word_embeddings. For untied models this aliased away the trained lm_head loaded by from_pretrained, and corrupted embeddings/head on checkpoint resume (both tensors loaded into one shared storage). Gate the re-tie on the controlling config flag via get_controlling_tie_word_embeddings. Tied models (the #1817 Nemotron-Flash case) keep the existing re-tie behavior; models without a config fall back to re-tying. Fixes #2941 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Yuhe Zhang <yuhez@nvidia.com>
Contributor
Author
|
/ok to test 9199d1e |
9 tasks
3 tasks
akoumpa
approved these changes
Jul 9, 2026
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.
What does this PR do ?
Fixes #2941:
_tie_weights_nemore-tiedlm_head.weightto the input embeddings for any model exposing_nemo_tied_weights_keys, without consulting the controllingconfig.tie_word_embeddingsflag._nemo_tied_weights_keysrecords candidate tied keys from pre-v5/list-form_tied_weights_keys; it does not provethat the checkpoint is actually tied.
Impact is real for shipped untied remote-code force-HF recipes. Confirmed affected:
examples/llm_finetune/nemotron/nemotron_nano_4b_squad.yamlexamples/llm_finetune/nemotron/nemotron_nano_9b_squad.yamlexamples/llm_finetune/nemotron/nemotron_nano_9b_squad_peft.yamlThose recipes load Nemotron Nano 4B/9B through
trust_remote_code: true+force_hf: true. The hub configs settie_word_embeddings=False, the checkpoints contain separate trainedlm_head.weightandbackbone.embeddings.weight, and the recipe compatibility patch converts remote list-form_tied_weights_keys = ["lm_head.weight"]into_nemo_tied_weights_keys. Before this PR,_tie_weights_nemothenforced an alias and discarded the trained head.
The fix gates the re-tie on the canonical
get_controlling_tie_word_embeddingsresolver. Tied models, such as theNemotron-Flash case #1817 was written for, keep the existing re-tie behavior unchanged. Models without a
configattribute conservatively fall back to re-tying.
Before / after
Real recipe-path source load:
nvidia/NVIDIA-Nemotron-Nano-9B-v2.Using the same recipe condition that exposes the bug (
apply_cache_compatibility_patches()+trust_remote_code=Trueforce_hf=True), compare a raw HF source load against a NeMoAuto source load on a seeded 128-token prompt:lm_headaliased?true22.56252.9893-0.0224false0.250.01411.000This is the silent manifestation: the model loads successfully, but logits diverge from the raw HF reference immediately
on main because the trained untied
lm_headis aliased away.Unit/mechanism coverage.
The regression tests fail on main and pass on this branch:
test_untied_config_keeps_separate_lm_headtest_untied_state_dict_roundtrip_is_losslesstest_tied_config_retiestest_missing_config_still_retiesTargeted result on this branch:
4 passed.Resume smoke note.
A real 9B FSDP2 DCP recipe resume smoke was also run. This PR produced a checkpoint at step 3, then main and this PR
both resumed one step from that same checkpoint. Both resumed step 4 at
loss=0.466808.That result means the DCP checkpoint resume path was correct in this smoke. It does not cover the bug fixed here: on
main, the model is already wrong immediately after the initial NeMoAuto source load, before any training checkpoint is
saved or resumed. This is also why the current checkpoint robustness test can miss the issue: it captures reference
logits from the already-loaded NeMo model, then validates checkpoint reloads against that reference. We should add a
separate source-load parity check that compares raw HF source load vs NeMoAuto source load before training starts.
Changelog
_tie_weights_nemonow returns early when the controllingtie_word_embeddingsflag isFalse, preservingseparately loaded
lm_head.weightfor untied models.TestTieWeightsNemoConfigGateregression tests: untied preservation, tied re-tie, missing-config fallback, andlossless construct-then-load round-trip.
Before your PR is "Ready for review"
Pre checks:
Guide.
tests/unit_tests/_transformers/test_model_init.py::TestTieWeightsNemoConfigGateAdditional Information
pipeline.
_tied_weights_keys. That workaround is still needed fortied remote-code models; this PR only adds the missing config gate for untied checkpoints.
from the already-constructed NeMo model, then compares checkpoint reloads against that reference. If the source load is
already corrupted, the corrupted model can become the baseline. We should add an opt-in raw-HF-source vs
NeMoAuto-source parity check for remote-code/force-HF recipes.