multimodalart HF Staff commited on
Commit
44fa161
·
verified ·
1 Parent(s): afb6483

Upload folder using huggingface_hub

Browse files
Files changed (3) hide show
  1. README.md +16 -11
  2. app.py +170 -220
  3. 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: Unified scientific model for protein, RNA, DNA, molecules
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 of [`sais-org/Polaris_Pro`](https://huggingface.co/sais-org/Polaris_Pro),
18
- an 8B scientific foundation model built on Qwen3-VL. It reasons over **proteins, RNA, DNA,
19
- and small molecules** through one natural-language interface:
20
 
21
- - RNA / DNA / protein / molecule **classification and regression**
22
- - **Molecule generation** from a natural-language description → SMILES
 
 
 
 
 
23
  - **Scientific text QA**
24
 
25
- Runs on ZeroGPU. Uses the authors' custom multimodal architecture (vendored under `code/`)
26
- and each task's benchmark system prompt.
27
 
28
- Weather forecasting and medical-image segmentation (which needs Meta's gated SAM 3 weights)
29
- are supported by the model but are out of scope for this text-interface demo.
 
 
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 Polaris-Pro custom architecture lives in ./code
9
  CODE_DIR = Path(__file__).parent / "code"
10
  sys.path.insert(0, str(CODE_DIR))
11
 
12
- import spaces # noqa: E402 (must precede torch / CUDA-touching imports)
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 on CPU. This custom architecture's engine
23
- # constructor performs real CUDA initialisation (beyond the plain `.to("cuda")`
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(f"Downloading {MODEL_ID} ...")
30
- MODEL_PATH = snapshot_download(MODEL_ID)
31
- print(f"Model at {MODEL_PATH}")
32
-
33
- print("Building Polaris-Pro inference engine (CPU) ...")
34
- ENGINE = BioQwen3VLInference(
35
- model_path=MODEL_PATH,
36
- device="cpu",
 
 
 
37
  dtype=torch.bfloat16,
38
- attn_impl="sdpa", # SDPA — torch-native, works on ZeroGPU Blackwell
39
- multi_gpu=False,
40
  fail_on_legacy_mol_decoder=False,
41
  )
42
- print("Engine ready (CPU). Model moves to CUDA on first GPU request.")
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
- # Task definitions: each fixes a system prompt + how the sequence is fed.
 
 
59
  # ---------------------------------------------------------------------------
60
  TASKS = {
61
- "RNA — ncRNA family classification": {
62
- "modality": "rna",
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
- "max_new_tokens": 64,
69
  },
70
- "RNA — translation efficiency (regression)": {
71
- "modality": "rna",
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
- "max_new_tokens": 64,
78
  },
79
- "DNA — promoter detection (Yes/No)": {
80
- "modality": "dna",
81
- "system": ("You are a DNA sequence analysis expert. Read the DNA sequence(s) and the "
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
- "max_new_tokens": 16,
89
  },
90
- "DNA — enhancer activity (regression)": {
91
- "modality": "dna",
92
- "system": ("You are a DNA sequence analysis expert. Read the DNA sequence and the question "
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
- "max_new_tokens": 32,
100
  },
101
- "Protein — solubility (0/1)": {
102
- "modality": "protein",
103
- "system": ("You are a protein solubility predictor. This is a binary classification task. "
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
- "max_new_tokens": 16,
 
 
 
 
 
 
 
110
  },
111
- "Protein — Enzyme Commission (EC) number": {
112
- "modality": "protein",
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
- "max_new_tokens": 32,
119
  },
120
- "Molecule — Ames mutagenicity (0/1)": {
121
- "modality": "mol",
122
- "system": ("You are a molecular property prediction expert; given a molecule's SMILES string "
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
- "max_new_tokens": 16,
130
  },
131
- "Molecule — dipole moment (regression)": {
132
- "modality": "mol",
133
- "system": ("You are a molecular property prediction expert. Based on the input molecular "
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
- "max_new_tokens": 32,
140
  },
141
- "Molecule — text description → SMILES (generation)": {
142
- "modality": "text",
143
- "system": ("You are a molecule generation expert. Given a natural-language molecular "
144
- "description, generate one molecule as a valid canonical SMILES string. Output "
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
- "max_new_tokens": 128,
154
  },
155
- "Scientific text QA": {
156
- "modality": "text",
157
  "system": None,
158
- "prompt": None, # user supplies the whole question
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
- "max_new_tokens": 256,
166
  },
167
  }
168
 
169
  TASK_NAMES = list(TASKS.keys())
170
 
171
 
172
- def _estimate_duration(task_name, user_text, max_new_tokens=None, *args, **kwargs):
173
- cfg = TASKS.get(task_name, {})
174
- mnt = int(max_new_tokens or cfg.get("max_new_tokens", 64))
175
- # Generous fixed headroom covers the first-call CPU->CUDA move of the
176
- # ~23GB model (cold worker); generation itself is fast.
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
- @spaces.GPU(duration=_estimate_duration)
190
- def run_task(task_name: str, user_text: str, max_new_tokens: int = None) -> str:
191
- """Run Polaris-Pro on one scientific task.
192
 
193
  Args:
194
- task_name: which task to run (selects the fixed system prompt + input modality).
195
- user_text: the sequence (RNA/DNA/protein SMILES) or free-text prompt for the task.
196
- max_new_tokens: optional cap on generated tokens.
 
197
 
198
  Returns:
199
- The model's text answer (label, score, SMILES, or free-form text).
200
  """
201
- if not user_text or not user_text.strip():
202
- return "Please provide an input."
203
-
204
- _ensure_cuda()
205
- cfg = TASKS[task_name]
206
- user_text = user_text.strip()
207
- mnt = int(max_new_tokens) if max_new_tokens else int(cfg["max_new_tokens"])
 
 
 
 
208
 
209
- modality = cfg["modality"]
210
  kwargs = dict(
211
- max_new_tokens=mnt,
212
- do_sample=False, # greedy — deterministic, matches benchmark setting
213
- temperature=0.7,
214
- top_p=0.9,
215
- top_k=50,
216
- system=cfg["system"],
217
  )
218
- if cfg["task"]:
219
- kwargs["task"] = cfg["task"]
220
-
221
- if modality == "rna":
222
- kwargs["rna"] = [user_text.upper()]
223
- kwargs["prompt"] = cfg["prompt"]
224
- elif modality == "dna":
225
- kwargs["dna"] = [user_text.upper()]
226
- kwargs["prompt"] = cfg["prompt"]
227
- elif modality == "protein":
228
- kwargs["protein"] = [user_text.upper()]
229
- kwargs["prompt"] = cfg["prompt"]
230
- elif modality == "mol":
231
- kwargs["mol"] = [user_text]
232
- kwargs["prompt"] = cfg["prompt"]
233
- else: # text (scientific QA or molecule generation from description)
234
- kwargs["prompt"] = user_text
235
-
236
- result = ENGINE.generate_from_prompt(**kwargs)
237
- return result if result else "(empty response)"
238
 
239
 
240
  # ---------------------------------------------------------------------------
241
- # UI
 
242
  # ---------------------------------------------------------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
243
  CSS = """
244
  #col-container { max-width: 1080px; margin: 0 auto; }
245
  .dark .gradio-container { color: var(--body-text-color); }
246
  """
247
 
248
- DESCRIPTION = """
249
- # 🔬 Polaris-Pro — Unified Scientific Multimodal Model
250
-
251
- [**Polaris-Pro**](https://huggingface.co/sais-org/Polaris_Pro) is an 8B scientific foundation model
252
- (built on Qwen3-VL) that reasons over **proteins, RNA, DNA, and small molecules** through one
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
- def _on_task_change(task_name):
260
- cfg = TASKS[task_name]
261
- return gr.update(label=cfg["seq_label"], placeholder=cfg["placeholder"], value="")
262
 
 
 
 
 
263
 
264
- with gr.Blocks(title="Polaris-Pro") as demo:
265
- with gr.Column(elem_id="col-container"):
266
- gr.Markdown(DESCRIPTION)
 
 
267
 
268
- task = gr.Dropdown(
269
- choices=TASK_NAMES,
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
- run_btn = gr.Button("Run", variant="primary")
279
- output = gr.Textbox(label="Model output", lines=4)
280
 
281
  with gr.Accordion("Advanced settings", open=False):
282
- max_new_tokens = gr.Slider(
283
- minimum=8, maximum=512, value=64, step=8,
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
- ["RNA — ncRNA family classification",
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=run_task,
307
  cache_examples=False,
308
  run_on_click=True,
309
  )
310
 
311
- run_btn.click(
312
- run_task,
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.queue(max_size=10).launch(theme=gr.themes.Citrus(), css=CSS, mcp_server=True)
 
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
- # torchvision is required by transformers 5.0.0's video-processor auto-loader
2
- # (Qwen3VLProcessor pulls in Qwen3VLVideoProcessor); without it processor
3
- # loading crashes with "argument of type 'NoneType' is not iterable".
4
- # Left unpinned so it matches the ZeroGPU-preinstalled torch.
5
- torchvision
6
- # transformers 5.0.0 provides the qwen3_vl arch + Sam3Model used by Polaris-Pro.
7
- # Upstream pins this exactly for correctness; keep pinned.
8
  transformers==5.0.0
9
- accelerate
 
10
  safetensors
11
- sentencepiece
12
  # Numerics
13
  numpy
14
  scipy
15
  einops
16
- # Molecule modality: RDKit (SMILES<->graph) + PyTorch-Geometric (GNN encoder)
 
17
  rdkit
18
  torch-geometric
19
- # Image ops (med_seg processor import path)
 
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