Add ChEMBL3D explicit-H epochs 200/300/400 and implicit-H epochs 100/200/300 with inference bundles
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- README.md +97 -0
- chembl3d/explicit_h/atom_tokens.txt +13 -0
- chembl3d/explicit_h/charge_tokens.txt +3 -0
- chembl3d/explicit_h/config.yaml +329 -0
- chembl3d/explicit_h/distributions.json +0 -0
- chembl3d/explicit_h/distributions.pt +3 -0
- chembl3d/explicit_h/edge_tokens.txt +4 -0
- chembl3d/explicit_h/epoch_200.ckpt +3 -0
- chembl3d/explicit_h/epoch_300.ckpt +3 -0
- chembl3d/explicit_h/epoch_400.ckpt +3 -0
- chembl3d/explicit_h/metadata.json +71 -0
- chembl3d/explicit_h/runtime/README.md +3 -0
- chembl3d/explicit_h/runtime/configs/callbacks/callbacks.yaml +20 -0
- chembl3d/explicit_h/runtime/configs/data/chembl3d/chembl3d.yaml +36 -0
- chembl3d/explicit_h/runtime/configs/data/chembl3d/max100.yaml +8 -0
- chembl3d/explicit_h/runtime/configs/data/geom/geom.yaml +113 -0
- chembl3d/explicit_h/runtime/configs/data/geom/scaffold_decoration.yaml +24 -0
- chembl3d/explicit_h/runtime/configs/data/qm9/qm9.yaml +106 -0
- chembl3d/explicit_h/runtime/configs/data/qm9/scaffold_decoration.yaml +22 -0
- chembl3d/explicit_h/runtime/configs/default.yaml +25 -0
- chembl3d/explicit_h/runtime/configs/defaults/chembl3d/chembl3d.yaml +5 -0
- chembl3d/explicit_h/runtime/configs/defaults/chembl3d/train_2x4090.yaml +41 -0
- chembl3d/explicit_h/runtime/configs/defaults/core.yaml +13 -0
- chembl3d/explicit_h/runtime/configs/defaults/geom/geom.yaml +6 -0
- chembl3d/explicit_h/runtime/configs/defaults/geom/scaffold_decoration.yaml +23 -0
- chembl3d/explicit_h/runtime/configs/defaults/qm9/qm9.yaml +6 -0
- chembl3d/explicit_h/runtime/configs/defaults/qm9/scaffold_decoration.yaml +11 -0
- chembl3d/explicit_h/runtime/configs/embeddings/geom.yaml +28 -0
- chembl3d/explicit_h/runtime/configs/embeddings/qm9.yaml +29 -0
- chembl3d/explicit_h/runtime/configs/heads/head.yaml +75 -0
- chembl3d/explicit_h/runtime/configs/insertion_head/gmm.yaml +16 -0
- chembl3d/explicit_h/runtime/configs/insertion_head/unimodal.yaml +13 -0
- chembl3d/explicit_h/runtime/configs/integrator/integrator.yaml +30 -0
- chembl3d/explicit_h/runtime/configs/logging/wandb_logger.yaml +6 -0
- chembl3d/explicit_h/runtime/configs/loss_weights/calibrated_geom.yaml +18 -0
- chembl3d/explicit_h/runtime/configs/loss_weights/calibrated_geom_frozen.yaml +25 -0
- chembl3d/explicit_h/runtime/configs/loss_weights/tuned_weights.yaml +15 -0
- chembl3d/explicit_h/runtime/configs/loss_weights/uniform.yaml +15 -0
- chembl3d/explicit_h/runtime/configs/model/backbones/dit.yaml +123 -0
- chembl3d/explicit_h/runtime/configs/model/finetune/lora_finetune.yaml +13 -0
- chembl3d/explicit_h/runtime/configs/model/finetune/scaffold_finetune.yaml +134 -0
- chembl3d/explicit_h/runtime/configs/optimizer/muon_aux_adam.yaml +39 -0
- chembl3d/explicit_h/runtime/configs/schedule/beta.yaml +3 -0
- chembl3d/explicit_h/runtime/configs/schedule/cubic.yaml +1 -0
- chembl3d/explicit_h/runtime/configs/schedule/fast_power.yaml +3 -0
- chembl3d/explicit_h/runtime/configs/schedule/linear.yaml +1 -0
- chembl3d/explicit_h/runtime/configs/schedule/smoothstep.yaml +2 -0
- chembl3d/explicit_h/runtime/configs/trainer/trainer.yaml +37 -0
- chembl3d/explicit_h/runtime/pyproject.toml +136 -0
- chembl3d/explicit_h/runtime/requirements-tested.txt +19 -0
README.md
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
library_name: pytorch
|
| 3 |
+
tags:
|
| 4 |
+
- chemistry
|
| 5 |
+
- molecular-generation
|
| 6 |
+
- flow-matching
|
| 7 |
+
- chembl3d
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
# Morph ChEMBL3D checkpoints
|
| 11 |
+
|
| 12 |
+
Six checkpoints for unconditional 3D molecular generation, with matching
|
| 13 |
+
vocabularies, training distributions, portable inference configurations and
|
| 14 |
+
the frozen Morph runtime from each training run.
|
| 15 |
+
|
| 16 |
+
| Variant | Completed training epochs | Directory |
|
| 17 |
+
| --- | --- | --- |
|
| 18 |
+
| Explicit hydrogens | 200, 300, 400 | [chembl3d/explicit_h](chembl3d/explicit_h) |
|
| 19 |
+
| Implicit hydrogens (heavy-atom graphs) | 100, 200, 300 | [chembl3d/implicit_h](chembl3d/implicit_h) |
|
| 20 |
+
|
| 21 |
+
The `epoch_200.ckpt` name means **200 completed epochs**. The original Lightning
|
| 22 |
+
checkpoint stores `epoch=199`. Files are byte-for-byte copies of the selected
|
| 23 |
+
training checkpoints, including current weights, EMA weights, and optimizer
|
| 24 |
+
state. Sampling uses EMA weights by default.
|
| 25 |
+
|
| 26 |
+
Each variant contains:
|
| 27 |
+
|
| 28 |
+
- `epoch_NNN.ckpt`: the three selected checkpoints.
|
| 29 |
+
- `atom_tokens.txt`, `edge_tokens.txt`, `charge_tokens.txt`: ordered vocabularies.
|
| 30 |
+
- `distributions.pt`: the original training statistics, including atom counts,
|
| 31 |
+
type frequencies, coordinate scale, and geometry histograms.
|
| 32 |
+
- `distributions.json`: a readable export of the same statistics.
|
| 33 |
+
- `config.yaml`: portable sampling config with dataset loading disabled.
|
| 34 |
+
- `training_config.yaml`: the original Hydra training configuration, for provenance.
|
| 35 |
+
- `runtime/`: frozen source/configs and dependency specifications for this run.
|
| 36 |
+
- `metadata.json`: epochs, optimizer steps, original filenames, and SHA-256 hashes.
|
| 37 |
+
- `validation.json`: strict checkpoint loading, current/EMA forward checks and
|
| 38 |
+
a one-molecule end-to-end sampling check. This is a functionality check, not
|
| 39 |
+
a validity benchmark.
|
| 40 |
+
|
| 41 |
+
## Download and generate
|
| 42 |
+
|
| 43 |
+
Download the variant you need (authenticate with `hf auth login` if required):
|
| 44 |
+
|
| 45 |
+
```bash
|
| 46 |
+
hf download maltefranke/morph --local-dir morph-models \
|
| 47 |
+
--include 'chembl3d/implicit_h/*' --include sample.py --include README.md --include manifest.json
|
| 48 |
+
cd morph-models
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
Install that variant's runtime in an environment with Python >=3.11 and a
|
| 52 |
+
compatible PyTorch 2.9 installation. A CUDA-capable PyTorch build is needed to
|
| 53 |
+
use `--device cuda`. The tested package versions are recorded in
|
| 54 |
+
`runtime/requirements-tested.txt`; the runtime's `pyproject.toml` specifies the
|
| 55 |
+
dependency constraints.
|
| 56 |
+
|
| 57 |
+
```bash
|
| 58 |
+
python -m pip install ./chembl3d/implicit_h/runtime
|
| 59 |
+
python sample.py --variant implicit_h --epoch 300 \
|
| 60 |
+
--num-mols 64 --batch-size 16 --device cuda --output implicit_h.sdf
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
For explicit hydrogens, download `chembl3d/explicit_h/*` instead, install its
|
| 64 |
+
runtime, and run:
|
| 65 |
+
|
| 66 |
+
```bash
|
| 67 |
+
python sample.py --variant explicit_h --epoch 400 \
|
| 68 |
+
--num-mols 64 --batch-size 16 --device cuda --output explicit_h.sdf
|
| 69 |
+
```
|
| 70 |
+
|
| 71 |
+
The script imports the matching bundled source automatically. Each invocation
|
| 72 |
+
uses one variant in a fresh process. No ChEMBL3D dataset or cluster paths are
|
| 73 |
+
needed. `--device cpu` is supported, but use a compute worker for substantial
|
| 74 |
+
sampling. Compilation is disabled by default; attention uses PyTorch SDPA.
|
| 75 |
+
|
| 76 |
+
Generated coordinates are in Angstrom. The SDF contains valid molecules; the
|
| 77 |
+
adjacent JSON records every attempted molecule and its validity result, including
|
| 78 |
+
failures. Generation uses 100 integration steps and seed 42 by default.
|
| 79 |
+
|
| 80 |
+
## Hydrogen handling and training data
|
| 81 |
+
|
| 82 |
+
Both variants use the same split membership: 1,474,008 training, 184,295 validation,
|
| 83 |
+
and 184,242 test molecules. The dataset was filtered to **at most 100 total atoms,
|
| 84 |
+
including hydrogens**, before removing hydrogens for the implicit variant.
|
| 85 |
+
|
| 86 |
+
The explicit variant predicts H atoms as graph vertices. The implicit variant
|
| 87 |
+
predicts heavy atoms, bonds and formal charges; RDKit infers hydrogens during
|
| 88 |
+
conversion and validity checking. Its bundled runtime preserves that behavior.
|
| 89 |
+
Do not mix vocabularies or distributions between variants. Geometry statistics
|
| 90 |
+
and coordinate normalization were recomputed for the heavy-atom representation.
|
| 91 |
+
|
| 92 |
+
The 100-atom cap describes the training filter, not a hard limit imposed on
|
| 93 |
+
generated molecules. Sample counts follow the saved training prior settings.
|
| 94 |
+
|
| 95 |
+
`manifest.json` lists file sizes and SHA-256 hashes. The original training configs
|
| 96 |
+
contain historical cluster paths and should be treated as provenance; use
|
| 97 |
+
`sample.py` with the portable `config.yaml` for inference.
|
chembl3d/explicit_h/atom_tokens.txt
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
B
|
| 2 |
+
Br
|
| 3 |
+
C
|
| 4 |
+
Cl
|
| 5 |
+
F
|
| 6 |
+
H
|
| 7 |
+
I
|
| 8 |
+
N
|
| 9 |
+
O
|
| 10 |
+
P
|
| 11 |
+
S
|
| 12 |
+
Se
|
| 13 |
+
Si
|
chembl3d/explicit_h/charge_tokens.txt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
-1
|
| 2 |
+
0
|
| 3 |
+
1
|
chembl3d/explicit_h/config.yaml
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
representation: molecule
|
| 2 |
+
seed: 42
|
| 3 |
+
heads:
|
| 4 |
+
heads:
|
| 5 |
+
_target_: morph.model.layers.heads.MultiHeadModule
|
| 6 |
+
output_init_std: ${model.head_initialization.output_std}
|
| 7 |
+
independent_adapters: ${oc.select:model.head_independent_adapters,false}
|
| 8 |
+
heads_configs:
|
| 9 |
+
graph_aggregation: mean
|
| 10 |
+
node_heads:
|
| 11 |
+
atom_type_head:
|
| 12 |
+
input_dim: ${model.head_node_dim}
|
| 13 |
+
output_dim: ${len:${data.vocab.atom_tokens}}
|
| 14 |
+
adapter_dim: ${model.head_node_adapter_dim}
|
| 15 |
+
group: state
|
| 16 |
+
charge_head:
|
| 17 |
+
input_dim: ${model.head_node_dim}
|
| 18 |
+
output_dim: ${len:${data.vocab.charge_tokens}}
|
| 19 |
+
adapter_dim: ${model.head_node_adapter_dim}
|
| 20 |
+
group: state
|
| 21 |
+
ins_rate_head:
|
| 22 |
+
input_dim: ${model.head_node_dim}
|
| 23 |
+
output_dim: 1
|
| 24 |
+
adapter_dim: ${model.head_node_adapter_dim}
|
| 25 |
+
group: edit
|
| 26 |
+
initial_rate: ${model.head_initialization.ins_rate}
|
| 27 |
+
do_del_head:
|
| 28 |
+
input_dim: ${model.head_node_dim}
|
| 29 |
+
output_dim: 1
|
| 30 |
+
adapter_dim: ${model.head_node_adapter_dim}
|
| 31 |
+
group: edit
|
| 32 |
+
initial_probability: ${model.head_initialization.do_del_probability}
|
| 33 |
+
do_sub_a_head:
|
| 34 |
+
input_dim: ${model.head_node_dim}
|
| 35 |
+
output_dim: 1
|
| 36 |
+
adapter_dim: ${model.head_node_adapter_dim}
|
| 37 |
+
group: edit
|
| 38 |
+
initial_probability: ${model.head_initialization.do_sub_a_probability}
|
| 39 |
+
edge_heads:
|
| 40 |
+
edge_type_head:
|
| 41 |
+
input_dim: ${model.head_edge_dim}
|
| 42 |
+
output_dim: ${len:${data.vocab.edge_tokens}}
|
| 43 |
+
adapter_dim: ${model.head_edge_adapter_dim}
|
| 44 |
+
group: state
|
| 45 |
+
do_sub_e_head:
|
| 46 |
+
input_dim: ${model.head_edge_dim}
|
| 47 |
+
output_dim: 1
|
| 48 |
+
adapter_dim: ${model.head_edge_adapter_dim}
|
| 49 |
+
group: state
|
| 50 |
+
initial_probability: ${model.head_initialization.do_sub_e_probability}
|
| 51 |
+
ins_edge_head:
|
| 52 |
+
_target_: morph.model.layers.heads.InsertionEdgeHead
|
| 53 |
+
_recursive_: false
|
| 54 |
+
hidden_dim: ${model.head_node_dim}
|
| 55 |
+
readout_dim: ${model.head_insertion_edge_dim}
|
| 56 |
+
output_init_std: ${model.head_initialization.output_std}
|
| 57 |
+
n_edge_types: ${len:${data.vocab.edge_tokens}}
|
| 58 |
+
n_atom_types: ${len:${data.vocab.atom_tokens}}
|
| 59 |
+
n_charge_types: ${len:${data.vocab.charge_tokens}}
|
| 60 |
+
rbf_embedding_args:
|
| 61 |
+
_target_: morph.model.layers.embedding.RBFEmbedding
|
| 62 |
+
num_rbf: 128
|
| 63 |
+
rbf_dmax: 3.5
|
| 64 |
+
out_dim: 64
|
| 65 |
+
dropout: 0.0
|
| 66 |
+
trainable: false
|
| 67 |
+
insertion_head:
|
| 68 |
+
mode: unimodal
|
| 69 |
+
dimension: 3
|
| 70 |
+
num_components: 1
|
| 71 |
+
model:
|
| 72 |
+
_target_: morph.model.layers.heads.UnimodalInsertionHead
|
| 73 |
+
hidden_dim: ${model.head_node_dim}
|
| 74 |
+
readout_dim: ${model.head_insertion_adapter_dim}
|
| 75 |
+
output_init_std: ${model.head_initialization.output_std}
|
| 76 |
+
independent_adapters: ${oc.select:model.head_independent_adapters,false}
|
| 77 |
+
N_a: ${len:${data.vocab.atom_tokens}}
|
| 78 |
+
N_c: ${len:${data.vocab.charge_tokens}}
|
| 79 |
+
model:
|
| 80 |
+
module:
|
| 81 |
+
optimizer_config:
|
| 82 |
+
implementation: torch
|
| 83 |
+
optimizer:
|
| 84 |
+
execution: ${oc.env:MORPH_MUON_EXECUTION,native}
|
| 85 |
+
lr: 0.01
|
| 86 |
+
momentum: 0.95
|
| 87 |
+
weight_decay: 0.0
|
| 88 |
+
nesterov: true
|
| 89 |
+
ns_steps: 5
|
| 90 |
+
eps: 1.0e-07
|
| 91 |
+
adjust_lr_fn: original
|
| 92 |
+
aux_optimizer:
|
| 93 |
+
lr: 0.0003
|
| 94 |
+
betas:
|
| 95 |
+
- 0.9
|
| 96 |
+
- 0.95
|
| 97 |
+
eps: 1.0e-10
|
| 98 |
+
weight_decay: 0.0
|
| 99 |
+
gradient_clip_val: 1.0
|
| 100 |
+
gradient_clip_algorithm: norm
|
| 101 |
+
scheduler:
|
| 102 |
+
_target_: morph.utils.lr_schedulers.MultiGroupCosineWarmupLR
|
| 103 |
+
warmup_steps: 2000
|
| 104 |
+
total_steps: 200000
|
| 105 |
+
min_lr_fraction: 0.05
|
| 106 |
+
last_epoch: -1
|
| 107 |
+
interval: step
|
| 108 |
+
monitor: null
|
| 109 |
+
loss_weights:
|
| 110 |
+
do_sub_a: 0.2
|
| 111 |
+
sub_a_class: 0.03
|
| 112 |
+
do_sub_e: 0.25
|
| 113 |
+
sub_e_class: 0.5
|
| 114 |
+
do_del: 0.4
|
| 115 |
+
ins_rate: 0.15
|
| 116 |
+
ins_state: 0.01
|
| 117 |
+
ins_e: 0.4
|
| 118 |
+
x: 2.0
|
| 119 |
+
c: 0.5
|
| 120 |
+
ins_e_ii: 0.03
|
| 121 |
+
_target_: morph.model.lightning.base.LightningModuleRates
|
| 122 |
+
insertion_head_mode: ${insertion_head.mode}
|
| 123 |
+
ins_noise_scale: ${data.ins_noise_scale}
|
| 124 |
+
ema_decay: 0.999
|
| 125 |
+
ema_warmup_steps: 5000
|
| 126 |
+
use_ema_for_eval: true
|
| 127 |
+
time_dist: ${if:${eq:${model.sample_time_strategy}, log}, ${model.beta_sched},
|
| 128 |
+
${model.linear_sched}}
|
| 129 |
+
integrator: ${model.integrator}
|
| 130 |
+
vocab: ${data.vocab}
|
| 131 |
+
use_learnable_loss_weights: false
|
| 132 |
+
use_time_weights: false
|
| 133 |
+
model:
|
| 134 |
+
_target_: morph.model.backbones.dit.DiTBackboneWithHeads
|
| 135 |
+
_recursive_: false
|
| 136 |
+
atom_type_embedding_args: ${embeddings.atom_type_embedding}
|
| 137 |
+
edge_type_embedding_args: ${embeddings.edge_type_embedding}
|
| 138 |
+
time_embedding_args: ${embeddings.time_embedding}
|
| 139 |
+
node_count_embedding_args: ${embeddings.node_count_embedding}
|
| 140 |
+
incident_bond_embedding: false
|
| 141 |
+
backbone_model_args: ${model.backbone}
|
| 142 |
+
heads_args: ${heads.heads}
|
| 143 |
+
insertion_head_args: ${insertion_head.model}
|
| 144 |
+
ins_edge_head_args: ${heads.ins_edge_head}
|
| 145 |
+
rotate: true
|
| 146 |
+
n_augmentations: 1
|
| 147 |
+
integrator: ${integrator.integrator}
|
| 148 |
+
beta_sched:
|
| 149 |
+
_target_: torch.distributions.Beta
|
| 150 |
+
concentration1: 2.0
|
| 151 |
+
concentration0: 1.0
|
| 152 |
+
linear_sched:
|
| 153 |
+
_target_: torch.distributions.Uniform
|
| 154 |
+
low: 0.0
|
| 155 |
+
high: 1.0
|
| 156 |
+
sample_time_strategy: linear
|
| 157 |
+
head_node_dim: ${model.backbone.out_node_nf}
|
| 158 |
+
head_edge_dim: ${model.backbone.out_edge_nf}
|
| 159 |
+
head_node_adapter_dim: 256
|
| 160 |
+
head_edge_adapter_dim: 256
|
| 161 |
+
head_insertion_adapter_dim: 256
|
| 162 |
+
head_insertion_edge_dim: 256
|
| 163 |
+
head_independent_adapters: true
|
| 164 |
+
head_initialization:
|
| 165 |
+
output_std: 0.001
|
| 166 |
+
coordinate_output_std: 0.0
|
| 167 |
+
do_del_probability: 0.5
|
| 168 |
+
do_sub_a_probability: 0.5
|
| 169 |
+
do_sub_e_probability: 0.5
|
| 170 |
+
ins_rate: 0.6931471805599453
|
| 171 |
+
token_weighting:
|
| 172 |
+
type_loss_token_weights: uniform
|
| 173 |
+
weight_alpha: 0.4
|
| 174 |
+
lora:
|
| 175 |
+
enabled: false
|
| 176 |
+
rank: 8
|
| 177 |
+
alpha: 8.0
|
| 178 |
+
dropout: 0.0
|
| 179 |
+
targets:
|
| 180 |
+
- qkv
|
| 181 |
+
- proj
|
| 182 |
+
- mlp.gate_value
|
| 183 |
+
- mlp.out_proj
|
| 184 |
+
freeze_base: true
|
| 185 |
+
backbone:
|
| 186 |
+
_target_: morph.model.backbones.dit.DiTBackbone
|
| 187 |
+
_recursive_: false
|
| 188 |
+
d_input: ${embeddings.atom_type_embedding.out_dim}
|
| 189 |
+
d_cond: ${oc.eval:"${embeddings.time_embedding.out_dim} + ${embeddings.node_count_embedding.out_dim}"}
|
| 190 |
+
d_model: 512
|
| 191 |
+
out_node_nf: 256
|
| 192 |
+
out_edge_nf: 256
|
| 193 |
+
num_layers: 8
|
| 194 |
+
nhead: 16
|
| 195 |
+
mlp_ratio: 4.0
|
| 196 |
+
ffn_hidden_dim: null
|
| 197 |
+
ffn_multiple_of: 64
|
| 198 |
+
qk_norm: true
|
| 199 |
+
coordinate_output_init_std: ${model.head_initialization.coordinate_output_std}
|
| 200 |
+
center_coordinate_output: true
|
| 201 |
+
in_edge_nf: ${embeddings.edge_type_embedding.out_dim}
|
| 202 |
+
edge_pair_dim: 256
|
| 203 |
+
pair_bias_hidden_dim: 32
|
| 204 |
+
num_atom_types: ${len:${data.vocab.atom_tokens}}
|
| 205 |
+
lora: ${model.lora}
|
| 206 |
+
rbf_embedding_args:
|
| 207 |
+
_target_: morph.model.layers.embedding.RBFEmbedding
|
| 208 |
+
num_rbf: 128
|
| 209 |
+
rbf_dmax: 3.5
|
| 210 |
+
out_dim: 64
|
| 211 |
+
dropout: 0.0
|
| 212 |
+
trainable: false
|
| 213 |
+
schedule:
|
| 214 |
+
_target_: morph.flow_matching.schedules.SmoothstepSchedule
|
| 215 |
+
shift: 0.8
|
| 216 |
+
trainer:
|
| 217 |
+
trainer:
|
| 218 |
+
deterministic: warn
|
| 219 |
+
devices: 1
|
| 220 |
+
accelerator: auto
|
| 221 |
+
strategy: auto
|
| 222 |
+
precision: 32-true
|
| 223 |
+
max_epochs: -1
|
| 224 |
+
accumulate_grad_batches: 1
|
| 225 |
+
num_sanity_val_steps: 0
|
| 226 |
+
gradient_clip_val: null
|
| 227 |
+
gradient_clip_algorithm: norm
|
| 228 |
+
check_val_every_n_epoch: 5
|
| 229 |
+
num_val_samples: 4096
|
| 230 |
+
limit_val_batches: null
|
| 231 |
+
limit_predict_batches: 4
|
| 232 |
+
max_time: null
|
| 233 |
+
do_train: false
|
| 234 |
+
do_validate: false
|
| 235 |
+
do_predict: true
|
| 236 |
+
checkpoint_path: null
|
| 237 |
+
final_checkpoint_path: final.ckpt
|
| 238 |
+
callbacks: {}
|
| 239 |
+
logging:
|
| 240 |
+
enabled: false
|
| 241 |
+
project: morph-${data.dataset_name}
|
| 242 |
+
name: chembl3d-explicit_h
|
| 243 |
+
save_dir: .
|
| 244 |
+
offline: false
|
| 245 |
+
integrator:
|
| 246 |
+
integrator:
|
| 247 |
+
_target_: morph.flow_matching.integration.RateIntegrator
|
| 248 |
+
vocab: ${data.vocab}
|
| 249 |
+
distributions: None
|
| 250 |
+
insertion_head_mode: ${insertion_head.mode}
|
| 251 |
+
insertion_dimension: ${insertion_head.dimension}
|
| 252 |
+
insertion_num_components: ${insertion_head.num_components}
|
| 253 |
+
time_strategy: log
|
| 254 |
+
time_beta_alpha: 2.0
|
| 255 |
+
time_beta_beta: 1.0
|
| 256 |
+
num_integration_steps: 100
|
| 257 |
+
ins_noise_scale: ${data.ins_noise_scale}
|
| 258 |
+
renoise_insertions: true
|
| 259 |
+
exact_schedule_integration: true
|
| 260 |
+
del_schedule: ${data.interpolator.del_schedule}
|
| 261 |
+
ins_schedule: ${data.interpolator.ins_schedule}
|
| 262 |
+
sub_schedule: ${data.interpolator.sub_schedule}
|
| 263 |
+
sub_e_schedule: ${data.interpolator.sub_e_schedule}
|
| 264 |
+
data:
|
| 265 |
+
dataset_name: chembl3d
|
| 266 |
+
root_path: .
|
| 267 |
+
preprocess_workers: 0
|
| 268 |
+
n_atoms_strategy: uniform
|
| 269 |
+
uniform_n_atoms_padding: 5
|
| 270 |
+
optimal_transport: equivariant
|
| 271 |
+
ins_noise_scale: 0.65
|
| 272 |
+
move_noise_scale: 0.5
|
| 273 |
+
allow_charged: true
|
| 274 |
+
preprocessing:
|
| 275 |
+
_target_: morph.dataset.core.preprocessing.Preprocessing
|
| 276 |
+
_recursive_: false
|
| 277 |
+
root: .
|
| 278 |
+
train_dataset: null
|
| 279 |
+
atom_tokens_path: ${data.preprocessing.root}/atom_tokens.txt
|
| 280 |
+
edge_tokens_path: ${data.preprocessing.root}/edge_tokens.txt
|
| 281 |
+
charge_tokens_path: ${data.preprocessing.root}/charge_tokens.txt
|
| 282 |
+
distributions_path: ${data.preprocessing.root}/distributions.pt
|
| 283 |
+
num_workers: 0
|
| 284 |
+
vocab: null
|
| 285 |
+
interpolator:
|
| 286 |
+
_target_: morph.flow_matching.interpolation.Interpolator
|
| 287 |
+
distributions: None
|
| 288 |
+
optimal_transport: ${data.optimal_transport}
|
| 289 |
+
ins_noise_scale: ${data.ins_noise_scale}
|
| 290 |
+
move_noise_scale: ${data.move_noise_scale}
|
| 291 |
+
atom_type_noise_prob: 0.0
|
| 292 |
+
pre_align_iters: 2
|
| 293 |
+
c_move: 1.0
|
| 294 |
+
c_sub: 0.0
|
| 295 |
+
c_ins: 6.125
|
| 296 |
+
c_del: 6.125
|
| 297 |
+
del_schedule: ${schedule}
|
| 298 |
+
ins_schedule: ${schedule}
|
| 299 |
+
sub_schedule:
|
| 300 |
+
_target_: morph.flow_matching.schedules.SmoothstepSchedule
|
| 301 |
+
shift: 1.5
|
| 302 |
+
sub_e_schedule:
|
| 303 |
+
_target_: morph.flow_matching.schedules.SmoothstepSchedule
|
| 304 |
+
shift: 1.5
|
| 305 |
+
datamodule: null
|
| 306 |
+
source_dir: null
|
| 307 |
+
max_total_atoms: 100
|
| 308 |
+
embeddings:
|
| 309 |
+
atom_type_embedding:
|
| 310 |
+
_target_: morph.model.layers.embedding.Embedding
|
| 311 |
+
num_embeddings: ${len:${data.vocab.atom_tokens}}
|
| 312 |
+
embedding_dim: 64
|
| 313 |
+
out_dim: 128
|
| 314 |
+
edge_type_embedding:
|
| 315 |
+
_target_: morph.model.layers.embedding.Embedding
|
| 316 |
+
num_embeddings: ${len:${data.vocab.edge_tokens}}
|
| 317 |
+
embedding_dim: 64
|
| 318 |
+
out_dim: 128
|
| 319 |
+
time_embedding:
|
| 320 |
+
_target_: morph.model.layers.embedding.TimeEmbedding
|
| 321 |
+
embedding_dim: 128
|
| 322 |
+
out_dim: 128
|
| 323 |
+
node_count_embedding:
|
| 324 |
+
_target_: morph.model.layers.embedding.ExtrapolatableCountEmbedding
|
| 325 |
+
embedding_dim: 64
|
| 326 |
+
out_dim: 128
|
| 327 |
+
min_period: 128.0
|
| 328 |
+
max_period: 1024.0
|
| 329 |
+
max_count: 96.0
|
chembl3d/explicit_h/distributions.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
chembl3d/explicit_h/distributions.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c92bdce3e70763f23b3132a910a9939791fd147f98e11f7d6ea9c0e6ef9622b4
|
| 3 |
+
size 105093
|
chembl3d/explicit_h/edge_tokens.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<NO_BOND>
|
| 2 |
+
1
|
| 3 |
+
2
|
| 4 |
+
3
|
chembl3d/explicit_h/epoch_200.ckpt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:087f67faf38847d34780d061b0d192e61f8b4365d50cab6c32e0cc7dc5368743
|
| 3 |
+
size 509357065
|
chembl3d/explicit_h/epoch_300.ckpt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:ad7b4535cf8d40ead915c59e525fbe3ec555e6f03885b78ed54c45497793f32b
|
| 3 |
+
size 509357129
|
chembl3d/explicit_h/epoch_400.ckpt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:efb5c2f85972df19eca85d2d69cb56f6f52453d91ee26293b3e68b299e2bac8f
|
| 3 |
+
size 509357129
|
chembl3d/explicit_h/metadata.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"dataset": "ChEMBL3D",
|
| 3 |
+
"variant": "explicit_h",
|
| 4 |
+
"includes_explicit_hydrogens": true,
|
| 5 |
+
"source_max_total_atoms_including_h": 100,
|
| 6 |
+
"training_run": "chembl3d-max100-2x4090-15313268",
|
| 7 |
+
"source_snapshot": "chembl3d_recovery_20260927_v2",
|
| 8 |
+
"checkpoint_format": "Original Lightning checkpoint, current + EMA + optimizer state",
|
| 9 |
+
"epoch_numbering": "Completed epochs (one-based); original checkpoint epoch is zero-based",
|
| 10 |
+
"split_molecules": {
|
| 11 |
+
"train": 1474008,
|
| 12 |
+
"val": 184295,
|
| 13 |
+
"test": 184242
|
| 14 |
+
},
|
| 15 |
+
"checkpoints": [
|
| 16 |
+
{
|
| 17 |
+
"filename": "epoch_200.ckpt",
|
| 18 |
+
"original_filename": "epoch=199-step=460600.ckpt",
|
| 19 |
+
"completed_epochs": 200,
|
| 20 |
+
"checkpoint_epoch_index": 199,
|
| 21 |
+
"bytes": 509357065,
|
| 22 |
+
"sha256": "087f67faf38847d34780d061b0d192e61f8b4365d50cab6c32e0cc7dc5368743",
|
| 23 |
+
"global_step": 460600
|
| 24 |
+
},
|
| 25 |
+
{
|
| 26 |
+
"filename": "epoch_300.ckpt",
|
| 27 |
+
"original_filename": "epoch=299-step=690900.ckpt",
|
| 28 |
+
"completed_epochs": 300,
|
| 29 |
+
"checkpoint_epoch_index": 299,
|
| 30 |
+
"bytes": 509357129,
|
| 31 |
+
"sha256": "ad7b4535cf8d40ead915c59e525fbe3ec555e6f03885b78ed54c45497793f32b",
|
| 32 |
+
"global_step": 690900
|
| 33 |
+
},
|
| 34 |
+
{
|
| 35 |
+
"filename": "epoch_400.ckpt",
|
| 36 |
+
"original_filename": "epoch=399-step=921200.ckpt",
|
| 37 |
+
"completed_epochs": 400,
|
| 38 |
+
"checkpoint_epoch_index": 399,
|
| 39 |
+
"bytes": 509357129,
|
| 40 |
+
"sha256": "efb5c2f85972df19eca85d2d69cb56f6f52453d91ee26293b3e68b299e2bac8f",
|
| 41 |
+
"global_step": 921200
|
| 42 |
+
}
|
| 43 |
+
],
|
| 44 |
+
"coordinate_std_angstrom": 2.707319498062134,
|
| 45 |
+
"atom_tokens": [
|
| 46 |
+
"B",
|
| 47 |
+
"Br",
|
| 48 |
+
"C",
|
| 49 |
+
"Cl",
|
| 50 |
+
"F",
|
| 51 |
+
"H",
|
| 52 |
+
"I",
|
| 53 |
+
"N",
|
| 54 |
+
"O",
|
| 55 |
+
"P",
|
| 56 |
+
"S",
|
| 57 |
+
"Se",
|
| 58 |
+
"Si"
|
| 59 |
+
],
|
| 60 |
+
"edge_tokens": [
|
| 61 |
+
"<NO_BOND>",
|
| 62 |
+
"1",
|
| 63 |
+
"2",
|
| 64 |
+
"3"
|
| 65 |
+
],
|
| 66 |
+
"charge_tokens": [
|
| 67 |
+
"-1",
|
| 68 |
+
"0",
|
| 69 |
+
"1"
|
| 70 |
+
]
|
| 71 |
+
}
|
chembl3d/explicit_h/runtime/README.md
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Morph runtime for ChEMBL3D explicit_h
|
| 2 |
+
|
| 3 |
+
Frozen training source. See the model repository README and sample.py.
|
chembl3d/explicit_h/runtime/configs/callbacks/callbacks.yaml
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# @package _global_
|
| 2 |
+
|
| 3 |
+
## Put your callback args here
|
| 4 |
+
callbacks:
|
| 5 |
+
monitor_metric: ${if:${oc.eval:"'${representation}' in ('geometric_graph', 'molecule')"},val/validity,val/batch/pair_dist_l1}
|
| 6 |
+
monitor_metric_mode: ${if:${oc.eval:"'${representation}' in ('geometric_graph', 'molecule')"},max,min}
|
| 7 |
+
|
| 8 |
+
# model_checkpoints -> best-by-metric (top-1, monitored)
|
| 9 |
+
# every_n_epochs_checkpoint -> latest (no monitor; overwrites every epoch)
|
| 10 |
+
model_checkpoints:
|
| 11 |
+
save_top_k: 1
|
| 12 |
+
save_last: False
|
| 13 |
+
verbose: False
|
| 14 |
+
|
| 15 |
+
every_n_epochs_checkpoint:
|
| 16 |
+
every_n_epochs: 1
|
| 17 |
+
save_top_k: 1
|
| 18 |
+
save_last: False
|
| 19 |
+
verbose: False
|
| 20 |
+
save_on_train_epoch_end: True # Force per-train-epoch firing
|
chembl3d/explicit_h/runtime/configs/data/chembl3d/chembl3d.yaml
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# LoQI Release 2 -> Morph LMDB, using the shared drug-like training setup.
|
| 2 |
+
defaults:
|
| 3 |
+
- /data/geom/geom@_here_
|
| 4 |
+
- _self_
|
| 5 |
+
|
| 6 |
+
dataset_name: chembl3d
|
| 7 |
+
# Optional directory with the downloaded train_h.pt / val_h.pt / test_h.pt.
|
| 8 |
+
# When unset, missing files are downloaded to <root>/raw/.
|
| 9 |
+
source_dir: ${oc.env:CHEMBL3D_SOURCE_DIR,null}
|
| 10 |
+
vocab: null
|
| 11 |
+
|
| 12 |
+
preprocessing:
|
| 13 |
+
num_workers: ${data.preprocess_workers}
|
| 14 |
+
root: ${data.root_path}/chembl3d
|
| 15 |
+
|
| 16 |
+
datamodule:
|
| 17 |
+
distributions: null
|
| 18 |
+
datasets:
|
| 19 |
+
train:
|
| 20 |
+
_target_: morph.dataset.unconditional.chembl3d.FlowMatchingChEMBL3DDataset
|
| 21 |
+
source_dir: ${data.source_dir}
|
| 22 |
+
distributions: null
|
| 23 |
+
val:
|
| 24 |
+
- _target_: morph.dataset.unconditional.chembl3d.FlowMatchingChEMBL3DDataset
|
| 25 |
+
root: ${data.preprocessing.root}
|
| 26 |
+
source_dir: ${data.source_dir}
|
| 27 |
+
preprocess_workers: ${data.preprocess_workers}
|
| 28 |
+
vocab: ${data.vocab}
|
| 29 |
+
distributions: null
|
| 30 |
+
test:
|
| 31 |
+
- _target_: morph.dataset.unconditional.chembl3d.FlowMatchingChEMBL3DDataset
|
| 32 |
+
root: ${data.preprocessing.root}
|
| 33 |
+
source_dir: ${data.source_dir}
|
| 34 |
+
preprocess_workers: ${data.preprocess_workers}
|
| 35 |
+
vocab: ${data.vocab}
|
| 36 |
+
distributions: null
|
chembl3d/explicit_h/runtime/configs/data/chembl3d/max100.yaml
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Dataset cache built by morph.dataset.unconditional.chembl3d_filter.
|
| 2 |
+
defaults:
|
| 3 |
+
- /data/chembl3d/chembl3d@_here_
|
| 4 |
+
- _self_
|
| 5 |
+
|
| 6 |
+
max_total_atoms: 100 # Inclusive, with explicit hydrogens retained.
|
| 7 |
+
preprocessing:
|
| 8 |
+
root: ${data.root_path}/chembl3d_max100
|
chembl3d/explicit_h/runtime/configs/data/geom/geom.yaml
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
## GEOM-Drugs data and preprocessing configuration.
|
| 2 |
+
|
| 3 |
+
dataset_name: geom
|
| 4 |
+
root_path: ${oc.env:PROJECT_ROOT}/data
|
| 5 |
+
preprocess_workers: 10
|
| 6 |
+
|
| 7 |
+
n_atoms_strategy: "uniform" # "approx", "empirical", "learned"
|
| 8 |
+
# Uniform source-count support padding; explicit so inference and training can
|
| 9 |
+
# be matched or ablated without code changes.
|
| 10 |
+
uniform_n_atoms_padding: 5
|
| 11 |
+
optimal_transport: "equivariant"
|
| 12 |
+
# Physical Angstroms (converted to normalized coordinates internally). These
|
| 13 |
+
# reproduce the former normalized 0.25 / 0.2 scales at GEOM's training-set std.
|
| 14 |
+
# Physical Angstroms. Insertion bridge noise vanishes as (1-t); move noise is
|
| 15 |
+
# the persistent training-time jitter floor for matched atoms and insertions
|
| 16 |
+
# that are already present.
|
| 17 |
+
ins_noise_scale: 0.65
|
| 18 |
+
move_noise_scale: 0.5
|
| 19 |
+
|
| 20 |
+
# GEOM-Drugs contains charged species; allow them when scoring validity.
|
| 21 |
+
allow_charged: true
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
# Preprocessing configuration - computes tokens from data and distributions from training dataset only
|
| 25 |
+
preprocessing:
|
| 26 |
+
_target_: morph.dataset.core.preprocessing.Preprocessing
|
| 27 |
+
_recursive_: false
|
| 28 |
+
root: ${data.root_path}/geom
|
| 29 |
+
train_dataset: ${data.datamodule.datasets.train}
|
| 30 |
+
atom_tokens_path: ${data.preprocessing.root}/processed/atom_tokens.txt
|
| 31 |
+
edge_tokens_path: ${data.preprocessing.root}/processed/edge_tokens.txt
|
| 32 |
+
charge_tokens_path: ${data.preprocessing.root}/processed/charge_tokens.txt
|
| 33 |
+
distributions_path: ${data.preprocessing.root}/processed/distributions.pt
|
| 34 |
+
# Tokens and distributions will be discovered from data and saved to files
|
| 35 |
+
# On subsequent runs, they will be loaded from the files
|
| 36 |
+
|
| 37 |
+
vocab: None
|
| 38 |
+
|
| 39 |
+
interpolator:
|
| 40 |
+
_target_: morph.flow_matching.interpolation.Interpolator
|
| 41 |
+
distributions: None
|
| 42 |
+
optimal_transport: ${data.optimal_transport}
|
| 43 |
+
ins_noise_scale: ${data.ins_noise_scale}
|
| 44 |
+
move_noise_scale: ${data.move_noise_scale}
|
| 45 |
+
# Training-only categorical corruption; pairs are scaled per molecule to
|
| 46 |
+
# match the expected number of changed atoms. Clean targets stay unchanged.
|
| 47 |
+
atom_type_noise_prob: 0.0
|
| 48 |
+
# OT matching (cost always squared-Euclidean; move cost in Angstrom). c_move
|
| 49 |
+
# scales the squared move distance (A^2); c_ins/c_del/c_sub are fixed A^2 costs,
|
| 50 |
+
# directly comparable. At 6.125 each, delete+insert costs 3.5^2, so atoms
|
| 51 |
+
# farther than 3.5 A apart are left unmatched by default.
|
| 52 |
+
pre_align_iters: 2 # equivariant-OT inner loop (Hungarian+Kabsch) iters
|
| 53 |
+
c_move: 1.0
|
| 54 |
+
c_sub: 0.0
|
| 55 |
+
c_ins: 6.125
|
| 56 |
+
c_del: 6.125
|
| 57 |
+
del_schedule: ${schedule}
|
| 58 |
+
ins_schedule: ${schedule}
|
| 59 |
+
sub_schedule:
|
| 60 |
+
_target_: morph.flow_matching.schedules.SmoothstepSchedule
|
| 61 |
+
shift: 1.5
|
| 62 |
+
sub_e_schedule:
|
| 63 |
+
_target_: morph.flow_matching.schedules.SmoothstepSchedule
|
| 64 |
+
shift: 1.5
|
| 65 |
+
|
| 66 |
+
datamodule:
|
| 67 |
+
_target_: morph.dataset.core.lightning_datamodule.LightningDataModule
|
| 68 |
+
# tokens and distributions will be passed from preprocessing in run.py
|
| 69 |
+
|
| 70 |
+
vocab: ${data.vocab}
|
| 71 |
+
distributions: None
|
| 72 |
+
representation: ${representation}
|
| 73 |
+
|
| 74 |
+
datasets:
|
| 75 |
+
train:
|
| 76 |
+
_target_: morph.dataset.unconditional.geom.FlowMatchingGEOMDataset
|
| 77 |
+
root: ${data.preprocessing.root}
|
| 78 |
+
preprocess_workers: ${data.preprocess_workers}
|
| 79 |
+
vocab: ${data.vocab}
|
| 80 |
+
distributions: None
|
| 81 |
+
|
| 82 |
+
val:
|
| 83 |
+
- _target_: morph.dataset.unconditional.geom.FlowMatchingGEOMDataset
|
| 84 |
+
root: ${data.preprocessing.root}
|
| 85 |
+
preprocess_workers: ${data.preprocess_workers}
|
| 86 |
+
vocab: ${data.vocab}
|
| 87 |
+
distributions: None
|
| 88 |
+
|
| 89 |
+
test:
|
| 90 |
+
- _target_: morph.dataset.unconditional.geom.FlowMatchingGEOMDataset
|
| 91 |
+
root: ${data.preprocessing.root}
|
| 92 |
+
preprocess_workers: ${data.preprocess_workers}
|
| 93 |
+
vocab: ${data.vocab}
|
| 94 |
+
distributions: None
|
| 95 |
+
|
| 96 |
+
interpolator: ${data.interpolator}
|
| 97 |
+
|
| 98 |
+
rotate: ${model.rotate}
|
| 99 |
+
n_augmentations: ${model.n_augmentations}
|
| 100 |
+
|
| 101 |
+
num_workers:
|
| 102 |
+
train: 10
|
| 103 |
+
val: 0
|
| 104 |
+
test: 0
|
| 105 |
+
|
| 106 |
+
batch_size:
|
| 107 |
+
train: 320
|
| 108 |
+
val: 128
|
| 109 |
+
test: 64
|
| 110 |
+
|
| 111 |
+
n_atoms_strategy: ${data.n_atoms_strategy}
|
| 112 |
+
uniform_n_atoms_padding: ${data.uniform_n_atoms_padding}
|
| 113 |
+
time_dist: ${model.module.time_dist}
|
chembl3d/explicit_h/runtime/configs/data/geom/scaffold_decoration.yaml
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# GEOM-Drugs data config for the **scaffold decoration** task.
|
| 2 |
+
#
|
| 3 |
+
# Mirrors configs/data/qm9/scaffold_decoration.yaml but on GEOM-Drugs: source
|
| 4 |
+
# is the selected scaffold of each target; target is the full molecule. The model
|
| 5 |
+
# learns to grow it by inserting decoration atoms around the fixed scaffold.
|
| 6 |
+
#
|
| 7 |
+
# Inherits from configs/data/geom/geom.yaml and overrides:
|
| 8 |
+
# - datamodule._target_ -> ScaffoldLightningDataModule
|
| 9 |
+
#
|
| 10 |
+
# The scaffold cache is built lazily into ``scaffold_groups_dir`` on first run
|
| 11 |
+
# (one file per split and scaffold kind) and reused afterwards.
|
| 12 |
+
defaults:
|
| 13 |
+
- /data/geom/geom@_here_
|
| 14 |
+
- _self_
|
| 15 |
+
|
| 16 |
+
scaffold_groups_dir: ${data.preprocessing.root}/processed/scaffold_groups
|
| 17 |
+
scaffold_kind: rgroup # murcko, rgroup (BRICS), heavy (generate H), linker
|
| 18 |
+
# Evaluate each distinct molecule/scaffold pair; training always draws randomly.
|
| 19 |
+
enumerate_slicings: false
|
| 20 |
+
datamodule:
|
| 21 |
+
_target_: morph.dataset.conditional.scaffold.datamodule.ScaffoldLightningDataModule
|
| 22 |
+
scaffold_groups_dir: ${data.scaffold_groups_dir}
|
| 23 |
+
scaffold_kind: ${data.scaffold_kind}
|
| 24 |
+
enumerate_slicings: ${data.enumerate_slicings}
|
chembl3d/explicit_h/runtime/configs/data/qm9/qm9.yaml
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
## QM9 data and preprocessing configuration.
|
| 2 |
+
|
| 3 |
+
dataset_name: qm9
|
| 4 |
+
root_path: ${oc.env:PROJECT_ROOT}/data
|
| 5 |
+
preprocess_workers: 0
|
| 6 |
+
|
| 7 |
+
n_atoms_strategy: "uniform" # "approx", "empirical", "learned"
|
| 8 |
+
# Expand both ends of the empirical count support for the uniform flow source.
|
| 9 |
+
# QM9's minimum is already the hard lower bound of 3, so positive padding is
|
| 10 |
+
# necessarily asymmetric; set to 0 for an unpadded 3--29 diagnostic prior.
|
| 11 |
+
uniform_n_atoms_padding: 5
|
| 12 |
+
optimal_transport: "equivariant"
|
| 13 |
+
# Physical Angstroms (converted to normalized coordinates internally). Insertion
|
| 14 |
+
# bridge noise vanishes as (1-t); move noise is the persistent training-time
|
| 15 |
+
# jitter floor for matched atoms and insertions that are already present.
|
| 16 |
+
ins_noise_scale: 0.35
|
| 17 |
+
move_noise_scale: 0.3
|
| 18 |
+
|
| 19 |
+
# QM9 contains only neutral molecules; charged species are treated as invalid.
|
| 20 |
+
allow_charged: false
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
# Preprocessing configuration - computes tokens from data and distributions from training dataset only
|
| 24 |
+
preprocessing:
|
| 25 |
+
_target_: morph.dataset.core.preprocessing.Preprocessing
|
| 26 |
+
_recursive_: false
|
| 27 |
+
root: ${data.root_path}/qm9
|
| 28 |
+
train_dataset: ${data.datamodule.datasets.train}
|
| 29 |
+
# Canonical (representation-agnostic) cache. One preprocessing per dataset;
|
| 30 |
+
# representation-specific projection happens at runtime in the wrapper.
|
| 31 |
+
atom_tokens_path: ${data.preprocessing.root}/processed/atom_tokens.txt
|
| 32 |
+
edge_tokens_path: ${data.preprocessing.root}/processed/edge_tokens.txt
|
| 33 |
+
charge_tokens_path: ${data.preprocessing.root}/processed/charge_tokens.txt
|
| 34 |
+
distributions_path: ${data.preprocessing.root}/processed/distributions.pt
|
| 35 |
+
|
| 36 |
+
vocab: None
|
| 37 |
+
|
| 38 |
+
interpolator:
|
| 39 |
+
_target_: morph.flow_matching.interpolation.Interpolator
|
| 40 |
+
distributions: None
|
| 41 |
+
optimal_transport: ${data.optimal_transport}
|
| 42 |
+
ins_noise_scale: ${data.ins_noise_scale}
|
| 43 |
+
move_noise_scale: ${data.move_noise_scale}
|
| 44 |
+
# OT matching (cost always squared-Euclidean; move cost in Angstrom). c_move
|
| 45 |
+
# scales the squared move distance (A^2); c_ins/c_del/c_sub are fixed A^2 costs,
|
| 46 |
+
# directly comparable. At 6.125 each, delete+insert costs 3.5^2, so atoms
|
| 47 |
+
# farther than 3.5 A apart are left unmatched by default.
|
| 48 |
+
pre_align_iters: 2 # equivariant-OT inner loop (Hungarian+Kabsch) iters
|
| 49 |
+
c_move: 1.0
|
| 50 |
+
c_sub: 0.0
|
| 51 |
+
c_ins: 6.125
|
| 52 |
+
c_del: 6.125
|
| 53 |
+
del_schedule: ${schedule}
|
| 54 |
+
ins_schedule: ${schedule}
|
| 55 |
+
sub_schedule:
|
| 56 |
+
_target_: morph.flow_matching.schedules.SmoothstepSchedule
|
| 57 |
+
shift: 1.5
|
| 58 |
+
sub_e_schedule:
|
| 59 |
+
_target_: morph.flow_matching.schedules.SmoothstepSchedule
|
| 60 |
+
shift: 1.5
|
| 61 |
+
|
| 62 |
+
datamodule:
|
| 63 |
+
_target_: morph.dataset.core.lightning_datamodule.LightningDataModule
|
| 64 |
+
# tokens and distributions will be passed from preprocessing in run.py
|
| 65 |
+
|
| 66 |
+
vocab: ${data.vocab}
|
| 67 |
+
distributions: None
|
| 68 |
+
representation: ${representation}
|
| 69 |
+
|
| 70 |
+
datasets:
|
| 71 |
+
train:
|
| 72 |
+
_target_: morph.dataset.unconditional.qm9.FlowMatchingQM9Dataset
|
| 73 |
+
root: ${data.root_path}/qm9
|
| 74 |
+
vocab: ${data.vocab}
|
| 75 |
+
distributions: None
|
| 76 |
+
|
| 77 |
+
val:
|
| 78 |
+
- _target_: morph.dataset.unconditional.qm9.FlowMatchingQM9Dataset
|
| 79 |
+
root: ${data.root_path}/qm9
|
| 80 |
+
vocab: ${data.vocab}
|
| 81 |
+
distributions: None
|
| 82 |
+
|
| 83 |
+
test:
|
| 84 |
+
- _target_: morph.dataset.unconditional.qm9.FlowMatchingQM9Dataset
|
| 85 |
+
root: ${data.root_path}/qm9
|
| 86 |
+
vocab: ${data.vocab}
|
| 87 |
+
distributions: None
|
| 88 |
+
|
| 89 |
+
interpolator: ${data.interpolator}
|
| 90 |
+
|
| 91 |
+
rotate: ${model.rotate}
|
| 92 |
+
n_augmentations: ${model.n_augmentations}
|
| 93 |
+
|
| 94 |
+
num_workers:
|
| 95 |
+
train: 10
|
| 96 |
+
val: 0
|
| 97 |
+
test: 0
|
| 98 |
+
|
| 99 |
+
batch_size:
|
| 100 |
+
train: 1024
|
| 101 |
+
val: 128
|
| 102 |
+
test: 32
|
| 103 |
+
|
| 104 |
+
n_atoms_strategy: ${data.n_atoms_strategy}
|
| 105 |
+
uniform_n_atoms_padding: ${data.uniform_n_atoms_padding}
|
| 106 |
+
time_dist: ${model.module.time_dist}
|
chembl3d/explicit_h/runtime/configs/data/qm9/scaffold_decoration.yaml
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# QM9 data config for the **scaffold decoration** task.
|
| 2 |
+
#
|
| 3 |
+
# Source is the selected scaffold of each target; target is the full molecule.
|
| 4 |
+
# The model learns to grow the molecule by inserting decoration atoms
|
| 5 |
+
# around the fixed scaffold.
|
| 6 |
+
#
|
| 7 |
+
# Inherits from configs/data/qm9/qm9.yaml and overrides:
|
| 8 |
+
# - datamodule._target_ → ScaffoldLightningDataModule
|
| 9 |
+
#
|
| 10 |
+
defaults:
|
| 11 |
+
- /data/qm9/qm9@_here_
|
| 12 |
+
- _self_
|
| 13 |
+
|
| 14 |
+
scaffold_groups_dir: ${data.root_path}/qm9/processed/scaffold_groups
|
| 15 |
+
scaffold_kind: rgroup # murcko, rgroup (BRICS), heavy (generate H), linker
|
| 16 |
+
# Evaluate each distinct molecule/scaffold pair; training always draws randomly.
|
| 17 |
+
enumerate_slicings: false
|
| 18 |
+
datamodule:
|
| 19 |
+
_target_: morph.dataset.conditional.scaffold.datamodule.ScaffoldLightningDataModule
|
| 20 |
+
scaffold_groups_dir: ${data.scaffold_groups_dir}
|
| 21 |
+
scaffold_kind: ${data.scaffold_kind}
|
| 22 |
+
enumerate_slicings: ${data.enumerate_slicings}
|
chembl3d/explicit_h/runtime/configs/default.yaml
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
defaults:
|
| 2 |
+
- _self_
|
| 3 |
+
# Single-knob presets bundle
|
| 4 |
+
# `data` + `embeddings` (with dataset-tuned ExtrapolatableCount params)
|
| 5 |
+
# plus the shared `core` group selections. Override individual groups as
|
| 6 |
+
# usual, e.g. `defaults=geom/geom model=backbones/dit embeddings=qm9`.
|
| 7 |
+
- defaults: qm9/qm9
|
| 8 |
+
|
| 9 |
+
# What the model trains on. Datasets declare their CAPABILITIES; run.py validates
|
| 10 |
+
# the requested mode against them before preprocessing.
|
| 11 |
+
# pointcloud — x, a only (charges and edges are dummy singletons)
|
| 12 |
+
# charged_pointcloud — x, a, c (edges are dummy singletons)
|
| 13 |
+
# geometric_graph — x, a, edge_index, e (charges are dummy neutral)
|
| 14 |
+
# molecule — full chemistry (current behavior)
|
| 15 |
+
representation: molecule
|
| 16 |
+
seed: 42
|
| 17 |
+
|
| 18 |
+
# Run directory: <root>/<dataset>/<cfg_method>/<date>/<wandb_id>. Root is
|
| 19 |
+
# MORPH_RUN_ROOT (scratch, set in .env) so every run's outputs/checkpoints/wandb
|
| 20 |
+
# land off-repo; falls back to <cwd>/outputs for ad-hoc runs with no env set.
|
| 21 |
+
# wandb id is MORPH_RUN_ID (same as WANDB_RUN_ID). Set in the shell before
|
| 22 |
+
# torchrun, or set automatically for single-process runs in run.py before Hydra composes.
|
| 23 |
+
hydra:
|
| 24 |
+
run:
|
| 25 |
+
dir: ${oc.env:MORPH_RUN_ROOT,${hydra:runtime.cwd}/outputs}/${hydra:runtime.choices.data}/${now:%Y-%m-%d}/${oc.env:MORPH_RUN_ID}
|
chembl3d/explicit_h/runtime/configs/defaults/chembl3d/chembl3d.yaml
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# @package _global_
|
| 2 |
+
defaults:
|
| 3 |
+
- core
|
| 4 |
+
- /data: chembl3d/chembl3d
|
| 5 |
+
- /embeddings: geom
|
chembl3d/explicit_h/runtime/configs/defaults/chembl3d/train_2x4090.yaml
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# @package _global_
|
| 2 |
+
defaults:
|
| 3 |
+
- core
|
| 4 |
+
- /data: chembl3d/max100
|
| 5 |
+
- /embeddings: geom
|
| 6 |
+
- _self_
|
| 7 |
+
|
| 8 |
+
# Preserve the base recipe's effective global batch: 2 GPUs * 80 * 4 = 640.
|
| 9 |
+
data:
|
| 10 |
+
datamodule:
|
| 11 |
+
train_prefetch_factor: 1
|
| 12 |
+
batch_size:
|
| 13 |
+
train: 80
|
| 14 |
+
val: 32
|
| 15 |
+
test: 32
|
| 16 |
+
num_workers:
|
| 17 |
+
train: 20
|
| 18 |
+
val: 2
|
| 19 |
+
test: 2
|
| 20 |
+
|
| 21 |
+
trainer:
|
| 22 |
+
trainer:
|
| 23 |
+
accelerator: gpu
|
| 24 |
+
devices: 2
|
| 25 |
+
accumulate_grad_batches: 4
|
| 26 |
+
max_epochs: -1
|
| 27 |
+
check_val_every_n_epoch: 5
|
| 28 |
+
num_val_samples: 4096
|
| 29 |
+
# Validation runs during fit; leave wall-clock headroom for final checkpointing.
|
| 30 |
+
do_validate: false
|
| 31 |
+
do_predict: false
|
| 32 |
+
final_checkpoint_path: final.ckpt
|
| 33 |
+
|
| 34 |
+
callbacks:
|
| 35 |
+
runtime_monitor:
|
| 36 |
+
_target_: morph.utils.training_monitor.TrainingMonitor
|
| 37 |
+
every_n_batches: 100
|
| 38 |
+
every_n_epochs_checkpoint:
|
| 39 |
+
every_n_epochs: 5
|
| 40 |
+
save_top_k: -1
|
| 41 |
+
save_last: link
|
chembl3d/explicit_h/runtime/configs/defaults/core.yaml
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# @package _global_
|
| 2 |
+
# Shared default groups that don't change between QM9 / GEOM runs.
|
| 3 |
+
# Dataset-specific presets (defaults/qm9/qm9.yaml, defaults/geom/geom.yaml) inherit
|
| 4 |
+
# from this and only add `data` + `embeddings`.
|
| 5 |
+
defaults:
|
| 6 |
+
- /heads: head
|
| 7 |
+
- /insertion_head: unimodal
|
| 8 |
+
- /model: backbones/dit
|
| 9 |
+
- /schedule: smoothstep
|
| 10 |
+
- /trainer: trainer
|
| 11 |
+
- /callbacks: callbacks
|
| 12 |
+
- /logging: wandb_logger
|
| 13 |
+
- /integrator: integrator
|
chembl3d/explicit_h/runtime/configs/defaults/geom/geom.yaml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# @package _global_
|
| 2 |
+
# GEOM preset: shared core + GEOM data and embeddings.
|
| 3 |
+
defaults:
|
| 4 |
+
- core
|
| 5 |
+
- /data: geom/geom
|
| 6 |
+
- /embeddings: geom
|
chembl3d/explicit_h/runtime/configs/defaults/geom/scaffold_decoration.yaml
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# @package _global_
|
| 2 |
+
# GEOM-Drugs **scaffold decoration** fine-tune preset.
|
| 3 |
+
#
|
| 4 |
+
# Mirrors defaults/qm9/scaffold_decoration.yaml on GEOM-Drugs. Bare-scaffold
|
| 5 |
+
# source -> full-molecule target: the model learns to grow a molecule by
|
| 6 |
+
# inserting decoration atoms around the fixed scaffold.
|
| 7 |
+
#
|
| 8 |
+
# Differences from the QM9 scaffold preset:
|
| 9 |
+
# - data -> geom/scaffold_decoration (GEOM base datasets)
|
| 10 |
+
# - embeddings -> geom
|
| 11 |
+
#
|
| 12 |
+
# Reuses the scaffold_finetune model config (loads the pretrained unconditional
|
| 13 |
+
# DiT into the clean lane + adds the node/edge conditioning and the gated
|
| 14 |
+
# cross-attn blocks).
|
| 15 |
+
#
|
| 16 |
+
# Pair with a GEOM unconditional DiT checkpoint via
|
| 17 |
+
# ``model.module.pretrained_ckpt=...``.
|
| 18 |
+
defaults:
|
| 19 |
+
- core
|
| 20 |
+
- /data: geom/scaffold_decoration
|
| 21 |
+
- /embeddings: geom
|
| 22 |
+
- override /model: finetune/scaffold_finetune
|
| 23 |
+
- _self_
|
chembl3d/explicit_h/runtime/configs/defaults/qm9/qm9.yaml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# @package _global_
|
| 2 |
+
# QM9 preset: shared core + QM9 data and embeddings.
|
| 3 |
+
defaults:
|
| 4 |
+
- core
|
| 5 |
+
- /data: qm9/qm9
|
| 6 |
+
- /embeddings: qm9
|
chembl3d/explicit_h/runtime/configs/defaults/qm9/scaffold_decoration.yaml
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# @package _global_
|
| 2 |
+
# QM9 **scaffold decoration** fine-tune preset.
|
| 3 |
+
#
|
| 4 |
+
# Bare-scaffold source → full-molecule target. The model learns to grow
|
| 5 |
+
# a molecule by inserting decoration atoms around the fixed scaffold.
|
| 6 |
+
defaults:
|
| 7 |
+
- core
|
| 8 |
+
- /data: qm9/scaffold_decoration
|
| 9 |
+
- /embeddings: qm9
|
| 10 |
+
- override /model: finetune/scaffold_finetune
|
| 11 |
+
- _self_
|
chembl3d/explicit_h/runtime/configs/embeddings/geom.yaml
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Shared embedding stack tuned for GEOM-Drugs.
|
| 2 |
+
# - node_count_embedding uses ExtrapolatableCountEmbedding with periods/max_count
|
| 3 |
+
# scaled up for the much larger atom counts in GEOM-Drugs.
|
| 4 |
+
|
| 5 |
+
atom_type_embedding:
|
| 6 |
+
_target_: morph.model.layers.embedding.Embedding
|
| 7 |
+
num_embeddings: ${len:${data.vocab.atom_tokens}}
|
| 8 |
+
embedding_dim: 64
|
| 9 |
+
out_dim: 128
|
| 10 |
+
|
| 11 |
+
edge_type_embedding:
|
| 12 |
+
_target_: morph.model.layers.embedding.Embedding
|
| 13 |
+
num_embeddings: ${len:${data.vocab.edge_tokens}}
|
| 14 |
+
embedding_dim: 64
|
| 15 |
+
out_dim: 128
|
| 16 |
+
|
| 17 |
+
time_embedding:
|
| 18 |
+
_target_: morph.model.layers.embedding.TimeEmbedding
|
| 19 |
+
embedding_dim: 128
|
| 20 |
+
out_dim: 128
|
| 21 |
+
|
| 22 |
+
node_count_embedding:
|
| 23 |
+
_target_: morph.model.layers.embedding.ExtrapolatableCountEmbedding
|
| 24 |
+
embedding_dim: 64
|
| 25 |
+
out_dim: 128
|
| 26 |
+
min_period: 128.0
|
| 27 |
+
max_period: 1024.0
|
| 28 |
+
max_count: 96.0
|
chembl3d/explicit_h/runtime/configs/embeddings/qm9.yaml
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Shared embedding stack tuned for QM9.
|
| 2 |
+
# - node_count_embedding uses ExtrapolatableCountEmbedding so the model can
|
| 3 |
+
# generalise slightly outside the training atom-count support.
|
| 4 |
+
# - Defaults sized for QM9 (training counts in [3, 29]).
|
| 5 |
+
|
| 6 |
+
atom_type_embedding:
|
| 7 |
+
_target_: morph.model.layers.embedding.Embedding
|
| 8 |
+
num_embeddings: ${len:${data.vocab.atom_tokens}}
|
| 9 |
+
embedding_dim: 64
|
| 10 |
+
out_dim: 128
|
| 11 |
+
|
| 12 |
+
edge_type_embedding:
|
| 13 |
+
_target_: morph.model.layers.embedding.Embedding
|
| 14 |
+
num_embeddings: ${len:${data.vocab.edge_tokens}}
|
| 15 |
+
embedding_dim: 64
|
| 16 |
+
out_dim: 128
|
| 17 |
+
|
| 18 |
+
time_embedding:
|
| 19 |
+
_target_: morph.model.layers.embedding.TimeEmbedding
|
| 20 |
+
embedding_dim: 128
|
| 21 |
+
out_dim: 128
|
| 22 |
+
|
| 23 |
+
node_count_embedding:
|
| 24 |
+
_target_: morph.model.layers.embedding.ExtrapolatableCountEmbedding
|
| 25 |
+
embedding_dim: 64
|
| 26 |
+
out_dim: 128
|
| 27 |
+
min_period: 64.0
|
| 28 |
+
max_period: 512.0
|
| 29 |
+
max_count: 32.0
|
chembl3d/explicit_h/runtime/configs/heads/head.yaml
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Standard molecular DiT readouts. The insertion-content head is an independent
|
| 2 |
+
# Hydra choice (``insertion_head=unimodal`` by default, or ``=gmm``).
|
| 3 |
+
|
| 4 |
+
heads:
|
| 5 |
+
_target_: morph.model.layers.heads.MultiHeadModule
|
| 6 |
+
output_init_std: ${model.head_initialization.output_std}
|
| 7 |
+
independent_adapters: ${oc.select:model.head_independent_adapters,false}
|
| 8 |
+
|
| 9 |
+
heads_configs:
|
| 10 |
+
graph_aggregation: mean
|
| 11 |
+
|
| 12 |
+
node_heads:
|
| 13 |
+
atom_type_head:
|
| 14 |
+
input_dim: ${model.head_node_dim}
|
| 15 |
+
output_dim: ${len:${data.vocab.atom_tokens}}
|
| 16 |
+
adapter_dim: ${model.head_node_adapter_dim}
|
| 17 |
+
group: state
|
| 18 |
+
|
| 19 |
+
charge_head:
|
| 20 |
+
input_dim: ${model.head_node_dim}
|
| 21 |
+
output_dim: ${len:${data.vocab.charge_tokens}}
|
| 22 |
+
adapter_dim: ${model.head_node_adapter_dim}
|
| 23 |
+
group: state
|
| 24 |
+
|
| 25 |
+
ins_rate_head:
|
| 26 |
+
input_dim: ${model.head_node_dim}
|
| 27 |
+
output_dim: 1
|
| 28 |
+
adapter_dim: ${model.head_node_adapter_dim}
|
| 29 |
+
group: edit
|
| 30 |
+
initial_rate: ${model.head_initialization.ins_rate}
|
| 31 |
+
|
| 32 |
+
do_del_head:
|
| 33 |
+
input_dim: ${model.head_node_dim}
|
| 34 |
+
output_dim: 1
|
| 35 |
+
adapter_dim: ${model.head_node_adapter_dim}
|
| 36 |
+
group: edit
|
| 37 |
+
initial_probability: ${model.head_initialization.do_del_probability}
|
| 38 |
+
|
| 39 |
+
do_sub_a_head:
|
| 40 |
+
input_dim: ${model.head_node_dim}
|
| 41 |
+
output_dim: 1
|
| 42 |
+
adapter_dim: ${model.head_node_adapter_dim}
|
| 43 |
+
group: edit
|
| 44 |
+
initial_probability: ${model.head_initialization.do_sub_a_probability}
|
| 45 |
+
|
| 46 |
+
edge_heads:
|
| 47 |
+
edge_type_head:
|
| 48 |
+
input_dim: ${model.head_edge_dim}
|
| 49 |
+
output_dim: ${len:${data.vocab.edge_tokens}}
|
| 50 |
+
adapter_dim: ${model.head_edge_adapter_dim}
|
| 51 |
+
group: state
|
| 52 |
+
|
| 53 |
+
do_sub_e_head:
|
| 54 |
+
input_dim: ${model.head_edge_dim}
|
| 55 |
+
output_dim: 1
|
| 56 |
+
adapter_dim: ${model.head_edge_adapter_dim}
|
| 57 |
+
group: state
|
| 58 |
+
initial_probability: ${model.head_initialization.do_sub_e_probability}
|
| 59 |
+
|
| 60 |
+
ins_edge_head:
|
| 61 |
+
_target_: morph.model.layers.heads.InsertionEdgeHead
|
| 62 |
+
_recursive_: false
|
| 63 |
+
hidden_dim: ${model.head_node_dim}
|
| 64 |
+
readout_dim: ${model.head_insertion_edge_dim}
|
| 65 |
+
output_init_std: ${model.head_initialization.output_std}
|
| 66 |
+
n_edge_types: ${len:${data.vocab.edge_tokens}}
|
| 67 |
+
n_atom_types: ${len:${data.vocab.atom_tokens}}
|
| 68 |
+
n_charge_types: ${len:${data.vocab.charge_tokens}}
|
| 69 |
+
rbf_embedding_args:
|
| 70 |
+
_target_: morph.model.layers.embedding.RBFEmbedding
|
| 71 |
+
num_rbf: 128
|
| 72 |
+
rbf_dmax: 3.5
|
| 73 |
+
out_dim: 64
|
| 74 |
+
dropout: 0.0
|
| 75 |
+
trainable: false
|
chembl3d/explicit_h/runtime/configs/insertion_head/gmm.yaml
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Optional mixture-density insertion-content head.
|
| 2 |
+
mode: gmm
|
| 3 |
+
dimension: 3
|
| 4 |
+
num_components: 10
|
| 5 |
+
|
| 6 |
+
model:
|
| 7 |
+
_target_: morph.model.layers.heads.GMMInsertionHead
|
| 8 |
+
hidden_dim: ${model.head_node_dim}
|
| 9 |
+
readout_dim: ${model.head_insertion_adapter_dim}
|
| 10 |
+
output_init_std: ${model.head_initialization.output_std}
|
| 11 |
+
num_components: ${insertion_head.num_components}
|
| 12 |
+
sigma_min: 0.05
|
| 13 |
+
sigma_max: 2.0
|
| 14 |
+
sigma_init: 0.5
|
| 15 |
+
N_a: ${len:${data.vocab.atom_tokens}}
|
| 16 |
+
N_c: ${len:${data.vocab.charge_tokens}}
|
chembl3d/explicit_h/runtime/configs/insertion_head/unimodal.yaml
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Default insertion-content head: one nearest pending atom per spawning node.
|
| 2 |
+
mode: unimodal
|
| 3 |
+
dimension: 3
|
| 4 |
+
num_components: 1
|
| 5 |
+
|
| 6 |
+
model:
|
| 7 |
+
_target_: morph.model.layers.heads.UnimodalInsertionHead
|
| 8 |
+
hidden_dim: ${model.head_node_dim}
|
| 9 |
+
readout_dim: ${model.head_insertion_adapter_dim}
|
| 10 |
+
output_init_std: ${model.head_initialization.output_std}
|
| 11 |
+
independent_adapters: ${oc.select:model.head_independent_adapters,false}
|
| 12 |
+
N_a: ${len:${data.vocab.atom_tokens}}
|
| 13 |
+
N_c: ${len:${data.vocab.charge_tokens}}
|
chembl3d/explicit_h/runtime/configs/integrator/integrator.yaml
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
integrator:
|
| 2 |
+
_target_: morph.flow_matching.integration.RateIntegrator
|
| 3 |
+
vocab: ${data.vocab}
|
| 4 |
+
distributions: None
|
| 5 |
+
insertion_head_mode: ${insertion_head.mode}
|
| 6 |
+
insertion_dimension: ${insertion_head.dimension}
|
| 7 |
+
insertion_num_components: ${insertion_head.num_components}
|
| 8 |
+
# strategy used for sampling time during **integration**
|
| 9 |
+
# NOTE can be different from the time strategy used for the model (see below)
|
| 10 |
+
time_strategy: log # "linear", "log", or "beta"
|
| 11 |
+
# shape params for time_strategy: beta (Beta inverse-CDF spacing).
|
| 12 |
+
# defaults match the model's beta_sched (concentration1=2.0, concentration0=1.0)
|
| 13 |
+
time_beta_alpha: 2.0
|
| 14 |
+
time_beta_beta: 1.0
|
| 15 |
+
num_integration_steps: 100
|
| 16 |
+
ins_noise_scale: ${data.ins_noise_scale}
|
| 17 |
+
# When true, freshly-inserted nodes are pushed to the t+dt marginal (kappa-mix
|
| 18 |
+
# on atom/bond types, (1-t)*ins_noise on x) to match training. Charge remains
|
| 19 |
+
# a clean prediction target rather than an interpolated state. When false,
|
| 20 |
+
# insertions enter at the model's clean prediction.
|
| 21 |
+
renoise_insertions: true
|
| 22 |
+
# Integrate the known categorical schedule hazard over every time interval
|
| 23 |
+
# instead of using its left-endpoint Euler approximation.
|
| 24 |
+
# With either method, the final interval always completes pending discrete
|
| 25 |
+
# transitions using the learned edit probabilities (no extra model call).
|
| 26 |
+
exact_schedule_integration: true
|
| 27 |
+
del_schedule: ${data.interpolator.del_schedule}
|
| 28 |
+
ins_schedule: ${data.interpolator.ins_schedule}
|
| 29 |
+
sub_schedule: ${data.interpolator.sub_schedule}
|
| 30 |
+
sub_e_schedule: ${data.interpolator.sub_e_schedule}
|
chembl3d/explicit_h/runtime/configs/logging/wandb_logger.yaml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
## Put your model args here
|
| 2 |
+
enabled: true
|
| 3 |
+
project: morph-${data.dataset_name}
|
| 4 |
+
name: ${oc.env:MORPH_RUN_ID,base}
|
| 5 |
+
save_dir: .
|
| 6 |
+
offline: false
|
chembl3d/explicit_h/runtime/configs/loss_weights/calibrated_geom.yaml
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# @package model.module.loss_weights
|
| 2 |
+
# Balance-only (geometric-mean=1) calibration weights, scaled down by a global
|
| 3 |
+
# factor of 0.2 (1/5). At the original geometric-mean=1 scale the realized
|
| 4 |
+
# full-model grad-norm was ~5, far above gradient_clip_val=1.0, so clipping
|
| 5 |
+
# fired on every step. The 0.2 factor brings the realized norm to ~1.0 so the
|
| 6 |
+
# clip becomes a spike safety-valve again. Re-running calibration now applies
|
| 7 |
+
# this scaling automatically (see trainer.target_total_grad_norm).
|
| 8 |
+
c: 0.164
|
| 9 |
+
do_del: 0.20
|
| 10 |
+
do_sub_a: 0.30
|
| 11 |
+
do_sub_e: 0.24
|
| 12 |
+
ins_e: 0.21
|
| 13 |
+
ins_e_ii: 0.22
|
| 14 |
+
ins_state: 0.14
|
| 15 |
+
ins_rate: 0.20
|
| 16 |
+
sub_a_class: 0.15
|
| 17 |
+
sub_e_class: 0.19
|
| 18 |
+
x: 0.22
|
chembl3d/explicit_h/runtime/configs/loss_weights/calibrated_geom_frozen.yaml
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# @package model.module.loss_weights
|
| 2 |
+
# Balance-only (geometric-mean = 1) loss weights — ratios only. The learning
|
| 3 |
+
# rate owns step size and gradient_clip_val stays a pure spike catch, so these
|
| 4 |
+
# are NOT scaled to a target grad-norm (unlike the older calibrated_geom.yaml,
|
| 5 |
+
# which baked in a ~0.2 global scale to hit the clip target).
|
| 6 |
+
#
|
| 7 |
+
# Source: frozen init-state calibration (trainer.calibrate_mode=loss,
|
| 8 |
+
# calibrate_freeze_model=true), geom / DiT / uncond,
|
| 9 |
+
# run 2026-06-07 (outputs/geom/uncond/cal_then_lr/2026-06-07_12-29-25/calibrate).
|
| 10 |
+
# Because the model is held at init, every probe batch is an unbiased snapshot of
|
| 11 |
+
# the initial per-term losses — so the three binary do_* losses come out equal
|
| 12 |
+
# (all BCE at p=0.5 = ln2), unlike the previous 2500-step *training* probe
|
| 13 |
+
# (calibrated_geom.yaml) which gave them different weights purely from differing
|
| 14 |
+
# training rates. See notebooks/lr_calibration_analysis.ipynb for the comparison.
|
| 15 |
+
c: 0.745146
|
| 16 |
+
do_del: 1.730107
|
| 17 |
+
do_sub_a: 1.730107
|
| 18 |
+
do_sub_e: 1.730106
|
| 19 |
+
ins_e: 0.867232
|
| 20 |
+
ins_e_ii: 0.867232
|
| 21 |
+
ins_state: 0.519379
|
| 22 |
+
ins_rate: 1.128104
|
| 23 |
+
sub_a_class: 0.522109
|
| 24 |
+
sub_e_class: 0.867232
|
| 25 |
+
x: 1.298792
|
chembl3d/explicit_h/runtime/configs/loss_weights/tuned_weights.yaml
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# @package model.module.loss_weights
|
| 2 |
+
# Canonical tuned weights for molecular training. Backbones can select this
|
| 3 |
+
# preset explicitly, and task-specific experiments may override the group. The
|
| 4 |
+
# package directive mounts the values under model.module.loss_weights.
|
| 5 |
+
do_sub_a: 0.2
|
| 6 |
+
sub_a_class: 0.03
|
| 7 |
+
do_sub_e: 0.25
|
| 8 |
+
sub_e_class: 0.5
|
| 9 |
+
do_del: 0.4
|
| 10 |
+
ins_rate: 0.15
|
| 11 |
+
ins_state: 0.01
|
| 12 |
+
ins_e: 0.4
|
| 13 |
+
x: 2.0 # was 1.0
|
| 14 |
+
c: 0.5
|
| 15 |
+
ins_e_ii: 0.03
|
chembl3d/explicit_h/runtime/configs/loss_weights/uniform.yaml
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# @package model.module.loss_weights
|
| 2 |
+
# Uniform 1.0 weight per loss component. The key set matches
|
| 3 |
+
# LightningModuleRates.LOSS_GROUPS exactly (the 11 actual loss terms
|
| 4 |
+
# emitted by shared_step).
|
| 5 |
+
do_sub_a: 1.0
|
| 6 |
+
sub_a_class: 1.0
|
| 7 |
+
do_sub_e: 1.0
|
| 8 |
+
sub_e_class: 1.0
|
| 9 |
+
do_del: 1.0
|
| 10 |
+
ins_rate: 1.0
|
| 11 |
+
ins_state: 1.0
|
| 12 |
+
ins_e: 1.0
|
| 13 |
+
ins_e_ii: 1.0
|
| 14 |
+
x: 1.0
|
| 15 |
+
c: 1.0
|
chembl3d/explicit_h/runtime/configs/model/backbones/dit.yaml
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
## Molecular DiT: the repository's first-class generative backbone.
|
| 2 |
+
##
|
| 3 |
+
## This intentionally keeps charge out of the state and adds neither an
|
| 4 |
+
## equivariant lane nor an evolving dense pair state. It retains the atom-count
|
| 5 |
+
## condition: time plus current graph size disambiguates otherwise identical
|
| 6 |
+
## local states and is especially useful while insertion/deletion is active.
|
| 7 |
+
defaults:
|
| 8 |
+
- /optimizer: muon_aux_adam
|
| 9 |
+
- /loss_weights: tuned_weights
|
| 10 |
+
- _self_
|
| 11 |
+
|
| 12 |
+
rotate: true
|
| 13 |
+
n_augmentations: 1
|
| 14 |
+
|
| 15 |
+
integrator: ${integrator.integrator}
|
| 16 |
+
|
| 17 |
+
beta_sched:
|
| 18 |
+
_target_: torch.distributions.Beta
|
| 19 |
+
concentration1: 2.0
|
| 20 |
+
concentration0: 1.0
|
| 21 |
+
|
| 22 |
+
linear_sched:
|
| 23 |
+
_target_: torch.distributions.Uniform
|
| 24 |
+
low: 0.0
|
| 25 |
+
high: 1.0
|
| 26 |
+
|
| 27 |
+
sample_time_strategy: linear
|
| 28 |
+
|
| 29 |
+
module:
|
| 30 |
+
_target_: morph.model.lightning.base.LightningModuleRates
|
| 31 |
+
insertion_head_mode: ${insertion_head.mode}
|
| 32 |
+
ins_noise_scale: ${data.ins_noise_scale}
|
| 33 |
+
ema_decay: 0.999
|
| 34 |
+
ema_warmup_steps: 5000
|
| 35 |
+
use_ema_for_eval: true
|
| 36 |
+
time_dist: ${if:${eq:${model.sample_time_strategy}, log}, ${model.beta_sched}, ${model.linear_sched}}
|
| 37 |
+
integrator: ${model.integrator}
|
| 38 |
+
vocab: ${data.vocab}
|
| 39 |
+
use_learnable_loss_weights: false
|
| 40 |
+
use_time_weights: false
|
| 41 |
+
|
| 42 |
+
model:
|
| 43 |
+
_target_: morph.model.backbones.dit.DiTBackboneWithHeads
|
| 44 |
+
_recursive_: false
|
| 45 |
+
|
| 46 |
+
atom_type_embedding_args: ${embeddings.atom_type_embedding}
|
| 47 |
+
edge_type_embedding_args: ${embeddings.edge_type_embedding}
|
| 48 |
+
time_embedding_args: ${embeddings.time_embedding}
|
| 49 |
+
node_count_embedding_args: ${embeddings.node_count_embedding}
|
| 50 |
+
# Sum the existing incident edge embeddings into each atom's input, excluding
|
| 51 |
+
# NO_BOND, through a zero-initialized projection. Uses the existing heads.
|
| 52 |
+
incident_bond_embedding: false
|
| 53 |
+
backbone_model_args: ${model.backbone}
|
| 54 |
+
heads_args: ${heads.heads}
|
| 55 |
+
insertion_head_args: ${insertion_head.model}
|
| 56 |
+
ins_edge_head_args: ${heads.ins_edge_head}
|
| 57 |
+
|
| 58 |
+
head_node_dim: ${model.backbone.out_node_nf}
|
| 59 |
+
head_edge_dim: ${model.backbone.out_edge_nf}
|
| 60 |
+
|
| 61 |
+
# Shared adapters avoid seven separate full-width MLPs. At the >150M preset,
|
| 62 |
+
# node/insertion adapters widen while pair readouts remain compact.
|
| 63 |
+
head_node_adapter_dim: 256
|
| 64 |
+
head_edge_adapter_dim: 256
|
| 65 |
+
head_insertion_adapter_dim: 256
|
| 66 |
+
head_insertion_edge_dim: 256
|
| 67 |
+
head_independent_adapters: true
|
| 68 |
+
|
| 69 |
+
# Categorical biases are replaced at module construction with exact dataset
|
| 70 |
+
# marginals. There is no dataset-level transition-frequency statistic for the
|
| 71 |
+
# binary/rate heads, so their defaults deliberately preserve the old neutral
|
| 72 |
+
# predictions instead of inventing a corpus-independent edit prior.
|
| 73 |
+
head_initialization:
|
| 74 |
+
output_std: 1.0e-3
|
| 75 |
+
coordinate_output_std: 0.0
|
| 76 |
+
do_del_probability: 0.5
|
| 77 |
+
do_sub_a_probability: 0.5
|
| 78 |
+
do_sub_e_probability: 0.5
|
| 79 |
+
ins_rate: 0.6931471805599453
|
| 80 |
+
|
| 81 |
+
token_weighting:
|
| 82 |
+
type_loss_token_weights: uniform
|
| 83 |
+
weight_alpha: 0.4
|
| 84 |
+
|
| 85 |
+
# Optional low-rank adaptation of the attention and SwiGLU projections. The
|
| 86 |
+
# adapter residual is zero at initialization, so enabling this while loading a
|
| 87 |
+
# pretrained checkpoint preserves its outputs exactly.
|
| 88 |
+
lora:
|
| 89 |
+
enabled: false
|
| 90 |
+
rank: 8
|
| 91 |
+
alpha: 8.0
|
| 92 |
+
dropout: 0.0
|
| 93 |
+
targets: [qkv, proj, mlp.gate_value, mlp.out_proj]
|
| 94 |
+
freeze_base: true
|
| 95 |
+
|
| 96 |
+
backbone:
|
| 97 |
+
_target_: morph.model.backbones.dit.DiTBackbone
|
| 98 |
+
_recursive_: false
|
| 99 |
+
d_input: ${embeddings.atom_type_embedding.out_dim}
|
| 100 |
+
d_cond: ${oc.eval:"${embeddings.time_embedding.out_dim} + ${embeddings.node_count_embedding.out_dim}"}
|
| 101 |
+
d_model: 512
|
| 102 |
+
out_node_nf: 256
|
| 103 |
+
out_edge_nf: 256
|
| 104 |
+
num_layers: 8
|
| 105 |
+
nhead: 16
|
| 106 |
+
mlp_ratio: 4.0
|
| 107 |
+
ffn_hidden_dim: null
|
| 108 |
+
ffn_multiple_of: 64
|
| 109 |
+
qk_norm: true
|
| 110 |
+
coordinate_output_init_std: ${model.head_initialization.coordinate_output_std}
|
| 111 |
+
center_coordinate_output: true
|
| 112 |
+
in_edge_nf: ${embeddings.edge_type_embedding.out_dim}
|
| 113 |
+
edge_pair_dim: 256
|
| 114 |
+
pair_bias_hidden_dim: 32
|
| 115 |
+
num_atom_types: ${len:${data.vocab.atom_tokens}}
|
| 116 |
+
lora: ${model.lora}
|
| 117 |
+
rbf_embedding_args:
|
| 118 |
+
_target_: morph.model.layers.embedding.RBFEmbedding
|
| 119 |
+
num_rbf: 128
|
| 120 |
+
rbf_dmax: 3.5
|
| 121 |
+
out_dim: 64
|
| 122 |
+
dropout: 0.0
|
| 123 |
+
trainable: false
|
chembl3d/explicit_h/runtime/configs/model/finetune/lora_finetune.yaml
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Parameter-efficient fine-tuning of the unconditional DiT.
|
| 2 |
+
defaults:
|
| 3 |
+
- /model/backbones/dit@_here_
|
| 4 |
+
- _self_
|
| 5 |
+
|
| 6 |
+
lora:
|
| 7 |
+
enabled: true
|
| 8 |
+
|
| 9 |
+
module:
|
| 10 |
+
_target_: morph.model.finetune.base.LightningModuleRatesFinetune
|
| 11 |
+
pretrained_ckpt: ${oc.env:MORPH_PRETRAINED_CKPT}
|
| 12 |
+
pretrained_prefer_ema: true
|
| 13 |
+
freeze_pretrained_backbone: true
|
chembl3d/explicit_h/runtime/configs/model/finetune/scaffold_finetune.yaml
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# DiT backbone augmented with Flamingo-style gated cross-attention for
|
| 2 |
+
# scaffold-decoration fine-tuning.
|
| 3 |
+
#
|
| 4 |
+
# Conditioning (all derived from mol_t.keep_mask):
|
| 5 |
+
# - node_cond : per-atom scaffold membership (always on)
|
| 6 |
+
# - edge_cond : per-edge scaffold class (always on)
|
| 7 |
+
# - cross-attn: scaffold subgraph encoded by a shared LightweightContextEncoder
|
| 8 |
+
# (OPTIONAL — toggle via backbone.use_cross_attn):
|
| 9 |
+
# * false → option 1: node+edge only
|
| 10 |
+
# * true → option 2: node+edge+cross-attn
|
| 11 |
+
#
|
| 12 |
+
# Inherits everything from configs/model/backbones/dit.yaml and overrides:
|
| 13 |
+
# - module._target_ → LightningModuleRatesScaffoldFinetune (loads a pretrained
|
| 14 |
+
# unconditional DiT checkpoint and adds scaffold loss/inference masking)
|
| 15 |
+
# - model._target_ → ScaffoldConditionalDiTBackboneWithHeads (auto-builds the
|
| 16 |
+
# node/edge cond and, when use_cross_attn, the scaffold cross-attn tokens)
|
| 17 |
+
# - backbone._target_ → ConditionalDiTBackbone (DiT + gated cross-attn blocks)
|
| 18 |
+
#
|
| 19 |
+
# Provides a ``pretrained_ckpt`` field for the lightning module to load.
|
| 20 |
+
defaults:
|
| 21 |
+
- /model/backbones/dit@_here_
|
| 22 |
+
- _self_
|
| 23 |
+
|
| 24 |
+
module:
|
| 25 |
+
_target_: morph.model.finetune.scaffold.LightningModuleRatesScaffoldFinetune
|
| 26 |
+
# Path to the unconditional DiT checkpoint to load weights from.
|
| 27 |
+
# ConditionalDiT keys are auto-retargeted (backbone.* → backbone.dit.*).
|
| 28 |
+
pretrained_ckpt: ${oc.env:MORPH_PRETRAINED_CKPT}
|
| 29 |
+
pretrained_prefer_ema: true
|
| 30 |
+
# If true, dispatches to ``self.model.freeze_pretrained()`` — for the
|
| 31 |
+
# cross-attn scaffold model this freezes the entire clean lane
|
| 32 |
+
# (embedding, ``backbone.dit``, all heads). Only the gated cross-attn
|
| 33 |
+
# blocks and edge gate plus the scaffold encoder train.
|
| 34 |
+
freeze_pretrained_backbone: true
|
| 35 |
+
optimizer_config:
|
| 36 |
+
optimizer:
|
| 37 |
+
lr: 1e-3
|
| 38 |
+
aux_optimizer:
|
| 39 |
+
lr: 1e-4
|
| 40 |
+
# Whether to exclude scaffold atoms from sub / x losses. These atoms are
|
| 41 |
+
# frozen at inference, so training them adds no useful signal.
|
| 42 |
+
exclude_scaffold_from_sub_loss: true
|
| 43 |
+
exclude_scaffold_from_x_loss: true
|
| 44 |
+
# Pin kept coordinates during integration and restore the input coordinate
|
| 45 |
+
# frame on output. To train flowing scaffold geometry, set this and the x-loss
|
| 46 |
+
# exclusion above to false.
|
| 47 |
+
freeze_keep_positions: true
|
| 48 |
+
|
| 49 |
+
# Override the model target to the scaffold-aware cross-attn wrapper.
|
| 50 |
+
model:
|
| 51 |
+
_target_: morph.model.finetune.scaffold.ScaffoldConditionalDiTBackboneWithHeads
|
| 52 |
+
_recursive_: false
|
| 53 |
+
|
| 54 |
+
atom_type_embedding_args: ${embeddings.atom_type_embedding}
|
| 55 |
+
edge_type_embedding_args: ${embeddings.edge_type_embedding}
|
| 56 |
+
time_embedding_args: ${embeddings.time_embedding}
|
| 57 |
+
node_count_embedding_args: ${embeddings.node_count_embedding}
|
| 58 |
+
|
| 59 |
+
backbone_model_args: ${model.backbone}
|
| 60 |
+
|
| 61 |
+
heads_args: ${heads.heads}
|
| 62 |
+
insertion_head_args: ${insertion_head.model}
|
| 63 |
+
ins_edge_head_args: ${heads.ins_edge_head}
|
| 64 |
+
|
| 65 |
+
# Cross-attention context encoder (option 2). Required because the backbone
|
| 66 |
+
# below has use_cross_attn: true. For option 1 (node+edge only), set
|
| 67 |
+
# backbone.use_cross_attn=false AND null this (the two must agree).
|
| 68 |
+
xattn_encoder_args: ${model.xattn_encoder}
|
| 69 |
+
# Geometric correspondence prior (query atom ↔ co-located scaffold token).
|
| 70 |
+
use_distance_bias: true
|
| 71 |
+
|
| 72 |
+
# Cross-attention context encoder (option 2): encodes the scaffold subgraph of
|
| 73 |
+
# mol_t (extracted via filter_nodes) into context tokens. ctx_dim must match
|
| 74 |
+
# model.backbone.ctx_dim.
|
| 75 |
+
# Node/edge conditioning is NOT configured here — the wrapper builds those directly
|
| 76 |
+
# from backbone.d_model / backbone.in_edge_nf. Null this (and set
|
| 77 |
+
# backbone.use_cross_attn=false) for node+edge-only scaffold conditioning.
|
| 78 |
+
xattn_encoder:
|
| 79 |
+
_target_: morph.model.finetune.base.LightweightContextEncoder
|
| 80 |
+
num_atom_types: ${len:${data.vocab.atom_tokens}}
|
| 81 |
+
num_charges: ${len:${data.vocab.charge_tokens}}
|
| 82 |
+
atom_embed_dim: 64
|
| 83 |
+
charge_embed_dim: 16
|
| 84 |
+
pos_embed_dim: 32
|
| 85 |
+
ctx_dim: ${model.backbone.ctx_dim}
|
| 86 |
+
num_attn_layers: 4
|
| 87 |
+
nhead: 8
|
| 88 |
+
mlp_ratio: 4.0
|
| 89 |
+
dropout: 0.0
|
| 90 |
+
# Edge-based pair bias on the encoder's self-attention (RBF distance +
|
| 91 |
+
# edge-type of the scaffold's own bonds) so tokens mix with geometry/topology
|
| 92 |
+
# awareness, like the DiT backbone. num_edge_types/edge_embed_dim only used here.
|
| 93 |
+
use_pair_bias: true
|
| 94 |
+
num_edge_types: ${len:${data.vocab.edge_tokens}}
|
| 95 |
+
edge_embed_dim: 32
|
| 96 |
+
num_rbf: 32
|
| 97 |
+
rbf_dmax: 5.0
|
| 98 |
+
pair_bias_hidden_dim: 32
|
| 99 |
+
|
| 100 |
+
# Override the backbone to use ConditionalDiTBackbone.
|
| 101 |
+
backbone:
|
| 102 |
+
_target_: morph.model.backbones.conditional_dit.ConditionalDiTBackbone
|
| 103 |
+
_recursive_: false
|
| 104 |
+
d_input: ${embeddings.atom_type_embedding.out_dim}
|
| 105 |
+
d_cond: ${oc.eval:"${embeddings.time_embedding.out_dim} + ${embeddings.node_count_embedding.out_dim}"}
|
| 106 |
+
d_model: 512
|
| 107 |
+
out_node_nf: 256
|
| 108 |
+
out_edge_nf: 256
|
| 109 |
+
num_layers: 8
|
| 110 |
+
nhead: 16
|
| 111 |
+
mlp_ratio: 4.0
|
| 112 |
+
ffn_hidden_dim: null
|
| 113 |
+
ffn_multiple_of: 64
|
| 114 |
+
qk_norm: true
|
| 115 |
+
coordinate_output_init_std: ${model.head_initialization.coordinate_output_std}
|
| 116 |
+
center_coordinate_output: true
|
| 117 |
+
in_edge_nf: ${embeddings.edge_type_embedding.out_dim}
|
| 118 |
+
edge_pair_dim: 256
|
| 119 |
+
pair_bias_hidden_dim: 32
|
| 120 |
+
num_atom_types: ${len:${data.vocab.atom_tokens}}
|
| 121 |
+
lora: ${model.lora}
|
| 122 |
+
ctx_dim: 128
|
| 123 |
+
use_cross_attn: true # cross-attention (cond_tokens) port
|
| 124 |
+
# Per-node conditioning: scaffold membership (which atoms to keep).
|
| 125 |
+
use_node_cond: true
|
| 126 |
+
# Per-edge conditioning: the scaffold encoder emits edge_cond at in_edge_nf.
|
| 127 |
+
use_edge_cond: true
|
| 128 |
+
rbf_embedding_args:
|
| 129 |
+
_target_: morph.model.layers.embedding.RBFEmbedding
|
| 130 |
+
num_rbf: 128
|
| 131 |
+
rbf_dmax: 3.5
|
| 132 |
+
out_dim: 64
|
| 133 |
+
dropout: 0.0
|
| 134 |
+
trainable: false
|
chembl3d/explicit_h/runtime/configs/optimizer/muon_aux_adam.yaml
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# @package model.module.optimizer_config
|
| 2 |
+
# Muon + auxiliary AdamW preset for the molecular DiT. Per-run overrides
|
| 3 |
+
# go on ``model.module.optimizer_config``:
|
| 4 |
+
# model.module.optimizer_config.optimizer.lr=0.005 ...
|
| 5 |
+
implementation: torch
|
| 6 |
+
|
| 7 |
+
optimizer:
|
| 8 |
+
# no _target_ -> triggers Muon path in configure_optimizers
|
| 9 |
+
# The RTX 4090 Euler template selects native-arithmetic CUDA graph replay.
|
| 10 |
+
execution: ${oc.env:MORPH_MUON_EXECUTION,native}
|
| 11 |
+
lr: 0.01
|
| 12 |
+
momentum: 0.95
|
| 13 |
+
weight_decay: 0.0
|
| 14 |
+
nesterov: true
|
| 15 |
+
ns_steps: 5
|
| 16 |
+
eps: 1e-7
|
| 17 |
+
adjust_lr_fn: original
|
| 18 |
+
|
| 19 |
+
aux_optimizer:
|
| 20 |
+
lr: 3e-4
|
| 21 |
+
betas: [0.9, 0.95]
|
| 22 |
+
eps: 1e-10
|
| 23 |
+
weight_decay: 0.0
|
| 24 |
+
|
| 25 |
+
# Lightning's two-optimizer path uses manual optimization, so clipping is
|
| 26 |
+
# configured here and applied once to the full model gradient.
|
| 27 |
+
gradient_clip_val: 1.0
|
| 28 |
+
gradient_clip_algorithm: norm
|
| 29 |
+
|
| 30 |
+
scheduler:
|
| 31 |
+
_target_: morph.utils.lr_schedulers.MultiGroupCosineWarmupLR
|
| 32 |
+
# configure_optimizers injects each native optimizer here.
|
| 33 |
+
warmup_steps: 2000 # Typically 2-5% of total_steps
|
| 34 |
+
total_steps: 200000 # The total number of training iterations
|
| 35 |
+
min_lr_fraction: 0.05 # Decays to 5% of each optimizer's peak LR
|
| 36 |
+
last_epoch: -1
|
| 37 |
+
|
| 38 |
+
interval: step
|
| 39 |
+
monitor: null
|
chembl3d/explicit_h/runtime/configs/schedule/beta.yaml
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
_target_: morph.flow_matching.schedules.BetaSchedule
|
| 2 |
+
k_alpha: 1.0
|
| 3 |
+
k_beta: 1.5
|
chembl3d/explicit_h/runtime/configs/schedule/cubic.yaml
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
_target_: morph.flow_matching.schedules.CubicSchedule
|
chembl3d/explicit_h/runtime/configs/schedule/fast_power.yaml
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Default kappa schedule: FastPowerSchedule with beta=2.5
|
| 2 |
+
_target_: morph.flow_matching.schedules.FastPowerSchedule
|
| 3 |
+
beta: 2.0
|
chembl3d/explicit_h/runtime/configs/schedule/linear.yaml
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
_target_: morph.flow_matching.schedules.LinearSchedule
|
chembl3d/explicit_h/runtime/configs/schedule/smoothstep.yaml
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
_target_: morph.flow_matching.schedules.SmoothstepSchedule
|
| 2 |
+
shift: 0.8
|
chembl3d/explicit_h/runtime/configs/trainer/trainer.yaml
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
## Put your trainer args here
|
| 2 |
+
trainer:
|
| 3 |
+
deterministic: warn
|
| 4 |
+
devices: auto
|
| 5 |
+
accelerator: auto
|
| 6 |
+
#precision: 32
|
| 7 |
+
strategy: ddp_find_unused_parameters_false
|
| 8 |
+
precision: "bf16-mixed"
|
| 9 |
+
max_epochs: 3000
|
| 10 |
+
# Effective global batch = devices * batch_size.train * accumulation. The
|
| 11 |
+
# native optimizer mixin handles this because it steps Muon + AdamW.
|
| 12 |
+
accumulate_grad_batches: 1
|
| 13 |
+
# A validation batch performs full generative integration, so Lightning's
|
| 14 |
+
# pre-training sanity pass is expensive and does not exercise optimization.
|
| 15 |
+
num_sanity_val_steps: 0
|
| 16 |
+
# Muon + AdamW use Lightning manual optimization; clipping is configured in
|
| 17 |
+
# configs/optimizer/muon_aux_adam.yaml and applied to both branches together.
|
| 18 |
+
gradient_clip_val: null
|
| 19 |
+
gradient_clip_algorithm: norm
|
| 20 |
+
|
| 21 |
+
# Validation performs full generative integration. The established QM9
|
| 22 |
+
# training recipes use this cadence so sampling does not dominate fitting.
|
| 23 |
+
check_val_every_n_epoch: 50
|
| 24 |
+
# profiler: simple
|
| 25 |
+
|
| 26 |
+
# Fixed GLOBAL validation population. run.py converts this to a per-rank
|
| 27 |
+
# limit, giving the same 4096 molecules on 1, 2 or 4 GPUs.
|
| 28 |
+
num_val_samples: 4096
|
| 29 |
+
# Explicit profiling escape hatch; a non-null value takes precedence over
|
| 30 |
+
# num_val_samples and is interpreted by Lightning per rank.
|
| 31 |
+
limit_val_batches: null
|
| 32 |
+
limit_predict_batches: 4
|
| 33 |
+
|
| 34 |
+
do_train: true
|
| 35 |
+
do_validate: true
|
| 36 |
+
do_predict: true
|
| 37 |
+
checkpoint_path: null
|
chembl3d/explicit_h/runtime/pyproject.toml
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[project]
|
| 2 |
+
name = "morph"
|
| 3 |
+
version = "0.1.0"
|
| 4 |
+
description = "Flow-matching molecular generation and scaffold decoration with a molecular DiT"
|
| 5 |
+
readme = "README.md"
|
| 6 |
+
keywords = ["molecular-generation", "flow-matching", "scaffold-decoration"]
|
| 7 |
+
requires-python = ">=3.11"
|
| 8 |
+
dependencies = [
|
| 9 |
+
"click>=8.3.1",
|
| 10 |
+
"hydra-core>=1.3.2",
|
| 11 |
+
"lightning>=2.5.5",
|
| 12 |
+
"omegaconf>=2.3.0",
|
| 13 |
+
"numpy>=2.0",
|
| 14 |
+
"pandas>=2.3.3",
|
| 15 |
+
"rdkit>=2025.9.1",
|
| 16 |
+
"scipy>=1.15.0",
|
| 17 |
+
# Pre-release allowed: the NVIDIA 25.10 container ships a CUDA-enabled
|
| 18 |
+
# 2.9.0a0 build. Preserve that container build instead of replacing it with
|
| 19 |
+
# a mismatched PyPI Torch/torchvision pair.
|
| 20 |
+
"torch>=2.9.0a0, <2.9.1",
|
| 21 |
+
"torch-geometric>=2.7.0",
|
| 22 |
+
"torchmetrics>=1.8.0",
|
| 23 |
+
"wandb>=0.22.2",
|
| 24 |
+
"posebusters>=0.6.0",
|
| 25 |
+
"matplotlib>=3.10.8",
|
| 26 |
+
"lmdb>=2.2.0",
|
| 27 |
+
"py3dmol>=2.5.4",
|
| 28 |
+
"ipykernel>=7.2.0",
|
| 29 |
+
"ase>=3.28.0",
|
| 30 |
+
]
|
| 31 |
+
|
| 32 |
+
[dependency-groups]
|
| 33 |
+
dev = ["pytest>=9.1.1", "ruff>=0.16.5"]
|
| 34 |
+
|
| 35 |
+
[project.optional-dependencies]
|
| 36 |
+
flash-attn = ["flash-attn>=2.8.3, <2.8.4"]
|
| 37 |
+
|
| 38 |
+
[project.scripts]
|
| 39 |
+
morph = "morph.cli:cli"
|
| 40 |
+
|
| 41 |
+
[tool.uv.extra-build-dependencies]
|
| 42 |
+
flash-attn = [{ requirement = "torch", match-runtime = true }]
|
| 43 |
+
|
| 44 |
+
[build-system]
|
| 45 |
+
requires = ["hatchling"]
|
| 46 |
+
build-backend = "hatchling.build"
|
| 47 |
+
|
| 48 |
+
[tool.hatch.build.targets.wheel]
|
| 49 |
+
packages = ["src/morph"]
|
| 50 |
+
|
| 51 |
+
[tool.hatch.build.targets.wheel.force-include]
|
| 52 |
+
"configs" = "morph/configs"
|
| 53 |
+
|
| 54 |
+
[tool.ruff]
|
| 55 |
+
# Allow lines to be as long as 88.
|
| 56 |
+
line-length = 88
|
| 57 |
+
# Keep attributed third-party implementations close to their upstream sources.
|
| 58 |
+
extend-exclude = ["src/morph/_vendor"]
|
| 59 |
+
|
| 60 |
+
[tool.ruff.format]
|
| 61 |
+
docstring-code-format = true
|
| 62 |
+
|
| 63 |
+
# Correctness, readability, and public API documentation.
|
| 64 |
+
[tool.ruff.lint]
|
| 65 |
+
# consider selecting "ISC", for implicit string concat
|
| 66 |
+
# consider selecting "T201", for print in production code
|
| 67 |
+
select = [
|
| 68 |
+
"B", # flake8-bugbear
|
| 69 |
+
"C4", # flake8-comprehensions
|
| 70 |
+
"D", # pydocstyle
|
| 71 |
+
"E", # pycodestyle error
|
| 72 |
+
"E501", # line too long
|
| 73 |
+
"EXE", # flake8-executable
|
| 74 |
+
"F", # pyflakes
|
| 75 |
+
"FLY", # flynt
|
| 76 |
+
"I", # isort
|
| 77 |
+
"ICN", # flake8-import-conventions
|
| 78 |
+
"PD", # pandas-vet
|
| 79 |
+
"PERF", # perflint
|
| 80 |
+
"PIE", # flake8-pie
|
| 81 |
+
"PL", # pylint
|
| 82 |
+
"PT", # flake8-pytest-style
|
| 83 |
+
"PYI", # flakes8-pyi
|
| 84 |
+
"Q", # flake8-quotes
|
| 85 |
+
"RSE", # flake8-raise
|
| 86 |
+
"RUF", # Ruff-specific rules
|
| 87 |
+
"SIM", # flake8-simplify
|
| 88 |
+
"SLOT", # flakes8-slot
|
| 89 |
+
"TCH", # flake8-type-checking
|
| 90 |
+
"TID", # flake8-tidy-imports
|
| 91 |
+
"UP", # pyupgrade
|
| 92 |
+
"W", # pycodestyle warning
|
| 93 |
+
"W505", # line too long
|
| 94 |
+
"YTT", # flake8-2020
|
| 95 |
+
]
|
| 96 |
+
# also consider ignoring "C901", for high mccabe function complexity
|
| 97 |
+
# also consider ignoring "ERA001", for found commented out code
|
| 98 |
+
ignore = [
|
| 99 |
+
"EXE002", # shebang is not a python interpreter
|
| 100 |
+
"RUF012", # missing classvar type
|
| 101 |
+
"RUF013", # Optional prohibited by PEP484
|
| 102 |
+
"B006", # Do not use mutable data structures for argument defaults
|
| 103 |
+
"ANN003", # missing type annotations for kwargs
|
| 104 |
+
"B019", # Use of functools.lru_cache on methods can lead to memory leaks
|
| 105 |
+
"C408", # unnecessary-collection-call
|
| 106 |
+
"COM812", # trailing comma missing
|
| 107 |
+
"D205", # 1 blank line required between summary line and description
|
| 108 |
+
"D415", # first line of docstring must end with punctuation
|
| 109 |
+
"DTZ005", # use of datetime.now() without timezone
|
| 110 |
+
"E731", # do not assign a lambda expression, use a def
|
| 111 |
+
"EM", # flake8-errmsg
|
| 112 |
+
"FBT001", # Boolean positional argument in function
|
| 113 |
+
"FBT002", # Boolean keyword argument in function
|
| 114 |
+
"NPY002", # TODO replace legacy np.random.seed
|
| 115 |
+
"PLR", # pylint refactor
|
| 116 |
+
"PLW2901", # Outer for loop variable overwritten by inner assignment target
|
| 117 |
+
"PT006", # pytest-parametrize-names-wrong-type
|
| 118 |
+
"PT011", # pytest-raises-too-broad
|
| 119 |
+
"PT013", # pytest-incorrect-pytest-import
|
| 120 |
+
"PT019", # pytest-fixture-param-without-value
|
| 121 |
+
"PTH", # prefer Path to os.path
|
| 122 |
+
"S301", # pickle can be unsafe
|
| 123 |
+
"S310", # url open functions can be unsafe
|
| 124 |
+
"TRY003", # long exception messages not defined in the exception class itself
|
| 125 |
+
"UP015", # unnecessary "r" in open call
|
| 126 |
+
"D107", # docstring for __init__
|
| 127 |
+
"D212", # remove whitespace after opening quotes
|
| 128 |
+
"D202", # no blank lines after docstring
|
| 129 |
+
"D301",
|
| 130 |
+
"D100",
|
| 131 |
+
"D103",
|
| 132 |
+
]
|
| 133 |
+
pydocstyle.convention = "google"
|
| 134 |
+
|
| 135 |
+
[env]
|
| 136 |
+
PROJECT_ROOT = "."
|
chembl3d/explicit_h/runtime/requirements-tested.txt
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torch==2.9.0
|
| 2 |
+
numpy==2.4.2
|
| 3 |
+
rdkit==2025.9.6
|
| 4 |
+
lightning==2.6.1
|
| 5 |
+
pytorch-lightning==2.6.1
|
| 6 |
+
torch-geometric==2.7.0
|
| 7 |
+
torchmetrics==1.8.2
|
| 8 |
+
hydra-core==1.3.2
|
| 9 |
+
omegaconf==2.3.0
|
| 10 |
+
scipy==1.17.1
|
| 11 |
+
pandas==3.0.1
|
| 12 |
+
wandb==0.25.0
|
| 13 |
+
posebusters==0.6.5
|
| 14 |
+
matplotlib==3.10.9
|
| 15 |
+
lmdb==2.2.0
|
| 16 |
+
click==8.3.1
|
| 17 |
+
py3dmol==2.5.4
|
| 18 |
+
ipykernel==7.2.0
|
| 19 |
+
ase==3.28.0
|