Shrey Goel commited on
Commit
f211cf7
·
1 Parent(s): 0c19938

Add desolubilization, multipass, and olig tasks

Browse files
.gitignore CHANGED
@@ -13,12 +13,12 @@
13
  /src/lm/evoflow_playground.ipynb
14
  /src/utils/ubuntu_font
15
 
16
- /src/sampling/old_guidance.py
17
-
18
  /MeMDLM_v2.egg-info/
19
  *.pth
20
  *.ckpt
21
  *.err
22
  *.out
23
  *.csv
 
 
24
  __pycache__/
 
13
  /src/lm/evoflow_playground.ipynb
14
  /src/utils/ubuntu_font
15
 
 
 
16
  /MeMDLM_v2.egg-info/
17
  *.pth
18
  *.ckpt
19
  *.err
20
  *.out
21
  *.csv
22
+ *.ipynb
23
+ *.png
24
  __pycache__/
README.md CHANGED
@@ -2,25 +2,373 @@
2
 
3
  ![MemDLM diagram](./memdlm_schematic.png)
4
 
 
5
 
6
- arXiv preprint: ...
7
 
8
- Reparameterized diffusion models (RDMs) have recently matched autoregressive methods in protein generation, motivating their use for challenging tasks such as designing membrane proteins, which possess interleaved soluble and transmembrane (TM) regions.
9
 
10
- We introduce ***Membrane Diffusion Language Model (MemDLM)***, a fine-tuned RDM-based protein language model that enables controllable membrane protein sequence design. MemDLM-generated sequences recapitulate the TM residue density and structural features of natural membrane proteins, achieving comparable biological plausibility and outperforming state-of-the-art diffusion baselines in motif scaffolding tasks by producing:
 
 
11
 
12
- - Lower perplexity
13
- - Higher BLOSUM-62 scores
14
- - Improved pLDDT confidence
15
 
16
- To enhance controllability, we develop ***Per-Token Guidance (PET)***, a novel classifier-guided sampling strategy that selectively solubilizes residues while preserving conserved TM domains. This yields sequences with reduced TM density but intact functional cores.
17
 
18
- Importantly, MemDLM designs validated in TOXCAT β-lactamase growth assays demonstrate successful TM insertion, distinguishing high-quality generated sequences from poor ones.
19
 
20
- Together, our framework establishes the first experimentally validated diffusion-based model for rational membrane protein generation, integrating *de novo* design, motif scaffolding, and targeted property optimization.
21
 
 
22
 
 
 
23
 
24
- ## **Repository Authors**
25
- - <u>[Shrey Goel](https://shreygoel09.github.io/)</u> – undergraduate student at Duke University
26
- - <u>[Pranam Chatterjee](mailto:pranam@seas.upenn.edu)</u> – Assistant Professor at University of Pennsylvania
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
  ![MemDLM diagram](./memdlm_schematic.png)
4
 
5
+ **arXiv preprint:** [link TBD]
6
 
7
+ Reparameterized diffusion models (RDMs) have recently matched autoregressive methods in protein generation, motivating their use for challenging tasks such as designing membrane proteins, which possess interleaved soluble and transmembrane (TM) regions.
8
 
9
+ We introduce ***Membrane Diffusion Language Model (MemDLM)***, a fine-tuned RDM-based protein language model that enables controllable membrane protein sequence design. MemDLM-generated sequences recapitulate the TM residue density and structural features of natural membrane proteins, achieving comparable biological plausibility and outperforming state-of-the-art diffusion baselines in motif scaffolding tasks by producing:
10
 
11
+ - Lower perplexity
12
+ - Higher BLOSUM-62 scores
13
+ - Improved pLDDT confidence
14
 
15
+ To enhance controllability, we develop ***Per-Token Guidance (PET)***, a novel classifier-guided sampling strategy that selectively solubilizes residues while preserving conserved TM domains. This yields sequences with reduced TM density but intact functional cores.
 
 
16
 
17
+ Importantly, MemDLM designs validated in TOXCAT β-lactamase growth assays demonstrate successful TM insertion, distinguishing high-quality generated sequences from poor ones.
18
 
19
+ Together, our framework establishes the first experimentally validated diffusion-based model for rational membrane protein generation, integrating *de novo* design, motif scaffolding, and targeted property optimization.
20
 
21
+ ---
22
 
23
+ ## Repository Authors
24
 
25
+ - <u>[Shrey Goel](https://shreygoel09.github.io/)</u> – undergraduate student at Duke University
26
+ - <u>[Pranam Chatterjee](mailto:pranam@seas.upenn.edu)</u> – Assistant Professor at University of Pennsylvania
27
 
28
+ ---
29
+
30
+ ## Table of Contents
31
+
32
+ 1. [Installation](#installation)
33
+ 2. [Configuration](#configuration)
34
+ 3. [Data Preparation](#data-preparation)
35
+ 4. [Training](#training)
36
+ - [MeMDLM diffusion model](#1-memdlm-diffusion-model)
37
+ - [Solubility classifier](#2-solubility-classifier)
38
+ - [Oligomerization classifier](#3-oligomerization-classifier)
39
+ - [Multipass classifier](#4-multipass-classifier)
40
+ 5. [Sampling](#sampling)
41
+ - [Unconditional generation](#1-unconditional-generation)
42
+ - [Solubilization (PET)](#2-solubilization-pet)
43
+ - [Desolubilization (PET)](#3-desolubilization-pet)
44
+ - [Multipass generation](#4-multipass-generation)
45
+ - [Oligomerization](#5-oligomerization)
46
+ 6. [Citation](#citation)
47
+
48
+ ---
49
+
50
+ ## Installation
51
+
52
+ Clone the repository and install it in editable mode from the repository root:
53
+
54
+ ```bash
55
+ git clone https://github.com/<your-org>/MeMDLM_v2.git
56
+ cd MeMDLM_v2
57
+ pip install -e .
58
+ pip install -r requirements.txt
59
+ ```
60
+
61
+ Log in to Weights & Biases (used for experiment logging during training):
62
+
63
+ ```bash
64
+ wandb login
65
+ # or: export WANDB_API_KEY=<your-key>
66
+ ```
67
+
68
+ **Requirements:** CUDA-capable GPU(s), Python 3.10+, and sufficient disk space for pretrained checkpoints (EvoFlow-650M, ESM2-650M) and saved model weights.
69
+
70
+ ---
71
+
72
+ ## Configuration
73
+
74
+ All hyperparameters live in YAML files under `src/configs/`. Before running anything, set `base_dir` in each config to your local clone path:
75
+
76
+ ```yaml
77
+ base_dir: /path/to/MeMDLM_v2
78
+ ```
79
+
80
+ | Config file | Purpose |
81
+ |---|---|
82
+ | `src/configs/lm.yaml` | Fine-tune the MeMDLM diffusion language model |
83
+ | `src/configs/solubility.yaml` | Train/evaluate the per-residue solubility classifier (used for PET solubilization) |
84
+ | `src/configs/desolubilize.yaml` | PET desolubilization sampling settings (shares the solubility classifier checkpoint) |
85
+ | `src/configs/oligo.yaml` | Train/evaluate the oligomerization classifier |
86
+ | `src/configs/multipass.yaml` | Train/evaluate the multipass TM-segment classifier |
87
+
88
+ **Training modes** are controlled by `training.mode` in each config:
89
+
90
+ | Value | Behavior |
91
+ |---|---|
92
+ | `train` | Run training from scratch |
93
+ | `test` | Load `best_model.ckpt` and evaluate on the test set |
94
+ | `resume_from_checkpoint` | Resume MeMDLM training from `checkpointing.resume_ckpt_path` (`lm.yaml` only) |
95
+
96
+ Checkpoints are saved to `checkpoints/<wandb.name>/best_model.ckpt`. After training the diffusion model, ensure `lm.ft_evoflow` in the classifier and sampling configs matches the `wandb.name` from `lm.yaml`.
97
+
98
+ ---
99
+
100
+ ## Data Preparation
101
+
102
+ Place your datasets under `<base_dir>/data/`. Expected CSV formats:
103
+
104
+ ### MeMDLM & solubility classifier (`data/train.csv`, `data/test.csv`, `data/val.csv`)
105
+
106
+ | Column | Description |
107
+ |---|---|
108
+ | `Sequence` | Amino acid sequence. For the solubility classifier, use **uppercase** for soluble residues and **lowercase** for TM residues. For MeMDLM pretraining, sequences are uppercased automatically. |
109
+
110
+ ### Oligomerization classifier (`data/olig_clf/`)
111
+
112
+ | Column | Description |
113
+ |---|---|
114
+ | `Sequence` | Input sequence |
115
+ | `Binary Label` | `0` or `1` oligomerization label |
116
+
117
+ ### Multipass classifier (`data/multipass/`)
118
+
119
+ | Column | Description |
120
+ |---|---|
121
+ | `Sequence` | Input sequence |
122
+ | `TM_segments` | Integer count of predicted TM segments |
123
+
124
+ ---
125
+
126
+ ## Training
127
+
128
+ All training jobs are launched from the **repository root**. We recommend running long jobs with `nohup` so they persist after logout. Create a `logs/` directory first:
129
+
130
+ ```bash
131
+ mkdir -p logs
132
+ ```
133
+
134
+ ### 1. MeMDLM diffusion model
135
+
136
+ **Config:** `src/configs/lm.yaml`
137
+ **Entry point:** `src/lm/memdlm/main.py`
138
+
139
+ Set `training.mode: train` and adjust `training.devices` to the number of GPUs available.
140
+
141
+ ```bash
142
+ nohup python src/lm/memdlm/main.py > logs/memdlm_train.out 2>&1 &
143
+ ```
144
+
145
+ Key settings in `lm.yaml` (do not change unless reproducing a new experiment):
146
+
147
+ | Parameter | Default |
148
+ |---|---|
149
+ | Base model | `fredzzp/EvoFlow-650M-context-3070` |
150
+ | `training.max_steps` | 5000 |
151
+ | `training.n_layers` | 3 (unfrozen transformer layers) |
152
+ | `optim.lr` | 4e-5 |
153
+ | `data.batch_size` | 32 |
154
+ | `lm.num_diffusion_timesteps` | 500 |
155
+
156
+ Checkpoint saved to: `checkpoints/<wandb.name>/best_model.ckpt`
157
+
158
+ To evaluate a trained checkpoint, set `training.mode: test`.
159
+
160
+ ```bash
161
+ nohup python src/lm/memdlm/main.py > logs/memdlm_test.out 2>&1 &
162
+ ```
163
+
164
+ ---
165
+
166
+ ### 2. Solubility classifier
167
+
168
+ **Config:** `src/configs/solubility.yaml`
169
+ **Entry point:** `src/guidance/solubility/main.py`
170
+
171
+ Trains a per-residue ESM-based classifier that predicts soluble vs. TM residues. This checkpoint is required for PET solubilization and desolubilization sampling.
172
+
173
+ Set `training.mode: train`.
174
+
175
+ ```bash
176
+ nohup python src/guidance/solubility/main.py > logs/solubility_train.out 2>&1 &
177
+ ```
178
+
179
+ Key settings:
180
+
181
+ | Parameter | Default |
182
+ |---|---|
183
+ | `training.max_steps` | 3000 |
184
+ | `model.num_layers` | 4 |
185
+ | `optim.lr` | 3e-5 |
186
+ | `data.batch_size` | 32 |
187
+
188
+ Checkpoint saved to: `checkpoints/<wandb.name>/best_model.ckpt`
189
+
190
+ ---
191
+
192
+ ### 3. Oligomerization classifier
193
+
194
+ **Config:** `src/configs/oligo.yaml`
195
+ **Entry point:** `src/guidance/oligo/main.py`
196
+
197
+ Set `training.mode: train`.
198
+
199
+ ```bash
200
+ nohup python src/guidance/oligo/main.py > logs/oligo_train.out 2>&1 &
201
+ ```
202
+
203
+ Key settings:
204
+
205
+ | Parameter | Default |
206
+ |---|---|
207
+ | `training.max_steps` | 3000 |
208
+ | `model.num_layers` | 1 |
209
+ | `optim.lr` | 1e-4 |
210
+ | `data.batch_size` | 64 |
211
+ | `data.max_seq_len` | 54 |
212
+
213
+ Checkpoint saved to: `checkpoints/<wandb.name>/best_model.ckpt`
214
+
215
+ ---
216
+
217
+ ### 4. Multipass classifier
218
+
219
+ **Config:** `src/configs/multipass.yaml`
220
+ **Entry point:** `src/guidance/multipass/main.py`
221
+
222
+ Predicts the number of TM segments in a sequence. Required for multipass-guided generation.
223
+
224
+ Set `training.mode: train`.
225
+
226
+ ```bash
227
+ nohup python src/guidance/multipass/main.py > logs/multipass_train.out 2>&1 &
228
+ ```
229
+
230
+ Key settings:
231
+
232
+ | Parameter | Default |
233
+ |---|---|
234
+ | `training.max_steps` | 3000 |
235
+ | `model.num_layers` | 4 |
236
+ | `optim.lr` | 3e-5 |
237
+ | `data.batch_size` | 32 |
238
+
239
+ Checkpoint saved to: `checkpoints/<wandb.name>/best_model.ckpt`
240
+
241
+ ---
242
+
243
+ ### Recommended training order
244
+
245
+ ```
246
+ 1. MeMDLM (lm.yaml)
247
+
248
+ 2. Classifiers in any order:
249
+ • solubility.yaml
250
+ • oligo.yaml
251
+ • multipass.yaml
252
+ ```
253
+
254
+ Update `lm.ft_evoflow` in all downstream configs to match the `wandb.name` produced by step 1.
255
+
256
+ ---
257
+
258
+ ## Sampling
259
+
260
+ Sampling scripts generate sequences and write CSVs under `<base_dir>/results/`. Each script loads the fine-tuned MeMDLM checkpoint (`checkpoints/<lm.ft_evoflow>/best_model.ckpt`) and, where applicable, the corresponding classifier checkpoint (`checkpoints/<wandb.name>/best_model.ckpt`).
261
+
262
+ Run all sampling jobs from the **repository root**:
263
+
264
+ ```bash
265
+ mkdir -p logs
266
+ ```
267
+
268
+ ### 1. Unconditional generation
269
+
270
+ **Config:** `src/configs/lm.yaml`
271
+ **Script:** `src/sampling/unconditional_generator.py`
272
+
273
+ Generates *de novo* membrane protein sequences from a fully masked prior.
274
+
275
+ ```bash
276
+ nohup python src/sampling/unconditional_generator.py > logs/unconditional_sample.out 2>&1 &
277
+ ```
278
+
279
+ **Output:** `results/denovo/<wandb.name>/<date>_multipass/seqs_with_ppl.csv`
280
+
281
+ Columns: `Generated Sequence`, `ESM PPL`, `MeMDLM PPL`
282
+
283
+ ---
284
+
285
+ ### 2. Solubilization (PET)
286
+
287
+ **Config:** `src/configs/solubility.yaml`
288
+ **Script:** `src/sampling/pet_generator.py`
289
+
290
+ Uses Per-Token Guidance to redesign **uppercase (soluble)** positions in a scaffold while preserving **lowercase (TM)** residues. Update the input CSV path in `pet_generator.py` (default: `results/heme/cybtx.csv`) to point to your scaffold sequences.
291
+
292
+ ```bash
293
+ nohup python src/sampling/pet_generator.py > logs/solubilize_sample.out 2>&1 &
294
+ ```
295
+
296
+ **Output:** `results/heme/<lm.ft_evoflow>/solubilize/<date>/<prior-params>/infilled_seqs.csv`
297
+
298
+ ---
299
+
300
+ ### 3. Desolubilization (PET)
301
+
302
+ **Config:** `src/configs/desolubilize.yaml`
303
+ **Script:** `src/sampling/desolubilize_generator.py`
304
+
305
+ The inverse of solubilization: redesigns **lowercase (soluble)** positions while preserving **uppercase (TM)** residues. Uses the same solubility classifier checkpoint. Update the input CSV path in `desolubilize_generator.py` (default: `results/heme/4d2.csv`).
306
+
307
+ ```bash
308
+ nohup python src/sampling/desolubilize_generator.py > logs/desolubilize_sample.out 2>&1 &
309
+ ```
310
+
311
+ **Output:** `results/heme/<lm.ft_evoflow>/desolubilize/<date>/<prior-params>/infilled_seqs.csv`
312
+
313
+ ---
314
+
315
+ ### 4. Multipass generation
316
+
317
+ **Config:** `src/configs/multipass.yaml`
318
+ **Script:** `src/sampling/multipass_generator.py`
319
+
320
+ Generates sequences guided toward a target multipass TM topology using the multipass classifier.
321
+
322
+ ```bash
323
+ nohup python src/sampling/multipass_generator.py > logs/multipass_sample.out 2>&1 &
324
+ ```
325
+
326
+ **Output:** `results/multipass/<wandb.name>/<date>/lamb=<reg_strength>_tau=<sampling_temperature>/seqs_with_ppl.csv`
327
+
328
+ Columns: `Generated Sequence`, `ESM PPL`, `MeMDLM PPL`, `Pred TM Segments`
329
+
330
+ ---
331
+
332
+ ### 5. Oligomerization
333
+
334
+ **Config:** `src/configs/oligo.yaml`
335
+ **Script:** `src/sampling/olig_generator.py`
336
+
337
+ Redesigns sequences to increase predicted oligomerization propensity using the oligomerization classifier (NOS guidance).
338
+
339
+ ```bash
340
+ nohup python src/sampling/olig_generator.py > logs/oligo_sample.out 2>&1 &
341
+ ```
342
+
343
+ **Output:** `results/oligo/<wandb.name>/<date>/seqs_with_ppl.csv`
344
+
345
+ Columns: `Original Sequence`, `Generated Sequence`, `OG Olig Value`, `New Olig Value`, `Olig Increase`, `ESM PPL`, `MeMDLM PPL`, `MemDLM Blosum`
346
+
347
+ ---
348
+
349
+ ### Sampling quick-reference
350
+
351
+ | Task | Config | Script | Classifier required |
352
+ |---|---|---|---|
353
+ | Unconditional | `lm.yaml` | `unconditional_generator.py` | No |
354
+ | Solubilization | `solubility.yaml` | `pet_generator.py` | Solubility |
355
+ | Desolubilization | `desolubilize.yaml` | `desolubilize_generator.py` | Solubility |
356
+ | Multipass | `multipass.yaml` | `multipass_generator.py` | Multipass |
357
+ | Oligomerization | `oligo.yaml` | `olig_generator.py` | Oligomerization |
358
+
359
+ ---
360
+
361
+ ## Citation
362
+
363
+ If you use this repository in your research, please cite:
364
+
365
+ ```bibtex
366
+ @article{goel2026memdlm,
367
+ title = {Token-Level Guided Discrete Diffusion for Membrane Protein Design},
368
+ author = {Goel, Shrey and Chatterjee, Pranam},
369
+ journal = {arXiv preprint arXiv:XXXX.XXXXX},
370
+ year = {2026}
371
+ }
372
+ ```
373
+
374
+ If you enjoyed this repo, please cite it and star the repository. We appreciate your support!
requirements.txt ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ aiohappyeyeballs==2.6.1
2
+ aiohttp==3.12.15
3
+ aiosignal==1.4.0
4
+ annotated-types==0.7.0
5
+ antlr4-python3-runtime==4.9.3
6
+ anyio==4.10.0
7
+ argon2-cffi==25.1.0
8
+ argon2-cffi-bindings==25.1.0
9
+ arrow==1.3.0
10
+ asttokens==3.0.0
11
+ async-lru==2.0.5
12
+ attrs==25.3.0
13
+ babel==2.17.0
14
+ beautifulsoup4==4.13.5
15
+ bio==1.8.0
16
+ biopython==1.85
17
+ biothings_client==0.4.1
18
+ bleach==6.2.0
19
+ blosum==2.2.0
20
+ certifi==2025.8.3
21
+ cffi==2.0.0
22
+ charset-normalizer==3.4.3
23
+ click==8.3.0
24
+ comm==0.2.3
25
+ contourpy==1.3.3
26
+ cycler==0.12.1
27
+ datasets==4.4.2
28
+ debugpy==1.8.16
29
+ decorator==5.2.1
30
+ defusedxml==0.7.1
31
+ dill==0.4.0
32
+ executing==2.2.1
33
+ fastjsonschema==2.21.2
34
+ filelock==3.19.1
35
+ fonttools==4.61.1
36
+ fqdn==1.5.1
37
+ frozenlist==1.7.0
38
+ fsspec==2025.9.0
39
+ gitdb==4.0.12
40
+ GitPython==3.1.45
41
+ gprofiler-official==1.0.0
42
+ h11==0.16.0
43
+ hf-xet==1.1.10
44
+ httpcore==1.0.9
45
+ httpx==0.28.1
46
+ huggingface-hub==0.34.4
47
+ idna==3.10
48
+ ipykernel==6.30.1
49
+ ipython==9.5.0
50
+ ipython_pygments_lexers==1.1.1
51
+ isoduration==20.11.0
52
+ jedi==0.19.2
53
+ Jinja2==3.1.6
54
+ joblib==1.5.2
55
+ json5==0.12.1
56
+ jsonpointer==3.0.0
57
+ jsonschema==4.25.1
58
+ jsonschema-specifications==2025.9.1
59
+ jupyter_client==8.6.3
60
+ jupyter_core==5.8.1
61
+ jupyter-events==0.12.0
62
+ jupyter-lsp==2.3.0
63
+ jupyter_server==2.17.0
64
+ jupyter_server_terminals==0.5.3
65
+ jupyterlab==4.4.7
66
+ jupyterlab_pygments==0.3.0
67
+ jupyterlab_server==2.27.3
68
+ kiwisolver==1.4.9
69
+ lark==1.2.2
70
+ lightning==2.5.5
71
+ lightning-utilities==0.15.2
72
+ MarkupSafe==3.0.2
73
+ matplotlib==3.10.8
74
+ matplotlib-inline==0.1.7
75
+ MeMDLM_v2==1.0
76
+ mistune==3.1.4
77
+ mpmath==1.3.0
78
+ multidict==6.6.4
79
+ multiprocess==0.70.18
80
+ mygene==3.2.2
81
+ nbclient==0.10.2
82
+ nbconvert==7.16.6
83
+ nbformat==5.10.4
84
+ nest-asyncio==1.6.0
85
+ networkx==3.5
86
+ notebook==7.4.5
87
+ notebook_shim==0.2.4
88
+ numpy==2.3.3
89
+ nvidia-cublas-cu12==12.8.4.1
90
+ nvidia-cuda-cupti-cu12==12.8.90
91
+ nvidia-cuda-nvrtc-cu12==12.8.93
92
+ nvidia-cuda-runtime-cu12==12.8.90
93
+ nvidia-cudnn-cu12==9.10.2.21
94
+ nvidia-cufft-cu12==11.3.3.83
95
+ nvidia-cufile-cu12==1.13.1.3
96
+ nvidia-curand-cu12==10.3.9.90
97
+ nvidia-cusolver-cu12==11.7.3.90
98
+ nvidia-cusparse-cu12==12.5.8.93
99
+ nvidia-cusparselt-cu12==0.7.1
100
+ nvidia-nccl-cu12==2.27.3
101
+ nvidia-nvjitlink-cu12==12.8.93
102
+ nvidia-nvtx-cu12==12.8.90
103
+ omegaconf==2.3.0
104
+ packaging==25.0
105
+ pandas==2.3.2
106
+ pandocfilters==1.5.1
107
+ parso==0.8.5
108
+ pexpect==4.9.0
109
+ pillow==12.1.0
110
+ pip==25.2
111
+ platformdirs==4.4.0
112
+ pooch==1.8.2
113
+ prometheus_client==0.22.1
114
+ prompt_toolkit==3.0.52
115
+ propcache==0.3.2
116
+ protobuf==6.32.1
117
+ psutil==7.0.0
118
+ ptyprocess==0.7.0
119
+ pure_eval==0.2.3
120
+ pyarrow==22.0.0
121
+ pycparser==2.23
122
+ pydantic==2.11.9
123
+ pydantic_core==2.33.2
124
+ Pygments==2.19.2
125
+ pyparsing==3.3.2
126
+ python-dateutil==2.9.0.post0
127
+ python-json-logger==3.3.0
128
+ pytorch-lightning==2.5.5
129
+ pytz==2025.2
130
+ PyYAML==6.0.2
131
+ pyzmq==27.1.0
132
+ referencing==0.36.2
133
+ regex==2025.9.1
134
+ requests==2.32.5
135
+ rfc3339-validator==0.1.4
136
+ rfc3986-validator==0.1.1
137
+ rfc3987-syntax==1.1.0
138
+ rpds-py==0.27.1
139
+ safetensors==0.6.2
140
+ scikit-learn==1.7.2
141
+ scipy==1.16.2
142
+ seaborn==0.13.2
143
+ Send2Trash==1.8.3
144
+ sentry-sdk==2.38.0
145
+ setuptools==78.1.1
146
+ six==1.17.0
147
+ smmap==5.0.2
148
+ sniffio==1.3.1
149
+ soupsieve==2.8
150
+ stack-data==0.6.3
151
+ sympy==1.14.0
152
+ terminado==0.18.1
153
+ threadpoolctl==3.6.0
154
+ tinycss2==1.4.0
155
+ tokenizers==0.20.3
156
+ torch==2.8.0
157
+ torchmetrics==1.8.2
158
+ tornado==6.5.2
159
+ tqdm==4.67.1
160
+ traitlets==5.14.3
161
+ transformers==4.46.0
162
+ triton==3.4.0
163
+ types-python-dateutil==2.9.0.20250822
164
+ typing_extensions==4.15.0
165
+ typing-inspection==0.4.1
166
+ tzdata==2025.2
167
+ uri-template==1.3.0
168
+ urllib3==2.5.0
169
+ wandb==0.22.0
170
+ wcwidth==0.2.13
171
+ webcolors==24.11.1
172
+ webencodings==0.5.1
173
+ websocket-client==1.8.0
174
+ wheel==0.45.1
175
+ xxhash==3.6.0
176
+ yarl==1.20.1
src/configs/{guidance.yaml → desolubilize.yaml} RENAMED
@@ -1,14 +1,14 @@
1
 
2
 
3
  seed: 42
4
- base_dir: /scratch/sgoel/MeMDLM_v2
5
 
6
 
7
  lm:
8
  pretrained_esm: facebook/esm2_t33_650M_UR50D
9
  pretrained_evoflow: fredzzp/EvoFlow-650M-context-3070
10
  pretrained_dplm: airkingbd/dplm_650m
11
- ft_evoflow: ft_eflow-3070-650M_steps=50k_layers=3_lr=0.00004_wd=.01_polynom_pwr=1_betas=.9-.98_bsz=8_gclip=1.0
12
  ft_dplm: ft_dplm-650M_steps=5k_layers=3_lr=0.00004_wd=.01_polynom_pwr=1_betas=.9-.98_bsz=32_gclip=1.0
13
 
14
  model:
@@ -38,32 +38,33 @@ training:
38
  val_check_interval: 250
39
  enable_progress_bar: true
40
  grad_clip_val: 1.0
41
- devices: [0] # list of GPU IDs from 0-7
42
 
43
  guidance:
 
44
  n_steps: 128
45
- alpha: 3
46
  gamma: 0.3
47
  saliency_eps: 1e-4
48
  saliency_t: 2.0
49
- sampling_t: 0.7
50
- boltzmann_t: 0.3
51
- top_p: 0.2
52
  steps: 128
53
- prior: lm_probs # lm_probs / boltzmann
54
 
55
  data:
56
  batch_size: 32
57
  max_seq_len: 1024
58
- train: ${base_dir}/data/classifier/train.csv
59
- test: ${base_dir}/data/classifier/test.csv
60
- val: ${base_dir}/data/classifier/val.csv
61
 
62
 
63
  wandb:
64
- project: memdlm_guidance
65
  group: programmablebio
66
- name: new_data_cleaned_steps3k_lr3e-5_bsz32_heads2_drpt0.5_layers4
67
  id: ${.name}_${seed}
68
 
69
 
 
1
 
2
 
3
  seed: 42
4
+ base_dir: /scratch/pranamlab/sgoel/MeMDLM_v2
5
 
6
 
7
  lm:
8
  pretrained_esm: facebook/esm2_t33_650M_UR50D
9
  pretrained_evoflow: fredzzp/EvoFlow-650M-context-3070
10
  pretrained_dplm: airkingbd/dplm_650m
11
+ ft_evoflow: ft_eflow-3070-650M_steps=5k_layers=3_lr=1e-5_lr-end=2e-6_wd=.01_polynom_pwr=1_betas=.9-.98_bsz=8_gclip=1.0_ml=1024
12
  ft_dplm: ft_dplm-650M_steps=5k_layers=3_lr=0.00004_wd=.01_polynom_pwr=1_betas=.9-.98_bsz=32_gclip=1.0
13
 
14
  model:
 
38
  val_check_interval: 250
39
  enable_progress_bar: true
40
  grad_clip_val: 1.0
41
+ devices: 2
42
 
43
  guidance:
44
+ task: desolubilize
45
  n_steps: 128
46
+ alpha: 5
47
  gamma: 0.3
48
  saliency_eps: 1e-4
49
  saliency_t: 2.0
50
+ sampling_t: 0.8
51
+ boltzmann_t: 0.2
52
+ top_p: 0.3
53
  steps: 128
54
+ prior: boltzmann
55
 
56
  data:
57
  batch_size: 32
58
  max_seq_len: 1024
59
+ train: ${base_dir}/data/train.csv
60
+ test: ${base_dir}/data/test.csv
61
+ val: ${base_dir}/data/val.csv
62
 
63
 
64
  wandb:
65
+ project: memdlm_again
66
  group: programmablebio
67
+ name: clf_steps3k_lr3e-5_bsz32_heads2_drpt0.5_layers4
68
  id: ${.name}_${seed}
69
 
70
 
src/configs/lm.yaml CHANGED
@@ -17,7 +17,7 @@ optim:
17
  type: adamw
18
  scheduler: polynomial
19
  lr: 0.00004
20
- lr_end: 1e-5
21
  warmup_init_lr: 1e-07
22
  weight_decay: 0.01
23
  beta1: 0.9
@@ -35,24 +35,24 @@ training:
35
  val_check_interval: 250
36
  enable_progress_bar: true
37
  grad_clip_val: 1.0
38
- devices: [0,1,2] # list of GPU IDs
39
 
40
  sampling:
41
  n_steps: 128
42
 
43
 
44
  data:
45
- batch_size: 8
46
  max_seq_len: 1024
47
- train: ${base_dir}/data/new/train.csv
48
- test: ${base_dir}/data/new/test.csv
49
- val: ${base_dir}/data/new/val.csv
50
 
51
 
52
  wandb:
53
- project: memdlm
54
  group: programmablebio
55
- name: ft_eflow-3070-650M_steps=5k_layers=3_lr=0.00004_wd=.01_polynom_pwr=1_betas=.9-.98_bsz=8_gclip=1.0_ml=1024
56
  # name: ft_progen-base-764M_steps=50k_layers=2_lr=0.00004_wd=.1_cosine-to-frac_betas=.9-.999_bsz=8_gclip=0.8
57
  # name: ft_dplm-650M_steps=5k_layers=3_lr=0.00004_wd=.01_polynom_pwr=1_betas=.9-.98_bsz=32_gclip=1.0
58
  # name: ft_esm-650M_steps=3k_layers=3_lr=0.00004_wd=.01_polynom_pwr=1_betas=.9-.98_bsz=32_gclip=1.0
 
17
  type: adamw
18
  scheduler: polynomial
19
  lr: 0.00004
20
+ lr_end: 2e-6
21
  warmup_init_lr: 1e-07
22
  weight_decay: 0.01
23
  beta1: 0.9
 
35
  val_check_interval: 250
36
  enable_progress_bar: true
37
  grad_clip_val: 1.0
38
+ devices: 3 # number of GPUs
39
 
40
  sampling:
41
  n_steps: 128
42
 
43
 
44
  data:
45
+ batch_size: 32
46
  max_seq_len: 1024
47
+ train: ${base_dir}/data/train.csv
48
+ test: ${base_dir}/data/test.csv
49
+ val: ${base_dir}/data/val.csv
50
 
51
 
52
  wandb:
53
+ project: memdlm_again
54
  group: programmablebio
55
+ name: ft_eflow-3070-650M_steps=5k_layers=3_lr=1e-5_lr-end=2e-6_wd=.01_polynom_pwr=1_betas=.9-.98_bsz=8_gclip=1.0_ml=1024
56
  # name: ft_progen-base-764M_steps=50k_layers=2_lr=0.00004_wd=.1_cosine-to-frac_betas=.9-.999_bsz=8_gclip=0.8
57
  # name: ft_dplm-650M_steps=5k_layers=3_lr=0.00004_wd=.01_polynom_pwr=1_betas=.9-.98_bsz=32_gclip=1.0
58
  # name: ft_esm-650M_steps=3k_layers=3_lr=0.00004_wd=.01_polynom_pwr=1_betas=.9-.98_bsz=32_gclip=1.0
src/configs/multipass.yaml ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ seed: 42
4
+ base_dir: /scratch/pranamlab/sgoel/MeMDLM_v2
5
+
6
+
7
+ lm:
8
+ pretrained_esm: facebook/esm2_t33_650M_UR50D
9
+ pretrained_evoflow: fredzzp/EvoFlow-650M-context-3070
10
+ pretrained_dplm: airkingbd/dplm_650m
11
+ ft_evoflow: ft_eflow-3070-650M_steps=5k_layers=3_lr=1e-5_lr-end=2e-6_wd=.01_polynom_pwr=1_betas=.9-.98_bsz=8_gclip=1.0_ml=1024
12
+ ft_dplm: ft_dplm-650M_steps=5k_layers=3_lr=0.00004_wd=.01_polynom_pwr=1_betas=.9-.98_bsz=32_gclip=1.0
13
+ num_diffusion_timesteps: 500
14
+
15
+ model:
16
+ d_model: 1280
17
+ num_heads: 2
18
+ dropout: 0.5
19
+ num_layers: 4
20
+ label_pad_value: -100
21
+
22
+ optim:
23
+ type: adamw
24
+ lr: 3e-5
25
+ lr_end: 1e-5
26
+ weight_decay: 0.01
27
+ beta1: 0.9
28
+ beta2: 0.98
29
+ power: 1
30
+
31
+
32
+ training:
33
+ mode: test # train / test
34
+ n_layers: 4
35
+ max_steps: 3000
36
+ warmup_steps: 150
37
+ log_every_n_steps: 10
38
+ num_sanity_val_steps: 2
39
+ val_check_interval: 250
40
+ enable_progress_bar: true
41
+ grad_clip_val: 1.0
42
+ devices: 2
43
+
44
+ guidance:
45
+ guide_steps: 12
46
+ diffusion_steps: 128
47
+ reg_strength: 3 # 3 vs 0.1
48
+ step_size: 0.1
49
+ sampling_temperature: 1.15
50
+
51
+ data:
52
+ batch_size: 32
53
+ max_seq_len: 1024
54
+ train: ${base_dir}/data/multipass/train_tm_segment_counts.csv
55
+ test: ${base_dir}/data/multipass/test_tm_segment_counts.csv
56
+ val: ${base_dir}/data/multipass/val_tm_segment_counts.csv
57
+
58
+
59
+ wandb:
60
+ project: memdlm_again
61
+ group: programmablebio
62
+ name: multipass_clf_steps3k_lr3e-5_bsz32_heads2_drpt0.5_layers4
63
+ id: ${.name}_${seed}
64
+
65
+
66
+ checkpointing:
67
+ save_every_n_steps: 250
68
+ save_dir: ${base_dir}/checkpoints/${wandb.name}
69
+ resume_ckpt_path: ${checkpointing.save_dir}/last.ckpt
70
+ best_ckpt_path: ${checkpointing.save_dir}/best_model.ckpt
src/configs/oligo.yaml ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+
4
+ seed: 42
5
+ base_dir: /scratch/pranamlab/sgoel/MeMDLM_v2
6
+
7
+
8
+ lm:
9
+ pretrained_esm: facebook/esm2_t33_650M_UR50D
10
+ pretrained_evoflow: fredzzp/EvoFlow-650M-context-3070
11
+ pretrained_dplm: airkingbd/dplm_650m
12
+ ft_evoflow: ft_eflow-3070-650M_steps=5k_layers=3_lr=1e-5_lr-end=2e-6_wd=.01_polynom_pwr=1_betas=.9-.98_bsz=8_gclip=1.0_ml=1024
13
+ ft_dplm: ft_dplm-650M_steps=5k_layers=3_lr=0.00004_wd=.01_polynom_pwr=1_betas=.9-.98_bsz=32_gclip=1.0
14
+
15
+ model:
16
+ esm_dim: 1280
17
+ d_model: 128
18
+ num_heads: 2
19
+ dropout: 0.3
20
+ num_layers: 1
21
+ label_pad_value: -100
22
+
23
+ optim:
24
+ type: adamw
25
+ lr: 1e-4
26
+ lr_end: 1e-5
27
+ weight_decay: 0.02
28
+ beta1: 0.9
29
+ beta2: 0.98
30
+ power: 1
31
+
32
+
33
+ training:
34
+ mode: test # train / test
35
+ n_layers: 4
36
+ max_steps: 3000
37
+ warmup_steps: 200
38
+ log_every_n_steps: 10
39
+ num_sanity_val_steps: 2
40
+ val_check_interval: 100
41
+ enable_progress_bar: true
42
+ grad_clip_val: 1.0
43
+ devices: 2
44
+
45
+ guidance:
46
+ n_steps: 128
47
+ alpha: 3
48
+ gamma: 0.3
49
+ saliency_eps: 1e-4
50
+ saliency_t: 2.0
51
+ sampling_t: 0.7
52
+ boltzmann_t: 0.3
53
+ top_p: 0.2
54
+ steps: 128
55
+ prior: lm_probs # lm_probs / boltzmann
56
+
57
+ olig_guidance:
58
+ guide_steps: 12
59
+ diffusion_steps: 64
60
+ reg_strength: 1
61
+ step_size: 0.1
62
+ temperature: 1.0
63
+ eps: 1e-8
64
+ residue_thresh: 0.5
65
+ topk_frac: 0.2
66
+
67
+ data:
68
+ batch_size: 64
69
+ max_seq_len: 54
70
+ train: ${base_dir}/data/olig_clf/train.csv
71
+ test: ${base_dir}/data/olig_clf/test.csv
72
+ val: ${base_dir}/data/olig_clf/val.csv
73
+
74
+
75
+ wandb:
76
+ project: memdlm_oligo
77
+ group: programmablebio
78
+ name: oligo-clf_steps3k_lr1e-4_bsz64_drpt0.3_wd0.02_dmodel128_simple-mlp_mean-pool
79
+ id: ${.name}_${seed}
80
+
81
+
82
+ checkpointing:
83
+ save_every_n_steps: 250
84
+ save_dir: ${base_dir}/checkpoints/${wandb.name}
85
+ resume_ckpt_path: ${checkpointing.save_dir}/last.ckpt
86
+ best_ckpt_path: ${checkpointing.save_dir}/best_model.ckpt
src/configs/solubility.yaml ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ seed: 42
4
+ base_dir: /scratch/pranamlab/sgoel/MeMDLM_v2
5
+
6
+
7
+ lm:
8
+ pretrained_esm: facebook/esm2_t33_650M_UR50D
9
+ pretrained_evoflow: fredzzp/EvoFlow-650M-context-3070
10
+ pretrained_dplm: airkingbd/dplm_650m
11
+ ft_evoflow: ft_eflow-3070-650M_steps=5k_layers=3_lr=1e-5_lr-end=2e-6_wd=.01_polynom_pwr=1_betas=.9-.98_bsz=8_gclip=1.0_ml=1024
12
+ ft_dplm: ft_dplm-650M_steps=5k_layers=3_lr=0.00004_wd=.01_polynom_pwr=1_betas=.9-.98_bsz=32_gclip=1.0
13
+
14
+ model:
15
+ d_model: 1280
16
+ num_heads: 2
17
+ dropout: 0.5
18
+ num_layers: 4
19
+ label_pad_value: -100
20
+
21
+ optim:
22
+ type: adamw
23
+ lr: 3e-5
24
+ lr_end: 1e-5
25
+ weight_decay: 0.01
26
+ beta1: 0.9
27
+ beta2: 0.98
28
+ power: 1
29
+
30
+
31
+ training:
32
+ mode: test # train / test
33
+ n_layers: 4
34
+ max_steps: 3000
35
+ warmup_steps: 150
36
+ log_every_n_steps: 10
37
+ num_sanity_val_steps: 2
38
+ val_check_interval: 250
39
+ enable_progress_bar: true
40
+ grad_clip_val: 1.0
41
+ devices: 2
42
+
43
+ guidance:
44
+ task: solubilize
45
+ n_steps: 128
46
+ alpha: 5 # 3
47
+ gamma: 0.3
48
+ saliency_eps: 1e-4
49
+ saliency_t: 2.0
50
+ sampling_t: 1.0 # 0.7
51
+ boltzmann_t: 0.15 # 0.3
52
+ top_p: 0.3 # 0.2
53
+ steps: 128
54
+ prior: boltzmann
55
+
56
+ data:
57
+ batch_size: 32
58
+ max_seq_len: 1024
59
+ train: ${base_dir}/data/train.csv
60
+ test: ${base_dir}/data/test.csv
61
+ val: ${base_dir}/data/val.csv
62
+
63
+
64
+ wandb:
65
+ project: memdlm_again
66
+ group: programmablebio
67
+ name: clf_steps3k_lr3e-5_bsz32_heads2_drpt0.5_layers4
68
+ id: ${.name}_${seed}
69
+
70
+
71
+ checkpointing:
72
+ save_every_n_steps: 250
73
+ save_dir: ${base_dir}/checkpoints/${wandb.name}
74
+ resume_ckpt_path: ${checkpointing.save_dir}/last.ckpt
75
+ best_ckpt_path: ${checkpointing.save_dir}/best_model.ckpt
src/guidance/multipass/dataloader.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import pandas as pd
3
+ import lightning.pytorch as pl
4
+
5
+ from transformers import AutoModel, AutoTokenizer
6
+ from torch.utils.data import Dataset, DataLoader
7
+
8
+
9
+ class MembraneDataset(Dataset):
10
+ def __init__(self, config, data_path):
11
+ self.config = config
12
+ self.data = pd.read_csv(data_path)
13
+ self.tokenizer = AutoTokenizer.from_pretrained(self.config.lm.pretrained_esm)
14
+
15
+ def __len__(self):
16
+ return len(self.data)
17
+
18
+ def __getitem__(self, idx):
19
+ sequence = self.data.iloc[idx]["Sequence"]
20
+ tm_segs = self.data.iloc[idx]["TM_segments"]
21
+
22
+ tokens = self.tokenizer(
23
+ sequence.upper(),
24
+ return_tensors='pt',
25
+ padding='max_length',
26
+ truncation=True,
27
+ max_length=self.config.data.max_seq_len,
28
+ )
29
+
30
+ labels = torch.tensor(1 if tm_segs > 0 else 0, dtype=torch.float)
31
+
32
+ return {
33
+ "input_ids": tokens['input_ids'],
34
+ "attention_mask": tokens['attention_mask'],
35
+ "labels": labels
36
+ }
37
+
38
+
39
+ def collate_fn(batch):
40
+ input_ids = torch.stack([item['input_ids'].squeeze(0) for item in batch])
41
+ masks = torch.stack([item['attention_mask'].squeeze(0) for item in batch])
42
+ labels = torch.stack([item['labels'] for item in batch])
43
+
44
+ return {
45
+ 'input_ids': input_ids,
46
+ 'attention_mask': masks,
47
+ 'labels': labels
48
+ }
49
+
50
+
51
+ class MembraneDataModule(pl.LightningDataModule):
52
+ def __init__(self, config, train_dataset, val_dataset, test_dataset, collate_fn=collate_fn):
53
+ super().__init__()
54
+ self.train_dataset = train_dataset
55
+ self.val_dataset = val_dataset
56
+ self.test_dataset = test_dataset
57
+ self.collate_fn = collate_fn
58
+ self.batch_size = config.data.batch_size
59
+
60
+ def train_dataloader(self):
61
+ return DataLoader(self.train_dataset,
62
+ batch_size=self.batch_size,
63
+ collate_fn=self.collate_fn,
64
+ num_workers=8,
65
+ pin_memory=True)
66
+
67
+ def val_dataloader(self):
68
+ return DataLoader(self.val_dataset,
69
+ batch_size=self.batch_size,
70
+ collate_fn=self.collate_fn,
71
+ num_workers=8,
72
+ pin_memory=True)
73
+
74
+ def test_dataloader(self):
75
+ return DataLoader(self.test_dataset,
76
+ batch_size=self.batch_size,
77
+ collate_fn=self.collate_fn,
78
+ num_workers=8,
79
+ pin_memory=True)
80
+
81
+
82
+ def get_datasets(config):
83
+ """Helper method to grab datasets to quickly init data module in main.py"""
84
+ train_dataset = MembraneDataset(config, config.data.train)
85
+ val_dataset = MembraneDataset(config, config.data.val)
86
+ test_dataset = MembraneDataset(config, config.data.test)
87
+
88
+ return {
89
+ "train": train_dataset,
90
+ "val": val_dataset,
91
+ "test": test_dataset
92
+ }
src/guidance/multipass/main.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+
3
+ import os
4
+ import wandb
5
+ import lightning.pytorch as pl
6
+
7
+ from omegaconf import OmegaConf
8
+ from lightning.pytorch.loggers import WandbLogger
9
+ from lightning.pytorch.callbacks import ModelCheckpoint, LearningRateMonitor
10
+
11
+ from src.utils.model_utils import _print
12
+ from src.utils.config_utils import load_config
13
+ from src.guidance.multipass.multipass_module import MultipassClassifier
14
+ from src.guidance.multipass.dataloader import MembraneDataModule, get_datasets
15
+ from src.lm.memdlm.diffusion_module import MembraneDiffusion
16
+
17
+
18
+ config = load_config("multipass.yaml")
19
+ wandb.login()
20
+
21
+ # data
22
+ datasets = get_datasets(config)
23
+ data_module = MembraneDataModule(
24
+ config=config,
25
+ train_dataset=datasets['train'],
26
+ val_dataset=datasets['val'],
27
+ test_dataset=datasets['test'],
28
+ )
29
+
30
+ # wandb logging
31
+ #wandb.init(project=config.wandb.project, name=config.wandb.name)
32
+ wandb_logger = WandbLogger(**config.wandb)
33
+
34
+ # lightning checkpoints
35
+ lr_monitor = LearningRateMonitor(logging_interval="step")
36
+ checkpoint_callback = ModelCheckpoint(
37
+ monitor="val/loss",
38
+ save_top_k=1,
39
+ mode="min",
40
+ dirpath=config.checkpointing.save_dir,
41
+ filename="best_model",
42
+ )
43
+
44
+ # lightning trainer
45
+ trainer = pl.Trainer(
46
+ max_steps=config.training.max_steps,
47
+ accelerator="cuda",
48
+ devices=1, #config.training.devices if config.training.mode=='train' else [0],
49
+ #strategy=DDPStrategy(find_unused_parameters=True),
50
+ callbacks=[checkpoint_callback, lr_monitor],
51
+ logger=wandb_logger,
52
+ log_every_n_steps=config.training.log_every_n_steps
53
+ )
54
+
55
+ # Folder to save checkpoints
56
+ ckpt_dir = config.checkpointing.save_dir
57
+ os.makedirs(ckpt_dir, exist_ok=True)
58
+
59
+ # instantiate model
60
+ model = MultipassClassifier(config, MembraneDiffusion(config))
61
+
62
+ # train or evalute the model
63
+ if config.training.mode == "train":
64
+ trainer.fit(model, datamodule=data_module)
65
+
66
+ elif config.training.mode == "test":
67
+ ckpt_path = os.path.join(ckpt_dir, "best_model.ckpt")
68
+ state_dict = model.get_state_dict(ckpt_path)
69
+ model.load_state_dict(state_dict)
70
+ trainer.test(model, datamodule=data_module, ckpt_path=ckpt_path)
71
+ else:
72
+ raise ValueError(f"{config.training.mode} is invalid. Must be 'train' or 'test'")
73
+
74
+ wandb.finish()
src/guidance/multipass/multipass_module.py ADDED
@@ -0,0 +1,167 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gc
2
+ import torch
3
+ import torch.nn as nn
4
+ import lightning.pytorch as pl
5
+ import torch.nn.functional as F
6
+
7
+ from omegaconf import OmegaConf
8
+
9
+ from src.utils.model_utils import _print
10
+ from src.guidance.solubility.utils import CosineWarmup
11
+
12
+ from sklearn.metrics import roc_auc_score, accuracy_score
13
+
14
+
15
+ config = OmegaConf.load("/scratch/pranamlab/sgoel/MeMDLM_v2/src/configs/multipass.yaml")
16
+
17
+
18
+ class MultipassClassifier(pl.LightningModule):
19
+ def __init__(self, config, diffusion_model):
20
+ super().__init__()
21
+ self.config = config
22
+ self.loss_fn = nn.BCEWithLogitsLoss(reduction='none')
23
+ self.all_preds = []
24
+ self.all_labels = []
25
+
26
+ self.diffusion_model = diffusion_model
27
+ for p in self.diffusion_model.model.parameters():
28
+ p.requires_grad = False
29
+ self.diffusion_model.eval()
30
+
31
+ encoder_layer = nn.TransformerEncoderLayer(
32
+ d_model=config.model.d_model,
33
+ nhead=config.model.num_heads,
34
+ dropout=config.model.dropout,
35
+ batch_first=True
36
+ )
37
+ self.encoder = nn.TransformerEncoder(encoder_layer, config.model.num_layers)
38
+ self.layer_norm = nn.LayerNorm(config.model.d_model)
39
+ self.dropout = nn.Dropout(config.model.dropout)
40
+ self.mlp = nn.Sequential(
41
+ nn.Linear(config.model.d_model, config.model.d_model // 2),
42
+ nn.ReLU(),
43
+ nn.Dropout(config.model.dropout),
44
+ nn.Linear(config.model.d_model // 2, 1),
45
+ )
46
+
47
+
48
+ # -------# Classifier step #-------- #
49
+ def forward(self, x_t, attn_mask, embeds=None, with_hidden=None):
50
+ if embeds is None:
51
+ with torch.no_grad():
52
+ embeds = self.diffusion_model.forward(
53
+ input_ids=x_t,
54
+ attention_mask=attn_mask,
55
+ return_hidden=True
56
+ )
57
+ else:
58
+ assert with_hidden is not None
59
+
60
+ encodings = self.encoder(embeds, src_key_padding_mask=(attn_mask== 0))
61
+ encodings = self.dropout(self.layer_norm(encodings))
62
+ mask = attn_mask.unsqueeze(-1)
63
+ pooled = (encodings * mask).sum(dim=1) / mask.sum(dim=1).clamp(min=1)
64
+ logits = self.mlp(pooled).squeeze(-1)
65
+ return logits
66
+
67
+ def step(self, batch):
68
+ input_ids = batch['input_ids']
69
+ attention_mask = batch['attention_mask']
70
+ labels = batch['labels']
71
+
72
+ t1 = self.diffusion_model.sample_t(input_ids)
73
+ maskable = self.diffusion_model.is_maskable(input_ids)
74
+
75
+ x_t, _ = self.diffusion_model.noise_x0(input_ids, t1, maskable_mask=maskable)
76
+
77
+ logits = self.forward(x_t, attention_mask)
78
+ loss = self.compute_loss(logits, labels)
79
+
80
+ return loss, logits
81
+
82
+ # -------# Training / Evaluation #-------- #
83
+ def training_step(self, batch, batch_idx):
84
+ train_loss, _ = self.step(batch)
85
+ self.log(name="train/loss", value=train_loss.item(), on_step=True, on_epoch=False, logger=True, sync_dist=True)
86
+ self.save_ckpt()
87
+ return train_loss
88
+
89
+ def validation_step(self, batch, batch_idx):
90
+ val_loss, _ = self.step(batch)
91
+ self.log(name="val/loss", value=val_loss.item(), on_step=False, on_epoch=True, logger=True, sync_dist=True)
92
+ return val_loss
93
+
94
+ def test_step(self, batch):
95
+ test_loss, logits = self.step(batch)
96
+ preds = F.sigmoid(logits)
97
+ self.all_preds.append(preds.detach().cpu())
98
+ self.all_labels.append(batch['labels'].detach().cpu())
99
+ self.log(name="test/loss", value=test_loss.item(), on_step=False, on_epoch=True, logger=True, sync_dist=True)
100
+ return test_loss
101
+
102
+ def on_test_epoch_start(self):
103
+ self.all_preds = []
104
+ self.all_labels = []
105
+
106
+ def on_test_epoch_end(self):
107
+ all_preds = torch.cat(self.all_preds).numpy()
108
+ all_labels = torch.cat(self.all_labels).numpy()
109
+
110
+ auroc = roc_auc_score(all_labels, all_preds)
111
+ binaries = (all_preds > 0.5).astype(int)
112
+ accuracy = accuracy_score(all_labels, binaries)
113
+
114
+ self.log(name="test/AUROC", value=auroc, on_step=False, on_epoch=True, logger=True, sync_dist=True)
115
+ self.log(name="test/accuracy", value=accuracy, on_step=False, on_epoch=True, logger=True, sync_dist=True)
116
+
117
+ def optimizer_step(self, *args, **kwargs):
118
+ super().optimizer_step(*args, **kwargs)
119
+ gc.collect()
120
+ torch.cuda.empty_cache()
121
+
122
+ def configure_optimizers(self):
123
+ path = self.config.training
124
+ optimizer = torch.optim.AdamW(self.parameters(), lr=self.config.optim.lr)
125
+ lr_scheduler = CosineWarmup(
126
+ optimizer,
127
+ warmup_steps=path.warmup_steps,
128
+ total_steps=path.max_steps,
129
+ )
130
+ scheduler_dict = {
131
+ "scheduler": lr_scheduler,
132
+ "interval": 'step',
133
+ 'frequency': 1,
134
+ 'monitor': 'val/loss',
135
+ 'name': 'learning_rate'
136
+ }
137
+ return [optimizer], [scheduler_dict]
138
+
139
+ def save_ckpt(self):
140
+ curr_step = self.global_step
141
+ save_every = self.config.training.val_check_interval
142
+ if curr_step % save_every == 0 and curr_step > 0: # Save every 250 steps
143
+ ckpt_path = f"{self.config.checkpointing.save_dir}/step={curr_step}.ckpt"
144
+ self.trainer.save_checkpoint(ckpt_path)
145
+
146
+ # -------# Loss and Test Set Metrics #-------- #
147
+ def compute_loss(self, logits, labels):
148
+ """Helper method to handle loss calculation"""
149
+ loss = self.loss_fn(logits, labels.float()).mean()
150
+ return loss
151
+
152
+ # -------# Helper Functions #-------- #
153
+ def get_state_dict(self, ckpt_path):
154
+ """Helper method to load and process a trained model's state dict from saved checkpoint"""
155
+ def remove_model_prefix(state_dict):
156
+ for k in state_dict.keys():
157
+ if "model." in k:
158
+ k.replace('model.', '')
159
+ return state_dict
160
+
161
+ checkpoint = torch.load(ckpt_path, weights_only=False)#, map_location='cuda' if torch.cuda.is_available() else 'cpu')
162
+ state_dict = checkpoint.get("state_dict", checkpoint)
163
+
164
+ if any(k.startswith("model.") for k in state_dict.keys()):
165
+ state_dict = remove_model_prefix(state_dict)
166
+
167
+ return state_dict
src/guidance/{utils.py → multipass/utils.py} RENAMED
File without changes
src/guidance/oligo/dataloader.py ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import pandas as pd
3
+ import lightning.pytorch as pl
4
+
5
+ from transformers import AutoTokenizer
6
+ from torch.utils.data import Dataset, DataLoader
7
+
8
+ class OligomerDataset(Dataset):
9
+ def __init__(self, config, data_path):
10
+ self.config = config
11
+ self.data = pd.read_csv(data_path)
12
+ self.tokenizer = AutoTokenizer.from_pretrained(self.config.lm.pretrained_esm)
13
+
14
+ def __len__(self):
15
+ return len(self.data)
16
+
17
+ def __getitem__(self, idx):
18
+ sequence = self.data.iloc[idx]["Sequence"]
19
+
20
+ tokens = self.tokenizer(
21
+ sequence,
22
+ return_tensors='pt',
23
+ padding='max_length',
24
+ truncation=True,
25
+ max_length=self.config.data.max_seq_len,
26
+ )
27
+
28
+ labels = torch.tensor(self.data.iloc[idx]['Binary Label'], dtype=torch.float)
29
+
30
+ return {
31
+ "input_ids": tokens['input_ids'],
32
+ "attention_mask": tokens['attention_mask'],
33
+ "labels": labels
34
+ }
35
+
36
+
37
+ def collate_fn(batch):
38
+ input_ids = torch.stack([item['input_ids'].squeeze(0) for item in batch])
39
+ masks = torch.stack([item['attention_mask'].squeeze(0) for item in batch])
40
+ labels = torch.stack([item['labels'] for item in batch])
41
+
42
+ return {
43
+ 'input_ids': input_ids,
44
+ 'attention_mask': masks,
45
+ 'labels': labels
46
+ }
47
+
48
+
49
+ class OligomerDataModule(pl.LightningDataModule):
50
+ def __init__(self, config, train_dataset, val_dataset, test_dataset, collate_fn=collate_fn):
51
+ super().__init__()
52
+ self.train_dataset = train_dataset
53
+ self.val_dataset = val_dataset
54
+ self.test_dataset = test_dataset
55
+ self.collate_fn = collate_fn
56
+ self.batch_size = config.data.batch_size
57
+
58
+ def train_dataloader(self):
59
+ return DataLoader(self.train_dataset,
60
+ batch_size=self.batch_size,
61
+ collate_fn=self.collate_fn,
62
+ num_workers=8,
63
+ pin_memory=True,
64
+ shuffle=True)
65
+
66
+ def val_dataloader(self):
67
+ return DataLoader(self.val_dataset,
68
+ batch_size=self.batch_size,
69
+ collate_fn=self.collate_fn,
70
+ num_workers=8,
71
+ pin_memory=True)
72
+
73
+ def test_dataloader(self):
74
+ return DataLoader(self.test_dataset,
75
+ batch_size=self.batch_size,
76
+ collate_fn=self.collate_fn,
77
+ num_workers=8,
78
+ pin_memory=True)
79
+
80
+
81
+ def get_datasets(config):
82
+ """Helper method to grab datasets to quickly init data module in main.py"""
83
+ train_dataset = OligomerDataset(config, config.data.train)
84
+ val_dataset = OligomerDataset(config, config.data.val)
85
+ test_dataset = OligomerDataset(config, config.data.test)
86
+
87
+ return {
88
+ "train": train_dataset,
89
+ "val": val_dataset,
90
+ "test": test_dataset
91
+ }
src/guidance/oligo/main.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+
3
+ import os
4
+ import wandb
5
+ import lightning.pytorch as pl
6
+
7
+ from omegaconf import OmegaConf
8
+ from lightning.pytorch.strategies import DDPStrategy
9
+ from lightning.pytorch.loggers import WandbLogger
10
+ from lightning.pytorch.callbacks import ModelCheckpoint, LearningRateMonitor
11
+
12
+ from src.utils.model_utils import _print
13
+ from src.utils.config_utils import load_config
14
+ from src.guidance.oligo.oligo_module import OligomerClassifier
15
+ from src.guidance.oligo.dataloader import get_datasets, OligomerDataModule
16
+
17
+
18
+ config = load_config("oligo.yaml")
19
+ wandb.login()
20
+
21
+ # data
22
+ datasets = get_datasets(config)
23
+ data_module = OligomerDataModule(
24
+ config=config,
25
+ train_dataset=datasets['train'],
26
+ val_dataset=datasets['val'],
27
+ test_dataset=datasets['test'],
28
+ )
29
+
30
+ # wandb logging
31
+ #wandb.init(project=config.wandb.project, name=config.wandb.name)
32
+ wandb_logger = WandbLogger(**config.wandb)
33
+
34
+ # lightning checkpoints
35
+ lr_monitor = LearningRateMonitor(logging_interval="step")
36
+ checkpoint_callback = ModelCheckpoint(
37
+ monitor="val/loss",
38
+ save_top_k=1,
39
+ mode="min",
40
+ dirpath=config.checkpointing.save_dir,
41
+ filename="best_model",
42
+ )
43
+
44
+ # lightning trainer
45
+ trainer = pl.Trainer(
46
+ max_steps=config.training.max_steps,
47
+ accelerator="cuda",
48
+ devices=1, #config.training.devices if config.training.mode=='train' else [0],
49
+ #strategy=DDPStrategy(find_unused_parameters=True),
50
+ callbacks=[checkpoint_callback, lr_monitor],
51
+ logger=wandb_logger,
52
+ log_every_n_steps=config.training.log_every_n_steps
53
+ )
54
+
55
+ # Folder to save checkpoints
56
+ ckpt_dir = config.checkpointing.save_dir
57
+ os.makedirs(ckpt_dir, exist_ok=True)
58
+
59
+ # instantiate model
60
+ model = OligomerClassifier(config)
61
+
62
+ # train or evalute the model
63
+ if config.training.mode == "train":
64
+ trainer.fit(model, datamodule=data_module)
65
+
66
+ elif config.training.mode == "test":
67
+ ckpt_path = os.path.join(ckpt_dir, "best_model.ckpt")
68
+ state_dict = model.get_state_dict(ckpt_path)
69
+ model.load_state_dict(state_dict)
70
+ trainer.test(model, datamodule=data_module, ckpt_path=ckpt_path)
71
+ else:
72
+ raise ValueError(f"{config.training.mode} is invalid. Must be 'train' or 'test'")
73
+
74
+ wandb.finish()
src/guidance/oligo/oligo_module.py ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gc
2
+ import torch
3
+ import torch.nn as nn
4
+ import lightning.pytorch as pl
5
+
6
+ from omegaconf import OmegaConf
7
+ from transformers import AutoModel
8
+ from torchmetrics.classification import BinaryAUROC, BinaryAccuracy
9
+
10
+ from src.utils.model_utils import _print
11
+ from src.guidance.oligo.utils import CosineWarmup, HelixRoPE
12
+
13
+
14
+ config = OmegaConf.load("/scratch/pranamlab/sgoel/MeMDLM_v2/src/configs/oligo.yaml")
15
+
16
+ class OligomerClassifier(pl.LightningModule):
17
+ def __init__(self, config):
18
+ super().__init__()
19
+ self.config = config
20
+ self.loss_fn = nn.BCEWithLogitsLoss()
21
+ self.auroc = BinaryAUROC()
22
+ self.accuracy = BinaryAccuracy()
23
+
24
+ self.esm_model = AutoModel.from_pretrained(config.lm.pretrained_esm)
25
+ for p in self.esm_model.parameters():
26
+ p.requires_grad = False
27
+
28
+ self.down_proj = nn.Linear(config.model.esm_dim, config.model.d_model)
29
+ self.layer_norm = nn.LayerNorm(config.model.d_model)
30
+ self.dropout = nn.Dropout(config.model.dropout)
31
+
32
+ self.mlp = nn.Sequential(
33
+ nn.Linear(config.model.d_model, config.model.d_model // 2),
34
+ nn.ReLU(),
35
+ nn.Dropout(config.model.dropout),
36
+ nn.Linear(config.model.d_model // 2, 1),
37
+ )
38
+
39
+ def forward(self, batch):
40
+ attention_mask = batch["attention_mask"]
41
+
42
+ if "input_ids" in batch:
43
+ esm_embeds = self.get_esm_embeddings(batch["input_ids"], attention_mask)
44
+ else:
45
+ esm_embeds = batch["embeds"]
46
+
47
+ if esm_embeds.ndim == 2:
48
+ esm_embeds = esm_embeds.unsqueeze(0)
49
+ if attention_mask.ndim == 1:
50
+ attention_mask = attention_mask.unsqueeze(0)
51
+
52
+ x = self.down_proj(esm_embeds)
53
+ x = self.layer_norm(x)
54
+ x = self.dropout(x)
55
+
56
+ mask = attention_mask.unsqueeze(-1).float()
57
+ x = x * mask
58
+
59
+ pooled = x.sum(dim=1) / mask.sum(dim=1).clamp(min=1.0)
60
+ logits = self.mlp(pooled).squeeze(-1)
61
+
62
+ return logits
63
+
64
+ # -------# Training / Evaluation #-------- #
65
+ def training_step(self, batch, batch_idx):
66
+ train_loss, _ = self.compute_loss(batch)
67
+ self.log(name="train/loss", value=train_loss.item(), on_step=True, on_epoch=False, logger=True, sync_dist=True)
68
+ self.save_ckpt()
69
+ return train_loss
70
+
71
+ def validation_step(self, batch, batch_idx):
72
+ val_loss, _ = self.compute_loss(batch)
73
+ self.log(name="val/loss", value=val_loss.item(), on_step=False, on_epoch=True, logger=True, sync_dist=True)
74
+ return val_loss
75
+
76
+ def test_step(self, batch):
77
+ test_loss, preds = self.compute_loss(batch)
78
+ auroc, accuracy = self.get_metrics(batch, preds)
79
+ self.log(name="test/loss", value=test_loss.item(), on_step=False, on_epoch=True, logger=True, sync_dist=True)
80
+ self.log(name="test/AUROC", value=auroc.item(), on_step=False, on_epoch=True, logger=True, sync_dist=True)
81
+ self.log(name="test/accuracy", value=accuracy.item(), on_step=False, on_epoch=True, logger=True, sync_dist=True)
82
+ return test_loss
83
+
84
+ def on_test_epoch_end(self):
85
+ self.auroc.reset()
86
+ self.accuracy.reset()
87
+
88
+ def optimizer_step(self, *args, **kwargs):
89
+ super().optimizer_step(*args, **kwargs)
90
+ gc.collect()
91
+ torch.cuda.empty_cache()
92
+
93
+ def configure_optimizers(self):
94
+ path = self.config.training
95
+ optimizer = torch.optim.AdamW(self.parameters(), lr=self.config.optim.lr)
96
+ lr_scheduler = CosineWarmup(
97
+ optimizer,
98
+ warmup_steps=path.warmup_steps,
99
+ total_steps=path.max_steps,
100
+ )
101
+ scheduler_dict = {
102
+ "scheduler": lr_scheduler,
103
+ "interval": 'step',
104
+ 'frequency': 1,
105
+ 'monitor': 'val/loss',
106
+ 'name': 'learning_rate'
107
+ }
108
+ return [optimizer], [scheduler_dict]
109
+
110
+ def save_ckpt(self):
111
+ curr_step = self.global_step
112
+ save_every = self.config.training.val_check_interval
113
+ if curr_step % save_every == 0 and curr_step > 0: # Save every 250 steps
114
+ ckpt_path = f"{self.config.checkpointing.save_dir}/step={curr_step}.ckpt"
115
+ self.trainer.save_checkpoint(ckpt_path)
116
+
117
+ # -------# Loss and Test Set Metrics #-------- #
118
+ @torch.no_grad
119
+ def get_esm_embeddings(self, input_ids, attention_mask):
120
+ outputs = self.esm_model(input_ids=input_ids, attention_mask=attention_mask)
121
+ embeddings = outputs.last_hidden_state
122
+ return embeddings
123
+
124
+ def compute_loss(self, batch):
125
+ """Helper method to handle loss calculation"""
126
+ labels = batch['labels']
127
+ preds = self.forward(batch)
128
+ loss = self.loss_fn(preds, labels)
129
+ return loss, preds
130
+
131
+ def get_metrics(self, batch, preds):
132
+ """Helper method to compute metrics"""
133
+ labels = batch['labels']
134
+ _print(f"labels {labels.shape}")
135
+ _print(f"preds {preds.shape}")
136
+ auroc = self.auroc.forward(preds, labels)
137
+ accuracy = self.accuracy.forward(preds, labels)
138
+ return auroc, accuracy
139
+
140
+ # -------# Helper Functions #-------- #
141
+ def get_state_dict(self, ckpt_path):
142
+ """Helper method to load and process a trained model's state dict from saved checkpoint"""
143
+ def remove_model_prefix(state_dict):
144
+ for k in state_dict.keys():
145
+ if "model." in k:
146
+ k.replace('model.', '')
147
+ return state_dict
148
+
149
+ checkpoint = torch.load(ckpt_path, weights_only=False)#, map_location='cuda' if torch.cuda.is_available() else 'cpu')
150
+ state_dict = checkpoint.get("state_dict", checkpoint)
151
+
152
+ if any(k.startswith("model.") for k in state_dict.keys()):
153
+ state_dict = remove_model_prefix(state_dict)
154
+
155
+ return state_dict
src/guidance/oligo/utils.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import torch
3
+ import torch.nn as nn
4
+ import numpy as np
5
+ from torch.optim.lr_scheduler import _LRScheduler
6
+
7
+
8
+
9
+ class HelixRoPE(nn.Module):
10
+ def __init__(self, config):
11
+ super().__init__()
12
+ pos = torch.arange(config.data.max_seq_len).float()
13
+ thetas = (2 * torch.pi / 3.6) * pos
14
+ self.register_buffer("thetas", thetas)
15
+
16
+ def forward(self, x, mask):
17
+ B, L, D = x.shape
18
+ assert D % 2 == 0
19
+
20
+ thetas = self.thetas[:L]
21
+ cos = torch.cos(thetas).unsqueeze(0).unsqueeze(-1)
22
+ sin = torch.sin(thetas).unsqueeze(0).unsqueeze(-1)
23
+
24
+ x_double = x.view(B, L, D//2, 2)
25
+ x1 = x_double[..., 0]
26
+ x2 = x_double[..., 1]
27
+
28
+ r1 = cos * x1 - sin * x2
29
+ r2 = sin * x1 + cos * x2
30
+
31
+ ropes = torch.stack([r1, r2], dim=-1).view(B, L, D)
32
+ return ropes * mask # attention mask to ignore pad tokens
33
+
34
+
35
+ class CosineWarmup(_LRScheduler):
36
+ def __init__(self, optimizer, warmup_steps, total_steps, eta_ratio=0.1, last_epoch=-1):
37
+ self.warmup_steps = warmup_steps
38
+ self.total_steps = total_steps
39
+ self.eta_ratio = eta_ratio # The ratio of minimum to maximum learning rate
40
+ super(CosineWarmup, self).__init__(optimizer, last_epoch)
41
+
42
+ def get_lr(self):
43
+ if self.last_epoch < self.warmup_steps:
44
+ return [base_lr * self.last_epoch / self.warmup_steps for base_lr in self.base_lrs]
45
+
46
+ progress = (self.last_epoch - self.warmup_steps) / (self.total_steps - self.warmup_steps)
47
+ cosine_decay = 0.5 * (1 + np.cos(np.pi * progress))
48
+ decayed_lr = (1 - self.eta_ratio) * cosine_decay + self.eta_ratio
49
+
50
+ return [decayed_lr * base_lr for base_lr in self.base_lrs]
src/guidance/{dataloader.py → solubility/dataloader.py} RENAMED
File without changes
src/guidance/{main.py → solubility/main.py} RENAMED
@@ -10,12 +10,13 @@ from lightning.pytorch.loggers import WandbLogger
10
  from lightning.pytorch.callbacks import ModelCheckpoint, LearningRateMonitor
11
 
12
  from src.utils.model_utils import _print
13
- from src.guidance.solubility_module import SolubilityClassifier
14
- from src.guidance.dataloader import MembraneDataModule, get_datasets
 
15
 
16
 
17
- config = OmegaConf.load("/scratch/sgoel/MeMDLM_v2/src/configs/guidance.yaml")
18
- wandb.login(key='2b76a2fa2c1cdfddc5f443602c17b011fefb0a8f')
19
 
20
  # data
21
  datasets = get_datasets(config)
 
10
  from lightning.pytorch.callbacks import ModelCheckpoint, LearningRateMonitor
11
 
12
  from src.utils.model_utils import _print
13
+ from src.utils.config_utils import load_config
14
+ from src.guidance.solubility.solubility_module import SolubilityClassifier
15
+ from src.guidance.solubility.dataloader import MembraneDataModule, get_datasets
16
 
17
 
18
+ config = load_config("solubility.yaml")
19
+ wandb.login()
20
 
21
  # data
22
  datasets = get_datasets(config)
src/guidance/{solubility_module.py → solubility/solubility_module.py} RENAMED
@@ -8,10 +8,10 @@ from transformers import AutoModel
8
  from torchmetrics.classification import BinaryAUROC, BinaryAccuracy
9
 
10
  from src.utils.model_utils import _print
11
- from src.guidance.utils import CosineWarmup
12
 
13
 
14
- config = OmegaConf.load("/scratch/sgoel/MeMDLM_v2/src/configs/guidance.yaml")
15
 
16
  class SolubilityClassifier(pl.LightningModule):
17
  def __init__(self, config):
@@ -146,7 +146,7 @@ class SolubilityClassifier(pl.LightningModule):
146
  k.replace('model.', '')
147
  return state_dict
148
 
149
- checkpoint = torch.load(ckpt_path, map_location='cuda' if torch.cuda.is_available() else 'cpu')
150
  state_dict = checkpoint.get("state_dict", checkpoint)
151
 
152
  if any(k.startswith("model.") for k in state_dict.keys()):
 
8
  from torchmetrics.classification import BinaryAUROC, BinaryAccuracy
9
 
10
  from src.utils.model_utils import _print
11
+ from src.guidance.solubility.utils import CosineWarmup
12
 
13
 
14
+ config = OmegaConf.load("/scratch/pranamlab/sgoel/MeMDLM_v2/src/configs/solubility.yaml")
15
 
16
  class SolubilityClassifier(pl.LightningModule):
17
  def __init__(self, config):
 
146
  k.replace('model.', '')
147
  return state_dict
148
 
149
+ checkpoint = torch.load(ckpt_path, weights_only=False)#, map_location='cuda' if torch.cuda.is_available() else 'cpu')
150
  state_dict = checkpoint.get("state_dict", checkpoint)
151
 
152
  if any(k.startswith("model.") for k in state_dict.keys()):
src/guidance/solubility/utils.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+
3
+ import numpy as np
4
+ from torch.optim.lr_scheduler import _LRScheduler
5
+
6
+ class CosineWarmup(_LRScheduler):
7
+ def __init__(self, optimizer, warmup_steps, total_steps, eta_ratio=0.1, last_epoch=-1):
8
+ self.warmup_steps = warmup_steps
9
+ self.total_steps = total_steps
10
+ self.eta_ratio = eta_ratio # The ratio of minimum to maximum learning rate
11
+ super(CosineWarmup, self).__init__(optimizer, last_epoch)
12
+
13
+ def get_lr(self):
14
+ if self.last_epoch < self.warmup_steps:
15
+ return [base_lr * self.last_epoch / self.warmup_steps for base_lr in self.base_lrs]
16
+
17
+ progress = (self.last_epoch - self.warmup_steps) / (self.total_steps - self.warmup_steps)
18
+ cosine_decay = 0.5 * (1 + np.cos(np.pi * progress))
19
+ decayed_lr = (1 - self.eta_ratio) * cosine_decay + self.eta_ratio
20
+
21
+ return [decayed_lr * base_lr for base_lr in self.base_lrs]
src/lm/memdlm/diffusion_module.py CHANGED
@@ -28,7 +28,7 @@ class MembraneDiffusion(pl.LightningModule):
28
  self.mask_id = self.tokenizer.mask_token_id
29
  self.pad_id = self.tokenizer.pad_token_id
30
 
31
- def forward(self, input_ids, attention_mask, guidance: Optional[bool] = False):
32
  """
33
  Forward pass through language model.
34
 
@@ -38,7 +38,12 @@ class MembraneDiffusion(pl.LightningModule):
38
  Returns:
39
  - logits (torch.Tensor): [B, L, V], unnormalized model outputs
40
  """
41
- return self.model(input_ids=input_ids, attention_mask=attention_mask).logits
 
 
 
 
 
42
 
43
  # -------# Diffusion #-------- #
44
  def step(self, batch):
@@ -78,7 +83,6 @@ class MembraneDiffusion(pl.LightningModule):
78
  u = torch.rand_like(x0, dtype=torch.float)
79
  t1_mask = (u < (t1 / self.config.lm.num_diffusion_timesteps)[:, None]) & maskable_mask
80
  x_t1 = x0.masked_fill(t1_mask, self.mask_id)
81
- x_t1 = x_t1.masked_fill(t1_mask, self.mask_id)
82
  return x_t1, t1_mask
83
 
84
  def get_weight(self, t, weight_type):
@@ -100,7 +104,7 @@ class MembraneDiffusion(pl.LightningModule):
100
 
101
  Args:
102
  logits (torch.Tensor): [B, L, vocab_size], unnormalized model outputs
103
- labels (torch.Tensor): [B, L], target labels (with padding tokens as -100)
104
  weight (torch.Tensor): [B, 1], per-sample weight for loss calculation
105
  Returns:
106
  loss (torch.Tensor): Averaged loss over the batch
@@ -127,22 +131,22 @@ class MembraneDiffusion(pl.LightningModule):
127
  # -------# Training / Evaluation #-------- #
128
  def training_step(self, batch):
129
  loss, ppl = self.step(batch)
130
- self.log("train/loss", loss.item(), on_step=True, on_epoch=False, prog_bar=True)
131
- self.log("train/ppl", ppl.item(), on_step=True, on_epoch=False, prog_bar=False)
132
  return loss
133
 
134
  def validation_step(self, batch):
135
  loss, ppl = self.step(batch)
136
  self.cleanup()
137
- self.log("val/loss", loss.item(), on_step=False, on_epoch=True, prog_bar=True, sync_dist=True)
138
- self.log("val/ppl", ppl.item(), on_step=False, on_epoch=True, prog_bar=False, sync_dist=True)
139
  return loss
140
 
141
  def test_step(self, batch):
142
  loss, ppl = self.step(batch)
143
  self.cleanup()
144
- self.log('test/loss', loss.item(), on_step=False, on_epoch=True, prog_bar=True, sync_dist=True)
145
- self.log("test/ppl", ppl.item(), on_step=False, on_epoch=True, prog_bar=False, sync_dist=True)
146
  return loss
147
 
148
 
@@ -176,7 +180,7 @@ class MembraneDiffusion(pl.LightningModule):
176
  k.replace('model.', '')
177
  return state_dict
178
 
179
- checkpoint = torch.load(ckpt_path, map_location='cuda' if torch.cuda.is_available() else 'cpu')
180
  state_dict = checkpoint.get("state_dict", checkpoint)
181
 
182
  if any(k.startswith("model.") for k in state_dict.keys()):
 
28
  self.mask_id = self.tokenizer.mask_token_id
29
  self.pad_id = self.tokenizer.pad_token_id
30
 
31
+ def forward(self, input_ids, attention_mask, return_hidden=None):
32
  """
33
  Forward pass through language model.
34
 
 
38
  Returns:
39
  - logits (torch.Tensor): [B, L, V], unnormalized model outputs
40
  """
41
+ if return_hidden:
42
+ out = self.model(input_ids=input_ids, attention_mask=attention_mask, output_hidden_states=True)
43
+ return out.hidden_states[-1]
44
+
45
+ else:
46
+ return self.model(input_ids=input_ids, attention_mask=attention_mask).logits
47
 
48
  # -------# Diffusion #-------- #
49
  def step(self, batch):
 
83
  u = torch.rand_like(x0, dtype=torch.float)
84
  t1_mask = (u < (t1 / self.config.lm.num_diffusion_timesteps)[:, None]) & maskable_mask
85
  x_t1 = x0.masked_fill(t1_mask, self.mask_id)
 
86
  return x_t1, t1_mask
87
 
88
  def get_weight(self, t, weight_type):
 
104
 
105
  Args:
106
  logits (torch.Tensor): [B, L, vocab_size], unnormalized model outputs
107
+ labels (torch.Tensor): [B, L], target labels with pad tokens
108
  weight (torch.Tensor): [B, 1], per-sample weight for loss calculation
109
  Returns:
110
  loss (torch.Tensor): Averaged loss over the batch
 
131
  # -------# Training / Evaluation #-------- #
132
  def training_step(self, batch):
133
  loss, ppl = self.step(batch)
134
+ self.log("train/loss", loss, on_step=True, on_epoch=False, prog_bar=True)
135
+ self.log("train/ppl", ppl, on_step=True, on_epoch=False, prog_bar=False)
136
  return loss
137
 
138
  def validation_step(self, batch):
139
  loss, ppl = self.step(batch)
140
  self.cleanup()
141
+ self.log("val/loss", loss, on_step=False, on_epoch=True, prog_bar=True, sync_dist=True)
142
+ self.log("val/ppl", ppl, on_step=False, on_epoch=True, prog_bar=False, sync_dist=True)
143
  return loss
144
 
145
  def test_step(self, batch):
146
  loss, ppl = self.step(batch)
147
  self.cleanup()
148
+ self.log('test/loss', loss, on_step=False, on_epoch=True, prog_bar=True, sync_dist=True)
149
+ self.log("test/ppl", ppl, on_step=False, on_epoch=True, prog_bar=False, sync_dist=True)
150
  return loss
151
 
152
 
 
180
  k.replace('model.', '')
181
  return state_dict
182
 
183
+ checkpoint = torch.load(ckpt_path, weights_only=False)#, map_location='cuda' if torch.cuda.is_available() else 'cpu')
184
  state_dict = checkpoint.get("state_dict", checkpoint)
185
 
186
  if any(k.startswith("model.") for k in state_dict.keys()):
src/lm/memdlm/main.py CHANGED
@@ -14,12 +14,13 @@ from lightning.pytorch.callbacks import ModelCheckpoint, LearningRateMonitor
14
  from src.lm.memdlm.diffusion_module import MembraneDiffusion
15
  from src.lm.memdlm.dataloader import MembraneDataModule, get_datasets
16
  from src.utils.model_utils import apply_rdm_freezing
 
17
 
18
- wandb.login(key='2b76a2fa2c1cdfddc5f443602c17b011fefb0a8f')
19
 
20
 
21
  # Load yaml config
22
- config = OmegaConf.load("/scratch/pranamlab/sgoel/MeMDLM_v2/src/configs/lm.yaml")
23
 
24
  # Get datasets
25
  datasets = get_datasets(config)
 
14
  from src.lm.memdlm.diffusion_module import MembraneDiffusion
15
  from src.lm.memdlm.dataloader import MembraneDataModule, get_datasets
16
  from src.utils.model_utils import apply_rdm_freezing
17
+ from src.utils.config_utils import load_config
18
 
19
+ wandb.login()
20
 
21
 
22
  # Load yaml config
23
+ config = load_config("lm.yaml")
24
 
25
  # Get datasets
26
  datasets = get_datasets(config)
src/sampling/{guided_generator.py → desolubilize_generator.py} RENAMED
@@ -1,6 +1,6 @@
1
  #!/usr/bin/env python3
2
 
3
- import sys
4
  import os
5
  import torch
6
  import pandas as pd
@@ -9,20 +9,30 @@ from datetime import datetime
9
  from omegaconf import OmegaConf
10
  from transformers import AutoTokenizer, AutoModelForMaskedLM
11
 
12
- from src.lm.memdlm.diffusion_module import MembraneFlow
13
- from src.utils.model_utils import _print
14
- from src.sampling.guided_sampler import GuidedSampler
15
  from src.utils.generate_utils import (
16
  mask_for_scaffold,
17
  calc_blosum_score,
18
- calc_ppl
 
19
  )
20
 
21
- config = OmegaConf.load("/home/a03-sgoel/MeMDLM_v2/src/configs/guidance.yaml")
 
 
 
 
 
22
 
23
- os.chdir(f'/home/a03-sgoel/MeMDLM_v2/results/infilling/guided/{config.lm.ft_evoflow}/test_set/')
24
  todays_date = datetime.today().strftime('%Y-%m-%d')
25
- csv_save_path = f'./{todays_date}_boltzmann-soft_new_clf_data_cleaned/'
 
 
 
 
 
26
  try: os.makedirs(csv_save_path, exist_ok=False)
27
  except FileExistsError: pass
28
 
@@ -33,25 +43,28 @@ def main():
33
  tokenizer = AutoTokenizer.from_pretrained(config.lm.pretrained_esm)
34
  esm_model = AutoModelForMaskedLM.from_pretrained(config.lm.pretrained_esm).eval().to(device)
35
 
36
- diffusion = MembraneFlow(config).to(device)
37
- state_dict = diffusion.get_state_dict(f"/home/a03-sgoel/MeMDLM_v2/checkpoints/{config.lm.ft_evoflow}/best_model.ckpt")
38
  diffusion.load_state_dict(state_dict)
39
  diffusion.eval().to(device)
40
 
41
- sampler = GuidedSampler(config, esm_model, tokenizer, diffusion, device)
42
 
43
- df = pd.read_csv('/home/a03-sgoel/MeMDLM_v2/data/classifier/test.csv')
 
44
  sequences = df['Sequence'].tolist()
45
 
46
- gen_seqs, ppls, blosums = [], [], []
47
 
48
 
49
- for seq in tqdm(sequences, desc='Infilling Sequences'):
50
  masked_seq = mask_for_scaffold(seq, generate_type='uppercase', mask_token='<mask>')
51
  tokens = tokenizer(masked_seq, return_tensors='pt')
52
  input_ids, attn_masks = tokens['input_ids'].to(device), tokens['attention_mask'].to(device)
53
-
54
- soluble_idxs = [i for i in range(len(seq)) if seq[i].isupper()]
 
 
55
  infilled_tokens = sampler.optimize_sequence(
56
  input_ids=input_ids,
57
  attn_masks=attn_masks,
@@ -59,32 +72,49 @@ def main():
59
  )
60
  infilled_seq = tokenizer.decode(infilled_tokens).replace(" ", "")[5:-5]
61
 
62
- bl = calc_blosum_score(seq.upper(), infilled_seq, soluble_idxs)
 
 
 
 
63
  try:
64
  ppl = calc_ppl(esm_model, tokenizer, infilled_seq, [i for i in range(len(seq))], model_type='esm')
65
  except:
66
  ppl = float('inf')
67
 
 
 
 
 
 
68
  gen_seqs.append(infilled_seq)
69
  ppls.append(ppl)
70
  blosums.append(bl)
 
 
 
71
 
72
  _print(seq)
73
  _print(infilled_seq)
74
  _print(ppl)
75
  _print(bl)
 
 
 
76
  _print('\n')
77
 
78
 
79
  df['MeMDLM Sequence'] = gen_seqs
80
  df['MeMDLM PPL'] = ppls
81
  df['MeMDLM BLOSUM'] = blosums
 
 
 
82
 
83
  _print(df)
84
- df.to_csv(f'./{csv_save_path}/t=0.7_new-data-cleaned_infilled_seqs.csv', index=False)
85
 
86
 
87
 
88
  if __name__ == "__main__":
89
  main()
90
-
 
1
  #!/usr/bin/env python3
2
 
3
+
4
  import os
5
  import torch
6
  import pandas as pd
 
9
  from omegaconf import OmegaConf
10
  from transformers import AutoTokenizer, AutoModelForMaskedLM
11
 
12
+ from src.lm.memdlm.diffusion_module import MembraneDiffusion
13
+ from src.sampling.pet_sampler import PETSampler
 
14
  from src.utils.generate_utils import (
15
  mask_for_scaffold,
16
  calc_blosum_score,
17
+ calc_ppl,
18
+ calc_tm_enrich
19
  )
20
 
21
+ from src.utils.model_utils import _print
22
+ from src.utils.config_utils import load_config, repo_path
23
+
24
+
25
+ config = load_config("desolubilize.yaml")
26
+ task = config.guidance.task
27
 
28
+ results_root = repo_path("results", "heme", config.lm.ft_evoflow)
29
  todays_date = datetime.today().strftime('%Y-%m-%d')
30
+
31
+ if config.guidance.prior == 'boltzmann':
32
+ csv_save_path = results_root / task / todays_date / f"{config.guidance.prior}-t={config.guidance.boltzmann_t}_p={config.guidance.top_p}_tau={config.guidance.sampling_t}"
33
+ elif config.guidance.prior == 'lm_probs':
34
+ csv_save_path = results_root / task / todays_date / f"{config.guidance.prior}_p={config.guidance.top_p}_tau={config.guidance.sampling_t}"
35
+
36
  try: os.makedirs(csv_save_path, exist_ok=False)
37
  except FileExistsError: pass
38
 
 
43
  tokenizer = AutoTokenizer.from_pretrained(config.lm.pretrained_esm)
44
  esm_model = AutoModelForMaskedLM.from_pretrained(config.lm.pretrained_esm).eval().to(device)
45
 
46
+ diffusion = MembraneDiffusion(config).to(device)
47
+ state_dict = diffusion.get_state_dict(str(repo_path("checkpoints", config.lm.ft_evoflow, "best_model.ckpt")))
48
  diffusion.load_state_dict(state_dict)
49
  diffusion.eval().to(device)
50
 
51
+ sampler = PETSampler(config, esm_model, tokenizer, diffusion, device)
52
 
53
+ # Update this path to your input CSV of scaffold sequences (uppercase = TM, lowercase = soluble).
54
+ df = pd.read_csv(str(repo_path("results", "heme", "4d2.csv")))
55
  sequences = df['Sequence'].tolist()
56
 
57
+ gen_seqs, ppls, blosums, og_tms, gen_tms, delta_tms = [], [], [], [], [], []
58
 
59
 
60
+ for seq in tqdm(sequences, desc='Desolubilizing Sequences'):
61
  masked_seq = mask_for_scaffold(seq, generate_type='uppercase', mask_token='<mask>')
62
  tokens = tokenizer(masked_seq, return_tensors='pt')
63
  input_ids, attn_masks = tokens['input_ids'].to(device), tokens['attention_mask'].to(device)
64
+
65
+ tm_idxs = [i for i in range(len(seq)) if seq[i].isupper()]
66
+ soluble_idxs = [i + 1 for i in range(len(seq)) if seq[i].islower()]
67
+
68
  infilled_tokens = sampler.optimize_sequence(
69
  input_ids=input_ids,
70
  attn_masks=attn_masks,
 
72
  )
73
  infilled_seq = tokenizer.decode(infilled_tokens).replace(" ", "")[5:-5]
74
 
75
+ try:
76
+ bl = calc_blosum_score(seq.upper(), infilled_seq, tm_idxs)
77
+ except:
78
+ bl = float('inf')
79
+
80
  try:
81
  ppl = calc_ppl(esm_model, tokenizer, infilled_seq, [i for i in range(len(seq))], model_type='esm')
82
  except:
83
  ppl = float('inf')
84
 
85
+ try:
86
+ og_tm, gen_tm, delta_tm = calc_tm_enrich(seq.upper(), infilled_seq, tm_idxs)
87
+ except:
88
+ og_tm, gen_tm, delta_tm = float('inf'), float('inf'), float('inf')
89
+
90
  gen_seqs.append(infilled_seq)
91
  ppls.append(ppl)
92
  blosums.append(bl)
93
+ og_tms.append(og_tm)
94
+ gen_tms.append(gen_tm)
95
+ delta_tms.append(delta_tm)
96
 
97
  _print(seq)
98
  _print(infilled_seq)
99
  _print(ppl)
100
  _print(bl)
101
+ _print(og_tm)
102
+ _print(gen_tm)
103
+ _print(delta_tm)
104
  _print('\n')
105
 
106
 
107
  df['MeMDLM Sequence'] = gen_seqs
108
  df['MeMDLM PPL'] = ppls
109
  df['MeMDLM BLOSUM'] = blosums
110
+ df['OG TM Enrichment'] = og_tms
111
+ df['MeMDLM TM Enrichment'] = gen_tms
112
+ df['Delta TM Enrichment'] = delta_tms
113
 
114
  _print(df)
115
+ df.to_csv(csv_save_path / "infilled_seqs.csv", index=False)
116
 
117
 
118
 
119
  if __name__ == "__main__":
120
  main()
 
src/sampling/multipass_generator.py ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+
3
+ import sys
4
+ import os
5
+
6
+ import random
7
+ import torch
8
+ import pandas as pd
9
+ import numpy as np
10
+
11
+ from tqdm import tqdm
12
+ from collections import Counter
13
+ from omegaconf import OmegaConf
14
+ from datetime import datetime
15
+ from src.utils.generate_utils import mask_for_de_novo
16
+ from transformers import AutoTokenizer, AutoModelForMaskedLM
17
+
18
+ from src.lm.memdlm.diffusion_module import MembraneDiffusion
19
+ from src.sampling.multipass_sampler import MultipassSampler
20
+
21
+ from src.utils.generate_utils import calc_ppl
22
+ from src.utils.model_utils import _print
23
+ from src.utils.config_utils import load_config, repo_path
24
+
25
+
26
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
27
+ config = load_config("multipass.yaml")
28
+
29
+ date = datetime.now().strftime("%Y-%m-%d")
30
+
31
+
32
+
33
+
34
+ def main():
35
+ csv_save_path = repo_path(
36
+ 'results', 'multipass', config.wandb.name, date,
37
+ f"lamb={config.guidance.reg_strength}_tau={config.guidance.sampling_temperature}"
38
+ )
39
+
40
+ try: os.makedirs(csv_save_path, exist_ok=False)
41
+ except FileExistsError: pass
42
+
43
+ tokenizer = AutoTokenizer.from_pretrained(config.lm.pretrained_evoflow)
44
+
45
+ memdlm = MembraneDiffusion(config).to(device)
46
+ state_dict = memdlm.get_state_dict(str(repo_path("checkpoints", config.lm.ft_evoflow, "best_model.ckpt")))
47
+ memdlm.load_state_dict(state_dict)
48
+ memdlm.eval()
49
+
50
+ esm_pth = config.lm.pretrained_esm
51
+ esm_model = AutoModelForMaskedLM.from_pretrained(esm_pth).to(device)
52
+ esm_model.eval()
53
+
54
+ generator = MultipassSampler(config, device, memdlm, tokenizer)
55
+
56
+ seq_lengths = [x for x in range(60, 161) for _ in range(3)]
57
+ #seq_lengths = random.sample([x for x in range(120, 161)], 15)
58
+
59
+ generation_results = []
60
+ for seq_len in tqdm(seq_lengths, desc=f"Generating sequences: "):
61
+ seq_res = []
62
+
63
+ masked_seq = mask_for_de_novo(seq_len) # Sequence of all <mask> tokens
64
+ tokens = tokenizer(masked_seq, return_tensors='pt')
65
+
66
+ gen_seq = ""
67
+ attempts = 0
68
+
69
+ while len(gen_seq) != seq_len and attempts < 3:
70
+ gen_seq, pred_tm_segs = generator.sample_guidance(
71
+ tokens,
72
+ config.guidance.guide_steps,
73
+ config.guidance.diffusion_steps
74
+ )
75
+ attempts += 1
76
+
77
+ if len(gen_seq) != seq_len:
78
+ esm_ppl, memdlm_ppl = None, None
79
+ else:
80
+ esm_ppl = calc_ppl(esm_model, tokenizer, gen_seq, [i for i in range(len(gen_seq))], model_type='esm')
81
+ memdlm_ppl = calc_ppl(memdlm, tokenizer, gen_seq, [i for i in range(len(gen_seq))], model_type='diffusion')
82
+
83
+ _print(f'seq: {gen_seq}')
84
+ _print(f'pred_tm_segs: {pred_tm_segs}')
85
+ _print(f"ESM PPL: {esm_ppl}")
86
+ _print(f"MeMDLM PPL: {memdlm_ppl}")
87
+ _print('\n')
88
+
89
+ seq_res.append(gen_seq)
90
+ seq_res.append(esm_ppl)
91
+ seq_res.append(memdlm_ppl)
92
+ seq_res.append(pred_tm_segs)
93
+ generation_results.append(seq_res)
94
+
95
+ df = pd.DataFrame(generation_results, columns=['Generated Sequence', 'ESM PPL', 'MeMDLM PPL', 'Pred TM Segments'])
96
+ df.to_csv(str(csv_save_path / "seqs_with_ppl.csv"), index=False)
97
+
98
+
99
+ if __name__ == "__main__":
100
+ main()
src/sampling/multipass_sampler.py ADDED
@@ -0,0 +1,166 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import torch
3
+ from torch import nn
4
+ import torch.nn.functional as F
5
+
6
+ from src.guidance.multipass.multipass_module import MultipassClassifier
7
+ from src.utils.model_utils import _print
8
+ from src.utils.config_utils import repo_path
9
+
10
+
11
+
12
+ class MultipassSampler:
13
+ def __init__(self, config, device, mdlm, tokenizer):
14
+ self.config = config
15
+ self.device = device
16
+
17
+ self.diffusion = mdlm
18
+ self.memdlm_lm = self.diffusion.model.lm_head
19
+ self.tokenizer = self.diffusion.tokenizer
20
+
21
+ ckpt_path = str(repo_path("checkpoints", config.wandb.name, "best_model.ckpt"))
22
+ self.classifier_model = MultipassClassifier(config, self.diffusion).eval().to(self.device)
23
+ state_dict = self.classifier_model.get_state_dict(ckpt_path)
24
+ self.classifier_model.load_state_dict(state_dict)
25
+
26
+ self.SPECIAL_TOKEN_IDS = {0, 1, 2, 3, 29}
27
+
28
+
29
+ def stochastic_sample_from_categorical(self, logits, temperature, noise_scale=1.0):
30
+ """
31
+ Sample from a categorical distribution with optional temperature scaling and Gumbel noise.
32
+ Returns the sampled tokens and their log-probabilities (used as confidence scores).
33
+ """
34
+ logits = logits.double()
35
+ if temperature != 0:
36
+ gumbel_noise = -torch.log(-torch.log(torch.rand_like(logits) + 1e-8) + 1e-8)
37
+ logits = (logits / temperature) + (noise_scale * gumbel_noise)
38
+ scores, tokens = logits.log_softmax(dim=-1).max(dim=-1)
39
+ return tokens, scores
40
+
41
+
42
+ def topk_lowest_masking(self, scores, cutoff_len):
43
+ """
44
+ scores: [b, n]; cutoff_len: [b, 1]
45
+ Returns a [b, n] bool mask, True at the cutoff_len lowest-scoring positions.
46
+ """
47
+ sorted_index = scores.sort(-1)[0]
48
+ cutoff = sorted_index.gather(dim=-1, index=cutoff_len)
49
+ return scores < cutoff
50
+
51
+
52
+ def classifier_score(self, hidden_states, attention_mask):
53
+ return self.classifier_model(x_t=None, attn_mask=attention_mask, with_hidden=True, embeds=hidden_states)
54
+
55
+
56
+ def guidance_loss(self, og_hidden, og_logits, attention_mask, delta):
57
+ """
58
+ Implementation of explore-exploi guidance as in LaMBO-2 (https://arxiv.org/pdf/2305.20009).
59
+ Disregarding use of saliency map as we have one score per sequence.
60
+ """
61
+ lamb = self.config.guidance.reg_strength
62
+
63
+ h_current = og_hidden + delta
64
+ new_logits = self.memdlm_lm(h_current)
65
+ score = self.classifier_score(h_current, attention_mask)
66
+
67
+ kl = F.kl_div(
68
+ F.log_softmax(new_logits, dim=-1),
69
+ F.softmax(og_logits, dim=-1),
70
+ reduction='sum'
71
+ )
72
+
73
+ loss = lamb * kl - score.sum()
74
+ return loss
75
+
76
+
77
+ def optimized_sampling(self, og_logits, og_hidden, attention_mask, n_steps):
78
+ """
79
+ At each diffusion timestep, take n_steps of gradient-based guidance on the hidden states
80
+ """
81
+ eta = self.config.guidance.step_size
82
+ og_logits = og_logits.detach()
83
+
84
+ delta = nn.Parameter(torch.zeros_like(og_hidden), requires_grad=True)
85
+ optimizer = torch.optim.Adagrad([delta], lr=eta)
86
+
87
+ with torch.enable_grad():
88
+ for _ in range(n_steps):
89
+ optimizer.zero_grad()
90
+ loss = self.guidance_loss(og_hidden, og_logits, attention_mask, delta)
91
+ loss.backward()
92
+ optimizer.step()
93
+
94
+ h_new = og_hidden + delta.data
95
+ new_logits = self.memdlm_lm(h_new)
96
+ return new_logits, h_new
97
+
98
+
99
+ def guided_logits(self, xt, attention_mask, guide_steps):
100
+ """
101
+ Run the diffusion model on the current sequence and apply classifier guidance to the
102
+ hidden states, returning the guided LM-head logits used for scoring/sampling this step.
103
+ """
104
+ with torch.no_grad():
105
+ hidden_states = self.diffusion(xt, attention_mask, return_hidden=True)
106
+ hidden_states = hidden_states.unsqueeze(0) if hidden_states.ndim != 3 else hidden_states
107
+ base_logits = self.memdlm_lm(hidden_states)
108
+
109
+ logits, _ = self.optimized_sampling(base_logits, hidden_states, attention_mask, guide_steps)
110
+ return logits
111
+
112
+
113
+ def sample_guidance(self, tokens, guide_steps, diffusion_steps,
114
+ kappa_fn=lambda t: t, eta=1.0, alpha=1.0):
115
+ """
116
+ Confidence-based progressive-unmasking denoising (as in the unconditional sampler),
117
+ but each step uses classifier-guided logits instead of the raw model logits.
118
+
119
+ Args:
120
+ kappa_fn: unmasking schedule, kappa(t) in [0,1]; fraction of positions committed by step t.
121
+ eta: re-masking scale applied to already-unmasked candidate positions.
122
+ alpha: blends token log-prob (alpha=1) and negative entropy (alpha=0) in the score.
123
+ """
124
+ tau = self.config.guidance.sampling_temperature
125
+
126
+ xt = tokens['input_ids'].to(self.device)
127
+ attention_mask = torch.ones_like(xt).to(self.device)
128
+
129
+ dt = 1 / diffusion_steps
130
+ fix_mask = (xt != self.tokenizer.mask_token_id) # would be none for de novo setting
131
+
132
+ x0 = xt
133
+ for i in range(1, diffusion_steps + 1):
134
+ kappa_t = kappa_fn(i * dt)
135
+
136
+ logits = self.guided_logits(xt, attention_mask, guide_steps)
137
+
138
+ with torch.no_grad():
139
+ last_mask = xt == self.tokenizer.mask_token_id
140
+ unmask_t = ~last_mask & ~fix_mask
141
+
142
+ x0, logp = self.stochastic_sample_from_categorical(logits, tau)
143
+
144
+ entropy = torch.distributions.Categorical(logits=logits).entropy()
145
+ score = alpha * logp + (1 - alpha) * -entropy
146
+ score = score.masked_fill(fix_mask, float('inf')) # never remask fixed tokens
147
+ score[unmask_t] = score[unmask_t] * eta
148
+
149
+ num_to_mask = ((~fix_mask).sum(1, keepdim=True).float() * (1 - kappa_t)).long()
150
+ lowest_k_mask = self.topk_lowest_masking(score, num_to_mask)
151
+
152
+ xt[lowest_k_mask] = self.tokenizer.mask_token_id # remask lowest-confidence
153
+ mask_2_x0 = last_mask & ~lowest_k_mask # commit newly-confident positions
154
+ xt[mask_2_x0] = x0[mask_2_x0]
155
+
156
+ xt[xt == self.tokenizer.mask_token_id] = x0[xt == self.tokenizer.mask_token_id] # commit remaining
157
+
158
+ seq = xt.squeeze()[1:-1]
159
+ optim_tokens = self.tokenizer.decode(seq, skip_special_tokens=True).replace(" ", "")
160
+
161
+ with torch.no_grad():
162
+ final_hidden = self.diffusion(xt, attention_mask, return_hidden=True)
163
+ final_hidden = final_hidden.unsqueeze(0) if final_hidden.ndim != 3 else final_hidden
164
+ final_preds = self.classifier_score(final_hidden, attention_mask)
165
+
166
+ return optim_tokens, torch.sigmoid(final_preds).item()
src/sampling/olig_generator.py ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+
3
+ import sys
4
+ import os
5
+
6
+ import random
7
+ import torch
8
+ import pandas as pd
9
+ import numpy as np
10
+
11
+ from tqdm import tqdm
12
+ from datetime import datetime
13
+ from transformers import AutoTokenizer, AutoModelForMaskedLM
14
+
15
+ from src.lm.memdlm.diffusion_module import MembraneDiffusion
16
+ from src.sampling.olig_sampler import NOSSampler
17
+
18
+ from src.utils.generate_utils import calc_blosum_score, calc_ppl
19
+ from src.utils.model_utils import _print
20
+ from src.utils.config_utils import load_config, repo_path
21
+
22
+
23
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
24
+ config = load_config("oligo.yaml")
25
+
26
+ date = datetime.now().strftime("%Y-%m-%d")
27
+
28
+
29
+
30
+
31
+ def main():
32
+ csv_save_path = repo_path('results', 'oligo', config.wandb.name, date)
33
+
34
+ try: os.makedirs(csv_save_path, exist_ok=False)
35
+ except FileExistsError: pass
36
+
37
+ tokenizer = AutoTokenizer.from_pretrained(config.lm.pretrained_evoflow)
38
+
39
+ memdlm = MembraneDiffusion(config).to(device)
40
+ state_dict = memdlm.get_state_dict(str(repo_path("checkpoints", config.lm.ft_evoflow, "best_model.ckpt")))
41
+ memdlm.load_state_dict(state_dict)
42
+ memdlm.eval()
43
+
44
+ esm_pth = config.lm.pretrained_esm
45
+ esm_model = AutoModelForMaskedLM.from_pretrained(esm_pth).to(device)
46
+ esm_model.eval()
47
+
48
+ generator = NOSSampler(config, device, memdlm, esm_model, tokenizer)
49
+
50
+ # Determine length from positive controls
51
+ df = pd.read_csv(str(repo_path('data', 'olig_clf', 'test.csv')))
52
+ seqs = df['Sequence'].tolist()
53
+
54
+
55
+ generation_results = []
56
+ for seq in tqdm(seqs, desc=f"Generating sequences: "):
57
+ seq_res = []
58
+
59
+ seq_len = len(seq)
60
+ tokens = tokenizer(seq, return_tensors='pt')
61
+
62
+ gen_seq = ""
63
+ attempts = 0
64
+
65
+ while len(gen_seq) != seq_len and attempts < 3:
66
+ gen_seq, og_pred, final_pred = generator.sample_guidance(
67
+ tokens,
68
+ config.olig_guidance.guide_steps,
69
+ config.olig_guidance.diffusion_steps
70
+ )
71
+ attempts += 1
72
+
73
+ if len(gen_seq) != seq_len:
74
+ esm_ppl, memdlm_ppl = None, None
75
+ else:
76
+ esm_ppl = calc_ppl(esm_model, tokenizer, gen_seq, [i for i in range(len(gen_seq))], model_type='esm')
77
+ memdlm_ppl = calc_ppl(memdlm, tokenizer, gen_seq, [i for i in range(len(gen_seq))], model_type='diffusion')
78
+ blosum = calc_blosum_score(seq, gen_seq, indices=[i for i in range(len(gen_seq))])
79
+
80
+ seq_res.append(seq)
81
+ seq_res.append(gen_seq)
82
+ seq_res.append(og_pred)
83
+ seq_res.append(final_pred)
84
+ seq_res.append(final_pred - og_pred)
85
+ seq_res.append(esm_ppl)
86
+ seq_res.append(memdlm_ppl)
87
+ seq_res.append(blosum)
88
+ generation_results.append(seq_res)
89
+
90
+ df = pd.DataFrame(generation_results, columns=['Original Sequence', 'Generated Sequence', 'OG Olig Value', 'New Olig Value', 'Olig Increase', 'ESM PPL', 'MeMDLM PPL', 'MemDLM Blosum'])
91
+ df.to_csv(str(csv_save_path / "seqs_with_ppl.csv"), index=False)
92
+
93
+
94
+ if __name__ == "__main__":
95
+ main()
src/sampling/olig_sampler.py ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import torch
3
+ from torch import nn
4
+ import torch.nn.functional as F
5
+ from transformers import AutoModelForMaskedLM, AutoTokenizer
6
+
7
+ from src.guidance.oligo.oligo_module import OligomerClassifier
8
+ from src.sampling.unconditional_sampler import UnconditionalSampler
9
+ from src.lm.memdlm.diffusion_module import MembraneDiffusion
10
+ from src.utils.model_utils import _print
11
+ from src.utils.config_utils import repo_path
12
+
13
+
14
+
15
+ class NOSSampler:
16
+ def __init__(self, config, device, mdlm, esm, tokenizer):
17
+ self.config = config
18
+ self.device = device
19
+
20
+ self.diffusion = mdlm
21
+ self.memdlm_lm = self.diffusion.model.lm_head
22
+ self.tokenizer = self.diffusion.tokenizer
23
+
24
+ ckpt_path = str(repo_path("checkpoints", config.wandb.name, "best_model.ckpt"))
25
+ self.classifier_model = OligomerClassifier(config).eval().to(self.device)
26
+ state_dict = self.classifier_model.get_state_dict(ckpt_path)
27
+ self.classifier_model.load_state_dict(state_dict)
28
+
29
+ self.SPECIAL_TOKEN_IDS = {0, 1, 2, 3, 29}
30
+
31
+
32
+ def sample_from_categorical(self, logits):
33
+ gumbel_noise = -torch.log(-torch.log(torch.rand_like(logits) + 1e-8) + 1e-8)
34
+ logits += gumbel_noise
35
+ log_probs = F.log_softmax(logits, dim=-1)
36
+ _, tokens = log_probs.max(dim=-1)
37
+ return tokens, log_probs
38
+
39
+
40
+ def get_clf_preds(self, hidden_states, attention_mask):
41
+ """Obtain diffusion model logits and classifier predictions from hidden states"""
42
+ batch = {"embeds": hidden_states.squeeze(), "attention_mask": attention_mask.squeeze()}
43
+ preds = self.classifier_model(batch)
44
+ return self.memdlm_lm(hidden_states), preds
45
+
46
+
47
+ def embed_and_run_clf(self, input_ids, attention_masks):
48
+ """Get sequence embeddings and classifier model predictions"""
49
+ outputs = self.esm(input_ids=input_ids, attention_mask=attention_masks)
50
+ sequence_embeddings = outputs.last_hidden_state.squeeze(0)
51
+
52
+ batch = {"embeds": sequence_embeddings, "attention_mask": attention_masks}
53
+ preds = self.classifier_model(batch)
54
+
55
+ return {
56
+ "clf_preds": preds.requires_grad_(True), # Enable gradients for backprop
57
+ "embeds": sequence_embeddings
58
+ }
59
+
60
+
61
+ def compute_saliency(self, embeddings, attention_masks):
62
+ """
63
+ Compute a saliency map using gradients as defined in LaMBO-2 (https://arxiv.org/pdf/2305.20009)
64
+ """
65
+ embeddings = embeddings.detach().requires_grad_(True)
66
+ batch = {
67
+ "embeds": embeddings.squeeze(),
68
+ "attention_mask": attention_masks.squeeze()
69
+ }
70
+ out = self.classifier_model(batch).sum()
71
+ out.backward(retain_graph=True)
72
+
73
+ # Creating the saliency map (Eq.5 in LaMBO-2 paper)
74
+ grads = embeddings.grad.abs().sum(dim=-1) # Summation across hidden dim. Abs value for mangitude only
75
+ saliency = grads.pow(1.0 / self.config.olig_guidance.temperature).clamp(min=self.config.olig_guidance.eps)
76
+ return saliency.squeeze()
77
+
78
+
79
+ def determine_edit_positions(self, saliency_map, preds):
80
+ """
81
+ Create a one-hot mask that indicates the top-k low-value residue positions.
82
+ We defind low-value positions as those with high saliency scores and
83
+ thus a high edit probability.
84
+ """
85
+ probabilities = saliency_map.masked_fill(preds >= self.config.olig_guidance.residue_thresh, 0.0) # exclude high-value tokens
86
+ probabilities = probabilities / probabilities.sum()
87
+
88
+ nonzero = probabilities.count_nonzero().item()
89
+ seq_len = probabilities.shape[0]
90
+ topk = max(1, int(seq_len * self.config.olig_guidance.topk_frac))
91
+ _, topk_edit_pos = torch.topk(probabilities, min(topk, nonzero))
92
+
93
+ mask = torch.zeros_like(probabilities).scatter(0, topk_edit_pos, torch.ones_like(probabilities))
94
+ return mask.unsqueeze(-1)
95
+
96
+
97
+ def update_logits(self, og_hidden, og_logits, hidden_state_mask, attention_mask, optimizer, delta):
98
+ """
99
+ Shift logits distribution towards only high-quality residues by applying the explore-exploit loss.
100
+ """
101
+ lamb = self.config.olig_guidance.reg_strength
102
+
103
+ h_current = og_hidden + hidden_state_mask * delta
104
+ new_logits, v_ht_prime = self.get_clf_preds(h_current, attention_mask)
105
+
106
+ kl = F.kl_div(
107
+ F.log_softmax(new_logits, dim=-1),
108
+ F.softmax(og_logits, dim=-1),
109
+ reduction='sum'
110
+ )
111
+
112
+ loss = lamb * kl - v_ht_prime.sum()
113
+ loss.backward(retain_graph=True)
114
+ optimizer.step()
115
+ optimizer.zero_grad()
116
+
117
+ return delta
118
+
119
+
120
+ def optimized_sampling(self, og_logits, og_hidden, attention_mask, n_steps):
121
+ """Main entry point to optimize a generated sequence."""
122
+ eta = self.config.olig_guidance.step_size
123
+
124
+ # Calculate initial clf predictions
125
+ batch = {"embeds": og_hidden, "attention_mask": attention_mask}
126
+ preds = self.classifier_model(batch)
127
+
128
+ delta = nn.Parameter(torch.zeros_like(og_hidden), requires_grad=True)
129
+ optimizer = torch.optim.Adagrad([delta], lr=eta)
130
+ optimizer.zero_grad()
131
+
132
+ with torch.enable_grad():
133
+ for n in range(n_steps):
134
+
135
+ # Compute saliency map and edit positions using updated hidden states
136
+ saliency_map = self.compute_saliency(og_hidden + delta.data, attention_mask)
137
+
138
+ # One-hot mask that encodes the saliency-selected edit positions
139
+ mask = self.determine_edit_positions(saliency_map, preds)
140
+
141
+ # Optimize and generate the new sequence
142
+ delta = self.update_logits(
143
+ og_hidden=og_hidden,
144
+ og_logits=og_logits,
145
+ hidden_state_mask=mask,
146
+ attention_mask=attention_mask,
147
+ optimizer=optimizer,
148
+ delta=delta
149
+ )
150
+
151
+ h_new = og_hidden + delta.data
152
+ new_logits, _ = self.get_clf_preds(h_new, attention_mask)
153
+
154
+ return new_logits, h_new
155
+
156
+
157
+ def sample_guidance(self, tokens, guide_steps, diffusion_steps):
158
+ x = tokens['input_ids'].to(self.device)
159
+ attention_mask = tokens['attention_mask'].to(self.device)
160
+
161
+ og_pred = self.classifier_model({"input_ids": x, "attention_mask": attention_mask,})
162
+
163
+ for _ in range(diffusion_steps):
164
+ hidden_states = self.diffusion(x, attention_mask, return_hidden=True)
165
+ logits = self.memdlm_lm(hidden_states)
166
+ hidden_states = hidden_states.unsqueeze(0) if hidden_states.ndim != 3 else hidden_states
167
+
168
+ logits, hidden_states = self.optimized_sampling(logits, hidden_states, attention_mask, guide_steps)
169
+ logits = self.memdlm_lm(hidden_states)
170
+ x, _ = self.sample_from_categorical(logits)
171
+
172
+ seq = x.squeeze()
173
+ # _print(seq)
174
+ # start = 1 if seq[0].item() in self.SPECIAL_TOKEN_IDS else 0
175
+ # end = -1 if seq[-1].item() in self.SPECIAL_TOKEN_IDS else len(seq)
176
+ # seq = seq[start:end]
177
+ # _print(seq)
178
+
179
+ _print(seq)
180
+ seq = seq[1:-1]
181
+ _print(seq)
182
+
183
+
184
+ optim_tokens = self.tokenizer.decode(seq, skip_special_tokens=True).replace(" ", "")
185
+ final_pred = self.classifier_model({"embeds": hidden_states, "attention_mask": attention_mask,})
186
+
187
+ return optim_tokens, F.sigmoid(og_pred).item(), F.sigmoid(final_pred).item()
src/sampling/pet_generator.py ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+
3
+
4
+ import os
5
+ import torch
6
+ import pandas as pd
7
+ from tqdm import tqdm
8
+ from datetime import datetime
9
+ from omegaconf import OmegaConf
10
+ from transformers import AutoTokenizer, AutoModelForMaskedLM
11
+
12
+ from src.lm.memdlm.diffusion_module import MembraneDiffusion
13
+ from src.sampling.pet_sampler import PETSampler
14
+ from src.utils.generate_utils import (
15
+ mask_for_scaffold,
16
+ calc_blosum_score,
17
+ calc_ppl,
18
+ calc_tm_enrich
19
+ )
20
+
21
+ from src.utils.model_utils import _print
22
+ from src.utils.config_utils import load_config, repo_path
23
+
24
+
25
+ config = load_config("solubility.yaml")
26
+ task = config.guidance.get("task", "solubilize")
27
+
28
+ results_root = repo_path("results", "heme", config.lm.ft_evoflow)
29
+ todays_date = datetime.today().strftime('%Y-%m-%d')
30
+
31
+ if config.guidance.prior == 'boltzmann':
32
+ csv_save_path = results_root / task / todays_date / f"{config.guidance.prior}-t={config.guidance.boltzmann_t}_p={config.guidance.top_p}_tau={config.guidance.sampling_t}"
33
+ elif config.guidance.prior == 'lm_probs':
34
+ csv_save_path = results_root / task / todays_date / f"{config.guidance.prior}_p={config.guidance.top_p}_tau={config.guidance.sampling_t}"
35
+
36
+ try: os.makedirs(csv_save_path, exist_ok=False)
37
+ except FileExistsError: pass
38
+
39
+
40
+ def main():
41
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
42
+
43
+ tokenizer = AutoTokenizer.from_pretrained(config.lm.pretrained_esm)
44
+ esm_model = AutoModelForMaskedLM.from_pretrained(config.lm.pretrained_esm).eval().to(device)
45
+
46
+ diffusion = MembraneDiffusion(config).to(device)
47
+ state_dict = diffusion.get_state_dict(str(repo_path("checkpoints", config.lm.ft_evoflow, "best_model.ckpt")))
48
+ diffusion.load_state_dict(state_dict)
49
+ diffusion.eval().to(device)
50
+
51
+ sampler = PETSampler(config, esm_model, tokenizer, diffusion, device)
52
+
53
+ # Update this path to your input CSV of scaffold sequences (uppercase = soluble, lowercase = TM).
54
+ df = pd.read_csv(str(repo_path("results", "heme", "cybtx.csv")))
55
+ sequences = df['Sequence'].tolist()
56
+
57
+ gen_seqs, ppls, blosums, og_tms, gen_tms, delta_tms = [], [], [], [], [], []
58
+
59
+
60
+ for seq in tqdm(sequences, desc='Infilling Sequences'):
61
+ masked_seq = mask_for_scaffold(seq, generate_type='lowercase', mask_token='<mask>')
62
+ tokens = tokenizer(masked_seq, return_tensors='pt')
63
+ input_ids, attn_masks = tokens['input_ids'].to(device), tokens['attention_mask'].to(device)
64
+
65
+ tm_idxs = [i for i in range(len(seq)) if seq[i].islower()] # keep same for blosum calcs, etc
66
+ soluble_idxs = [i + 1 for i in range(len(seq)) if seq[i].isupper()] # 1 offset for cls token
67
+
68
+ infilled_tokens = sampler.optimize_sequence(
69
+ input_ids=input_ids,
70
+ attn_masks=attn_masks,
71
+ soluble_indices=soluble_idxs,
72
+ )
73
+ infilled_seq = tokenizer.decode(infilled_tokens).replace(" ", "")[5:-5]
74
+
75
+ try:
76
+ bl = calc_blosum_score(seq.upper(), infilled_seq, tm_idxs)
77
+ except:
78
+ bl = float('inf')
79
+
80
+ try:
81
+ ppl = calc_ppl(esm_model, tokenizer, infilled_seq, [i for i in range(len(seq))], model_type='esm')
82
+ except:
83
+ ppl = float('inf')
84
+
85
+ try:
86
+ og_tm, gen_tm, delta_tm = calc_tm_enrich(seq.upper(), infilled_seq, tm_idxs)
87
+ except:
88
+ og_tm, gen_tm, delta_tm = float('inf'), float('inf'), float('inf')
89
+
90
+ gen_seqs.append(infilled_seq)
91
+ ppls.append(ppl)
92
+ blosums.append(bl)
93
+ og_tms.append(og_tm)
94
+ gen_tms.append(gen_tm)
95
+ delta_tms.append(delta_tm)
96
+
97
+ _print(seq)
98
+ _print(infilled_seq)
99
+ _print(ppl)
100
+ _print(bl)
101
+ _print(og_tm)
102
+ _print(gen_tm)
103
+ _print(delta_tm)
104
+ _print('\n')
105
+
106
+
107
+ df['MeMDLM Sequence'] = gen_seqs
108
+ df['MeMDLM PPL'] = ppls
109
+ df['MeMDLM BLOSUM'] = blosums
110
+ df['OG TM Enrichment'] = og_tms
111
+ df['MeMDLM TM Enrichment'] = gen_tms
112
+ df['Delta TM Enrichment'] = delta_tms
113
+
114
+ _print(df)
115
+ df.to_csv(csv_save_path / "infilled_seqs.csv", index=False)
116
+
117
+
118
+
119
+ if __name__ == "__main__":
120
+ main()
121
+
src/sampling/{guided_sampler.py → pet_sampler.py} RENAMED
@@ -4,11 +4,12 @@ import torch
4
  import torch.nn.functional as F
5
 
6
  from src.utils.model_utils import _print
7
- from src.guidance.solubility_module import SolubilityClassifier
 
8
  from src.sampling.unconditional_sampler import UnconditionalSampler
9
 
10
 
11
- class GuidedSampler:
12
  def __init__(self, config, esm_model, tokenizer, diffusion, device):
13
  self.config = config
14
  self.device = device
@@ -18,7 +19,7 @@ class GuidedSampler:
18
  self.tokenizer = tokenizer
19
  self.uncond_generator = UnconditionalSampler(self.tokenizer, self.memdlm)
20
 
21
- ckpt_path = os.path.join(f"/home/a03-sgoel/MeMDLM_v2/checkpoints/{config.wandb.name}/best_model.ckpt")
22
  self.classifier_model = SolubilityClassifier(config)
23
  state_dict = self.classifier_model.get_state_dict(ckpt_path)
24
  self.classifier_model.load_state_dict(state_dict)
@@ -31,7 +32,7 @@ class GuidedSampler:
31
  self.saliency_t = self.config.guidance.saliency_t
32
  self.sampling_t = self.config.guidance.sampling_t
33
  self.boltzmann_t = self.config.guidance.boltzmann_t
34
-
35
 
36
  def embed_sequence(self, input_ids, attention_masks):
37
  with torch.no_grad():
@@ -107,7 +108,13 @@ class GuidedSampler:
107
  bias[other_idxs] = 0.0
108
 
109
  sol_scores = torch.sigmoid(solubility_logits)
110
- token_bias = sol_scores.unsqueeze(-1) * bias
 
 
 
 
 
 
111
 
112
  lm_probs = F.softmax(logits_prior / self.sampling_t, dim=-1)
113
  boltz_weight = torch.exp(token_bias / self.boltzmann_t)
@@ -143,6 +150,10 @@ class GuidedSampler:
143
 
144
  # Initialize a mask to store the editable token positions
145
  edit_mask = torch.ones(seq_len, dtype=torch.bool, device=self.device)
 
 
 
 
146
 
147
  # Check for any provided soluble residues, otherwise use classifier preds
148
  if len(soluble_indices) > 0:
@@ -151,10 +162,12 @@ class GuidedSampler:
151
  solubility_preds = F.sigmoid(solubility_logits)
152
  edit_mask[solubility_preds > 0.5] = False
153
 
154
- # Find additional TM residues
155
- num_conserved = max(1, int(0.1 * edit_mask.sum()))
156
- _, topk_idxs = torch.topk(saliency_map, num_conserved)
157
- edit_mask[topk_idxs] = False
 
 
158
 
159
  edit_idxs = edit_mask.nonzero(as_tuple=True)[0]
160
  return edit_idxs
@@ -207,6 +220,7 @@ class GuidedSampler:
207
  Compute the log probs of the "new" (optimized) token.
208
  """
209
  w = torch.sigmoid(saliency_weight * self.alpha) # Between [0, 1] to ensure valid probs
 
210
  p_lm = torch.exp(logp_lm)
211
  p_prior = torch.exp(logp_prior)
212
  mixed_probs = (1 - w) * p_lm + w * p_prior
@@ -225,8 +239,12 @@ class GuidedSampler:
225
  def optimize_sequence(self, input_ids, attn_masks, soluble_indices):
226
  _print(f'soluble idx: {soluble_indices}')
227
 
 
 
228
  # Initialize token ids, logits, and log probs of sequence
229
  x0, logp_lm, logits_prior = self.denoise_sequence(input_ids, attn_masks)
 
 
230
  _print(f'og tokens: {x0}')
231
  _print(f'og tokens: {x0.shape}')
232
  _print(f'og log probs: {logp_lm.shape}')
@@ -281,11 +299,12 @@ class GuidedSampler:
281
  # Sample new tokens
282
  x0_prime = torch.distributions.Categorical(logits=logp_lm).sample()
283
 
284
- # Check if any soluble residues have been changed
285
- self.check_scaffold(x0, x0_prime, soluble_indices)
286
 
287
- # Preserve the initial sequence scaffold by copying over the soluble tokens
288
- x0_prime[soluble_indices] = x0[soluble_indices]
289
- self.check_scaffold(x0, x0_prime, soluble_indices)
 
290
 
291
  return x0_prime
 
4
  import torch.nn.functional as F
5
 
6
  from src.utils.model_utils import _print
7
+ from src.utils.config_utils import repo_path
8
+ from src.guidance.solubility.solubility_module import SolubilityClassifier
9
  from src.sampling.unconditional_sampler import UnconditionalSampler
10
 
11
 
12
+ class PETSampler:
13
  def __init__(self, config, esm_model, tokenizer, diffusion, device):
14
  self.config = config
15
  self.device = device
 
19
  self.tokenizer = tokenizer
20
  self.uncond_generator = UnconditionalSampler(self.tokenizer, self.memdlm)
21
 
22
+ ckpt_path = str(repo_path("checkpoints", config.wandb.name, "best_model.ckpt"))
23
  self.classifier_model = SolubilityClassifier(config)
24
  state_dict = self.classifier_model.get_state_dict(ckpt_path)
25
  self.classifier_model.load_state_dict(state_dict)
 
32
  self.saliency_t = self.config.guidance.saliency_t
33
  self.sampling_t = self.config.guidance.sampling_t
34
  self.boltzmann_t = self.config.guidance.boltzmann_t
35
+ self.task = self.config.guidance.get("task", "solubilize")
36
 
37
  def embed_sequence(self, input_ids, attention_masks):
38
  with torch.no_grad():
 
108
  bias[other_idxs] = 0.0
109
 
110
  sol_scores = torch.sigmoid(solubility_logits)
111
+ task = self.task
112
+ if task == "desolubilize":
113
+ # At soluble positions, bias toward hydrophobic / TM-like residues.
114
+ token_bias = sol_scores.unsqueeze(-1) * (-bias)
115
+ else:
116
+ # At insoluble positions, bias toward hydrophilic residues.
117
+ token_bias = (1.0 - sol_scores).unsqueeze(-1) * bias
118
 
119
  lm_probs = F.softmax(logits_prior / self.sampling_t, dim=-1)
120
  boltz_weight = torch.exp(token_bias / self.boltzmann_t)
 
150
 
151
  # Initialize a mask to store the editable token positions
152
  edit_mask = torch.ones(seq_len, dtype=torch.bool, device=self.device)
153
+
154
+ # ignore <cls> and <eos> tokens
155
+ edit_mask[0] = False
156
+ edit_mask[-1] = False
157
 
158
  # Check for any provided soluble residues, otherwise use classifier preds
159
  if len(soluble_indices) > 0:
 
162
  solubility_preds = F.sigmoid(solubility_logits)
163
  edit_mask[solubility_preds > 0.5] = False
164
 
165
+ # during solubilization, we also fix the high-saliency TM residues
166
+ # but for desolubilization, skip this as high-saliency positions are the soluble sites we want to edit
167
+ if self.task != "desolubilize":
168
+ num_conserved = max(1, int(0.1 * edit_mask.sum()))
169
+ _, topk_idxs = torch.topk(saliency_map, num_conserved)
170
+ edit_mask[topk_idxs] = False
171
 
172
  edit_idxs = edit_mask.nonzero(as_tuple=True)[0]
173
  return edit_idxs
 
220
  Compute the log probs of the "new" (optimized) token.
221
  """
222
  w = torch.sigmoid(saliency_weight * self.alpha) # Between [0, 1] to ensure valid probs
223
+ _print(f'w: {w}')
224
  p_lm = torch.exp(logp_lm)
225
  p_prior = torch.exp(logp_prior)
226
  mixed_probs = (1 - w) * p_lm + w * p_prior
 
239
  def optimize_sequence(self, input_ids, attn_masks, soluble_indices):
240
  _print(f'soluble idx: {soluble_indices}')
241
 
242
+ scaffold_ids = input_ids.squeeze()
243
+
244
  # Initialize token ids, logits, and log probs of sequence
245
  x0, logp_lm, logits_prior = self.denoise_sequence(input_ids, attn_masks)
246
+ # x0 is resampled from LM logits at all positions, so restore scaffold from input
247
+ x0[soluble_indices] = scaffold_ids[soluble_indices]
248
  _print(f'og tokens: {x0}')
249
  _print(f'og tokens: {x0.shape}')
250
  _print(f'og log probs: {logp_lm.shape}')
 
299
  # Sample new tokens
300
  x0_prime = torch.distributions.Categorical(logits=logp_lm).sample()
301
 
302
+ # Check if any scaffold residues have been changed
303
+ self.check_scaffold(scaffold_ids, x0_prime, soluble_indices)
304
 
305
+ # Preserve scaffold from the _original_ tokenized input, not resampled x0
306
+ # since the categorical sampling might not sample the original scaffold token
307
+ x0_prime[soluble_indices] = scaffold_ids[soluble_indices]
308
+ self.check_scaffold(scaffold_ids, x0_prime, soluble_indices)
309
 
310
  return x0_prime
src/sampling/unconditional_generator.py CHANGED
@@ -14,15 +14,15 @@ from omegaconf import OmegaConf
14
  from datetime import datetime
15
  from transformers import AutoTokenizer, AutoModelForMaskedLM
16
 
17
- from MeMDLM_v2.src.lm.diffusion_module import MembraneFlow
18
  from src.sampling.unconditional_sampler import UnconditionalSampler
19
  from src.utils.generate_utils import mask_for_de_novo, calc_ppl
20
  from src.utils.model_utils import _print
 
21
 
22
 
23
  device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
24
- os.chdir('/home/a03-sgoel/MeMDLM_v2')
25
- config = OmegaConf.load("./src/configs/lm.yaml")
26
 
27
  date = datetime.now().strftime("%Y-%m-%d")
28
 
@@ -42,24 +42,23 @@ def generate_sequence(prior: str, tokenizer, generator, device):
42
 
43
 
44
  def main():
45
- csv_save_path = f'./results/denovo/unconditional/{config.wandb.name}/{date}_tau=3.0_test-set_distribution'
46
 
47
  try: os.makedirs(csv_save_path, exist_ok=False)
48
  except FileExistsError: pass
49
 
50
-
51
  tokenizer = AutoTokenizer.from_pretrained(config.lm.pretrained_evoflow)
52
 
53
- flow = MembraneFlow(config).to(device)
54
- state_dict = flow.get_state_dict(f"./checkpoints/{config.wandb.name}/best_model.ckpt")
55
- flow.load_state_dict(state_dict)
56
- flow.eval()
57
 
58
  esm_pth = config.lm.pretrained_esm
59
  esm_model = AutoModelForMaskedLM.from_pretrained(esm_pth).to(device)
60
  esm_model.eval()
61
 
62
- generator = UnconditionalSampler(tokenizer, flow)
63
 
64
  # # Get 100 random sequence lengths to generate
65
  # seq_lengths = [random.randint(50, 250) for _ in range(5000)]
@@ -69,14 +68,16 @@ def main():
69
  # seq_lengths = [len(seq) for seq in df['Sequence'].tolist() for _ in range(500)] # generate each length 100 times
70
  # _print(seq_lengths)
71
 
72
- # Determine lengths from test set distribution
73
- df = pd.read_csv("./data/test.csv")
74
- seq_lengths = [len(seq) for seq in df['Sequence'].tolist()]
75
- length_counts = Counter(seq_lengths) # {L1: freq, L2: freq, ...}
76
- total = sum(length_counts.values()) # total number of tokens
77
- lengths = np.array(list(length_counts.keys())) # Frequency of each length
78
- probs = np.array([length_counts[l] / total for l in lengths])
79
- seq_lengths = np.random.choice(lengths, size=len(seq_lengths), p=probs)
 
 
80
 
81
  generation_results = []
82
  for seq_len in tqdm(seq_lengths, desc=f"Generating sequences: "):
@@ -91,23 +92,23 @@ def main():
91
  attempts += 1
92
 
93
  if len(gen_seq) != seq_len:
94
- esm_ppl, flow_ppl = None, None
95
  else:
96
  esm_ppl = calc_ppl(esm_model, tokenizer, gen_seq, [i for i in range(len(gen_seq))], model_type='esm')
97
- flow_ppl = calc_ppl(flow, tokenizer, gen_seq, [i for i in range(len(gen_seq))], model_type='flow')
98
 
99
  _print(f'gen seq: {gen_seq}')
100
  _print(f'esm ppl: {esm_ppl}')
101
- _print(f'flow ppl: {flow_ppl}')
102
 
103
  seq_res.append(gen_seq)
104
  seq_res.append(esm_ppl)
105
- seq_res.append(flow_ppl)
106
 
107
  generation_results.append(seq_res)
108
 
109
- df = pd.DataFrame(generation_results, columns=['Generated Sequence', 'ESM PPL', 'Flow PPL'])
110
- df.to_csv(csv_save_path + "/seqs_with_ppl.csv", index=False)
111
 
112
 
113
  if __name__ == "__main__":
 
14
  from datetime import datetime
15
  from transformers import AutoTokenizer, AutoModelForMaskedLM
16
 
17
+ from src.lm.memdlm.diffusion_module import MembraneDiffusion
18
  from src.sampling.unconditional_sampler import UnconditionalSampler
19
  from src.utils.generate_utils import mask_for_de_novo, calc_ppl
20
  from src.utils.model_utils import _print
21
+ from src.utils.config_utils import load_config, repo_path
22
 
23
 
24
  device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
25
+ config = load_config("lm.yaml")
 
26
 
27
  date = datetime.now().strftime("%Y-%m-%d")
28
 
 
42
 
43
 
44
  def main():
45
+ csv_save_path = repo_path('results', 'multipass', config.wandb.name, date)
46
 
47
  try: os.makedirs(csv_save_path, exist_ok=False)
48
  except FileExistsError: pass
49
 
 
50
  tokenizer = AutoTokenizer.from_pretrained(config.lm.pretrained_evoflow)
51
 
52
+ memdlm = MembraneDiffusion(config).to(device)
53
+ state_dict = memdlm.get_state_dict(str(repo_path("checkpoints", config.wandb.name, "best_model.ckpt")))
54
+ memdlm.load_state_dict(state_dict)
55
+ memdlm.eval()
56
 
57
  esm_pth = config.lm.pretrained_esm
58
  esm_model = AutoModelForMaskedLM.from_pretrained(esm_pth).to(device)
59
  esm_model.eval()
60
 
61
+ generator = UnconditionalSampler(tokenizer, memdlm)
62
 
63
  # # Get 100 random sequence lengths to generate
64
  # seq_lengths = [random.randint(50, 250) for _ in range(5000)]
 
68
  # seq_lengths = [len(seq) for seq in df['Sequence'].tolist() for _ in range(500)] # generate each length 100 times
69
  # _print(seq_lengths)
70
 
71
+ # # Determine lengths from test set distribution
72
+ # df = pd.read_csv("./data/test.csv")
73
+ # seq_lengths = [len(seq) for seq in df['Sequence'].tolist()]
74
+ # length_counts = Counter(seq_lengths) # {L1: freq, L2: freq, ...}
75
+ # total = sum(length_counts.values()) # total number of tokens
76
+ # lengths = np.array(list(length_counts.keys())) # Frequency of each length
77
+ # probs = np.array([length_counts[l] / total for l in lengths])
78
+ # seq_lengths = np.random.choice(lengths, size=len(seq_lengths), p=probs)
79
+
80
+ seq_lengths = [x for x in range(60, 161) for _ in range(3)]
81
 
82
  generation_results = []
83
  for seq_len in tqdm(seq_lengths, desc=f"Generating sequences: "):
 
92
  attempts += 1
93
 
94
  if len(gen_seq) != seq_len:
95
+ esm_ppl, memdlm_ppl = None, None
96
  else:
97
  esm_ppl = calc_ppl(esm_model, tokenizer, gen_seq, [i for i in range(len(gen_seq))], model_type='esm')
98
+ memdlm_ppl = calc_ppl(memdlm, tokenizer, gen_seq, [i for i in range(len(gen_seq))], model_type='diffusion')
99
 
100
  _print(f'gen seq: {gen_seq}')
101
  _print(f'esm ppl: {esm_ppl}')
102
+ _print(f'memdlm ppl: {memdlm_ppl}')
103
 
104
  seq_res.append(gen_seq)
105
  seq_res.append(esm_ppl)
106
+ seq_res.append(memdlm_ppl)
107
 
108
  generation_results.append(seq_res)
109
 
110
+ df = pd.DataFrame(generation_results, columns=['Generated Sequence', 'ESM PPL', 'MeMDLM PPL'])
111
+ df.to_csv(str(csv_save_path / "seqs_with_ppl.csv"), index=False)
112
 
113
 
114
  if __name__ == "__main__":
src/sampling/unconditional_sampler.py CHANGED
@@ -15,7 +15,7 @@ class UnconditionalSampler:
15
  self.seed_everything(seed=42)
16
 
17
  @torch.inference_mode()
18
- def sample_unconditional(self, xt, num_steps, tau=0.7, kappa_fn=lambda t: t, eta=1, alpha=1., banned_token_ids=None, return_logits=None):
19
  """
20
  Stochastic remasking sampling method for iterative refinement of sequences.
21
 
 
15
  self.seed_everything(seed=42)
16
 
17
  @torch.inference_mode()
18
+ def sample_unconditional(self, xt, num_steps, tau=1.0, kappa_fn=lambda t: t, eta=1, alpha=1., banned_token_ids=None, return_logits=None):
19
  """
20
  Stochastic remasking sampling method for iterative refinement of sequences.
21
 
src/utils/__init__.py ADDED
File without changes
src/utils/config_utils.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+ from omegaconf import OmegaConf
3
+
4
+ # Repository root (MeMDLM_v2/)
5
+ REPO_ROOT = Path(__file__).resolve().parents[2]
6
+
7
+
8
+ def load_config(config_name: str):
9
+ """Load a YAML config from src/configs/."""
10
+ return OmegaConf.load(REPO_ROOT / "src" / "configs" / config_name)
11
+
12
+
13
+ def repo_path(*parts: str) -> Path:
14
+ """Build an absolute path relative to the repository root."""
15
+ return REPO_ROOT.joinpath(*parts)
src/utils/generate_utils.py CHANGED
@@ -9,13 +9,13 @@ import numpy as np
9
  from omegaconf import OmegaConf
10
  from transformers import AutoModelForMaskedLM, AutoModel, AutoTokenizer
11
 
12
- from src.lm.memdlm.diffusion_module import MembraneFlow
13
- from src.lm.dplm.diffusion_module import DPLM
14
  from src.utils.model_utils import get_latents, _print
15
  from src.sampling.unconditional_sampler import UnconditionalSampler
16
- from src.lm.dplm.unconditional_sampler import UnconditionalSampler as DPLMUnconditionalSampler
17
 
18
- config = OmegaConf.load("/home/a03-sgoel/MeMDLM_v2/src/configs/lm.yaml")
 
 
19
 
20
  # -------# Masking #-------- #
21
  def mask_for_de_novo(sequence_length):
@@ -30,7 +30,7 @@ def mask_for_scaffold(sequence, generate_type, mask_token):
30
 
31
 
32
  # -------# Generation #-------- #
33
- def memflow_infill_uncond(masked_seq, tokenizer, model: MembraneFlow):
34
  generator = UnconditionalSampler(tokenizer, model) # initialize the generator object
35
  xt = tokenizer(masked_seq, return_tensors='pt')['input_ids'].to(model.device)
36
  denoised_tokens = generator.sample_unconditional(xt, config.sampling.n_steps)[0].squeeze()
@@ -68,7 +68,10 @@ def evodiff_infill(motif_seq, tokenizer, model, device, batch_size=1):
68
  return output[0] #if batch_size==1 else output, og_sample, loc
69
 
70
 
71
- def dplm_infill(masked_seq, tokenizer, model: DPLM, device):
 
 
 
72
  generator = DPLMUnconditionalSampler(tokenizer, model)
73
  xt = tokenizer(masked_seq, return_tensors='pt')['input_ids'].to(model.device)
74
  denoised_tokens = generator.sample_unconditional(xt, config.sampling.n_steps)[0].squeeze()
@@ -111,7 +114,7 @@ def calc_ppl(model, tokenizer, generated_sequence, mask_token_indices, model_typ
111
  with torch.no_grad():
112
  if model_type == 'esm':
113
  loss = model(masked_input, labels=labels).loss.item()
114
- elif model_type == 'flow':
115
  logits = model.forward(masked_input, attention_mask=attn_mask)
116
  loss = F.cross_entropy(
117
  logits.view(-1, logits.size(-1)),
@@ -143,6 +146,28 @@ def calc_blosum_score(og_seq, gen_seq, indices):
143
  return tot_score / len(indices) if indices else 0
144
 
145
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  def calc_cos_sim(original_sequence, generated_sequence, tokenizer, esm_model, device):
147
  og_embeddings = get_latents(esm_model, tokenizer, original_sequence.upper(), device)
148
  new_embeddings = get_latents(esm_model, tokenizer, generated_sequence, device)
 
9
  from omegaconf import OmegaConf
10
  from transformers import AutoModelForMaskedLM, AutoModel, AutoTokenizer
11
 
12
+ from src.lm.memdlm.diffusion_module import MembraneDiffusion
 
13
  from src.utils.model_utils import get_latents, _print
14
  from src.sampling.unconditional_sampler import UnconditionalSampler
 
15
 
16
+
17
+ config = OmegaConf.load("/scratch/pranamlab/sgoel/MeMDLM_v2/src/configs/lm.yaml")
18
+
19
 
20
  # -------# Masking #-------- #
21
  def mask_for_de_novo(sequence_length):
 
30
 
31
 
32
  # -------# Generation #-------- #
33
+ def memflow_infill_uncond(masked_seq, tokenizer, model: MembraneDiffusion):
34
  generator = UnconditionalSampler(tokenizer, model) # initialize the generator object
35
  xt = tokenizer(masked_seq, return_tensors='pt')['input_ids'].to(model.device)
36
  denoised_tokens = generator.sample_unconditional(xt, config.sampling.n_steps)[0].squeeze()
 
68
  return output[0] #if batch_size==1 else output, og_sample, loc
69
 
70
 
71
+ def dplm_infill(masked_seq, tokenizer, model, device):
72
+ from src.lm.dplm.diffusion_module import DPLM
73
+ from src.lm.dplm.unconditional_sampler import UnconditionalSampler as DPLMUnconditionalSampler
74
+
75
  generator = DPLMUnconditionalSampler(tokenizer, model)
76
  xt = tokenizer(masked_seq, return_tensors='pt')['input_ids'].to(model.device)
77
  denoised_tokens = generator.sample_unconditional(xt, config.sampling.n_steps)[0].squeeze()
 
114
  with torch.no_grad():
115
  if model_type == 'esm':
116
  loss = model(masked_input, labels=labels).loss.item()
117
+ elif model_type == 'diffusion':
118
  logits = model.forward(masked_input, attention_mask=attn_mask)
119
  loss = F.cross_entropy(
120
  logits.view(-1, logits.size(-1)),
 
146
  return tot_score / len(indices) if indices else 0
147
 
148
 
149
+ # define TM enrichment dict
150
+ TM_ENRICHMENT = {
151
+ 'A': -0.06647, 'C': 0.312396, 'D': -1.52276, 'E': -1.7743, 'F': 0.800983,
152
+ 'G': 0.756636, 'H': -0.44366, 'I': 0.551521, 'K': -1.56399, 'L': 0.299315,
153
+ 'M': 0.279242, 'N': -0.54795, 'P': 0.180039, 'Q': -1.16639, 'R': -1.17808,
154
+ 'S': 0.110367, 'T': 0.228464, 'V': 0.510702, 'W': 0.562891, 'Y': 0.084097
155
+ }
156
+
157
+ def calc_tm_enrich(og_seq, gen_seq, indices):
158
+ def _score(seq):
159
+ chars = [seq[i] for i in indices if i < len(seq)] if indices else list(seq)
160
+ vals = [TM_ENRICHMENT.get(aa.upper()) for aa in chars]
161
+ vals = [v for v in vals if v is not None]
162
+ return float(np.mean(vals)) if vals else float('nan')
163
+
164
+ og_score = _score(og_seq)
165
+ gen_score = _score(gen_seq)
166
+ delta_score = gen_score - og_score
167
+ return og_score, gen_score, delta_score
168
+
169
+
170
+
171
  def calc_cos_sim(original_sequence, generated_sequence, tokenizer, esm_model, device):
172
  og_embeddings = get_latents(esm_model, tokenizer, original_sequence.upper(), device)
173
  new_embeddings = get_latents(esm_model, tokenizer, generated_sequence, device)