huggingface / huggingface/diffusers

save_pretrained(safe_serialization=False) leaves the old safetensors checkpoint behind, and from_pretrained loads it instead of the new weights

オープン 初心者向け
#14,769 コメント 1 件 リアクション 0 件 担当者 0 名 GitHub で見る
bug pipelines
主要言語
Python
スター
34.5k
フォーク
7.3k
平均マージ
3日 3時間
マージ済み PR(30日)
91

説明

### Describe the bug

If a directory already has a safetensors checkpoint and I save the model again with `safe_serialization=False`, the old safetensors files are not removed. `from_pretrained` checks for safetensors first, so the next load silently returns the **old** weights. There's no error or warning.

The cleanup in `save_pretrained` ([modeling_utils.py#L804-L820](https://github.com/huggingface/diffusers/blob/83107dcbb9839e362e170dada18ed2b91849b8fd/src/diffusers/models/modeling_utils.py#L804-L820)) only deletes files matching the shard pattern (`...-00001-of-00002`). An unsharded `diffusion_pytorch_model.safetensors` and the `diffusion_pytorch_model.safetensors.index.json` never match, so they survive a `.bin` save.

What happens after saving safetensors first, then `.bin` (default `from_pretrained`):

| first save | second save | result on main |
|---|---|---|
| safetensors | bin | loads **old weights**, silently |
| safetensors (`variant="ema"`) | bin (`variant="ema"`) | loads **old weights**, silently |
| safetensors, sharded | bin | `FileNotFoundError` (old index left, its shards deleted) |

Saving an unsharded checkpoint twice in the same format works, and so does bin -> safetensors. (Going from sharded to unsharded in the same format has its own stale-index problem, which is #14719.)

This is related to #14719 but not the same problem. That issue is about deleting another variant's shards and a stale index when going sharded -> unsharded in the same format. Here it's the other format's checkpoint being left behind.

In the pipeline repro below only the diffusers components are affected: transformers components like `text_encoder/` are written as `model.safetensors` even with `safe_serialization=False`, so they never end up with two formats.

### Reproduction

```python
import tempfile, glob, os, torch
from diffusers import DiffusionPipeline

pipe = DiffusionPipeline.from_pretrained(
"hf-internal-testing/tiny-stable-diffusion-torch", safety_checker=None
)
with torch.no_grad():
pipe.unet.conv_in.weight.zero_()

with tempfile.TemporaryDirectory() as p:
pipe.save_pretrained(p) # unet/diffusion_pytorch_model.safetensors

with torch.no_grad():
pipe.unet.conv_in.weight.fill_(7.0)
pipe.save_pretrained(p, safe_serialization=False) # unet/diffusion_pytorch_model.bin

print(sorted(os.path.basename(f) for f in glob.glob(p + "/unet/*")))
reloaded = DiffusionPipeline.from_pretrained(p, safety_checker=None)
print(reloaded.unet.conv_in.weight[0, 0, 0, 0].item())
```

Output:

```
['config.json', 'diffusion_pytorch_model.bin', 'diffusion_pytorch_model.safetensors']
0.0
```

Expected `7.0`.

### Logs

```shell
No error or warning is printed.
```

### System Info

- 🤗 Diffusers version: 0.41.0.dev0 (main @ 83107dc)
- Platform: Linux-7.0.0-31-generic-x86_64-with-glibc2.43
- Running on Google Colab?: No
- Python version: 3.13.3
- PyTorch version (GPU?): 2.14.0+cu130 (False)
- Huggingface_hub version: 1.31.0
- Transformers version: 5.17.0
- Accelerate version: 1.15.0
- Safetensors version: 0.8.0
- Using GPU in script?: No
- Using distributed or parallel set-up in script?: No

### Who can help?

@sayakpaul @DN6

I have a small fix ready (removes the other format's weights file and index for the same variant when saving, plus a regression test). Would a PR be welcome? One thing to decide: it changes behaviour for anyone who saves both formats into one folder on purpose. I couldn't find that pattern anywhere in the repo, but it's your call.

コントリビューションガイド

コントリビューションガイドを開く

調査の方向性

Start in src/diffusers/models/modeling_utils.py at lines 804-820 and trace the cleanup performed by save_pretrained when switching from safetensors to .bin. Reproduce the issue with the provided DiffusionPipeline script, then add or run the regression test mentioned in the issue. Done means the old safetensors checkpoint and index no longer remain for the same variant, and from_pretrained loads the newly saved weights.

索引モデルが issue の本文から書いたものです。

評価

技術スタック
python, pytorch
領域
machine-learning
issue の種類
バグ
難易度
2/5
見積もり時間
1〜3時間
活発さ
活発
明瞭さ
明確に書かれている
初心者へのやさしさ
78/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。