`save_pretrained` corrupts keys (`encoder.encoder.*`) for trust_remote_code / from_config models via non-idempotent reverse rename
Author: Bluear7878
Pull request: no

### System Info

```
- `transformers` version: 5.5.3
- Platform: Linux-5.15.0-100-generic-x86_64-with-glibc2.35
- Python version: 3.11.12
- Huggingface_hub version: 1.6.0
- PyTorch version (accelerator?): 2.10.0+cu128 (CUDA)
- Using GPU in script?: No (pure key-renaming logic)
```

Also reproduces on `main` (`5.13.0.dev0`): the `nomic_bert` conversion mapping is unchanged on `main`
and still leads with the unanchored `WeightRenaming("encoder.layers", "layers")`, and the
`WeightTransform` rename regex is still unanchored. The reproduction below uses only `transformers`
internals, so it is independent of platform / torch version.

### Who can help?

@Cyrilvallez — this directly extends your #45567 (`PrefixChange` filter in `revert_weight_conversion`).

### Information

- [X] My own modified scripts

### Tasks

- [X] My own task or dataset (give details below)

### Reproduction

`save_pretrained` silently corrupts a checkpoint for models that were **not** loaded through the core
weight-conversion engine (`model._weight_conversions is None`) — e.g. a `trust_remote_code` model whose
remote `from_pretrained` builds the state dict directly, or a model built with `from_config`.

`revert_weight_conversion` (`core_model_loading.py`) then fabricates the conversion from the hardcoded
`conversion_mapping.py` table and applies its **reverse**. For a mapping whose reverse rename is not
idempotent — an unanchored `WeightRenaming` whose target string is a substring of its source — reverting
keys that are *already* in their on-disk layout double-prepends the prefix.

Minimal, no download (transformers internals only):

```python
from transformers.core_model_loading import WeightRenaming

# nomic_bert / jina_embeddings_v3 first mapping entry: disk "encoder.layers" -> canonical "layers"
rev = WeightRenaming(r"encoder.layers", r"layers").reverse_transform()
print(rev.rename_source_key("encoder.layers.0.attn.out_proj.weight")[0])
# -> 'encoder.encoder.layers.0.attn.out_proj.weight'   (expected: unchanged)
```

End-to-end:

```python
from transformers import AutoModel
m = AutoModel.from_pretrained("nomic-ai/nomic-embed-text-v1", trust_remote_code=True)
m.save_pretrained("/tmp/nomic_roundtrip")
AutoModel.from_pretrained("/tmp/nomic_roundtrip", trust_remote_code=True)
# every `encoder.layers.*` backbone weight is reported missing and silently kept random-initialised
```

### Expected behavior

A `save_pretrained` → `from_pretrained` round-trip should preserve the checkpoint keys (no
`encoder.encoder.*`), and no backbone weight should be silently dropped on reload.

---

### Root cause

`reverse_transform()` swaps source/target, and `rename_source_key` matches with an **unanchored**
`search()` then `replace(..., 1)`, so the reverse pattern `layers` matches the `layers` inside an
already-original key `encoder.layers.0...` and prepends another `encoder.`. The reverse rename is
**non-idempotent** on keys already in on-disk layout. Because `revert_weight_conversion` fabricates this
reverse from the hardcoded table whenever `_weight_conversions is None`, it is applied to keys that were
never converted on load → silent corruption.

This is the same family of save round-trip corruption that #45567 fixed with the `PrefixChange` filter,
but that filter only drops `PrefixChange` transforms. The affected entries here are plain unanchored
`WeightRenaming`s whose target is a substring of their source — verified still present on `main`:
`nomic_bert`, `jina_embeddings_v3`, `llava` (+ aliases), `emu3`, `colqwen2`, and the `detr` family.

### Proposed fix

Only revert the fabricated mapping when the in-memory keys are actually in the canonical (post-load)
layout: if applying the **forward** renamings would still rename a key, the keys are already in on-disk
layout (the `trust_remote_code` / `from_config`-from-disk case) and there is nothing to revert. This
preserves the documented `from_config` → original-format round-trip (the reason the `None` fallback
exists) while fixing the whole substring-doubling family at once.

Alternatives I considered and ruled out:
- **Anchoring each mapping** (`^encoder\.layers`): only patches one model, and the `^` anchor breaks the
  `nomic_bert` wrapper classes (`base_model_prefix` nesting under `nomic_bert.`) on both load and save.
- **Skipping revert whenever `_weight_conversions is None`**: regresses the documented `from_config` →
  original-format round-trip, which relies on that fallback.
- **Migrating these mappings to `PrefixChange`** (à la #45567): does not address the unanchored
  `rename_source_key` root cause, and the `X.model -> X` subclass (llava lm.model + aliases, emu3,
  colqwen2, detr) is undecidable per-key (a canonical `a.b.x` is indistinguishable from a disk `a.b...`).

A PR implementing this engine-level guard (with the regression tests above) is up — see the linked PR.


Comments: