pswap commited on
Commit
12cd4e6
·
1 Parent(s): 0010cb0
Files changed (3) hide show
  1. README.md +48 -28
  2. app.py +62 -121
  3. requirements.txt +18 -1
README.md CHANGED
@@ -8,8 +8,7 @@ python_version: "3.12"
8
  app_file: app.py
9
  pinned: false
10
  license: mit
11
- short_description: Protein structure prediction with ESMFold2 (ZeroGPU)
12
- suggested_hardware: zero-a10g
13
  tags:
14
  - biology
15
  - protein
@@ -21,39 +20,59 @@ tags:
21
  # Foldy
22
 
23
  Predicts 3D protein structure from amino acid sequences using
24
- [ESMFold2](https://huggingface.co/biohub/ESMFold2-Fast) (Biohub), a 7B-parameter
25
- diffusion-based structure predictor. Runs on Hugging Face's ZeroGPU.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
 
27
  ## Input
28
 
29
- A single raw amino acid sequence, or a single-record FASTA (the header is ignored).
30
- Length is capped (see `MAX_RESIDUES` in [app.py](app.py)) to keep inference within the
31
- ZeroGPU time budget. Multi-chain/complex folding isn't wired up here — it needs the
32
- `esm` PyPI package's `StructurePredictionInput` builder, which pulls in a dependency
33
- tree (a hard `torch<2.12` pin, `cuequivariance-torch`, ...) that conflicts with the
34
- versions this Space's ZeroGPU/Gradio build forces. This app instead uses ESMFold2 as
35
- natively vendored inside `transformers` (`transformers.models.esmfold2`), which only
36
- supports single-chain protein folding but has a clean, conflict-free dependency set.
37
 
38
  ## Output
39
 
40
- - An interactive 3D viewer (py3Dmol), colored by predicted confidence (pLDDT)
41
- - Mean pLDDT and pTM
42
- - A downloadable PDB file
43
 
44
  ## Notes
45
 
46
- - Uses the `ESMFold2-Fast` checkpoint (single-sequence, no MSA) for speed. Swap
47
- `MODEL_NAME` in [app.py](app.py) for `biohub/ESMFold2` if you want MSA-conditioned
48
- predictions (slower, and you'll need to supply an MSA yourself — the native
49
- `transformers` path here doesn't build one for you).
50
- - Model weights load once at startup in bfloat16 and are moved to the GPU inside the
51
- `@spaces.GPU`-decorated function, per the ZeroGPU pattern.
52
- - "Recycling loops" and "diffusion sampling steps" trade accuracy for speed — raise
53
- them for harder targets, lower them for a faster demo.
54
- - Do **not** add `esm`, `torch`, `spaces`, or `gradio` to `requirements.txt`: the
55
- platform force-pins `torch`/`spaces`/`gradio` during the ZeroGPU build, and `esm`'s
56
- own pins (`torch<2.12`, `transformers<5.0`) directly conflict with them.
 
 
 
 
 
 
57
 
58
  ## Local development
59
 
@@ -62,5 +81,6 @@ pip install -r requirements.txt
62
  python app.py
63
  ```
64
 
65
- Folding requires a CUDA GPU; on Spaces, ZeroGPU provides this on demand via the
66
- `spaces` package. Locally, you need your own GPU for `model.to("cuda")` to work.
 
 
8
  app_file: app.py
9
  pinned: false
10
  license: mit
11
+ short_description: Protein structure prediction with ESMFold2-300 (ZeroGPU)
 
12
  tags:
13
  - biology
14
  - protein
 
20
  # Foldy
21
 
22
  Predicts 3D protein structure from amino acid sequences using
23
+ [Synthyra/ESMFold2-300](https://huggingface.co/Synthyra/ESMFold2-300), a 300M-backbone
24
+ experimental checkpoint from Biohub. Runs on Hugging Face's ZeroGPU.
25
+
26
+ ## ⚠️ Third-party remote code — read before touching `MODEL_NAME`
27
+
28
+ This app loads its model with `trust_remote_code=True`, from `Synthyra/ESMFold2-300` —
29
+ **not** an official Biohub repo. That model's `modeling_fastplms.py` doesn't contain plain
30
+ readable model code: it base85-decodes a bundled ~400KB payload (`fastplms_bundle.py`) into
31
+ a temp directory and imports it at runtime. That's a materially bigger trust step than a
32
+ typical `trust_remote_code` model with inspectable source — the actual code only becomes
33
+ readable after it's already unpacked and about to run. This was a deliberate, explicit
34
+ decision (see conversation history), not a default anyone should assume is fine to repeat
35
+ elsewhere without the same consideration.
36
+
37
+ Why we ended up here: the *official* `biohub/ESMFold2-Fast` (7B params) reliably OOMs on
38
+ large complexes even on ZeroGPU's `xlarge` (96GB) tier, because of the model's O(L²) pairwise
39
+ representation. `Synthyra/ESMFold2-300` wraps a genuinely smaller (~300M) Biohub checkpoint
40
+ with a much lower memory footprint, at the cost of: third-party trust (above), no confidence
41
+ head (no pLDDT/pTM/ipTM), and "experimental" status per Biohub's own labeling (thin
42
+ validation — "passed Docker BF16 reference comparison on one compact Protein G sequence").
43
 
44
  ## Input
45
 
46
+ Up to 6 protein chains (fixed rows in the UI), each with a copies count — set copies > 1 for
47
+ a homooligomer. No cap on total residues; oversized inputs just hit the
48
+ `@spaces.GPU(duration=300)` timeout instead of a friendly error, by design (see conversation
49
+ history — no evidence-based cap has been set yet).
 
 
 
 
50
 
51
  ## Output
52
 
53
+ - An interactive 3D viewer (py3Dmol), colored by chain (no confidence scores to color by)
54
+ - A downloadable mmCIF file
55
+ - No pLDDT/pTM/ipTM — this checkpoint's confidence head is disabled
56
 
57
  ## Notes
58
 
59
+ - `model.set_chunk_size(32)` and `esmc_precision="bf16"` are both part of this checkpoint's
60
+ own documented usage (not something we bolted on) — they trade some accuracy/speed for
61
+ lower memory.
62
+ - Folding goes through `model.input_types.StructurePredictionInput` /
63
+ `.ProteinInput` and `model.fold(...)` / `model.result_to_cif(...)` — all native to this
64
+ checkpoint's bundle, not the hand-rolled multi-chain featurization used in earlier
65
+ iterations of this app.
66
+ - GPU size is the ZeroGPU default (`large`, 48GB) — the 300M model's weights are ~685MB, so
67
+ this should have real headroom versus the old 7B setup. Bump `@spaces.GPU(size="xlarge")`
68
+ if you still hit OOM on large complexes.
69
+ - "Recycling loops" and "diffusion sampling steps" trade accuracy for speed — raise them for
70
+ harder targets, lower them for a faster demo. Defaults (3 loops / 15 steps) match this
71
+ checkpoint's own documented config, not the smaller/faster defaults used for the old model.
72
+ - Do **not** add `spaces` or `gradio` to `requirements.txt` — the platform force-pins those
73
+ during the ZeroGPU build; adding them again risks a resolver conflict. `torch`/`transformers`
74
+ *are* pinned here deliberately (matching what `Synthyra/ESMFold2-300` requires), verified to
75
+ resolve cleanly against the platform's forced pins before being added.
76
 
77
  ## Local development
78
 
 
81
  python app.py
82
  ```
83
 
84
+ Folding requires a CUDA GPU; on Spaces, ZeroGPU provides this on demand via the `spaces`
85
+ package. Locally, you need your own GPU — `device_map="cuda"` in `app.py` assumes one is
86
+ present at import time.
app.py CHANGED
@@ -1,4 +1,9 @@
1
- """Foldy — protein structure prediction with ESMFold2, on HF ZeroGPU."""
 
 
 
 
 
2
 
3
  import os
4
  import re
@@ -9,13 +14,9 @@ import gradio as gr
9
  import py3Dmol
10
  import spaces
11
  import torch
12
- from transformers.models.esmfold2.modeling_esmfold2 import EsmFold2Model
13
- from transformers.models.esmfold2.protein_utils import output_to_pdb, prepare_protein_features
14
 
15
- # The -Fast checkpoint is single-sequence (no MSA), which keeps inference time
16
- # reasonable on a shared ZeroGPU slot. Swap for "biohub/ESMFold2" for MSA-conditioned
17
- # accuracy on hard targets (slower, and needs an MSA pipeline this app doesn't provide).
18
- MODEL_NAME = "biohub/ESMFold2-Fast"
19
  NUM_ROWS = 6
20
  VALID_AMINO_ACIDS = set("ACDEFGHIKLMNPQRSTVWYXBZUO")
21
 
@@ -23,11 +24,16 @@ EXAMPLE_UBIQUITIN = "MQIFVKTLTGKTITLEVEPSDTIENVKAKIQDKEGIPPDQQRLIFAGKQLEDGRTLSDY
23
  EXAMPLE_CAP = "MKTVRQERLKSIVRILERSKEPVSGAQLAEELSVSRQVIVQDIAYLRSLGYNIVATPRGYVLAGG"
24
 
25
  print(f"Loading {MODEL_NAME} (this happens once at startup)...")
26
- model = EsmFold2Model.from_pretrained(MODEL_NAME).eval()
27
- # Per HF's ZeroGPU docs, models must move to "cuda" at module load time (not inside an
28
- # @spaces.GPU function) — a CUDA emulation mode makes this safe even without a real GPU
29
- # attached yet, and it's more efficient than a per-call transfer.
30
- model.to("cuda")
 
 
 
 
 
31
  print("Model loaded.")
32
 
33
 
@@ -38,148 +44,83 @@ def clean_sequence(raw_sequence: str) -> str:
38
  return re.sub(r"[^A-Z]", "", sequence)
39
 
40
 
41
- def render_structure(pdb_str: str) -> str:
42
  view = py3Dmol.view(width=600, height=500)
43
- view.addModel(pdb_str, "pdb")
44
- view.setStyle({"cartoon": {"colorscheme": {"prop": "b", "gradient": "roygb", "min": 50, "max": 90}}})
 
 
45
  view.zoomTo()
46
  raw_html = view._make_html()
47
  escaped = raw_html.replace('"', "&quot;")
48
  return f'<iframe style="width: 100%; height: 500px; border: none;" srcdoc="{escaped}"></iframe>'
49
 
50
 
51
- def prepare_multi_chain_protein_features(sequences: list[str], device) -> dict[str, torch.Tensor]:
52
- """Combine several single-chain protein featurizations into one complex.
53
-
54
- Reuses ``prepare_protein_features`` per chain (already correct for one chain), then
55
- reassigns the complex-level bookkeeping fields per the convention ``EsmFold2Model.forward``
56
- documents: ``token_index``/``atom_to_token``/``ref_space_uid`` become complex-global,
57
- ``residue_index`` stays chain-local, ``asym_id`` is unique per chain instance, ``entity_id``
58
- is shared by chains with an identical sequence, and ``sym_id`` numbers the copies within an
59
- entity (so copies of one sequence fold as a homooligomer, not independent monomers).
60
- """
61
- per_chain = [prepare_protein_features(seq, device="cpu") for seq in sequences]
62
-
63
- entity_id_by_sequence: dict[str, int] = {}
64
- next_entity_id = 1
65
- copies_seen: dict[int, int] = {}
66
- token_offset = 0
67
- atom_offset = 0
68
-
69
- for asym_idx, (seq, feats) in enumerate(zip(sequences, per_chain)):
70
- entity_id = entity_id_by_sequence.setdefault(seq, next_entity_id)
71
- if entity_id == next_entity_id:
72
- next_entity_id += 1
73
- sym_id = copies_seen.get(entity_id, 0)
74
- copies_seen[entity_id] = sym_id + 1
75
-
76
- num_tokens = feats["token_index"].shape[1]
77
-
78
- feats["token_index"] = feats["token_index"] + token_offset
79
- feats["asym_id"] = torch.full_like(feats["asym_id"], asym_idx)
80
- feats["sym_id"] = torch.full_like(feats["sym_id"], sym_id)
81
- feats["entity_id"] = torch.full_like(feats["entity_id"], entity_id)
82
- feats["atom_to_token"] = feats["atom_to_token"] + token_offset
83
- feats["ref_space_uid"] = feats["ref_space_uid"] + token_offset
84
- feats["distogram_atom_idx"] = feats["distogram_atom_idx"] + atom_offset
85
-
86
- token_offset += num_tokens
87
- atom_offset += feats["ref_pos"].shape[1]
88
-
89
- total_tokens = token_offset
90
-
91
- def cat(key: str, dim: int = 1) -> torch.Tensor:
92
- return torch.cat([f[key] for f in per_chain], dim=dim)
93
-
94
- combined = {
95
- "token_index": cat("token_index"),
96
- "residue_index": cat("residue_index"),
97
- "asym_id": cat("asym_id"),
98
- "sym_id": cat("sym_id"),
99
- "entity_id": cat("entity_id"),
100
- "mol_type": cat("mol_type"),
101
- "res_type": cat("res_type"),
102
- "input_ids": cat("input_ids"),
103
- "attention_mask": cat("attention_mask"),
104
- "distogram_atom_idx": cat("distogram_atom_idx"),
105
- "ref_pos": cat("ref_pos"),
106
- "ref_element": cat("ref_element"),
107
- "ref_charge": cat("ref_charge"),
108
- "ref_atom_name_chars": cat("ref_atom_name_chars"),
109
- "ref_space_uid": cat("ref_space_uid"),
110
- "atom_attention_mask": cat("atom_attention_mask"),
111
- "atom_to_token": cat("atom_to_token"),
112
- "msa": cat("msa", dim=2),
113
- "msa_attention_mask": cat("msa_attention_mask", dim=2),
114
- "has_deletion": cat("has_deletion", dim=2),
115
- "deletion_value": cat("deletion_value", dim=2),
116
- "deletion_mean": cat("deletion_mean"),
117
- "token_bonds": torch.zeros(1, total_tokens, total_tokens, 1, dtype=torch.float32),
118
- }
119
- return {k: v.to(device) for k, v in combined.items()}
120
-
121
-
122
- @spaces.GPU(duration=300, size="xlarge")
123
  def fold_rows(*args, progress=gr.Progress()):
124
  *seqs, num_loops, num_sampling_steps = args
125
 
126
- chain_sequences: list[str] = []
127
- for raw_seq, copies in zip(seqs[:NUM_ROWS], seqs[NUM_ROWS:]):
128
  sequence = clean_sequence(raw_seq or "")
129
  if not sequence:
130
  continue
131
  invalid_chars = set(sequence) - VALID_AMINO_ACIDS
132
  if invalid_chars:
133
  raise gr.Error(f"Invalid characters in sequence: {', '.join(sorted(invalid_chars))}")
134
- chain_sequences.extend([sequence] * max(1, int(copies or 1)))
135
 
136
- if not chain_sequences:
137
  raise gr.Error("Please enter at least one protein sequence.")
138
 
139
- total_residues = sum(len(s) for s in chain_sequences)
140
 
141
- progress(0.2, desc=f"Folding {len(chain_sequences)} chain(s), {total_residues} residues total...")
142
- features = prepare_multi_chain_protein_features(chain_sequences, device="cuda")
 
 
 
 
 
143
 
144
  start = time.time()
145
- output = model.fold(
146
- **features,
147
- num_loops=num_loops,
148
- num_sampling_steps=num_sampling_steps,
149
- num_diffusion_samples=1,
150
- )
 
 
 
151
  elapsed = time.time() - start
152
 
153
  progress(0.9, desc="Building structure file...")
154
- pdb_str = output_to_pdb(output, features)
155
 
156
- pdb_path = os.path.join(tempfile.gettempdir(), f"foldy_{int(time.time())}.pdb")
157
- with open(pdb_path, "w") as f:
158
- f.write(pdb_str)
159
 
160
- viewer_html = render_structure(pdb_str)
161
- mean_plddt = float(output["plddt"].mean())
162
- ptm = float(output["ptm"].mean())
163
  summary_lines = [
164
- f"**Chains:** {len(chain_sequences)}",
165
  f"**Total length:** {total_residues} residues",
166
- f"**Mean pLDDT:** {mean_plddt:.1f} / 100",
167
- f"**pTM:** {ptm:.3f}",
168
  ]
169
- iptm_tensor = output.get("iptm") if hasattr(output, "get") else None
170
- if len(chain_sequences) > 1 and iptm_tensor is not None:
171
- summary_lines.append(f"**ipTM:** {float(iptm_tensor.mean()):.3f}")
172
- summary_lines.append(f"**Inference time:** {elapsed:.1f}s")
173
 
174
- return viewer_html, " \n".join(summary_lines), pdb_path
175
 
176
 
177
  with gr.Blocks(title="Foldy — ESMFold2 Protein Structure Prediction") as demo:
178
  gr.Markdown(
179
  """
180
  # 🧬 Foldy
181
- Predict 3D protein structure using [ESMFold2](https://huggingface.co/biohub/ESMFold2-Fast)
182
- (Biohub). Fill in one row per chain, and set copies > 1 for a homooligomer.
 
 
183
  """
184
  )
185
 
@@ -206,13 +147,13 @@ with gr.Blocks(title="Foldy — ESMFold2 Protein Structure Prediction") as demo:
206
  copies_boxes.append(copies)
207
 
208
  with gr.Accordion("Advanced settings", open=False):
209
- num_loops = gr.Slider(1, 3, value=1, step=1, label="Recycling loops")
210
- num_sampling_steps = gr.Slider(10, 50, value=20, step=5, label="Diffusion sampling steps")
211
  gr.Markdown("Higher values improve accuracy but increase inference time.")
212
 
213
  fold_btn = gr.Button("Fold", variant="primary")
214
  summary_output = gr.Markdown()
215
- pdb_output = gr.File(label="Download PDB", interactive=False)
216
 
217
  with gr.Column(scale=2):
218
  structure_output = gr.HTML(label="Predicted structure")
@@ -223,7 +164,7 @@ with gr.Blocks(title="Foldy — ESMFold2 Protein Structure Prediction") as demo:
223
  fold_btn.click(
224
  fn=fold_rows,
225
  inputs=[*seq_boxes, *copies_boxes, num_loops, num_sampling_steps],
226
- outputs=[structure_output, summary_output, pdb_output],
227
  )
228
 
229
  if __name__ == "__main__":
 
1
+ """Foldy — protein structure prediction with ESMFold2-300, on HF ZeroGPU.
2
+
3
+ Uses the Synthyra/ESMFold2-300 FastPLMs bundle (trust_remote_code=True), which wraps
4
+ Biohub's experimental 300M-backbone ESMFold2 checkpoint. This is third-party remote code —
5
+ see the README for what that means before touching MODEL_NAME.
6
+ """
7
 
8
  import os
9
  import re
 
14
  import py3Dmol
15
  import spaces
16
  import torch
17
+ from transformers import AutoModel
 
18
 
19
+ MODEL_NAME = "Synthyra/ESMFold2-300"
 
 
 
20
  NUM_ROWS = 6
21
  VALID_AMINO_ACIDS = set("ACDEFGHIKLMNPQRSTVWYXBZUO")
22
 
 
24
  EXAMPLE_CAP = "MKTVRQERLKSIVRILERSKEPVSGAQLAEELSVSRQVIVQDIAYLRSLGYNIVATPRGYVLAGG"
25
 
26
  print(f"Loading {MODEL_NAME} (this happens once at startup)...")
27
+ model = AutoModel.from_pretrained(
28
+ MODEL_NAME,
29
+ trust_remote_code=True,
30
+ dtype=torch.float32,
31
+ device_map="cuda",
32
+ esmc_precision="bf16",
33
+ attn_implementation="sdpa",
34
+ ).eval()
35
+ model.set_chunk_size(32)
36
+ input_types = model.input_types
37
  print("Model loaded.")
38
 
39
 
 
44
  return re.sub(r"[^A-Z]", "", sequence)
45
 
46
 
47
+ def render_structure(cif_str: str) -> str:
48
  view = py3Dmol.view(width=600, height=500)
49
+ view.addModel(cif_str, "cif")
50
+ # No per-residue confidence on this checkpoint (confidence head is disabled), so color by
51
+ # chain instead of pLDDT.
52
+ view.setStyle({"cartoon": {"colorscheme": "chain"}})
53
  view.zoomTo()
54
  raw_html = view._make_html()
55
  escaped = raw_html.replace('"', "&quot;")
56
  return f'<iframe style="width: 100%; height: 500px; border: none;" srcdoc="{escaped}"></iframe>'
57
 
58
 
59
+ @spaces.GPU(duration=300)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  def fold_rows(*args, progress=gr.Progress()):
61
  *seqs, num_loops, num_sampling_steps = args
62
 
63
+ chains: list[str] = []
64
+ for row_idx, (raw_seq, copies) in enumerate(zip(seqs[:NUM_ROWS], seqs[NUM_ROWS:])):
65
  sequence = clean_sequence(raw_seq or "")
66
  if not sequence:
67
  continue
68
  invalid_chars = set(sequence) - VALID_AMINO_ACIDS
69
  if invalid_chars:
70
  raise gr.Error(f"Invalid characters in sequence: {', '.join(sorted(invalid_chars))}")
71
+ chains.extend([sequence] * max(1, int(copies or 1)))
72
 
73
+ if not chains:
74
  raise gr.Error("Please enter at least one protein sequence.")
75
 
76
+ total_residues = sum(len(s) for s in chains)
77
 
78
+ progress(0.2, desc=f"Folding {len(chains)} chain(s), {total_residues} residues total...")
79
+ chain_ids = [chr(ord("A") + i) for i in range(len(chains))]
80
+ complex_input = input_types.StructurePredictionInput(
81
+ sequences=[
82
+ input_types.ProteinInput(id=chain_id, sequence=seq) for chain_id, seq in zip(chain_ids, chains)
83
+ ]
84
+ )
85
 
86
  start = time.time()
87
+ with torch.inference_mode():
88
+ result = model.fold(
89
+ complex_input,
90
+ num_loops=num_loops,
91
+ num_sampling_steps=num_sampling_steps,
92
+ num_diffusion_samples=1,
93
+ seed=0,
94
+ verbose=False,
95
+ )
96
  elapsed = time.time() - start
97
 
98
  progress(0.9, desc="Building structure file...")
99
+ cif_str = model.result_to_cif(result)
100
 
101
+ cif_path = os.path.join(tempfile.gettempdir(), f"foldy_{int(time.time())}.cif")
102
+ with open(cif_path, "w") as f:
103
+ f.write(cif_str)
104
 
105
+ viewer_html = render_structure(cif_str)
 
 
106
  summary_lines = [
107
+ f"**Chains:** {len(chains)}",
108
  f"**Total length:** {total_residues} residues",
109
+ "**Confidence scores:** unavailable (this checkpoint's confidence head is disabled)",
110
+ f"**Inference time:** {elapsed:.1f}s",
111
  ]
 
 
 
 
112
 
113
+ return viewer_html, " \n".join(summary_lines), cif_path
114
 
115
 
116
  with gr.Blocks(title="Foldy — ESMFold2 Protein Structure Prediction") as demo:
117
  gr.Markdown(
118
  """
119
  # 🧬 Foldy
120
+ Predict 3D protein structure using [ESMFold2-300](https://huggingface.co/Synthyra/ESMFold2-300)
121
+ (a smaller, 300M-backbone experimental checkpoint from Biohub, via Synthyra's runtime).
122
+ Fill in one row per chain, and set copies > 1 for a homooligomer. This checkpoint has no
123
+ confidence head, so pLDDT/pTM aren't available.
124
  """
125
  )
126
 
 
147
  copies_boxes.append(copies)
148
 
149
  with gr.Accordion("Advanced settings", open=False):
150
+ num_loops = gr.Slider(1, 3, value=3, step=1, label="Recycling loops")
151
+ num_sampling_steps = gr.Slider(10, 50, value=15, step=5, label="Diffusion sampling steps")
152
  gr.Markdown("Higher values improve accuracy but increase inference time.")
153
 
154
  fold_btn = gr.Button("Fold", variant="primary")
155
  summary_output = gr.Markdown()
156
+ cif_output = gr.File(label="Download mmCIF", interactive=False)
157
 
158
  with gr.Column(scale=2):
159
  structure_output = gr.HTML(label="Predicted structure")
 
164
  fold_btn.click(
165
  fn=fold_rows,
166
  inputs=[*seq_boxes, *copies_boxes, num_loops, num_sampling_steps],
167
+ outputs=[structure_output, summary_output, cif_output],
168
  )
169
 
170
  if __name__ == "__main__":
requirements.txt CHANGED
@@ -1,2 +1,19 @@
1
- transformers>=5.16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  py3Dmol
 
1
+ torch>=2.13,<2.14
2
+ transformers>=5.13,<5.14
3
+ huggingface-hub>=0.34,<2
4
+ tokenizers>=0.22,<0.23
5
+ safetensors>=0.5,<1
6
+ numpy>=1.26,<3
7
+ einops>=0.8,<1
8
+ tqdm>=4.67,<5
9
+ accelerate>=1.10,<2
10
+ biopython>=1.85,<2
11
+ biotite>=1.4,<2
12
+ brotli>=1.1,<2
13
+ msgpack>=1.1,<2
14
+ msgpack-numpy>=0.4.8,<1
15
+ omegaconf>=2.3,<3
16
+ rdkit>=2025.9,<2027
17
+ scipy>=1.15,<2
18
+ zstandard>=0.23,<1
19
  py3Dmol