File size: 11,652 Bytes
3023912
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f9d3b3a
3023912
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f9d3b3a
3023912
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f9d3b3a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
import os

# Environment preamble before any heavy import: these gate AtomWorks/numba/biotite startup and
# keep the ZeroGPU parent process free of CUDA initialization.
os.environ.setdefault("NUMBA_DISABLE_CUDA", "1")
os.environ.setdefault("ALLOW_BIOTITE_CCD", "true")
os.environ.setdefault("DISABLE_CUEQUIVARIANCE", "true")
os.environ.setdefault("WANDB_MODE", "disabled")
os.environ.setdefault("GRADIO_ANALYTICS_ENABLED", "false")
os.environ.setdefault("CLUSTER", "local")
os.environ.setdefault("OMP_NUM_THREADS", "1")
os.environ.setdefault("OPENBLAS_NUM_THREADS", "1")

import spaces  # noqa: E402  # MUST precede torch / diffusers / atomworks

import time
from pathlib import Path

import gradio as gr
import torch

MODEL_ID = "nvidia/RFD4-Proteina-Diffusers"
EXAMPLES_DIR = Path(__file__).parent / "examples"

from diffusers import RFD4ProteinaPipeline  # noqa: E402
from rfd4_proteina_prep import prepare_cif, write_cif  # noqa: E402

# The feature tensors the pipeline consumes as direct keyword arguments; everything else the
# preprocessor emits rides inside `conditioning_features`.
REQUIRED_KEYS = (
    "padding_mask",
    "is_in_context_conditioning",
    "supports_local_latent",
    "token_type",
    "atom37_mask",
)

MODES = (
    "Full design",
    "Inverse folding",
    "Partial diffusion",
)

MODE_INFO = {
    "Full design": (
        "Design every free token in the input: sequence and structure are generated from noise, "
        "conditioned on whatever the annotated CIF holds fixed."
    ),
    "Inverse folding": (
        "The backbone of every free token is held fixed; RFD4 predicts only the sequence. Use it on "
        "a structure whose fold you want to keep."
    ),
    "Partial diffusion": (
        "Start from the input structure at a partly-noised state: the beginning of the trajectory "
        "is skipped, so designs stay close to the input fold."
    ),
}

# The released recipe fixes the number of diffusion steps (len(config.recycle_depths) = 60).
N_STEPS = 60

CSS = """
#col-container { max-width: 1100px; margin: 0 auto; }
.dark .gradio-container { color: var(--body-text-color); }
"""

_PLACEHOLDER_HTML = (
    '<div style="width:100%;height:520px;display:flex;align-items:center;justify-content:center;'
    "color:var(--body-text-color);border:1px dashed var(--border-color-primary);border-radius:8px;"
    'font-size:14px;">Press “Design” to see the generated structure here</div>'
)

# The pipeline is loaded once at module scope, eagerly on the GPU: no offloading, no device_map.
pipe = RFD4ProteinaPipeline.from_pretrained(MODEL_ID, dtype=torch.bfloat16).to("cuda")


def _describe(mode: str) -> str:
    """One-line explanation of a design mode, shown under the dropdown."""
    return MODE_INFO.get(mode, "")


def _viewer_for(cif_path: str | Path, height: int = 520) -> str:
    """Render a designed CIF as an interactive 3Dmol.js cartoon view."""
    import py3Dmol

    with open(cif_path) as handle:
        data = handle.read()
    view = py3Dmol.view(width="100%", height=height)
    view.addModel(data, "cif")
    view.setStyle({"cartoon": {"colorscheme": "chain"}})
    view.zoomTo()
    return view._make_html()


@spaces.GPU(duration=120)
def design_protein(
    cif_file,
    mode="Full design",
    seed=42,
    num_designs=1,
    remaining_fraction=0.4,
    progress=gr.Progress(track_tqdm=True),
):
    """Run RFD4-Proteina on an annotated CIF and return the designed structure(s).

    Args:
        cif_file: Path to the uploaded annotated CIF (AtomWorks Studio format).
        mode: "Full design", "Inverse folding" or "Partial diffusion".
        seed: Random seed; the same input and seed reproduce the same design.
        num_designs: How many independent designs to draw in one call (1-8).
        remaining_fraction: "Partial diffusion" only — the fraction of the trajectory left to
            run. The released example uses 0.4.
        progress: Gradio progress bar.

    Returns:
        The design CIF path (or list of paths), an interactive 3D view of the first design, and a
        short text summary.
    """
    started = time.perf_counter()
    if cif_file is None:
        raise gr.Error("Upload an annotated CIF first, or pick one of the examples below.")
    num_designs = int(num_designs)
    if not 1 <= num_designs <= 8:
        raise gr.Error("Number of designs must be between 1 and 8.")

    # Feature preparation is CPU-side AtomWorks parsing; the pipeline moves every tensor to its
    # execution device itself, so CPU tensors are fine here.
    features, atoms = prepare_cif(str(cif_file), seed=int(seed), device="cpu")
    tokens = int(features["padding_mask"].shape[1])

    required = {name: features[name] for name in REQUIRED_KEYS}
    conditioning = {name: value for name, value in features.items() if name not in REQUIRED_KEYS}

    if mode == "Inverse folding":
        kwargs = {
            "inverse_folding": True,
            "partial_t_bb_ca": 0.0,
            "partial_t_local_latents": 0.0,
        }
        required["coords_nm"] = features["coords_nm"]
    elif mode == "Partial diffusion":
        # Reproduce the released partial-diffusion entry point: start each modality at the time
        # its schedule reaches after skipping (1 - remaining_fraction) of the trajectory.
        fraction = float(min(max(remaining_fraction, 0.05), 1.0))
        index = round((1 - fraction) * N_STEPS)
        pipe.bb_ca_scheduler.set_timesteps(N_STEPS)
        pipe.local_latents_scheduler.set_timesteps(N_STEPS)
        kwargs = {
            "partial_t_bb_ca": float(pipe.bb_ca_scheduler.timesteps[index]),
            "partial_t_local_latents": float(pipe.local_latents_scheduler.timesteps[index]),
        }
        required["coords_nm"] = features["coords_nm"]
    else:
        kwargs = {}
        conditioning["coords_nm"] = features["coords_nm"]

    if num_designs == 1:
        generator = torch.Generator("cuda").manual_seed(int(seed))
    else:
        generator = [
            torch.Generator("cuda").manual_seed(int(seed) + offset)
            for offset in range(num_designs)
        ]

    with torch.no_grad():
        output = pipe(
            padding_mask=required["padding_mask"],
            is_in_context_conditioning=required["is_in_context_conditioning"],
            supports_local_latent=required["supports_local_latent"],
            token_type=required["token_type"],
            atom37_mask=required["atom37_mask"],
            coords_nm=required.get("coords_nm"),
            conditioning_features=conditioning,
            num_designs_per_prompt=num_designs,
            generator=generator,
            **kwargs,
        )

    # `write_cif` reads one feature row per output row: repeat the input features per design.
    if num_designs > 1:
        features = {
            name: value.repeat_interleave(num_designs, dim=0) for name, value in features.items()
        }

    out_paths = []
    for row in range(num_designs):
        out_paths.append(write_cif(output, features, atoms, _unique_path(row, seed), row=row))

    elapsed = time.perf_counter() - started
    summary = (
        f"**{num_designs}** design{'s' if num_designs > 1 else ''} · **{tokens}** tokens · "
        f"**{mode.lower()}** · seed **{int(seed)}** · **{elapsed:.1f} s** on this GPU"
    )
    viewer_html = _viewer_for(out_paths[0])

    if num_designs == 1:
        return out_paths[0], viewer_html, summary
    return out_paths, viewer_html, summary


def _unique_path(row: int, seed) -> Path:
    """A fresh, non-existent output path in this worker's temp directory."""
    base = Path(os.environ.get("GRADIO_TEMP_DIR") or "/tmp/gradio") / f"rfd4_design_{int(seed)}_{row}.cif"
    base.parent.mkdir(parents=True, exist_ok=True)
    candidate = base
    suffix = 0
    while candidate.exists():
        suffix += 1
        candidate = base.with_name(f"{base.stem}_{suffix}{base.suffix}")
    return candidate


with gr.Blocks(title="RFD4-Proteina · Diffusers") as demo:
    gr.HTML(
        """
    <h1 style="margin: 0; font-size: 28px;">RFD4-Proteina · Diffusers</h1>
    <p style="margin-top: 4px;">
      Joint sequence–structure generation for biomolecular design, conditioned on an
      <b>annotated CIF</b> that says what to hold fixed and what to design. This Space runs
      <code>RFD4ProteinaPipeline</code> from 🤗 Diffusers on ZeroGPU.
    </p>
    """
    )
    with gr.Column(elem_id="col-container"):
        with gr.Row():
            with gr.Column(scale=1):
                cif_input = gr.File(
                    label="Annotated CIF (input)",
                    file_types=[".cif"],
                    type="filepath",
                    file_count="single",
                )
                mode = gr.Dropdown(
                    choices=list(MODES),
                    value="Full design",
                    label="Design mode",
                    info="What RFD4 generates versus keeps from the input.",
                )
                mode_info = gr.Markdown(_describe("Full design"))
                with gr.Accordion("Advanced options", open=False):
                    seed = gr.Slider(0, 2**31 - 1, value=42, step=1, label="Seed")
                    num_designs = gr.Slider(1, 8, value=1, step=1, label="Number of designs")
                    remaining_fraction = gr.Slider(
                        0.05,
                        1.0,
                        value=0.4,
                        step=0.05,
                        label="Remaining schedule (partial diffusion only)",
                        info="The released partial-diffusion example uses 0.4.",
                    )
                go = gr.Button("Design", variant="primary")
            with gr.Column(scale=2):
                viewer = gr.HTML(value=_PLACEHOLDER_HTML, show_label=False)
                out_files = gr.File(
                    label="Designed structure(s) · annotated CIF",
                    file_count="single",
                    interactive=False,
                )
                summary = gr.Markdown()
        mode.change(_describe, inputs=mode, outputs=mode_info)
        go.click(
            design_protein,
            inputs=[cif_input, mode, seed, num_designs, remaining_fraction],
            outputs=[out_files, viewer, summary],
            api_name="design",
        )

        gr.Examples(
            examples=[
                [str(EXAMPLES_DIR / "motif_scaffolding.cif"), "Full design"],
                [str(EXAMPLES_DIR / "inverse_folding.cif"), "Inverse folding"],
                [str(EXAMPLES_DIR / "inverse_folding.cif"), "Partial diffusion"],
                [str(EXAMPLES_DIR / "protein_binder.cif"), "Full design"],
                [str(EXAMPLES_DIR / "enzyme_design.cif"), "Full design"],
                [str(EXAMPLES_DIR / "small_molecule_binder.cif"), "Full design"],
                [str(EXAMPLES_DIR / "unconditional.cif"), "Full design"],
            ],
            inputs=[cif_input, mode],
            outputs=[out_files, viewer, summary],
            fn=design_protein,
            cache_examples=True,
            cache_mode="lazy",
            example_labels=[
                "Motif scaffolding (1QJG)",
                "Inverse folding (2KL8)",
                "Partial diffusion (2KL8)",
                "Protein binder (PD-L1)",
                "Enzyme (catalytic motif)",
                "Small-molecule binder (SAM)",
                "Unconditional protein",
            ],
            label="Author examples (from the model card, BSD-3-Clause)",
        )

demo.launch(theme=gr.themes.Citrus(), css=CSS, mcp_server=True)