Instructions to use Synthyra/ESMFold2-Fast with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use Synthyra/ESMFold2-Fast with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("feature-extraction", model="Synthyra/ESMFold2-Fast", trust_remote_code=True)# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("Synthyra/ESMFold2-Fast", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
File size: 7,649 Bytes
6cc35b0 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 | """Protein-only ESMFold2 featurization without the Biohub runtime package.
Input is one amino-acid sequence. The transformation expands each residue into
the checkpoint atom schema, pads atoms to a multiple of 32, and emits batched
token, atom, and single-sequence MSA tensors. Reference coordinates are loaded
lazily from a provenance-bearing declarative package asset.
"""
from __future__ import annotations
import json
from functools import cache
from importlib.resources import files
from typing import Any
import torch
from torch import Tensor
from .esmfold2_constants import (
CHARGED_ATOMS,
ELEMENT_TO_ATOMIC_NUM,
ESM_PROTEIN_VOCAB,
MOL_TYPE_PROTEIN,
PROTEIN_1TO3,
PROTEIN_HEAVY_ATOMS,
PROTEIN_RESIDUE_TO_RES_TYPE,
PROTEIN_UNK_RES_TYPE,
)
_GEOMETRY_ASSET = "protein_reference_geometry.json"
_GEOMETRY_SCHEMA = "fastplms.esmfold2.reference_geometry.v1"
@cache
def _reference_geometry() -> dict[str, dict[str, tuple[float, float, float]]]:
resource = files(__package__).joinpath(_GEOMETRY_ASSET)
with resource.open(mode="r", encoding="utf-8") as handle:
payload = json.load(handle)
if (
payload.get("schema") != _GEOMETRY_SCHEMA
or payload.get("dtype") != "float32"
or payload.get("provenance", {}).get("manifest_family") != "esmfold2"
):
raise RuntimeError("The ESMFold2 reference-geometry asset has invalid provenance.")
raw_residues = payload.get("residues")
if not isinstance(raw_residues, dict):
raise RuntimeError("The ESMFold2 reference-geometry asset has no residue table.")
geometry: dict[str, dict[str, tuple[float, float, float]]] = {}
for residue, atom_positions in raw_residues.items():
if not isinstance(residue, str) or not isinstance(atom_positions, dict):
raise RuntimeError("The ESMFold2 reference-geometry residue table is malformed.")
geometry[residue] = {}
for atom_name, position in atom_positions.items():
if (
not isinstance(atom_name, str)
or not isinstance(position, list)
or len(position) != 3
):
raise RuntimeError("The ESMFold2 reference-geometry atom table is malformed.")
geometry[residue][atom_name] = tuple(float(value) for value in position)
expected_residues = set(PROTEIN_HEAVY_ATOMS) - {"MSE"}
if set(geometry) != expected_residues:
raise RuntimeError("The ESMFold2 reference-geometry residue set is incomplete.")
for residue, atom_names in PROTEIN_HEAVY_ATOMS.items():
if residue == "MSE":
continue
if set(geometry[residue]) != set(atom_names):
raise RuntimeError(f"Reference geometry differs from the atom schema for {residue}.")
return geometry
def _encode_atom_name(atom_name: str) -> tuple[int, int, int, int]:
padded = atom_name.ljust(4)[:4]
return tuple(ord(character) - 32 if character != " " else 0 for character in padded)
def _padded_atom_count(actual_count: int) -> int:
return max(32, ((actual_count + 31) // 32) * 32)
def _residue_records(sequence: str) -> tuple[list[dict[str, Any]], list[int], list[int], list[int]]:
geometry = _reference_geometry()
atoms: list[dict[str, Any]] = []
residue_types: list[int] = []
input_ids: list[int] = []
representative_atoms: list[int] = []
for token_index, residue_letter in enumerate(sequence):
residue_name = PROTEIN_1TO3.get(residue_letter, "UNK")
atom_names = PROTEIN_HEAVY_ATOMS[residue_name]
atom_start = len(atoms)
for atom_name in atom_names:
atoms.append(
{
"token_index": token_index,
"name": atom_name,
"element": atom_name[0],
"charge": CHARGED_ATOMS.get((residue_name, atom_name), 0),
"position": geometry[residue_name][atom_name],
}
)
representative_name = "CB" if "CB" in atom_names else "CA"
representative_atoms.append(atom_start + atom_names.index(representative_name))
residue_types.append(PROTEIN_RESIDUE_TO_RES_TYPE.get(residue_name, PROTEIN_UNK_RES_TYPE))
input_ids.append(ESM_PROTEIN_VOCAB.get(residue_letter, ESM_PROTEIN_VOCAB["X"]))
return atoms, residue_types, input_ids, representative_atoms
def prepare_protein_features(sequence: str) -> dict[str, Tensor]:
"""Build the protein-only feature mapping consumed by ESMFold2.
Every tensor includes a leading batch dimension. Biological tokens have
length ``l``; atom tensors have length ``n_atoms``, where ``n_atoms`` is the
smallest multiple of 32 covering all heavy atoms.
"""
if not sequence:
raise ValueError("sequence must be non-empty")
atoms, residue_types, input_ids, representative_atoms = _residue_records(sequence)
sequence_length = len(sequence)
n_atoms = _padded_atom_count(len(atoms))
ref_pos = torch.zeros((n_atoms, 3), dtype=torch.float32)
ref_element = torch.zeros(n_atoms, dtype=torch.int64)
ref_charge = torch.zeros(n_atoms, dtype=torch.int8)
ref_atom_name_chars = torch.zeros((n_atoms, 4), dtype=torch.int64)
ref_space_uid = torch.zeros(n_atoms, dtype=torch.int64)
atom_attention_mask = torch.zeros(n_atoms, dtype=torch.bool)
atom_to_token = torch.zeros(n_atoms, dtype=torch.int64)
for atom_index, atom in enumerate(atoms):
token_index = atom["token_index"]
ref_pos[atom_index] = torch.tensor(atom["position"], dtype=torch.float32)
ref_element[atom_index] = ELEMENT_TO_ATOMIC_NUM[atom["element"]]
ref_charge[atom_index] = atom["charge"]
ref_atom_name_chars[atom_index] = torch.tensor(
_encode_atom_name(atom["name"]), dtype=torch.int64
)
ref_space_uid[atom_index] = token_index
atom_attention_mask[atom_index] = True
atom_to_token[atom_index] = token_index
residue_type_tensor = torch.tensor(residue_types, dtype=torch.int64)
msa = residue_type_tensor.unsqueeze(0)
features = {
"token_index": torch.arange(sequence_length, dtype=torch.int64),
"residue_index": torch.arange(sequence_length, dtype=torch.int64),
"asym_id": torch.zeros(sequence_length, dtype=torch.int64),
"sym_id": torch.zeros(sequence_length, dtype=torch.int64),
"entity_id": torch.ones(sequence_length, dtype=torch.int64),
"mol_type": torch.full((sequence_length,), MOL_TYPE_PROTEIN, dtype=torch.int64),
"res_type": residue_type_tensor,
"input_ids": torch.tensor(input_ids, dtype=torch.int64),
"token_bonds": torch.zeros((sequence_length, sequence_length, 1), dtype=torch.float32),
"token_attention_mask": torch.ones(sequence_length, dtype=torch.bool),
"ref_pos": ref_pos,
"ref_element": ref_element,
"ref_charge": ref_charge,
"ref_atom_name_chars": ref_atom_name_chars,
"ref_space_uid": ref_space_uid,
"atom_attention_mask": atom_attention_mask,
"atom_to_token": atom_to_token,
"distogram_atom_idx": torch.tensor(representative_atoms, dtype=torch.int64),
"msa": msa,
"msa_attention_mask": torch.ones_like(msa, dtype=torch.bool),
"has_deletion": torch.zeros_like(msa, dtype=torch.bool),
"deletion_value": torch.zeros_like(msa, dtype=torch.float32),
"deletion_mean": torch.zeros(sequence_length, dtype=torch.float32),
}
return {name: tensor.unsqueeze(0) for name, tensor in features.items()}
__all__ = ["prepare_protein_features"]
|