Video-Text-to-Text
Safetensors
qwen2_5_vl
robotic-manipulation
reinforcement-learning
chain-of-thought
LeonOverload commited on
Commit
cdb4797
·
verified ·
1 Parent(s): eb1187c

Add usage example, interleaved input spec, and download guidance

Browse files
Files changed (1) hide show
  1. README.md +258 -4
README.md CHANGED
@@ -17,6 +17,8 @@ datasets:
17
 
18
  PRIMO R1 (Process Reasoning Induced Monitoring) is a 7B video multimodal large language model (MLLM) framework designed for accurate process supervision in long-horizon robotic manipulation. It was introduced in the paper [From Passive Observer to Active Critic: Reinforcement Learning Elicits Process Reasoning for Robotic Manipulation](https://huggingface.co/papers/2603.15600).
19
 
 
 
20
  ## Model Description
21
 
22
  Current video MLLMs often function as passive "Observers" that recognize ongoing events rather than evaluating the current state relative to the final task goal. PRIMO R1 transforms these models into active "Critics" by:
@@ -25,12 +27,264 @@ Current video MLLMs often function as passive "Observers" that recognize ongoing
25
  - **Temporal Anchoring**: Constructing a structured temporal input that explicitly anchors the video sequence between initial and current state images.
26
  - **Process Reasoning**: Focusing on evaluating the current state against the intended task goal to detect failures and track progress.
27
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  ## Performance
29
 
30
- PRIMO R1 achieves state-of-the-art performance across several benchmarks:
31
- - **RoboFail Benchmark**: Achieves 67.0% accuracy, surpassing closed-source models like OpenAI o1 by 6.0%.
32
- - **Error Reduction**: Demonstrates a 50% reduction in the mean absolute error compared to specialized reasoning baselines.
33
- - **Generalization**: Shows strong zero-shot generalization on difficult failure detection tasks in out-of-domain real-world humanoid scenarios.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
 
35
  ## Citations
36
 
 
17
 
18
  PRIMO R1 (Process Reasoning Induced Monitoring) is a 7B video multimodal large language model (MLLM) framework designed for accurate process supervision in long-horizon robotic manipulation. It was introduced in the paper [From Passive Observer to Active Critic: Reinforcement Learning Elicits Process Reasoning for Robotic Manipulation](https://huggingface.co/papers/2603.15600).
19
 
20
+ This is the **final checkpoint**: a Qwen2.5-VL-7B-Instruct base taken through an SFT cold start and then GRPO reinforcement learning. Use this model unless you specifically want the ablation.
21
+
22
  ## Model Description
23
 
24
  Current video MLLMs often function as passive "Observers" that recognize ongoing events rather than evaluating the current state relative to the final task goal. PRIMO R1 transforms these models into active "Critics" by:
 
27
  - **Temporal Anchoring**: Constructing a structured temporal input that explicitly anchors the video sequence between initial and current state images.
28
  - **Process Reasoning**: Focusing on evaluating the current state against the intended task goal to detect failures and track progress.
29
 
30
+ ## Resources
31
+
32
+ | | |
33
+ | --- | --- |
34
+ | Code | [10-OASIS-01/PRIMO-R1](https://github.com/10-OASIS-01/PRIMO-R1) |
35
+ | Collection | [PRIMO R1](https://huggingface.co/collections/LeonOverload/primo-r1) |
36
+ | Paper | [arXiv 2603.15600](https://arxiv.org/abs/2603.15600) · [project page](https://10-oasis-01.github.io/primo-r1-website/) |
37
+ | Stage-1 ablation | [PRIMO-COT-SFT-7B](https://huggingface.co/LeonOverload/PRIMO-COT-SFT-7B) |
38
+ | Benchmark | [primo-bench-json](https://huggingface.co/datasets/LeonOverload/primo-bench-json) |
39
+ | Training data | [primo-sft-json](https://huggingface.co/datasets/LeonOverload/primo-sft-json) · [primo-rl-json](https://huggingface.co/datasets/LeonOverload/primo-rl-json) |
40
+ | Videos | [primo-video-media](https://huggingface.co/datasets/LeonOverload/primo-video-media) |
41
+
42
+ ## Download
43
+
44
+ The `main` branch holds inference files only (**16.6 GB**). DeepSpeed resume state from RL step 2500 lives on a separate branch, so you do not pay ~100 GB for it by accident.
45
+
46
+ ```python
47
+ from huggingface_hub import snapshot_download
48
+
49
+ # Inference: 16.6 GB
50
+ snapshot_download("LeonOverload/PRIMO-R1-7B", local_dir="models/PRIMO-R1-7B")
51
+
52
+ # Resume RL training from step 2500: ~116 GB
53
+ snapshot_download("LeonOverload/PRIMO-R1-7B", revision="training-state")
54
+ ```
55
+
56
+ Or from the CLI:
57
+
58
+ ```bash
59
+ hf download LeonOverload/PRIMO-R1-7B --local-dir models/PRIMO-R1-7B
60
+ ```
61
+
62
+ If you are on a revision where the optimizer state is still on `main`, exclude it explicitly. Note that `--exclude` is ignored if you also pass filenames positionally:
63
+
64
+ ```bash
65
+ hf download LeonOverload/PRIMO-R1-7B \
66
+ --exclude "global_step*" --exclude "rng_state*" \
67
+ --exclude "scheduler.pt" --exclude "latest" \
68
+ --local-dir models/PRIMO-R1-7B
69
+ ```
70
+
71
+ ## Setup
72
+
73
+ The environment matters more than usual here. Qwen2.5-VL support shifted between transformers releases, and installing a PyPI `transformers` over the pinned tree is the most common cause of shape and processor errors:
74
+
75
+ ```bash
76
+ git clone https://github.com/10-OASIS-01/PRIMO-R1 && cd PRIMO-R1
77
+ conda create -n primo-r1 python=3.11 && conda activate primo-r1
78
+ bash setup.sh
79
+ ```
80
+
81
+ `setup.sh` pins `vllm==0.7.2`, `trl==0.16.0`, and installs the vendored `transformers-main/` tree last so nothing replaces it.
82
+
83
+ ## Input format — read this before running anything
84
+
85
+ PRIMO R1 is trained on an **interleaved** input that anchors the clip between two still frames. The content list must be in exactly this order:
86
+
87
+ ```
88
+ image (initial frame) → video (the clip) → image (current frame) → text (question)
89
+ ```
90
+
91
+ Feeding a bare video clip will still produce output, but it degrades quality silently — the paper's Table 4 ablation measures this directly, and a current-state image alone raises average MAE from 15.52 to 59.50. Keep the order and keep both anchor frames.
92
+
93
+ The prompt template is equally load-bearing: the answer extractor is a regex over `<answer>...</answer>`, and the `<think>` block is required to contain `<planning>`, `<observation>`, and `<reasoning>` subsections in that order. Changing the template without changing the extractor collapses scores rather than raising an error.
94
+
95
+ ## Usage
96
+
97
+ This example is transcribed from `src/eval/eval_interleave.py`, the harness that produced the published numbers.
98
+
99
+ ```python
100
+ import cv2
101
+ import torch
102
+ from PIL import Image
103
+ from transformers import AutoProcessor, AutoTokenizer
104
+ from vllm import LLM, SamplingParams
105
+ from qwen_vl_utils import process_vision_info
106
+
107
+ MODEL_PATH = "models/PRIMO-R1-7B" # or "LeonOverload/PRIMO-R1-7B"
108
+ video_path = "path/to/your/episode.mp4"
109
+ question = "What is the completion percentage of the task in the video?"
110
+ problem_type = "regression"
111
+
112
+ SYSTEM_PROMPT = (
113
+ "A conversation between User and Assistant. The Assistant is an expert AI specializing in embodied procedure and event reasoning based on visual input (video or images). "
114
+ "The assistant must strictly follow a specific thought process and output format. "
115
+ "The reasoning process is enclosed within <think> </think> tags, and the final answer is within <answer> </answer> tags. "
116
+ "The <think> block must contain three ordered subsections: <planning>, <observation>, and <reasoning>. "
117
+ "The <answer> block must contain only the final output required by the question type and no other commentary."
118
+ )
119
+
120
+ QUESTION_TEMPLATE = (
121
+ "QUESTION:\n{Question}\n\n"
122
+ "QUESTION TYPE:\n{question_type}\n\n"
123
+ "Analyze the provided visual data and reason about the ongoing task.\n\n"
124
+ "Please think about this question as if you were a human pondering deeply. "
125
+ "Provide your detailed reasoning between the <think> and </think> tags, following the subsections <planning>, <observation>, and <reasoning>. "
126
+ "Then give your final answer between the <answer> and </answer> tags.\n\n"
127
+ "Below is the required template:\n\n"
128
+ "<think>\n"
129
+ "<planning>\n"
130
+ "Identify the high-level goal of the agent, what is the initial state? What does successful completion look like?\n"
131
+ "Break down the high-level goal into a logical sequence of canonical steps. This serves as your mental plan for interpreting the task.\n"
132
+ "Use this plan to interpret actions, map observed behaviors to steps, assess progress, detect anomalies, and predict what happens next.\n"
133
+ "</planning>\n"
134
+ "<observation>\n"
135
+ "View the video as a temporal sequence of actions contributing to the procedure.\n"
136
+ "Objectively describe what is occurring in the current moment, noting evidence of progress or state changes.\n"
137
+ "Identify fine-grained actions and explain how they move the task forward.\n"
138
+ "List relevant objects, tools, and environmental context, emphasizing functional states and transformations.\n"
139
+ "Note cues—repetition, transitions, or completion indicators—that situate the action in the procedural script.\n"
140
+ "</observation>\n"
141
+ "<reasoning>\n"
142
+ "Think through the question as a human would, Engage in an internal dialogue using expressions such as 'let me think', 'wait', 'hmm', 'oh, I see', 'let's break it down', etc.\n"
143
+ "Connect observations to the procedural plan to determine which step is being executed, progress, correctness, or anomalies.\n"
144
+ "Reflect on assumptions, verify interpretations, and, if appropriate, predict the agent's next likely action.\n"
145
+ "Synthesize understanding of what the agent is doing, how it fits into the broader task, and whether the process seems successful.\n"
146
+ "You are encouraged to include self-reflection or verification in your reasoning process.\n"
147
+ "</reasoning>\n"
148
+ "</think>\n"
149
+ "<answer>\n"
150
+ "[Final answer here — strictly follow the `{question_type}` output format and include no extra commentary.]\n"
151
+ "</answer>"
152
+ )
153
+
154
+ TYPE_TEMPLATE = {
155
+ "multiple choice": " Please provide only the single option letter (e.g., A, B, C, D, etc.) within the <answer> </answer> tags.",
156
+ "numerical": " Please provide the numerical value (e.g., 42 or 3.14) within the <answer> </answer> tags.",
157
+ "OCR": " Please transcribe text from the image/video clearly and provide your text answer within the <answer> </answer> tags.",
158
+ "free-form": " Please provide your text answer within the <answer> </answer> tags.",
159
+ "regression": " Please provide the numerical value (e.g., 42 or 3.14) within the <answer> </answer> tags.",
160
+ "boolean": " Please provide only 'Yes' or 'No' as your answer within the <answer> </answer> tags.",
161
+ }
162
+
163
+
164
+ def extract_first_and_last_frame(path):
165
+ """Initial state = first frame, current state = last decodable frame."""
166
+ cap = cv2.VideoCapture(path)
167
+ ok, first = cap.read()
168
+ if not ok:
169
+ raise RuntimeError(f"cannot read {path}")
170
+ total = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
171
+ cap.set(cv2.CAP_PROP_POS_FRAMES, max(0, total - 1))
172
+ ok, last = cap.read()
173
+ if not ok: # some containers mis-report the count
174
+ cap.set(cv2.CAP_PROP_POS_FRAMES, max(0, total - 2))
175
+ ok, last = cap.read()
176
+ cap.release()
177
+ to_pil = lambda f: Image.fromarray(cv2.cvtColor(f, cv2.COLOR_BGR2RGB))
178
+ return to_pil(first), to_pil(last if ok else first)
179
+
180
+
181
+ init_img, current_img = extract_first_and_last_frame(video_path)
182
+
183
+ messages = [
184
+ {"role": "system", "content": [{"type": "text", "text": SYSTEM_PROMPT}]},
185
+ {
186
+ "role": "user",
187
+ "content": [
188
+ {"type": "image", "image": init_img}, # 1. initial state
189
+ {"type": "video", "video": video_path, "nframes": 22}, # 2. the clip
190
+ {"type": "image", "image": current_img}, # 3. current state
191
+ {
192
+ "type": "text",
193
+ "text": QUESTION_TEMPLATE.format(
194
+ Question=question, question_type=problem_type
195
+ ) + TYPE_TEMPLATE[problem_type],
196
+ },
197
+ ],
198
+ },
199
+ ]
200
+
201
+ llm = LLM(
202
+ model=MODEL_PATH,
203
+ tensor_parallel_size=torch.cuda.device_count(),
204
+ max_model_len=16384,
205
+ gpu_memory_utilization=0.8,
206
+ limit_mm_per_prompt={"image": 3, "video": 1}, # 2 anchor frames + 1 video
207
+ )
208
+
209
+ # top_p must stay this low. Larger values produce garbled output on this model.
210
+ sampling_params = SamplingParams(temperature=0.1, top_p=0.001, max_tokens=4096)
211
+
212
+ processor = AutoProcessor.from_pretrained(MODEL_PATH)
213
+ tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH)
214
+ tokenizer.padding_side = "left"
215
+ processor.tokenizer = tokenizer
216
+
217
+ prompt = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
218
+ image_inputs, video_inputs, video_kwargs = process_vision_info(messages, return_video_kwargs=True)
219
+
220
+ mm_data = {"video": video_inputs[0]}
221
+ if image_inputs:
222
+ mm_data["image"] = image_inputs
223
+
224
+ outputs = llm.generate(
225
+ [{
226
+ "prompt": prompt,
227
+ "multi_modal_data": mm_data,
228
+ "mm_processor_kwargs": {k: v[0] for k, v in video_kwargs.items()},
229
+ }],
230
+ sampling_params=sampling_params,
231
+ )
232
+ print(outputs[0].outputs[0].text)
233
+ ```
234
+
235
+ ### Parsing the output
236
+
237
+ ```python
238
+ import re
239
+
240
+ def extract_answer(text):
241
+ m = re.search(r"<answer>\s*(.*?)\s*</answer>", text, re.DOTALL)
242
+ return m.group(1).strip() if m else ""
243
+
244
+ def extract_think(text):
245
+ m = re.search(r"<think>\s*(.*?)\s*</think>", text, re.DOTALL)
246
+ return m.group(1).strip() if m else ""
247
+ ```
248
+
249
+ For `regression` and `numerical` questions the answer is a **progress percentage on a 0–100 scale** (`42.5`, not `0.425`). A trailing `%` is tolerated by the reference parser, which divides by 100 when it sees one.
250
+
251
+ ### Question types
252
+
253
+ `regression` and `numerical` (progress estimation), `multiple choice`, `boolean` (failure detection), `free-form`, `OCR`. Pass the type both in `QUESTION_TEMPLATE` and via `TYPE_TEMPLATE`; the model was trained with both present.
254
+
255
+ ### A note on frame counts
256
+
257
+ The published results were produced with the per-video frame cap at 22, so the launcher's `--nframes 32` effectively sampled 22. `nframes=22` above reproduces that. Training capped videos at 16 frames; eval samples more at higher resolution.
258
+
259
  ## Performance
260
 
261
+ Progress estimation averaged over four environments (paper Table 1):
262
+
263
+ | Model | Avg MRA ↑ | Avg MAE ↓ |
264
+ | --- | --- | --- |
265
+ | GPT-4o | 79.33 | 20.67 |
266
+ | GPT-5 mini | 75.38 | 23.96 |
267
+ | Qwen2.5-VL-72B | 73.80 | 23.80 |
268
+ | Qwen2.5-VL-7B (base) | 67.79 | 29.99 |
269
+ | InternVL 3.5 8B | 71.74 | 28.09 |
270
+ | ProgressLM | 78.32 | 20.87 |
271
+ | VLAC | 74.90 | 25.10 |
272
+ | **PRIMO R1 (7B)** | **82.90** | **15.52** |
273
+
274
+ Both training stages are needed (paper Table 2, MRA↑):
275
+
276
+ | Model | ID avg | OOD avg | Overall |
277
+ | --- | --- | --- | --- |
278
+ | Qwen2.5-VL-7B (base) | 70.38 | 65.26 | 67.46 |
279
+ | SFT only | 81.46 | 77.77 | 79.35 |
280
+ | RL only | 81.71 | 72.97 | 76.72 |
281
+ | **PRIMO R1 (SFT+RL)** | **88.47** | **82.90** | **85.28** |
282
+
283
+ RL without SFT underperforms because the model struggles to discover the output format from scratch; SFT alone generalizes poorly out of domain.
284
+
285
+ Zero-shot failure detection on RoboFail (paper Table 3): **67.0%**, matching Gemini 2.0 Flash and above GPT-4o at 63.0.
286
+
287
+ To reproduce these numbers, see [primo-bench-json](https://huggingface.co/datasets/LeonOverload/primo-bench-json), which documents the full evaluation path. One caveat when comparing against other work: the repo's harnesses score `regression` with three different formulas, and each output file records which one it used in its `regression_metric` field. The published numbers above come from `linear_relative_accuracy`.
288
 
289
  ## Citations
290