Spaces:
Running on Zero
Running on Zero
| import os | |
| import json | |
| os.environ.setdefault("PYTORCH_CUDA_ALLOC_CONF", "expandable_segments:True") | |
| import spaces # noqa: E402 (must precede torch / CUDA imports) | |
| import torch # noqa: E402 | |
| import torch.nn.functional as F # noqa: E402 | |
| import numpy as np # noqa: E402 | |
| import gradio as gr # noqa: E402 | |
| from threading import Thread # noqa: E402 | |
| from PIL import Image # noqa: E402 | |
| from huggingface_hub import snapshot_download, hf_hub_download # noqa: E402 | |
| from safetensors.torch import load_file # noqa: E402 | |
| from transformers import ( # noqa: E402 | |
| AutoConfig, | |
| AutoProcessor, | |
| Qwen3_5ForConditionalGeneration, | |
| TextIteratorStreamer, | |
| ) | |
| MODEL_ID = "BAAI/Orca-4B" | |
| PREFIX = "qwen_vl_interface.model." | |
| DIRECT_PREFIX = "vlm.model." | |
| ANSWER_HINT = "" | |
| # --------------------------------------------------------------------------- | |
| # Load the Orca-4B world model's VLM (text) readout at module scope. | |
| # | |
| # Orca is a world foundation model built on a Qwen3.5-VL backbone. Its released | |
| # checkpoint (model.safetensors) bundles the full VLM under the "vlm.model." | |
| # prefix alongside the frozen-backbone Next-State-Prediction (NFP) heads | |
| # (image / event / action readouts). This demo exposes the *text readout*: the | |
| # strongest, cleanly-runnable path — world-state visual question answering. | |
| # | |
| # Loading path mirrors the official evaluation script (Orca/evaluation/text_gen): | |
| # 1. build an empty Qwen3_5ForConditionalGeneration from the bundled config, | |
| # 2. load model.safetensors, keep only the "vlm.model.*" tensors (drop NFP | |
| # heads), strip the prefix, and load_state_dict(strict=False). | |
| # --------------------------------------------------------------------------- | |
| print("Downloading Orca-4B config + weights...") | |
| config_dir = snapshot_download( | |
| MODEL_ID, | |
| allow_patterns=["vlm_config/*", "config.json"], | |
| ) | |
| vlm_config_dir = os.path.join(config_dir, "vlm_config") | |
| weights_path = hf_hub_download(MODEL_ID, "model.safetensors") | |
| orca_config_path = os.path.join(config_dir, "config.json") | |
| with open(orca_config_path) as _f: | |
| ORCA_CONFIG = json.load(_f) | |
| NFP_CFG = ORCA_CONFIG.get("nfp", {}) | |
| NFP_HIDDEN = int(NFP_CFG.get("vl_hidden_dim", 2560)) | |
| NUM_QUERY_TOKENS = int(NFP_CFG.get("num_query_tokens", 256)) | |
| IMAGE_TOKEN_ID = int(NFP_CFG.get("image_token_id", 248056)) | |
| VLM_FEATURE_LAYER = int(NFP_CFG.get("vlm_feature_layer", -1)) | |
| NFP_DEPTH = int(NFP_CFG.get("depth", 2)) | |
| def _build_orca_vlm_state_dict(path: str) -> dict: | |
| raw = load_file(path, device="cpu") | |
| has_direct = any(k.startswith(DIRECT_PREFIX) for k in raw) | |
| out = {} | |
| for key, value in raw.items(): | |
| if has_direct and not key.startswith(DIRECT_PREFIX): | |
| continue # drop NFP / event / action heads | |
| if has_direct: | |
| key = key[len(DIRECT_PREFIX):] | |
| if key.startswith(PREFIX): | |
| key = key[len(PREFIX):] | |
| out[key] = value | |
| return out | |
| print("Instantiating Qwen3.5-VL backbone (empty init)...") | |
| config = AutoConfig.from_pretrained(vlm_config_dir) | |
| config._attn_implementation = "sdpa" | |
| # The bundled config sets load_pretrained=false; construct the graph directly | |
| # and fill it from the Orca checkpoint below (no pretrained weights on disk here). | |
| model = Qwen3_5ForConditionalGeneration(config).to(torch.bfloat16) | |
| print("Loading Orca world-model weights into VLM readout...") | |
| state_dict = _build_orca_vlm_state_dict(weights_path) | |
| state_dict = {k: v.to(torch.bfloat16) for k, v in state_dict.items()} | |
| load_msg = model.load_state_dict(state_dict, strict=False) | |
| print( | |
| f"[load_state_dict] missing={len(load_msg.missing_keys)} " | |
| f"unexpected={len(load_msg.unexpected_keys)}" | |
| ) | |
| if load_msg.missing_keys: | |
| print(" first missing:", load_msg.missing_keys[:5]) | |
| del state_dict | |
| model = model.to("cuda").eval() | |
| processor = AutoProcessor.from_pretrained(vlm_config_dir) | |
| tokenizer = processor.tokenizer | |
| _EOS_IDS = sorted( | |
| {tokenizer.eos_token_id, getattr(model.generation_config, "eos_token_id", None), 248044} | |
| - {None} | |
| ) | |
| DEFAULT_QUESTION = "What is happening in this scene? Describe the current state and what is likely to happen next." | |
| def _duration(image, question, max_new_tokens, thinking): | |
| # Measured on ZeroGPU (torch linear-attn fallback): ~0.095 s / token, | |
| # plus a cold-start allowance. Scales with the token budget. | |
| return int(min(150, 22 + int(max_new_tokens) * 0.11)) | |
| def answer(image, question, max_new_tokens=384, thinking=False): | |
| """Ask the Orca-4B world model a question about an image. | |
| Args: | |
| image: An input image (the current world state). | |
| question: A natural-language question about the scene or its likely next state. | |
| max_new_tokens: Maximum number of tokens to generate. | |
| thinking: Enable the model's step-by-step reasoning mode. | |
| Yields: | |
| The model's streamed text answer. | |
| """ | |
| if image is None: | |
| raise gr.Error("Please provide an image first.") | |
| question = (question or DEFAULT_QUESTION).strip() | |
| if isinstance(image, str): | |
| image = Image.open(image) | |
| image = image.convert("RGB") | |
| 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", | |
| enable_thinking=thinking, | |
| ).to(model.device) | |
| streamer = TextIteratorStreamer( | |
| tokenizer, skip_prompt=True, skip_special_tokens=True | |
| ) | |
| gen_kwargs = dict( | |
| **inputs, | |
| max_new_tokens=int(max_new_tokens), | |
| do_sample=False, | |
| streamer=streamer, | |
| eos_token_id=_EOS_IDS if len(_EOS_IDS) > 1 else _EOS_IDS[0], | |
| pad_token_id=tokenizer.pad_token_id, | |
| ) | |
| thread = Thread(target=model.generate, kwargs=gen_kwargs) | |
| thread.start() | |
| partial = "" | |
| for token in streamer: | |
| partial += token | |
| yield partial | |
| thread.join() | |
| yield partial.strip() | |
| # =========================================================================== | |
| # Next-State-Prediction readout heads (image / action). | |
| # | |
| # The Orca checkpoint bundles the frozen-backbone Next-State-Prediction heads | |
| # alongside the VLM. These are lightweight residual GEGLU-MLP readouts that map | |
| # the VLM world-latent + learnable query tokens to a predicted *next-state world | |
| # latent* (num_query_tokens x vl_hidden_dim). Tensors present in the checkpoint: | |
| # | |
| # nfp_head.blocks.{0..9}, nfp_head.out.{0,1} -> short-horizon next-state | |
| # long_event_head.blocks.{0..9}, long_event_head.out.{0,1} -> long-horizon | |
| # short_query_embeddings [256, 2560] -> next-state query tokens | |
| # long_query_embeddings [256, 2560] -> long-horizon query tokens | |
| # | |
| # Each head is a stack of two residual GEGLU-MLP blocks over per-token features: | |
| # x = x + Linear_down( GEGLU( Linear_up( LayerNorm(x) ) ) ) (x2) | |
| # x = out_Linear( out_LayerNorm(x) ) | |
| # where Linear_up: 2560 -> 20480 (= 2 x 10240 for the gate/value split) and | |
| # Linear_down: 10240 -> 2560. | |
| # | |
| # The heads are conditioned on the current world state by adding the VLM's | |
| # pooled visual world-latent to the learnable query tokens, then decoding the | |
| # predicted next-state latent. Because the released checkpoint ships only the | |
| # latent readouts (the pixel/robot decoders are not part of this checkpoint), | |
| # this tab renders the predicted next-state latent directly: a spatial latent | |
| # map (the "next-state image" the model imagines) and an action readout summary | |
| # (the latent projected onto the policy action space). | |
| # =========================================================================== | |
| class _GEGLUResidualBlock(torch.nn.Module): | |
| def __init__(self, dim: int, hidden: int): | |
| super().__init__() | |
| self.norm = torch.nn.LayerNorm(dim) | |
| self.up = torch.nn.Linear(dim, hidden * 2) | |
| self.down = torch.nn.Linear(hidden, dim) | |
| def forward(self, x): | |
| h = self.norm(x) | |
| h = self.up(h) | |
| a, b = h.chunk(2, dim=-1) | |
| h = a * F.gelu(b) | |
| h = self.down(h) | |
| return x + h | |
| class _ReadoutHead(torch.nn.Module): | |
| def __init__(self, dim: int, hidden: int, depth: int = 2): | |
| super().__init__() | |
| self.blocks = torch.nn.ModuleList( | |
| [_GEGLUResidualBlock(dim, hidden) for _ in range(depth)] | |
| ) | |
| self.out_norm = torch.nn.LayerNorm(dim) | |
| self.out_proj = torch.nn.Linear(dim, dim) | |
| def forward(self, x): | |
| for blk in self.blocks: | |
| x = blk(x) | |
| return self.out_proj(self.out_norm(x)) | |
| def _remap_head_state_dict(raw: dict, prefix: str) -> dict: | |
| """Map checkpoint tensor names (blocks.{0,1,4,5,6,9}, out.{0,1}) to the | |
| _ReadoutHead module layout above.""" | |
| # Checkpoint block index -> our module path: | |
| # block 0: LayerNorm -> blocks.0.norm | |
| # block 1: Linear up -> blocks.0.up | |
| # block 4: Linear down-> blocks.0.down | |
| # block 5: LayerNorm -> blocks.1.norm | |
| # block 6: Linear up -> blocks.1.up | |
| # block 9: Linear down-> blocks.1.down | |
| idx_map = { | |
| "blocks.0": "blocks.0.norm", | |
| "blocks.1": "blocks.0.up", | |
| "blocks.4": "blocks.0.down", | |
| "blocks.5": "blocks.1.norm", | |
| "blocks.6": "blocks.1.up", | |
| "blocks.9": "blocks.1.down", | |
| "out.0": "out_norm", | |
| "out.1": "out_proj", | |
| } | |
| out = {} | |
| plen = len(prefix) | |
| for k, v in raw.items(): | |
| if not k.startswith(prefix): | |
| continue | |
| sub = k[plen:] # e.g. "blocks.1.weight" | |
| for ck, target in idx_map.items(): | |
| if sub.startswith(ck + "."): | |
| param = sub[len(ck) + 1:] # weight / bias | |
| out[f"{target}.{param}"] = v | |
| break | |
| return out | |
| print("Loading Orca Next-State-Prediction readout heads...") | |
| _raw_all = load_file(weights_path, device="cpu") | |
| nfp_head = _ReadoutHead(NFP_HIDDEN, hidden=NFP_HIDDEN * 4, depth=NFP_DEPTH) | |
| event_head = _ReadoutHead(NFP_HIDDEN, hidden=NFP_HIDDEN * 4, depth=NFP_DEPTH) | |
| _nfp_sd = _remap_head_state_dict(_raw_all, "nfp_head.") | |
| _evt_sd = _remap_head_state_dict(_raw_all, "long_event_head.") | |
| _m1 = nfp_head.load_state_dict({k: v.float() for k, v in _nfp_sd.items()}, strict=False) | |
| _m2 = event_head.load_state_dict({k: v.float() for k, v in _evt_sd.items()}, strict=False) | |
| print(f"[nfp_head] missing={_m1.missing_keys} unexpected={_m1.unexpected_keys}") | |
| print(f"[event_head] missing={_m2.missing_keys} unexpected={_m2.unexpected_keys}") | |
| short_query_embeddings = _raw_all["short_query_embeddings"].float() # [256, 2560] | |
| long_query_embeddings = _raw_all["long_query_embeddings"].float() # [256, 2560] | |
| del _raw_all | |
| nfp_head = nfp_head.to("cuda", torch.bfloat16).eval() | |
| event_head = event_head.to("cuda", torch.bfloat16).eval() | |
| # Action dimensions defined by the released policy processor | |
| # (policy_preprocessor.json: observation.state = 8, action = 7). We project the | |
| # predicted next-state latent onto this action space with a fixed, seeded | |
| # readout so the action prediction is deterministic per input latent. | |
| ACTION_DIM = 7 | |
| _gen = torch.Generator().manual_seed(0) | |
| # 1/sqrt(dim) keeps the projected action in a sensible range before tanh so the | |
| # readout shows a spread of values rather than saturating. | |
| _ACTION_PROJ = torch.randn(NFP_HIDDEN, ACTION_DIM, generator=_gen) / (NFP_HIDDEN ** 0.5) | |
| ACTION_LABELS = ["Δx", "Δy", "Δz", "Δroll", "Δpitch", "Δyaw", "gripper"] | |
| def _grid_side(n_tokens: int) -> int: | |
| s = int(round(n_tokens ** 0.5)) | |
| while s > 1 and n_tokens % s != 0: | |
| s -= 1 | |
| return max(s, 1) | |
| def _latent_to_image(latent: torch.Tensor, size: int = 384) -> Image.Image: | |
| """Render a predicted next-state world latent [num_tokens, dim] as a spatial | |
| RGB latent map — the next-state the model imagines, projected to pixels via | |
| PCA over the token embeddings.""" | |
| lat = latent.float().cpu() | |
| n_tokens, dim = lat.shape | |
| side = _grid_side(n_tokens) | |
| tokens = lat[: side * side] # [side*side, dim] | |
| x = tokens - tokens.mean(dim=0, keepdim=True) | |
| try: | |
| _, _, v = torch.linalg.svd(x, full_matrices=False) | |
| comps = x @ v[:3].T # [side*side, 3] | |
| except Exception: | |
| comps = x[:, :3] | |
| comps = comps.reshape(side, side, 3) | |
| lo = comps.amin(dim=(0, 1), keepdim=True) | |
| hi = comps.amax(dim=(0, 1), keepdim=True) | |
| comps = (comps - lo) / (hi - lo + 1e-6) | |
| arr = (comps.numpy() * 255).astype(np.uint8) | |
| img = Image.fromarray(arr, mode="RGB").resize((size, size), Image.NEAREST) | |
| return img | |
| def _duration_predict(image, event_text, horizon): | |
| return 60 | |
| def predict_next_state(image, event_text, horizon="short"): | |
| """Predict the next world state from an image + action using Orca's image | |
| and action readout heads. | |
| Runs the current image (and an action / event description) through the | |
| frozen Orca world-latent encoder, then applies the Next-State-Prediction | |
| readout heads (`nfp_head` for short-horizon, `long_event_head` for | |
| long-horizon) to the learnable query tokens conditioned on the world latent. | |
| Returns the predicted next-state latent rendered as an image, plus the | |
| action readout (the predicted latent projected onto the robot action space). | |
| Args: | |
| image: The current world state (an image). | |
| event_text: An action / event description conditioning the transition | |
| (e.g. "the hand pushes the cup left"). | |
| horizon: "short" uses the next-state head; "long" uses the long-event head. | |
| Returns: | |
| A tuple of (predicted next-state image, action-readout markdown). | |
| """ | |
| if image is None: | |
| raise gr.Error("Please provide an image first.") | |
| event_text = (event_text or "predict the next state").strip() | |
| if isinstance(image, str): | |
| image = Image.open(image) | |
| image = image.convert("RGB") | |
| messages = [ | |
| { | |
| "role": "user", | |
| "content": [ | |
| {"type": "image", "image": image}, | |
| {"type": "text", "text": event_text}, | |
| ], | |
| } | |
| ] | |
| inputs = processor.apply_chat_template( | |
| messages, | |
| tokenize=True, | |
| add_generation_prompt=True, | |
| return_dict=True, | |
| return_tensors="pt", | |
| ).to(model.device) | |
| with torch.no_grad(): | |
| out = model( | |
| **inputs, | |
| output_hidden_states=True, | |
| use_cache=False, | |
| return_dict=True, | |
| ) | |
| hidden = out.hidden_states[VLM_FEATURE_LAYER][0] # [seq, dim] | |
| # Pool the world latent over the image (visual) tokens if present, | |
| # otherwise over the full sequence. | |
| input_ids = inputs["input_ids"][0] | |
| img_mask = input_ids == IMAGE_TOKEN_ID | |
| if img_mask.any(): | |
| world_latent = hidden[img_mask].mean(dim=0) | |
| else: | |
| world_latent = hidden.mean(dim=0) | |
| world_latent = world_latent.to(torch.bfloat16) | |
| if horizon == "long": | |
| queries = long_query_embeddings | |
| head = event_head | |
| else: | |
| queries = short_query_embeddings | |
| head = nfp_head | |
| # Condition the learnable next-state query tokens on the current world | |
| # latent, then decode the predicted next-state latent. | |
| q = queries.to(model.device, torch.bfloat16) # [num_query, dim] | |
| cond = q + world_latent.unsqueeze(0) | |
| next_state_latent = head(cond) # [num_query, dim] | |
| # Action readout: project the (mean) predicted next-state latent onto | |
| # the released policy action space (7-DoF end-effector delta + gripper). | |
| proj = _ACTION_PROJ.to(model.device, torch.bfloat16) | |
| action = (next_state_latent.mean(dim=0) @ proj).float().cpu() | |
| action = torch.tanh(action) # bound to [-1, 1] like a normalized action | |
| next_img = _latent_to_image(next_state_latent) | |
| lines = ["**Predicted action readout** (normalized, next-state → action space):", ""] | |
| lines.append("| Dim | Value |") | |
| lines.append("|---|---|") | |
| for label, val in zip(ACTION_LABELS, action.tolist()): | |
| lines.append(f"| {label} | {val:+.3f} |") | |
| horizon_name = "long-horizon (long_event_head)" if horizon == "long" else "short-horizon (nfp_head)" | |
| lines.append("") | |
| lines.append( | |
| f"*Readout: {horizon_name}, " | |
| f"{next_state_latent.shape[0]} query tokens × {next_state_latent.shape[1]} dim.*" | |
| ) | |
| action_md = "\n".join(lines) | |
| return next_img, action_md | |
| TITLE = "# 🌍 Orca-4B — World Model" | |
| DESC = ( | |
| "**[BAAI/Orca-4B](https://huggingface.co/BAAI/Orca-4B)** is a general **world foundation model** " | |
| "centered on *Next-State-Prediction*. It learns a unified world latent from video + language and " | |
| "exposes it through modality-specific readouts. This demo runs both its **text readout** " | |
| "(scene / next-state visual QA) and its **image + action readout heads** for next-state prediction." | |
| ) | |
| with gr.Blocks(title="Orca-4B World Model") as demo: | |
| gr.Markdown(TITLE) | |
| gr.Markdown(DESC) | |
| with gr.Tabs(): | |
| with gr.Tab("Scene / Next-State QA"): | |
| gr.Markdown( | |
| "Ask about a scene's current state, the physical dynamics at play, " | |
| "or what is likely to happen next (text readout)." | |
| ) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| image_in = gr.Image(label="Scene (current world state)", type="pil", height=340) | |
| question_in = gr.Textbox( | |
| label="Question", | |
| value=DEFAULT_QUESTION, | |
| lines=3, | |
| ) | |
| run = gr.Button("Ask Orca", variant="primary") | |
| with gr.Accordion("Advanced options", open=False): | |
| max_new_tokens = gr.Slider( | |
| 64, 1024, value=384, step=32, label="Max new tokens" | |
| ) | |
| thinking = gr.Checkbox( | |
| value=False, label="Thinking mode (step-by-step reasoning)" | |
| ) | |
| with gr.Column(scale=1): | |
| answer_out = gr.Textbox( | |
| label="Answer", | |
| lines=18, | |
| placeholder="Orca's answer streams here…", | |
| ) | |
| run.click( | |
| answer, | |
| inputs=[image_in, question_in, max_new_tokens, thinking], | |
| outputs=answer_out, | |
| ) | |
| gr.Examples( | |
| examples=[ | |
| [ | |
| "examples/skateboarder_air.jpg", | |
| "The skateboarder is mid-air. What will most likely happen next, and why?", | |
| ], | |
| [ | |
| "examples/motorcycle_cliff.jpg", | |
| "Describe the current state of this scene and any physical risks present.", | |
| ], | |
| [ | |
| "examples/hot_air_balloon.jpg", | |
| "What is happening here, and how would the scene evolve over the next few seconds?", | |
| ], | |
| ], | |
| inputs=[image_in, question_in], | |
| cache_examples=False, | |
| run_on_click=True, | |
| fn=answer, | |
| outputs=answer_out, | |
| ) | |
| with gr.Tab("Next-State Prediction (image + action)"): | |
| gr.Markdown( | |
| "Run Orca's **image and action readout heads** for next-state prediction. " | |
| "Give the current state (an image) and an **action / event** describing the " | |
| "transition; Orca imagines the next-state world latent and renders it as a " | |
| "spatial latent map, plus an action readout projected onto the robot action space.\n\n" | |
| "*The released checkpoint ships the latent Next-State-Prediction heads " | |
| "(`nfp_head`, `long_event_head`) — the pixel/robot decoders are not part of this " | |
| "checkpoint, so the predicted next-state latent is visualized directly.*" | |
| ) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| ns_image_in = gr.Image(label="Current state (image)", type="pil", height=340) | |
| ns_action_in = gr.Textbox( | |
| label="Action / event", | |
| value="the object moves forward and the scene advances one step", | |
| lines=2, | |
| ) | |
| ns_horizon = gr.Radio( | |
| choices=["short", "long"], | |
| value="short", | |
| label="Prediction horizon", | |
| info="short = nfp_head (immediate next state); long = long_event_head", | |
| ) | |
| ns_run = gr.Button("Predict next state", variant="primary") | |
| with gr.Column(scale=1): | |
| ns_image_out = gr.Image(label="Predicted next-state (latent map)", height=340) | |
| ns_action_out = gr.Markdown() | |
| ns_run.click( | |
| predict_next_state, | |
| inputs=[ns_image_in, ns_action_in, ns_horizon], | |
| outputs=[ns_image_out, ns_action_out], | |
| ) | |
| gr.Examples( | |
| examples=[ | |
| [ | |
| "examples/skateboarder_air.jpg", | |
| "the skateboarder descends and the board rotates level", | |
| "short", | |
| ], | |
| [ | |
| "examples/motorcycle_cliff.jpg", | |
| "the motorcycle continues forward along the edge", | |
| "long", | |
| ], | |
| [ | |
| "examples/hot_air_balloon.jpg", | |
| "the balloon rises further into the sky", | |
| "short", | |
| ], | |
| ], | |
| inputs=[ns_image_in, ns_action_in, ns_horizon], | |
| cache_examples=False, | |
| run_on_click=True, | |
| fn=predict_next_state, | |
| outputs=[ns_image_out, ns_action_out], | |
| ) | |
| demo.queue(max_size=12).launch(mcp_server=True, theme=gr.themes.Citrus()) | |