Image-Text-to-Text
Transformers
Safetensors
qwen3_5
vllm
video
multimodal
reinforcement-learning
temporal-grounding
object-tracking
video-segmentation
visual-question-answering
spatial-reasoning
qwen3.5
conversational
Instructions to use OraRL/Video-ORA-4B with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use OraRL/Video-ORA-4B with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("image-text-to-text", model="OraRL/Video-ORA-4B") messages = [ { "role": "user", "content": [ {"type": "image", "url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/p-blog/candy.JPG"}, {"type": "text", "text": "What animal is on the candy?"} ] }, ] pipe(text=messages)# Load model directly from transformers import AutoProcessor, AutoModelForMultimodalLM processor = AutoProcessor.from_pretrained("OraRL/Video-ORA-4B") model = AutoModelForMultimodalLM.from_pretrained("OraRL/Video-ORA-4B", device_map="auto") messages = [ { "role": "user", "content": [ {"type": "image", "url": "https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/p-blog/candy.JPG"}, {"type": "text", "text": "What animal is on the candy?"} ] }, ] inputs = processor.apply_chat_template( messages, add_generation_prompt=True, tokenize=True, return_dict=True, return_tensors="pt", ).to(model.device) outputs = model.generate(**inputs, max_new_tokens=40) print(processor.decode(outputs[0][inputs["input_ids"].shape[-1]:])) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use OraRL/Video-ORA-4B with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "OraRL/Video-ORA-4B" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "OraRL/Video-ORA-4B", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }'Use Docker
docker model run hf.co/OraRL/Video-ORA-4B
- SGLang
How to use OraRL/Video-ORA-4B with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "OraRL/Video-ORA-4B" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "OraRL/Video-ORA-4B", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "OraRL/Video-ORA-4B" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "OraRL/Video-ORA-4B", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "Describe this image in one sentence." }, { "type": "image_url", "image_url": { "url": "https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg" } } ] } ] }' - Docker Model Runner
How to use OraRL/Video-ORA-4B with Docker Model Runner:
docker model run hf.co/OraRL/Video-ORA-4B
| #!/usr/bin/env python3 | |
| """ | |
| Thin project-local wrapper around OneThinker's SAM2 segmentation post-process. | |
| The OneThinker script contains the full image/video mask generation and metric | |
| logic. This wrapper only injects paths from CLI args so evaluation can be run | |
| from this repository without editing the third-party checkout. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import importlib.util | |
| import json | |
| import multiprocessing.spawn as mp_spawn | |
| import os | |
| import sys | |
| import tempfile | |
| from pathlib import Path | |
| from typing import Any | |
| DEFAULT_ONETHINKER_SCRIPT = str( | |
| Path(__file__).resolve().parents[3] | |
| / "third_party" | |
| / "OneThinker" | |
| / "Evaluation" | |
| / "Eval" | |
| / "seg_post_sam2.py" | |
| ) | |
| def sam2_config_name( | |
| value: str, | |
| package_root: str | Path | None = None, | |
| ) -> str: | |
| """Convert an installed SAM2 YAML path to Hydra's package-relative name.""" | |
| path = Path(value).expanduser() | |
| if not path.is_absolute(): | |
| return path.as_posix() | |
| if package_root is None: | |
| try: | |
| import sam2 | |
| except ImportError: | |
| return str(path) | |
| package_root = Path(sam2.__file__).resolve().parent | |
| try: | |
| return path.resolve().relative_to(Path(package_root).resolve()).as_posix() | |
| except ValueError: | |
| return str(path) | |
| def load_module(script_path: str): | |
| path = Path(script_path) | |
| if not path.is_file(): | |
| raise FileNotFoundError(f"OneThinker SAM2 script not found: {path}") | |
| # Use the real filename as the module name so multiprocessing "spawn" | |
| # workers can import it from path.parent when unpickling worker_run. | |
| module_name = path.stem | |
| spec = importlib.util.spec_from_file_location(module_name, path) | |
| if spec is None or spec.loader is None: | |
| raise RuntimeError(f"Failed to load module spec from {path}") | |
| module = importlib.util.module_from_spec(spec) | |
| sys.path.insert(0, str(path.parent)) | |
| # multiprocessing pickles functions by module name; dynamic modules must be | |
| # registered so child processes can resolve worker_run. | |
| sys.modules[module_name] = module | |
| spec.loader.exec_module(module) | |
| return module | |
| def _results(payload: Any) -> list[dict[str, Any]]: | |
| if isinstance(payload, dict) and isinstance(payload.get("results"), list): | |
| return payload["results"] | |
| if isinstance(payload, list): | |
| return payload | |
| raise ValueError("SAM2 input must be a list or an object containing `results`.") | |
| def _segmentation_output(sample: dict[str, Any]) -> dict[str, Any] | None: | |
| output = sample.get("segmentation_output") | |
| if isinstance(output, dict): | |
| return output | |
| task_payload = sample.get("task_payload") | |
| if isinstance(task_payload, dict): | |
| output = task_payload.get("segmentation_output") | |
| if isinstance(output, dict): | |
| sample["segmentation_output"] = output | |
| return output | |
| return None | |
| def _compressed_rle( | |
| size: Any, | |
| counts: list[Any], | |
| cocomask: Any, | |
| ) -> dict[str, Any] | None: | |
| if ( | |
| not isinstance(size, (list, tuple)) | |
| or len(size) != 2 | |
| or not all(isinstance(value, (int, float)) for value in size) | |
| ): | |
| return None | |
| height, width = (int(size[0]), int(size[1])) | |
| if height <= 0 or width <= 0: | |
| return None | |
| encoded = cocomask.frPyObjects( | |
| {"size": [height, width], "counts": counts}, | |
| height, | |
| width, | |
| ) | |
| if isinstance(encoded, list): | |
| encoded = cocomask.merge(encoded) | |
| encoded_counts = encoded["counts"] | |
| if isinstance(encoded_counts, bytes): | |
| encoded_counts = encoded_counts.decode("utf-8") | |
| return {"size": [height, width], "counts": encoded_counts} | |
| def normalize_missing_rle_counts(payload: Any, cocomask: Any) -> int: | |
| """Repair mask annotations that encode empty masks or polygons without RLE counts.""" | |
| repaired = 0 | |
| for sample in _results(payload): | |
| segmentation = _segmentation_output(sample) | |
| if segmentation is None: | |
| continue | |
| rle = segmentation.get("segmentation_rle") | |
| if sample.get("data_type") == "video": | |
| if not isinstance(rle, dict): | |
| continue | |
| for frame, frame_rle in list(rle.items()): | |
| if not isinstance(frame_rle, dict): | |
| continue | |
| counts = frame_rle.get("counts") | |
| if counts is not None and not isinstance(counts, list): | |
| continue | |
| size = frame_rle.get("size") | |
| if ( | |
| counts is None | |
| and ( | |
| not isinstance(size, (list, tuple)) | |
| or len(size) != 2 | |
| ) | |
| ): | |
| continue | |
| replacement = _compressed_rle( | |
| size, | |
| counts | |
| if isinstance(counts, list) | |
| else [int(size[0]) * int(size[1])], | |
| cocomask, | |
| ) | |
| if replacement is not None: | |
| rle[frame] = replacement | |
| repaired += 1 | |
| continue | |
| if not isinstance(rle, dict): | |
| continue | |
| size = rle.get("size") | |
| counts = rle.get("counts") | |
| if isinstance(counts, list): | |
| replacement = _compressed_rle(size, counts, cocomask) | |
| if replacement is not None: | |
| segmentation["segmentation_rle"] = replacement | |
| repaired += 1 | |
| continue | |
| if counts is not None: | |
| continue | |
| polygons = segmentation.get("segmentation_polygon") | |
| if polygons: | |
| if isinstance(polygons, list) and polygons and isinstance( | |
| polygons[0], (int, float) | |
| ): | |
| polygons = [polygons] | |
| if ( | |
| isinstance(size, (list, tuple)) | |
| and len(size) == 2 | |
| and polygons | |
| ): | |
| height, width = int(size[0]), int(size[1]) | |
| encoded = cocomask.frPyObjects(polygons, height, width) | |
| if isinstance(encoded, list): | |
| encoded = cocomask.merge(encoded) | |
| counts = encoded["counts"] | |
| if isinstance(counts, bytes): | |
| counts = counts.decode("utf-8") | |
| segmentation["segmentation_rle"] = { | |
| "size": [height, width], | |
| "counts": counts, | |
| } | |
| repaired += 1 | |
| continue | |
| replacement = None | |
| if isinstance(size, (list, tuple)) and len(size) == 2: | |
| replacement = _compressed_rle( | |
| size, | |
| [int(size[0]) * int(size[1])], | |
| cocomask, | |
| ) | |
| if replacement is not None: | |
| segmentation["segmentation_rle"] = replacement | |
| repaired += 1 | |
| return repaired | |
| def _sam2_output_path(input_json: str | Path) -> Path: | |
| path = Path(input_json) | |
| return path.with_name(f"{path.stem}_sam2.json") | |
| def _aggregate_rewards(payload: dict[str, Any]) -> None: | |
| ok_items = [ | |
| row | |
| for row in _results(payload) | |
| if row.get("status") == "ok" and isinstance(row.get("reward"), dict) | |
| ] | |
| video_j: list[float] = [] | |
| video_f: list[float] = [] | |
| video_jf: list[float] = [] | |
| image_iou: list[float] = [] | |
| total_inter = 0 | |
| total_union = 0 | |
| for row in ok_items: | |
| reward = row["reward"] | |
| if all(key in reward for key in ("J", "F", "J&F")): | |
| video_j.append(float(reward["J"])) | |
| video_f.append(float(reward["F"])) | |
| video_jf.append(float(reward["J&F"])) | |
| if "IoU" in reward: | |
| image_iou.append(float(reward["IoU"])) | |
| total_inter += int(reward.get("inter", 0)) | |
| total_union += int(reward.get("union", 0)) | |
| averages: dict[str, float] = {} | |
| if video_j: | |
| averages.update( | |
| { | |
| "video/J": sum(video_j) / len(video_j), | |
| "video/F": sum(video_f) / len(video_f), | |
| "video/J&F": sum(video_jf) / len(video_jf), | |
| } | |
| ) | |
| if image_iou: | |
| giou = sum(image_iou) / len(image_iou) | |
| averages.update( | |
| { | |
| "image/IoU": giou, | |
| "image/gIoU": giou, | |
| "image/cIoU": ( | |
| float(total_inter) / float(total_union) if total_union else 0.0 | |
| ), | |
| } | |
| ) | |
| payload["avg_rewards"] = averages | |
| def _write_json_atomic(path: Path, payload: dict[str, Any]) -> None: | |
| path.parent.mkdir(parents=True, exist_ok=True) | |
| fd, temporary = tempfile.mkstemp( | |
| dir=path.parent, | |
| prefix=f".{path.name}.", | |
| suffix=".tmp", | |
| ) | |
| try: | |
| with os.fdopen(fd, "w", encoding="utf-8") as stream: | |
| json.dump(payload, stream, ensure_ascii=False, indent=2) | |
| os.replace(temporary, path) | |
| except BaseException: | |
| try: | |
| os.unlink(temporary) | |
| except FileNotFoundError: | |
| pass | |
| raise | |
| def _run_upstream( | |
| module: Any, | |
| *, | |
| input_json: Path, | |
| viz_ratio: float, | |
| normalize: bool, | |
| ) -> Path: | |
| original_load = module.json.load | |
| if normalize: | |
| def load_and_normalize(*args: Any, **kwargs: Any) -> Any: | |
| payload = original_load(*args, **kwargs) | |
| repaired = normalize_missing_rle_counts(payload, module.cocomask) | |
| if repaired: | |
| print(f"[INFO] normalized {repaired} mask RLE entries without counts") | |
| return payload | |
| module.json.load = load_and_normalize | |
| try: | |
| sys.argv = [ | |
| str(Path(module.__file__).name), | |
| "--input_json", | |
| str(input_json), | |
| "--viz_ratio", | |
| str(viz_ratio), | |
| ] | |
| module.main() | |
| finally: | |
| module.json.load = original_load | |
| return _sam2_output_path(input_json) | |
| def _retry_errors( | |
| module: Any, | |
| *, | |
| output_path: Path, | |
| attempts: int, | |
| viz_ratio: float, | |
| ) -> tuple[dict[str, Any], int]: | |
| with output_path.open(encoding="utf-8") as stream: | |
| payload = json.load(stream) | |
| retried = 0 | |
| result_fields = ( | |
| "reward", | |
| "status", | |
| "viz_key_pred", | |
| "viz_key_gt", | |
| "viz_pred_video", | |
| "viz_gt_video", | |
| ) | |
| module.WORKERS_PER_GPU = 1 | |
| for attempt in range(1, attempts + 1): | |
| failed = [ | |
| row | |
| for row in _results(payload) | |
| if str(row.get("status", "")).startswith("error:") | |
| ] | |
| if not failed: | |
| break | |
| retried += len(failed) | |
| retry_payload = {"results": failed} | |
| repaired = normalize_missing_rle_counts(retry_payload, module.cocomask) | |
| if repaired: | |
| print(f"[INFO] normalized {repaired} mask RLE entries without counts") | |
| print( | |
| f"[INFO] retrying {len(failed)} SAM2 errors " | |
| f"(attempt {attempt}/{attempts}, workers_per_gpu=1)" | |
| ) | |
| with tempfile.TemporaryDirectory( | |
| dir=output_path.parent, | |
| prefix=".sam2-retry-", | |
| ) as temporary_dir: | |
| retry_input = Path(temporary_dir) / "retry.json" | |
| with retry_input.open("w", encoding="utf-8") as stream: | |
| json.dump(retry_payload, stream, ensure_ascii=False) | |
| retry_output = _run_upstream( | |
| module, | |
| input_json=retry_input, | |
| viz_ratio=viz_ratio, | |
| normalize=False, | |
| ) | |
| with retry_output.open(encoding="utf-8") as stream: | |
| retry_result = json.load(stream) | |
| retried_by_id = { | |
| row.get("problem_id"): row for row in _results(retry_result) | |
| } | |
| for row in _results(payload): | |
| retry_row = retried_by_id.get(row.get("problem_id")) | |
| if retry_row is not None: | |
| for field in result_fields: | |
| row[field] = retry_row.get(field) | |
| _aggregate_rewards(payload) | |
| _write_json_atomic(output_path, payload) | |
| return payload, retried | |
| def main() -> None: | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument("--input_json", required=True, help="Prediction JSON from eval_seg_vllm.py.") | |
| parser.add_argument("--data_root", required=True, help="Root joined with each record's relative `path`.") | |
| parser.add_argument("--sam2_ckpt", required=True) | |
| parser.add_argument("--sam2_cfg", required=True) | |
| parser.add_argument("--onethinker_script", default=DEFAULT_ONETHINKER_SCRIPT) | |
| parser.add_argument("--num_gpus", type=int, default=None) | |
| parser.add_argument("--workers_per_gpu", type=int, default=None) | |
| parser.add_argument("--pre_extract_threads", type=int, default=None) | |
| parser.add_argument("--epoch_size", type=int, default=None) | |
| parser.add_argument("--viz_ratio", type=float, default=0.0) | |
| parser.add_argument( | |
| "--retry_attempts", | |
| type=int, | |
| default=1, | |
| help="Retry per-sample SAM2 errors with one worker per GPU.", | |
| ) | |
| parser.add_argument( | |
| "--retry_existing_errors", | |
| action="store_true", | |
| help="Retry errors in an existing sibling *_sam2.json without rerunning all samples.", | |
| ) | |
| args = parser.parse_args() | |
| # Python's spawn start method calls os.getcwd() for every new worker pool. | |
| # If the command was launched from a transient directory, later epochs can | |
| # fail with FileNotFoundError. Pin cwd to the project root. | |
| project_dir = Path(__file__).resolve().parents[3] | |
| os.chdir(project_dir) | |
| original_get_preparation_data = mp_spawn.get_preparation_data | |
| def get_preparation_data(name): | |
| try: | |
| return original_get_preparation_data(name) | |
| except FileNotFoundError: | |
| os.chdir(project_dir) | |
| return original_get_preparation_data(name) | |
| mp_spawn.get_preparation_data = get_preparation_data | |
| config_name = sam2_config_name(args.sam2_cfg) | |
| os.environ["SAM2_DATA_ROOT"] = args.data_root | |
| os.environ["SAM2_CKPT"] = args.sam2_ckpt | |
| os.environ["SAM2_CFG"] = config_name | |
| module = load_module(args.onethinker_script) | |
| module.DATA_ROOT = args.data_root | |
| module.SAM2_CKPT = args.sam2_ckpt | |
| module.SAM2_CFG = config_name | |
| if args.num_gpus is not None: | |
| module.NUM_GPUS = args.num_gpus | |
| if args.workers_per_gpu is not None: | |
| module.WORKERS_PER_GPU = args.workers_per_gpu | |
| if args.pre_extract_threads is not None: | |
| module.PRE_EXTRACT_THREADS = args.pre_extract_threads | |
| if args.epoch_size is not None: | |
| module.EPOCH_SIZE = args.epoch_size | |
| sys.argv = [ | |
| str(Path(args.onethinker_script).name), | |
| "--input_json", | |
| args.input_json, | |
| "--viz_ratio", | |
| str(args.viz_ratio), | |
| ] | |
| os.environ.setdefault( | |
| "PYTORCH_CUDA_ALLOC_CONF", | |
| "expandable_segments:True,max_split_size_mb:64,garbage_collection_threshold:0.8", | |
| ) | |
| module.mp.set_start_method("spawn", force=True) | |
| input_path = Path(args.input_json).resolve() | |
| output_path = _sam2_output_path(input_path) | |
| if not args.retry_existing_errors: | |
| output_path = _run_upstream( | |
| module, | |
| input_json=input_path, | |
| viz_ratio=args.viz_ratio, | |
| normalize=True, | |
| ) | |
| elif not output_path.is_file(): | |
| raise FileNotFoundError(f"Existing SAM2 result not found: {output_path}") | |
| payload, retried = _retry_errors( | |
| module, | |
| output_path=output_path, | |
| attempts=max(0, args.retry_attempts), | |
| viz_ratio=args.viz_ratio, | |
| ) | |
| failed = [row for row in _results(payload) if row.get("status") != "ok"] | |
| print( | |
| f"[INFO] final SAM2 status: ok={len(_results(payload)) - len(failed)} " | |
| f"/ total={len(_results(payload))}; retried={retried}" | |
| ) | |
| if failed: | |
| examples = ", ".join( | |
| f"{row.get('problem_id')}={row.get('status')}" for row in failed[:5] | |
| ) | |
| raise RuntimeError( | |
| f"SAM2 post-processing left {len(failed)} failed samples: {examples}" | |
| ) | |
| if __name__ == "__main__": | |
| main() | |