Stage-2 (GRPO reinforcement learning) training annotations for PRIMO R1 (paper). Unlike the SFT data, these records carry no chain-of-thought traces β RL optimizes against a verifiable progress reward, so only the ground-truth answer is needed. That is the point of the method: the model discovers its own reasoning instead of imitating someone else's.
328,454 records across 6 subsets. Annotations only (1.7 GB); videos are in primo-video-media.
| Subset | Records | Share | JSON | Video group |
|---|---|---|---|---|
behavior-1k | 206,029 | 63% | 1.2 GB | 5,981 GB |
agibot | 38,536 | 12% | 102 MB | 349 GB |
robotwin-randomized | 38,142 | 12% | 90 MB | 12.5 GB (shared with robotwin-clean) |
robovqa | 26,453 | 8% | 169 MB | 9.8 GB |
sharerobot | 12,000 | 4% | 33 MB | 0.97 GB |
robotwin-clean | 7,294 | 2% | 18 MB | 12.5 GB (shared with robotwin-randomized) |
behavior-1k dominates this repo twice over. It is 206,029 of the 328,454 records (63%), and its video archive is 5,981 GB β 91% of the entire 6.58 TB media release. The JSON is cheap to fetch; the video behind it is not.
Recommendation: skip behavior-1k unless you specifically need the long-horizon task-progress results. Its episodes are the longest in the release, so add it if long-horizon progress estimation is what you are after. The other five subsets together are 122,425 records and about 372 GB of video β and without agibot, under 25 GB.
The launchers read the mixture from an environment variable, so dropping a subset needs no code change:
# Full mixture minus behavior-1k
export RL_DATASET=primo-rl-agibot,primo-rl-robovqa,primo-rl-robotwin-clean,primo-rl-robotwin-randomized,primo-rl-sharerobot
# Or something small to start with: ~23 GB of video total
export RL_DATASET=primo-rl-robotwin-clean,primo-rl-robotwin-randomized,primo-rl-sharerobot,primo-rl-robovqa
GRPO generates num_generations=8 completions per prompt, so step count matters more than raw dataset size. A subset of the mixture still trains; the released checkpoint stopped at step 2500.
hf download LeonOverload/primo-rl-json --repo-type dataset \
--include "jsonl_subsets/agibot/*" --local-dir ./primo-rl
The training entry points read raw_json/, not the JSONL shards. Download into $VIDEO_DATA_ROOT so the paths the dataset registry expects (primo-rl/<source>/train.json) line up:
export VIDEO_DATA_ROOT=/path/to/PRIMO-Data
# All 6 subsets, 1.7 GB
hf download LeonOverload/primo-rl-json --repo-type dataset \
--include "raw_json/*" --local-dir /tmp/primo-rl
cp -r /tmp/primo-rl/raw_json/primo-rl "$VIDEO_DATA_ROOT/"
# Or skip the 1.2 GB behavior-1k file
hf download LeonOverload/primo-rl-json --repo-type dataset \
--include "raw_json/*" --exclude "raw_json/primo-rl/behavior-1k/*" \
--local-dir /tmp/primo-rl
Use --include / --exclude on their own β passing filenames positionally makes the client ignore both.
from datasets import load_dataset
ds = load_dataset("LeonOverload/primo-rl-json", "agibot", split="train")
print(ds[0]["problem"], ds[0]["solution"])
Convenient for inspection, but use raw_json/ for training.
Match each subset to its group and pull only those:
hf download LeonOverload/primo-video-media --repo-type dataset \
--include "robotwin.z*" --include "sharerobot.z*" \
--local-dir /tmp/primo-video-zips
Extract into $VIDEO_DATA_ROOT/primo-video/. See primo-video-media for reassembly β the archives are multipart, and they already include the pre-extracted anchor frames.
{
"problem": "Task info:\nSort in the warehouse\n\nInit Scene:\n...",
"problem_type": "numerical", # numerical | regression | multiple choice | boolean | free-form
"data_type": "video",
"path": "./agibot/observation/361/667819/100.0/head_color.mp4",
"solution": "<answer>100.0</answer>", # the verifiable target the reward is computed against
"options": [],
"done_actions": [...],
"meta_data": {...},
# interleaved-input anchors, read directly during training
"init_frame_path": "./primo-video/agibot/frames/observation/361/667819/100.0/head_color_init.jpg",
"current_frame_path": "./primo-video/agibot/frames/observation/361/667819/100.0/head_color_current.jpg"
}
No select, process, planning, observation, or reasoning fields β those are SFT-only. Progress targets are percentages on a 0β100 scale.
Unlike evaluation β which re-derives anchor frames with OpenCV at run time β training reads init_frame_path and current_frame_path from the JSON. Those files ship inside the video archives, so no preprocessing step is needed. src/preprocess_video_frames.py exists for datasets you add yourself.
Stage 2 starts from a stage-1 checkpoint. Use the published one to skip SFT entirely:
git clone https://github.com/10-OASIS-01/PRIMO-R1 && cd PRIMO-R1
conda create -n primo-r1 python=3.11 && conda activate primo-r1
bash setup.sh
hf download LeonOverload/PRIMO-COT-SFT-7B --local-dir models/PRIMO-COT-SFT-7B
export VIDEO_DATA_ROOT=/path/to/PRIMO-Data
export MODEL_ROOT=/path/to/models
export SFT_CKPT=models/PRIMO-COT-SFT-7B
export RL_DATASET=primo-rl-robotwin-clean,primo-rl-sharerobot # see above
bash src/scripts/run_rl_7b.sh # or run_rl_3b.sh
Defaults follow the paper: learning rate 1e-6, num_generations=8 (GRPO group size G), max_completion_length=4096, max_pixels=401408, beta=0.04, DeepSpeed ZeRO-3. Keep per_device_train_batch_size=1 β an invariant inherited from R1-V. Lower max_completion_length if you hit OOM; lowering num_generations is cheaper but raises gradient variance.
--temporal selects T-GRPO over plain GRPO and --len_control toggles the length-control reward. Reward functions live in reward_funcs_registry in grpo_interleave.py (accuracy_reward, format_reward); add new ones there so --reward_funcs can select them.
The 3B launcher is scaled from the 7B recipe and has not been validated; the released checkpoint is 7B.
Both stages are needed (paper Table 2, MRAβ):
| Model | ID avg | OOD avg | Overall |
|---|---|---|---|
| Qwen2.5-VL-7B (base) | 70.38 | 65.26 | 67.46 |
| SFT only | 81.46 | 77.77 | 79.35 |
| RL only (no SFT) | 81.71 | 72.97 | 76.72 |
| PRIMO R1 (SFT+RL) | 88.47 | 82.90 | 85.28 |
Training with this data alone, from the base model, gives the "RL only" row β competitive in domain but noticeably weaker out of domain, because the model has to discover the output format and reasoning structure from scratch. Pair it with the SFT data or start from the published cold start.
| Code | 10-OASIS-01/PRIMO-R1 |
| Collection | PRIMO R1 |
| Paper | arXiv 2603.15600 Β· project page |
| Models | PRIMO-R1-7B Β· PRIMO-COT-SFT-7B |
| Stage-1 data | primo-sft-json |
| Benchmark | primo-bench-json |
| Videos | primo-video-media |
raw_json/ β the files the training entry points read, in release layoutjsonl/ β flattened shards for the Dataset Viewerjsonl_subsets/ β per-subset shards backing the viewer's subset selectorsummary.json β row and shard counts generated at build timeThe authoritative dataset registry is the cfg dict in src/r1-v/src/open_r1/DatasetLoader.py.
If you find our work helpful for your research, please consider citing our work.
@misc{liu2026passiveobserveractivecritic,
title={From Passive Observer to Active Critic: Reinforcement Learning Elicits Process Reasoning for Robotic Manipulation},
author={Yibin Liu and Yaxing Lyu and Daqi Gao and Zhixuan Liang and Weiliang Tang and Shilong Mu and Xiaokang Yang and Yao Mu},
year={2026},
eprint={2603.15600},
archivePrefix={arXiv},
primaryClass={cs.RO},
url={https://arxiv.org/abs/2603.15600},
}
5 commits
Stage-2 (GRPO reinforcement learning) training annotations for PRIMO R1 (paper). Unlike the SFT data, these records carry no chain-of-thought traces β RL optimizes against a verifiable progress reward, so only the ground-truth answer is needed. That is the point of the method: the model discovers its own reasoning instead of imitating someone else's.
328,454 records across 6 subsets. Annotations only (1.7 GB); videos are in primo-video-media.
| Subset | Records | Share | JSON | Video group |
|---|---|---|---|---|
behavior-1k | 206,029 | 63% | 1.2 GB | 5,981 GB |
agibot | 38,536 | 12% | 102 MB | 349 GB |
robotwin-randomized | 38,142 | 12% | 90 MB | 12.5 GB (shared with robotwin-clean) |
robovqa | 26,453 | 8% | 169 MB | 9.8 GB |
sharerobot | 12,000 | 4% | 33 MB | 0.97 GB |
robotwin-clean | 7,294 | 2% | 18 MB | 12.5 GB (shared with robotwin-randomized) |
behavior-1k dominates this repo twice over. It is 206,029 of the 328,454 records (63%), and its video archive is 5,981 GB β 91% of the entire 6.58 TB media release. The JSON is cheap to fetch; the video behind it is not.
Recommendation: skip behavior-1k unless you specifically need the long-horizon task-progress results. Its episodes are the longest in the release, so add it if long-horizon progress estimation is what you are after. The other five subsets together are 122,425 records and about 372 GB of video β and without agibot, under 25 GB.
The launchers read the mixture from an environment variable, so dropping a subset needs no code change:
# Full mixture minus behavior-1k
export RL_DATASET=primo-rl-agibot,primo-rl-robovqa,primo-rl-robotwin-clean,primo-rl-robotwin-randomized,primo-rl-sharerobot
# Or something small to start with: ~23 GB of video total
export RL_DATASET=primo-rl-robotwin-clean,primo-rl-robotwin-randomized,primo-rl-sharerobot,primo-rl-robovqa
GRPO generates num_generations=8 completions per prompt, so step count matters more than raw dataset size. A subset of the mixture still trains; the released checkpoint stopped at step 2500.
hf download LeonOverload/primo-rl-json --repo-type dataset \
--include "jsonl_subsets/agibot/*" --local-dir ./primo-rl
The training entry points read raw_json/, not the JSONL shards. Download into $VIDEO_DATA_ROOT so the paths the dataset registry expects (primo-rl/<source>/train.json) line up:
export VIDEO_DATA_ROOT=/path/to/PRIMO-Data
# All 6 subsets, 1.7 GB
hf download LeonOverload/primo-rl-json --repo-type dataset \
--include "raw_json/*" --local-dir /tmp/primo-rl
cp -r /tmp/primo-rl/raw_json/primo-rl "$VIDEO_DATA_ROOT/"
# Or skip the 1.2 GB behavior-1k file
hf download LeonOverload/primo-rl-json --repo-type dataset \
--include "raw_json/*" --exclude "raw_json/primo-rl/behavior-1k/*" \
--local-dir /tmp/primo-rl
Use --include / --exclude on their own β passing filenames positionally makes the client ignore both.
from datasets import load_dataset
ds = load_dataset("LeonOverload/primo-rl-json", "agibot", split="train")
print(ds[0]["problem"], ds[0]["solution"])
Convenient for inspection, but use raw_json/ for training.
Match each subset to its group and pull only those:
hf download LeonOverload/primo-video-media --repo-type dataset \
--include "robotwin.z*" --include "sharerobot.z*" \
--local-dir /tmp/primo-video-zips
Extract into $VIDEO_DATA_ROOT/primo-video/. See primo-video-media for reassembly β the archives are multipart, and they already include the pre-extracted anchor frames.
{
"problem": "Task info:\nSort in the warehouse\n\nInit Scene:\n...",
"problem_type": "numerical", # numerical | regression | multiple choice | boolean | free-form
"data_type": "video",
"path": "./agibot/observation/361/667819/100.0/head_color.mp4",
"solution": "<answer>100.0</answer>", # the verifiable target the reward is computed against
"options": [],
"done_actions": [...],
"meta_data": {...},
# interleaved-input anchors, read directly during training
"init_frame_path": "./primo-video/agibot/frames/observation/361/667819/100.0/head_color_init.jpg",
"current_frame_path": "./primo-video/agibot/frames/observation/361/667819/100.0/head_color_current.jpg"
}
No select, process, planning, observation, or reasoning fields β those are SFT-only. Progress targets are percentages on a 0β100 scale.
Unlike evaluation β which re-derives anchor frames with OpenCV at run time β training reads init_frame_path and current_frame_path from the JSON. Those files ship inside the video archives, so no preprocessing step is needed. src/preprocess_video_frames.py exists for datasets you add yourself.
Stage 2 starts from a stage-1 checkpoint. Use the published one to skip SFT entirely:
git clone https://github.com/10-OASIS-01/PRIMO-R1 && cd PRIMO-R1
conda create -n primo-r1 python=3.11 && conda activate primo-r1
bash setup.sh
hf download LeonOverload/PRIMO-COT-SFT-7B --local-dir models/PRIMO-COT-SFT-7B
export VIDEO_DATA_ROOT=/path/to/PRIMO-Data
export MODEL_ROOT=/path/to/models
export SFT_CKPT=models/PRIMO-COT-SFT-7B
export RL_DATASET=primo-rl-robotwin-clean,primo-rl-sharerobot # see above
bash src/scripts/run_rl_7b.sh # or run_rl_3b.sh
Defaults follow the paper: learning rate 1e-6, num_generations=8 (GRPO group size G), max_completion_length=4096, max_pixels=401408, beta=0.04, DeepSpeed ZeRO-3. Keep per_device_train_batch_size=1 β an invariant inherited from R1-V. Lower max_completion_length if you hit OOM; lowering num_generations is cheaper but raises gradient variance.
--temporal selects T-GRPO over plain GRPO and --len_control toggles the length-control reward. Reward functions live in reward_funcs_registry in grpo_interleave.py (accuracy_reward, format_reward); add new ones there so --reward_funcs can select them.
The 3B launcher is scaled from the 7B recipe and has not been validated; the released checkpoint is 7B.
Both stages are needed (paper Table 2, MRAβ):
| Model | ID avg | OOD avg | Overall |
|---|---|---|---|
| Qwen2.5-VL-7B (base) | 70.38 | 65.26 | 67.46 |
| SFT only | 81.46 | 77.77 | 79.35 |
| RL only (no SFT) | 81.71 | 72.97 | 76.72 |
| PRIMO R1 (SFT+RL) | 88.47 | 82.90 | 85.28 |
Training with this data alone, from the base model, gives the "RL only" row β competitive in domain but noticeably weaker out of domain, because the model has to discover the output format and reasoning structure from scratch. Pair it with the SFT data or start from the published cold start.
| Code | 10-OASIS-01/PRIMO-R1 |
| Collection | PRIMO R1 |
| Paper | arXiv 2603.15600 Β· project page |
| Models | PRIMO-R1-7B Β· PRIMO-COT-SFT-7B |
| Stage-1 data | primo-sft-json |
| Benchmark | primo-bench-json |
| Videos | primo-video-media |
raw_json/ β the files the training entry points read, in release layoutjsonl/ β flattened shards for the Dataset Viewerjsonl_subsets/ β per-subset shards backing the viewer's subset selectorsummary.json β row and shard counts generated at build timeThe authoritative dataset registry is the cfg dict in src/r1-v/src/open_r1/DatasetLoader.py.
If you find our work helpful for your research, please consider citing our work.
@misc{liu2026passiveobserveractivecritic,
title={From Passive Observer to Active Critic: Reinforcement Learning Elicits Process Reasoning for Robotic Manipulation},
author={Yibin Liu and Yaxing Lyu and Daqi Gao and Zhixuan Liang and Weiliang Tang and Shilong Mu and Xiaokang Yang and Yao Mu},
year={2026},
eprint={2603.15600},
archivePrefix={arXiv},
primaryClass={cs.RO},
url={https://arxiv.org/abs/2603.15600},
}
5 commits