-
Notifications
You must be signed in to change notification settings - Fork 24
Expand file tree
/
Copy pathmodal_boltzgen.py
More file actions
208 lines (176 loc) · 6.38 KB
/
Copy pathmodal_boltzgen.py
File metadata and controls
208 lines (176 loc) · 6.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
"""BoltzGen https://github.com/HannesStark/boltzgen
Example yaml file:
```yaml
entities:
- protein:
id: B
sequence: 80..140
- file:
path: 6m1u.cif
include:
- chain:
id: A
```
Example usage:
```bash
wget https://raw.githubusercontent.com/HannesStark/boltzgen/refs/heads/main/example/vanilla_protein/1g13.cif
wget https://raw.githubusercontent.com/HannesStark/boltzgen/refs/heads/main/example/vanilla_protein/1g13prot.yaml
modal run modal_boltzgen.py --input-yaml 1g13prot.yaml --protocol protein-anything --num-designs 2
```
Available protocols: protein-anything, peptide-anything, protein-small_molecule, nanobody-anything
Other useful options:
--steps design inverse_folding folding # Run specific steps only
--cache /path/to/cache # Custom cache directory
--devices 2 # Number of GPUs to use
"""
import os
from pathlib import Path
from modal import App, Image
GPU = os.environ.get("GPU", "L40S")
TIMEOUT = int(os.environ.get("TIMEOUT", 120))
def download_boltzgen_models():
"""Download all boltzgen models during image build to avoid runtime timeouts."""
import subprocess
# Download all artifacts to default cache location (~/.cache)
print("Downloading boltzgen models...")
subprocess.run(
["boltzgen", "download", "all"],
check=True,
)
print("Model download complete")
image = (
Image.debian_slim()
.apt_install("git", "wget", "build-essential")
.uv_pip_install("torch>=2.4.1")
.run_commands(
"git clone https://github.com/HannesStark/boltzgen /root/boltzgen",
"cd /root/boltzgen && git checkout 247b9bbd8b68a60aba854c2968d6a0cddd21ad6d && pip install -e .", # Dec 18 2025 - includes weights_only fix
gpu="a10g",
)
.run_function(
download_boltzgen_models,
gpu="a10g",
)
)
app = App("boltzgen", image=image)
@app.function(timeout=TIMEOUT * 60, gpu=GPU)
def boltzgen_run(
yaml_str: str,
yaml_name: str,
additional_files: dict[str, bytes],
protocol: str = "protein-anything",
num_designs: int = 10,
steps: str | None = None,
cache: str | None = None,
devices: int | None = None,
extra_args: str | None = None,
) -> list:
"""Run BoltzGen on a yaml specification.
Args:
yaml_str: YAML design specification as string
yaml_name: Name of the yaml file
additional_files: Dict of relative_path -> file_content for referenced files
protocol: Design protocol (protein-anything, peptide-anything, etc.)
num_designs: Number of designs to generate
steps: Specific pipeline steps to run (e.g. "design inverse_folding")
cache: Custom cache directory path
devices: Number of GPUs to use
extra_args: Additional CLI arguments as string
Returns:
List of (path, content) tuples for all output files
"""
from subprocess import run
from tempfile import TemporaryDirectory
with TemporaryDirectory() as in_dir, TemporaryDirectory() as out_dir:
# Write yaml to file
yaml_path = Path(in_dir) / yaml_name
yaml_path.write_text(yaml_str)
# Write any additional files (e.g., .cif files referenced in yaml)
for rel_path, content in additional_files.items():
file_path = Path(in_dir) / rel_path
file_path.parent.mkdir(parents=True, exist_ok=True)
file_path.write_bytes(content)
# Build command
cmd = [
"boltzgen",
"run",
str(yaml_path),
"--output",
out_dir,
"--protocol",
protocol,
"--num_designs",
str(num_designs),
]
if steps:
cmd.extend(["--steps"] + steps.split())
if cache:
cmd.extend(["--cache", cache])
if devices:
cmd.extend(["--devices", str(devices)])
if extra_args:
cmd.extend(extra_args.split())
print(f"Running: {' '.join(cmd)}")
run(cmd, check=True)
# Collect all output files
return [
(out_file.relative_to(out_dir), out_file.read_bytes())
for out_file in Path(out_dir).rglob("*")
if out_file.is_file()
]
@app.local_entrypoint()
def main(
input_yaml: str,
protocol: str = "protein-anything",
num_designs: int = 10,
steps: str | None = None,
cache: str | None = None,
devices: int | None = None,
extra_args: str | None = None,
out_dir: str = "./out/boltzgen",
run_name: str | None = None,
):
"""Run BoltzGen locally with results saved to out_dir.
Args:
input_yaml: Path to YAML design specification file
protocol: Design protocol (protein-anything, peptide-anything, protein-small_molecule, nanobody-anything)
num_designs: Number of designs to generate
steps: Specific pipeline steps to run (e.g. "design inverse_folding")
cache: Custom cache directory path
devices: Number of GPUs to use
extra_args: Additional CLI arguments as string
out_dir: Local output directory
run_name: Optional run name (defaults to timestamp)
"""
import re
from datetime import datetime
yaml_path = Path(input_yaml)
yaml_str = yaml_path.read_text()
yaml_dir = yaml_path.parent
# Find any file references in the yaml (path: something.cif)
# File paths in yaml are relative to the yaml file location
additional_files = {}
for match in re.finditer(r"path:\s*([^\s\n]+)", yaml_str):
ref_file = match.group(1)
ref_path = yaml_dir / ref_file
if ref_path.exists():
additional_files[ref_file] = ref_path.read_bytes()
print(f"Including referenced file: {ref_file}")
outputs = boltzgen_run.remote(
yaml_str=yaml_str,
yaml_name=yaml_path.name,
additional_files=additional_files,
protocol=protocol,
num_designs=num_designs,
steps=steps,
cache=cache,
devices=devices,
extra_args=extra_args,
)
today = datetime.now().strftime("%Y%m%d%H%M")[2:]
out_dir_full = Path(out_dir) / (run_name or today)
for out_file, out_content in outputs:
output_path = out_dir_full / out_file
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_bytes(out_content)
print(f"\nResults saved to: {out_dir_full}")