Skip to content

Commit 0e88661

Browse files
MystriMystriclaude
authored
Fixed weight tying causes corrupted safetensors file for lm_head.weight in HF checkpoint (#3764)
## Problem When training with qwen3 1.7B, all configs set `enable_weight_tying=True` in torchtitan/models/common/decoder.py, so in `to_hf`, the converter skips `lm_head.weight` because the tied weight is already covered by `model.embed_tokens.weight`. However, `fqn_to_index_mapping` is loaded verbatim from `model.safetensors.index.json`, which maps `lm_head.weight` to the 2nd safetensors file. When `to_hf` returns, the mapping references a key absent from the converted state dict, causing the creation of an output file for `lm_head.weight` with empty contents: `_FqnData` (`shape=[]`, `dtype=""`, `data_offsets=[0,0]`), and when this checkpoint is loaded, it will get rejected: ``` SafetensorError: Error while deserializing header: invalid JSON in header: unknown variant ``, expected one of `F32`, `F16`, `BF16`, `F8_E4M3`, `F8_E5M2`, `I64`, `I32`, `I16`, `I8`, `U8`, `BOOL` at line 1 column 1 ``` ## Root cause In torchtitan/components/checkpoint.py, `HuggingFaceStorageWriter` is created with the original mapping, and passed in to torch.distributed.save(). ## Reproduction I use commit #b70844d5, but should be reproducible on the latest version. Training: ```bash bash run_train.sh--module qwen3 --config qwen3_1_7b --checkpoint.last_save_model_only=True --checkpoint.last_save_in_hf=True --checkpoint.export_dtype=float16 --training.steps=3 --dataloader.dataset=c4_test --checkpoint.enable=True ``` The safetensors produced by this training run should be corrupted. Observe that there is a 78bytes `model-00002-of-00002.safetensors` in outputs/checkpoint/step-3. Verify the break by loading from the folder: ```bash python3 -c " from safetensors import safe_open safe_open('outputs/checkpoint/step-3/model-00002-of-00002.safetensors', framework='pt') " ``` ## Proposed fix See changes: In torchtitan/models/qwen3/state_dict_adapter.py, at the end of to_hf(), before return hf_state_dict, filter fqn_to_index_mapping. This prevents the empty entry from being saved. I’m offering this as a temporary fix for Qwen3 (Qwen3.5 and Llama also seem susceptible to this problem), and would love to hear from the maintainers on a more general, robust solution. --------- Co-authored-by: Mystri <hanboyou@huawei.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 2e962a1 commit 0e88661

4 files changed

Lines changed: 7 additions & 3 deletions

File tree

torchtitan/distributed/utils.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -546,9 +546,7 @@ def set_pg_timeouts(
546546
for mesh in parallel_dims.get_all_one_dimensional_meshes().values()
547547
] + [None]
548548
for group in groups:
549-
torch.distributed.distributed_c10d._set_pg_timeout(
550-
timeout, group
551-
) # pyrefly: ignore[deprecated]
549+
torch.distributed.set_timeout(timeout, group)
552550

553551

554552
@torch.no_grad()

torchtitan/models/llama3/state_dict_adapter.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ def to_hf(self, state_dict: dict[str, Any]) -> dict[str, Any]:
101101
self.model_config.enable_weight_tying # pyrefly: ignore [missing-attribute]
102102
and key == "lm_head.weight"
103103
):
104+
if self.fqn_to_index_mapping:
105+
self.fqn_to_index_mapping.pop("lm_head.weight", None)
104106
continue
105107
new_key = to_hf_map[key]
106108

torchtitan/models/qwen3/state_dict_adapter.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,8 @@ def to_hf(self, state_dict: dict[str, Any]) -> dict[str, Any]:
120120
continue
121121
# pyrefly: ignore [missing-attribute]
122122
if self.model_config.enable_weight_tying and key == "lm_head.weight":
123+
if self.fqn_to_index_mapping:
124+
self.fqn_to_index_mapping.pop("lm_head.weight", None)
123125
continue
124126
new_key = to_hf_map[key]
125127
hf_state_dict[new_key] = value

torchtitan/models/qwen3_5/state_dict_adapter.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,8 @@ def to_hf(self, state_dict: dict[str, Any]) -> dict[str, Any]:
186186
if tt_key == "lm_head.weight" and getattr(
187187
self.model_config, "enable_weight_tying", False
188188
):
189+
if self.fqn_to_index_mapping:
190+
self.fqn_to_index_mapping.pop("lm_head.weight", None)
189191
continue
190192
hf_value = value
191193
# Linear weight (out, C*T*H*W) → Conv3d weight (out, C, T, H, W)

0 commit comments

Comments
 (0)