Spaces:
Running on Zero
Running on Zero
| 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") | |
| 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) |