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
| # Copyright 2024 Bytedance Ltd. and/or its affiliates | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| """ | |
| Implement Critic | |
| """ | |
| import os | |
| from collections import defaultdict | |
| from typing import Any | |
| import torch | |
| import torch.distributed as dist | |
| from ray.experimental.tqdm_ray import tqdm | |
| from torch import nn | |
| from torch.distributed.fsdp import FullyShardedDataParallel as FSDP | |
| from ...protocol import DataProto, batch_collate | |
| from ...trainer.core_algos import compute_value_loss | |
| from ...utils.py_functional import append_to_dict | |
| from ...utils.seqlen_balancing import prepare_dynamic_batch, restore_dynamic_batch | |
| from ...utils.ulysses import gather_outputs_and_unpad, ulysses_pad_and_slice_inputs | |
| from .base import BasePPOCritic | |
| from .config import CriticConfig | |
| try: | |
| from flash_attn.bert_padding import index_first_axis, pad_input, rearrange, unpad_input | |
| except ImportError: | |
| pass | |
| __all__ = ["DataParallelPPOCritic"] | |
| def _disable_tqdm() -> bool: | |
| return os.getenv("VERL_DISABLE_TQDM", "0") == "1" | |
| class DataParallelPPOCritic(BasePPOCritic): | |
| def __init__(self, config: CriticConfig, critic_module: nn.Module, critic_optimizer: torch.optim.Optimizer): | |
| super().__init__(config) | |
| self.rank = int(os.getenv("RANK", "0")) | |
| self.world_size = int(os.getenv("WORLD_SIZE", "1")) | |
| self.critic_module = critic_module | |
| self.critic_optimizer = critic_optimizer | |
| def _forward_micro_batch(self, micro_batch: dict[str, torch.Tensor]) -> torch.Tensor: | |
| input_ids = micro_batch["input_ids"] | |
| batch_size, seqlen = input_ids.shape | |
| attention_mask = micro_batch["attention_mask"] | |
| position_ids = micro_batch["position_ids"] | |
| responses = micro_batch["responses"] | |
| response_length = responses.size(-1) | |
| if position_ids.dim() == 3: # qwen2vl mrope | |
| position_ids = position_ids.transpose(0, 1) # (bsz, 4, seqlen) -> (4, bsz, seqlen) | |
| if "multi_modal_inputs" in micro_batch: | |
| multi_modal_inputs = batch_collate(micro_batch["multi_modal_inputs"]) | |
| merged = {} | |
| for key, value in multi_modal_inputs.items(): | |
| tensors = [v for v in value if v is not None] | |
| if tensors: | |
| merged[key] = torch.cat(tensors, dim=0) | |
| multi_modal_inputs = merged | |
| else: | |
| multi_modal_inputs = {} | |
| if self.config.padding_free: | |
| input_ids_rmpad, indices, *_ = unpad_input( | |
| input_ids.unsqueeze(-1), attention_mask | |
| ) # input_ids_rmpad (total_nnz, ...) | |
| input_ids_rmpad = input_ids_rmpad.transpose(0, 1) # (1, total_nnz) | |
| # unpad the position_ids to align the rotary | |
| if position_ids.dim() == 3: | |
| position_ids_rmpad = ( | |
| index_first_axis(rearrange(position_ids, "c b s ... -> (b s) c ..."), indices) | |
| .transpose(0, 1) | |
| .unsqueeze(1) | |
| ) # (4, bsz, seqlen) -> (4, 1, bsz * seqlen) | |
| else: | |
| position_ids_rmpad = index_first_axis( | |
| rearrange(position_ids.unsqueeze(-1), "b s ... -> (b s) ..."), indices | |
| ).transpose(0, 1) | |
| # pad and slice the inputs if sp > 1 | |
| if self.config.ulysses_size > 1: | |
| input_ids_rmpad, position_ids_rmpad, pad_size = ulysses_pad_and_slice_inputs( | |
| input_ids_rmpad, position_ids_rmpad, sp_size=self.config.ulysses_size | |
| ) | |
| # only pass input_ids and position_ids to enable flash_attn_varlen | |
| output = self.critic_module( | |
| input_ids=input_ids_rmpad, | |
| attention_mask=None, | |
| position_ids=position_ids_rmpad, | |
| **multi_modal_inputs, | |
| use_cache=False, | |
| ) # prevent model thinks we are generating | |
| values_rmpad = output.logits | |
| values_rmpad = values_rmpad.squeeze(0) # (total_nnz) | |
| # gather output if sp > 1 | |
| if self.config.ulysses_size > 1: | |
| values_rmpad = gather_outputs_and_unpad(values_rmpad, gather_dim=0, unpad_dim=0, padding_size=pad_size) | |
| # pad it back | |
| values = pad_input(values_rmpad, indices=indices, batch=batch_size, seqlen=seqlen).squeeze(-1) | |
| values = values[:, -response_length - 1 : -1] | |
| else: | |
| output = self.critic_module( | |
| input_ids=input_ids, | |
| attention_mask=attention_mask, | |
| position_ids=position_ids, | |
| **multi_modal_inputs, | |
| use_cache=False, | |
| ) | |
| values: torch.Tensor = output.logits | |
| values = values[:, -response_length - 1 : -1].squeeze(-1) # (bsz, response_length, vocab_size) | |
| return values | |
| def _optimizer_step(self) -> torch.Tensor: | |
| if isinstance(self.critic_module, FSDP): | |
| grad_norm = self.critic_module.clip_grad_norm_(self.config.max_grad_norm) | |
| else: | |
| grad_norm = torch.nn.utils.clip_grad_norm_( | |
| self.critic_module.parameters(), max_norm=self.config.max_grad_norm | |
| ) | |
| if not torch.isfinite(grad_norm): | |
| print("Gradient norm is not finite. Skip update.") | |
| else: | |
| self.critic_optimizer.step() | |
| self.critic_optimizer.zero_grad() | |
| return grad_norm | |
| def compute_values(self, data: DataProto) -> torch.Tensor: | |
| self.critic_module.eval() | |
| select_keys = ["input_ids", "attention_mask", "position_ids", "responses", "response_mask"] | |
| non_tensor_select_keys = ["multi_modal_inputs"] | |
| data = data.select(select_keys, non_tensor_select_keys) | |
| if self.config.dynamic_batching: | |
| if self.config.max_token_len_per_gpu is not None: | |
| max_token_len = self.config.max_token_len_per_gpu | |
| else: | |
| max_token_len = self.config.micro_batch_size_per_device_for_experience * data.batch["input_ids"].size( | |
| -1 | |
| ) | |
| micro_batches, batch_idx_list = prepare_dynamic_batch(data, max_token_len=max_token_len) | |
| else: | |
| micro_batches = data.split(self.config.micro_batch_size_per_device_for_experience) | |
| values_lst = [] | |
| if self.rank == 0 and not _disable_tqdm(): | |
| micro_batches = tqdm(micro_batches, desc="Compute values", position=1) | |
| for micro_batch in micro_batches: | |
| model_inputs = {**micro_batch.batch, **micro_batch.non_tensor_batch} | |
| values = self._forward_micro_batch(model_inputs) | |
| values_lst.append(values) | |
| values = torch.concat(values_lst, dim=0) | |
| if self.config.dynamic_batching: | |
| values = restore_dynamic_batch(values, batch_idx_list) | |
| values = values * data.batch["response_mask"] # only action tokens have values | |
| return values | |
| def update_critic(self, data: DataProto) -> dict[str, Any]: | |
| self.critic_module.train() | |
| select_keys = ["input_ids", "attention_mask", "position_ids", "responses", "response_mask"] | |
| select_keys.extend(["values", "returns"]) | |
| non_tensor_select_keys = ["multi_modal_inputs"] | |
| # Split to make minibatch iterator for updating the actor | |
| # See PPO paper for details. https://arxiv.org/abs/1707.06347 | |
| mini_batches = data.select(select_keys, non_tensor_select_keys).split(self.config.global_batch_size_per_device) | |
| metrics = defaultdict(list) | |
| for _ in range(self.config.ppo_epochs): | |
| if self.rank == 0 and not _disable_tqdm(): | |
| mini_batches = tqdm(mini_batches, desc="Train mini-batches", position=1) | |
| for mini_batch in mini_batches: | |
| total_response_tokens = torch.sum(mini_batch.batch["response_mask"]) | |
| dist.all_reduce(total_response_tokens, op=dist.ReduceOp.SUM) | |
| if self.config.dynamic_batching: | |
| if self.config.max_token_len_per_gpu is not None: | |
| max_token_len = self.config.max_token_len_per_gpu | |
| else: | |
| max_input_len = mini_batch.batch["input_ids"].size(-1) | |
| max_token_len = self.config.micro_batch_size_per_device_for_update * max_input_len | |
| micro_batches, _ = prepare_dynamic_batch(mini_batch, max_token_len=max_token_len) | |
| else: | |
| micro_batches = mini_batch.split(self.config.micro_batch_size_per_device_for_update) | |
| if self.rank == 0 and not _disable_tqdm(): | |
| micro_batches = tqdm(micro_batches, desc="Update critic", position=2) | |
| for micro_batch in micro_batches: | |
| model_inputs = {**micro_batch.batch, **micro_batch.non_tensor_batch} | |
| response_mask = model_inputs["response_mask"] | |
| values = model_inputs["values"] | |
| returns = model_inputs["returns"] | |
| vpreds = self._forward_micro_batch(model_inputs) | |
| vf_loss, vf_metrics = compute_value_loss( | |
| vpreds=vpreds, | |
| returns=returns, | |
| values=values, | |
| response_mask=response_mask, | |
| cliprange_value=self.config.cliprange_value, | |
| loss_avg_mode=self.config.loss_avg_mode, | |
| ) | |
| loss = vf_loss * torch.sum(response_mask) * self.world_size / total_response_tokens | |
| loss.backward() | |
| batch_metrics = {f"critic/{k}": v for k, v in vf_metrics.items()} | |
| batch_metrics["critic/vf_loss"] = vf_loss.detach().item() | |
| append_to_dict(metrics, batch_metrics) | |
| grad_norm = self._optimizer_step() | |
| append_to_dict(metrics, {"critic/grad_norm": grad_norm.detach().item()}) | |
| return metrics | |