JunyuZ Cuiunbo cursor commited on
Commit
7f5d27f
·
0 Parent(s):

Initial commit

Browse files

Co-authored-by: Cuiunbo <Cuiunbo@users.noreply.huggingface.co>
Co-authored-by: cursor <cursor@users.noreply.huggingface.co>

.gitattributes ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ tokenizer.json filter=lfs diff=lfs merge=lfs -text
37
+ example1.gif filter=lfs diff=lfs merge=lfs -text
38
+ example2.gif filter=lfs diff=lfs merge=lfs -text
39
+ example3.gif filter=lfs diff=lfs merge=lfs -text
40
+ example4.gif filter=lfs diff=lfs merge=lfs -text
41
+ example5.gif filter=lfs diff=lfs merge=lfs -text
42
+ manip_benchmark.png filter=lfs diff=lfs merge=lfs -text
43
+ manip_case_en.gif filter=lfs diff=lfs merge=lfs -text
README.md ADDED
@@ -0,0 +1,239 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ library_name: transformers
4
+ pipeline_tag: robotics
5
+ tags:
6
+ - vision-language-action
7
+ - robotics
8
+ - embodied-ai
9
+ - minicpm
10
+ - manipulation
11
+ ---
12
+
13
+ <h1 align="center">
14
+ MiniCPM-RobotManip
15
+ </h1>
16
+
17
+ <p align="center">
18
+ <strong>A Smarter and Faster On-Device AI Brain for Robots</strong>
19
+ </p>
20
+
21
+ <p align="center">
22
+ <span style="display: inline-flex; align-items: center; margin-right: 2px;">
23
+ <img src="github_logo.png" alt="github" width="15" height="15" style="margin-right: 4px;">
24
+ <a href="https://github.com/OpenBMB/MiniCPM-Robot" target="_blank"> Github</a> &nbsp;|
25
+ </span>
26
+ <span style="display: inline-flex; align-items: center; margin-right: 2px;">
27
+ <img src="feishu_logo.png" alt="feishu" width="15" height="15" style="margin-right: 4px;">
28
+ <a href="feishu_qrcode.png" target="_blank"> Feishu</a> &nbsp;|
29
+ </span>
30
+ <span style="display: inline-flex; align-items: center; margin-right: 2px;">
31
+ <img src="X_logo.jpg" alt="X" width="15" height="15" style="margin-right: 4px;">
32
+ <a href="x.png" target="_blank"> X</a> &nbsp;|
33
+ </span>
34
+ <span style="display: inline-flex; align-items: center; margin-right: 2px;">
35
+ <img src="discord_logo.png" alt="Discord" width="15" height="15" style="margin-right: 4px;">
36
+ <a href="discord.jpeg" target="_blank"> Discord</a> &nbsp;|
37
+ </span>
38
+ </p>
39
+
40
+ <strong>MiniCPM-RobotManip</strong> is a 1.5B vision-language-action model for embodied manipulation with the following highlights:
41
+ <ul>
42
+ <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>
43
+ <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>
44
+ <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>
45
+ </ul>
46
+
47
+ <p align="center">
48
+ <img src="manip_case_en.gif" width="800" alt="MiniCPM-RobotManip task demonstrations" />
49
+ </p>
50
+
51
+ ## Benchmark Results
52
+ <p align="center">
53
+ <img src="manip_benchmark.png" width="700" alt="manip_benchmark" />
54
+ </p>
55
+
56
+ ## Inference Example
57
+
58
+ Please Ensure transformers==5.7.0
59
+
60
+ <pre><code class="language-python">from __future__ import annotations
61
+
62
+ import argparse
63
+ import json
64
+ from pathlib import Path
65
+ from typing import Sequence
66
+
67
+ import numpy as np
68
+ import torch
69
+ from PIL import Image
70
+ from transformers import AutoModel, AutoProcessor
71
+
72
+
73
+ STATE_DIM = 80
74
+ IMAGE_SIZE = (448, 448)
75
+
76
+
77
+ class MiniCPMVLAInference:
78
+ """Processor and model wrapper for single-sample VLA inference."""
79
+
80
+ def __init__(
81
+ self,
82
+ checkpoint_path: str | Path = "openbmb/MiniCPM-RobotManip",
83
+ device: str | torch.device | None = None,
84
+ ):
85
+ if device is None:
86
+ device = "cuda" if torch.cuda.is_available() else "cpu"
87
+ self.device = torch.device(device)
88
+ checkpoint = str(checkpoint_path)
89
+ self.processor = AutoProcessor.from_pretrained(checkpoint, trust_remote_code=True)
90
+ self.model = AutoModel.from_pretrained(checkpoint, trust_remote_code=True)
91
+ self.model.to(self.device).eval()
92
+
93
+ @staticmethod
94
+ def _load_images(images: Sequence[str | Path | Image.Image | np.ndarray]) -> list[np.ndarray]:
95
+ if not images:
96
+ raise ValueError("At least one image is required")
97
+ loaded = []
98
+ for image in images:
99
+ if isinstance(image, (str, Path)):
100
+ with Image.open(image) as pil_image:
101
+ array = np.asarray(pil_image.convert("RGB"))
102
+ elif isinstance(image, Image.Image):
103
+ array = np.asarray(image.convert("RGB"))
104
+ elif isinstance(image, np.ndarray):
105
+ array = image
106
+ else:
107
+ raise TypeError(f"Unsupported image type: {type(image)!r}")
108
+ if array.ndim != 3 or array.shape[-1] != 3:
109
+ raise ValueError(f"Expected an HxWx3 image, got shape {array.shape}")
110
+ # Match the training pipeline's ResizeImage(size=(448, 448)),
111
+ # including PIL's default resize interpolation.
112
+ resized = Image.fromarray(array).resize(IMAGE_SIZE)
113
+ loaded.append(np.asarray(resized).copy())
114
+ return loaded
115
+
116
+ def preprocess(self, images: Sequence, text: str) -> dict[str, torch.Tensor]:
117
+ """Apply the same MiniCPM-V chat template and processor as training."""
118
+ content = [
119
+ {"type": "image", "image": image}
120
+ for image in self._load_images(images)
121
+ ]
122
+ content.append({"type": "text", "text": text})
123
+ messages = [{"role": "user", "content": content}]
124
+ inputs = self.processor.apply_chat_template(
125
+ messages,
126
+ tokenize=True,
127
+ add_generation_prompt=True,
128
+ return_dict=True,
129
+ return_tensors="pt",
130
+ processor_kwargs={"padding": False},
131
+ )
132
+ return {
133
+ key: value.to(self.device)
134
+ for key, value in inputs.items()
135
+ if isinstance(value, torch.Tensor)
136
+ }
137
+
138
+ def _prepare_state(self, state: torch.Tensor | np.ndarray | Sequence[float]) -> torch.Tensor:
139
+ state = torch.as_tensor(state, dtype=torch.float32, device=self.device)
140
+ if state.ndim == 1:
141
+ state = state.unsqueeze(0).unsqueeze(0)
142
+ elif state.ndim == 2:
143
+ state = state.unsqueeze(1)
144
+ if state.shape != (1, 1, STATE_DIM):
145
+ raise ValueError(f"state must have shape (80,), (1, 80), or (1, 1, 80); got {tuple(state.shape)}")
146
+ return state
147
+
148
+ @torch.inference_mode()
149
+ def predict(
150
+ self,
151
+ images: Sequence[str | Path | Image.Image | np.ndarray],
152
+ text: str,
153
+ state: torch.Tensor | np.ndarray | Sequence[float] | None = None,
154
+ embodiment_id: int = 0,
155
+ seed: int | None = None,
156
+ ) -> torch.Tensor:
157
+ """Return one action chunk with shape ``(30, 80)`` on CPU."""
158
+ if not 0 <= embodiment_id < self.model.action_head.max_num_embodiments:
159
+ raise ValueError(
160
+ f"embodiment_id must be in [0, {self.model.action_head.max_num_embodiments - 1}]"
161
+ )
162
+ if state is None:
163
+ state = torch.zeros(STATE_DIM)
164
+ state_tensor = self._prepare_state(state)
165
+ embodiment = torch.tensor([embodiment_id], dtype=torch.long, device=self.device)
166
+ if seed is not None:
167
+ torch.manual_seed(seed)
168
+ if self.device.type == "cuda":
169
+ torch.cuda.manual_seed_all(seed)
170
+
171
+ vlm_inputs = self.preprocess(images, text)
172
+ actions = self.model.predict_action(
173
+ state=state_tensor,
174
+ embodiment_id=embodiment,
175
+ **vlm_inputs,
176
+ )
177
+ return actions[0].float().cpu()
178
+
179
+
180
+ def parse_args() -> argparse.Namespace:
181
+ parser = argparse.ArgumentParser(description=__doc__)
182
+ parser.add_argument("--image", action="append", required=True, help="Input image; repeat for multiple views")
183
+ parser.add_argument("--text", required=True, help="Robot instruction/prompt")
184
+ parser.add_argument("--device", default=None, help="Default: cuda if available, otherwise cpu")
185
+ state_group = parser.add_mutually_exclusive_group()
186
+ state_group.add_argument("--state-file", help="A .npy file containing 80 state values")
187
+ state_group.add_argument("--state", nargs=STATE_DIM, type=float, metavar="VALUE")
188
+ parser.add_argument("--embodiment-id", type=int, default=0)
189
+ parser.add_argument("--seed", type=int, default=None)
190
+ parser.add_argument("--output", help="Optional output .npy path; otherwise print JSON")
191
+ return parser.parse_args()
192
+
193
+
194
+ if __name__ == "__main__":
195
+ args = parse_args()
196
+ if args.state_file:
197
+ state = np.load(args.state_file)
198
+ elif args.state is not None:
199
+ state = args.state
200
+ else:
201
+ state = np.zeros(STATE_DIM, dtype=np.float32)
202
+
203
+ infer_runner = MiniCPMVLAInference(
204
+ checkpoint_path="openbmb/MiniCPM-RobotManip",
205
+ device=args.device,
206
+ )
207
+ action = infer_runner.predict(
208
+ images=args.image,
209
+ text=args.text,
210
+ state=state,
211
+ embodiment_id=args.embodiment_id,
212
+ seed=args.seed,
213
+ )
214
+ if args.output:
215
+ output_path = Path(args.output)
216
+ output_path.parent.mkdir(parents=True, exist_ok=True)
217
+ np.save(output_path, action.numpy())
218
+ print(f"Saved action {tuple(action.shape)} to {output_path}")
219
+ else:
220
+ print(json.dumps(action.tolist()))
221
+
222
+ </code></pre>
223
+
224
+
225
+
226
+ ## Acknowledgement
227
+
228
+ <p>
229
+ This project builds on and references
230
+ <a href="https://github.com/starVLA/starVLA">starVLA</a>
231
+ and
232
+ <a href="https://github.com/huggingface/lerobot">LeRobot</a>.
233
+ We thank the authors for their open-source contributions.
234
+ </p>
235
+
236
+ ## License
237
+
238
+ Model weights and code are open-sourced under the [Apache-2.0](./LICENSE) license.
239
+
X_logo.jpg ADDED
action_head.py ADDED
@@ -0,0 +1,387 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from typing import Optional
4
+
5
+ import torch
6
+ import torch.nn.functional as F
7
+ from diffusers import ConfigMixin, ModelMixin
8
+ from diffusers.configuration_utils import register_to_config
9
+ from diffusers.models.attention import Attention, FeedForward
10
+ from diffusers.models.embeddings import SinusoidalPositionalEmbedding, TimestepEmbedding, Timesteps
11
+ from torch import nn
12
+
13
+
14
+ class TimestepEncoder(nn.Module):
15
+ def __init__(self, embedding_dim: int):
16
+ super().__init__()
17
+ self.time_proj = Timesteps(num_channels=256, flip_sin_to_cos=True, downscale_freq_shift=1)
18
+ self.timestep_embedder = TimestepEmbedding(in_channels=256, time_embed_dim=embedding_dim)
19
+
20
+ def forward(self, timesteps: torch.Tensor) -> torch.Tensor:
21
+ return self.timestep_embedder(self.time_proj(timesteps).to(next(self.parameters()).dtype))
22
+
23
+
24
+ class AdaLayerNorm(nn.Module):
25
+ def __init__(
26
+ self,
27
+ embedding_dim: int,
28
+ norm_elementwise_affine: bool = False,
29
+ norm_eps: float = 1e-5,
30
+ ):
31
+ super().__init__()
32
+ self.silu = nn.SiLU()
33
+ self.linear = nn.Linear(embedding_dim, embedding_dim * 2)
34
+ self.norm = nn.LayerNorm(embedding_dim, norm_eps, norm_elementwise_affine)
35
+
36
+ def forward(self, x: torch.Tensor, temb: torch.Tensor) -> torch.Tensor:
37
+ scale, shift = self.linear(self.silu(temb)).chunk(2, dim=1)
38
+ return self.norm(x) * (1 + scale[:, None]) + shift[:, None]
39
+
40
+
41
+ class BasicTransformerBlock(nn.Module):
42
+ def __init__(
43
+ self,
44
+ dim: int,
45
+ num_attention_heads: int,
46
+ attention_head_dim: int,
47
+ dropout: float = 0.0,
48
+ cross_attention_dim: Optional[int] = None,
49
+ activation_fn: str = "geglu",
50
+ attention_bias: bool = False,
51
+ upcast_attention: bool = False,
52
+ norm_elementwise_affine: bool = True,
53
+ norm_type: str = "layer_norm",
54
+ norm_eps: float = 1e-5,
55
+ final_dropout: bool = False,
56
+ positional_embeddings: Optional[str] = None,
57
+ num_positional_embeddings: Optional[int] = None,
58
+ ff_inner_dim: Optional[int] = None,
59
+ ff_bias: bool = True,
60
+ attention_out_bias: bool = True,
61
+ ):
62
+ super().__init__()
63
+ if positional_embeddings and num_positional_embeddings is None:
64
+ raise ValueError("num_positional_embeddings is required for positional embeddings")
65
+ self.norm_type = norm_type
66
+ self.pos_embed = (
67
+ SinusoidalPositionalEmbedding(dim, max_seq_length=num_positional_embeddings)
68
+ if positional_embeddings == "sinusoidal"
69
+ else None
70
+ )
71
+ self.norm1 = (
72
+ AdaLayerNorm(dim)
73
+ if norm_type == "ada_norm"
74
+ else nn.LayerNorm(dim, elementwise_affine=norm_elementwise_affine, eps=norm_eps)
75
+ )
76
+ self.attn1 = Attention(
77
+ query_dim=dim,
78
+ heads=num_attention_heads,
79
+ dim_head=attention_head_dim,
80
+ dropout=dropout,
81
+ bias=attention_bias,
82
+ cross_attention_dim=cross_attention_dim,
83
+ upcast_attention=upcast_attention,
84
+ out_bias=attention_out_bias,
85
+ )
86
+ self.norm3 = nn.LayerNorm(dim, norm_eps, norm_elementwise_affine)
87
+ self.ff = FeedForward(
88
+ dim,
89
+ dropout=dropout,
90
+ activation_fn=activation_fn,
91
+ final_dropout=final_dropout,
92
+ inner_dim=ff_inner_dim,
93
+ bias=ff_bias,
94
+ )
95
+ self.final_dropout = nn.Dropout(dropout) if final_dropout else None
96
+
97
+ def forward(
98
+ self,
99
+ hidden_states: torch.Tensor,
100
+ encoder_hidden_states: Optional[torch.Tensor] = None,
101
+ encoder_attention_mask: Optional[torch.Tensor] = None,
102
+ temb: Optional[torch.Tensor] = None,
103
+ ) -> torch.Tensor:
104
+ norm_hidden_states = (
105
+ self.norm1(hidden_states, temb) if self.norm_type == "ada_norm" else self.norm1(hidden_states)
106
+ )
107
+ if self.pos_embed is not None:
108
+ norm_hidden_states = self.pos_embed(norm_hidden_states)
109
+ attention_output = self.attn1(
110
+ norm_hidden_states,
111
+ encoder_hidden_states=encoder_hidden_states,
112
+ attention_mask=encoder_attention_mask,
113
+ )
114
+ if self.final_dropout is not None:
115
+ attention_output = self.final_dropout(attention_output)
116
+ hidden_states = attention_output + hidden_states
117
+ if hidden_states.ndim == 4:
118
+ hidden_states = hidden_states.squeeze(1)
119
+ hidden_states = self.ff(self.norm3(hidden_states)) + hidden_states
120
+ if hidden_states.ndim == 4:
121
+ hidden_states = hidden_states.squeeze(1)
122
+ return hidden_states
123
+
124
+
125
+ class DiT(ModelMixin, ConfigMixin):
126
+ _supports_gradient_checkpointing = True
127
+
128
+ @register_to_config
129
+ def __init__(
130
+ self,
131
+ num_attention_heads: int = 8,
132
+ attention_head_dim: int = 64,
133
+ output_dim: int = 26,
134
+ num_layers: int = 12,
135
+ dropout: float = 0.1,
136
+ attention_bias: bool = True,
137
+ activation_fn: str = "gelu-approximate",
138
+ num_embeds_ada_norm: Optional[int] = 1000,
139
+ upcast_attention: bool = False,
140
+ norm_type: str = "ada_norm",
141
+ norm_elementwise_affine: bool = False,
142
+ norm_eps: float = 1e-5,
143
+ max_num_positional_embeddings: int = 512,
144
+ compute_dtype: torch.dtype = torch.float32,
145
+ final_dropout: bool = True,
146
+ positional_embeddings: Optional[str] = "sinusoidal",
147
+ interleave_self_attention: bool = False,
148
+ cross_attention_dim: Optional[int] = None,
149
+ **kwargs,
150
+ ):
151
+ super().__init__()
152
+ self.attention_head_dim = attention_head_dim
153
+ self.inner_dim = self.config.num_attention_heads * self.config.attention_head_dim
154
+ self.gradient_checkpointing = False
155
+ self.timestep_encoder = TimestepEncoder(self.inner_dim)
156
+ self.transformer_blocks = nn.ModuleList(
157
+ [
158
+ BasicTransformerBlock(
159
+ self.inner_dim,
160
+ self.config.num_attention_heads,
161
+ self.config.attention_head_dim,
162
+ dropout=self.config.dropout,
163
+ activation_fn=self.config.activation_fn,
164
+ attention_bias=self.config.attention_bias,
165
+ upcast_attention=self.config.upcast_attention,
166
+ norm_type=norm_type,
167
+ norm_elementwise_affine=self.config.norm_elementwise_affine,
168
+ norm_eps=self.config.norm_eps,
169
+ positional_embeddings=positional_embeddings,
170
+ num_positional_embeddings=self.config.max_num_positional_embeddings,
171
+ final_dropout=final_dropout,
172
+ cross_attention_dim=(
173
+ None if index % 2 == 1 and interleave_self_attention else cross_attention_dim
174
+ ),
175
+ )
176
+ for index in range(self.config.num_layers)
177
+ ]
178
+ )
179
+ self.norm_out = nn.LayerNorm(self.inner_dim, elementwise_affine=False, eps=1e-6)
180
+ self.proj_out_1 = nn.Linear(self.inner_dim, 2 * self.inner_dim)
181
+ self.proj_out_2 = nn.Linear(self.inner_dim, self.config.output_dim)
182
+
183
+ def forward(
184
+ self,
185
+ hidden_states: torch.Tensor,
186
+ encoder_hidden_states: torch.Tensor,
187
+ timestep: Optional[torch.LongTensor] = None,
188
+ return_all_hidden_states: bool = False,
189
+ encoder_attention_mask: Optional[torch.Tensor] = None,
190
+ ):
191
+ time_embedding = self.timestep_encoder(timestep)
192
+ hidden_states = hidden_states.contiguous()
193
+ encoder_hidden_states = encoder_hidden_states.contiguous()
194
+ all_hidden_states = [hidden_states]
195
+ for index, block in enumerate(self.transformer_blocks):
196
+ self_attention = index % 2 == 1 and self.config.interleave_self_attention
197
+ hidden_states = block(
198
+ hidden_states,
199
+ encoder_hidden_states=None if self_attention else encoder_hidden_states,
200
+ encoder_attention_mask=None if self_attention else encoder_attention_mask,
201
+ temb=time_embedding,
202
+ )
203
+ all_hidden_states.append(hidden_states)
204
+ shift, scale = self.proj_out_1(F.silu(time_embedding)).chunk(2, dim=1)
205
+ hidden_states = self.norm_out(hidden_states) * (1 + scale[:, None]) + shift[:, None]
206
+ output = self.proj_out_2(hidden_states)
207
+ return (output, all_hidden_states) if return_all_hidden_states else output
208
+
209
+
210
+ class SinusoidalPositionalEncoding(nn.Module):
211
+ def __init__(self, embedding_dim: int):
212
+ super().__init__()
213
+ self.embedding_dim = embedding_dim
214
+
215
+ def forward(self, timesteps: torch.Tensor) -> torch.Tensor:
216
+ timesteps = timesteps.float()
217
+ half_dim = self.embedding_dim // 2
218
+ exponent = -torch.arange(half_dim, dtype=torch.float, device=timesteps.device) * (
219
+ torch.log(torch.tensor(10000.0, device=timesteps.device)) / half_dim
220
+ )
221
+ frequencies = timesteps.unsqueeze(-1) * exponent.exp()
222
+ return torch.cat([torch.sin(frequencies), torch.cos(frequencies)], dim=-1)
223
+
224
+
225
+ class CategorySpecificLinear(nn.Module):
226
+ def __init__(self, num_categories: int, input_dim: int, output_dim: int):
227
+ super().__init__()
228
+ self.num_categories = num_categories
229
+ self.W = nn.Parameter(0.02 * torch.randn(num_categories, input_dim, output_dim))
230
+ self.b = nn.Parameter(torch.zeros(num_categories, output_dim))
231
+
232
+ def forward(self, x: torch.Tensor, category_ids: torch.Tensor) -> torch.Tensor:
233
+ if category_ids is None:
234
+ raise ValueError("embodiment_id (B,) is required")
235
+ return torch.bmm(x, self.W[category_ids]) + self.b[category_ids].unsqueeze(1)
236
+
237
+
238
+ class CategorySpecificMLP(nn.Module):
239
+ def __init__(self, num_categories: int, input_dim: int, hidden_dim: int, output_dim: int):
240
+ super().__init__()
241
+ self.layer1 = CategorySpecificLinear(num_categories, input_dim, hidden_dim)
242
+ self.layer2 = CategorySpecificLinear(num_categories, hidden_dim, output_dim)
243
+
244
+ def forward(self, x: torch.Tensor, category_ids: torch.Tensor) -> torch.Tensor:
245
+ return self.layer2(F.relu(self.layer1(x, category_ids)), category_ids)
246
+
247
+
248
+ class MultiEmbodimentActionEncoder(nn.Module):
249
+ def __init__(self, action_dim: int, hidden_size: int, num_embodiments: int):
250
+ super().__init__()
251
+ self.W1 = CategorySpecificLinear(num_embodiments, action_dim, hidden_size)
252
+ self.W2 = CategorySpecificLinear(num_embodiments, 2 * hidden_size, hidden_size)
253
+ self.W3 = CategorySpecificLinear(num_embodiments, hidden_size, hidden_size)
254
+ self.pos_encoding = SinusoidalPositionalEncoding(hidden_size)
255
+
256
+ def forward(
257
+ self,
258
+ actions: torch.Tensor,
259
+ timesteps: torch.Tensor,
260
+ embodiment_id: torch.Tensor,
261
+ ) -> torch.Tensor:
262
+ batch_size, horizon, _ = actions.shape
263
+ if timesteps.dim() != 1 or timesteps.shape[0] != batch_size:
264
+ raise ValueError("timesteps must have shape (B,)")
265
+ timesteps = timesteps.unsqueeze(1).expand(-1, horizon)
266
+ action_embedding = self.W1(actions, embodiment_id)
267
+ time_embedding = self.pos_encoding(timesteps).to(dtype=action_embedding.dtype)
268
+ hidden = self.W2(torch.cat([action_embedding, time_embedding], dim=-1), embodiment_id)
269
+ return self.W3(hidden * torch.sigmoid(hidden), embodiment_id)
270
+
271
+
272
+ class MiniCPMV_VLA_ActionHead(nn.Module):
273
+ """80-D, 32-embodiment action head for MiniCPM-VLA."""
274
+
275
+ def __init__(
276
+ self,
277
+ hidden_size: int = 1024,
278
+ action_dim: int = 80,
279
+ state_dim: int = 80,
280
+ action_horizon: int = 30,
281
+ num_inference_timesteps: int = 4,
282
+ num_target_vision_tokens: int = 32,
283
+ max_seq_len: int = 1024,
284
+ num_timestep_buckets: int = 1000,
285
+ max_num_embodiments: int = 32,
286
+ ):
287
+ super().__init__()
288
+ self.hidden_size = hidden_size
289
+ self.input_embedding_dim = 768
290
+ self.model = DiT(
291
+ input_embedding_dim=768,
292
+ attention_head_dim=64,
293
+ num_attention_heads=12,
294
+ cross_attention_dim=1024,
295
+ dropout=0.2,
296
+ final_dropout=True,
297
+ interleave_self_attention=True,
298
+ norm_type="ada_norm",
299
+ num_layers=16,
300
+ output_dim=1024,
301
+ positional_embeddings=None,
302
+ )
303
+ self.action_dim = action_dim
304
+ self.state_dim = state_dim
305
+ self.action_horizon = action_horizon
306
+ self.num_inference_timesteps = num_inference_timesteps
307
+ self.max_num_embodiments = max_num_embodiments
308
+ self.multi_embodiment = True
309
+ self.proprio_inject = "concat"
310
+ self.state_encoder = None
311
+ self.action_encoder = MultiEmbodimentActionEncoder(
312
+ action_dim=action_dim + state_dim,
313
+ hidden_size=self.input_embedding_dim,
314
+ num_embodiments=max_num_embodiments,
315
+ )
316
+ self.action_decoder = CategorySpecificMLP(
317
+ num_categories=max_num_embodiments,
318
+ input_dim=self.model.config.output_dim,
319
+ hidden_dim=hidden_size,
320
+ output_dim=action_dim,
321
+ )
322
+ self.future_tokens = nn.Embedding(num_target_vision_tokens, self.input_embedding_dim)
323
+ nn.init.normal_(self.future_tokens.weight, mean=0.0, std=0.02)
324
+ self.position_embedding = nn.Embedding(max_seq_len, self.input_embedding_dim)
325
+ nn.init.normal_(self.position_embedding.weight, mean=0.0, std=0.02)
326
+ self.num_timestep_buckets = num_timestep_buckets
327
+
328
+ def _encode_action_tokens(
329
+ self,
330
+ noisy_actions: torch.Tensor,
331
+ state: torch.Tensor,
332
+ timesteps: torch.Tensor,
333
+ embodiment_id: torch.Tensor,
334
+ ) -> torch.Tensor:
335
+ if state is None:
336
+ raise ValueError("state is required because PROPRIO_INJECT=concat")
337
+ state = state.expand(-1, noisy_actions.shape[1], -1)
338
+ inputs = torch.cat([noisy_actions, state], dim=-1)
339
+ return self.action_encoder(inputs, timesteps, embodiment_id)
340
+
341
+ def _build_sequence(self, action_features: torch.Tensor) -> torch.Tensor:
342
+ future_tokens = self.future_tokens.weight.unsqueeze(0).expand(action_features.shape[0], -1, -1)
343
+ return torch.cat((future_tokens, action_features), dim=1)
344
+
345
+ def _predict(
346
+ self,
347
+ noisy_actions: torch.Tensor,
348
+ vl_embs: torch.Tensor,
349
+ state: torch.Tensor,
350
+ timesteps: torch.Tensor,
351
+ embodiment_id: torch.Tensor,
352
+ ) -> torch.Tensor:
353
+ features = self._encode_action_tokens(noisy_actions, state, timesteps, embodiment_id)
354
+ position_ids = torch.arange(features.shape[1], dtype=torch.long, device=features.device)
355
+ features = features + self.position_embedding(position_ids).unsqueeze(0)
356
+ output = self.model(
357
+ hidden_states=self._build_sequence(features),
358
+ encoder_hidden_states=vl_embs,
359
+ timestep=timesteps,
360
+ )
361
+ return self.action_decoder(output, embodiment_id)[:, -self.action_horizon :]
362
+
363
+ @torch.no_grad()
364
+ def predict_action(
365
+ self,
366
+ vl_embs: torch.Tensor,
367
+ state: torch.Tensor,
368
+ embodiment_id: torch.Tensor,
369
+ ) -> torch.Tensor:
370
+ actions = torch.zeros(
371
+ (vl_embs.shape[0], self.action_horizon, self.action_dim),
372
+ dtype=vl_embs.dtype,
373
+ device=vl_embs.device,
374
+ )
375
+ noise = torch.randn_like(actions)
376
+ for step in range(self.num_inference_timesteps, 0, -1):
377
+ time = step / float(self.num_inference_timesteps)
378
+ timestep = min(int(time * self.num_timestep_buckets), self.num_timestep_buckets - 1)
379
+ timesteps = torch.full(
380
+ (vl_embs.shape[0],),
381
+ timestep,
382
+ device=vl_embs.device,
383
+ dtype=torch.long,
384
+ )
385
+ noisy_actions = time * noise + (1 - time) * actions
386
+ actions = self._predict(noisy_actions, vl_embs, state, timesteps, embodiment_id)
387
+ return actions
chat_template.jinja ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {%- if enable_thinking is not defined -%}
2
+ {%- set enable_thinking = false -%}
3
+ {%- endif -%}
4
+ {%- macro render_content(content, is_system_content=false) -%}
5
+ {%- if content is string -%}
6
+ {{- content -}}
7
+ {%- elif content is iterable and content is not mapping -%}
8
+ {%- set ns = namespace(parts=[]) -%}
9
+ {%- for item in content -%}
10
+ {%- if 'image' in item or 'image_url' in item or item.type == 'image' -%}
11
+ {%- if is_system_content -%}
12
+ {{- raise_exception('System message cannot contain images.') -}}
13
+ {%- endif -%}
14
+ {%- set ns.parts = ns.parts + ['<|image_pad|>'] -%}
15
+ {%- elif 'video' in item or item.type == 'video' -%}
16
+ {%- if is_system_content -%}
17
+ {{- raise_exception('System message cannot contain videos.') -}}
18
+ {%- endif -%}
19
+ {%- set ns.parts = ns.parts + ['<|video_pad|>'] -%}
20
+ {%- elif 'text' in item -%}
21
+ {%- set ns.parts = ns.parts + [item.text] -%}
22
+ {%- else -%}
23
+ {{- raise_exception('Unexpected item type in content.') -}}
24
+ {%- endif -%}
25
+ {%- endfor -%}
26
+ {{- ns.parts | join('\n') -}}
27
+ {%- elif content is none or content is undefined -%}
28
+ {{- '' -}}
29
+ {%- else -%}
30
+ {{- raise_exception('Unexpected content type.') -}}
31
+ {%- endif -%}
32
+ {%- endmacro -%}
33
+ {%- if not messages %}
34
+ {{- raise_exception('No messages provided.') }}
35
+ {%- endif %}
36
+ {%- if tools and tools is iterable and tools is not mapping %}
37
+ {{- '<|im_start|>system\n' }}
38
+ {{- "# Tools\n\nYou have access to the following functions:\n\n<tools>" }}
39
+ {%- for tool in tools %}
40
+ {{- "\n" }}
41
+ {{- tool | tojson }}
42
+ {%- endfor %}
43
+ {{- "\n</tools>" }}
44
+ {{- '\n\nIf you choose to call a function ONLY reply in the following format with NO suffix:\n\n<tool_call>\n<function=example_function_name>\n<parameter=example_parameter_1>\nvalue_1\n</parameter>\n<parameter=example_parameter_2>\nThis is the value for the second parameter\nthat can span\nmultiple lines\n</parameter>\n</function>\n</tool_call>\n\n<IMPORTANT>\nReminder:\n- Function calls MUST follow the specified format: an inner <function=...></function> block must be nested within <tool_call></tool_call> XML tags\n- Required parameters MUST be specified\n- You may provide optional reasoning for your function call in natural language BEFORE the function call, but NOT after\n- If there is no function call available, answer the question like normal with your current knowledge and do not tell the user about function calls\n</IMPORTANT>' }}
45
+ {%- if messages[0].role == 'system' %}
46
+ {%- set content = render_content(messages[0].content, true)|trim %}
47
+ {%- if content %}
48
+ {{- '\n\n' + content }}
49
+ {%- endif %}
50
+ {%- endif %}
51
+ {{- '<|im_end|>\n' }}
52
+ {%- else %}
53
+ {%- if messages[0].role == 'system' %}
54
+ {%- set content = render_content(messages[0].content, true)|trim %}
55
+ {{- '<|im_start|>system\n' + content + '<|im_end|>\n' }}
56
+ {%- endif %}
57
+ {%- endif %}
58
+ {%- set ns = namespace(multi_step_tool=true, last_query_index=messages|length - 1) %}
59
+ {%- for message in messages[::-1] %}
60
+ {%- set index = (messages|length - 1) - loop.index0 %}
61
+ {%- if ns.multi_step_tool and message.role == "user" %}
62
+ {%- set content = render_content(message.content)|trim %}
63
+ {%- if not(content.startswith('<tool_response>') and content.endswith('</tool_response>')) %}
64
+ {%- set ns.multi_step_tool = false %}
65
+ {%- set ns.last_query_index = index %}
66
+ {%- endif %}
67
+ {%- endif %}
68
+ {%- endfor %}
69
+ {%- if ns.multi_step_tool %}
70
+ {{- raise_exception('No user query found in messages.') }}
71
+ {%- endif %}
72
+ {%- for message in messages %}
73
+ {%- set content = render_content(message.content)|trim %}
74
+ {%- if message.role == "system" %}
75
+ {%- if not loop.first %}
76
+ {{- raise_exception('System message must be at the beginning.') }}
77
+ {%- endif %}
78
+ {%- elif message.role == "user" %}
79
+ {{- '<|im_start|>' + message.role + '\n' + content + '<|im_end|>' + '\n' }}
80
+ {%- elif message.role == "assistant" %}
81
+ {%- set reasoning_content = '' %}
82
+ {%- if message.reasoning_content is string %}
83
+ {%- set reasoning_content = message.reasoning_content %}
84
+ {%- else %}
85
+ {%- if '</think>' in content %}
86
+ {%- set reasoning_content = content.split('</think>')[0].rstrip('\n').split('<think>')[-1].lstrip('\n') %}
87
+ {%- set content = content.split('</think>')[-1].lstrip('\n') %}
88
+ {%- endif %}
89
+ {%- endif %}
90
+ {%- set reasoning_content = reasoning_content|trim %}
91
+ {%- if loop.index0 > ns.last_query_index %}
92
+ {{- '<|im_start|>' + message.role + '\n<think>\n' + reasoning_content + '\n</think>\n\n' + content }}
93
+ {%- else %}
94
+ {{- '<|im_start|>' + message.role + '\n' + content }}
95
+ {%- endif %}
96
+ {%- if message.tool_calls and message.tool_calls is iterable and message.tool_calls is not mapping %}
97
+ {%- for tool_call in message.tool_calls %}
98
+ {%- if tool_call.function is defined %}
99
+ {%- set tool_call = tool_call.function %}
100
+ {%- endif %}
101
+ {%- if loop.first %}
102
+ {%- if content|trim %}
103
+ {{- '\n\n<tool_call>\n<function=' + tool_call.name + '>\n' }}
104
+ {%- else %}
105
+ {{- '<tool_call>\n<function=' + tool_call.name + '>\n' }}
106
+ {%- endif %}
107
+ {%- else %}
108
+ {{- '\n<tool_call>\n<function=' + tool_call.name + '>\n' }}
109
+ {%- endif %}
110
+ {%- if tool_call.arguments is defined %}
111
+ {%- for args_name, args_value in tool_call.arguments|items %}
112
+ {{- '<parameter=' + args_name + '>\n' }}
113
+ {%- set args_value = args_value | tojson | safe if args_value is mapping or (args_value is sequence and args_value is not string) else args_value | string %}
114
+ {{- args_value }}
115
+ {{- '\n</parameter>\n' }}
116
+ {%- endfor %}
117
+ {%- endif %}
118
+ {{- '</function>\n</tool_call>' }}
119
+ {%- endfor %}
120
+ {%- endif %}
121
+ {{- '<|im_end|>\n' }}
122
+ {%- elif message.role == "tool" %}
123
+ {%- if loop.previtem and loop.previtem.role != "tool" %}
124
+ {{- '<|im_start|>user' }}
125
+ {%- endif %}
126
+ {{- '\n<tool_response>\n' }}
127
+ {{- content }}
128
+ {{- '\n</tool_response>' }}
129
+ {%- if not loop.last and loop.nextitem.role != "tool" %}
130
+ {{- '<|im_end|>\n' }}
131
+ {%- elif loop.last %}
132
+ {{- '<|im_end|>\n' }}
133
+ {%- endif %}
134
+ {%- else %}
135
+ {{- raise_exception('Unexpected message role.') }}
136
+ {%- endif %}
137
+ {%- endfor %}
138
+ {%- if add_generation_prompt %}
139
+ {{- '<|im_start|>assistant\n' }}
140
+ {%- if enable_thinking is defined and enable_thinking is false %}
141
+ {{- '<think>\n\n</think>\n\n' }}
142
+ {%- else %}
143
+ {{- '<think>\n' }}
144
+ {%- endif %}
145
+ {%- endif %}
config.json ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "action_dim": 80,
3
+ "action_head_dtype": "float32",
4
+ "action_horizon": 30,
5
+ "architectures": [
6
+ "MiniCPMV_VLA"
7
+ ],
8
+ "dtype": "bfloat16",
9
+ "max_num_embodiments": 32,
10
+ "model_type": "minicpm_vla",
11
+ "num_inference_timesteps": 4,
12
+ "state_dim": 80,
13
+ "transformers_version": "5.7.0",
14
+ "vlm_config": {
15
+ "architectures": [
16
+ "MiniCPMV4_6ForConditionalGeneration"
17
+ ],
18
+ "bos_token_id": null,
19
+ "chunk_size_feed_forward": 0,
20
+ "downsample_mode": "16x",
21
+ "drop_vision_last_layer": false,
22
+ "dtype": null,
23
+ "eos_token_id": 248044,
24
+ "id2label": {
25
+ "0": "LABEL_0",
26
+ "1": "LABEL_1"
27
+ },
28
+ "image_size": 1120,
29
+ "image_token_id": 248056,
30
+ "insert_layer_id": 6,
31
+ "is_encoder_decoder": false,
32
+ "label2id": {
33
+ "LABEL_0": 0,
34
+ "LABEL_1": 1
35
+ },
36
+ "merge_kernel_size": [
37
+ 2,
38
+ 2
39
+ ],
40
+ "merger_times": 1,
41
+ "model_type": "minicpmv4_6",
42
+ "output_attentions": false,
43
+ "output_hidden_states": false,
44
+ "pad_token_id": null,
45
+ "patch_size": 14,
46
+ "problem_type": null,
47
+ "return_dict": true,
48
+ "text_config": {
49
+ "architectures": null,
50
+ "attention_bias": false,
51
+ "attention_dropout": 0.0,
52
+ "attn_output_gate": true,
53
+ "bos_token_id": null,
54
+ "chunk_size_feed_forward": 0,
55
+ "dtype": null,
56
+ "eos_token_id": null,
57
+ "full_attention_interval": 4,
58
+ "head_dim": 256,
59
+ "hidden_act": "silu",
60
+ "hidden_size": 1024,
61
+ "id2label": {
62
+ "0": "LABEL_0",
63
+ "1": "LABEL_1"
64
+ },
65
+ "initializer_range": 0.02,
66
+ "intermediate_size": 3584,
67
+ "is_encoder_decoder": false,
68
+ "label2id": {
69
+ "LABEL_0": 0,
70
+ "LABEL_1": 1
71
+ },
72
+ "layer_types": [
73
+ "linear_attention",
74
+ "linear_attention",
75
+ "linear_attention",
76
+ "full_attention",
77
+ "linear_attention",
78
+ "linear_attention",
79
+ "linear_attention",
80
+ "full_attention",
81
+ "linear_attention",
82
+ "linear_attention",
83
+ "linear_attention",
84
+ "full_attention",
85
+ "linear_attention",
86
+ "linear_attention",
87
+ "linear_attention",
88
+ "full_attention",
89
+ "linear_attention",
90
+ "linear_attention",
91
+ "linear_attention",
92
+ "full_attention",
93
+ "linear_attention",
94
+ "linear_attention",
95
+ "linear_attention",
96
+ "full_attention"
97
+ ],
98
+ "linear_conv_kernel_dim": 4,
99
+ "linear_key_head_dim": 128,
100
+ "linear_num_key_heads": 16,
101
+ "linear_num_value_heads": 16,
102
+ "linear_value_head_dim": 128,
103
+ "mamba_ssm_dtype": "float32",
104
+ "max_position_embeddings": 262144,
105
+ "mlp_only_layers": [],
106
+ "model_type": "qwen3_5_text",
107
+ "mtp_num_hidden_layers": 1,
108
+ "mtp_use_dedicated_embeddings": false,
109
+ "num_attention_heads": 8,
110
+ "num_hidden_layers": 24,
111
+ "num_key_value_heads": 2,
112
+ "output_attentions": false,
113
+ "output_hidden_states": false,
114
+ "pad_token_id": null,
115
+ "partial_rotary_factor": 0.25,
116
+ "problem_type": null,
117
+ "return_dict": true,
118
+ "rms_norm_eps": 1e-06,
119
+ "rope_parameters": {
120
+ "partial_rotary_factor": 0.25,
121
+ "rope_theta": 10000000,
122
+ "rope_type": "default"
123
+ },
124
+ "tie_word_embeddings": true,
125
+ "use_cache": true,
126
+ "vocab_size": 248094
127
+ },
128
+ "tie_word_embeddings": true,
129
+ "transformers_version": "5.7.0",
130
+ "video_token_id": 248057,
131
+ "vision_config": {
132
+ "architectures": null,
133
+ "attention_dropout": 0.0,
134
+ "chunk_size_feed_forward": 0,
135
+ "dtype": null,
136
+ "hidden_act": "gelu_pytorch_tanh",
137
+ "hidden_size": 1152,
138
+ "id2label": {
139
+ "0": "LABEL_0",
140
+ "1": "LABEL_1"
141
+ },
142
+ "image_size": 980,
143
+ "insert_layer_id": 6,
144
+ "intermediate_size": 4304,
145
+ "is_encoder_decoder": false,
146
+ "label2id": {
147
+ "LABEL_0": 0,
148
+ "LABEL_1": 1
149
+ },
150
+ "layer_norm_eps": 1e-06,
151
+ "num_attention_heads": 16,
152
+ "num_channels": 3,
153
+ "num_hidden_layers": 27,
154
+ "output_attentions": false,
155
+ "output_hidden_states": false,
156
+ "patch_size": 14,
157
+ "problem_type": null,
158
+ "return_dict": true,
159
+ "window_kernel_size": [
160
+ 2,
161
+ 2
162
+ ]
163
+ }
164
+ },
165
+ "vlm_dtype": "bfloat16",
166
+ "auto_map": {
167
+ "AutoConfig": "configuration_minicpm_vla.MiniCPMVLAConfig",
168
+ "AutoModel": "modeling_minicpm_vla.MiniCPMV_VLA"
169
+ }
170
+ }
configuration_minicpm_vla.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from transformers import PretrainedConfig
4
+
5
+
6
+ class MiniCPMVLAConfig(PretrainedConfig):
7
+ """Configuration for the complete VLM and action-head VLA model."""
8
+
9
+ model_type = "minicpm_vla"
10
+ is_composition = True
11
+
12
+ def __init__(
13
+ self,
14
+ vlm_config: dict | PretrainedConfig | None = None,
15
+ action_dim: int = 80,
16
+ state_dim: int = 80,
17
+ action_horizon: int = 30,
18
+ max_num_embodiments: int = 32,
19
+ num_inference_timesteps: int = 4,
20
+ vlm_dtype: str = "bfloat16",
21
+ action_head_dtype: str = "float32",
22
+ **kwargs,
23
+ ):
24
+ super().__init__(**kwargs)
25
+ if isinstance(vlm_config, PretrainedConfig):
26
+ vlm_config = vlm_config.to_dict()
27
+ self.vlm_config = vlm_config
28
+ self.action_dim = action_dim
29
+ self.state_dim = state_dim
30
+ self.action_horizon = action_horizon
31
+ self.max_num_embodiments = max_num_embodiments
32
+ self.num_inference_timesteps = num_inference_timesteps
33
+ self.vlm_dtype = vlm_dtype
34
+ self.action_head_dtype = action_head_dtype
35
+ self.architectures = ["MiniCPMV_VLA"]
discord.jpeg ADDED
discord_logo.png ADDED
feishu_logo.png ADDED
generation_config.json ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "bos_token_id": 248045,
3
+ "do_sample": true,
4
+ "eos_token_id": [
5
+ 248044,
6
+ 248046
7
+ ],
8
+ "temperature": 0.7,
9
+ "top_k": 0,
10
+ "top_p": 1.0,
11
+ "repetition_penalty": 1.0,
12
+ "transformers_version": "5.7.0"
13
+ }
github_logo.png ADDED
manip_benchmark.png ADDED

Git LFS Details

  • SHA256: 2899f1a811b52a55b8f531dda073a3e0cc2c0468797db3587654045da011a995
  • Pointer size: 131 Bytes
  • Size of remote file: 173 kB
manip_case_en.gif ADDED

Git LFS Details

  • SHA256: 47e593bfeb7ec800c36dbc22eeeaed61775d61b91de0e0d9b4cafb8d89ba99f1
  • Pointer size: 133 Bytes
  • Size of remote file: 31.5 MB
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:404314e1f9599d3162fdaa46e4d0c40acf82c27bc7aee2e15e7fe675fb1635b7
3
+ size 3544229208
modeling_minicpm_vla.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Mapping
4
+
5
+ import torch
6
+ from transformers import AutoConfig, AutoModel, AutoModelForImageTextToText, PretrainedConfig, PreTrainedModel
7
+
8
+ try:
9
+ from .action_head import MiniCPMV_VLA_ActionHead
10
+ from .configuration_minicpm_vla import MiniCPMVLAConfig
11
+ except ImportError: # pragma: no cover - fallback for non-package loaders
12
+ from action_head import MiniCPMV_VLA_ActionHead
13
+ from configuration_minicpm_vla import MiniCPMVLAConfig
14
+
15
+
16
+ def _torch_dtype(name: str) -> torch.dtype:
17
+ try:
18
+ return getattr(torch, name)
19
+ except AttributeError as exc:
20
+ raise ValueError(f"Unsupported torch dtype: {name}") from exc
21
+
22
+
23
+ def _build_vlm_config(config_dict: dict) -> PretrainedConfig:
24
+ values = dict(config_dict)
25
+ model_type = values.pop("model_type")
26
+ return AutoConfig.for_model(model_type, **values)
27
+
28
+
29
+ class MiniCPMV_VLA(PreTrainedModel):
30
+ """Complete VLA model loadable with ``AutoModel.from_pretrained``."""
31
+
32
+ config_class = MiniCPMVLAConfig
33
+ base_model_prefix = ""
34
+ _tied_weights_keys = {
35
+ "vlm.lm_head.weight": "vlm.model.language_model.embed_tokens.weight",
36
+ }
37
+ _no_split_modules = ["BasicTransformerBlock"]
38
+
39
+ def __init__(self, config: MiniCPMVLAConfig):
40
+ super().__init__(config)
41
+ if config.vlm_config is None:
42
+ raise ValueError("config.vlm_config is required")
43
+ vlm_config = _build_vlm_config(config.vlm_config)
44
+ self.vlm_dtype = _torch_dtype(config.vlm_dtype)
45
+ self.action_head_dtype = _torch_dtype(config.action_head_dtype)
46
+ self.vlm = AutoModelForImageTextToText.from_config(vlm_config).to(self.vlm_dtype)
47
+ self.vlm.config.hidden_size = self.vlm.config.text_config.hidden_size
48
+ self.action_head = MiniCPMV_VLA_ActionHead(
49
+ action_dim=config.action_dim,
50
+ state_dim=config.state_dim,
51
+ action_horizon=config.action_horizon,
52
+ num_inference_timesteps=config.num_inference_timesteps,
53
+ max_num_embodiments=config.max_num_embodiments,
54
+ ).to(self.action_head_dtype)
55
+ self.post_init()
56
+
57
+ def get_input_embeddings(self):
58
+ return self.vlm.get_input_embeddings()
59
+
60
+ def set_input_embeddings(self, value):
61
+ return self.vlm.set_input_embeddings(value)
62
+
63
+ def _vlm_forward(self, data: Mapping[str, object]):
64
+ inputs = {key: value for key, value in data.items() if value is not None}
65
+ if "language_attention_mask" in inputs and "attention_mask" not in inputs:
66
+ inputs["attention_mask"] = inputs.pop("language_attention_mask")
67
+ inputs.setdefault("output_hidden_states", True)
68
+ inputs.setdefault("return_dict", True)
69
+ inputs.setdefault("use_cache", False)
70
+ inputs.setdefault("logits_to_keep", 1)
71
+ with torch.autocast("cuda", dtype=self.vlm_dtype, enabled=torch.cuda.is_available()):
72
+ try:
73
+ return self.vlm(**inputs)
74
+ except TypeError as exc:
75
+ if "logits_to_keep" not in str(exc):
76
+ raise
77
+ inputs.pop("logits_to_keep")
78
+ return self.vlm(**inputs)
79
+
80
+ @torch.no_grad()
81
+ def predict_action(
82
+ self,
83
+ state: torch.Tensor,
84
+ embodiment_id: torch.Tensor,
85
+ **vlm_inputs,
86
+ ) -> torch.Tensor:
87
+ vl_embs = self._vlm_forward(vlm_inputs).hidden_states[-1].to(self.action_head_dtype)
88
+ return self.action_head.predict_action(vl_embs, state, embodiment_id)
89
+
90
+
91
+ AutoConfig.register("minicpm_vla", MiniCPMVLAConfig)
92
+ AutoModel.register(MiniCPMVLAConfig, MiniCPMV_VLA)
modelscope_logo.png ADDED
preprocessor_config.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "image_processor_type": "MiniCPMV4_6ImageProcessor",
3
+ "processor_class": "MiniCPMV4_6Processor",
4
+ "max_slice_nums": 9,
5
+ "scale_resolution": 448,
6
+ "patch_size": 14,
7
+ "use_image_id": true,
8
+ "slice_mode": true,
9
+ "image_mean": [
10
+ 0.5,
11
+ 0.5,
12
+ 0.5
13
+ ],
14
+ "image_std": [
15
+ 0.5,
16
+ 0.5,
17
+ 0.5
18
+ ]
19
+ }
tokenizer.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:33861e37bb955af1e3f3061182b820f347eba2b9c2c1011c82794bf0d6e77b54
3
+ size 19992481
tokenizer_config.json ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "add_prefix_space": false,
3
+ "audio_bos_token": "<|audio_start|>",
4
+ "audio_eos_token": "<|audio_end|>",
5
+ "audio_token": "<|audio_pad|>",
6
+ "backend": "tokenizers",
7
+ "bos_token": "<|im_start|>",
8
+ "clean_up_tokenization_spaces": false,
9
+ "eos_token": "<|im_end|>",
10
+ "errors": "replace",
11
+ "extra_special_tokens": {
12
+ "image_token": "<|image_pad|>",
13
+ "video_token": "<|video_pad|>",
14
+ "image_start_token": "<image>",
15
+ "image_end_token": "</image>",
16
+ "slice_start_token": "<slice>",
17
+ "slice_end_token": "</slice>",
18
+ "image_id_start_token": "<image_id>",
19
+ "image_id_end_token": "</image_id>"
20
+ },
21
+ "image_token": "<|image_pad|>",
22
+ "is_local": true,
23
+ "model_max_length": 262144,
24
+ "model_specific_special_tokens": {
25
+ "audio_bos_token": "<|audio_start|>",
26
+ "audio_eos_token": "<|audio_end|>",
27
+ "audio_token": "<|audio_pad|>",
28
+ "image_token": "<|image_pad|>",
29
+ "video_token": "<|video_pad|>",
30
+ "vision_bos_token": "<|vision_start|>",
31
+ "vision_eos_token": "<|vision_end|>"
32
+ },
33
+ "pad_token": "<|endoftext|>",
34
+ "pretokenize_regex": "(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\\r\\n\\p{L}\\p{N}]?[\\p{L}\\p{M}]+|\\p{N}| ?[^\\s\\p{L}\\p{M}\\p{N}]+[\\r\\n]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+",
35
+ "split_special_tokens": false,
36
+ "unk_token": "<unk>",
37
+ "video_token": "<|video_pad|>",
38
+ "vision_bos_token": "<|vision_start|>",
39
+ "vision_eos_token": "<|vision_end|>",
40
+ "chat_template": "{%- if enable_thinking is not defined -%}\n {%- set enable_thinking = false -%}\n{%- endif -%}\n{%- macro render_content(content, is_system_content=false) -%}\n {%- if content is string -%}\n {{- content -}}\n {%- elif content is iterable and content is not mapping -%}\n {%- set ns = namespace(parts=[]) -%}\n {%- for item in content -%}\n {%- if 'image' in item or 'image_url' in item or item.type == 'image' -%}\n {%- if is_system_content -%}\n {{- raise_exception('System message cannot contain images.') -}}\n {%- endif -%}\n {%- set ns.parts = ns.parts + ['<|image_pad|>'] -%}\n {%- elif 'video' in item or item.type == 'video' -%}\n {%- if is_system_content -%}\n {{- raise_exception('System message cannot contain videos.') -}}\n {%- endif -%}\n {%- set ns.parts = ns.parts + ['<|video_pad|>'] -%}\n {%- elif 'text' in item -%}\n {%- set ns.parts = ns.parts + [item.text] -%}\n {%- else -%}\n {{- raise_exception('Unexpected item type in content.') -}}\n {%- endif -%}\n {%- endfor -%}\n {{- ns.parts | join('\\n') -}}\n {%- elif content is none or content is undefined -%}\n {{- '' -}}\n {%- else -%}\n {{- raise_exception('Unexpected content type.') -}}\n {%- endif -%}\n{%- endmacro -%}\n{%- if not messages %}\n {{- raise_exception('No messages provided.') }}\n{%- endif %}\n{%- if tools and tools is iterable and tools is not mapping %}\n {{- '<|im_start|>system\\n' }}\n {{- \"# Tools\\n\\nYou have access to the following functions:\\n\\n<tools>\" }}\n {%- for tool in tools %}\n {{- \"\\n\" }}\n {{- tool | tojson }}\n {%- endfor %}\n {{- \"\\n</tools>\" }}\n {{- '\\n\\nIf you choose to call a function ONLY reply in the following format with NO suffix:\\n\\n<tool_call>\\n<function=example_function_name>\\n<parameter=example_parameter_1>\\nvalue_1\\n</parameter>\\n<parameter=example_parameter_2>\\nThis is the value for the second parameter\\nthat can span\\nmultiple lines\\n</parameter>\\n</function>\\n</tool_call>\\n\\n<IMPORTANT>\\nReminder:\\n- Function calls MUST follow the specified format: an inner <function=...></function> block must be nested within <tool_call></tool_call> XML tags\\n- Required parameters MUST be specified\\n- You may provide optional reasoning for your function call in natural language BEFORE the function call, but NOT after\\n- If there is no function call available, answer the question like normal with your current knowledge and do not tell the user about function calls\\n</IMPORTANT>' }}\n {%- if messages[0].role == 'system' %}\n {%- set content = render_content(messages[0].content, true)|trim %}\n {%- if content %}\n {{- '\\n\\n' + content }}\n {%- endif %}\n {%- endif %}\n {{- '<|im_end|>\\n' }}\n{%- else %}\n {%- if messages[0].role == 'system' %}\n {%- set content = render_content(messages[0].content, true)|trim %}\n {{- '<|im_start|>system\\n' + content + '<|im_end|>\\n' }}\n {%- endif %}\n{%- endif %}\n{%- set ns = namespace(multi_step_tool=true, last_query_index=messages|length - 1) %}\n{%- for message in messages[::-1] %}\n {%- set index = (messages|length - 1) - loop.index0 %}\n {%- if ns.multi_step_tool and message.role == \"user\" %}\n {%- set content = render_content(message.content)|trim %}\n {%- if not(content.startswith('<tool_response>') and content.endswith('</tool_response>')) %}\n {%- set ns.multi_step_tool = false %}\n {%- set ns.last_query_index = index %}\n {%- endif %}\n {%- endif %}\n{%- endfor %}\n{%- if ns.multi_step_tool %}\n {{- raise_exception('No user query found in messages.') }}\n{%- endif %}\n{%- for message in messages %}\n {%- set content = render_content(message.content)|trim %}\n {%- if message.role == \"system\" %}\n {%- if not loop.first %}\n {{- raise_exception('System message must be at the beginning.') }}\n {%- endif %}\n {%- elif message.role == \"user\" %}\n {{- '<|im_start|>' + message.role + '\\n' + content + '<|im_end|>' + '\\n' }}\n {%- elif message.role == \"assistant\" %}\n {%- set reasoning_content = '' %}\n {%- if message.reasoning_content is string %}\n {%- set reasoning_content = message.reasoning_content %}\n {%- else %}\n {%- if '</think>' in content %}\n {%- set reasoning_content = content.split('</think>')[0].rstrip('\\n').split('<think>')[-1].lstrip('\\n') %}\n {%- set content = content.split('</think>')[-1].lstrip('\\n') %}\n {%- endif %}\n {%- endif %}\n {%- set reasoning_content = reasoning_content|trim %}\n {%- if loop.index0 > ns.last_query_index %}\n {{- '<|im_start|>' + message.role + '\\n<think>\\n' + reasoning_content + '\\n</think>\\n\\n' + content }}\n {%- else %}\n {{- '<|im_start|>' + message.role + '\\n' + content }}\n {%- endif %}\n {%- if message.tool_calls and message.tool_calls is iterable and message.tool_calls is not mapping %}\n {%- for tool_call in message.tool_calls %}\n {%- if tool_call.function is defined %}\n {%- set tool_call = tool_call.function %}\n {%- endif %}\n {%- if loop.first %}\n {%- if content|trim %}\n {{- '\\n\\n<tool_call>\\n<function=' + tool_call.name + '>\\n' }}\n {%- else %}\n {{- '<tool_call>\\n<function=' + tool_call.name + '>\\n' }}\n {%- endif %}\n {%- else %}\n {{- '\\n<tool_call>\\n<function=' + tool_call.name + '>\\n' }}\n {%- endif %}\n {%- if tool_call.arguments is defined %}\n {%- for args_name, args_value in tool_call.arguments|items %}\n {{- '<parameter=' + args_name + '>\\n' }}\n {%- set args_value = args_value | tojson | safe if args_value is mapping or (args_value is sequence and args_value is not string) else args_value | string %}\n {{- args_value }}\n {{- '\\n</parameter>\\n' }}\n {%- endfor %}\n {%- endif %}\n {{- '</function>\\n</tool_call>' }}\n {%- endfor %}\n {%- endif %}\n {{- '<|im_end|>\\n' }}\n {%- elif message.role == \"tool\" %}\n {%- if loop.previtem and loop.previtem.role != \"tool\" %}\n {{- '<|im_start|>user' }}\n {%- endif %}\n {{- '\\n<tool_response>\\n' }}\n {{- content }}\n {{- '\\n</tool_response>' }}\n {%- if not loop.last and loop.nextitem.role != \"tool\" %}\n {{- '<|im_end|>\\n' }}\n {%- elif loop.last %}\n {{- '<|im_end|>\\n' }}\n {%- endif %}\n {%- else %}\n {{- raise_exception('Unexpected message role.') }}\n {%- endif %}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|im_start|>assistant\\n' }}\n {%- if enable_thinking is defined and enable_thinking is false %}\n {{- '<think>\\n\\n</think>\\n\\n' }}\n {%- else %}\n {{- '<think>\\n' }}\n {%- endif %}\n{%- endif %}\n",
41
+ "tokenizer_class": "Qwen2Tokenizer"
42
+ }
x.png ADDED