Spaces:
Running on Zero
Running on Zero
Upload folder using huggingface_hub
Browse files- README.md +16 -11
- app.py +170 -220
- requirements.txt +13 -12
README.md
CHANGED
|
@@ -6,24 +6,29 @@ colorTo: indigo
|
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 6.20.0
|
| 8 |
app_file: app.py
|
| 9 |
-
short_description:
|
| 10 |
python_version: "3.12"
|
| 11 |
startup_duration_timeout: 1h
|
| 12 |
-
pinned: false
|
| 13 |
---
|
| 14 |
|
| 15 |
# Polaris-Pro — Unified Scientific Multimodal Model
|
| 16 |
|
| 17 |
-
Interactive demo
|
| 18 |
-
an 8B
|
| 19 |
-
and small molecules
|
| 20 |
|
| 21 |
-
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
- **Scientific text QA**
|
| 24 |
|
| 25 |
-
|
| 26 |
-
|
| 27 |
|
| 28 |
-
|
| 29 |
-
|
|
|
|
|
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 6.20.0
|
| 8 |
app_file: app.py
|
| 9 |
+
short_description: Scientific multimodal model for RNA/DNA/protein/molecules
|
| 10 |
python_version: "3.12"
|
| 11 |
startup_duration_timeout: 1h
|
|
|
|
| 12 |
---
|
| 13 |
|
| 14 |
# Polaris-Pro — Unified Scientific Multimodal Model
|
| 15 |
|
| 16 |
+
Interactive demo for [`sais-org/Polaris_Pro`](https://huggingface.co/sais-org/Polaris_Pro),
|
| 17 |
+
an 8B foundation model that understands and generates across proteins, RNA, DNA
|
| 18 |
+
and small molecules through a single natural-language interface.
|
| 19 |
|
| 20 |
+
Tasks exposed here (each uses the authors' official system prompt from
|
| 21 |
+
`run_examples.sh`):
|
| 22 |
+
|
| 23 |
+
- **RNA** — ncRNA family classification, translation-efficiency regression
|
| 24 |
+
- **DNA** — promoter detection, enhancer-activity regression
|
| 25 |
+
- **Protein** — solubility, stability, Enzyme Commission number
|
| 26 |
+
- **Molecule** — Ames mutagenicity, dipole moment, text → SMILES generation
|
| 27 |
- **Scientific text QA**
|
| 28 |
|
| 29 |
+
Weather forecasting and medical-image segmentation are part of the model but
|
| 30 |
+
need gridded netCDF I/O / gated SAM-3 weights, so they are out of scope here.
|
| 31 |
|
| 32 |
+
Built on the custom architecture from the
|
| 33 |
+
[Polaris-Pro GitHub repo](https://github.com/Shanghai-Academy-of-AI-For-Science/Polaris-Pro)
|
| 34 |
+
(vendored under `code/`). Runs on ZeroGPU.
|
app.py
CHANGED
|
@@ -1,15 +1,17 @@
|
|
| 1 |
import os
|
|
|
|
|
|
|
| 2 |
os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
|
| 3 |
os.environ.setdefault("TOKENIZERS_PARALLELISM", "false")
|
| 4 |
|
| 5 |
import sys
|
| 6 |
from pathlib import Path
|
| 7 |
|
| 8 |
-
# Vendored
|
| 9 |
CODE_DIR = Path(__file__).parent / "code"
|
| 10 |
sys.path.insert(0, str(CODE_DIR))
|
| 11 |
|
| 12 |
-
import spaces # noqa: E402
|
| 13 |
import torch # noqa: E402
|
| 14 |
import gradio as gr # noqa: E402
|
| 15 |
from huggingface_hub import snapshot_download # noqa: E402
|
|
@@ -19,305 +21,253 @@ from inference import BioQwen3VLInference # noqa: E402
|
|
| 19 |
MODEL_ID = "sais-org/Polaris_Pro"
|
| 20 |
|
| 21 |
# ---------------------------------------------------------------------------
|
| 22 |
-
# Load the model at module scope
|
| 23 |
-
#
|
| 24 |
-
# that ZeroGPU intercepts), which would poison the forked GPU worker
|
| 25 |
-
# ("No CUDA GPUs are available"). So we build on CPU here and move the model to
|
| 26 |
-
# CUDA lazily inside the first @spaces.GPU call (the worker process where real
|
| 27 |
-
# CUDA is available). ~23GB bf16 weights stay in host RAM between calls.
|
| 28 |
# ---------------------------------------------------------------------------
|
| 29 |
-
print(
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
|
|
|
|
|
|
|
|
|
| 37 |
dtype=torch.bfloat16,
|
| 38 |
-
attn_impl="sdpa", #
|
| 39 |
-
multi_gpu=False,
|
| 40 |
fail_on_legacy_mol_decoder=False,
|
| 41 |
)
|
| 42 |
-
print("
|
| 43 |
-
|
| 44 |
-
_ENGINE_ON_CUDA = False
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
def _ensure_cuda():
|
| 48 |
-
"""Move the model onto the GPU worker's CUDA device on first use."""
|
| 49 |
-
global _ENGINE_ON_CUDA
|
| 50 |
-
if _ENGINE_ON_CUDA:
|
| 51 |
-
return
|
| 52 |
-
ENGINE.model.to("cuda")
|
| 53 |
-
ENGINE._input_device = torch.device("cuda")
|
| 54 |
-
_ENGINE_ON_CUDA = True
|
| 55 |
|
| 56 |
|
| 57 |
# ---------------------------------------------------------------------------
|
| 58 |
-
#
|
|
|
|
|
|
|
| 59 |
# ---------------------------------------------------------------------------
|
| 60 |
TASKS = {
|
| 61 |
-
"RNA
|
| 62 |
-
"
|
| 63 |
"system": "You are a non-coding RNA family classifier. Output only the family name, no other text.",
|
| 64 |
"prompt": "<rna>\nWhich family does this non-coding RNA sequence belong to?",
|
| 65 |
-
"seq_label": "RNA sequence (A/U/G/C or A/C/G/T)",
|
| 66 |
-
"placeholder": "GGATGCGATCATGTCTGCACTAACACACCGGATCCCATCAGAACTCCG...",
|
| 67 |
"task": None,
|
| 68 |
-
"
|
| 69 |
},
|
| 70 |
-
"RNA
|
| 71 |
-
"
|
| 72 |
"system": None,
|
| 73 |
"prompt": "<rna>\nWhat is the expected translation efficiency associated with the sequence?",
|
| 74 |
-
"seq_label": "RNA sequence",
|
| 75 |
-
"placeholder": "CCCCCCAAGCAACACGCGCGGGCCTATCGGCAGCACCATGGCCGCATATACCGCATATA",
|
| 76 |
"task": None,
|
| 77 |
-
"
|
| 78 |
},
|
| 79 |
-
"DNA
|
| 80 |
-
"
|
| 81 |
-
"system":
|
| 82 |
-
"question carefully. Respond with a single token: exactly 'Yes' or 'No'. Do not "
|
| 83 |
-
"add any explanation, punctuation, reasoning, or additional text."),
|
| 84 |
"prompt": "<dna>\nIs this 300 bp DNA sequence a promoter region (all promoters, TATA and non-TATA combined)? Answer Yes or No.",
|
| 85 |
-
"seq_label": "DNA sequence",
|
| 86 |
-
"placeholder": "GCAATAAAAGGCTTAGCCACATAGTGCATGCATGTACACAGCATGTACAC",
|
| 87 |
"task": None,
|
| 88 |
-
"
|
| 89 |
},
|
| 90 |
-
"DNA
|
| 91 |
-
"
|
| 92 |
-
"system":
|
| 93 |
-
"carefully. Respond with a single floating-point number only. Do not add units, "
|
| 94 |
-
"explanations, reasoning, or any additional text."),
|
| 95 |
"prompt": "<dna>\nPredict the quantile-normalized developmental enhancer (Dev) log2 enrichment activity score of this DNA sequence. Answer with a float number.",
|
| 96 |
-
"seq_label": "DNA sequence",
|
| 97 |
-
"placeholder": "AACATACCCTGCTCTAGCGTATTGCTTTTTGGCAGCTACGTAGCTAGCTAGCTTTTCGTTTGG",
|
| 98 |
"task": None,
|
| 99 |
-
"
|
| 100 |
},
|
| 101 |
-
"Protein
|
| 102 |
-
"
|
| 103 |
-
"system":
|
| 104 |
-
"Output only one digit: 1 for soluble, 0 for insoluble. Do not output any other text."),
|
| 105 |
"prompt": "<protein>\nSolubility prediction involves forecasting if a protein can dissolve. What is the solubility status of this protein? Output only one digit: 1 for soluble, 0 for insoluble.",
|
| 106 |
-
"seq_label": "Protein sequence (amino acids)",
|
| 107 |
-
"placeholder": "MLSVRIAAAVARALPRRAGLVSKNALGSSFIAARNFHASNTHLQKTGTAEMSSILEE...",
|
| 108 |
"task": None,
|
| 109 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
},
|
| 111 |
-
"Protein
|
| 112 |
-
"
|
| 113 |
"system": "You are a protein function predictor. Output only the EC number(s), comma-separated, no other text.",
|
| 114 |
"prompt": "<protein>\nPredict the Enzyme Commission (EC) number(s) of this protein. Output only the EC numbers, comma-separated.",
|
| 115 |
-
"seq_label": "Protein sequence (amino acids)",
|
| 116 |
-
"placeholder": "MHHHHHHSSGVDLGTENLYFQSNAMDFPQQLEACVKQANQALSRFIAPLPFQNTPVVE...",
|
| 117 |
"task": None,
|
| 118 |
-
"
|
| 119 |
},
|
| 120 |
-
"Molecule
|
| 121 |
-
"
|
| 122 |
-
"system":
|
| 123 |
-
"and an ADMET endpoint description, respond with only 0 or 1 to indicate whether "
|
| 124 |
-
"the molecule possesses that property."),
|
| 125 |
"prompt": "<mol>\nGiven the SMILES representation of a molecule, predict whether it is mutagenic (1) or non-mutagenic (0) based on the Ames test.",
|
| 126 |
-
"seq_label": "Molecule SMILES",
|
| 127 |
-
"placeholder": "CC(=O)Nc1ccc2c(=O)c(=O)c3cccc4ccc1c2c43",
|
| 128 |
"task": None,
|
| 129 |
-
"
|
| 130 |
},
|
| 131 |
-
"Molecule
|
| 132 |
-
"
|
| 133 |
-
"system":
|
| 134 |
-
"representations and instructions, answer with the specific molecular property values."),
|
| 135 |
"prompt": "<mol>\nWhat is the dipole moment value of this molecular?",
|
| 136 |
-
"seq_label": "Molecule SMILES",
|
| 137 |
-
"placeholder": "CC(=O)Nc1ccccc1",
|
| 138 |
"task": None,
|
| 139 |
-
"
|
| 140 |
},
|
| 141 |
-
"Molecule
|
| 142 |
-
"
|
| 143 |
-
"system":
|
| 144 |
-
|
| 145 |
-
"only the SMILES string, with no additional text."),
|
| 146 |
-
"prompt": None, # user supplies the free-text description
|
| 147 |
-
"seq_label": "Molecule description (natural language)",
|
| 148 |
-
"placeholder": ("Generate a molecule that matches the following description:\n"
|
| 149 |
-
"The molecule is a long-chain fatty acid that is henicosane in which one of the "
|
| 150 |
-
"methyl groups has been oxidised to give the corresponding carboxylic acid.\n"
|
| 151 |
-
"Output only the canonical SMILES string."),
|
| 152 |
"task": "mol_generation",
|
| 153 |
-
"
|
| 154 |
},
|
| 155 |
-
"Scientific text QA": {
|
| 156 |
-
"
|
| 157 |
"system": None,
|
| 158 |
-
"prompt":
|
| 159 |
-
"seq_label": "Question / prompt",
|
| 160 |
-
"placeholder": ("The following is a multiple choice question about biology. Think step by step "
|
| 161 |
-
"and then finish your answer with \"the answer is (X)\".\nQuestion:\nWhich molecule "
|
| 162 |
-
"carries amino acids to the ribosome during translation?\nOptions:\nA. mRNA\nB. tRNA\n"
|
| 163 |
-
"C. rRNA\nD. snRNA\nAnswer:"),
|
| 164 |
"task": None,
|
| 165 |
-
"
|
| 166 |
},
|
| 167 |
}
|
| 168 |
|
| 169 |
TASK_NAMES = list(TASKS.keys())
|
| 170 |
|
| 171 |
|
| 172 |
-
def
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
return min(240, 120 + int(mnt * 0.5))
|
| 178 |
-
|
| 179 |
|
| 180 |
-
@spaces.GPU(duration=120)
|
| 181 |
-
def gpu_probe() -> str:
|
| 182 |
-
"""Minimal GPU probe: move model to CUDA and report the device."""
|
| 183 |
-
import torch as _t
|
| 184 |
-
_ensure_cuda()
|
| 185 |
-
x = _t.zeros(4, device="cuda")
|
| 186 |
-
return f"ok device={_t.cuda.get_device_name(0)} sum={float(x.sum().cpu())}"
|
| 187 |
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
"""Run Polaris-Pro on one scientific task.
|
| 192 |
|
| 193 |
Args:
|
| 194 |
-
task_name:
|
| 195 |
-
|
| 196 |
-
|
|
|
|
| 197 |
|
| 198 |
Returns:
|
| 199 |
-
The model's text
|
| 200 |
"""
|
| 201 |
-
if
|
| 202 |
-
return "
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 208 |
|
| 209 |
-
modality = cfg["modality"]
|
| 210 |
kwargs = dict(
|
| 211 |
-
max_new_tokens=
|
| 212 |
-
do_sample=False, # greedy —
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
top_k=50,
|
| 216 |
-
system=cfg["system"],
|
| 217 |
)
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
kwargs["prompt"] =
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
result = ENGINE.generate_from_prompt(**kwargs)
|
| 237 |
-
return result if result else "(empty response)"
|
| 238 |
|
| 239 |
|
| 240 |
# ---------------------------------------------------------------------------
|
| 241 |
-
#
|
|
|
|
| 242 |
# ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 243 |
CSS = """
|
| 244 |
#col-container { max-width: 1080px; margin: 0 auto; }
|
| 245 |
.dark .gradio-container { color: var(--body-text-color); }
|
| 246 |
"""
|
| 247 |
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
natural-language interface — classification, regression, sequence/molecule design, and scientific QA.
|
| 254 |
-
|
| 255 |
-
Pick a task, paste a sequence / SMILES / question, and run. Each task uses the authors' benchmark system prompt.
|
| 256 |
-
"""
|
| 257 |
|
|
|
|
|
|
|
|
|
|
| 258 |
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
return gr.update(label=cfg["seq_label"], placeholder=cfg["placeholder"], value="")
|
| 262 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 263 |
|
| 264 |
-
with gr.
|
| 265 |
-
|
| 266 |
-
|
|
|
|
|
|
|
| 267 |
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
value=TASK_NAMES[0],
|
| 271 |
-
label="Task",
|
| 272 |
-
)
|
| 273 |
-
user_text = gr.Textbox(
|
| 274 |
-
label=TASKS[TASK_NAMES[0]]["seq_label"],
|
| 275 |
-
placeholder=TASKS[TASK_NAMES[0]]["placeholder"],
|
| 276 |
lines=4,
|
|
|
|
| 277 |
)
|
| 278 |
-
|
| 279 |
-
output = gr.Textbox(label="Model output", lines=4)
|
| 280 |
|
| 281 |
with gr.Accordion("Advanced settings", open=False):
|
| 282 |
-
|
| 283 |
-
minimum=
|
| 284 |
-
label="Max new tokens",
|
| 285 |
)
|
| 286 |
|
| 287 |
-
task.change(_on_task_change, inputs=task, outputs=user_text)
|
| 288 |
-
|
| 289 |
gr.Examples(
|
| 290 |
-
examples=
|
| 291 |
-
|
| 292 |
-
"GGATGCGATCATGTCTGCACTAACACACCGGATCCCATCAGAACTCCGAAGTTAAGCGTGCTTGGGCGGGAGTAGTACTAGGATGGGCGACCCCTTAGGAAGTACTCGTGTTGCATCCC", 64],
|
| 293 |
-
["DNA — promoter detection (Yes/No)",
|
| 294 |
-
"GCAATAAAAGGCTTAGCCACATAGTGCATGCATGTACACAGCATGTACAC", 16],
|
| 295 |
-
["Protein — solubility (0/1)",
|
| 296 |
-
"MLSVRIAAAVARALPRRAGLVSKNALGSSFIAARNFHASNTHLQKTGTAEMSSILEERILGADTSVDLEETGRVLSIGDGIARVHGLRNVQAEEMVEFSSGLKGMSLNLEP", 16],
|
| 297 |
-
["Molecule — Ames mutagenicity (0/1)",
|
| 298 |
-
"CC(=O)Nc1ccc2c(=O)c(=O)c3cccc4ccc1c2c43", 16],
|
| 299 |
-
["Molecule — text description → SMILES (generation)",
|
| 300 |
-
"Generate a molecule that matches the following description:\nThe molecule is a long-chain fatty acid that is henicosane in which one of the methyl groups has been oxidised to give the corresponding carboxylic acid.\nOutput only the canonical SMILES string.", 128],
|
| 301 |
-
["Scientific text QA",
|
| 302 |
-
"The following is a multiple choice question about biology. Think step by step and then finish your answer with \"the answer is (X)\".\nQuestion:\nWhich molecule carries amino acids to the ribosome during translation?\nOptions:\nA. mRNA\nB. tRNA\nC. rRNA\nD. snRNA\nAnswer:", 256],
|
| 303 |
-
],
|
| 304 |
-
inputs=[task, user_text, max_new_tokens],
|
| 305 |
outputs=output,
|
| 306 |
-
fn=
|
| 307 |
cache_examples=False,
|
| 308 |
run_on_click=True,
|
| 309 |
)
|
| 310 |
|
| 311 |
-
|
| 312 |
-
|
| 313 |
-
inputs=[task, user_text, max_new_tokens],
|
| 314 |
-
outputs=output,
|
| 315 |
-
api_name="run_task",
|
| 316 |
-
)
|
| 317 |
-
|
| 318 |
-
probe_btn = gr.Button("probe", visible=False)
|
| 319 |
-
probe_btn.click(gpu_probe, inputs=None, outputs=output, api_name="gpu_probe")
|
| 320 |
-
|
| 321 |
|
| 322 |
if __name__ == "__main__":
|
| 323 |
-
demo.
|
|
|
|
| 1 |
import os
|
| 2 |
+
# Expandable segments guards against transient allocator fragmentation on the
|
| 3 |
+
# ~23 GB checkpoint + activations.
|
| 4 |
os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
|
| 5 |
os.environ.setdefault("TOKENIZERS_PARALLELISM", "false")
|
| 6 |
|
| 7 |
import sys
|
| 8 |
from pathlib import Path
|
| 9 |
|
| 10 |
+
# Vendored custom architecture code (qwenvl package) lives under ./code
|
| 11 |
CODE_DIR = Path(__file__).parent / "code"
|
| 12 |
sys.path.insert(0, str(CODE_DIR))
|
| 13 |
|
| 14 |
+
import spaces # noqa: E402 MUST come before torch / CUDA-touching imports
|
| 15 |
import torch # noqa: E402
|
| 16 |
import gradio as gr # noqa: E402
|
| 17 |
from huggingface_hub import snapshot_download # noqa: E402
|
|
|
|
| 21 |
MODEL_ID = "sais-org/Polaris_Pro"
|
| 22 |
|
| 23 |
# ---------------------------------------------------------------------------
|
| 24 |
+
# Load the model once at module scope (ZeroGPU packs weights to disk here and
|
| 25 |
+
# streams them to VRAM on the first @spaces.GPU call).
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
# ---------------------------------------------------------------------------
|
| 27 |
+
print("Downloading model weights ...")
|
| 28 |
+
MODEL_DIR = snapshot_download(
|
| 29 |
+
MODEL_ID,
|
| 30 |
+
token=os.environ.get("HF_TOKEN"),
|
| 31 |
+
)
|
| 32 |
+
print(f"Model downloaded to {MODEL_DIR}")
|
| 33 |
+
|
| 34 |
+
print("Instantiating BioQwen3VLInference (this loads ~23 GB of weights) ...")
|
| 35 |
+
INFER = BioQwen3VLInference(
|
| 36 |
+
model_path=MODEL_DIR,
|
| 37 |
+
device="cuda",
|
| 38 |
dtype=torch.bfloat16,
|
| 39 |
+
attn_impl="sdpa", # torch-native; correct on ZeroGPU Blackwell
|
|
|
|
| 40 |
fail_on_legacy_mol_decoder=False,
|
| 41 |
)
|
| 42 |
+
print("Model ready.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
|
| 44 |
|
| 45 |
# ---------------------------------------------------------------------------
|
| 46 |
+
# Per-task presets: system prompt + prompt template + which modality field the
|
| 47 |
+
# sequence input maps to. Prompts / sequences mirror run_examples.sh, which the
|
| 48 |
+
# authors ship to reproduce the benchmark numbers.
|
| 49 |
# ---------------------------------------------------------------------------
|
| 50 |
TASKS = {
|
| 51 |
+
"RNA · ncRNA family classification": {
|
| 52 |
+
"field": "rna",
|
| 53 |
"system": "You are a non-coding RNA family classifier. Output only the family name, no other text.",
|
| 54 |
"prompt": "<rna>\nWhich family does this non-coding RNA sequence belong to?",
|
|
|
|
|
|
|
| 55 |
"task": None,
|
| 56 |
+
"placeholder": "RNA / cDNA nucleotide sequence (A/C/G/U or A/C/G/T)",
|
| 57 |
},
|
| 58 |
+
"RNA · translation efficiency (regression)": {
|
| 59 |
+
"field": "rna",
|
| 60 |
"system": None,
|
| 61 |
"prompt": "<rna>\nWhat is the expected translation efficiency associated with the sequence?",
|
|
|
|
|
|
|
| 62 |
"task": None,
|
| 63 |
+
"placeholder": "RNA nucleotide sequence",
|
| 64 |
},
|
| 65 |
+
"DNA · promoter detection (Yes/No)": {
|
| 66 |
+
"field": "dna",
|
| 67 |
+
"system": "You are a DNA sequence analysis expert. Read the DNA sequence(s) and the question carefully. Respond with a single token: exactly 'Yes' or 'No'. Do not add any explanation, punctuation, reasoning, or additional text.",
|
|
|
|
|
|
|
| 68 |
"prompt": "<dna>\nIs this 300 bp DNA sequence a promoter region (all promoters, TATA and non-TATA combined)? Answer Yes or No.",
|
|
|
|
|
|
|
| 69 |
"task": None,
|
| 70 |
+
"placeholder": "DNA nucleotide sequence (A/C/G/T)",
|
| 71 |
},
|
| 72 |
+
"DNA · enhancer activity (regression)": {
|
| 73 |
+
"field": "dna",
|
| 74 |
+
"system": "You are a DNA sequence analysis expert. Read the DNA sequence and the question carefully. Respond with a single floating-point number only. Do not add units, explanations, reasoning, or any additional text.",
|
|
|
|
|
|
|
| 75 |
"prompt": "<dna>\nPredict the quantile-normalized developmental enhancer (Dev) log2 enrichment activity score of this DNA sequence. Answer with a float number.",
|
|
|
|
|
|
|
| 76 |
"task": None,
|
| 77 |
+
"placeholder": "DNA nucleotide sequence (A/C/G/T)",
|
| 78 |
},
|
| 79 |
+
"Protein · solubility (0/1)": {
|
| 80 |
+
"field": "protein",
|
| 81 |
+
"system": "You are a protein solubility predictor. This is a binary classification task. Output only one digit: 1 for soluble, 0 for insoluble. Do not output any other text.",
|
|
|
|
| 82 |
"prompt": "<protein>\nSolubility prediction involves forecasting if a protein can dissolve. What is the solubility status of this protein? Output only one digit: 1 for soluble, 0 for insoluble.",
|
|
|
|
|
|
|
| 83 |
"task": None,
|
| 84 |
+
"placeholder": "Protein amino-acid sequence",
|
| 85 |
+
},
|
| 86 |
+
"Protein · stability (regression)": {
|
| 87 |
+
"field": "protein",
|
| 88 |
+
"system": "You are a protein stability predictor. Output only the stability score as a number, no other text.",
|
| 89 |
+
"prompt": "<protein>\nHow is the stability of this protein sequence calculated?",
|
| 90 |
+
"task": None,
|
| 91 |
+
"placeholder": "Protein amino-acid sequence",
|
| 92 |
},
|
| 93 |
+
"Protein · Enzyme Commission number": {
|
| 94 |
+
"field": "protein",
|
| 95 |
"system": "You are a protein function predictor. Output only the EC number(s), comma-separated, no other text.",
|
| 96 |
"prompt": "<protein>\nPredict the Enzyme Commission (EC) number(s) of this protein. Output only the EC numbers, comma-separated.",
|
|
|
|
|
|
|
| 97 |
"task": None,
|
| 98 |
+
"placeholder": "Protein amino-acid sequence",
|
| 99 |
},
|
| 100 |
+
"Molecule · Ames mutagenicity (0/1)": {
|
| 101 |
+
"field": "mol",
|
| 102 |
+
"system": "You are a molecular property prediction expert; given a molecule's SMILES string and an ADMET endpoint description, respond with only 0 or 1 to indicate whether the molecule possesses that property.",
|
|
|
|
|
|
|
| 103 |
"prompt": "<mol>\nGiven the SMILES representation of a molecule, predict whether it is mutagenic (1) or non-mutagenic (0) based on the Ames test.",
|
|
|
|
|
|
|
| 104 |
"task": None,
|
| 105 |
+
"placeholder": "Molecule SMILES string",
|
| 106 |
},
|
| 107 |
+
"Molecule · dipole moment (regression)": {
|
| 108 |
+
"field": "mol",
|
| 109 |
+
"system": "You are a molecular property prediction expert. Based on the input molecular representations and instructions, answer with the specific molecular property values.",
|
|
|
|
| 110 |
"prompt": "<mol>\nWhat is the dipole moment value of this molecular?",
|
|
|
|
|
|
|
| 111 |
"task": None,
|
| 112 |
+
"placeholder": "Molecule SMILES string",
|
| 113 |
},
|
| 114 |
+
"Molecule · text → SMILES (generation)": {
|
| 115 |
+
"field": "text", # no bio input; description goes in the prompt
|
| 116 |
+
"system": "You are a molecule generation expert. Given a natural-language molecular description, generate one molecule as a valid canonical SMILES string. Output only the SMILES string, with no additional text.",
|
| 117 |
+
"prompt": "Generate a molecule that matches the following description:\n{input}\nOutput only the canonical SMILES string.",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
"task": "mol_generation",
|
| 119 |
+
"placeholder": "Natural-language description of the molecule to generate",
|
| 120 |
},
|
| 121 |
+
"Scientific text QA (no sequence)": {
|
| 122 |
+
"field": "text",
|
| 123 |
"system": None,
|
| 124 |
+
"prompt": "{input}",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
"task": None,
|
| 126 |
+
"placeholder": "Ask a scientific question (multiple-choice, definitions, reasoning, ...)",
|
| 127 |
},
|
| 128 |
}
|
| 129 |
|
| 130 |
TASK_NAMES = list(TASKS.keys())
|
| 131 |
|
| 132 |
|
| 133 |
+
def _duration(task_name, seq_input, max_new_tokens=64, *args, **kwargs):
|
| 134 |
+
# Generation tasks decode more tokens; bio-sequence encoding + a 23 GB
|
| 135 |
+
# model make the worst case a bit slow on a cold worker.
|
| 136 |
+
n = int(max_new_tokens or 64)
|
| 137 |
+
return min(160, 45 + int(n * 0.7))
|
|
|
|
|
|
|
| 138 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
|
| 140 |
+
@spaces.GPU(duration=_duration)
|
| 141 |
+
def run_inference(task_name: str, seq_input: str, max_new_tokens: int = 64) -> str:
|
| 142 |
+
"""Run Polaris-Pro on one scientific input and return the model's text answer.
|
|
|
|
| 143 |
|
| 144 |
Args:
|
| 145 |
+
task_name: Which scientific task / output format to use (see the dropdown).
|
| 146 |
+
seq_input: The biological sequence (RNA/DNA/protein SMILES) or, for
|
| 147 |
+
text tasks, the natural-language question / molecule description.
|
| 148 |
+
max_new_tokens: Maximum number of tokens to generate.
|
| 149 |
|
| 150 |
Returns:
|
| 151 |
+
The model's text response (a label, score, SMILES string, or answer).
|
| 152 |
"""
|
| 153 |
+
if task_name not in TASKS:
|
| 154 |
+
return "Unknown task."
|
| 155 |
+
spec = TASKS[task_name]
|
| 156 |
+
seq_input = (seq_input or "").strip()
|
| 157 |
+
if not seq_input:
|
| 158 |
+
return "Please provide an input sequence / question."
|
| 159 |
+
|
| 160 |
+
field = spec["field"]
|
| 161 |
+
system = spec["system"]
|
| 162 |
+
prompt_tmpl = spec["prompt"]
|
| 163 |
+
task = spec["task"]
|
| 164 |
|
|
|
|
| 165 |
kwargs = dict(
|
| 166 |
+
max_new_tokens=int(max_new_tokens),
|
| 167 |
+
do_sample=False, # greedy — matches run_examples.sh (--greedy)
|
| 168 |
+
system=system,
|
| 169 |
+
task=task,
|
|
|
|
|
|
|
| 170 |
)
|
| 171 |
+
|
| 172 |
+
if field == "text":
|
| 173 |
+
prompt = prompt_tmpl.format(input=seq_input) if "{input}" in prompt_tmpl else prompt_tmpl
|
| 174 |
+
kwargs["prompt"] = prompt
|
| 175 |
+
else:
|
| 176 |
+
# Bio-sequence task: the sequence goes to its own encoder field, and
|
| 177 |
+
# the prompt already carries the matching <rna>/<dna>/... placeholder.
|
| 178 |
+
seq = seq_input.upper() if field in ("rna", "dna", "protein") else seq_input
|
| 179 |
+
kwargs["prompt"] = prompt_tmpl
|
| 180 |
+
kwargs[field] = [seq]
|
| 181 |
+
|
| 182 |
+
out = INFER.generate_from_prompt(**kwargs)
|
| 183 |
+
return (out or "").strip() or "(empty response)"
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
def on_task_change(task_name):
|
| 187 |
+
spec = TASKS.get(task_name, {})
|
| 188 |
+
return gr.update(placeholder=spec.get("placeholder", ""))
|
|
|
|
|
|
|
| 189 |
|
| 190 |
|
| 191 |
# ---------------------------------------------------------------------------
|
| 192 |
+
# Example inputs (task_name, sequence/question, max_new_tokens) — taken from
|
| 193 |
+
# the authors' run_examples.sh.
|
| 194 |
# ---------------------------------------------------------------------------
|
| 195 |
+
EXAMPLES = [
|
| 196 |
+
["RNA · ncRNA family classification",
|
| 197 |
+
"GGATGCGATCATGTCTGCACTAACACACCGGATCCCATCAGAACTCCGAAGTTAAGCGTGCTTGGGCGGGAGTAGTACTAGGATGGGCGACCCCTTAGGAAGTACTCGTGTTGCATCCC",
|
| 198 |
+
64],
|
| 199 |
+
["DNA · promoter detection (Yes/No)",
|
| 200 |
+
"GCAATAAAAGGCTTAGCCACATAGTGCATGCATGTACACAGCATGTACAC",
|
| 201 |
+
16],
|
| 202 |
+
["Protein · solubility (0/1)",
|
| 203 |
+
"MLSVRIAAAVARALPRRAGLVSKNALGSSFIAARNFHASNTHLQKTGTAEMSSILEERILGADTSVDLEETGRVLSIGDGIARVHGLRNVQAEEMVEFSSGLKGMSLNLEP",
|
| 204 |
+
16],
|
| 205 |
+
["Protein · Enzyme Commission number",
|
| 206 |
+
"MHHHHHHSSGVDLGTENLYFQSNAMDFPQQLEACVKQANQALSRFIAPLPFQNTPVVETMQYGALLGGKRLRPFLVYATGHMFGVSTNTLDAPAAAVELIHAYSLIHDDLPAMDDDDLRRGLPTCHVKFGEANAILAGDALQTLAFSILSDADLADYIIQRNK",
|
| 207 |
+
32],
|
| 208 |
+
["Molecule · Ames mutagenicity (0/1)",
|
| 209 |
+
"CC(=O)Nc1ccc2c(=O)c(=O)c3cccc4ccc1c2c43",
|
| 210 |
+
16],
|
| 211 |
+
["Molecule · text → SMILES (generation)",
|
| 212 |
+
"The molecule is a long-chain fatty acid that is henicosane in which one of the methyl groups has been oxidised to give the corresponding carboxylic acid. It is a straight-chain saturated fatty acid and a long-chain fatty acid.",
|
| 213 |
+
128],
|
| 214 |
+
["Scientific text QA (no sequence)",
|
| 215 |
+
"The following is a multiple choice question about biology. Think step by step and then finish your answer with \"the answer is (X)\".\nQuestion:\nWhich molecule carries amino acids to the ribosome during translation?\nOptions:\nA. mRNA\nB. tRNA\nC. rRNA\nD. snRNA\nAnswer:",
|
| 216 |
+
256],
|
| 217 |
+
]
|
| 218 |
+
|
| 219 |
CSS = """
|
| 220 |
#col-container { max-width: 1080px; margin: 0 auto; }
|
| 221 |
.dark .gradio-container { color: var(--body-text-color); }
|
| 222 |
"""
|
| 223 |
|
| 224 |
+
with gr.Blocks(theme=gr.themes.Citrus(), css=CSS) as demo:
|
| 225 |
+
with gr.Column(elem_id="col-container"):
|
| 226 |
+
gr.Markdown(
|
| 227 |
+
"""
|
| 228 |
+
# 🔬 Polaris-Pro — Unified Scientific Multimodal Model
|
|
|
|
|
|
|
|
|
|
|
|
|
| 229 |
|
| 230 |
+
[`sais-org/Polaris_Pro`](https://huggingface.co/sais-org/Polaris_Pro) is an **8B** foundation
|
| 231 |
+
model that reasons over proteins, RNA, DNA, and small molecules through a single
|
| 232 |
+
natural-language interface — no per-task fine-tuning.
|
| 233 |
|
| 234 |
+
Pick a task, paste a sequence (or a question), and run. Each task uses the authors'
|
| 235 |
+
official system prompt so the output format matches their benchmarks.
|
|
|
|
| 236 |
|
| 237 |
+
*Weather forecasting and medical-image segmentation are part of the model but need
|
| 238 |
+
gridded netCDF I/O / gated SAM-3 weights, so they are out of scope for this demo.*
|
| 239 |
+
"""
|
| 240 |
+
)
|
| 241 |
|
| 242 |
+
with gr.Row():
|
| 243 |
+
task = gr.Dropdown(
|
| 244 |
+
choices=TASK_NAMES, value=TASK_NAMES[0], label="Task", scale=2,
|
| 245 |
+
)
|
| 246 |
+
run = gr.Button("Run", variant="primary", scale=1)
|
| 247 |
|
| 248 |
+
seq = gr.Textbox(
|
| 249 |
+
label="Input",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 250 |
lines=4,
|
| 251 |
+
placeholder=TASKS[TASK_NAMES[0]]["placeholder"],
|
| 252 |
)
|
| 253 |
+
output = gr.Textbox(label="Model response", lines=4, show_copy_button=True)
|
|
|
|
| 254 |
|
| 255 |
with gr.Accordion("Advanced settings", open=False):
|
| 256 |
+
max_new = gr.Slider(
|
| 257 |
+
label="Max new tokens", minimum=1, maximum=512, value=64, step=1,
|
|
|
|
| 258 |
)
|
| 259 |
|
|
|
|
|
|
|
| 260 |
gr.Examples(
|
| 261 |
+
examples=EXAMPLES,
|
| 262 |
+
inputs=[task, seq, max_new],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 263 |
outputs=output,
|
| 264 |
+
fn=run_inference,
|
| 265 |
cache_examples=False,
|
| 266 |
run_on_click=True,
|
| 267 |
)
|
| 268 |
|
| 269 |
+
task.change(on_task_change, inputs=task, outputs=seq)
|
| 270 |
+
run.click(run_inference, inputs=[task, seq, max_new], outputs=output, api_name="generate")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 271 |
|
| 272 |
if __name__ == "__main__":
|
| 273 |
+
demo.launch(mcp_server=True)
|
requirements.txt
CHANGED
|
@@ -1,21 +1,22 @@
|
|
| 1 |
-
#
|
| 2 |
-
#
|
| 3 |
-
#
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
#
|
| 7 |
-
# Upstream pins this exactly for correctness; keep pinned.
|
| 8 |
transformers==5.0.0
|
| 9 |
-
|
|
|
|
| 10 |
safetensors
|
| 11 |
-
|
| 12 |
# Numerics
|
| 13 |
numpy
|
| 14 |
scipy
|
| 15 |
einops
|
| 16 |
-
|
|
|
|
| 17 |
rdkit
|
| 18 |
torch-geometric
|
| 19 |
-
|
|
|
|
| 20 |
opencv-python-headless
|
| 21 |
-
pillow
|
|
|
|
| 1 |
+
# Polaris-Pro scientific multimodal model — ZeroGPU inference deps.
|
| 2 |
+
# torch / gradio / spaces / huggingface_hub are provided by the runtime and
|
| 3 |
+
# must NOT be pinned here.
|
| 4 |
+
|
| 5 |
+
# Transformers stack — transformers 5.0.0 provides the qwen3_vl arch + Sam3
|
| 6 |
+
# used by the custom Polaris-Pro architecture. Pin is required for correctness.
|
|
|
|
| 7 |
transformers==5.0.0
|
| 8 |
+
tokenizers==0.22.2
|
| 9 |
+
accelerate==1.7.0
|
| 10 |
safetensors
|
| 11 |
+
|
| 12 |
# Numerics
|
| 13 |
numpy
|
| 14 |
scipy
|
| 15 |
einops
|
| 16 |
+
|
| 17 |
+
# Molecule modality: RDKit (SMILES -> graph) + PyTorch-Geometric (GNN encoder)
|
| 18 |
rdkit
|
| 19 |
torch-geometric
|
| 20 |
+
|
| 21 |
+
# Image ops used by some modality processors
|
| 22 |
opencv-python-headless
|
|
|