multimodalart's picture
multimodalart HF Staff
Upload app.py with huggingface_hub
e55a04f verified
Raw
History Blame Contribute Delete
6.7 kB
import os
os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True")
import spaces # MUST come before any torch/CUDA-touching import
import torch
import gradio as gr
from transformers import AutoProcessor, Qwen3VLForConditionalGeneration
# SpatialCLI-8B is a fine-tune of Qwen3-VL-8B-Instruct. The published weights
# live at ZYT-MFM/SpatialCLI-8B; we try that first and fall back to the base
# model so the demo still runs if the fine-tuned checkpoint is unavailable.
SPATIALCLI_ID = "ZYT-MFM/SpatialCLI-8B"
BASE_ID = "Qwen/Qwen3-VL-8B-Instruct"
MODEL_ID = SPATIALCLI_ID
try:
from huggingface_hub import list_repo_files
sibs = list_repo_files(SPATIALCLI_ID, repo_type="model")
has_weights = any(s.endswith(".safetensors") or s.endswith(".bin") for s in sibs)
if not has_weights:
print(f"[load] {SPATIALCLI_ID} has no weights yet; falling back to {BASE_ID}")
MODEL_ID = BASE_ID
except Exception as e:
print(f"[load] probe failed ({e!r}); falling back to {BASE_ID}")
MODEL_ID = BASE_ID
print(f"[load] loading model + processor from {MODEL_ID}")
processor = AutoProcessor.from_pretrained(MODEL_ID)
model = Qwen3VLForConditionalGeneration.from_pretrained(
MODEL_ID,
torch_dtype=torch.bfloat16,
attn_implementation="sdpa",
).to("cuda").eval()
print("[load] model ready")
@spaces.GPU(duration=60)
def answer_image(
image,
question,
max_new_tokens=1024,
enable_thinking=False,
temperature=0.7,
top_p=0.8,
top_k=20,
):
"""Answer a spatial-reasoning question about an image.
Loads SpatialCLI-8B (a fine-tune of Qwen3-VL-8B-Instruct trained to
internalize specialist spatial-tool capabilities for localization,
segmentation, depth, and pose reasoning) and runs direct, tool-free
inference: image + text question -> text answer.
Args:
image: the input image.
question: the spatial-reasoning question to ask.
max_new_tokens: maximum number of new tokens to generate.
enable_thinking: enable the model's thinking/reasoning trace.
temperature: sampling temperature.
top_p: nucleus sampling probability.
top_k: top-k sampling.
"""
if image is None:
return "Please provide an image."
if not (question or "").strip():
return "Please provide a question."
messages = [
{
"role": "user",
"content": [
{"type": "image", "image": image},
{"type": "text", "text": question},
],
}
]
inputs = processor.apply_chat_template(
messages,
tokenize=True,
add_generation_prompt=True,
return_dict=True,
return_tensors="pt",
).to("cuda")
gen_kwargs = dict(
max_new_tokens=int(max_new_tokens),
do_sample=True,
temperature=float(temperature),
top_p=float(top_p),
top_k=int(top_k),
repetition_penalty=1.0,
)
if enable_thinking:
gen_kwargs["chat_template_kwargs"] = {"enable_thinking": True}
with torch.inference_mode():
out_ids = model.generate(**inputs, **gen_kwargs)
in_ids = inputs["input_ids"]
trimmed = [out[len(in_):] for in_, out in zip(in_ids, out_ids)]
text = processor.batch_decode(
trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=False
)[0]
return text.strip()
CSS = """
#col-container { max-width: 1100px; margin: 0 auto; }
.dark .gradio-container { color: var(--body-text-color); }
"""
with gr.Blocks() as demo:
gr.Markdown(
"# SpatialCLI-8B — Spatial Reasoning VLM\n"
"Ask spatial-reasoning questions (localization, depth, pose, "
"spatial relationships) about an image. SpatialCLI-8B is trained "
"to internalize specialist spatial-tool capabilities, performing "
"tool-free spatial reasoning at inference time."
)
with gr.Row(elem_id="col-container"):
with gr.Column(scale=1):
image_in = gr.Image(type="filepath", label="Image", height=320)
question_in = gr.Textbox(
label="Question",
placeholder="e.g. Which object is closer to the camera, the chair or the table?",
lines=3,
)
run_btn = gr.Button("Run", variant="primary")
with gr.Accordion("Advanced settings", open=False):
max_tokens = gr.Slider(64, 8192, value=1024, step=64,
label="Max new tokens")
thinking = gr.Checkbox(value=False,
label="Enable thinking trace")
temp = gr.Slider(0.0, 2.0, value=0.7, step=0.05, label="Temperature")
top_p_s = gr.Slider(0.0, 1.0, value=0.8, step=0.05, label="Top-p")
top_k_s = gr.Slider(1, 100, value=20, step=1, label="Top-k")
with gr.Column(scale=1):
out = gr.Textbox(label="Answer", lines=12)
run_btn.click(
answer_image,
inputs=[image_in, question_in, max_tokens, thinking, temp, top_p_s, top_k_s],
outputs=out,
api_name="answer",
)
gr.Examples(
examples=[
["cafe_interior.jpg",
"Describe the spatial layout of this cafe. Which tables are closest to the camera, and which are furthest? How are the chairs arranged relative to the tables?"],
["city_skyline_night.jpg",
"Estimate the relative depths of the buildings in this skyline. Which buildings appear closest to the camera, and which are furthest away?"],
["autumn_forest_path.jpg",
"Describe the spatial structure of this path. Does it recede into the distance? Estimate which trees are nearest vs. furthest from the viewer."],
],
inputs=[image_in, question_in],
outputs=out,
fn=answer_image,
cache_examples=True,
cache_mode="lazy",
)
gr.Markdown(
"---\n"
"**Model:** [ZYT-MFM/SpatialCLI-8B](https://huggingface.co/ZYT-MFM/SpatialCLI-8B) "
"(fine-tune of [Qwen3-VL-8B-Instruct](https://huggingface.co/Qwen/Qwen3-VL-8B-Instruct)). "
"**Paper:** [SpatialCLI: Learning to Reason With Spatial Tools, Then Without Them](https://huggingface.co/papers/2607.27703). "
"**Code:** [IANNXANG/SpatialCLI](https://github.com/IANNXANG/SpatialCLI)."
)
if __name__ == "__main__":
demo.launch(mcp_server=True, theme=gr.themes.Citrus(), css=CSS)