Repository: huggingface/transformers
Pull request: Fix UMT5EncoderModel embedding weights not being tied after loading

After the "Refactor weight loading" commit (#41580), UMT5EncoderModel fails to properly tie encoder.embed_tokens.weight to shared.weight when loading checkpoints with tie_word_embeddings=False (e.g., Wan-AI video generation models).

This causes encoder.embed_tokens.weight to remain uninitialized (all zeros), producing garbage output in downstream applications like WanPipeline.

Root Cause
The new tie_weights() in modeling_utils.py skips weight binding when tie_word_embeddings=False. However, UMT5EncoderModel architecturally requires encoder.embed_tokens to share weights with shared, regardless of the config setting.

Solution
Override tie_weights() in UMT5EncoderModel to always bind the embedding weights

Verification
Before fix:
```python
import torch
from diffusers import AutoencoderKLWan, WanPipeline

model_id = "Wan-AI/Wan2.1-T2V-1.3B-Diffusers"
vae = AutoencoderKLWan.from_pretrained(model_id, subfolder="vae", torch_dtype=torch.float32)
pipe = WanPipeline.from_pretrained(model_id, vae=vae, torch_dtype=torch.bfloat16)

text_encoder = pipe.text_encoder
print(f"text_encoder.shared.weight sum: {text_encoder.shared.weight.sum().item()}")
print(f"text_encoder.encoder.embed_tokens.weight sum: {text_encoder.encoder.embed_tokens.weight.sum().item()}")
print(f"Are they same tensor? {text_encoder.shared.weight.data_ptr() == text_encoder.encoder.embed_tokens.weight.data_ptr()}")
```

Before fix:
encoder.embed_tokens.weight.sum() = 0.0
shared.weight.sum() = -4456448.0
Same tensor: False

After fix:
Both weights sum to -4456448.0
Same tensor: True
WanPipeline video generation works correctly

The video script see https://github.com/huggingface/diffusers/issues/13105

Changed files:
filename: src/transformers/models/mt5/configuration_mt5.py
patch: @@ -93,7 +93,6 @@ def __init__(
         is_encoder_decoder=True,
         use_cache=True,
         tokenizer_class="T5Tokenizer",
-        tie_word_embeddings=False,
         bos_token_id=None,
         pad_token_id=0,
         eos_token_id=1,
@@ -136,6 +135,7 @@ def __init__(
             self.dense_act_fn = "gelu_new"
 
         # Force because official weights have False serialized, but we have to tie always
+        kwargs.pop("tie_word_embeddings", None)
         self.tie_word_embeddings = True
         self.tokenizer_class = tokenizer_class
         self.bos_token_id = bos_token_id

filename: src/transformers/models/umt5/configuration_umt5.py
patch: @@ -137,7 +137,9 @@ def __init__(
         self.pad_token_id = pad_token_id
         self.eos_token_id = eos_token_id
         self.decoder_start_token_id = decoder_start_token_id
-        self.tie_word_embeddings = True  # force it for T5 family
+        # Force tie_word_embeddings to `True` for T5 family
+        kwargs.pop("tie_word_embeddings", None)
+        self.tie_word_embeddings = True
 
         super().__init__(
             is_encoder_decoder=is_encoder_decoder,

filename: tests/models/mt5/test_modeling_mt5.py
patch: @@ -527,6 +527,11 @@ def test_custom_4d_attention_mask(self):
     def test_config(self):
         self.config_tester.run_common_tests()
 
+    def test_tie_word_embeddings(self):
+        # Force it to True, see https://github.com/huggingface/transformers/pull/43880
+        config = MT5Config(tie_word_embeddings=False)
+        self.assertTrue(config.tie_word_embeddings)
+
     def test_shift_right(self):
         config_and_inputs = self.model_tester.prepare_config_and_inputs()
         self.model_tester.check_prepare_lm_labels_via_shift_left(*config_and_inputs)

filename: tests/models/umt5/test_modeling_umt5.py
patch: @@ -338,6 +338,11 @@ def test_with_sequence_classification_head(self):
         config_and_inputs = self.model_tester.prepare_config_and_inputs()
         self.model_tester.create_and_check_with_sequence_classification_head(*config_and_inputs)
 
+    def test_tie_word_embeddings(self):
+        # Force it to True, see https://github.com/huggingface/transformers/pull/43880
+        config = UMT5Config(tie_word_embeddings=False)
+        self.assertTrue(config.tie_word_embeddings)
+
     @unittest.skipIf(torch_device == "cpu", "Can't do half precision")
     def test_model_fp16_forward(self):
         config_and_inputs = self.model_tester.prepare_config_and_inputs()