mgtotaro commited on
Commit
44f7fb3
·
1 Parent(s): c74a121

ESMC integration

Browse files
Files changed (6) hide show
  1. AGENTS.md +55 -0
  2. app.py +1 -1
  3. header.md +3 -3
  4. instructions.md +4 -2
  5. model.py +3 -31
  6. requirements.txt +1 -2
AGENTS.md ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ZSP — Agent Guide
2
+
3
+ **What it is.** Gradio web app that scores protein sequence variants using ESM2, E1, and ESMC language models. Implements ESM-Scan methodology ([doi:10.1002/pro.5221](https://doi.org/10.1002/pro.5221)).
4
+
5
+ ## Structure
6
+
7
+ ```
8
+ app.py — Gradio UI, entry point
9
+ model.py — ModelFactory + wrappers for ESM/E1/ESMC models
10
+ data.py — Input parsing, scoring modes, output rendering (heatmap/table)
11
+ requirements.txt — deps (gradio, torch, esm[Biohub], pandas, seaborn, E1)
12
+ header.md — UI header text
13
+ instructions.md — user instructions tab content
14
+ out.* — generated outputs (gitignored)
15
+ ```
16
+
17
+ ## Run
18
+
19
+ ```bash
20
+ source .venv/bin/activate # or however you activate
21
+ python app.py
22
+ ```
23
+
24
+ Deploys as a HuggingFace Space (`sdk: gradio`, `app_file: app.py`). Python 3.12.
25
+
26
+ ## Scoring Modes
27
+
28
+ Parsed from the substitutions input in `data.py`:
29
+
30
+ | Mode | Trigger | What it does |
31
+ |------|---------|-------------|
32
+ | **TMS** (triple mutational scan) | No match to other patterns | All-positions × all-amino-acids sweep → heatmap image |
33
+ | **DMS** (deep mutational scan) | All tokens are integers | Specified positions × all alternatives → sorted table |
34
+ | **MUT** (single mutations) | Tokens match `[A-Z]\d+[A-Z]` | Explicit substitution list → scored table |
35
+ | **MUT-seq-vs-seq** | Single token same length as sequence | Align two sequences, score differences → scored table |
36
+
37
+ Scoring strategy flag: `"masked-marginals"` (higher accuracy, slower) vs `"wt-marginals"`.
38
+
39
+ ## Key Design Notes
40
+
41
+ - Models loaded lazily via `ModelFactory`; ESM and ESMC use shared `ESMModel` wrapper, E1 uses its own package (`E1@git+...`)
42
+ - ESMC requires `esm@git+Biohub/esm` — registers custom `esmc` architecture with transformers fork
43
+ - GPU auto-detected; falls back to CPU
44
+ - E1 model chunks batches at 60 to avoid OOM
45
+ - TMS mode produces a seaborn heatmap PNG; other modes produce styled HTML tables
46
+ - Output files always written to `out.csv` / `out.png` in CWD
47
+ - Progress tracking wired into Gradio's `Progress` object
48
+
49
+ ## Edit Rules
50
+
51
+ - Match existing style: one-line docstrings on classes/methods, minimal comments
52
+ - No new dependencies unless absolutely necessary — stdlib or already-installed first
53
+ - Torch/transformers signatures: grep before guessing versions
54
+ - Output format changes affect both CSV and image/table paths — update both
55
+ - ModelFactory registry is class-level `_models` dict; adding a model type means registering there
app.py CHANGED
@@ -47,7 +47,7 @@ with Blocks() as demo:
47
  , label="Substitutions"
48
  , placeholder="Substitutions here…"
49
  , value='' )
50
- model_name = Dropdown(MODELS, label="Model", value="facebook/esm2_t30_150M_UR50D")
51
  acc_box = Checkbox(value=True, label="Use higher accuracy scoring", interactive=True)
52
  run_btn = Button(value="Run", variant="primary")
53
  dl_btn = DownloadButton(label="Download raw data", visible=False)
 
47
  , label="Substitutions"
48
  , placeholder="Substitutions here…"
49
  , value='' )
50
+ model_name = Dropdown(MODELS, label="Model", value="biohub/ESMC-300M")
51
  acc_box = Checkbox(value=True, label="Use higher accuracy scoring", interactive=True)
52
  run_btn = Button(value="Run", variant="primary")
53
  dl_btn = DownloadButton(label="Download raw data", visible=False)
header.md CHANGED
@@ -1,7 +1,7 @@
1
- Calculate the fitness of single amino acid substitutions on proteins, using a [zero-shot](https://doi.org/10.1101/2021.07.09.450648) protein language predictor of the [ESM model family](https://huggingface.co/facebook/esm2_t6_8M_UR50D).
2
 
3
- **UPDATE:**
4
- [Profluent-Bio](https://huggingface.co/Profluent-Bio)'s [E1 model family](https://huggingface.co/Profluent-Bio/E1-150m) is now available for inference.
5
 
6
  **WARNING:**
7
  Due to high server traffic, the tool might become slow or unresponsive.
 
1
+ Calculate the fitness of single amino acid substitutions on proteins, using zero-shot protein language models from the [ESM](https://huggingface.co/facebook) and [ESMC](https://huggingface.co/collections/biohub/esmc-model-family) families.
2
 
3
+ **UPDATE:**
4
+ [Biohub](https://biohub.ai)'s [ESMC model family](https://huggingface.co/collections/biohub/esmc-model-family) (trained on UniRef + MGnify + JGI) is now available alongside ESM2 models.
5
 
6
  **WARNING:**
7
  Due to high server traffic, the tool might become slow or unresponsive.
instructions.md CHANGED
@@ -16,8 +16,10 @@ Note: While jolly characters (e.g., `-X.B`) can be included, they currently cann
16
  - **Same-Length Sequence**: Analyze differing amino acid substitutions one by one within sequences of equal length.
17
  - **Different Inputs**: For any other input format, a deep mutational scan of the full sequence will be performed.
18
 
19
- **Model Selection**: Choose a model for calculations from those available on Hugging Face Model Hub.
20
- The `esm2_t33_650M_UR50D` model offers an optimal balance between cost and accuracy [*](https://doi.org/10.1126/science.ade2574).
 
 
21
 
22
  **Accuracy Option**: The **Use higher accuracy** option applies a masked-marginals scoring strategy, which considers sequence context during inference.
23
  While this method is slower, it enhances accuracy.
 
16
  - **Same-Length Sequence**: Analyze differing amino acid substitutions one by one within sequences of equal length.
17
  - **Different Inputs**: For any other input format, a deep mutational scan of the full sequence will be performed.
18
 
19
+ **Model Selection**: Choose a model for calculations from those available on Hugging Face Model Hub. Two model families are supported:
20
+
21
+ - **Facebook ESM2** — trained on UniRef50. `esm2_t33_650M_UR50D` offers an optimal balance between cost and accuracy [*](https://doi.org/10.1126/science.ade2574).
22
+ - **Biohub ESMC** — trained on UniRef + MGnify + JGI (broader metagenomic coverage). Pre-LN architecture with rotary embeddings, max 2048 token context. Rankings correlate strongly with ESM2 (Spearman ρ ≥ 0.93) while absolute scores may differ due to training data differences.
23
 
24
  **Accuracy Option**: The **Use higher accuracy** option applies a masked-marginals scoring strategy, which considers sequence context during inference.
25
  While this method is slower, it enhances accuracy.
model.py CHANGED
@@ -4,11 +4,9 @@ from typing import Any
4
  from transformers import AutoTokenizer, AutoModelForMaskedLM
5
  from transformers.tokenization_utils_base import BatchEncoding
6
  from transformers.modeling_outputs import MaskedLMOutput
7
- from E1.modeling import E1ForMaskedLM
8
- from E1.scorer import E1Scorer, EncoderScoreMethod
9
 
10
  class ESMModel:
11
- "Wrapper for ESM models"
12
  def __init__(self, model_name:str):
13
  "Load selected model and tokenizer"
14
  self.model_name = model_name
@@ -75,29 +73,6 @@ class ESMModel:
75
  lambda row: label_row(row['0'], token_probs)
76
  , axis=1 )
77
 
78
- class E1Model:
79
- "Wrapper for E1 models"
80
- def __init__(self, model_name:str):
81
- self.model_name = model_name
82
- self.scoring_strategy = EncoderScoreMethod.MASKED_MARGINAL
83
- if model_name:
84
- self.model = E1ForMaskedLM.from_pretrained(model_name, dtype=torch.float)
85
- self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
86
- self.model = self.model.to(self.device)
87
- self.scorer = E1Scorer(self.model, self.scoring_strategy)
88
- self.alphabet = self.scorer.vocab
89
-
90
- def run_model(self, data):
91
- "Run model on data"
92
- if not data.scoring_strategy.startswith("masked-marginals"):
93
- self.scorer = E1Scorer(self.model, EncoderScoreMethod.WILDTYPE_MARGINAL)
94
- batch_size = 60 ## chunking to avoid OOM
95
- out = []
96
- for chunk in data.progress.tqdm([data.trg[i:i+batch_size] for i in range(0, len(data.trg), batch_size)], desc="Calculating"):
97
- scores = self.scorer.score(parent_sequence=data.seq, sequences=chunk)
98
- out.extend(s['score'] for s in scores)
99
- data.out[self.model_name] = out
100
-
101
  class ModelFactory:
102
  "Factory for models"
103
  _models = { **{ m.id:ESMModel for m in HfApi().list_models(
@@ -106,11 +81,8 @@ class ModelFactory:
106
  , filter="fill-mask"
107
  , sort="lastModified"
108
  , direction=-1 ) }
109
- , **{ m.id:E1Model for m in HfApi().list_models(
110
- author="Profluent-Bio"
111
- , model_name="E1"
112
- , sort="lastModified"
113
- , direction=-1 ) } }
114
 
115
  @classmethod
116
  def register(cls, model_name, model_cls):
 
4
  from transformers import AutoTokenizer, AutoModelForMaskedLM
5
  from transformers.tokenization_utils_base import BatchEncoding
6
  from transformers.modeling_outputs import MaskedLMOutput
 
 
7
 
8
  class ESMModel:
9
+ "Wrapper for ESM/ESMC models"
10
  def __init__(self, model_name:str):
11
  "Load selected model and tokenizer"
12
  self.model_name = model_name
 
73
  lambda row: label_row(row['0'], token_probs)
74
  , axis=1 )
75
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  class ModelFactory:
77
  "Factory for models"
78
  _models = { **{ m.id:ESMModel for m in HfApi().list_models(
 
81
  , filter="fill-mask"
82
  , sort="lastModified"
83
  , direction=-1 ) }
84
+ , **{ m.id:ESMModel for m in HfApi().list_models(
85
+ author="biohub", model_name="esmc", filter="fill-mask" ) if "-step" not in m.id and "-2024-12" not in m.id } }
 
 
 
86
 
87
  @classmethod
88
  def register(cls, model_name, model_cls):
requirements.txt CHANGED
@@ -2,5 +2,4 @@ gradio
2
  pandas
3
  seaborn
4
  torch
5
- transformers
6
- E1@git+https://github.com/Profluent-AI/E1.git@main#egg=E1
 
2
  pandas
3
  seaborn
4
  torch
5
+ esm@git+https://github.com/Biohub/esm.git@main