Spaces:
Running on Zero
Running on Zero
Add ZeroGPU gr.Workflow app for North-Micro-Vision-Instruct
Browse files- README.md +1 -1
- app.py +133 -0
- requirements.txt +7 -0
- run.py +0 -6
- workflow.json +137 -1
README.md
CHANGED
|
@@ -5,7 +5,7 @@ colorFrom: indigo
|
|
| 5 |
colorTo: indigo
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 6.22.0
|
| 8 |
-
app_file:
|
| 9 |
pinned: false
|
| 10 |
hf_oauth: true
|
| 11 |
---
|
|
|
|
| 5 |
colorTo: indigo
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 6.22.0
|
| 8 |
+
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
hf_oauth: true
|
| 11 |
---
|
app.py
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
+
import spaces
|
| 4 |
+
import torch
|
| 5 |
+
import gradio as gr
|
| 6 |
+
from transformers import AutoModelForImageTextToText, AutoProcessor
|
| 7 |
+
|
| 8 |
+
MODEL_ID = "CohereLabs/North-Micro-Vision-Instruct"
|
| 9 |
+
|
| 10 |
+
# Load once at startup. On ZeroGPU the weights stay resident and
|
| 11 |
+
# @spaces.GPU allocates a worker per call.
|
| 12 |
+
print(f"Loading {MODEL_ID} ...")
|
| 13 |
+
processor = AutoProcessor.from_pretrained(MODEL_ID)
|
| 14 |
+
model = AutoModelForImageTextToText.from_pretrained(
|
| 15 |
+
MODEL_ID,
|
| 16 |
+
dtype=torch.bfloat16,
|
| 17 |
+
device_map="cuda",
|
| 18 |
+
)
|
| 19 |
+
print("Model loaded!")
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def _load_image(image):
|
| 23 |
+
"""Accept a workflow image value (dict with 'path', or a path/URL string)
|
| 24 |
+
and return a PIL.Image."""
|
| 25 |
+
from PIL import Image
|
| 26 |
+
|
| 27 |
+
if isinstance(image, dict):
|
| 28 |
+
image = image.get("path") or image.get("url")
|
| 29 |
+
if isinstance(image, str) and image.startswith(("http://", "https://")):
|
| 30 |
+
import requests
|
| 31 |
+
from io import BytesIO
|
| 32 |
+
|
| 33 |
+
return Image.open(BytesIO(requests.get(image, timeout=30).content)).convert("RGB")
|
| 34 |
+
return Image.open(image).convert("RGB")
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def _estimate_duration(image, prompt, max_new_tokens, temperature, top_p, top_k) -> int:
|
| 38 |
+
"""Rough wall-clock estimate (seconds) for one VLM call. Requesting less
|
| 39 |
+
than the 60s default raises queue priority and frees the GPU slot sooner
|
| 40 |
+
for the next visitor. Scaled by max_new_tokens; clamped to a safe range."""
|
| 41 |
+
seconds = 10 + int(max_new_tokens) * 0.15
|
| 42 |
+
return max(20, min(int(seconds), 120))
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def _friendly_gpu_error(err: Exception) -> str:
|
| 46 |
+
msg = (str(err) or "").lower()
|
| 47 |
+
capacity_hints = (
|
| 48 |
+
"gpu limit", "reached its gpu limit", "gpu quota", "out of quota",
|
| 49 |
+
"quota", "no gpu", "could not allocate", "gpu is busy", "too many",
|
| 50 |
+
"concurrent",
|
| 51 |
+
)
|
| 52 |
+
if any(h in msg for h in capacity_hints):
|
| 53 |
+
return (
|
| 54 |
+
"⛔ This demo's shared GPU is at capacity right now — it's not a "
|
| 55 |
+
"problem with your input or your account. Please wait a minute and retry."
|
| 56 |
+
)
|
| 57 |
+
if "out of memory" in msg or "oom" in msg:
|
| 58 |
+
return (
|
| 59 |
+
"💥 Ran out of GPU memory. Try a smaller image or fewer max new "
|
| 60 |
+
"tokens, then retry."
|
| 61 |
+
)
|
| 62 |
+
return "⚠️ Generation failed. Please try again in a moment."
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
@spaces.GPU(duration=_estimate_duration)
|
| 66 |
+
def _run_vlm_gpu(image, prompt, max_new_tokens, temperature, top_p, top_k):
|
| 67 |
+
"""GPU worker: runs only under a ZeroGPU allocation."""
|
| 68 |
+
if not prompt or not prompt.strip():
|
| 69 |
+
raise gr.Error("Please enter a prompt.")
|
| 70 |
+
if image is None:
|
| 71 |
+
raise gr.Error("Please provide an image.")
|
| 72 |
+
|
| 73 |
+
pil_image = _load_image(image)
|
| 74 |
+
|
| 75 |
+
messages = [
|
| 76 |
+
{
|
| 77 |
+
"role": "user",
|
| 78 |
+
"content": [
|
| 79 |
+
{"type": "image"},
|
| 80 |
+
{"type": "text", "text": prompt},
|
| 81 |
+
],
|
| 82 |
+
}
|
| 83 |
+
]
|
| 84 |
+
|
| 85 |
+
inputs = processor.apply_chat_template(
|
| 86 |
+
messages,
|
| 87 |
+
tokenize=True,
|
| 88 |
+
add_generation_prompt=True,
|
| 89 |
+
return_tensors="pt",
|
| 90 |
+
return_dict=True,
|
| 91 |
+
)
|
| 92 |
+
inputs = {k: v.to(model.device) if hasattr(v, "to") else v for k, v in inputs.items()}
|
| 93 |
+
inputs["pixel_values"] = inputs["pixel_values"].to(torch.bfloat16)
|
| 94 |
+
|
| 95 |
+
do_sample = float(temperature) > 0
|
| 96 |
+
gen_kwargs = dict(max_new_tokens=int(max_new_tokens), do_sample=do_sample)
|
| 97 |
+
if do_sample:
|
| 98 |
+
gen_kwargs.update(
|
| 99 |
+
temperature=float(temperature),
|
| 100 |
+
top_p=float(top_p),
|
| 101 |
+
top_k=int(top_k),
|
| 102 |
+
)
|
| 103 |
+
|
| 104 |
+
outputs = model.generate(**inputs, **gen_kwargs)
|
| 105 |
+
generated_ids = outputs[0][inputs["input_ids"].shape[1]:]
|
| 106 |
+
return processor.decode(
|
| 107 |
+
generated_ids,
|
| 108 |
+
skip_special_tokens=True,
|
| 109 |
+
clean_up_tokenization_spaces=False,
|
| 110 |
+
)
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def run_vlm(image, prompt, max_new_tokens, temperature, top_p, top_k):
|
| 114 |
+
"""Workflow-facing wrapper bound to the canvas as a `fn` operator node.
|
| 115 |
+
Catches ZeroGPU allocator rejections and rewords them for users."""
|
| 116 |
+
try:
|
| 117 |
+
return _run_vlm_gpu(image, prompt, max_new_tokens, temperature, top_p, top_k)
|
| 118 |
+
except gr.Error:
|
| 119 |
+
raise
|
| 120 |
+
except Exception as e:
|
| 121 |
+
raise gr.Error(_friendly_gpu_error(e)) from e
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
# The workflow (workflow.json) wires `run_vlm` as a `fn` operator:
|
| 125 |
+
# Image, Prompt, Max New Tokens, Temperature, Top P, Top K ─▶
|
| 126 |
+
# run_vlm (fn operator, kind="fn") ─▶ Response
|
| 127 |
+
demo = gr.Workflow(
|
| 128 |
+
graph="workflow.json",
|
| 129 |
+
bind={"run_vlm": run_vlm},
|
| 130 |
+
)
|
| 131 |
+
|
| 132 |
+
if __name__ == "__main__":
|
| 133 |
+
demo.launch()
|
requirements.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torch
|
| 2 |
+
accelerate
|
| 3 |
+
pillow
|
| 4 |
+
requests
|
| 5 |
+
spaces
|
| 6 |
+
gradio
|
| 7 |
+
git+https://github.com/huggingface/transformers.git
|
run.py
DELETED
|
@@ -1,6 +0,0 @@
|
|
| 1 |
-
import gradio as gr
|
| 2 |
-
|
| 3 |
-
demo = gr.Workflow()
|
| 4 |
-
|
| 5 |
-
if __name__ == "__main__":
|
| 6 |
-
demo.launch()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
workflow.json
CHANGED
|
@@ -1 +1,137 @@
|
|
| 1 |
-
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"schema_version": "2",
|
| 3 |
+
"name": "North Micro Vision Instruct",
|
| 4 |
+
"description": "Visual question answering, captioning, OCR, and grounding with CohereLabs/North-Micro-Vision-Instruct (2.4B native-resolution VLM) on ZeroGPU, driven by a gr.Workflow fn-bound @spaces.GPU function. Single Space: the workflow frontend and the GPU worker share one process.",
|
| 5 |
+
"runtime": { "default": "client" },
|
| 6 |
+
"view": { "default": "canvas" },
|
| 7 |
+
"references": [
|
| 8 |
+
{
|
| 9 |
+
"id": "ref_image",
|
| 10 |
+
"label": "Input Image",
|
| 11 |
+
"role": "reference",
|
| 12 |
+
"asset_type": "image",
|
| 13 |
+
"inputs": [{ "id": "in", "label": "Image", "type": "image" }],
|
| 14 |
+
"outputs": [{ "id": "out", "label": "Image", "type": "image" }],
|
| 15 |
+
"x": 60,
|
| 16 |
+
"y": 120,
|
| 17 |
+
"width": 240,
|
| 18 |
+
"height": 140,
|
| 19 |
+
"data": {}
|
| 20 |
+
},
|
| 21 |
+
{
|
| 22 |
+
"id": "ref_prompt",
|
| 23 |
+
"label": "Prompt",
|
| 24 |
+
"role": "reference",
|
| 25 |
+
"asset_type": "text",
|
| 26 |
+
"inputs": [{ "id": "in", "label": "Prompt", "type": "text" }],
|
| 27 |
+
"outputs": [{ "id": "out", "label": "Prompt", "type": "text" }],
|
| 28 |
+
"x": 60,
|
| 29 |
+
"y": 300,
|
| 30 |
+
"width": 240,
|
| 31 |
+
"height": 120,
|
| 32 |
+
"data": { "out": "What do you see in this image?" }
|
| 33 |
+
},
|
| 34 |
+
{
|
| 35 |
+
"id": "ref_max_tokens",
|
| 36 |
+
"label": "Max New Tokens",
|
| 37 |
+
"role": "reference",
|
| 38 |
+
"asset_type": "number",
|
| 39 |
+
"inputs": [{ "id": "in", "label": "Max New Tokens", "type": "number" }],
|
| 40 |
+
"outputs": [{ "id": "out", "label": "Max New Tokens", "type": "number" }],
|
| 41 |
+
"x": 60,
|
| 42 |
+
"y": 460,
|
| 43 |
+
"width": 200,
|
| 44 |
+
"height": 90,
|
| 45 |
+
"data": { "out": 256 }
|
| 46 |
+
},
|
| 47 |
+
{
|
| 48 |
+
"id": "ref_temperature",
|
| 49 |
+
"label": "Temperature",
|
| 50 |
+
"role": "reference",
|
| 51 |
+
"asset_type": "number",
|
| 52 |
+
"inputs": [{ "id": "in", "label": "Temperature", "type": "number" }],
|
| 53 |
+
"outputs": [{ "id": "out", "label": "Temperature", "type": "number" }],
|
| 54 |
+
"x": 60,
|
| 55 |
+
"y": 570,
|
| 56 |
+
"width": 200,
|
| 57 |
+
"height": 90,
|
| 58 |
+
"data": { "out": 0.7 }
|
| 59 |
+
},
|
| 60 |
+
{
|
| 61 |
+
"id": "ref_top_p",
|
| 62 |
+
"label": "Top P",
|
| 63 |
+
"role": "reference",
|
| 64 |
+
"asset_type": "number",
|
| 65 |
+
"inputs": [{ "id": "in", "label": "Top P", "type": "number" }],
|
| 66 |
+
"outputs": [{ "id": "out", "label": "Top P", "type": "number" }],
|
| 67 |
+
"x": 60,
|
| 68 |
+
"y": 680,
|
| 69 |
+
"width": 200,
|
| 70 |
+
"height": 90,
|
| 71 |
+
"data": { "out": 0.8 }
|
| 72 |
+
},
|
| 73 |
+
{
|
| 74 |
+
"id": "ref_top_k",
|
| 75 |
+
"label": "Top K",
|
| 76 |
+
"role": "reference",
|
| 77 |
+
"asset_type": "number",
|
| 78 |
+
"inputs": [{ "id": "in", "label": "Top K", "type": "number" }],
|
| 79 |
+
"outputs": [{ "id": "out", "label": "Top K", "type": "number" }],
|
| 80 |
+
"x": 60,
|
| 81 |
+
"y": 790,
|
| 82 |
+
"width": 200,
|
| 83 |
+
"height": 90,
|
| 84 |
+
"data": { "out": 20 }
|
| 85 |
+
}
|
| 86 |
+
],
|
| 87 |
+
"operators": [
|
| 88 |
+
{
|
| 89 |
+
"id": "op_vlm",
|
| 90 |
+
"label": "run_vlm",
|
| 91 |
+
"role": "operator",
|
| 92 |
+
"kind": "fn",
|
| 93 |
+
"source": "fn",
|
| 94 |
+
"fn": "run_vlm",
|
| 95 |
+
"inputs": [
|
| 96 |
+
{ "id": "in_0", "label": "image", "type": "image", "required": true },
|
| 97 |
+
{ "id": "in_1", "label": "prompt", "type": "text", "required": true },
|
| 98 |
+
{ "id": "in_2", "label": "max_new_tokens", "type": "number" },
|
| 99 |
+
{ "id": "in_3", "label": "temperature", "type": "number" },
|
| 100 |
+
{ "id": "in_4", "label": "top_p", "type": "number" },
|
| 101 |
+
{ "id": "in_5", "label": "top_k", "type": "number" }
|
| 102 |
+
],
|
| 103 |
+
"outputs": [
|
| 104 |
+
{ "id": "out_0", "label": "response", "type": "text", "output_index": 0 }
|
| 105 |
+
],
|
| 106 |
+
"x": 420,
|
| 107 |
+
"y": 380,
|
| 108 |
+
"width": 280,
|
| 109 |
+
"height": 220,
|
| 110 |
+
"data": {}
|
| 111 |
+
}
|
| 112 |
+
],
|
| 113 |
+
"subjects": [
|
| 114 |
+
{
|
| 115 |
+
"id": "sub_response",
|
| 116 |
+
"label": "Response",
|
| 117 |
+
"role": "subject",
|
| 118 |
+
"asset_type": "text",
|
| 119 |
+
"inputs": [{ "id": "in", "label": "Response", "type": "text" }],
|
| 120 |
+
"outputs": [{ "id": "out", "label": "Response", "type": "text" }],
|
| 121 |
+
"x": 820,
|
| 122 |
+
"y": 380,
|
| 123 |
+
"width": 280,
|
| 124 |
+
"height": 140,
|
| 125 |
+
"data": {}
|
| 126 |
+
}
|
| 127 |
+
],
|
| 128 |
+
"edges": [
|
| 129 |
+
{ "id": "e_image", "from_node_id": "ref_image", "from_port_id": "out", "to_node_id": "op_vlm", "to_port_id": "in_0", "type": "image" },
|
| 130 |
+
{ "id": "e_prompt", "from_node_id": "ref_prompt", "from_port_id": "out", "to_node_id": "op_vlm", "to_port_id": "in_1", "type": "text" },
|
| 131 |
+
{ "id": "e_max_tokens", "from_node_id": "ref_max_tokens", "from_port_id": "out", "to_node_id": "op_vlm", "to_port_id": "in_2", "type": "number" },
|
| 132 |
+
{ "id": "e_temperature", "from_node_id": "ref_temperature", "from_port_id": "out", "to_node_id": "op_vlm", "to_port_id": "in_3", "type": "number" },
|
| 133 |
+
{ "id": "e_top_p", "from_node_id": "ref_top_p", "from_port_id": "out", "to_node_id": "op_vlm", "to_port_id": "in_4", "type": "number" },
|
| 134 |
+
{ "id": "e_top_k", "from_node_id": "ref_top_k", "from_port_id": "out", "to_node_id": "op_vlm", "to_port_id": "in_5", "type": "number" },
|
| 135 |
+
{ "id": "e_response", "from_node_id": "op_vlm", "from_port_id": "out_0", "to_node_id": "sub_response", "to_port_id": "in", "type": "text" }
|
| 136 |
+
]
|
| 137 |
+
}
|