Repository: huggingface/transformers
Pull request: Fix resized LM head weights being overwritten by post_init

## What does this PR do?

Fixes #35141

When `tie_word_embeddings=False`, calling `resize_token_embeddings()` then `post_init()` overwrites
the LM head weights with random values. This happens because `_get_resized_lm_head()` returns a new
`nn.Linear` without setting `_is_hf_initialized`, so `post_init` treats it as uninitialized.

The fix adds `new_lm_head._is_hf_initialized = True` at the end of `_get_resized_lm_head()`, after
all weight copying is done. `_get_resized_embeddings()` doesn't need this because it reuses the
original module object.

Test added to `ModelTesterMixin` in `test_modeling_common.py`, following the existing
`test_resize_embeddings_untied` pattern. Verified on GPT2, LLaMA, and OPT.

Supersedes #36221 (stale since Feb 2025, reviewer feedback never addressed).

- [x] I confirm that this is not a pure code agent PR.
- [ ] This PR fixes a typo or improves the docs (you can dismiss the other checks if that's the case).
- [x] Did you read the contributor guideline, Pull Request section?
- [x] Was this discussed/approved via a Github issue or the forum?
- [ ] Did you make sure to update the documentation with your changes?
- [x] Did you write any new necessary tests?

## Who can review?

@Rocketknight1 @ArthurZucker

Changed files:
filename: src/transformers/modeling_utils.py
patch: @@ -2984,6 +2984,7 @@ def _get_resized_lm_head(
                 new_lm_head, old_lm_head, num_tokens_to_copy, transposed, has_new_lm_head_bias
             )
 
+        new_lm_head._is_hf_initialized = True
         return new_lm_head
 
     def _init_added_embeddings_weights_with_mean(

filename: tests/test_modeling_common.py
patch: @@ -2398,6 +2398,55 @@ def test_resize_embeddings_untied_with_deepspeed_multi_gpu(self):
         with _deepspeed_zero3(ds_config):
             self.test_resize_embeddings_untied()
 
+    def test_resize_embeddings_untied_no_reinit_on_post_init(self):
+        if not self.test_resize_embeddings:
+            self.skipTest(reason="test_resize_embeddings is set to `False`")
+
+        original_config, inputs_dict = self.model_tester.prepare_config_and_inputs_for_common()
+        original_config.tie_word_embeddings = False
+        try:
+            original_config.get_text_config().tie_word_embeddings = False
+        except Exception as e:
+            model_type = getattr(original_config, "model_type", "unknown")
+            print(f"Could not set text config's `tie_word_embeddings` for model type `{model_type}`: {e}")
+
+        if original_config.tie_word_embeddings:
+            self.skipTest(reason="Model cannot untie embeddings")
+
+        for model_class in self.all_model_classes:
+            with self.subTest(model_class):
+                config = copy.deepcopy(original_config)
+                model = model_class(config).to(torch_device)
+                model.eval()
+
+                # The bug only affects nn.Linear LM heads created by _get_resized_lm_head
+                output_embeds = model.get_output_embeddings()
+                if not isinstance(output_embeds, nn.Linear):
+                    continue
+
+                model_vocab_size = config.get_text_config().vocab_size
+                try:
+                    model.resize_token_embeddings(model_vocab_size + 10)
+                except (NotImplementedError, AttributeError):
+                    continue
+
+                output_embeds = model.get_output_embeddings()
+                weights_before = output_embeds.weight.data.clone()
+                bias_before = output_embeds.bias.data.clone() if output_embeds.bias is not None else None
+
+                model.post_init()
+
+                output_embeds_after = model.get_output_embeddings()
+                self.assertTrue(
+                    torch.equal(weights_before, output_embeds_after.weight.data),
+                    "Output embedding weights were reinitialized by post_init() after resize_token_embeddings()",
+                )
+                if bias_before is not None:
+                    self.assertTrue(
+                        torch.equal(bias_before, output_embeds_after.bias.data),
+                        "Output embedding bias was reinitialized by post_init() after resize_token_embeddings()",
+                    )
+
     def test_model_get_set_embeddings(self):
         config, inputs_dict = self.model_tester.prepare_config_and_inputs_for_common()