SIGN IN SIGN UP

Port ESMC and ESMFold2 to Transformers (#46419)

* Port ESMC + ESMFold2 model code from fork onto v5 main (baseline, unadapted)

Moves the purely-additive model code from the Biohub fork
(github.com/Biohub/transformers @ f9a5a374b, based on v4.57.6) onto a
branch off current main (v5.10.0.dev0). This is the verbatim fork code as
a starting point; v5 convention adaptation (attention interface, modular,
__all__, nested-config round-trip) is follow-up work per the port plan.

Contents:
- src/transformers/models/esmc/    (6 files: config, sae config, modeling,
  sae modeling, tokenizer) — imports and exports cleanly under v5.
- src/transformers/models/esmfold2/ (24 files incl. deferred kernels/,
  distributed/, experimental) — config + modeling import; ESMFold2Model is
  defined but not yet exported (no __all__ — known adaptation item).

Auto-registration adapted to the v5 layout (NOT a verbatim copy of the
fork's 4 hook diffs):
- models/__init__.py: from .esmc/.esmfold2 import *
- auto/auto_mappings.py: CONFIG_MAPPING_NAMES + SPECIAL_MODEL_TYPE_TO_MODULE_NAME
  (these moved out of configuration_auto.py in v5; MODEL_NAMES_MAPPING was
  dropped in v5 so the fork's hunk for it has no equivalent).
- auto/modeling_auto.py: base + masked-LM + seq-cls + token-cls maps.
- auto/tokenization_auto.py: flat ("esmc", "ESMCTokenizer") form (v5 dropped
  the (slow, fast) tuple format).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Strip vendored Triton kernels + set_kernel_backend selector from esmfold2

Transformers loads fused kernels from the Hub via the `kernels` library
(@use_kernel_forward_from_hub / register_kernel_mapping), not by vendoring
Triton in a model dir. Remove the fork's bespoke acceleration stack and
leave a single pure-PyTorch path; fused ops can return later via a Hub
kernels repo as an opt-in follow-up.

Removed:
- src/transformers/models/esmfold2/kernels/ (8 Triton files, ~3.3k LOC).
- The shared `set_kernel_backend` selector across the module tree, which
  drove BOTH backends: "fused" (vendored Triton) and "cuequivariance"
  (external lib). Both gone — the cueq import block and BACKEND_*/
  _VALID_BACKENDS/_fused_active/_cueq_active helpers with them.
- Per-module fused/cueq branches and now-dead helpers (_can_use_*,
  _fused_trimul_with_residual, split_kernel_weights, _kernel_flow_direction,
  Transition._swiglu_pre_w3/_addmm_residual, DropoutResidual fused impl).
- The vestigial no-op set_kernel_backend hooks in distributed/utils.py and
  modeling_esmfold2_experimental.py.

Kept intact: the independent `set_chunk_size` memory knob, and the optional
flash-attn / transformer_engine guards.

Verified: both modeling modules import; CPU smoke test runs AttentionPairBias,
TriangleMultiplicativeUpdate (both orientations), Transition (chunked ==
unchunked), DropoutResidual, and FoldingTrunk end-to-end on the pure-PyTorch
path. No new ruff errors introduced (9 pre-existing fork lint items remain for
the later `make style` pass).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Remove vendored distributed/ (2D context-parallel) stack from esmfold2

The distributed/ package is a NVIDIA/MIT-licensed 2D context-parallel
implementation of the folding trunk (DTensor + DeviceMesh + NCCL) for
multi-GPU 6B inference. It is dropped from the port because:

- It is not imported by any model code (core, config, __init__, or the
  experimental file) — fully inert in the package.
- It is broken on import: all 7 files import from
  `projects.huggingface.transformers.models.esmfold2...` (the fork's
  internal monorepo path), so `import transformers.models.esmfold2.distributed`
  raises ModuleNotFoundError. It never worked in the standalone layout.
- It is NVIDIA/MIT-licensed, unlike the Apache/Biohub model code.
- Transformers expresses parallelism declaratively via `base_model_tp_plan`
  / `tp_plan="auto"`, not a vendored per-model DTensor/NCCL stack.

Nothing unique is lost: the math it shards already exists as the
pure-PyTorch reference in modeling_esmfold2_common.py. If multi-GPU
inference is needed later, author a tp_plan on ESMFold2Model fresh.

Verified: nothing references distributed/; `import transformers` and the
esmfold2 modeling module still import cleanly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Refactor ESMC attention onto the v5 ALL_ATTENTION_FUNCTIONS interface

Replace ESMC's bespoke attention dispatch with the standard v5 interface,
mirroring models/esm (a bidirectional encoder). Behaviour is preserved
bit-exactly at all real-token positions.

Before: a hand-rolled `_scaled_dot_product_attention` choosing xformers ->
flash-attn-2 -> SDPA, a `_FlashMultiHeadAttention` subclass with manual
unpad_input/pad_input in ESMCModel.forward, a `_TritonRotaryEmbedding`, and
a `seq_id`-threaded chain mask.

After:
- Module-level `eager_attention_forward`; `MultiHeadAttention` dispatches via
  `ALL_ATTENTION_FUNCTIONS.get_interface(config._attn_implementation, ...)`
  with q/k/v shaped (B, H, S, Dh) and `scaling=head_dim**-0.5` (RoPE is
  rotation-invariant to scaling, so it stays in the module). output_attentions
  forces the eager interface so probabilities remain observable.
- ESMCModel.forward builds the 4D mask once: `create_bidirectional_mask` for
  the padding case (works for eager/sdpa/flash), and a block-diagonal additive
  bias for multi-chain `sequence_id` (eager/sdpa; flash multi-chain still
  raises). Removes the unpad/pad varlen path.
- Drops the xformers / flash-attn / triton-rotary import machinery and their
  warnings. transformer_engine LN/MLP fusion is unchanged. The
  `_supports_sdpa/_supports_flash_attn/_supports_attention_backend` flags
  (already declared) are now actually honoured.

flash-attn-2 is still supported — now via the standard attn_implementation
backend rather than bespoke dispatch.

Verified: loading identical weights into the refactored model reproduces the
pre-refactor outputs to 0.000e+00 at every non-padding position (plain,
padding-mask, and multi-chain cases); the only differences are at padding
positions, which are masked out downstream. eager and sdpa agree bit-exactly
on valid-token logits; MaskedLM/SequenceClassification/TokenClassification
and output_attentions all work.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Remove transformer_engine dependency from ESMC (pure-PyTorch only)

No other Transformers model depends on transformer_engine; drop it from
ESMC. TE provided fused fp32-reduction LayerNorm+Linear / LayerNorm+MLP
kernels, but the model already shipped pure-PyTorch fallbacks whose
parameter names (`layer_norm_weight`, `fc1_weight`, `fc2_weight`, ...) match
the published-checkpoint layout. Make those the only path.

- Drop the `transformer_engine` import + `_te_available` guard + the
  "TE not installed" warning.
- `_swiglu_ln_ffn` / `_make_attn_layernorm_qkv` / `_make_attn_out_proj` now
  unconditionally return the pure-PyTorch modules (`_PyTorchLayerNormMLP`,
  `_PyTorchLayerNormLinear`, `nn.Linear`).
- Remove dead `_SwiGLU` class (the MLP fallback inlines silu(x1)*x2).

If exact TE numerics (fp32-reduction LayerNorm) are ever required, that
belongs in a Hub kernel via the `kernels` library, not a hard dependency.

Verified: strict state_dict load from the pre-change baseline succeeds
(parameter names unchanged -> published checkpoints still load), and
last_hidden_state is bit-identical (0.0) at all valid positions for the
plain, padding-mask, and multi-chain cases. Locally TE was never installed,
so this is the exact path that already ran.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Modernize ESMC rotary to the standard (cos, sin) + apply_rotary_pos_emb convention

Replace the flash-attn-style cache-based rotary with the standard
Transformers convention used by esm/llama, as the last code-shaping step
before authoring modular_esmc.py.

Before: a stateful `RotaryEmbedding` (per-attention-module) caching cos/sin,
`forward(q, k)` returning rotated tensors, a custom `_apply` override to keep
`inv_freq` fp32 across device casts, plus `_rotate_half` / `_apply_rotary_emb_torch`.

After:
- `rotate_half` + `apply_rotary_pos_emb` (identical to esm/llama).
- `ESMCRotaryEmbedding(config)` -> `(cos, sin)`, computed once in
  `ESMCModel.forward` and threaded down (position_embeddings) through the
  stack/block to attention, mirroring esm. `inv_freq` is fp32 and
  non-persistent (matches the old behaviour: no rotary tensors in the
  checkpoint), and cos/sin are built in fp32 then cast.
- Add `config.rope_theta` (default 10000.0, the previously-hardcoded base).
- `_init_weights` recomputes `inv_freq` for `ESMCRotaryEmbedding` (meta-init safe).

Verified: strict state_dict load from the saved baseline succeeds (rotary
buffers are non-persistent, so keys are unchanged -> published checkpoints
still load), and last_hidden_state is bit-identical (0.0) at all valid
positions for plain, padding-mask, and multi-chain. The fp32 matmul-based
freqs equal the old `outer(t, inv_freq)`; same RoPE math, idiomatic shape.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Add modular_esmc.py; generate modeling_esmc.py from it

ESMC now follows the modular convention. modular_esmc.py is the source of
truth; modeling_esmc.py is generated by utils/modular_model_converter.py and
carries the auto-generated header.

Reuse from esm (the natural parent — also a bidirectional protein encoder):
`eager_attention_forward`, `rotate_half`, and `apply_rotary_pos_emb` are now
imported from ..esm.modeling_esm and inlined into the generated file with
`# Copied from` headers (so they stay in sync). `rotate_half` is pulled in
transitively as a dependency of `apply_rotary_pos_emb`, matching the qwen3
pattern.

Everything else stays ESMC-specific and is defined in the modular file: the
SAE-integrated ESMCModel + ForMaskedLM/SequenceClassification/
TokenClassification, the fused-LN MultiHeadAttention, SwiGLU FFN,
TransformerStack, ESMCRotaryEmbedding, and the SAE-carrying output
dataclasses. As expected for this architecture the dedup is modest; the win
is convention compliance + auto-sync of the shared functions.

The modular file was ruff-fixed/formatted (Optional[X] -> X | None, import
order) before regeneration, so both files are now ruff-clean.

Verified: `check_modular_conversion.py` passes (files in sync); `transformers`
imports; and loading identical weights reproduces the pre-conversion
last_hidden_state bit-for-bit (0.0) at all valid positions for plain,
padding-mask, and multi-chain inputs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Fix ESMC checkpoint loading + bidirectional attention (verified vs fork on ESMC-6B)

Two bugs surfaced only when loading the real biohub/ESMC-6B checkpoint and
running an unpadded sequence (prior parity tests all used padded inputs):

1. _init_weights re-initialized loaded nn.Linear weights. It used
   `module.weight.data.normal_()`, which writes through `.data` and bypasses
   the `_is_hf_initialized` flag that transformers sets on loaded params. So
   `from_pretrained` clobbered out_proj / lm_head with random init (silently —
   no missing-keys warning), while custom/LayerNorm/Embedding modules survived
   only because _init_weights had no branch for them. Switch to the
   flag-respecting `transformers.initialization` helpers (init.normal_,
   init.zeros_, init.copy_), matching esm/base. (This bug is latent in the
   v4 fork too; it only bites under the v5 init-after-load flow.)

2. Attention defaulted to causal on unpadded inputs. ESMC is a bidirectional
   encoder, but MultiHeadAttention never set `is_causal`, so the sdpa/flash
   interface fell back to `getattr(module, "is_causal", True)` and applied
   causal masking whenever `attention_mask is None`. Set `self.is_causal =
   False`. (Introduced by the attention-interface refactor; missed because
   every earlier parity test passed a padded 4D mask.)

Source of truth is modular_esmc.py; modeling_esmc.py regenerated.

Verified: loading biohub/ESMC-6B (and ESMC-300M) into the refactored model
and into the original fork code yields BIT-IDENTICAL logits (max|Δ| = 0.0)
and identical argmax predictions on an 80-residue sequence. State-dict loads
with no remapping (240 TE `_extra_state` keys ignored as before).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Fix ESMCTokenizer docstring example + add tokenizer tests

The ESMCTokenizer behaves correctly under v5 (verified against the published
biohub/ESMC-6B tokenizer.json), but its docstring doctest example was wrong:
it listed 21 ids for a 20-residue sequence with two residues mis-ordered/
dropped. Correct it to the actual output (22 ids, <cls> ... <eos>).

Add tests/models/esmc/test_tokenization_esmc.py (fast-only tokenizer, so it
mirrors models/esm's plain-TestCase style rather than the slow vocab-file
setup): documented example, character-level tokenize, <cls>/<eos> wrapping,
special-token ids (incl. bos==cls alias), batch padding + attention_mask,
chain-break token, mask token, unknown-residue -> <unk>, decode round-trip,
save/load round-trip, and a @slow integration test asserting AutoTokenizer
resolves to ESMCTokenizer and the hub tokenizer matches the code-built one.

Verified: 10 passed, 1 slow-skipped (the slow test passes with RUN_SLOW=1).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Drop vestigial vocab_file entry from ESMCTokenizer VOCAB_FILES_NAMES

ESMCTokenizer is fast-only (built from a tokenizer object); it never reads or
writes a slow `vocab.txt`. Declare only `tokenizer_file: tokenizer.json`.
save_pretrained already emits just tokenizer.json + tokenizer_config.json;
tokenizer tests still pass (10 passed, 1 slow-skipped).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Add ESMC model tests + make _init_weights cover all modules

Add tests/models/esmc/test_modeling_esmc.py (ModelTesterMixin +
PipelineTesterMixin), mirroring models/esm: ESMCModelTester +
create_and_check for ESMCModel / ForMaskedLM / ForSequenceClassification /
ForTokenClassification, plus a @slow masked-LM integration test on
biohub/ESMC-300M. ESMCModel has no pooler, so the base-model check asserts
only last_hidden_state.

Fix surfaced by test_can_init_all_missing_weights: _init_weights only handled
nn.Linear, so the fused-LN modules, nn.LayerNorm and nn.Embedding were left
uninitialized on a from-scratch / meta-device init. Handle the custom
_PyTorchLayerNormLinear / _PyTorchLayerNormMLP explicitly (their `weight` is a
Linear weight, not a norm — the base initializer matches norms by class-name
substring and would wrongly set it to ones), the rotary buffer explicitly,
and delegate nn.Linear / nn.Embedding / nn.LayerNorm to super()._init_weights.

Skip test_retain_grad_hidden_states_attentions: ESMC returns `hidden_states`
as one stacked tensor (consumed by the SAE feature), not the live per-layer
tensors, so grad cannot flow back to the returned copy — intentional.

Verified: full model test file = 92 passed, 102 skipped, 0 failed; the @slow
integration test passes; and from_pretrained still loads biohub/ESMC weights
bit-exactly (the flag-respecting init helpers don't clobber loaded params).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Add ESMC docs + resolve auto_docstring checkpoint for the classification heads

Add docs/source/en/model_doc/esmc.md (short: overview, links to the
biohub/ESMC-{300M,600M,6B} checkpoints and the evolutionaryscale/esm repo, a
usage snippet, and [[autodoc]] for ESMCConfig, ESMCSAEConfig, ESMCTokenizer,
ESMCModel, ESMCFor{MaskedLM,SequenceClassification,TokenClassification},
ESMCSAEModel) and register it in _toctree.yml (alphabetically after ESM).

ESMCForSequenceClassification/ForTokenClassification have no Examples block in
their forward docstrings, so auto_docstring could not find a checkpoint and
errored on import. Add the standard checkpoint sentence with a Hub link
([Biohub/ESMC-600M-2024-12](...)) to the ESMCConfig docstring; auto_docstring
falls back to the config's checkpoint, resolving it for all model classes at
once (no per-class decorator needed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Remove the SAE (esmc_sae) from ESMC — deferred to a follow-up PR

The sparse-autoencoder is architecturally distinct from ESMC (a linear
encoder + top-k sparsity + linear decoder operating on frozen hidden states,
not a transformer) and has a non-standard per-layer from_pretrained/
save_pretrained path. Keep it out of the core ESMC PR; it will land as its
own follow-up (recoverable from this commit's parent).

Removed:
- modeling_esmc_sae.py and configuration_esmc_sae.py.
- esmc_sae auto-registration (CONFIG_MAPPING_NAMES, MODEL_MAPPING_NAMES, and
  the esmc_sae -> esmc SPECIAL_MODEL_TYPE_TO_MODULE_NAME mapping).
- The SAE integration woven through ESMCModel and the heads: add_sae_models,
  _get_sae_outputs / _get_sae_layer_num_requested / _validate_sae_inputs /
  _SAE_KEY_RE / _sae_models, the compute_sae/normalize_sae params, and the
  sae_outputs field on all four output dataclasses. The forward simplifies
  accordingly (layers_to_collect now only serves output_hidden_states; the
  SAE-only bool_mask is gone).
- ESMCSAEConfig/ESMCSAEModel autodoc from the model doc.

Source of truth is modular_esmc.py; modeling_esmc.py regenerated.

Verified: ESMCSAE* no longer importable and esmc_sae deregistered; ruff clean;
model + tokenizer tests pass (102 passed, 103 skipped); and fork-vs-new logits
on biohub/ESMC-300M remain bit-identical (max|Δ| = 0.0) — the removal is
output-neutral for the core model.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Normalize ESMC docstrings to satisfy check_docstrings

`make fix-repo`/check_docstrings normalizations on the ESMC public objects:
- ESMCTokenizer: default values use single backticks (`"<unk>"`) per the
  canonical "*optional*, defaults to ..." format.
- ESMCModel / ESMCForMaskedLM / ESMCForSequenceClassification: reorder the
  forward docstring argument entries to match the signature order
  (output_attentions before labels) and add the missing blank line before the
  Examples block.

Docstring-only (no logic change); modular and generated modeling stay in sync.
ESMC now passes check_repo, check_copies, check_inits, check_dummies,
check_modular_conversion, and check_docstrings (remaining repo-wide failures
are pre-existing Qwen output-doc errors and Phase-B esmfold2, neither ESMC).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Get ESMFold2 importing: add __all__ + load ESMC via the Auto registry

- Add `__all__ = ["ESMFold2Model"]` to modeling_esmfold2.py so the model is
  exported (`from transformers import ESMFold2Model` now works; previously the
  class was defined but the module had no `__all__`).
- Replace the hard cross-model import `from ..esmc.modeling_esmc import
  ESMCModel` in `load_esmc` (both modeling_esmfold2.py and the experimental
  file) with `AutoModel.from_pretrained(...)`. ESMC is a shared, frozen 6B
  backbone loaded separately from its own repo (`config.esmc_id`); resolving it
  through the Auto registry (model_type "esmc" -> ESMCModel) keeps esmc and
  esmfold2 as separate model directories without a runtime cross-dir import.

Verified: ESMFold2Model + ESMFold2Config export; core + experimental modules
import; no `..esmc` imports remain in the esmfold2 dir; AutoModel.from_config
resolves ESMCConfig -> ESMCModel. (Also removed the empty kernels/ and
distributed/ dirs left behind by their earlier git rm.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Drop the experimental ESMFold2 variant + esmfold2_v2 mapping (release-only)

Scope the core ESMFold2 PR to the release model. The experimental
(legacy/dev binder-design) variant is deferred like the ESMC SAE.

- Delete modeling_esmfold2_experimental.py and remove it from the package
  __init__ and from the ESMFold2Model.from_pretrained `config.type ==
  "experimental"` routing (from_pretrained now always builds ESMFold2Model).
- ESMFold2Config: keep the `type` field for checkpoint compat but accept only
  "release"; fix the docstring example to use ESMFold2Model. Update the two
  stale `ESMFold2ExperimentalModel` references in comments/docstrings.
- Remove the `esmfold2_v2 -> esmfold2` SPECIAL_MODEL_TYPE_TO_MODULE_NAME entry
  (no v2 variant ships).

Verified: ESMFold2Model + ESMFold2Config import; ESMFold2ExperimentalModel
gone; no experimental/v2 references remain.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Remove TransformerEngine fp8 path from ESMFold2

ESMFold2's only TransformerEngine dependency was the optional fp8
quantization of the (now TE-free) ESMC backbone. Drop the import guard,
the dead _convert_te_modules_to_fp8_inplace walker, the "fp8" precision
option, and the fp8 padding/autocast plumbing. _lm_precision_context is
now a plain bf16 autocast; ESMC loads at bf16 (default) or fp32.

No other Transformers model depends on transformer_engine.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Route ESMFold2 plain self-attention through the v5 attention interface

SWA3DRoPEAttention's plain softmax(QKᵀ)V core now dispatches through
ALL_ATTENTION_FUNCTIONS / a local eager_attention_forward, keyed on
config._attn_implementation, with the sliding window expressed as an
additive attention mask. The custom flash-attention path (native
bidirectional window_size + varlen for packed inputs) is kept as an
opt-in backend, now gated on _attn_implementation == "flash_attention_2"
instead of auto-selecting whenever flash-attn is importable — so the
default is sdpa (matching the fork's SDPA fallback bit-for-bit) and
flash is opt-in, per v5 conventions.

ESMFold2Model declares _supports_sdpa / _supports_flash_attn /
_supports_attention_backend and, after construction, attaches its shared
config to every SWA3DRoPEAttention (the atom encoders/decoders build them
from explicit dims), so dispatch stays live under set_attn_implementation.
is_causal=False guards against the interface defaulting to causal when no
mask is passed. Pair-bias (AttentionPairBias) and triangular math are left
untouched.

Validated on CPU vs the pre-refactor forward (random weights): sdpa
max|Δ|=0.0 (bit-exact), eager max|Δ|=1.3e-3 (bf16 softmax precision).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Convert ESMFold2Config to the v5 @strict / PreTrainedConfig style

The config was one of ~4 holdouts still using the old `def __init__(**kwargs)`
+ hand-rolled `to_dict` style; 460/463 configs use `@strict` dataclass
`PreTrainedConfig`. Rewrite it to match, mirroring `models/esm` (ESMFold v1):

- Every sub-config (MSAEncoder/Parcae/LMEncoder/AtomAttention/FoldingTrunk/
  InputsEmbedder/DiffusionModule/DiffusionStructureHead/ConfidenceHead) is now a
  `@strict PreTrainedConfig` with typed fields + defaults, instead of a plain
  `@dataclass`.
- Nesting is declared via `sub_configs = {...}` on each parent + a `__post_init__`
  that turns `dict -> SubConfig(**dict)`. This deletes the brittle hand-rolled
  `ESMFold2Config.to_dict()` (base class now serializes via `sub_configs`) and
  gives recursive `_attn_implementation` propagation to sub-configs for free.
- Top config switches to `@auto_docstring(checkpoint="biohub/ESMFold2")`, which
  resolves the outstanding `check_docstrings` failure for ESMFold2Config.
- `__all__` is reduced to `["ESMFold2Config"]` (sub-configs are implementation
  detail, matching ESM which exports only `EsmConfig`).

check_config_attributes: top config keeps a precise 4-item allow-list (type +
three training/experimental recipe knobs not read by the core inference path);
the 9 sub-configs are allow-listed wholesale because their fields are threaded
into submodules as explicit dims (e.g. `d_atom=cfg.inputs.atom_encoder.d_atom`),
which the checker's `config.<attr>` heuristic cannot trace.

Verified: default + nested-dict construction, save/load round-trip (to_dict
identical, sub-config types preserved), recursive attn-impl propagation, type
validation, unknown-kwarg tolerance; the tiny ESMFold2Model still builds and the
SWA attention equivalence is unchanged. ruff + check_docstrings +
check_config_attributes all clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Add ESMFold2 tests + sub-config model_types

ESMFold2 is an all-atom structure predictor whose forward takes ~18 structural
feature tensors and returns a plain dict (not a ModelOutput), so it doesn't fit
ModelTesterMixin. Following the sanctioned pattern for such models, register the
test file in check_repo's TEST_FILES_WITH_NO_COMMON_TESTS and provide focused
coverage:

- ESMFold2ConfigTest: ConfigTester common tests (incl. composite sub-config
  save/load), `type` validation, nested round-trip, attn-impl propagation.
- ESMFold2ModelTest (CPU): full pure-PyTorch forward via infer_protein with no
  ESMC backbone (LM conditioning skipped) under both sdpa and eager; SWA config
  dispatch; weight-level save/load fidelity + a usable reloaded model.
- ESMFold2IntegrationTest: @slow real-weight fold on biohub/ESMFold2 (GPU-gated).

To make ConfigTester's composite test pass, each sub-config now declares a
unique `model_type` (e.g. "esmfold2_inputs_embedder") — the CLIP pattern — so
that `SubConfig.from_pretrained(<composite dir>)` extracts the matching nested
dict (configuration_utils keys this off model_type). ESM dodges this test
because its sub-config is None by default; ESMFold2's are always present.

check_repo: `modeling_esmfold2_common` (shared building blocks, no public model)
is added to get_model_modules' _ignore_modules. The remaining check_repo item
for esmfold2 is the model-doc page, which is a separate pending task.

The tiny test config encodes two real sizing constraints discovered via the
forward smoke: 3D RoPE needs 3*n_spatial + n_uid <= head_dim//2, and
inputs.d_inputs == 67 + d_token//2 == diffusion_module.c_s_inputs.

7 non-slow tests pass; ruff + check_config_attributes + check_docstrings clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Add ESMFold2 model doc page

Short overview page (mirrors esmc.md): describes the all-atom structure
predictor and its separately-loaded ESMC backbone, a usage snippet
(infer_protein_as_pdb), and autodoc for ESMFold2Config + ESMFold2Model. Registered
in _toctree.yml after ESMC. Resolves the final check_repo "objects documented"
item for esmfold2.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Ruff-format the ESMFold2 fork files (deferred style sweep)

Runs the previously-deferred ruff sweep over the esmfold2 files now that the
substantive work (TE removal, attention interface, config) is done: drops the
`# coding=utf-8` lines (UP009), rewrites the `_msa_kwargs = dict(...)` as a
literal (C408), sorts imports, and applies `ruff format`. No logic changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Convert ESMCConfig to the v5 @strict / @auto_docstring style

ESMCConfig was the last non-@strict direct config (old def __init__ style),
flagged by the TRF010 model-structure rule (make typing). Convert it to the
dataclass `@strict` form with class-level typed fields, mirroring ESM, and
switch the docstring to `@auto_docstring(checkpoint="biohub/ESMC-600M")` so the
standard/base fields are auto-documented and the classification heads keep a
checkpoint to fall back on. Verified: defaults, override, save/load round-trip,
real-config load (biohub/ESMC-300M), and the 92 esmc model tests still pass;
make typing + check_docstrings + check_config_attributes clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Fix ESMFold2 integration test: ubiquitin + correct 0-1 pLDDT/pTM scale

The @slow integration test used a poorly-folding sequence and asserted pLDDT on
a 0-100 scale, but this model emits pLDDT/pTM on a 0-1 scale
(_categorical_mean(..., start=0, end=1)) — so the old `mean > 50` assertion
could never pass. Switch to ubiquitin (PDB 1UBQ, a textbook well-folding 76-mer),
draw 8 diffusion samples and assert on the best one (these folders rank N samples
and return the best): best pLDDT > 0.7 and best pTM > 0.6.

Validated end-to-end on real weights (biohub/ESMFold2 + the 6B ESMC backbone,
CPU/fp32 since this box's GPU can't JIT-compile): weights load with 0 missing/
unexpected keys, and ubiquitin folds to best pLDDT ~0.80 / pTM ~0.74, GB1 to
~0.85 / ~0.78 — confirming the port produces correct, confident structures.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Make ESMFold2 dtype-honest: drop in-model autocast, support from_pretrained(dtype=)

ESMFold2 was the only ported model relying on in-model `torch.amp.autocast`
(`use_amp = device.type=="cuda"`) to (a) run matmuls in bf16 and (b) keep
norms/softmax in fp32. That made it GPU-only for bf16 and silently broke CPU and
`from_pretrained(dtype=bfloat16)`. Replace it with the standard Transformers
idiom — the model runs in its loaded dtype, with numerically-sensitive ops
explicitly pinned to fp32:

- Add an fp32-pinned `LayerNorm(nn.LayerNorm)` (computes in fp32, returns the
  input dtype, keeps fp32 weights under `from_pretrained(dtype=bf16)`) and use it
  for every `nn.LayerNorm`; the affine-free `RMSNorm`/`F.rms_norm` stay in the
  activation dtype (they ran bf16 under autocast too). Pin the remaining
  softmaxes with `dtype=torch.float32`.
- Remove the three in-model autocast regions (trunk, confidence trunk, ESMC
  `_lm_precision_context`) and convert the Kabsch/SVD `enabled=False` islands to
  plain `.float()`.
- The model interleaves fp32 islands (geometry, coords, one-hot/continuous input
  features, the fp32-accumulated confidence pair, the fp32 triangular contract)
  with bf16 compute, which autocast used to bridge; add explicit `.to(dtype)`
  casts at each island→matmul boundary (atom encoder, inputs embedder, rel_pos,
  token_bonds, distogram head, MSA embed, diffusion s/z/coords conditioning,
  confidence pair). Also align the ESMC hidden states to the LM projection dtype,
  which additionally lets a bf16 backbone feed an fp32 trunk (no more
  `esmc_precision="fp32"` requirement on CPU).

Validated vs the autocast baseline (ubiquitin, seed 0): GPU bf16 0.798/0.737,
GPU fp32 0.800/0.740, CPU fp32 0.801/0.740 — all match 0.80/0.74. CPU bf16 runs
correctly too (its matmul accumulates in fp32, ~4e-3 rel-err), just slower.
make typing / check_docstrings / check_config_attributes / ruff clean; 7 tests +
the @slow GPU integration test (now bf16) pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Apply make fix-repo sweep + bf16 ESMFold2 usage example

`make fix-repo` adds the `add_dates` "contributed on …" stamp to the esmc/esmfold2
doc pages and ruff-reformats a few esmc files (line-length only, no logic change).
Also update the esmfold2.md usage example to the recommended `dtype=torch.bfloat16`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Keep ESMFold2 sub-configs internal (drop their model_types)

The sub-config model_types added earlier (for the ConfigTester composite test)
made `make fix-repo` auto-register all 9 in CONFIG_MAPPING_NAMES, which then
failed check_repo (a registered config must be importable from `transformers`,
but these are intentionally not exported — `__all__ = ["ESMFold2Config"]`, like
ESM exporting only EsmConfig). Rather than expose 9 internal architecture configs
publicly (with the docs/docstring burden that implies), drop the sub-config
model_types so they stay internal, and skip the composite "load each sub-config
standalone from the parent dir" ConfigTester check, which doesn't apply to
internal sub-configs. Serialization still round-trips via `sub_configs` +
`__post_init__` (no model_type needed); config tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Add kernel, update docs

* Some cleanup

* More cleanup

* dtype cleanups to make outputs match the original fork

* More chasing dtypes

* More bf16 to match the original + bump speed

* Remove more float upcasts for speeds + closer fork match

* Remove more float upcasts for speeds + closer fork match

* More trunk norm matching

* Big general cleanup, no more _common.py, lots of repo standardization

* Remove a lot of redundant dtype casts now that we no longer need them

* Rename a lot of config attributes to the standard ones

* Modular cleanup, use standard output types

* More modular cleanup

* More modular cleanup

* More modular cleanup

* Even more modular cleanup

* No more modular ESMFold2

* Dead code cleanup

* No more apply_torch_compile

* Simplify the obvious einsums but leave the very messy ones

* Config cleanup, stop passing naked kwargs around

* Small docs cleanups

* Get rid of CPU-only path that we don't need

* Test cleanup

* Doc dates

* Comments cleanup

* Re-add the kernel after rebase

* Fix dates

* Push ESMC fixes

* Fix the tokenizer to match Llama

* Make the token classifier generic, attention cleanup, big modular reductions for ESMC

* Import the sequence classifier

* More review fixes

* make fix-repo

* tokenizer fixup

* Remove FA2 for esmfold2, lift some constants into the config

* Big refactor to address reviewer comments

* Bundle args together, drop some dead args

* date fixup for CI

* date fixup for CI

* Move more stuff to config, merge more swiglus

* Cleaning up some constants

* Lots of review fixes

* More review cleanup

* Fix chain-pair ipTM to use max-of-row-mean in ESMFold2 head (#47221)

pair_chains_iptm was a flat double-mean of tm_expected over all residue
pairs (i in c1, j in c2), whereas the global iptm (and AF3) use
max_i(mean_j TM(i,j)). Because mean <= max, the matrix read lower than
iptm and, for a heterodimer, iptm no longer equalled the max off-diagonal
entry.

Compute pair_chains_iptm[c1, c2] as max over rows i in chain c2 of the
mean over columns j in chain c1, matching the confidence head's
chain-pair convention and restoring iptm == max off-diagonal.

Co-authored-by: Fausto Milletari <fmilletari@tenant-ac-biohub-login-fmilletari-b42c3be-0.tenant-ac-biohub-login-all-users.tenant-ac-biohub.svc.cluster.local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Matt <rocketknight1@gmail.com>

* Small dtype bugfix

* Doc dates

* Cleanup, add the missing ESMFold2 conversion script, add some missing auto_docstring, fixed copyright headers

* More review fixes

* More review fixes

* More review fixes

* Committing review fixes so far

* i declare an end to the norm dance

* Move the trunk into forward(), collapse the step-invariants machinery

EsmFold2Model.forward was a six-line delegation to the diffusion module while the
folding trunk lived in the generation mixin, so modeling_esmfold2.py defined modules
it never composed. forward() now runs the trunk end to end (featurize -> inputs
embedder -> pair init -> recycling -> distogram) and returns EsmFold2TrunkOutput;
generation_esmfold2.py keeps only the diffusion sampling loop, the confidence-head
call and the infer_protein entry points. main_input_name is now genuinely forward's
first argument, and the circular-import dance is down to one local import.

Step invariants:
  - one flat EsmFold2DiffusionStepInvariants instead of two nested dataclasses
  - prepare_step_invariants is the single expansion boundary; num_diffusion_samples
    drops out of 7 signatures and all three "am I already expanded?" guards go away
  - the expansion sits after the unexpanded work, so the atom featurization and the
    pair-bias projection still run at the unexpanded batch (the bias projection now
    pushes num_heads channels instead of pairwise_hidden_size)
  - the single-inputs projection is hoisted out of the per-step loop
  - n_tokens comes from the token axis instead of atom_to_token.max().item(), which
    removes a device sync and fixes a crash on batches where every sequence is
    shorter than the padded axis

ESMC: EsmcLayerNorm restores its input dtype. Unfusing the reference's
LayerNormLinear into a standalone nn.LayerNorm meant autocast handed back fp32,
promoting the residual stream while the rotary cos/sin stayed bf16 -- invisible to
eager RoPE, which promotes, but it applied bf16-precision cos/sin to fp32 queries
and hard-failed the fused rotary kernel. fp32 output is unchanged (the cast is a
no-op there).

Also: drop the dead sigma_data override, remove 10 casts that were provably no-ops
in both fp32 and bf16, and add trust_remote_code to the trimul kernel mapping
(a personal namespace has no organization overview, so the publisher trust check
cannot resolve it; drop this once the kernel moves to kernels-community).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Let the 3D rotary and the triangular contraction follow the activation dtype

Two hardcoded-precision spots, both flagged in review, both now matching what the
reference actually does.

EsmFold2RotaryEmbedding3D forced cos/sin to bf16, so an fp32 model silently got
bf16-precision rotary and every later op that touched cos/sin was promoted back up.
It now takes the activation dtype from the caller, the same contract the
sequence-RoPE modules use. bf16 output is bit-identical; only fp32 changes, and it
changes by gaining the precision it asked for.

The triangular contraction ran in fp32 under a bf16 load, because `visibility` is an
fp32 pair mask and the masking multiply promoted `routed`. The reference looks like
it upcasts here too -- it calls `.float()` before the einsum -- but einsum is
autocast-eligible and its whole-fold bf16 autocast overrides that, so a bf16
reference model contracts in bf16 and an fp32 one in fp32. Casting the mask to the
activation dtype reproduces both. fp32 is unchanged; bf16 changes, and moves toward
the reference: vs the v4 fork on ubiquitin, distogram 1.65e-2 -> 1.47e-2 relative,
plddt 8.65e-2 -> 6.78e-2, ptm 3.61e-2 -> 3.23e-2. It is also ~1.40x faster on the
trunk's dominant op (L=384: 14.1 -> 10.1 ms) at ~5% less peak memory, and it makes
the following downcast into proj_emit a genuine no-op, so that one is gone.

Refresh the bf16 integration expectations for the contraction change. Fold quality
is unaffected (ptm 0.7433 vs 0.742, best pLDDT 0.8034); the fp32 expectations still
pass unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* More dtype chasing

* Fold more precomputation into the invariants step

* Cut down on arg passing, read config as much as possible

* Remove cross-model openfold imports

* Lots of folding into trunk_kwargs

* Big comment cleanup

* Broadcast cleanup to save memory

* Attention refactor, cleanup, remove _swa_window_mask_function

* More attention refactor, enable the FA2 path

* Last few fixes, update convert script

* Ship conversion mapping changes, drop a lot of weight renaming. Do everything at checkpoint creation now!

* Use the staging repo for now

* Add ESMC conversion script

* Big variable renaming pass

* The rest of the big renaming pass

* Go back to modular for esmfold2

* Modular refactor, generation stops accessing model attributes

* Get rid of EsmFold2AdaLNModulationLinear

* More confusing names gone

* Use config validation between parents and subconfigs

* Rename the GenerationMixin so it stops triggering generation checks, and shuffle configs a bit

* Regenerate modular ESMC after rebase onto main

Picks up upstream llama changes: nn.Buffer instead of register_buffer,
deprecate_kwarg on the rotary device arg, and the
use_kernel_func_from_hub -> use_kernel_forward_from_hub rename.

* Rename layers->blocks, decorate with no_grad() to reduce the need for detach()

* Batch of smaller review fixups

* Updated kernel decorator name

* more block->layer

* Make the attention layers a bit more similar (still can't merge them tho)

* dates

* Small fixes, comments, fewer magic numbers

* Precision correctness fix

* make fix-repo

* Update kernel path

* mlinter cleanup

* Subconfig passing cleanup

* Review fixes

* Review fixes

* Review fixes, split lists into _0 and _1

* More review fixes, reducing the big pile of args

* More review fixes, more big pile of args cleanup

* fix-dates

* Review fixes

* Review fixes

* Bugfixes for keep-in-fp32

* Drop comment

* mlinter fixes

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Fausto Milletari <fausto.milletari@gmail.com>
Co-authored-by: Fausto Milletari <fmilletari@tenant-ac-biohub-login-fmilletari-b42c3be-0.tenant-ac-biohub-login-all-users.tenant-ac-biohub.svc.cluster.local>
M
Matt committed
c8df3fc99546229bf4c7ab7d188973af1a64fc2e
Parent: cdea840
Committed by GitHub <noreply@github.com> on 8/20/2026, 9:12:55 PM