Robotics
Transformers
Safetensors
minicpm_vla
feature-extraction
vision-language-action
embodied-ai
minicpm
manipulation
custom_code
Instructions to use openbmb/MiniCPM-RobotManip with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use openbmb/MiniCPM-RobotManip with Transformers:
# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("openbmb/MiniCPM-RobotManip", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
File size: 9,091 Bytes
7f5d27f 3a9a955 7f5d27f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 | ---
license: apache-2.0
library_name: transformers
pipeline_tag: robotics
tags:
- vision-language-action
- robotics
- embodied-ai
- minicpm
- manipulation
---
<h1 align="center">
MiniCPM-RobotManip
</h1>
<p align="center">
<strong>A Smarter and Faster On-Device AI Brain for Robots</strong>
</p>
<p align="center">
<span style="display: inline-flex; align-items: center; margin-right: 2px;">
<img src="github_logo.png" alt="github" width="15" height="15" style="margin-right: 4px;">
<a href="https://github.com/OpenBMB/MiniCPM-Robot" target="_blank"> Github</a> |
</span>
<span style="display: inline-flex; align-items: center; margin-right: 2px;">
<img src="discord_logo.png" alt="Discord" width="15" height="15" style="margin-right: 4px;">
<a href="https://huggingface.co/openbmb/MiniCPM-RobotManip/resolve/main/discord.jpeg" target="_blank"> Discord</a> |
</span>
</p>
<strong>MiniCPM-RobotManip</strong> is a 1.5B vision-language-action model for embodied manipulation with the following highlights:
<ul>
<li><b>Generalist Manipulation:</b> A unified 1.5B generalist policy that <b>uses one set of weights across all downstream tasks</b> and <b>outperforms larger models such as π₀.₅ and Qwen-VLA across representative evaluations</b>.</li>
<li><b>Streaming Context:</b> Historical observations are continuously incorporated into the model context through streaming inference, <b>reducing per-step compute from 125 TFLOPs to 3.3 TFLOPs while retaining 60 frames of history</b> and supporting <b>up to one minute of visual memory</b>. This moves VLA beyond reactive action generation from single-frame observations toward continuous decision-making grounded in long-horizon visual context.</li>
<li><b>Efficient Inference:</b> Inherits MiniCPM-V 4.6's visual token compression, reducing each frame from 256 to 64 visual tokens for 4× compression. With H100, BF16, and single-frame input, model-forward latency per decision step is 120 ms, compared with 234 ms for π0.5. The measurement excludes task autoregressive decoding.</li>
</ul>
<p align="center">
<img src="manip_case_en.gif" width="800" alt="MiniCPM-RobotManip task demonstrations" />
</p>
## Benchmark Results
<p align="center">
<img src="manip_benchmark.png" width="700" alt="manip_benchmark" />
</p>
## Inference Example
Please Ensure transformers==5.7.0
<pre><code class="language-python">from __future__ import annotations
import argparse
import json
from pathlib import Path
from typing import Sequence
import numpy as np
import torch
from PIL import Image
from transformers import AutoModel, AutoProcessor
STATE_DIM = 80
IMAGE_SIZE = (448, 448)
class MiniCPMVLAInference:
"""Processor and model wrapper for single-sample VLA inference."""
def __init__(
self,
checkpoint_path: str | Path = "openbmb/MiniCPM-RobotManip",
device: str | torch.device | None = None,
):
if device is None:
device = "cuda" if torch.cuda.is_available() else "cpu"
self.device = torch.device(device)
checkpoint = str(checkpoint_path)
self.processor = AutoProcessor.from_pretrained(checkpoint, trust_remote_code=True)
self.model = AutoModel.from_pretrained(checkpoint, trust_remote_code=True)
self.model.to(self.device).eval()
@staticmethod
def _load_images(images: Sequence[str | Path | Image.Image | np.ndarray]) -> list[np.ndarray]:
if not images:
raise ValueError("At least one image is required")
loaded = []
for image in images:
if isinstance(image, (str, Path)):
with Image.open(image) as pil_image:
array = np.asarray(pil_image.convert("RGB"))
elif isinstance(image, Image.Image):
array = np.asarray(image.convert("RGB"))
elif isinstance(image, np.ndarray):
array = image
else:
raise TypeError(f"Unsupported image type: {type(image)!r}")
if array.ndim != 3 or array.shape[-1] != 3:
raise ValueError(f"Expected an HxWx3 image, got shape {array.shape}")
# Match the training pipeline's ResizeImage(size=(448, 448)),
# including PIL's default resize interpolation.
resized = Image.fromarray(array).resize(IMAGE_SIZE)
loaded.append(np.asarray(resized).copy())
return loaded
def preprocess(self, images: Sequence, text: str) -> dict[str, torch.Tensor]:
"""Apply the same MiniCPM-V chat template and processor as training."""
content = [
{"type": "image", "image": image}
for image in self._load_images(images)
]
content.append({"type": "text", "text": text})
messages = [{"role": "user", "content": content}]
inputs = self.processor.apply_chat_template(
messages,
tokenize=True,
add_generation_prompt=True,
return_dict=True,
return_tensors="pt",
processor_kwargs={"padding": False},
)
return {
key: value.to(self.device)
for key, value in inputs.items()
if isinstance(value, torch.Tensor)
}
def _prepare_state(self, state: torch.Tensor | np.ndarray | Sequence[float]) -> torch.Tensor:
state = torch.as_tensor(state, dtype=torch.float32, device=self.device)
if state.ndim == 1:
state = state.unsqueeze(0).unsqueeze(0)
elif state.ndim == 2:
state = state.unsqueeze(1)
if state.shape != (1, 1, STATE_DIM):
raise ValueError(f"state must have shape (80,), (1, 80), or (1, 1, 80); got {tuple(state.shape)}")
return state
@torch.inference_mode()
def predict(
self,
images: Sequence[str | Path | Image.Image | np.ndarray],
text: str,
state: torch.Tensor | np.ndarray | Sequence[float] | None = None,
embodiment_id: int = 0,
seed: int | None = None,
) -> torch.Tensor:
"""Return one action chunk with shape ``(30, 80)`` on CPU."""
if not 0 <= embodiment_id < self.model.action_head.max_num_embodiments:
raise ValueError(
f"embodiment_id must be in [0, {self.model.action_head.max_num_embodiments - 1}]"
)
if state is None:
state = torch.zeros(STATE_DIM)
state_tensor = self._prepare_state(state)
embodiment = torch.tensor([embodiment_id], dtype=torch.long, device=self.device)
if seed is not None:
torch.manual_seed(seed)
if self.device.type == "cuda":
torch.cuda.manual_seed_all(seed)
vlm_inputs = self.preprocess(images, text)
actions = self.model.predict_action(
state=state_tensor,
embodiment_id=embodiment,
**vlm_inputs,
)
return actions[0].float().cpu()
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--image", action="append", required=True, help="Input image; repeat for multiple views")
parser.add_argument("--text", required=True, help="Robot instruction/prompt")
parser.add_argument("--device", default=None, help="Default: cuda if available, otherwise cpu")
state_group = parser.add_mutually_exclusive_group()
state_group.add_argument("--state-file", help="A .npy file containing 80 state values")
state_group.add_argument("--state", nargs=STATE_DIM, type=float, metavar="VALUE")
parser.add_argument("--embodiment-id", type=int, default=0)
parser.add_argument("--seed", type=int, default=None)
parser.add_argument("--output", help="Optional output .npy path; otherwise print JSON")
return parser.parse_args()
if __name__ == "__main__":
args = parse_args()
if args.state_file:
state = np.load(args.state_file)
elif args.state is not None:
state = args.state
else:
state = np.zeros(STATE_DIM, dtype=np.float32)
infer_runner = MiniCPMVLAInference(
checkpoint_path="openbmb/MiniCPM-RobotManip",
device=args.device,
)
action = infer_runner.predict(
images=args.image,
text=args.text,
state=state,
embodiment_id=args.embodiment_id,
seed=args.seed,
)
if args.output:
output_path = Path(args.output)
output_path.parent.mkdir(parents=True, exist_ok=True)
np.save(output_path, action.numpy())
print(f"Saved action {tuple(action.shape)} to {output_path}")
else:
print(json.dumps(action.tolist()))
</code></pre>
## Acknowledgement
<p>
This project builds on and references
<a href="https://github.com/starVLA/starVLA">starVLA</a>
and
<a href="https://github.com/huggingface/lerobot">LeRobot</a>.
We thank the authors for their open-source contributions.
</p>
## License
Model weights and code are open-sourced under the [Apache-2.0](./LICENSE) license.
|