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
Type glycans and non-canonical residues, and drop the deprecated AtomWorks call
Browse filesTokenType carried only five members while the RFD4 repository has seven, and nothing ever assigned GLYCAN or NCAA. A glycan or non-canonical residue was therefore typed as protein, unknown or atomized, which hands the decoder the wrong per-token-type residue restriction - on a release whose point is that it handles them. Ports the two assignments from datapipes/bonds.py along with the 13 glycan and 100 NCAA residue names.
Replaces get_annotation_2d_categories() with get_annotation_categories(n_body=2) at the two call sites that still used it, matching main and silencing the DeprecationWarning AtomWorks 3.0.3 emits on every run.
check_vendored.py now compares vendored content against the source by default, rather than only comparing the recorded commit against the current one. The pin check cannot catch a header that was updated without the content being reconciled, which is how the above slipped through.
Output-neutral on the nine example inputs: all 720 tensors bit-identical. Glycan and NCAA typing is newly reachable but is not exercised by those examples.
|
@@ -27,6 +27,33 @@ from .datapipes_schema_proteina import TokenType
|
|
| 27 |
|
| 28 |
logger = logging.getLogger(__name__)
|
| 29 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
_UNKNOWN_RES_BY_TOKEN_TYPE = {TokenType.PROTEIN: UNKNOWN_AA, TokenType.RNA: UNKNOWN_RNA, TokenType.DNA: UNKNOWN_DNA}
|
| 31 |
|
| 32 |
_AROMATIC_BOND_TYPES = (
|
|
@@ -54,6 +81,8 @@ def _token_type_at(atom_array: AtomArray, atom_idxs: np.ndarray) -> np.ndarray:
|
|
| 54 |
types[atom_array.get_annotation("is_standard_unknown_or_masked_dna")[atom_idxs]] = TokenType.DNA
|
| 55 |
types[atom_array.get_annotation("is_standard_unknown_or_masked_rna")[atom_idxs]] = TokenType.RNA
|
| 56 |
types[atom_array.get_annotation("is_standard_unknown_or_masked_aa")[atom_idxs]] = TokenType.PROTEIN
|
|
|
|
|
|
|
| 57 |
types[atom_array.atomize[atom_idxs]] = TokenType.ATOMIZED
|
| 58 |
return types
|
| 59 |
|
|
|
|
| 27 |
|
| 28 |
logger = logging.getLogger(__name__)
|
| 29 |
|
| 30 |
+
# Residue names the unified vocabulary tokenizes as glycans and non-canonical amino acids,
|
| 31 |
+
# from the RFD4 repository's RESIDUES_BY_TOKEN_TYPE. A residue missing from these is typed as
|
| 32 |
+
# protein/RNA/DNA or atomized instead, which gives the decoder the wrong residue restriction.
|
| 33 |
+
GLYCAN_NAMES = frozenset(
|
| 34 |
+
(
|
| 35 |
+
"A2G", "BGC", "BMA", "FUC", "GAL", "GLA", "GLC", "MAN",
|
| 36 |
+
"NAG", "NDG", "NGA", "SIA", "XYP",
|
| 37 |
+
)
|
| 38 |
+
) # fmt: skip
|
| 39 |
+
NCAA_NAMES = frozenset(
|
| 40 |
+
(
|
| 41 |
+
"004", "0AF", "0TD", "2MR", "3FG", "4BF", "4D4", "4HH",
|
| 42 |
+
"5CT", "5OH", "ABA", "AGM", "AIB", "ALC", "ALN", "ALO",
|
| 43 |
+
"ALY", "API", "ASA", "CAF", "CAS", "CCS", "CGU", "CIR",
|
| 44 |
+
"CME", "CSD", "CSO", "CSS", "CSU", "CSX", "CYG", "D2T",
|
| 45 |
+
"DAB", "DAH", "DDE", "DDZ", "DPP", "EEP", "FTR", "GLZ",
|
| 46 |
+
"HIC", "HIP", "HRG", "HY3", "HYP", "IIL", "ILX", "IYR",
|
| 47 |
+
"KCR", "KCX", "KPI", "LLP", "LYR", "M3L", "MEN", "MEQ",
|
| 48 |
+
"MGN", "MHO", "MHS", "MIS", "MK8", "ML3", "MLY", "MLZ",
|
| 49 |
+
"MP8", "NEP", "NIY", "NLE", "NMM", "OAS", "OCS", "OMT",
|
| 50 |
+
"OMY", "ORN", "P1L", "PBF", "PHD", "PHI", "PPN", "PTR",
|
| 51 |
+
"SCH", "SCY", "SEB", "SEC", "SEP", "SLL", "SMC", "SME",
|
| 52 |
+
"SNC", "TBG", "TOX", "TPO", "TPQ", "TRQ", "TRX", "TYI",
|
| 53 |
+
"TYQ", "TYS", "V5N", "YCM",
|
| 54 |
+
)
|
| 55 |
+
) # fmt: skip
|
| 56 |
+
|
| 57 |
_UNKNOWN_RES_BY_TOKEN_TYPE = {TokenType.PROTEIN: UNKNOWN_AA, TokenType.RNA: UNKNOWN_RNA, TokenType.DNA: UNKNOWN_DNA}
|
| 58 |
|
| 59 |
_AROMATIC_BOND_TYPES = (
|
|
|
|
| 81 |
types[atom_array.get_annotation("is_standard_unknown_or_masked_dna")[atom_idxs]] = TokenType.DNA
|
| 82 |
types[atom_array.get_annotation("is_standard_unknown_or_masked_rna")[atom_idxs]] = TokenType.RNA
|
| 83 |
types[atom_array.get_annotation("is_standard_unknown_or_masked_aa")[atom_idxs]] = TokenType.PROTEIN
|
| 84 |
+
types[np.isin(atom_array.res_name[atom_idxs], list(NCAA_NAMES))] = TokenType.NCAA
|
| 85 |
+
types[np.isin(atom_array.res_name[atom_idxs], list(GLYCAN_NAMES))] = TokenType.GLYCAN
|
| 86 |
types[atom_array.atomize[atom_idxs]] = TokenType.ATOMIZED
|
| 87 |
return types
|
| 88 |
|
|
@@ -510,7 +510,7 @@ class ConvertToAtom37Format(Transform):
|
|
| 510 |
na_ss_pair = np.zeros((n_tokens, n_tokens), dtype=np.int16)
|
| 511 |
na_ss_pair_mask = np.zeros((n_tokens, n_tokens), dtype=bool)
|
| 512 |
|
| 513 |
-
if C_BP.full_name in atom_array.
|
| 514 |
pair_annotation = C_BP.annotation(atom_array, default="raise")
|
| 515 |
atom_to_token = np.full(atom_array.array_length(), -1, dtype=np.int32)
|
| 516 |
atom_to_token[token_starts] = np.arange(n_tokens, dtype=np.int32)
|
|
|
|
| 510 |
na_ss_pair = np.zeros((n_tokens, n_tokens), dtype=np.int16)
|
| 511 |
na_ss_pair_mask = np.zeros((n_tokens, n_tokens), dtype=bool)
|
| 512 |
|
| 513 |
+
if C_BP.full_name in atom_array.get_annotation_categories(n_body=2):
|
| 514 |
pair_annotation = C_BP.annotation(atom_array, default="raise")
|
| 515 |
atom_to_token = np.full(atom_array.array_length(), -1, dtype=np.int32)
|
| 516 |
atom_to_token[token_starts] = np.arange(n_tokens, dtype=np.int32)
|
|
@@ -42,6 +42,8 @@ class TokenType:
|
|
| 42 |
DNA = 2
|
| 43 |
ATOMIZED = 3
|
| 44 |
UNKNOWN = 4
|
|
|
|
|
|
|
| 45 |
|
| 46 |
|
| 47 |
SPECS: dict[str, tuple] = {
|
|
|
|
| 42 |
DNA = 2
|
| 43 |
ATOMIZED = 3
|
| 44 |
UNKNOWN = 4
|
| 45 |
+
GLYCAN = 5
|
| 46 |
+
NCAA = 6
|
| 47 |
|
| 48 |
|
| 49 |
SPECS: dict[str, tuple] = {
|
|
@@ -58,7 +58,7 @@ class FeaturizeNucleicAcidSecondaryStructure(Transform):
|
|
| 58 |
token_starts = get_token_starts(atom_array)
|
| 59 |
n_tokens = len(token_starts)
|
| 60 |
if (
|
| 61 |
-
C_BP.full_name not in atom_array.
|
| 62 |
or C_NA_SS_STATE.full_name not in atom_array.get_annotation_categories()
|
| 63 |
):
|
| 64 |
pair_annotation = AnnotationList2D(
|
|
|
|
| 58 |
token_starts = get_token_starts(atom_array)
|
| 59 |
n_tokens = len(token_starts)
|
| 60 |
if (
|
| 61 |
+
C_BP.full_name not in atom_array.get_annotation_categories(n_body=2)
|
| 62 |
or C_NA_SS_STATE.full_name not in atom_array.get_annotation_categories()
|
| 63 |
):
|
| 64 |
pair_annotation = AnnotationList2D(
|
|
@@ -1,13 +1,23 @@
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
"""Report drift between the vendored `_cif` modules and the RFD4 repository they came from.
|
| 3 |
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
|
| 10 |
python tools/check_vendored.py --rfd4 /path/to/RFD4-Proteina-dev
|
|
|
|
|
|
|
| 11 |
"""
|
| 12 |
|
| 13 |
from __future__ import annotations
|
|
@@ -43,18 +53,76 @@ SOURCES = {
|
|
| 43 |
}
|
| 44 |
|
| 45 |
|
| 46 |
-
def
|
| 47 |
result = subprocess.run(
|
| 48 |
["git", "-C", str(repo), "show", f"{commit}:{path}"], capture_output=True, text=True
|
| 49 |
)
|
| 50 |
return result.stdout.splitlines() if result.returncode == 0 else None
|
| 51 |
|
| 52 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
def main() -> int:
|
| 54 |
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
| 55 |
parser.add_argument("--rfd4", type=Path, required=True, help="Checkout of baker-laboratory/RFD4-Proteina-dev.")
|
| 56 |
parser.add_argument("--against", default="HEAD", help="Commit to compare against (default: HEAD).")
|
| 57 |
-
parser.add_argument("--show-diff", action="store_true", help="Print the
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
args = parser.parse_args()
|
| 59 |
|
| 60 |
vendored = Path(__file__).resolve().parent.parent / "src" / "rfd4_proteina_prep" / "_cif"
|
|
@@ -65,6 +133,9 @@ def main() -> int:
|
|
| 65 |
print(f"error: {args.rfd4} is not a readable git checkout", file=sys.stderr)
|
| 66 |
return 2
|
| 67 |
|
|
|
|
|
|
|
|
|
|
| 68 |
pins, moved, unpinned = set(), [], []
|
| 69 |
for name, source in sorted(SOURCES.items()):
|
| 70 |
local = vendored / name
|
|
@@ -75,7 +146,7 @@ def main() -> int:
|
|
| 75 |
unpinned.append(name)
|
| 76 |
continue
|
| 77 |
pins.add(match.group(1))
|
| 78 |
-
was, now =
|
| 79 |
if was is None or now is None:
|
| 80 |
moved.append((name, source, -1))
|
| 81 |
continue
|
|
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
"""Report drift between the vendored `_cif` modules and the RFD4 repository they came from.
|
| 3 |
|
| 4 |
+
Two checks, because they catch different things:
|
| 5 |
+
|
| 6 |
+
* `--mode pin` re-reads each source at the commit the vendored file records and at the
|
| 7 |
+
checkout's current commit, and reports which sources moved upstream. This answers "has main
|
| 8 |
+
changed since someone last looked", and it is only as truthful as the recorded commit.
|
| 9 |
+
* `--mode content` (the default) compares the vendored file against the source itself, and
|
| 10 |
+
reports lines that exist in both but differ. This answers "does the copy actually match",
|
| 11 |
+
and unlike the pin check it cannot be defeated by a header that was updated without the
|
| 12 |
+
content being reconciled. That mistake has already been made here once.
|
| 13 |
+
|
| 14 |
+
Neither rewrites anything. The vendored copies deliberately omit training-only code, so removals
|
| 15 |
+
are expected and are not reported; only lines that correspond and disagree are. Some of those are
|
| 16 |
+
legitimate inference-only adaptations, so the output is a triage list, not a defect list.
|
| 17 |
|
| 18 |
python tools/check_vendored.py --rfd4 /path/to/RFD4-Proteina-dev
|
| 19 |
+
python tools/check_vendored.py --rfd4 ../RFD4-Proteina-dev --mode pin
|
| 20 |
+
python tools/check_vendored.py --rfd4 ../RFD4-Proteina-dev --only transforms_na_geom.py --show-diff
|
| 21 |
"""
|
| 22 |
|
| 23 |
from __future__ import annotations
|
|
|
|
| 53 |
}
|
| 54 |
|
| 55 |
|
| 56 |
+
def show_file(repo: Path, commit: str, path: str) -> list[str] | None:
|
| 57 |
result = subprocess.run(
|
| 58 |
["git", "-C", str(repo), "show", f"{commit}:{path}"], capture_output=True, text=True
|
| 59 |
)
|
| 60 |
return result.stdout.splitlines() if result.returncode == 0 else None
|
| 61 |
|
| 62 |
|
| 63 |
+
def content_drift(vendored: Path, repo: Path, commit: str, only: str | None, show: bool) -> int:
|
| 64 |
+
"""Compare each vendored copy against its source, reporting lines that correspond but differ.
|
| 65 |
+
|
| 66 |
+
Removals are skipped: the vendored copies drop training-only code on purpose, so a missing
|
| 67 |
+
block is expected. What matters is a line that exists on both sides and says something
|
| 68 |
+
different — a renamed call, a changed constant, a deprecated API the copy still uses.
|
| 69 |
+
"""
|
| 70 |
+
noise = re.compile(r"^\s*(#|from\s|import\s|\"\"\"|\'\'\')")
|
| 71 |
+
total, reported = 0, 0
|
| 72 |
+
print(f"comparing vendored copies against {commit[:12]}\n")
|
| 73 |
+
for name, source in sorted(SOURCES.items()):
|
| 74 |
+
if only and name != only:
|
| 75 |
+
continue
|
| 76 |
+
local = vendored / name
|
| 77 |
+
if not local.is_file():
|
| 78 |
+
continue
|
| 79 |
+
theirs = show_file(repo, commit, source)
|
| 80 |
+
if theirs is None:
|
| 81 |
+
print(f" {name:<40} source unreadable at {commit[:12]} <- {source}")
|
| 82 |
+
continue
|
| 83 |
+
ours = local.read_text().splitlines()
|
| 84 |
+
differing = []
|
| 85 |
+
matcher = difflib.SequenceMatcher(None, ours, theirs, autojunk=False)
|
| 86 |
+
for tag, i1, i2, j1, j2 in matcher.get_opcodes():
|
| 87 |
+
if tag != "replace":
|
| 88 |
+
continue
|
| 89 |
+
for mine, theirs_line in zip(ours[i1:i2], theirs[j1:j2]):
|
| 90 |
+
left, right = mine.strip(), theirs_line.strip()
|
| 91 |
+
if not left or not right or left == right:
|
| 92 |
+
continue
|
| 93 |
+
if noise.match(mine) and noise.match(theirs_line):
|
| 94 |
+
continue
|
| 95 |
+
differing.append((left, right))
|
| 96 |
+
if differing:
|
| 97 |
+
reported += 1
|
| 98 |
+
total += len(differing)
|
| 99 |
+
print(f" {name:<40} {len(differing):>4} differing line(s) <- {source}")
|
| 100 |
+
if show:
|
| 101 |
+
for left, right in differing:
|
| 102 |
+
print(f" copy: {left}")
|
| 103 |
+
print(f" main: {right}")
|
| 104 |
+
if not total:
|
| 105 |
+
print("\nno differing lines: every vendored copy matches its source.")
|
| 106 |
+
return 0
|
| 107 |
+
print(f"\n{total} differing line(s) across {reported} file(s).")
|
| 108 |
+
print("Some are deliberate inference-only adaptations. Re-run with --show-diff to triage,")
|
| 109 |
+
print("and only update a file's recorded commit once its content has actually been reconciled.")
|
| 110 |
+
return 1
|
| 111 |
+
|
| 112 |
+
|
| 113 |
def main() -> int:
|
| 114 |
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
| 115 |
parser.add_argument("--rfd4", type=Path, required=True, help="Checkout of baker-laboratory/RFD4-Proteina-dev.")
|
| 116 |
parser.add_argument("--against", default="HEAD", help="Commit to compare against (default: HEAD).")
|
| 117 |
+
parser.add_argument("--show-diff", action="store_true", help="Print the differing lines, not just a count.")
|
| 118 |
+
parser.add_argument(
|
| 119 |
+
"--mode",
|
| 120 |
+
choices=("content", "pin"),
|
| 121 |
+
default="content",
|
| 122 |
+
help="content: compare the vendored copy against the source. pin: compare the source at the recorded "
|
| 123 |
+
"commit against the current one. Default: content.",
|
| 124 |
+
)
|
| 125 |
+
parser.add_argument("--only", help="Restrict to one vendored file name.")
|
| 126 |
args = parser.parse_args()
|
| 127 |
|
| 128 |
vendored = Path(__file__).resolve().parent.parent / "src" / "rfd4_proteina_prep" / "_cif"
|
|
|
|
| 133 |
print(f"error: {args.rfd4} is not a readable git checkout", file=sys.stderr)
|
| 134 |
return 2
|
| 135 |
|
| 136 |
+
if args.mode == "content":
|
| 137 |
+
return content_drift(vendored, args.rfd4, current, args.only, args.show_diff)
|
| 138 |
+
|
| 139 |
pins, moved, unpinned = set(), [], []
|
| 140 |
for name, source in sorted(SOURCES.items()):
|
| 141 |
local = vendored / name
|
|
|
|
| 146 |
unpinned.append(name)
|
| 147 |
continue
|
| 148 |
pins.add(match.group(1))
|
| 149 |
+
was, now = show_file(args.rfd4, match.group(1), source), show_file(args.rfd4, current, source)
|
| 150 |
if was is None or now is None:
|
| 151 |
moved.append((name, source, -1))
|
| 152 |
continue
|