akhaliq's picture
akhaliq HF Staff
Fix: pass image through chat template content so pixel_values are produced
3aa909c
Raw
History Blame Contribute Delete
3.99 kB
import os
import spaces
import torch
import gradio as gr
from transformers import AutoModelForImageTextToText, AutoProcessor
MODEL_ID = "CohereLabs/North-Micro-Vision-Instruct"
# Load once at startup. On ZeroGPU the weights stay resident and
# @spaces.GPU allocates a worker per call.
print(f"Loading {MODEL_ID} ...")
processor = AutoProcessor.from_pretrained(MODEL_ID)
model = AutoModelForImageTextToText.from_pretrained(
MODEL_ID,
dtype=torch.bfloat16,
device_map="cuda",
)
print("Model loaded!")
def _estimate_duration(image, prompt, max_new_tokens, temperature, top_p, top_k) -> int:
"""Rough wall-clock estimate (seconds) for one VLM call. Requesting less
than the 60s default raises queue priority and frees the GPU slot sooner
for the next visitor. Scaled by max_new_tokens; clamped to a safe range."""
seconds = 10 + int(max_new_tokens) * 0.15
return max(20, min(int(seconds), 120))
def _friendly_gpu_error(err: Exception) -> str:
msg = (str(err) or "").lower()
capacity_hints = (
"gpu limit", "reached its gpu limit", "gpu quota", "out of quota",
"quota", "no gpu", "could not allocate", "gpu is busy", "too many",
"concurrent",
)
if any(h in msg for h in capacity_hints):
return (
"⛔ This demo's shared GPU is at capacity right now — it's not a "
"problem with your input or your account. Please wait a minute and retry."
)
if "out of memory" in msg or "oom" in msg:
return (
"💥 Ran out of GPU memory. Try a smaller image or fewer max new "
"tokens, then retry."
)
return "⚠️ Generation failed. Please try again in a moment."
@spaces.GPU(duration=_estimate_duration)
def _run_vlm_gpu(image, prompt, max_new_tokens, temperature, top_p, top_k):
"""GPU worker: runs only under a ZeroGPU allocation."""
if not prompt or not prompt.strip():
raise gr.Error("Please enter a prompt.")
if image is None:
raise gr.Error("Please provide an image.")
if isinstance(image, dict):
image_ref = image.get("path") or image.get("url")
else:
image_ref = image
messages = [
{
"role": "user",
"content": [
{"type": "image", "url": image_ref},
{"type": "text", "text": prompt},
],
}
]
inputs = processor.apply_chat_template(
messages,
tokenize=True,
add_generation_prompt=True,
return_tensors="pt",
return_dict=True,
).to(model.device)
if "pixel_values" in inputs:
inputs["pixel_values"] = inputs["pixel_values"].to(torch.bfloat16)
do_sample = float(temperature) > 0
gen_kwargs = dict(max_new_tokens=int(max_new_tokens), do_sample=do_sample)
if do_sample:
gen_kwargs.update(
temperature=float(temperature),
top_p=float(top_p),
top_k=int(top_k),
)
outputs = model.generate(**inputs, **gen_kwargs)
generated_ids = outputs[0][inputs["input_ids"].shape[1]:]
return processor.decode(
generated_ids,
skip_special_tokens=True,
clean_up_tokenization_spaces=False,
)
def run_vlm(image, prompt, max_new_tokens, temperature, top_p, top_k):
"""Workflow-facing wrapper bound to the canvas as a `fn` operator node.
Catches ZeroGPU allocator rejections and rewords them for users."""
try:
return _run_vlm_gpu(image, prompt, max_new_tokens, temperature, top_p, top_k)
except gr.Error:
raise
except Exception as e:
raise gr.Error(_friendly_gpu_error(e)) from e
# The workflow (workflow.json) wires `run_vlm` as a `fn` operator:
# Image, Prompt, Max New Tokens, Temperature, Top P, Top K ─▶
# run_vlm (fn operator, kind="fn") ─▶ Response
demo = gr.Workflow(
graph="workflow.json",
bind={"run_vlm": run_vlm},
)
if __name__ == "__main__":
demo.launch()