alterlab-esm
Run ESM protein language models — ESMC for embeddings and representations, ESMFold2 for structure prediction, and ESM3 for generative multimodal protein design across sequence, structure, and function — locally or through the hosted Biohub Platform API (formerly Forge). Use when
Install
npx skills add https://github.com/AlterLab-IEU/AlterLab-Academic-Skills/tree/main/skills/bioinformatics/alterlab-esm
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install alterlab-ieu-alterlab-academic-skills@llmmart
git clone https://github.com/AlterLab-IEU/AlterLab-Academic-Skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole alterlab-ieu/alterlab-academic-skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
ESM: Evolutionary Scale Modeling
Overview
ESM provides state-of-the-art protein language models for understanding, generating, and designing proteins. Three families ship in the same esm package:
- ESMC — the current representation model (300M / 600M open weights, 6B via the hosted platform), used for embeddings, logits, and downstream ML features.
- ESMFold2 — structure prediction built on ESMC-6B, hosted via
esmfold2_client. - ESM3 — the generative multimodal model over sequence / structure / function tracks.
Naming and hosting changed — read this before copying old code
EvolutionaryScale now operates as Biohub. The practical consequences:
- The hosted API moved from
forge.evolutionaryscale.aitohttps://biohub.ai; every client inesm.sdkdefaults to that URL and reads the token fromESM_API_KEY. The classes are still named*ForgeInferenceClientfor backwards compatibility. - Model weights live under the
biohubHugging Face organisation (biohub/ESMC-6B,biohub/esm3-sm-open-v1,biohub/ESMFold2). - ESMC and ESMFold2 are also available through Hugging Face Transformers v5.16+, which is
a different dependency stack from the
esmpackage's optimized inference path.
When to Use This Skill
Use this skill when the user wants protein embeddings or logits, a protein structure from an ESM model, inverse folding, or generative sequence design.
Does NOT Trigger
| Scenario | Use Instead |
|---|---|
| Fold a protein or complex with AlphaFold2/ColabFold | alterlab-alphafold |
| Co-fold with a ligand, or predict binding affinity | alterlab-boltz |
| Antibody–antigen or mixed multi-entity complexes | alterlab-chai |
| Design a sequence for a fixed backbone (ProteinMPNN family) | alterlab-proteinmpnn, alterlab-ligandmpnn |
| Generate a de-novo backbone to design onto | alterlab-rfdiffusion |
Core Capabilities
1. Protein Sequence Generation with ESM3
Generate novel protein sequences with desired properties using multimodal generative modeling.
When to use:
- Designing proteins with specific functional properties
- Completing partial protein sequences
- Generating variants of existing proteins
- Creating proteins with desired structural characteristics
Basic usage:
from esm.models.esm3 import ESM3
from esm.sdk.api import ESM3InferenceClient, ESMProtein, GenerationConfig
# Load model locally
model: ESM3InferenceClient = ESM3.from_pretrained("esm3-sm-open-v1").to("cuda")
# Create protein prompt
protein = ESMProtein(sequence="MPRT___KEND") # '_' represents masked positions
# Generate completion
protein = model.generate(protein, GenerationConfig(track="sequence", num_steps=8))
print(protein.sequence)
For remote/cloud usage via the Biohub Platform:
import esm
from esm.sdk.api import ESMProtein, GenerationConfig
# url defaults to https://biohub.ai and token to os.environ["ESM_API_KEY"]
model = esm.sdk.client("esm3-medium-2024-08")
protein = model.generate(protein, GenerationConfig(track="sequence", num_steps=8))
See references/esm3-api.md for detailed ESM3 model specifications, advanced generation configurations, and multimodal prompting examples.
2. Structure Prediction and Inverse Folding
Use ESM3's structure track for structure prediction from sequence or inverse folding (sequence design from structure).
Structure prediction:
from esm.sdk.api import ESM3InferenceClient, ESMProtein, GenerationConfig
# Predict structure from a complete sequence
protein = ESMProtein(sequence="MPRTKEINDAGLIVHSP")
protein_with_structure = model.generate(
protein,
# num_steps controls how many structure tokens are decoded per step;
# use the sequence length (not a "_" count — the sequence is complete here)
GenerationConfig(track="structure", num_steps=len(protein.sequence))
)
# Access predicted structure
coordinates = protein_with_structure.coordinates # 3D coordinates
pdb_string = protein_with_structure.to_pdb()
Inverse folding (sequence from structure):
# Design sequence for a target structure
protein_with_structure = ESMProtein.from_pdb("target_structure.pdb")
protein_with_structure.sequence = None # Remove sequence
# Generate sequence that folds to this structure
designed_protein = model.generate(
protein_with_structure,
GenerationConfig(track="sequence", num_steps=50, temperature=0.7)
)
3. Protein Embeddings with ESM C
Generate high-quality embeddings for downstream tasks like function prediction, classification, or similarity analysis.
When to use:
- Extracting protein representations for machine learning
- Computing sequence similarities
- Feature extraction for protein classification
- Transfer learning for protein-related tasks
Basic usage:
from esm.models.esmc import ESMC
from esm.sdk.api import ESMProtein, LogitsConfig
# Load ESMC locally (local from_pretrained names use UNDERSCORES; default is esmc_600m)
model = ESMC.from_pretrained("esmc_300m").to("cuda")
# Encode, then request embeddings via the logits() API
protein = ESMProtein(sequence="MPRTKEINDAGLIVHSP")
protein_tensor = model.encode(protein)
out = model.logits(protein_tensor, LogitsConfig(sequence=True, return_embeddings=True))
embeddings = out.embeddings # (1, L+2, hidden_dim), incl. BOS/EOS tokens
logits = out.logits.sequence # per-position amino-acid logits
Do NOT call model.forward(...) to get embeddings — forward returns a raw model output, not a usable representation tensor. Use model.logits(..., LogitsConfig(return_embeddings=True)).embeddings.
Batch processing:
# Encode multiple proteins and pull mean-pooled embeddings
proteins = [
ESMProtein(sequence="MPRTKEIND"),
ESMProtein(sequence="AGLIVHSPQ"),
ESMProtein(sequence="KTEFLNDGR"),
]
cfg = LogitsConfig(sequence=True, return_embeddings=True)
embeddings_list = [
model.logits(model.encode(p), cfg).embeddings.mean(dim=1) for p in proteins
]
See references/esm-c-api.md for ESM C model details, efficiency comparisons, and advanced embedding strategies.
4. Function Conditioning and Annotation
Use ESM3's function track to generate proteins with specific functional annotations or predict function from sequence.
Function-conditioned generation:
from esm.sdk.api import ESMProtein, FunctionAnnotation, GenerationConfig
# Create protein with desired function
protein = ESMProtein(
sequence="_" * 200, # Generate 200 residue protein
function_annotations=[
FunctionAnnotation(label="fluorescent_protein", start=50, end=150)
]
)
# Generate sequence with specified function
functional_protein = model.generate(
protein,
GenerationConfig(track="sequence", num_steps=200)
)
5. Chain-of-Thought Generation
Iteratively refine protein designs using ESM3's chain-of-thought generation approach.
from esm.sdk.api import GenerationConfig
# Multi-step refinement
protein = ESMProtein(sequence="MPRT" + "_" * 100 + "KEND")
# Step 1: Generate initial structure
config = GenerationConfig(track="structure", num_steps=50)
protein = model.generate(protein, config)
# Step 2: Refine sequence based on structure
config = GenerationConfig(track="sequence", num_steps=50, temperature=0.5)
protein = model.generate(protein, config)
# Step 3: Predict function
config = GenerationConfig(track="function", num_steps=20)
protein = model.generate(protein, config)
6. Batch processing against the hosted platform
The client exposes both an async API and a parallel executor that handles retries and a progress bar:
import asyncio
import esm
from esm.sdk import parallel_executor
from esm.sdk.api import ESMProtein, GenerationConfig
client = esm.sdk.client("esm3-medium-2024-08") # token from ESM_API_KEY
proteins = [ESMProtein(sequence=f"MPRT{'_' * 50}KEND") for _ in range(10)]
cfg = GenerationConfig(track="sequence", num_steps=8)
# Option A: async fan-out
async def run():
return await asyncio.gather(*(client.async_generate(p, cfg) for p in proteins))
results = asyncio.run(run())
# Option B: managed executor (retries + progress)
with parallel_executor() as executor:
results = executor.execute_batch(client.generate, protein=proteins, config=cfg)
Also available: client.batch_generate(...) / async_batch_generate(...), and
async_fold / async_inverse_fold / async_logits for the other tracks.
See references/forge-api.md for platform documentation, authentication, rate limits, and
batch processing patterns.
Model Selection Guide
ESM3 Models (Generative):
esm3-sm-open-v1(1.4B, aliasesesm3_sm_open_v1/esm3-open) — open weights, localesm3-medium-2024-08(7B) — balance of quality and speed (hosted only)esm3-large-2024-03(98B) — highest quality, slower (hosted only)
ESMC Models (Representations):
esmc_300m— lightweight, fast; open weights, runs locallyesmc_600m— balanced; open weights, runs locally, and thefrom_pretraineddefaultesmc_6b/ hostedesmc-6b-2024-12— maximum representation quality
ESMFold2 (Structure): esmfold2-fast-2026-05 (the esmfold2_client default),
esmfold2-2026-05, esmfold2-2026-05-cutoff-2025.
Naming gotcha: local from_pretrained(...) names use underscores (esmc_300m,
esmc_600m, esmc_6b). Hosted client strings use hyphens with a date
(esmc-600m-2024-12, esm3-medium-2024-08, esmfold2-fast-2026-05).
Selection criteria:
- Local development/testing:
esmc_300mfor embeddings,esm3-sm-open-v1for generation - Production quality:
esm3-medium-2024-08oresmc-6b-2024-12via the Biohub Platform - Structure prediction:
esmfold2_client()rather than the ESM3 structure track - High throughput: hosted clients plus
esm.sdk.parallel_executor/ batch client - Cost optimization: smaller models, cache embeddings
Installation
Basic installation (pin the minor — the SDK still changes across releases):
uv pip install "esm>=3.4,<3.5" # 3.4.1.post1 as of 2026-09; needs Python >= 3.12
With Flash Attention (optional, faster GPU inference):
uv pip install flash-attn --no-build-isolation
The hosted clients (esm.sdk.client, esmc_client, esmfold2_client) ship inside the
esm package — no extra install. Create an API token in the Biohub developer console
(https://biohub.ai/developer-console/api-keys) and export it as ESM_API_KEY. Local
weights are pulled from Hugging Face, so run huggingface_hub.login() once with a
read-scoped token.
ESMC through the Biohub Platform:
from esm.sdk import esmc_client
from esm.sdk.api import ESMProtein, LogitsConfig
model = esmc_client(model="esmc-600m-2024-12") # url/token default as above
out = model.logits(model.encode(ESMProtein(sequence="MPRTKEINDAGLIVHSP")),
LogitsConfig(sequence=True, return_embeddings=True))
print(out.logits, out.embeddings)
Common Workflows
For detailed examples and complete workflows, see references/workflows.md which includes:
- Novel GFP design with chain-of-thought
- Protein variant generation and screening
- Structure-based sequence optimization
- Function prediction pipelines
- Embedding-based clustering and analysis
References
This skill includes comprehensive reference documentation:
references/esm3-api.md- ESM3 model architecture, API reference, generation parameters, and multimodal promptingreferences/esm-c-api.md- ESM C model details, embedding strategies, and performance optimizationreferences/forge-api.md- Forge platform documentation, authentication, batch processing, and deploymentreferences/workflows.md- Complete examples and common workflow patterns
These references contain detailed API specifications, parameter descriptions, and advanced usage patterns. Load them as needed for specific tasks.
Best Practices
For generation tasks:
- Start with smaller models for prototyping (
esm3-sm-open-v1) - Use temperature parameter to control diversity (0.0 = deterministic, 1.0 = diverse)
- Implement iterative refinement with chain-of-thought for complex designs
- Validate generated sequences with structure prediction or wet-lab experiments
For embedding tasks:
- Batch process sequences when possible for efficiency
- Cache embeddings for repeated analyses
- Normalize embeddings when computing similarities
- Use appropriate model size based on downstream task requirements
For production deployment:
- Use the hosted Biohub Platform for scalability and the largest models
- Implement error handling and retry logic for API calls
- Monitor token usage and implement rate limiting
- Consider AWS SageMaker deployment for dedicated infrastructure
Resources and Documentation
- GitHub Repository: https://github.com/Biohub/esm (ESM3 docs live in
_assets/ESM3_README.md) - Biohub Platform: https://biohub.ai — API keys at https://biohub.ai/developer-console/api-keys
- ESM Atlas: https://biohub.ai/esm/protein/atlas
- ESM3 paper: Hayes et al., Science — https://www.science.org/doi/10.1126/science.ads0018
- Tutorials: https://github.com/Biohub/esm/tree/main/cookbook/tutorials
- Model Weights: Hugging Face
biohuborganization
Responsible Use
ESM is designed for beneficial applications in protein engineering, drug discovery, and scientific research. Biohub publishes an Acceptable Use Policy and ran a biosafety/biosecurity risk assessment before release — follow that policy, and the Responsible Biodesign Framework (https://responsiblebiodesign.ai/), when designing novel proteins. Think through biosafety and dual-use implications before experimental validation, because a designed sequence leaves the computational realm the moment it is ordered.
Part of the AlterLab Academic Skills suite.
Files (alterlab-academic-skills)
-
evals
-
evals.json 4.9 KB
{ "skill": "alterlab-esm", "evals": [ { "id": "sequence-completion-esm3", "prompt": "I have a partial protein sequence MPRT___KEND with masked positions and I want ESM3 to fill in the masked residues. Can you write the generation code?", "expected_output": "Invokes alterlab-esm. Loads an ESM3 model (e.g. ESM3.from_pretrained('esm3-sm-open-v1')), builds an ESMProtein with '_' masked positions, and calls model.generate(protein, GenerationConfig(track='sequence', num_steps=...)). Sequence in-filling with the ESM3 generative model is a core capability.", "assertions": [ { "type": "should_trigger", "value": true }, { "type": "output_contains", "value": "ESM3" } ] }, { "id": "inverse-folding-from-pdb", "prompt": "I have a target backbone structure in target_structure.pdb. Design a new amino-acid sequence that should fold into that structure using inverse folding.", "expected_output": "Invokes alterlab-esm. Loads structure via ESMProtein.from_pdb('target_structure.pdb'), removes the sequence (sequence=None), then runs model.generate with GenerationConfig(track='sequence', temperature=...) to design a sequence for the fixed structure. Inverse folding (sequence-from-structure) on the ESM3 structure track is in scope.", "assertions": [ { "type": "should_trigger", "value": true }, { "type": "output_contains", "value": "inverse folding" } ] }, { "id": "esmc-embeddings-batch", "prompt": "I need fixed-length representation embeddings for a few thousand protein sequences to feed into a downstream classifier. What's the efficient way to do this with ESM?", "expected_output": "Invokes alterlab-esm. Recommends the ESM C model family (e.g. ESMC.from_pretrained('esmc_300m')) for efficient embeddings, encoding each ESMProtein and extracting representations via model.logits(tensor, LogitsConfig(return_embeddings=True)).embeddings (NOT model.forward), with batch looping and caching for throughput. Generating protein embeddings for downstream ML is an explicit ESM C use case.", "assertions": [ { "type": "should_trigger", "value": true }, { "type": "output_contains", "value": "ESM C" } ] }, { "id": "function-conditioned-design", "prompt": "Design a 200-residue protein conditioned on a fluorescent-protein function annotation between residues 50 and 150 using ESM3's function track.", "expected_output": "Invokes alterlab-esm. Builds an ESMProtein with a FunctionAnnotation(label='fluorescent_protein', start=50, end=150) and generates with GenerationConfig(track='sequence', ...), optionally using chain-of-thought refinement across structure/function tracks. Function-conditioned generation is a documented ESM3 capability.", "assertions": [ { "type": "should_trigger", "value": true }, { "type": "output_contains", "value": "FunctionAnnotation" } ] }, { "id": "near-miss-scvi-tools", "prompt": "I have a single-cell RNA-seq AnnData object and I want a low-dimensional latent embedding of the cells with batch correction across donors.", "expected_output": "Does NOT invoke this skill; defers to alterlab-scvi-tools. The ask is single-cell transcriptomic latent embeddings with batch correction (scVI on AnnData), not protein-sequence/structure embeddings. ESM's embeddings are for protein sequences, not cell-level scRNA-seq.", "assertions": [ { "type": "should_not_trigger", "value": true }, { "type": "output_contains", "value": "alterlab-scvi-tools" } ] }, { "id": "near-miss-biopython", "prompt": "Read this multi-FASTA of protein sequences, compute each sequence's molecular weight and isoelectric point, and write the results to a CSV.", "expected_output": "Does NOT invoke this skill; defers to alterlab-biopython. The ask is deterministic sequence parsing and physico-chemical property calculation (ProtParam-style), with no protein language model, generation, or learned embeddings. ESM is for generative design and neural representations, not basic sequence bookkeeping.", "assertions": [ { "type": "should_not_trigger", "value": true }, { "type": "output_contains", "value": "alterlab-biopython" } ] }, { "id": "near-miss-boltz", "prompt": "Co-fold my kinase with this inhibitor SMILES and give me a predicted binding affinity so I can rank a few analogues.", "expected_output": "Does NOT invoke this skill; defers to alterlab-boltz. Protein–ligand co-folding with an affinity estimate is Boltz-2's job; ESM models give sequence embeddings, ESMFold2 protein structures, and ESM3 generative design, but none of them place a small molecule or predict binding affinity.", "assertions": [ { "type": "should_not_trigger", "value": true }, { "type": "output_contains", "value": "alterlab-boltz" } ] } ] }
-
-
references
-
esm-c-api.md 17.6 KB
# ESM C API Reference ## Overview ESM C (Cambrian) is a family of protein language models optimized for representation learning and efficient embedding generation. Designed as a drop-in replacement for ESM2, ESM C provides significant improvements in speed and quality across all model sizes. ## Model Architecture **ESM C Family Models:** | Model ID | Parameters | Layers | Best For | |----------|-----------|--------|----------| | `esmc_300m` | 300M | 30 | Fast inference, lightweight applications | | `esmc_600m` | 600M | 36 | Balanced performance and quality | | `esmc-6b` | 6B | 80 | Maximum representation quality (Forge/Biohub: `esmc-6b-2024-12`) | (Local `ESMC.from_pretrained` uses the underscore IDs; the hosted 6B is reached via the Forge/Biohub client.) **Key Features:** - Improved perplexity and embedding quality versus ESM2 at comparable scale - Efficient architecture for production deployment - Reduced memory footprint relative to ESM2 **Architecture Improvements over ESM2:** - Optimized attention mechanisms - Better token representation - Enhanced training procedures - Reduced memory footprint ## Core API Components ### ESMC Class Main interface for ESM C models. **Model Loading:** Local `from_pretrained` names use **underscores**. The 6B model is Forge/Biohub-only and is reached through the client, not `from_pretrained`. ```python from esm.models.esmc import ESMC from esm.sdk.api import ESMProtein # Load model with automatic device placement model = ESMC.from_pretrained("esmc_300m").to("cuda") # Or specify device explicitly model = ESMC.from_pretrained("esmc_600m").to("cpu") # For maximum quality, use the hosted 6B via the Forge/Biohub client: from esm.sdk.forge import ESM3ForgeInferenceClient model_6b = ESM3ForgeInferenceClient(model="esmc-6b-2024-12", token="<token>") ``` **Model Selection Criteria:** - **esmc_300m**: Development, real-time applications, batch processing of many sequences - **esmc_600m**: Production deployments, good quality/speed balance - **esmc-6b-2024-12** (Forge/Biohub): Research, maximum accuracy for downstream tasks ### Basic Embedding Generation **Single Sequence:** The supported way to get embeddings and per-position logits is `model.logits(tensor, LogitsConfig(...))`. The returned object exposes `.embeddings` and `.logits`. Do not treat `model.forward(...)` as an embedding tensor — it returns a raw model output object. ```python from esm.models.esmc import ESMC from esm.sdk.api import ESMProtein, LogitsConfig # Load model model = ESMC.from_pretrained("esmc_600m").to("cuda") # Create protein and encode to a tensor protein = ESMProtein(sequence="MPRTKEINDAGLIVHSPQWFYK") protein_tensor = model.encode(protein) # Single call returns both embeddings and logits out = model.logits(protein_tensor, LogitsConfig(sequence=True, return_embeddings=True)) embeddings = out.embeddings # representations logits = out.logits.sequence # per-position amino-acid logits print(f"Embedding shape: {embeddings.shape}") print(f"Logits shape: {logits.shape}") ``` **Output Shapes:** For a sequence of length L (ESM C prepends BOS and appends EOS, so the token axis is L+2): - `embeddings.shape`: `(1, L+2, hidden_dim)` where hidden_dim depends on the model - esmc_300m: hidden_dim = 960 - esmc_600m: hidden_dim = 1152 - esmc-6b: hidden_dim = 2560 - `logits.sequence.shape`: `(1, L+2, vocab)` - per-position amino-acid logits When mean-pooling for a fixed-length sequence representation, decide whether to include or strip the BOS/EOS positions; for most downstream tasks mean-pooling over the residue positions (excluding BOS/EOS) is the cleaner choice. ### Batch Processing Process multiple sequences efficiently: ```python from esm.sdk.api import LogitsConfig # Multiple proteins sequences = [ "MPRTKEINDAGLIVHSP", "AGKWFYLTQSNHERVPM", "DEIFKRNAVWGSLTPQY" ] proteins = [ESMProtein(sequence=seq) for seq in sequences] cfg = LogitsConfig(sequence=True, return_embeddings=True) # The local ESMC client processes one protein at a time; loop and extract embeddings embeddings_list = [] for p in proteins: out = model.logits(model.encode(p), cfg) embeddings_list.append(out.embeddings) print(f"Processed {len(embeddings_list)} proteins") ``` **Efficient Batching for Variable Lengths:** ```python def batch_encode_variable_length(model, sequences, max_batch_size=32): """ Efficiently batch encode sequences of variable length. Groups by similar length for efficiency. """ # Sort by length sorted_seqs = sorted(enumerate(sequences), key=lambda x: len(x[1])) results = [None] * len(sequences) batch = [] batch_indices = [] for idx, seq in sorted_seqs: batch.append(seq) batch_indices.append(idx) # Process batch when full or length changes significantly if (len(batch) >= max_batch_size or (len(batch) > 0 and abs(len(seq) - len(batch[0])) > 10)): # Process current batch cfg = LogitsConfig(sequence=True, return_embeddings=True) proteins = [ESMProtein(sequence=s) for s in batch] embeddings = [model.logits(model.encode(p), cfg).embeddings for p in proteins] # Store results for i, emb in zip(batch_indices, embeddings): results[i] = emb batch = [] batch_indices = [] # Process remaining if batch: cfg = LogitsConfig(sequence=True, return_embeddings=True) proteins = [ESMProtein(sequence=s) for s in batch] embeddings = [model.logits(model.encode(p), cfg).embeddings for p in proteins] for i, emb in zip(batch_indices, embeddings): results[i] = emb return results ``` ## Common Use Cases ### 1. Sequence Similarity Analysis Compute similarity between proteins using embeddings: ```python import torch import torch.nn.functional as F def get_sequence_embedding(model, sequence): """Get mean-pooled sequence embedding.""" from esm.sdk.api import LogitsConfig protein = ESMProtein(sequence=sequence) out = model.logits( model.encode(protein), LogitsConfig(sequence=True, return_embeddings=True), ) # Mean pooling over the token axis return out.embeddings.mean(dim=1) # Get embeddings seq1_emb = get_sequence_embedding(model, "MPRTKEINDAGLIVHSP") seq2_emb = get_sequence_embedding(model, "MPRTKEINDAGLIVHSQ") # Similar seq3_emb = get_sequence_embedding(model, "WWWWWWWWWWWWWWWWW") # Different # Compute cosine similarity sim_1_2 = F.cosine_similarity(seq1_emb, seq2_emb) sim_1_3 = F.cosine_similarity(seq1_emb, seq3_emb) print(f"Similarity (1,2): {sim_1_2.item():.4f}") print(f"Similarity (1,3): {sim_1_3.item():.4f}") ``` ### 2. Protein Classification Use embeddings as features for classification: ```python import numpy as np from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split # Generate embeddings for training set def embed_dataset(model, sequences): from esm.sdk.api import LogitsConfig cfg = LogitsConfig(sequence=True, return_embeddings=True) embeddings = [] for seq in sequences: protein = ESMProtein(sequence=seq) out = model.logits(model.encode(protein), cfg) emb = out.embeddings.mean(dim=1) # Mean pooling over the token axis embeddings.append(emb.cpu().detach().numpy().flatten()) return np.array(embeddings) # Example: Classify proteins by function train_sequences = [...] # Your sequences train_labels = [...] # Your labels embeddings = embed_dataset(model, train_sequences) # Train classifier X_train, X_test, y_train, y_test = train_test_split( embeddings, train_labels, test_size=0.2 ) classifier = LogisticRegression(max_iter=1000) classifier.fit(X_train, y_train) # Evaluate accuracy = classifier.score(X_test, y_test) print(f"Classification accuracy: {accuracy:.4f}") ``` ### 3. Protein Clustering Cluster proteins based on sequence similarity: ```python from sklearn.cluster import KMeans import numpy as np # Generate embeddings sequences = [...] # Your protein sequences embeddings = embed_dataset(model, sequences) # Cluster n_clusters = 5 kmeans = KMeans(n_clusters=n_clusters, random_state=42) cluster_labels = kmeans.fit_predict(embeddings) # Analyze clusters for i in range(n_clusters): cluster_seqs = [seq for seq, label in zip(sequences, cluster_labels) if label == i] print(f"Cluster {i}: {len(cluster_seqs)} sequences") ``` ### 4. Sequence Search and Retrieval Find similar sequences in a database: ```python import torch import numpy as np from sklearn.metrics.pairwise import cosine_similarity def build_sequence_index(model, database_sequences): """Build searchable index of sequence embeddings.""" embeddings = [] for seq in database_sequences: emb = get_sequence_embedding(model, seq) embeddings.append(emb.cpu().detach().numpy().flatten()) return np.array(embeddings) def search_similar_sequences(model, query_seq, database_embeddings, database_sequences, top_k=10): """Find top-k most similar sequences.""" query_emb = get_sequence_embedding(model, query_seq) query_emb_np = query_emb.cpu().detach().numpy().flatten().reshape(1, -1) # Compute similarities similarities = cosine_similarity(query_emb_np, database_embeddings)[0] # Get top-k top_indices = np.argsort(similarities)[-top_k:][::-1] results = [ (database_sequences[idx], similarities[idx]) for idx in top_indices ] return results # Example usage database_seqs = [...] # Large sequence database index = build_sequence_index(model, database_seqs) query = "MPRTKEINDAGLIVHSP" similar = search_similar_sequences(model, query, index, database_seqs, top_k=5) for seq, score in similar: print(f"Score: {score:.4f} - {seq[:30]}...") ``` ### 5. Feature Extraction for Downstream Models Use ESM C embeddings as input to custom neural networks: ```python import torch.nn as nn from esm.sdk.api import LogitsConfig class ProteinPropertyPredictor(nn.Module): """Example: Predict protein properties from ESM C embeddings.""" def __init__(self, embedding_dim, hidden_dim, output_dim): super().__init__() self.fc1 = nn.Linear(embedding_dim, hidden_dim) self.fc2 = nn.Linear(hidden_dim, hidden_dim) self.fc3 = nn.Linear(hidden_dim, output_dim) self.relu = nn.ReLU() self.dropout = nn.Dropout(0.3) def forward(self, embeddings): # embeddings: (batch, seq_len, embedding_dim) # Mean pool over sequence x = embeddings.mean(dim=1) x = self.relu(self.fc1(x)) x = self.dropout(x) x = self.relu(self.fc2(x)) x = self.dropout(x) x = self.fc3(x) return x # Use ESM C as frozen feature extractor esm_model = ESMC.from_pretrained("esmc_600m").to("cuda") esm_model.eval() # Freeze # Create task-specific model predictor = ProteinPropertyPredictor( embedding_dim=1152, # esmc-600m dimension hidden_dim=512, output_dim=1 # e.g., stability score ).to("cuda") # Training loop cfg = LogitsConfig(sequence=True, return_embeddings=True) for sequence, target in dataloader: protein = ESMProtein(sequence=sequence) with torch.no_grad(): embeddings = esm_model.logits(esm_model.encode(protein), cfg).embeddings prediction = predictor(embeddings) loss = criterion(prediction, target) # ... backprop through predictor only ``` ### 6. Per-Residue Analysis Extract per-residue representations for detailed analysis: ```python def get_per_residue_embeddings(model, sequence): """Get embedding for each residue (token axis includes BOS/EOS).""" from esm.sdk.api import LogitsConfig protein = ESMProtein(sequence=sequence) out = model.logits( model.encode(protein), LogitsConfig(sequence=True, return_embeddings=True), ) # out.embeddings shape: (1, L+2, hidden_dim) return out.embeddings.squeeze(0) # (L+2, hidden_dim) # Analyze specific positions sequence = "MPRTKEINDAGLIVHSPQWFYK" residue_embeddings = get_per_residue_embeddings(model, sequence) # Extract features for position 10 position_10_features = residue_embeddings[10] print(f"Features for residue {sequence[10]} at position 10:") print(f"Shape: {position_10_features.shape}") # Compare residue representations pos_5 = residue_embeddings[5] pos_15 = residue_embeddings[15] similarity = F.cosine_similarity(pos_5, pos_15, dim=0) print(f"Residue similarity: {similarity.item():.4f}") ``` ## Performance Optimization ### Memory Management ```python import torch from esm.sdk.api import LogitsConfig # Use half precision for memory efficiency model = ESMC.from_pretrained("esmc_600m").to("cuda").half() cfg = LogitsConfig(sequence=True, return_embeddings=True) # Process with mixed precision with torch.autocast(device_type="cuda"): embeddings = model.logits(model.encode(protein), cfg).embeddings # Clear cache between batches torch.cuda.empty_cache() ``` ### Batch Processing Best Practices ```python def efficient_batch_processing(model, sequences, batch_size=32): """Process sequences in optimized batches.""" from esm.sdk.api import LogitsConfig cfg = LogitsConfig(sequence=True, return_embeddings=True) results = [] for i in range(0, len(sequences), batch_size): batch = sequences[i:i + batch_size] # Process batch batch_embeddings = [] for seq in batch: protein = ESMProtein(sequence=seq) emb = model.logits(model.encode(protein), cfg).embeddings batch_embeddings.append(emb) results.extend(batch_embeddings) # Periodically clear cache if i % (batch_size * 10) == 0: torch.cuda.empty_cache() return results ``` ### Caching Embeddings ```python import pickle import hashlib def get_cache_key(sequence): """Generate cache key for sequence.""" return hashlib.md5(sequence.encode()).hexdigest() class EmbeddingCache: """Cache for protein embeddings.""" def __init__(self, cache_file="embeddings_cache.pkl"): self.cache_file = cache_file try: with open(cache_file, 'rb') as f: self.cache = pickle.load(f) except FileNotFoundError: self.cache = {} def get(self, sequence): key = get_cache_key(sequence) return self.cache.get(key) def set(self, sequence, embedding): key = get_cache_key(sequence) self.cache[key] = embedding def save(self): with open(self.cache_file, 'wb') as f: pickle.dump(self.cache, f) # Usage cache = EmbeddingCache() def get_embedding_cached(model, sequence): cached = cache.get(sequence) if cached is not None: return cached # Compute from esm.sdk.api import LogitsConfig protein = ESMProtein(sequence=sequence) embedding = model.logits( model.encode(protein), LogitsConfig(sequence=True, return_embeddings=True), ).embeddings cache.set(sequence, embedding) return embedding # Don't forget to save cache cache.save() ``` ## Comparison with ESM2 Biohub (formerly EvolutionaryScale) positions ESMC as a successor to ESM2 with better quality-per-parameter and improved efficiency. For exact speed/perplexity/quality numbers, consult the official ESM Cambrian benchmarks rather than relying on rules of thumb: https://www.evolutionaryscale.ai/blog/esm-cambrian **Migration from ESM2:** ESM C is *not* a literal drop-in for the old `esm.pretrained` API — the interface changed. The conceptual mapping: ```python # Old ESM2 (archived facebookresearch/esm) from esm import pretrained model, alphabet = pretrained.esm2_t33_650M_UR50D() # ESMC (Biohub `esm` SDK): build ESMProtein, encode, then logits() from esm.models.esmc import ESMC from esm.sdk.api import ESMProtein, LogitsConfig model = ESMC.from_pretrained("esmc_600m").to("cuda") out = model.logits( model.encode(ESMProtein(sequence="...")), LogitsConfig(sequence=True, return_embeddings=True), ) embeddings = out.embeddings ``` Key differences: - Unified `ESMProtein` object instead of a separate alphabet/batch-converter - Embeddings come from `logits(..., return_embeddings=True).embeddings`, not a tuple from `forward` - Improved quality-per-parameter and memory efficiency over ESM2 ## Advanced Topics ### Fine-tuning ESM C ESM C can be fine-tuned for specific tasks: ```python import torch.optim as optim from esm.sdk.api import LogitsConfig # Load model model = ESMC.from_pretrained("esmc_300m").to("cuda") cfg = LogitsConfig(sequence=True, return_embeddings=True) # Unfreeze for fine-tuning for param in model.parameters(): param.requires_grad = True # Define optimizer optimizer = optim.Adam(model.parameters(), lr=1e-5) # Training loop for epoch in range(num_epochs): for sequences, labels in dataloader: optimizer.zero_grad() # Forward pass through the model proteins = [ESMProtein(sequence=seq) for seq in sequences] embeddings = [model.logits(model.encode(p), cfg).embeddings for p in proteins] # Your task-specific loss loss = compute_loss(embeddings, labels) loss.backward() optimizer.step() ``` For most use cases, prefer freezing ESM C and training a lightweight head on its embeddings (see "Feature Extraction" above) — full fine-tuning of a protein LM needs careful learning-rate and memory handling. ## Citation If using ESM C in research, cite: ``` ESM Cambrian: https://www.evolutionaryscale.ai/blog/esm-cambrian Biohub / EvolutionaryScale (2024) ``` ## Additional Resources - ESM C blog post: https://www.evolutionaryscale.ai/blog/esm-cambrian - Model weights: Hugging Face `biohub` organization (`biohub/ESMC-6B`, …) - Comparison benchmarks: See blog post for detailed performance comparisons -
esm3-api.md 12.5 KB
# ESM3 API Reference > **Check the console before relying on a model ID.** Biohub's current ESM3 model > table lists `esm3-small-2024-08` (1.4B), `esm3-medium-2024-08` (7B) and > `esm3-large-2024-03` (98B) as the flagship models, plus the 2024-03 "published" > replicas used in the paper. Other IDs (e.g. the experimental multimer model) may > or may not still be served — confirm in the developer console rather than > hard-coding them. ## Overview ESM3 is a frontier multimodal generative language model that reasons over the sequence, structure, and function of proteins. It uses iterative masked language modeling to simultaneously generate across these three modalities. ## Model Architecture **ESM3 Family Models:** | Model ID | Parameters | Availability | Best For | |----------|-----------|--------------|----------| | `esm3-sm-open-v1` | 1.4B | Open weights (local) | Development, testing, learning | | `esm3-medium-2024-08` | 7B | Forge API only | Production, balanced quality/speed | | `esm3-large-2024-03` | 98B | Forge API only | Maximum quality, research | | `esm3-medium-multimer-2024-09` | 7B | Forge API only | Protein complexes (experimental) | **Key Features:** - Simultaneous reasoning across sequence, structure, and function - Iterative generation with controllable number of steps - Support for partial prompting across modalities - Chain-of-thought generation for complex designs - Temperature control for generation diversity ## Core API Components ### ESMProtein Class The central data structure representing a protein with optional sequence, structure, and function information. **Constructor:** ```python from esm.sdk.api import ESMProtein protein = ESMProtein( sequence="MPRTKEINDAGLIVHSP", # Amino acid sequence (optional) coordinates=coordinates_array, # 3D structure (optional) function_annotations=[...], # Function labels (optional) secondary_structure="HHHEEEECCC", # SS annotations (optional) sasa=sasa_array # Solvent accessibility (optional) ) ``` **Key Methods:** ```python # Load from PDB file protein = ESMProtein.from_pdb("protein.pdb") # Export to PDB format pdb_string = protein.to_pdb() # Save to file with open("output.pdb", "w") as f: f.write(protein.to_pdb()) ``` **Masking Conventions:** Use `_` (underscore) to represent masked positions for generation: ```python # Mask positions 5-10 for generation protein = ESMProtein(sequence="MPRT______AGLIVHSP") # Fully masked sequence (generate from scratch) protein = ESMProtein(sequence="_" * 200) # Partial structure (some coordinates None) protein = ESMProtein( sequence="MPRTKEIND", coordinates=partial_coords # Some positions can be None ) ``` ### GenerationConfig Class Controls generation behavior and parameters. **Basic Configuration:** ```python from esm.sdk.api import GenerationConfig config = GenerationConfig( track="sequence", # Track to generate: "sequence", "structure", or "function" num_steps=8, # Number of demasking steps temperature=0.7, # Sampling temperature (0.0-1.0) top_p=None, # Nucleus sampling threshold condition_on_coordinates_only=False # For structure conditioning ) ``` **Parameter Details:** - **track**: Which modality to generate - `"sequence"`: Generate amino acid sequence - `"structure"`: Generate 3D coordinates - `"function"`: Generate function annotations - **num_steps**: Number of iterative demasking steps - Higher = slower but potentially better quality - Typical range: 8-100 depending on sequence length - For full sequence generation: approximately sequence_length / 2 - **temperature**: Controls randomness - 0.0: Fully deterministic (greedy decoding) - 0.5-0.7: Balanced exploration - 1.0: Maximum diversity - Higher values increase novelty but may reduce quality - **top_p**: Nucleus sampling parameter - Limits sampling to top probability mass - Values: 0.0-1.0 (e.g., 0.9 = sample from top 90% probability mass) - Use for controlled diversity without extreme sampling - **condition_on_coordinates_only**: Structure conditioning mode - `True`: Condition only on backbone coordinates (ignore sequence) - Useful for inverse folding tasks ### ESM3InferenceClient Interface The unified interface for both local and remote inference. **Local Model Loading:** ```python from esm.models.esm3 import ESM3 # Load with automatic device placement model = ESM3.from_pretrained("esm3-sm-open-v1").to("cuda") # Or explicitly specify device model = ESM3.from_pretrained("esm3-sm-open-v1").to("cpu") ``` **Generation Method:** ```python # Basic generation protein_output = model.generate(protein_input, config) # With explicit track specification protein_output = model.generate( protein_input, GenerationConfig(track="sequence", num_steps=16, temperature=0.6) ) ``` **Logits and decoding (Advanced):** ```python from esm.sdk.api import LogitsConfig # encode -> tensor, request per-track logits for custom sampling protein_tensor = model.encode(protein) logits_output = model.logits( protein_tensor, LogitsConfig(sequence=True, structure=True, return_embeddings=True), ) logits_output.logits.sequence # per-position sequence logits logits_output.embeddings # representations # decode() turns a (possibly edited) tensor back into an ESMProtein protein = model.decode(protein_tensor) ``` `model.logits(...)` (not `model.forward(...)`) is the supported way to pull logits/embeddings; `model.decode(tensor)` reconstructs an `ESMProtein` from a tensor. ## Common Usage Patterns ### 1. Sequence Completion Fill in masked regions of a protein sequence: ```python # Define partial sequence protein = ESMProtein(sequence="MPRTK____LIVHSP____END") # Generate missing positions config = GenerationConfig(track="sequence", num_steps=12, temperature=0.5) completed = model.generate(protein, config) print(f"Original: {protein.sequence}") print(f"Completed: {completed.sequence}") ``` ### 2. Structure Prediction Predict 3D structure from sequence: ```python # Input: sequence only protein = ESMProtein(sequence="MPRTKEINDAGLIVHSPQWFYK") # Generate structure config = GenerationConfig(track="structure", num_steps=len(protein.sequence)) protein_with_structure = model.generate(protein, config) # Save as PDB with open("predicted_structure.pdb", "w") as f: f.write(protein_with_structure.to_pdb()) ``` ### 3. Inverse Folding Design sequence for a target structure: ```python # Load target structure target = ESMProtein.from_pdb("target.pdb") # Remove sequence, keep structure target.sequence = None # Generate sequence that folds to this structure config = GenerationConfig( track="sequence", num_steps=50, temperature=0.7, condition_on_coordinates_only=True ) designed = model.generate(target, config) print(f"Designed sequence: {designed.sequence}") ``` ### 4. Function-Conditioned Generation Generate protein with specific function: ```python from esm.sdk.api import FunctionAnnotation # Specify desired function protein = ESMProtein( sequence="_" * 150, function_annotations=[ FunctionAnnotation( label="enzymatic_activity", start=30, end=90 ) ] ) # Generate sequence with this function config = GenerationConfig(track="sequence", num_steps=75, temperature=0.6) functional_protein = model.generate(protein, config) ``` ### 5. Multi-Track Generation (Chain-of-Thought) Iteratively generate across multiple tracks: ```python # Start with partial sequence protein = ESMProtein(sequence="MPRT" + "_" * 100) # Step 1: Complete sequence protein = model.generate( protein, GenerationConfig(track="sequence", num_steps=50, temperature=0.6) ) # Step 2: Predict structure for completed sequence protein = model.generate( protein, GenerationConfig(track="structure", num_steps=50) ) # Step 3: Predict function protein = model.generate( protein, GenerationConfig(track="function", num_steps=20) ) print(f"Final sequence: {protein.sequence}") print(f"Functions: {protein.function_annotations}") ``` ### 6. Variant Generation Generate multiple variants of a protein: ```python import numpy as np base_sequence = "MPRTKEINDAGLIVHSPQWFYK" variants = [] for i in range(10): # Mask random positions seq_list = list(base_sequence) mask_indices = np.random.choice(len(seq_list), size=5, replace=False) for idx in mask_indices: seq_list[idx] = '_' protein = ESMProtein(sequence=''.join(seq_list)) # Generate variant variant = model.generate( protein, GenerationConfig(track="sequence", num_steps=8, temperature=0.8) ) variants.append(variant.sequence) print(f"Generated {len(variants)} variants") ``` ## Advanced Topics ### Temperature Scheduling Vary temperature during generation for better control: ```python def generate_with_temperature_schedule(model, protein, temperatures): """Generate with decreasing temperature for annealing.""" current = protein steps_per_temp = 10 for temp in temperatures: config = GenerationConfig( track="sequence", num_steps=steps_per_temp, temperature=temp ) current = model.generate(current, config) return current # Example: Start diverse, end deterministic result = generate_with_temperature_schedule( model, protein, temperatures=[1.0, 0.8, 0.6, 0.4, 0.2] ) ``` ### Constrained Generation Preserve specific regions during generation: ```python # Keep active site residues fixed def mask_except_active_site(sequence, active_site_positions): """Mask everything except specified positions.""" seq_list = ['_'] * len(sequence) for pos in active_site_positions: seq_list[pos] = sequence[pos] return ''.join(seq_list) # Define active site active_site = [23, 24, 25, 45, 46, 89] constrained_seq = mask_except_active_site(original_sequence, active_site) protein = ESMProtein(sequence=constrained_seq) result = model.generate(protein, GenerationConfig(track="sequence", num_steps=50)) ``` ### Secondary Structure Conditioning Use secondary structure information in generation: ```python # Define secondary structure (H=helix, E=sheet, C=coil) protein = ESMProtein( sequence="_" * 80, secondary_structure="CCHHHHHHHEEEEECCCHHHHHHCC" + "C" * 55 ) # Generate sequence with this structure result = model.generate( protein, GenerationConfig(track="sequence", num_steps=40, temperature=0.6) ) ``` ## Performance Optimization ### Memory Management For large proteins or batch processing: ```python import torch # Clear CUDA cache between generations torch.cuda.empty_cache() # Use half precision for memory efficiency model = ESM3.from_pretrained("esm3-sm-open-v1").to("cuda").half() # Process in chunks for very long sequences def chunk_generate(model, long_sequence, chunk_size=500): chunks = [long_sequence[i:i+chunk_size] for i in range(0, len(long_sequence), chunk_size)] results = [] for chunk in chunks: protein = ESMProtein(sequence=chunk) result = model.generate(protein, GenerationConfig(track="sequence")) results.append(result.sequence) return ''.join(results) ``` ### Batch Processing Tips When processing multiple proteins: 1. Sort by sequence length for efficient batching 2. Use padding for similar-length sequences 3. Process on GPU when available 4. Implement checkpointing for long-running jobs 5. Use Forge API for large-scale processing (see `forge-api.md`) ## Error Handling ```python try: protein = model.generate(protein_input, config) except ValueError as e: print(f"Invalid input: {e}") # Handle invalid sequence or structure except RuntimeError as e: print(f"Generation failed: {e}") # Handle model errors except torch.cuda.OutOfMemoryError: print("GPU out of memory - try smaller model or CPU") # Fallback to CPU or smaller model ``` ## Model-Specific Considerations **esm3-sm-open-v1:** - Suitable for development and testing - Lower quality than larger models - Fast inference on consumer GPUs - Open weights allow fine-tuning **esm3-medium-2024-08:** - Production quality - Good balance of speed and accuracy - Requires Forge API access - Recommended for most applications **esm3-large-2024-03:** - State-of-the-art quality - Slowest inference - Use for critical applications - Best for novel protein design ## Citation If using ESM3 in research, cite: ``` Hayes, T. et al. (2025). Simulating 500 million years of evolution with a language model. Science. DOI: 10.1126/science.ads0018 ``` -
forge-api.md 18.4 KB
# Biohub Platform (Forge) API Reference > **Rebrand, verified 2026-09.** EvolutionaryScale now operates as **Biohub**, and the > hosted API moved from `forge.evolutionaryscale.ai` to **`https://biohub.ai`**. The SDK > classes kept their `*ForgeInferenceClient` names, every client defaults to the new URL, > and the token is read from the `ESM_API_KEY` environment variable when not passed > explicitly. Prefer the helpers `esm.sdk.client(...)` (ESM3), `esm.sdk.esmc_client(...)` > (ESMC) and `esm.sdk.esmfold2_client(...)` (ESMFold2) over constructing the classes > directly. Model weights live under the Hugging Face `biohub` organisation. ## Overview The Biohub Platform is the hosted service for scalable protein design and inference. It provides API access to the full ESM model families, including large models not available for local execution. **Key Benefits:** - Access to all ESM3 models including 98B parameter version - No local GPU requirements - Scalable batch processing - Automatic updates to latest models - Production-ready infrastructure - Async/concurrent request support ## Getting Started ### 1. Obtain API Token Create an API token in the developer console: https://biohub.ai/developer-console/api-keys Then export it so the SDK picks it up automatically: ```bash export ESM_API_KEY="<your-token>" ``` ### 2. Install ESM SDK ```bash pip install esm ``` The Forge client is included in the standard ESM package. ### 3. Basic Connection ```python import esm from esm.sdk.api import ESMProtein, GenerationConfig # url defaults to https://biohub.ai, token to os.environ["ESM_API_KEY"] client = esm.sdk.client("esm3-medium-2024-08") # Equivalent explicit form: # from esm.sdk.forge import ESM3ForgeInferenceClient # client = ESM3ForgeInferenceClient("esm3-medium-2024-08", url="https://biohub.ai", token="<token>") # Test connection protein = ESMProtein(sequence="MPRT___KEND") result = client.generate(protein, GenerationConfig(track="sequence", num_steps=8)) print(result.sequence) ``` ## Available Models | Model ID | Parameters | Speed | Quality | Use Case | |----------|-----------|-------|---------|----------| | `esm3-small-2024-08` | 1.4B | Fastest | Good | Rapid prototyping, testing | | `esm3-medium-2024-08` | 7B | Fast | Excellent | Production, most applications | | `esm3-large-2024-03` | 98B | Slower | Best | Research, critical designs | | `esm3-medium-multimer-2024-09` | 7B | Fast | Experimental | Protein complexes | > **Check the console before relying on a model ID.** Biohub's current ESM3 model > table lists `esm3-small-2024-08` (1.4B), `esm3-medium-2024-08` (7B) and > `esm3-large-2024-03` (98B) as the flagship models, plus the 2024-03 "published" > replicas used in the paper. Other IDs (e.g. the experimental multimer model) may > or may not still be served — confirm in the developer console rather than > hard-coding them. **Model Selection Guidelines:** - **Development/Testing**: Use `esm3-small-2024-08` for quick iteration - **Production**: Use `esm3-medium-2024-08` for best balance - **Research/Critical**: Use `esm3-large-2024-03` for highest quality - **Complexes**: Use `esm3-medium-multimer-2024-09` (experimental) ## ESM3ForgeInferenceClient API ### Initialization ```python from esm.sdk.forge import ESM3ForgeInferenceClient # Basic initialization client = ESM3ForgeInferenceClient( model="esm3-medium-2024-08", token="<your-token>" ) # With custom URL (for enterprise deployments) client = ESM3ForgeInferenceClient( model="esm3-medium-2024-08", url="https://custom.forge.instance.com", token="<your-token>" ) # With timeout configuration client = ESM3ForgeInferenceClient( model="esm3-medium-2024-08", token="<your-token>", timeout=300 # 5 minutes ) ``` ### Synchronous Generation Standard blocking generation calls: ```python from esm.sdk.api import ESMProtein, GenerationConfig # Basic generation protein = ESMProtein(sequence="MPRT___KEND") config = GenerationConfig(track="sequence", num_steps=8) result = client.generate(protein, config) print(f"Generated: {result.sequence}") ``` ### Asynchronous Generation For concurrent processing of multiple proteins: ```python import asyncio from esm.sdk.api import ESMProtein, GenerationConfig async def generate_many(client, proteins): """Generate multiple proteins concurrently.""" tasks = [] for protein in proteins: task = client.async_generate( protein, GenerationConfig(track="sequence", num_steps=8) ) tasks.append(task) results = await asyncio.gather(*tasks) return results # Usage proteins = [ ESMProtein(sequence=f"MPRT{'_' * 10}KEND"), ESMProtein(sequence=f"AGLV{'_' * 10}HSPQ"), ESMProtein(sequence=f"KEIT{'_' * 10}NDFL") ] results = asyncio.run(generate_many(client, proteins)) print(f"Generated {len(results)} proteins") ``` ### Batch Processing with BatchExecutor For large-scale processing with automatic concurrency management: ```python from esm.sdk.forge import BatchExecutor from esm.sdk.api import ESMProtein, GenerationConfig # Create batch executor executor = BatchExecutor( client=client, max_concurrent=10 # Process 10 requests concurrently ) # Prepare batch of proteins proteins = [ESMProtein(sequence=f"MPRT{'_' * 50}KEND") for _ in range(100)] config = GenerationConfig(track="sequence", num_steps=25) # Submit batch batch_results = executor.submit_batch( proteins=proteins, config=config, progress_callback=lambda i, total: print(f"Processed {i}/{total}") ) print(f"Completed {len(batch_results)} generations") ``` ## Rate Limiting and Quotas ### Understanding Limits Forge implements rate limiting based on: - Requests per minute (RPM) - Tokens per minute (TPM) - Concurrent requests **Typical Limits (subject to change):** - Free tier: 60 RPM, 5 concurrent - Pro tier: 300 RPM, 20 concurrent - Enterprise: Custom limits ### Handling Rate Limits ```python import time from requests.exceptions import HTTPError def generate_with_retry(client, protein, config, max_retries=3): """Generate with automatic retry on rate limit.""" for attempt in range(max_retries): try: return client.generate(protein, config) except HTTPError as e: if e.response.status_code == 429: # Rate limit wait_time = 2 ** attempt # Exponential backoff print(f"Rate limited, waiting {wait_time}s...") time.sleep(wait_time) else: raise raise Exception("Max retries exceeded") # Usage result = generate_with_retry(client, protein, config) ``` ### Implementing Custom Rate Limiter ```python import time from collections import deque class RateLimiter: """Simple rate limiter for API calls.""" def __init__(self, max_per_minute=60): self.max_per_minute = max_per_minute self.calls = deque() def wait_if_needed(self): """Wait if rate limit would be exceeded.""" now = time.time() # Remove old calls while self.calls and self.calls[0] < now - 60: self.calls.popleft() # Wait if at limit if len(self.calls) >= self.max_per_minute: sleep_time = 60 - (now - self.calls[0]) if sleep_time > 0: time.sleep(sleep_time) self.calls.popleft() self.calls.append(now) # Usage limiter = RateLimiter(max_per_minute=60) for protein in proteins: limiter.wait_if_needed() result = client.generate(protein, config) ``` ## Advanced Patterns ### Streaming Results Process results as they complete: ```python import asyncio from concurrent.futures import ThreadPoolExecutor async def stream_generate(client, proteins, config): """Stream results as they complete.""" pending = { asyncio.create_task(client.async_generate(p, config)): i for i, p in enumerate(proteins) } results = [None] * len(proteins) while pending: done, pending = await asyncio.wait( pending.keys(), return_when=asyncio.FIRST_COMPLETED ) for task in done: idx = pending.pop(task) result = await task results[idx] = result yield idx, result # Usage async def process_stream(): async for idx, result in stream_generate(client, proteins, config): print(f"Completed protein {idx}: {result.sequence[:20]}...") asyncio.run(process_stream()) ``` ### Batch with Progress Tracking ```python from tqdm import tqdm import asyncio async def batch_with_progress(client, proteins, config): """Process batch with progress bar.""" results = [] with tqdm(total=len(proteins)) as pbar: for protein in proteins: result = await client.async_generate(protein, config) results.append(result) pbar.update(1) return results # Usage results = asyncio.run(batch_with_progress(client, proteins, config)) ``` ### Checkpoint and Resume For long-running batch jobs: ```python import pickle import os class CheckpointedBatchProcessor: """Batch processor with checkpoint/resume capability.""" def __init__(self, client, checkpoint_file="checkpoint.pkl"): self.client = client self.checkpoint_file = checkpoint_file self.completed = self.load_checkpoint() def load_checkpoint(self): if os.path.exists(self.checkpoint_file): with open(self.checkpoint_file, 'rb') as f: return pickle.load(f) return {} def save_checkpoint(self): with open(self.checkpoint_file, 'wb') as f: pickle.dump(self.completed, f) def process_batch(self, proteins, config): """Process batch with checkpointing.""" results = {} for i, protein in enumerate(proteins): # Skip if already completed if i in self.completed: results[i] = self.completed[i] continue try: result = self.client.generate(protein, config) results[i] = result self.completed[i] = result # Save checkpoint every 10 items if i % 10 == 0: self.save_checkpoint() except Exception as e: print(f"Error processing {i}: {e}") self.save_checkpoint() raise self.save_checkpoint() return results # Usage processor = CheckpointedBatchProcessor(client) results = processor.process_batch(proteins, config) ``` ## Error Handling ### Common Errors and Solutions ```python from requests.exceptions import HTTPError, ConnectionError, Timeout def robust_generate(client, protein, config): """Generate with comprehensive error handling.""" try: return client.generate(protein, config) except HTTPError as e: if e.response.status_code == 401: raise ValueError("Invalid API token") elif e.response.status_code == 429: raise ValueError("Rate limit exceeded - slow down requests") elif e.response.status_code == 500: raise ValueError("Server error - try again later") else: raise except ConnectionError: raise ValueError("Network error - check internet connection") except Timeout: raise ValueError("Request timeout - try smaller protein or increase timeout") except Exception as e: raise ValueError(f"Unexpected error: {str(e)}") # Usage with retry logic def generate_with_full_retry(client, protein, config, max_retries=3): """Combine error handling with retry logic.""" for attempt in range(max_retries): try: return robust_generate(client, protein, config) except ValueError as e: if "rate limit" in str(e).lower() and attempt < max_retries - 1: time.sleep(2 ** attempt) continue raise ``` ## Cost Optimization ### Strategies to Reduce Costs **1. Use Appropriate Model Size:** ```python # Use smaller model for testing dev_client = ESM3ForgeInferenceClient( model="esm3-small-2024-08", token=token ) # Use larger model only for final generation prod_client = ESM3ForgeInferenceClient( model="esm3-large-2024-03", token=token ) ``` **2. Cache Results:** ```python import hashlib import json class ForgeCache: """Cache Forge API results locally.""" def __init__(self, cache_dir="forge_cache"): self.cache_dir = cache_dir os.makedirs(cache_dir, exist_ok=True) def get_cache_key(self, protein, config): """Generate cache key from inputs.""" data = { 'sequence': protein.sequence, 'config': str(config) } return hashlib.md5(json.dumps(data, sort_keys=True).encode()).hexdigest() def get(self, protein, config): """Get cached result.""" key = self.get_cache_key(protein, config) path = os.path.join(self.cache_dir, f"{key}.pkl") if os.path.exists(path): with open(path, 'rb') as f: return pickle.load(f) return None def set(self, protein, config, result): """Cache result.""" key = self.get_cache_key(protein, config) path = os.path.join(self.cache_dir, f"{key}.pkl") with open(path, 'wb') as f: pickle.dump(result, f) # Usage cache = ForgeCache() def cached_generate(client, protein, config): """Generate with caching.""" cached = cache.get(protein, config) if cached: return cached result = client.generate(protein, config) cache.set(protein, config, result) return result ``` **3. Batch Similar Requests:** Group similar generation tasks to reduce overhead: ```python def batch_similar_tasks(proteins, max_batch_size=50): """Group proteins by similar properties.""" # Sort by length for efficient processing sorted_proteins = sorted(proteins, key=lambda p: len(p.sequence)) batches = [] current_batch = [] for protein in sorted_proteins: current_batch.append(protein) if len(current_batch) >= max_batch_size: batches.append(current_batch) current_batch = [] if current_batch: batches.append(current_batch) return batches ``` ## Monitoring and Logging ### Track API Usage ```python import logging from datetime import datetime class ForgeMonitor: """Monitor Forge API usage.""" def __init__(self): self.calls = [] self.errors = [] def log_call(self, model, protein_length, duration, success=True, error=None): """Log API call.""" entry = { 'timestamp': datetime.now(), 'model': model, 'protein_length': protein_length, 'duration': duration, 'success': success, 'error': str(error) if error else None } if success: self.calls.append(entry) else: self.errors.append(entry) def get_stats(self): """Get usage statistics.""" total_calls = len(self.calls) + len(self.errors) success_rate = len(self.calls) / total_calls if total_calls > 0 else 0 avg_duration = sum(c['duration'] for c in self.calls) / len(self.calls) if self.calls else 0 return { 'total_calls': total_calls, 'successful': len(self.calls), 'failed': len(self.errors), 'success_rate': success_rate, 'avg_duration': avg_duration } # Usage monitor = ForgeMonitor() def monitored_generate(client, protein, config): """Generate with monitoring.""" start = time.time() try: result = client.generate(protein, config) duration = time.time() - start monitor.log_call( model=client.model, protein_length=len(protein.sequence), duration=duration, success=True ) return result except Exception as e: duration = time.time() - start monitor.log_call( model=client.model, protein_length=len(protein.sequence), duration=duration, success=False, error=e ) raise # Check stats print(monitor.get_stats()) ``` ## AWS SageMaker Deployment For dedicated infrastructure and enterprise use: ### Deployment Options 1. **AWS Marketplace Listing**: Deploy ESM3 via AWS SageMaker Marketplace 2. **Custom Endpoint**: Configure dedicated inference endpoint 3. **Batch Transform**: Use SageMaker Batch Transform for large-scale processing ### Benefits - Dedicated compute resources - No rate limiting beyond your infrastructure - Data stays in your AWS environment - Integration with AWS services - Custom instance types and scaling **More Information:** - AWS Marketplace: https://aws.amazon.com/marketplace/seller-profile?id=seller-iw2nbscescndm - Contact Biohub for enterprise licensing ## Best Practices Summary 1. **Authentication**: Store tokens securely (environment variables, secrets manager) 2. **Rate Limiting**: Implement exponential backoff and respect limits 3. **Error Handling**: Always handle network errors and retries 4. **Caching**: Cache results for repeated queries 5. **Model Selection**: Use appropriate model size for task 6. **Batch Processing**: Use async/batch processing for multiple proteins 7. **Monitoring**: Track usage and costs 8. **Checkpointing**: Save progress for long-running jobs ## Troubleshooting ### Connection Issues ```python # Test connection try: client = ESM3ForgeInferenceClient(model="esm3-medium-2024-08", token=token) test_protein = ESMProtein(sequence="MPRTK") result = client.generate(test_protein, GenerationConfig(track="sequence", num_steps=1)) print("Connection successful!") except Exception as e: print(f"Connection failed: {e}") ``` ### Token Validation ```python def validate_token(token): """Validate API token.""" try: client = ESM3ForgeInferenceClient( model="esm3-small-2024-08", token=token ) # Make minimal test call test = ESMProtein(sequence="MPR") client.generate(test, GenerationConfig(track="sequence", num_steps=1)) return True except HTTPError as e: if e.response.status_code == 401: return False raise ``` ## Additional Resources - **Biohub Platform**: https://biohub.ai - **API Documentation**: Check Forge dashboard for latest API specs - **Community Support**: Slack community at https://bit.ly/3FKwcWd - **Enterprise Contact**: Contact Biohub for custom deployments -
workflows.md 20.7 KB
# ESM Workflows and Examples ## Overview This document provides complete, end-to-end examples of common workflows using ESM3 and ESM C. Each workflow includes setup, execution, and analysis code. ## Workflow 1: Novel GFP Design with Chain-of-Thought Design a novel fluorescent protein using ESM3's multimodal generation capabilities. ### Objective Generate a green fluorescent protein (GFP) with specific properties using chain-of-thought reasoning across sequence, structure, and function. ### Complete Implementation ```python from esm.models.esm3 import ESM3 from esm.sdk.api import ESMProtein, GenerationConfig, FunctionAnnotation import matplotlib.pyplot as plt # Setup model = ESM3.from_pretrained("esm3-sm-open-v1").to("cuda") # Step 1: Define target properties print("Step 1: Defining target GFP properties...") # Create protein with desired function target_length = 238 # Typical GFP length protein = ESMProtein( sequence="_" * target_length, function_annotations=[ FunctionAnnotation( label="green_fluorescent_protein", start=65, end=75 # Chromophore region ) ] ) # Step 2: Generate initial sequence with function conditioning print("Step 2: Generating initial sequence...") config = GenerationConfig( track="sequence", num_steps=target_length // 3, # Gradual generation temperature=0.7 # Moderate diversity ) protein = model.generate(protein, config) print(f"Generated sequence: {protein.sequence[:50]}...") # Step 3: Predict structure print("Step 3: Predicting structure...") config = GenerationConfig( track="structure", num_steps=target_length // 2 ) protein = model.generate(protein, config) print(f"Structure predicted, coordinates shape: {protein.coordinates.shape}") # Step 4: Refine sequence based on structure print("Step 4: Refining sequence based on structure...") # Mask regions for refinement (e.g., surface residues) sequence_list = list(protein.sequence) # Keep chromophore region, refine others for i in range(0, 65): if i % 3 == 0: # Refine every third position sequence_list[i] = '_' for i in range(75, target_length): if i % 3 == 0: sequence_list[i] = '_' protein.sequence = ''.join(sequence_list) config = GenerationConfig( track="sequence", num_steps=50, temperature=0.5 # Lower temperature for refinement ) protein = model.generate(protein, config) # Step 5: Final validation print("Step 5: Final validation...") # Predict final structure config = GenerationConfig(track="structure", num_steps=30) protein = model.generate(protein, config) # Save results with open("novel_gfp.pdb", "w") as f: f.write(protein.to_pdb()) with open("novel_gfp_sequence.txt", "w") as f: f.write(f">Novel_GFP\n{protein.sequence}\n") print(f"\nFinal GFP sequence:\n{protein.sequence}") print(f"\nFunction annotations: {protein.function_annotations}") print(f"Structure saved to: novel_gfp.pdb") ``` ### Validation Steps ```python # Analyze designed GFP def analyze_gfp(protein): """Analyze generated GFP properties.""" # Check chromophore region (should be around Ser65-Tyr66-Gly67) chromophore_region = protein.sequence[64:68] print(f"Chromophore region: {chromophore_region}") # Check barrel structure (GFPs have beta-barrel) # Analyze secondary structure if available if protein.secondary_structure: beta_content = protein.secondary_structure.count('E') / len(protein.sequence) print(f"Beta sheet content: {beta_content:.2%}") # Check sequence similarity to known GFPs # (Would require BLAST or alignment tool in practice) return { 'length': len(protein.sequence), 'chromophore': chromophore_region, 'coordinates_available': protein.coordinates is not None } analysis = analyze_gfp(protein) print(f"\nAnalysis results: {analysis}") ``` ## Workflow 2: Protein Variant Library Generation Generate and analyze a library of protein variants for directed evolution. ### Objective Create variants of a parent protein by targeted mutagenesis while maintaining structural integrity. ### Complete Implementation ```python from esm.models.esm3 import ESM3 from esm.sdk.api import ESMProtein, GenerationConfig import numpy as np from sklearn.cluster import KMeans # Setup model = ESM3.from_pretrained("esm3-sm-open-v1").to("cuda") # Parent protein parent_sequence = "MPRTKEINDAGLIVHSPQWFYKARNDTESLGKIVHEFPM" parent_protein = ESMProtein(sequence=parent_sequence) # Define mutation parameters num_variants = 50 positions_to_mutate = 5 # Number of positions per variant # Step 1: Generate variant library print("Generating variant library...") variants = [] for i in range(num_variants): # Create masked sequence with random positions seq_list = list(parent_sequence) # Select random positions to mutate mutation_positions = np.random.choice( len(seq_list), size=positions_to_mutate, replace=False ) for pos in mutation_positions: seq_list[pos] = '_' # Generate variant variant_protein = ESMProtein(sequence=''.join(seq_list)) config = GenerationConfig( track="sequence", num_steps=positions_to_mutate * 2, temperature=0.8 # Higher diversity ) variant = model.generate(variant_protein, config) variants.append(variant.sequence) if (i + 1) % 10 == 0: print(f"Generated {i + 1}/{num_variants} variants") print(f"\nGenerated {len(variants)} variants") # Step 2: Predict structures for variants print("\nPredicting structures...") variant_proteins_with_structure = [] for i, seq in enumerate(variants): protein = ESMProtein(sequence=seq) config = GenerationConfig( track="structure", num_steps=len(seq) // 2 ) protein_with_structure = model.generate(protein, config) variant_proteins_with_structure.append(protein_with_structure) if (i + 1) % 10 == 0: print(f"Predicted structures for {i + 1}/{len(variants)} variants") # Step 3: Analyze variant diversity print("\nAnalyzing variant diversity...") # Calculate Hamming distances from parent def hamming_distance(seq1, seq2): """Calculate Hamming distance between sequences.""" return sum(c1 != c2 for c1, c2 in zip(seq1, seq2)) distances = [hamming_distance(parent_sequence, var) for var in variants] print(f"Average mutations per variant: {np.mean(distances):.1f}") print(f"Mutation range: {min(distances)}-{max(distances)}") # Step 4: Get embeddings for clustering print("\nGenerating embeddings for clustering...") from esm.models.esmc import ESMC from esm.sdk.api import LogitsConfig embedding_model = ESMC.from_pretrained("esmc_300m").to("cuda") _emb_cfg = LogitsConfig(sequence=True, return_embeddings=True) def get_embedding(sequence): """Get mean-pooled embedding for sequence.""" protein = ESMProtein(sequence=sequence) out = embedding_model.logits(embedding_model.encode(protein), _emb_cfg) return out.embeddings.mean(dim=1).cpu().detach().numpy().flatten() variant_embeddings = np.array([get_embedding(seq) for seq in variants]) # Step 5: Cluster variants print("Clustering variants...") n_clusters = 5 kmeans = KMeans(n_clusters=n_clusters, random_state=42) cluster_labels = kmeans.fit_predict(variant_embeddings) # Analyze clusters print("\nCluster analysis:") for i in range(n_clusters): cluster_variants = [var for var, label in zip(variants, cluster_labels) if label == i] cluster_distances = [hamming_distance(parent_sequence, var) for var in cluster_variants] print(f"\nCluster {i}:") print(f" Size: {len(cluster_variants)}") print(f" Avg distance from parent: {np.mean(cluster_distances):.1f}") print(f" Representative: {cluster_variants[0][:40]}...") # Step 6: Select diverse representatives print("\nSelecting diverse representatives...") representatives = [] for i in range(n_clusters): # Get centroid cluster_indices = np.where(cluster_labels == i)[0] cluster_embs = variant_embeddings[cluster_indices] # Find closest to centroid centroid = cluster_embs.mean(axis=0) distances_to_centroid = np.linalg.norm(cluster_embs - centroid, axis=1) rep_idx = cluster_indices[np.argmin(distances_to_centroid)] representatives.append(variants[rep_idx]) # Save results print("\nSaving results...") with open("variant_library.fasta", "w") as f: f.write(f">Parent\n{parent_sequence}\n\n") for i, var in enumerate(variants): f.write(f">Variant_{i+1}_Cluster_{cluster_labels[i]}\n{var}\n") with open("representative_variants.fasta", "w") as f: for i, rep in enumerate(representatives): f.write(f">Representative_Cluster_{i}\n{rep}\n") print("Variant library saved to: variant_library.fasta") print("Representatives saved to: representative_variants.fasta") ``` ## Workflow 3: Structure-Based Sequence Optimization Optimize a protein sequence to improve stability while maintaining function. ### Objective Given a protein structure, design sequences that maintain the fold but have improved properties. ### Complete Implementation ```python from esm.models.esm3 import ESM3 from esm.sdk.api import ESMProtein, GenerationConfig import numpy as np # Setup model = ESM3.from_pretrained("esm3-sm-open-v1").to("cuda") # Load target structure (e.g., from PDB) target_protein = ESMProtein.from_pdb("target_structure.pdb") original_sequence = target_protein.sequence print(f"Original sequence: {original_sequence}") print(f"Structure loaded: {target_protein.coordinates.shape}") # Step 1: Generate multiple sequence designs print("\nGenerating optimized sequences...") num_designs = 20 optimized_sequences = [] for i in range(num_designs): # Start with structure, remove sequence # coordinates is a torch tensor in the ESM SDK -> use .clone(), not .copy() design_protein = ESMProtein( coordinates=target_protein.coordinates.clone(), secondary_structure=target_protein.secondary_structure ) # Generate sequence for this structure config = GenerationConfig( track="sequence", num_steps=len(original_sequence), temperature=0.7, condition_on_coordinates_only=True ) designed = model.generate(design_protein, config) optimized_sequences.append(designed.sequence) if (i + 1) % 5 == 0: print(f"Generated {i + 1}/{num_designs} designs") # Step 2: Validate structural compatibility print("\nValidating structural compatibility...") validated_designs = [] for seq in optimized_sequences: # Predict structure for designed sequence test_protein = ESMProtein(sequence=seq) config = GenerationConfig( track="structure", num_steps=len(seq) // 2 ) predicted = model.generate(test_protein, config) # Calculate RMSD (simplified - in practice use proper alignment) # Here we just check if structure prediction succeeds if predicted.coordinates is not None: validated_designs.append(seq) print(f"Validated {len(validated_designs)}/{num_designs} designs") # Step 3: Analyze sequence properties print("\nAnalyzing sequence properties...") def calculate_properties(sequence): """Calculate basic sequence properties.""" # Hydrophobicity (simplified) hydrophobic = "AILMFWYV" hydrophobic_fraction = sum(1 for aa in sequence if aa in hydrophobic) / len(sequence) # Charge positive = "KR" negative = "DE" net_charge = sum(1 for aa in sequence if aa in positive) - sum(1 for aa in sequence if aa in negative) # Aromatic content aromatic = "FWY" aromatic_fraction = sum(1 for aa in sequence if aa in aromatic) / len(sequence) return { 'hydrophobic_fraction': hydrophobic_fraction, 'net_charge': net_charge, 'aromatic_fraction': aromatic_fraction } # Compare to original original_props = calculate_properties(original_sequence) print(f"\nOriginal properties:") print(f" Hydrophobic: {original_props['hydrophobic_fraction']:.2%}") print(f" Net charge: {original_props['net_charge']:+d}") print(f" Aromatic: {original_props['aromatic_fraction']:.2%}") # Analyze designs design_properties = [calculate_properties(seq) for seq in validated_designs] avg_hydrophobic = np.mean([p['hydrophobic_fraction'] for p in design_properties]) avg_charge = np.mean([p['net_charge'] for p in design_properties]) avg_aromatic = np.mean([p['aromatic_fraction'] for p in design_properties]) print(f"\nDesigned sequences (average):") print(f" Hydrophobic: {avg_hydrophobic:.2%}") print(f" Net charge: {avg_charge:+.1f}") print(f" Aromatic: {avg_aromatic:.2%}") # Step 4: Rank designs print("\nRanking designs...") def score_design(sequence, original_props): """Score design based on desired properties.""" props = calculate_properties(sequence) # Prefer higher hydrophobic content (for stability) hydrophobic_score = props['hydrophobic_fraction'] # Prefer similar charge to original charge_score = 1.0 / (1.0 + abs(props['net_charge'] - original_props['net_charge'])) # Combined score return hydrophobic_score * 0.6 + charge_score * 0.4 scores = [(seq, score_design(seq, original_props)) for seq in validated_designs] scores.sort(key=lambda x: x[1], reverse=True) print("\nTop 5 designs:") for i, (seq, score) in enumerate(scores[:5]): print(f"\n{i+1}. Score: {score:.3f}") print(f" Sequence: {seq[:40]}...") # Step 5: Save results print("\nSaving results...") with open("optimized_sequences.fasta", "w") as f: f.write(f">Original\n{original_sequence}\n\n") for i, (seq, score) in enumerate(scores): props = calculate_properties(seq) f.write(f">Design_{i+1}_Score_{score:.3f}\n") f.write(f"# Hydrophobic: {props['hydrophobic_fraction']:.2%}, ") f.write(f"Charge: {props['net_charge']:+d}, ") f.write(f"Aromatic: {props['aromatic_fraction']:.2%}\n") f.write(f"{seq}\n\n") print("Results saved to: optimized_sequences.fasta") ``` ## Workflow 4: Function Prediction Pipeline Predict protein function from sequence using ESM3 and ESM C. ### Objective Build a pipeline that predicts protein function using both generative (ESM3) and embedding (ESM C) approaches. ### Complete Implementation ```python from esm.models.esm3 import ESM3 from esm.models.esmc import ESMC from esm.sdk.api import ESMProtein, GenerationConfig, LogitsConfig import numpy as np from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import cross_val_score # Setup models esm3_model = ESM3.from_pretrained("esm3-sm-open-v1").to("cuda") esmc_model = ESMC.from_pretrained("esmc_600m").to("cuda") # Example: Predict if protein is an enzyme # (In practice, you'd have a labeled training set) def predict_function_generative(sequence): """Predict function using ESM3 generative approach.""" protein = ESMProtein(sequence=sequence) # Generate function annotations config = GenerationConfig( track="function", num_steps=20, temperature=0.3 # Low temperature for confident predictions ) protein_with_function = esm3_model.generate(protein, config) return protein_with_function.function_annotations def predict_function_embedding(sequence, function_classifier): """Predict function using ESM C embeddings + classifier.""" # Get embedding protein = ESMProtein(sequence=sequence) out = esmc_model.logits( esmc_model.encode(protein), LogitsConfig(sequence=True, return_embeddings=True), ) # Mean pool over the token axis embedding_pooled = out.embeddings.mean(dim=1).cpu().detach().numpy() # Predict with classifier prediction = function_classifier.predict(embedding_pooled) probability = function_classifier.predict_proba(embedding_pooled) return prediction[0], probability[0] # Example workflow with test sequences test_sequences = { "kinase": "MPRTKEINDAGLIVHSPQWFYKARNDTESLGKIVHEF", "protease": "AGLIVHSPQWFYKARNDTESLGKIVHEFPMCDEGH", "transporter": "KTEFLNDGRPMLIVHSPQWFYKARNDTESLGKIVH" } print("Predicting functions...\n") for name, sequence in test_sequences.items(): print(f"{name.upper()}:") print(f"Sequence: {sequence[:30]}...") # Method 1: Generative functions = predict_function_generative(sequence) print(f" Generative predictions: {functions}") # Method 2: Embedding-based would require trained classifier # (Skipped in this example as it needs training data) print() ``` ## Workflow 5: Embedding-Based Clustering and Analysis Cluster and analyze a large protein dataset using ESM C embeddings. ### Complete Implementation ```python from esm.models.esmc import ESMC from esm.sdk.api import ESMProtein, LogitsConfig import numpy as np from sklearn.cluster import DBSCAN from sklearn.decomposition import PCA from sklearn.manifold import TSNE import matplotlib.pyplot as plt # Setup model = ESMC.from_pretrained("esmc_600m").to("cuda") emb_cfg = LogitsConfig(sequence=True, return_embeddings=True) # Load protein dataset (example) sequences = [ # In practice, load from FASTA or database "MPRTKEINDAGLIVHSPQWFYK", "AGLIVHSPQWFYKARNDTESL", # ... more sequences ] print(f"Loaded {len(sequences)} sequences") # Step 1: Generate embeddings print("Generating embeddings...") embeddings = [] for i, seq in enumerate(sequences): protein = ESMProtein(sequence=seq) out = model.logits(model.encode(protein), emb_cfg) # Mean pooling over the token axis emb_pooled = out.embeddings.mean(dim=1).cpu().detach().numpy().flatten() embeddings.append(emb_pooled) if (i + 1) % 100 == 0: print(f"Processed {i + 1}/{len(sequences)}") embeddings = np.array(embeddings) print(f"Embeddings shape: {embeddings.shape}") # Step 2: Dimensionality reduction for visualization print("\nReducing dimensionality...") # PCA for initial reduction pca = PCA(n_components=50) embeddings_pca = pca.fit_transform(embeddings) print(f"PCA explained variance: {pca.explained_variance_ratio_[:10].sum():.2%}") # t-SNE for visualization tsne = TSNE(n_components=2, random_state=42) embeddings_2d = tsne.fit_transform(embeddings_pca) # Step 3: Clustering print("\nClustering...") # DBSCAN for density-based clustering. # Cluster on the PCA-reduced space (same space the t-SNE plot is derived from), # and tune `eps` to your data — 0.5 is rarely right for raw high-dim embeddings. clustering = DBSCAN(eps=0.5, min_samples=5) cluster_labels = clustering.fit_predict(embeddings_pca) n_clusters = len(set(cluster_labels)) - (1 if -1 in cluster_labels else 0) n_noise = list(cluster_labels).count(-1) print(f"Number of clusters: {n_clusters}") print(f"Number of noise points: {n_noise}") # Step 4: Visualize print("\nGenerating visualization...") plt.figure(figsize=(12, 8)) scatter = plt.scatter( embeddings_2d[:, 0], embeddings_2d[:, 1], c=cluster_labels, cmap='viridis', alpha=0.6 ) plt.colorbar(scatter) plt.title("Protein Sequence Clustering (ESM C Embeddings)") plt.xlabel("t-SNE 1") plt.ylabel("t-SNE 2") plt.savefig("protein_clusters.png", dpi=300, bbox_inches='tight') print("Visualization saved to: protein_clusters.png") # Step 5: Analyze clusters print("\nCluster analysis:") for cluster_id in range(n_clusters): cluster_indices = np.where(cluster_labels == cluster_id)[0] cluster_seqs = [sequences[i] for i in cluster_indices] print(f"\nCluster {cluster_id}:") print(f" Size: {len(cluster_seqs)}") print(f" Avg length: {np.mean([len(s) for s in cluster_seqs]):.1f}") print(f" Example: {cluster_seqs[0][:40]}...") # Save cluster assignments with open("cluster_assignments.txt", "w") as f: for i, (seq, label) in enumerate(zip(sequences, cluster_labels)): f.write(f"Sequence_{i}\tCluster_{label}\t{seq}\n") print("\nCluster assignments saved to: cluster_assignments.txt") ``` ## Additional Workflow Tips ### Memory Management for Large Datasets ```python def process_large_dataset(sequences, batch_size=32): """Process large dataset with memory management.""" import gc import torch results = [] for i in range(0, len(sequences), batch_size): batch = sequences[i:i + batch_size] # Process batch batch_results = [process_sequence(seq) for seq in batch] results.extend(batch_results) # Clear memory torch.cuda.empty_cache() gc.collect() if (i + batch_size) % 100 == 0: print(f"Processed {min(i + batch_size, len(sequences))}/{len(sequences)}") return results ``` ### Parallel Processing ```python from concurrent.futures import ThreadPoolExecutor import asyncio def parallel_workflow(sequences, n_workers=4): """Process sequences in parallel.""" with ThreadPoolExecutor(max_workers=n_workers) as executor: results = list(executor.map(process_sequence, sequences)) return results ``` These workflows provide comprehensive examples for common ESM use cases. Adapt them to your specific needs and always validate results with appropriate biological experiments.
-
-
SKILL.md 14.8 KB
--- name: alterlab-esm description: Run ESM protein language models — ESMC for embeddings and representations, ESMFold2 for structure prediction, and ESM3 for generative multimodal protein design across sequence, structure, and function — locally or through the hosted Biohub Platform API (formerly Forge). Use when working with protein sequences, structures, or function prediction, designing novel proteins, generating protein embeddings, performing inverse folding, or doing protein-engineering tasks. Part of the AlterLab Academic Skills suite. license: MIT allowed-tools: Read Write Edit Bash(python:*) Bash(uv:*) compatibility: "Runs under `uv run python` with the `esm` package (3.4.1.post1 as of 2026-09; requires Python >= 3.12 and torch >= 2.11). Local weights come from Hugging Face (`huggingface_hub` login) and run best on a CUDA GPU; CPU works for small ESMC embeddings but is slow. The hosted path is the Biohub Platform (`https://biohub.ai`, formerly forge.evolutionaryscale.ai) and needs an API token, read from the `ESM_API_KEY` environment variable by default." metadata: skill-author: AlterLab version: "1.1.0" last_updated: "2026-09-23" --- # ESM: Evolutionary Scale Modeling ## Overview ESM provides state-of-the-art protein language models for understanding, generating, and designing proteins. Three families ship in the same `esm` package: - **ESMC** — the current representation model (300M / 600M open weights, 6B via the hosted platform), used for embeddings, logits, and downstream ML features. - **ESMFold2** — structure prediction built on ESMC-6B, hosted via `esmfold2_client`. - **ESM3** — the generative multimodal model over sequence / structure / function tracks. ### Naming and hosting changed — read this before copying old code EvolutionaryScale now operates as **Biohub**. The practical consequences: - The hosted API moved from `forge.evolutionaryscale.ai` to **`https://biohub.ai`**; every client in `esm.sdk` defaults to that URL and reads the token from `ESM_API_KEY`. The classes are still named `*ForgeInferenceClient` for backwards compatibility. - Model weights live under the **`biohub`** Hugging Face organisation (`biohub/ESMC-6B`, `biohub/esm3-sm-open-v1`, `biohub/ESMFold2`). - ESMC and ESMFold2 are also available through Hugging Face Transformers v5.16+, which is a different dependency stack from the `esm` package's optimized inference path. ## When to Use This Skill Use this skill when the user wants protein embeddings or logits, a protein structure from an ESM model, inverse folding, or generative sequence design. ### Does NOT Trigger | Scenario | Use Instead | |----------|-------------| | Fold a protein or complex with AlphaFold2/ColabFold | `alterlab-alphafold` | | Co-fold with a ligand, or predict binding affinity | `alterlab-boltz` | | Antibody–antigen or mixed multi-entity complexes | `alterlab-chai` | | Design a sequence for a fixed backbone (ProteinMPNN family) | `alterlab-proteinmpnn`, `alterlab-ligandmpnn` | | Generate a de-novo backbone to design onto | `alterlab-rfdiffusion` | ## Core Capabilities ### 1. Protein Sequence Generation with ESM3 Generate novel protein sequences with desired properties using multimodal generative modeling. **When to use:** - Designing proteins with specific functional properties - Completing partial protein sequences - Generating variants of existing proteins - Creating proteins with desired structural characteristics **Basic usage:** ```python from esm.models.esm3 import ESM3 from esm.sdk.api import ESM3InferenceClient, ESMProtein, GenerationConfig # Load model locally model: ESM3InferenceClient = ESM3.from_pretrained("esm3-sm-open-v1").to("cuda") # Create protein prompt protein = ESMProtein(sequence="MPRT___KEND") # '_' represents masked positions # Generate completion protein = model.generate(protein, GenerationConfig(track="sequence", num_steps=8)) print(protein.sequence) ``` **For remote/cloud usage via the Biohub Platform:** ```python import esm from esm.sdk.api import ESMProtein, GenerationConfig # url defaults to https://biohub.ai and token to os.environ["ESM_API_KEY"] model = esm.sdk.client("esm3-medium-2024-08") protein = model.generate(protein, GenerationConfig(track="sequence", num_steps=8)) ``` See `references/esm3-api.md` for detailed ESM3 model specifications, advanced generation configurations, and multimodal prompting examples. ### 2. Structure Prediction and Inverse Folding Use ESM3's structure track for structure prediction from sequence or inverse folding (sequence design from structure). **Structure prediction:** ```python from esm.sdk.api import ESM3InferenceClient, ESMProtein, GenerationConfig # Predict structure from a complete sequence protein = ESMProtein(sequence="MPRTKEINDAGLIVHSP") protein_with_structure = model.generate( protein, # num_steps controls how many structure tokens are decoded per step; # use the sequence length (not a "_" count — the sequence is complete here) GenerationConfig(track="structure", num_steps=len(protein.sequence)) ) # Access predicted structure coordinates = protein_with_structure.coordinates # 3D coordinates pdb_string = protein_with_structure.to_pdb() ``` **Inverse folding (sequence from structure):** ```python # Design sequence for a target structure protein_with_structure = ESMProtein.from_pdb("target_structure.pdb") protein_with_structure.sequence = None # Remove sequence # Generate sequence that folds to this structure designed_protein = model.generate( protein_with_structure, GenerationConfig(track="sequence", num_steps=50, temperature=0.7) ) ``` ### 3. Protein Embeddings with ESM C Generate high-quality embeddings for downstream tasks like function prediction, classification, or similarity analysis. **When to use:** - Extracting protein representations for machine learning - Computing sequence similarities - Feature extraction for protein classification - Transfer learning for protein-related tasks **Basic usage:** ```python from esm.models.esmc import ESMC from esm.sdk.api import ESMProtein, LogitsConfig # Load ESMC locally (local from_pretrained names use UNDERSCORES; default is esmc_600m) model = ESMC.from_pretrained("esmc_300m").to("cuda") # Encode, then request embeddings via the logits() API protein = ESMProtein(sequence="MPRTKEINDAGLIVHSP") protein_tensor = model.encode(protein) out = model.logits(protein_tensor, LogitsConfig(sequence=True, return_embeddings=True)) embeddings = out.embeddings # (1, L+2, hidden_dim), incl. BOS/EOS tokens logits = out.logits.sequence # per-position amino-acid logits ``` Do NOT call `model.forward(...)` to get embeddings — `forward` returns a raw model output, not a usable representation tensor. Use `model.logits(..., LogitsConfig(return_embeddings=True)).embeddings`. **Batch processing:** ```python # Encode multiple proteins and pull mean-pooled embeddings proteins = [ ESMProtein(sequence="MPRTKEIND"), ESMProtein(sequence="AGLIVHSPQ"), ESMProtein(sequence="KTEFLNDGR"), ] cfg = LogitsConfig(sequence=True, return_embeddings=True) embeddings_list = [ model.logits(model.encode(p), cfg).embeddings.mean(dim=1) for p in proteins ] ``` See `references/esm-c-api.md` for ESM C model details, efficiency comparisons, and advanced embedding strategies. ### 4. Function Conditioning and Annotation Use ESM3's function track to generate proteins with specific functional annotations or predict function from sequence. **Function-conditioned generation:** ```python from esm.sdk.api import ESMProtein, FunctionAnnotation, GenerationConfig # Create protein with desired function protein = ESMProtein( sequence="_" * 200, # Generate 200 residue protein function_annotations=[ FunctionAnnotation(label="fluorescent_protein", start=50, end=150) ] ) # Generate sequence with specified function functional_protein = model.generate( protein, GenerationConfig(track="sequence", num_steps=200) ) ``` ### 5. Chain-of-Thought Generation Iteratively refine protein designs using ESM3's chain-of-thought generation approach. ```python from esm.sdk.api import GenerationConfig # Multi-step refinement protein = ESMProtein(sequence="MPRT" + "_" * 100 + "KEND") # Step 1: Generate initial structure config = GenerationConfig(track="structure", num_steps=50) protein = model.generate(protein, config) # Step 2: Refine sequence based on structure config = GenerationConfig(track="sequence", num_steps=50, temperature=0.5) protein = model.generate(protein, config) # Step 3: Predict function config = GenerationConfig(track="function", num_steps=20) protein = model.generate(protein, config) ``` ### 6. Batch processing against the hosted platform The client exposes both an async API and a parallel executor that handles retries and a progress bar: ```python import asyncio import esm from esm.sdk import parallel_executor from esm.sdk.api import ESMProtein, GenerationConfig client = esm.sdk.client("esm3-medium-2024-08") # token from ESM_API_KEY proteins = [ESMProtein(sequence=f"MPRT{'_' * 50}KEND") for _ in range(10)] cfg = GenerationConfig(track="sequence", num_steps=8) # Option A: async fan-out async def run(): return await asyncio.gather(*(client.async_generate(p, cfg) for p in proteins)) results = asyncio.run(run()) # Option B: managed executor (retries + progress) with parallel_executor() as executor: results = executor.execute_batch(client.generate, protein=proteins, config=cfg) ``` Also available: `client.batch_generate(...)` / `async_batch_generate(...)`, and `async_fold` / `async_inverse_fold` / `async_logits` for the other tracks. See `references/forge-api.md` for platform documentation, authentication, rate limits, and batch processing patterns. ## Model Selection Guide **ESM3 Models (Generative):** - `esm3-sm-open-v1` (1.4B, aliases `esm3_sm_open_v1` / `esm3-open`) — open weights, local - `esm3-medium-2024-08` (7B) — balance of quality and speed (hosted only) - `esm3-large-2024-03` (98B) — highest quality, slower (hosted only) **ESMC Models (Representations):** - `esmc_300m` — lightweight, fast; open weights, runs locally - `esmc_600m` — balanced; open weights, runs locally, and the `from_pretrained` default - `esmc_6b` / hosted `esmc-6b-2024-12` — maximum representation quality **ESMFold2 (Structure):** `esmfold2-fast-2026-05` (the `esmfold2_client` default), `esmfold2-2026-05`, `esmfold2-2026-05-cutoff-2025`. Naming gotcha: local `from_pretrained(...)` names use **underscores** (`esmc_300m`, `esmc_600m`, `esmc_6b`). Hosted client strings use **hyphens with a date** (`esmc-600m-2024-12`, `esm3-medium-2024-08`, `esmfold2-fast-2026-05`). **Selection criteria:** - **Local development/testing:** `esmc_300m` for embeddings, `esm3-sm-open-v1` for generation - **Production quality:** `esm3-medium-2024-08` or `esmc-6b-2024-12` via the Biohub Platform - **Structure prediction:** `esmfold2_client()` rather than the ESM3 structure track - **High throughput:** hosted clients plus `esm.sdk.parallel_executor` / batch client - **Cost optimization:** smaller models, cache embeddings ## Installation **Basic installation** (pin the minor — the SDK still changes across releases): ```bash uv pip install "esm>=3.4,<3.5" # 3.4.1.post1 as of 2026-09; needs Python >= 3.12 ``` **With Flash Attention (optional, faster GPU inference):** ```bash uv pip install flash-attn --no-build-isolation ``` The hosted clients (`esm.sdk.client`, `esmc_client`, `esmfold2_client`) ship inside the `esm` package — no extra install. Create an API token in the Biohub developer console (`https://biohub.ai/developer-console/api-keys`) and export it as `ESM_API_KEY`. Local weights are pulled from Hugging Face, so run `huggingface_hub.login()` once with a read-scoped token. **ESMC through the Biohub Platform:** ```python from esm.sdk import esmc_client from esm.sdk.api import ESMProtein, LogitsConfig model = esmc_client(model="esmc-600m-2024-12") # url/token default as above out = model.logits(model.encode(ESMProtein(sequence="MPRTKEINDAGLIVHSP")), LogitsConfig(sequence=True, return_embeddings=True)) print(out.logits, out.embeddings) ``` ## Common Workflows For detailed examples and complete workflows, see `references/workflows.md` which includes: - Novel GFP design with chain-of-thought - Protein variant generation and screening - Structure-based sequence optimization - Function prediction pipelines - Embedding-based clustering and analysis ## References This skill includes comprehensive reference documentation: - `references/esm3-api.md` - ESM3 model architecture, API reference, generation parameters, and multimodal prompting - `references/esm-c-api.md` - ESM C model details, embedding strategies, and performance optimization - `references/forge-api.md` - Forge platform documentation, authentication, batch processing, and deployment - `references/workflows.md` - Complete examples and common workflow patterns These references contain detailed API specifications, parameter descriptions, and advanced usage patterns. Load them as needed for specific tasks. ## Best Practices **For generation tasks:** - Start with smaller models for prototyping (`esm3-sm-open-v1`) - Use temperature parameter to control diversity (0.0 = deterministic, 1.0 = diverse) - Implement iterative refinement with chain-of-thought for complex designs - Validate generated sequences with structure prediction or wet-lab experiments **For embedding tasks:** - Batch process sequences when possible for efficiency - Cache embeddings for repeated analyses - Normalize embeddings when computing similarities - Use appropriate model size based on downstream task requirements **For production deployment:** - Use the hosted Biohub Platform for scalability and the largest models - Implement error handling and retry logic for API calls - Monitor token usage and implement rate limiting - Consider AWS SageMaker deployment for dedicated infrastructure ## Resources and Documentation - **GitHub Repository:** https://github.com/Biohub/esm (ESM3 docs live in `_assets/ESM3_README.md`) - **Biohub Platform:** https://biohub.ai — API keys at https://biohub.ai/developer-console/api-keys - **ESM Atlas:** https://biohub.ai/esm/protein/atlas - **ESM3 paper:** Hayes et al., *Science* — https://www.science.org/doi/10.1126/science.ads0018 - **Tutorials:** https://github.com/Biohub/esm/tree/main/cookbook/tutorials - **Model Weights:** Hugging Face `biohub` organization ## Responsible Use ESM is designed for beneficial applications in protein engineering, drug discovery, and scientific research. Biohub publishes an Acceptable Use Policy and ran a biosafety/biosecurity risk assessment before release — follow that policy, and the Responsible Biodesign Framework (https://responsiblebiodesign.ai/), when designing novel proteins. Think through biosafety and dual-use implications before experimental validation, because a designed sequence leaves the computational realm the moment it is ordered. Part of the AlterLab Academic Skills suite.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.