import os import subprocess import sys # Allocator config for transient memory spikes in the folding pipeline. os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True") # Persist checkpoints / common runtime files between calls. OPENDDE_ROOT_DIR = os.environ.setdefault("OPENDDE_ROOT_DIR", "/tmp/opendde_data") os.environ.setdefault("LAYERNORM_TYPE", "torch") # Install the OpenDDE package WITHOUT its dependencies, so its pinned # torch==2.7.1 does not clobber the ZeroGPU-managed torch. This makes the # `opendde` and `runner` modules importable. Done before `import spaces` # because pip itself does not initialize CUDA. try: import runner.batch_inference # noqa: F401 except Exception: subprocess.run( [ sys.executable, "-m", "pip", "install", "--no-deps", "git+https://github.com/aurekaresearch/OpenDDE.git", ], check=True, ) import spaces # noqa: E402 (must precede torch / CUDA-touching imports) import glob # noqa: E402 import json # noqa: E402 import tempfile # noqa: E402 import time # noqa: E402 import uuid # noqa: E402 import gradio as gr # noqa: E402 # --------------------------------------------------------------------------- # Model / runtime setup # --------------------------------------------------------------------------- CHECKPOINT_DIR = os.path.join(OPENDDE_ROOT_DIR, "checkpoint") COMMON_DIR = os.path.join(OPENDDE_ROOT_DIR, "common") os.makedirs(CHECKPOINT_DIR, exist_ok=True) os.makedirs(COMMON_DIR, exist_ok=True) MODEL_REPO = "aurekaresearch/OpenDDE" def _prefetch_runtime_assets(): """Download checkpoint + common runtime files once at startup so the GPU call only performs inference (no download inside the time-limited window).""" from huggingface_hub import hf_hub_download targets = [ ("opendde.pt", os.path.join(CHECKPOINT_DIR, "opendde.pt")), ("common/components.cif", os.path.join(COMMON_DIR, "components.cif")), ( "common/components.cif.rdkit_mol.pkl", os.path.join(COMMON_DIR, "components.cif.rdkit_mol.pkl"), ), ] for repo_file, dest in targets: if os.path.exists(dest): continue cached = hf_hub_download(MODEL_REPO, repo_file, repo_type="model") os.makedirs(os.path.dirname(dest), exist_ok=True) if not os.path.exists(dest): try: os.symlink(cached, dest) except OSError: import shutil shutil.copy(cached, dest) try: _prefetch_runtime_assets() print("OpenDDE runtime assets ready.", flush=True) except Exception as e: # non-fatal: CLI can still download lazily print(f"Asset prefetch failed ({e!r}); CLI will download on demand.", flush=True) # Amino-acid alphabet used for validation (20 standard + X). AA_ALPHABET = set("ACDEFGHIKLMNPQRSTVWYX") MAX_RESIDUES = 400 # keep single-request runtime within the ZeroGPU window DESCRIPTION = """ # 🧬 OpenDDE — Biomolecular Structure Prediction Predict the all-atom 3D structure of a **protein sequence** with [**OpenDDE**](https://huggingface.co/aurekaresearch/OpenDDE), an open-source AlphaFold3-family biomolecular foundation model from Aureka Research. Paste a single-letter amino-acid sequence, hit **Predict structure**, and get an interactive 3D structure plus a downloadable `.cif` file and confidence metrics (pLDDT / pTM). This demo runs **single-sequence** (no MSA / templates) so it stays fast on ZeroGPU. """.strip() def _pymol_style_html(cif_text: str) -> str: """Build a self-contained py3Dmol viewer for a CIF structure. The viewer is rendered inside an ``' ) def _validate_sequence(sequence: str) -> str: seq = "".join(sequence.split()).upper() if not seq: raise gr.Error("Please enter a protein amino-acid sequence.") bad = sorted(set(seq) - AA_ALPHABET) if bad: raise gr.Error( f"Invalid amino-acid letter(s): {', '.join(bad)}. " "Use the 20 standard amino acids (and X)." ) if len(seq) > MAX_RESIDUES: raise gr.Error( f"Sequence is {len(seq)} residues; this demo caps at {MAX_RESIDUES} " "residues to fit the ZeroGPU time window." ) return seq def _estimate_duration(sequence, *args, **kwargs): seq = "".join(str(sequence).split()) n = max(1, len(seq)) # fp32 torch-kernel path scales roughly with length; be generous. return int(min(600, 120 + n * 0.9)) @spaces.GPU(duration=_estimate_duration) def _run_opendde(sequence: str, steps: int, cycles: int, seed: int) -> dict: """Run OpenDDE single-sequence prediction inside the GPU worker.""" work = tempfile.mkdtemp(prefix="opendde_run_") out_dir = os.path.join(work, "output") os.makedirs(out_dir, exist_ok=True) job_name = "prediction" job = [ { "name": job_name, "modelSeeds": [int(seed)], "sequences": [ {"proteinChain": {"sequence": sequence, "count": 1}} ], } ] input_json = os.path.join(work, "input.json") with open(input_json, "w") as f: json.dump(job, f) cmd = [ sys.executable, "-m", "runner.batch_inference", "pred", "-i", input_json, "-o", out_dir, "-n", "opendde_v1", "--use_msa", "false", "--use_template", "false", "--use_rna_msa", "false", "--triatt_kernel", "torch", "--trimul_kernel", "torch", "--dtype", "fp32", "--sample", "1", "--step", str(int(steps)), "--cycle", str(int(cycles)), ] env = dict(os.environ) env["OPENDDE_ROOT_DIR"] = OPENDDE_ROOT_DIR env["LAYERNORM_TYPE"] = "torch" proc = subprocess.run(cmd, env=env, capture_output=True, text=True) tail = (proc.stdout or "")[-2000:] + "\n" + (proc.stderr or "")[-2000:] pred_dir = os.path.join(out_dir, job_name, f"seed_{int(seed)}", "predictions") cifs = sorted(glob.glob(os.path.join(pred_dir, "*.cif"))) if not cifs: # surface an error directory message if present err_dir = os.path.join(out_dir, "ERR") err_msg = "" if os.path.isdir(err_dir): for fn in glob.glob(os.path.join(err_dir, "*.txt")): err_msg += open(fn).read() raise RuntimeError( f"OpenDDE produced no structure.\n{err_msg}\n--- log ---\n{tail}" ) cif_path = cifs[0] with open(cif_path) as f: cif_text = f.read() confidence = {} conf_files = sorted( glob.glob(os.path.join(pred_dir, "*summary_confidence*.json")) ) if conf_files: try: confidence = json.load(open(conf_files[0])) except Exception: confidence = {} return {"cif_text": cif_text, "confidence": confidence, "log": tail} def predict(sequence, steps=100, cycles=4, seed=101, randomize_seed=False): """Predict a 3D protein structure from an amino-acid sequence. Args: sequence: Single-letter amino-acid sequence (20 standard AAs + X). steps: Number of diffusion sampling steps. cycles: Number of Pairformer recycling iterations. seed: Random seed for the diffusion sampler. randomize_seed: If True, pick a fresh random seed for this run. """ import random seq = _validate_sequence(sequence) if randomize_seed: seed = random.randint(1, 65535) seed = int(seed) t0 = time.perf_counter() result = _run_opendde(seq, int(steps), int(cycles), seed) elapsed = time.perf_counter() - t0 cif_text = result["cif_text"] conf = result["confidence"] or {} # Save CIF to a temp file for download. tmp_cif = tempfile.NamedTemporaryFile( delete=False, suffix=".cif", prefix="opendde_structure_" ) tmp_cif.write(cif_text.encode()) tmp_cif.close() def _fmt(v): try: return round(float(v), 4) except Exception: return v metrics = { "residues": len(seq), "seed": seed, "runtime_seconds": round(elapsed, 1), } for key in ("plddt", "ptm", "iptm", "gpde", "ranking_score", "has_clash"): if key in conf: metrics[key] = _fmt(conf[key]) viewer_html = _pymol_style_html(cif_text) return viewer_html, tmp_cif.name, metrics, gr.update(value=seed) CSS = """ #col-container { max-width: 1100px; margin: 0 auto; } .dark .gradio-container { color: var(--body-text-color); } """ EXAMPLE_SEQS = [ # Villin headpiece subdomain (HP36) — small fast-folding protein. ["MLSDEDFKAVFGMTRSAFANLPLWKQQNLKKEKGLF"], # Trp-cage (TC5b) miniprotein. ["NLYIQWLKDGGPSSGRPPPS"], # Chignolin-like / short helical peptide. ["ACDEFGHIKLMNPQRSTVWY"], ] with gr.Blocks(theme=gr.themes.Citrus(), css=CSS) as demo: with gr.Column(elem_id="col-container"): gr.Markdown(DESCRIPTION) with gr.Row(): sequence = gr.Textbox( label="Protein sequence", placeholder="Paste a single-letter amino-acid sequence, e.g. MLSDEDFK...", lines=3, scale=4, ) run = gr.Button("Predict structure", variant="primary") with gr.Row(): with gr.Column(scale=3): viewer = gr.HTML(label="Predicted 3D structure") with gr.Column(scale=1): metrics_out = gr.JSON(label="Confidence metrics") cif_file = gr.File(label="Download structure (.cif)") with gr.Accordion("Advanced settings", open=False): steps = gr.Slider( 20, 200, value=100, step=10, label="Diffusion steps" ) cycles = gr.Slider( 1, 10, value=4, step=1, label="Recycling cycles" ) with gr.Row(): seed = gr.Number(label="Seed", value=101, precision=0) randomize_seed = gr.Checkbox(label="Randomize seed", value=False) gr.Examples( examples=EXAMPLE_SEQS, inputs=[sequence], outputs=[viewer, cif_file, metrics_out, seed], fn=predict, cache_examples=False, run_on_click=True, ) run.click( predict, inputs=[sequence, steps, cycles, seed, randomize_seed], outputs=[viewer, cif_file, metrics_out, seed], api_name="predict", ) if __name__ == "__main__": demo.launch(mcp_server=True)