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
Download src/rfd4_proteina_prep/processing.py from nvidia/RFD4-Proteina-prep: direct link, hf CLI and curl.
- Browser
- Download file 6.69 kB
-
https://huggingface.co/nvidia/RFD4-Proteina-prep/resolve/main/src/rfd4_proteina_prep/processing.py
- Command line
-
hf download hf://nvidia/RFD4-Proteina-prep/src/rfd4_proteina_prep/processing.py
-
curl -L -o processing.py https://huggingface.co/nvidia/RFD4-Proteina-prep/resolve/main/src/rfd4_proteina_prep/processing.py
6.69 kB
| # Copyright 2026 The HuggingFace Team. All rights reserved. | |
| # RFD4-derived preprocessing: Copyright (c) 2026, Institute for Protein Design, | |
| # University of Washington (BSD-3-Clause). See THIRD_PARTY_LICENSES.txt. | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 | |
| """AtomWorks adapter: annotated CIF -> prepared tensors for `RFD4ProteinaPipeline`, and generated tensors -> CIF.""" | |
| import os | |
| import random | |
| import tempfile | |
| from pathlib import Path | |
| import numpy as np | |
| import torch | |
| def prepare_cif(path: str | Path, *, seed: int, device: torch.device, path_length_cutoff: int = 64): | |
| """Return a single-example feature batch and its atom mapping, without changing the caller's RNG state. | |
| `device` is where the returned tensors live; pass the pipeline's device so no copy is needed at call time. | |
| """ | |
| try: | |
| from atomworks.io.parser import parse | |
| except ImportError as error: | |
| raise ImportError( | |
| "Annotated CIF inputs require the optional AtomWorks package. Install atomworks[ml]; RFD4 is not required." | |
| ) from error | |
| # Import before parsing to register the model's condition and provenance annotations. | |
| from ._cif.datapipes_pipeline import build_design_validation_pipeline | |
| from ._cif.datapipes_schema_proteina import SPECS | |
| from ._cif.utils_constants import LOCAL_LATENT_RESIDUES | |
| from ._cif.utils_parse_presets import ANNOTATED_CIF | |
| path = Path(path) | |
| if not path.is_file(): | |
| raise FileNotFoundError(path) | |
| numpy_state, python_state = np.random.get_state(), random.getstate() | |
| try: | |
| with torch.random.fork_rng(devices=[]): | |
| np.random.seed(seed % 2**32) | |
| random.seed(seed) | |
| torch.random.default_generator.manual_seed(seed) | |
| parsed = parse(path, config=ANNOTATED_CIF) | |
| data = {"atom_array": parsed["assemblies"]["1"][0], "example_id": path.stem} | |
| # The RFD4 datapipe itself, in its inference mode: there is one pipeline here, not a | |
| # second one maintained alongside it. `build_design_validation_pipeline` is | |
| # `build_design_train_pipeline(is_inference=True)`. | |
| # | |
| # `na_ss_conditioning` turns on nucleic-acid secondary-structure featurization. RFD4 | |
| # gates that on the metrics it is about to compute, and its own inference default is | |
| # no tags at all -- but an annotated CIF can carry NA secondary-structure conditioning | |
| # in the file, and without this it is parsed and then silently discarded, leaving the | |
| # model's `na_ss_state` and `na_ss_pair` inputs at their defaults. This is RFD4's own | |
| # switch, set the way RFD4's `ss_conditioned` config sets it, not a flag invented here. | |
| pipeline = build_design_validation_pipeline( | |
| metrics_tags=["na_ss_conditioning"], | |
| residue_names_supporting_local_latents=list(LOCAL_LATENT_RESIDUES), | |
| path_length_search_cutoff=path_length_cutoff, | |
| expandable_segment_rng=np.random.default_rng(seed), | |
| ) | |
| data = pipeline(data) | |
| finally: | |
| np.random.set_state(numpy_state) | |
| random.setstate(python_state) | |
| # Match the released schema, including padding of auxiliary fields and absent conditions. | |
| tensors = { | |
| key: torch.as_tensor(value, dtype=SPECS[key][0] if key in SPECS else None) | |
| for key, value in data["feats"].items() | |
| if isinstance(value, (torch.Tensor, np.ndarray)) | |
| } | |
| length = max( | |
| tensor.shape[axis] | |
| for key, tensor in tensors.items() | |
| for axis, dimension in enumerate(SPECS[key][2].split() if key in SPECS else ["I"]) | |
| if dimension == "I" and axis < tensor.ndim | |
| ) | |
| features = {} | |
| for key, tensor in tensors.items(): | |
| dtype, default, dimensions = SPECS.get(key, (tensor.dtype, 0, "I")) | |
| axes = dimensions.split() | |
| padding = [] | |
| for axis in reversed(range(tensor.ndim)): | |
| padding.extend((0, length - tensor.shape[axis] if axis < len(axes) and axes[axis] == "I" else 0)) | |
| if any(padding): | |
| tensor = torch.nn.functional.pad(tensor, padding, value=default) | |
| features[key] = tensor.unsqueeze(0).to(device) | |
| for key, (dtype, default, dimensions) in SPECS.items(): | |
| if key not in features: | |
| shape = (1, *(length if dimension == "I" else int(dimension) for dimension in dimensions.split())) | |
| features[key] = torch.full(shape, default, dtype=dtype, device=device) | |
| features["padding_mask"] = torch.ones((1, length), dtype=torch.bool, device=device) | |
| features["coords_nm"] = features["coords"] / 10.0 | |
| features["coord_mask"] = features["atom37_mask"] | |
| return features, data["atom_array"] | |
| def write_cif(output, features, atom_array, path: str | Path, row: int = 0) -> str: | |
| """Write the generated atoms of an `RFD4ProteinaPipelineOutput` to a new annotated CIF. | |
| Conditioning annotations and provenance are preserved and predicted unindexed-motif insertion mappings are added. | |
| `features` and `atom_array` are the values returned by `prepare_cif`. Coordinates are converted from nanometers to | |
| angstroms. For a batched output (the features repeated for several designs), `row` selects the design to write. | |
| An existing `path` is never overwritten. | |
| """ | |
| from ._cif.transforms_unindexing import annotate_unindexed_insertion | |
| from ._cif.utils_structure_export import process_single_sample_to_atom_array, write_generated_structure_cif | |
| path = Path(path) | |
| sample = process_single_sample_to_atom_array( | |
| coords=output.coords_nm[row].detach().cpu() * 10, | |
| residue_type=output.residue_type[row].detach().cpu(), | |
| padding_mask=features["padding_mask"][row].detach().cpu(), | |
| atom_mask=output.atom_mask[row].detach().cpu(), | |
| input_atom_array=atom_array, | |
| insertion_logits=output.insertion_logits[row].detach().cpu(), | |
| ) | |
| array = sample["full_array_with_unindexed"] | |
| if array is None: | |
| array = sample["generated_atom_array"] | |
| array = annotate_unindexed_insertion(array, sample["predicted_insertion_mapping"]) | |
| # Publish a complete file without overwriting any existing output, including a racing writer. | |
| with tempfile.TemporaryDirectory(dir=path.parent, prefix=".rfd4-cif-") as temporary: | |
| staged = Path(temporary) / path.name | |
| write_generated_structure_cif(array, staged) | |
| os.link(staged, path) | |
| return str(path) | |