Instructions to use nvidia/RFD4-Proteina-prep with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Diffusers
How to use nvidia/RFD4-Proteina-prep with Diffusers:
pip install -U diffusers transformers accelerate
import torch from diffusers import DiffusionPipeline # switch to "mps" for apple devices pipe = DiffusionPipeline.from_pretrained("nvidia/RFD4-Proteina-prep", dtype=torch.bfloat16, device_map="cuda") prompt = "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k" image = pipe(prompt).images[0] - Notebooks
- Google Colab
- Kaggle
Document and test RFD4's batching contract for several designs
Browse filesNo change to how write_cif indexes anything. RFD4 replicates at collate time --
datapipes/collate.py does `[sample for sample in samples for _ in
range(num_replicates)]` -- so every per-row quantity already has one row per
design by the time anything reads it, and the export loop in proteina.py reads
row i of each. write_cif already did the same thing, and keeping it that way is
the point: this package indexes the way RFD4 indexes.
What was missing was saying so, and testing it. The README example replicated
with `repeat`, which tiles; an input's designs sit together, so it must be
`repeat_interleave`, and the difference is invisible for one input and wrong
for more than one. It now also shows `num_designs_per_prompt` alongside the
replication, since a pipeline repeating its input internally still leaves the
caller to replicate the features it passes back in.
Tests cover one and three designs with replicated features, that unreplicated
features raise IndexError on the second design rather than quietly reading
another row, and that a row past the end raises.
An earlier draft of this added a helper deriving the design count from the
feature and output shapes, so that unreplicated features would work. That was
invention: it is not what RFD4 does, it would have been one more thing to keep
in step with upstream, and the first version of it silently read the wrong
input's mask for batches of more than one input. Following the reference is
cheaper than being clever next to it.
Verified against the Diffusers pipeline on branch rfd4-torch: prepare_cif's
features pass its check_inputs with num_designs_per_prompt=3, three designs
write from replicated features, and the unreplicated case raises.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- README.md +19 -5
- tests/test_write_cif_designs.py +118 -0
|
@@ -202,11 +202,25 @@ provide is the rest of a training run: the dataset loader, the task configuratio
|
|
| 202 |
|
| 203 |
Source attribution and third-party licenses: [THIRD_PARTY_LICENSES.txt](THIRD_PARTY_LICENSES.txt).
|
| 204 |
|
| 205 |
-
To generate several designs of one input
|
|
|
|
|
|
|
| 206 |
|
| 207 |
```python
|
| 208 |
-
|
| 209 |
-
output = pipe(
|
| 210 |
-
|
| 211 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 212 |
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 202 |
|
| 203 |
Source attribution and third-party licenses: [THIRD_PARTY_LICENSES.txt](THIRD_PARTY_LICENSES.txt).
|
| 204 |
|
| 205 |
+
To generate several designs of one input, ask the pipeline for them and replicate the features to match, which is
|
| 206 |
+
what RFD4 itself does: its collate step repeats each sample `num_replicates` times before batching, so every
|
| 207 |
+
per-row quantity has one row per design and `row` indexes them all alike.
|
| 208 |
|
| 209 |
```python
|
| 210 |
+
designs = 8
|
| 211 |
+
output = pipe(
|
| 212 |
+
features,
|
| 213 |
+
num_designs_per_prompt=designs,
|
| 214 |
+
generator=[torch.Generator("cuda:0").manual_seed(seed) for seed in range(designs)],
|
| 215 |
+
)
|
| 216 |
+
replicated = {
|
| 217 |
+
name: value.repeat_interleave(designs, dim=0) if torch.is_tensor(value) else value
|
| 218 |
+
for name, value in features.items()
|
| 219 |
+
}
|
| 220 |
+
for row in range(designs):
|
| 221 |
+
write_cif(output, replicated, atoms, f"design_{row}.cif", row=row)
|
| 222 |
```
|
| 223 |
+
|
| 224 |
+
Use `repeat_interleave`, not `repeat`: an input's designs sit together, so output row `r` belongs to input
|
| 225 |
+
`r // designs`. Passing the features unreplicated raises `IndexError` on the second design rather than writing the
|
| 226 |
+
wrong row.
|
|
@@ -0,0 +1,118 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""`write_cif` across several designs, following RFD4's own batching contract.
|
| 2 |
+
|
| 3 |
+
RFD4 replicates at collate time -- `datapipes/collate.py` does
|
| 4 |
+
`[sample for sample in samples for _ in range(num_replicates)]` -- so by the time anything is
|
| 5 |
+
indexed, every per-row quantity already has one row per design, and the export loop in
|
| 6 |
+
`proteina.py` reads row `i` of each. `write_cif` does the same: `row` indexes both the output and
|
| 7 |
+
the features, so the caller passes as many feature rows as the output has rows.
|
| 8 |
+
|
| 9 |
+
These cover that contract, and that passing too few rows fails loudly rather than reading some
|
| 10 |
+
other row's mask.
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
import tempfile
|
| 14 |
+
import types
|
| 15 |
+
from pathlib import Path
|
| 16 |
+
|
| 17 |
+
import pytest
|
| 18 |
+
|
| 19 |
+
ROOT = Path(__file__).resolve().parents[1]
|
| 20 |
+
INPUT = ROOT / "examples/inputs/inverse_folding.cif"
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def _prepared():
|
| 24 |
+
pytest.importorskip("atomworks")
|
| 25 |
+
import torch
|
| 26 |
+
|
| 27 |
+
from rfd4_proteina_prep import prepare_cif
|
| 28 |
+
|
| 29 |
+
if not INPUT.is_file():
|
| 30 |
+
pytest.skip(f"{INPUT.name} is not present")
|
| 31 |
+
return prepare_cif(INPUT, seed=0, device=torch.device("cpu"))
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def _replicate(features, n_designs: int):
|
| 35 |
+
"""What RFD4's collate does, and what a pipeline repeating its input leaves the caller to do.
|
| 36 |
+
|
| 37 |
+
`repeat_interleave` rather than `repeat`: an input's designs sit together, so output row `r`
|
| 38 |
+
belongs to input `r // n_designs`. Tiling would pair the rows differently, and for more than
|
| 39 |
+
one input, wrongly.
|
| 40 |
+
"""
|
| 41 |
+
return {
|
| 42 |
+
name: value.repeat_interleave(n_designs, dim=0) if hasattr(value, "repeat_interleave") else value
|
| 43 |
+
for name, value in features.items()
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def _output_of(features, n_designs: int):
|
| 48 |
+
"""The shape a pipeline returns for `n_designs` designs of one input.
|
| 49 |
+
|
| 50 |
+
Every design carries the input's own coordinates, so the written files must agree; what is
|
| 51 |
+
under test is which row is read, not what a model would have produced.
|
| 52 |
+
"""
|
| 53 |
+
import torch
|
| 54 |
+
|
| 55 |
+
tokens = features["coords"].shape[1]
|
| 56 |
+
repeat = lambda tensor: tensor.repeat_interleave(n_designs, dim=0) # noqa: E731 - matches the pipeline
|
| 57 |
+
return types.SimpleNamespace(
|
| 58 |
+
coords_nm=repeat(features["coords_nm"]),
|
| 59 |
+
residue_type=repeat(features["residue_type"]),
|
| 60 |
+
atom_mask=repeat(features["atom37_mask"]),
|
| 61 |
+
insertion_logits=torch.zeros((n_designs, tokens, tokens)),
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
@pytest.mark.parametrize("n_designs", [1, 3])
|
| 66 |
+
def test_every_design_is_written(n_designs):
|
| 67 |
+
"""Features replicated to match the output, one file per design."""
|
| 68 |
+
from rfd4_proteina_prep import write_cif
|
| 69 |
+
|
| 70 |
+
features, atoms = _prepared()
|
| 71 |
+
assert features["padding_mask"].shape[0] == 1, "prepare_cif returns a single row"
|
| 72 |
+
replicated = _replicate(features, n_designs)
|
| 73 |
+
assert replicated["padding_mask"].shape[0] == n_designs
|
| 74 |
+
output = _output_of(features, n_designs)
|
| 75 |
+
|
| 76 |
+
with tempfile.TemporaryDirectory() as directory:
|
| 77 |
+
written = [
|
| 78 |
+
Path(write_cif(output, replicated, atoms, Path(directory) / f"design_{row}.cif", row=row))
|
| 79 |
+
for row in range(n_designs)
|
| 80 |
+
]
|
| 81 |
+
assert all(path.is_file() for path in written)
|
| 82 |
+
# Same coordinates in every design, so the files agree apart from the lines the writer
|
| 83 |
+
# derives from the output path and the clock.
|
| 84 |
+
skip = ("_entry.time", "_entry.id", "data_")
|
| 85 |
+
bodies = {
|
| 86 |
+
tuple(line for line in path.read_text().splitlines() if not line.startswith(skip)) for path in written
|
| 87 |
+
}
|
| 88 |
+
assert len(bodies) == 1, "designs of one input differ beyond their file-derived lines"
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def test_unreplicated_features_fail_loudly():
|
| 92 |
+
"""Too few feature rows raises rather than quietly reading another row.
|
| 93 |
+
|
| 94 |
+
This is what a caller sees when the pipeline repeated its input internally and they passed the
|
| 95 |
+
features straight through. Row 0 happens to work, because one feature row is the row it wants;
|
| 96 |
+
every later row must fail rather than succeed on the wrong data.
|
| 97 |
+
"""
|
| 98 |
+
from rfd4_proteina_prep import write_cif
|
| 99 |
+
|
| 100 |
+
features, atoms = _prepared()
|
| 101 |
+
output = _output_of(features, 3)
|
| 102 |
+
with tempfile.TemporaryDirectory() as directory:
|
| 103 |
+
write_cif(output, features, atoms, Path(directory) / "design_0.cif", row=0)
|
| 104 |
+
with pytest.raises(IndexError):
|
| 105 |
+
write_cif(output, features, atoms, Path(directory) / "design_1.cif", row=1)
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def test_row_beyond_the_output_is_an_error():
|
| 109 |
+
"""A row past the designs that exist fails rather than writing design 0 again."""
|
| 110 |
+
from rfd4_proteina_prep import write_cif
|
| 111 |
+
|
| 112 |
+
features, atoms = _prepared()
|
| 113 |
+
n_designs = 2
|
| 114 |
+
output = _output_of(features, n_designs)
|
| 115 |
+
replicated = _replicate(features, n_designs)
|
| 116 |
+
with tempfile.TemporaryDirectory() as directory:
|
| 117 |
+
with pytest.raises(IndexError):
|
| 118 |
+
write_cif(output, replicated, atoms, Path(directory) / "design_9.cif", row=9)
|