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
| from __future__ import annotations | |
| from typing import Optional | |
| import torch | |
| import torch.nn.functional as F | |
| from diffusers import ConfigMixin, ModelMixin | |
| from diffusers.configuration_utils import register_to_config | |
| from diffusers.models.attention import Attention, FeedForward | |
| from diffusers.models.embeddings import SinusoidalPositionalEmbedding, TimestepEmbedding, Timesteps | |
| from torch import nn | |
| class TimestepEncoder(nn.Module): | |
| def __init__(self, embedding_dim: int): | |
| super().__init__() | |
| self.time_proj = Timesteps(num_channels=256, flip_sin_to_cos=True, downscale_freq_shift=1) | |
| self.timestep_embedder = TimestepEmbedding(in_channels=256, time_embed_dim=embedding_dim) | |
| def forward(self, timesteps: torch.Tensor) -> torch.Tensor: | |
| return self.timestep_embedder(self.time_proj(timesteps).to(next(self.parameters()).dtype)) | |
| class AdaLayerNorm(nn.Module): | |
| def __init__( | |
| self, | |
| embedding_dim: int, | |
| norm_elementwise_affine: bool = False, | |
| norm_eps: float = 1e-5, | |
| ): | |
| super().__init__() | |
| self.silu = nn.SiLU() | |
| self.linear = nn.Linear(embedding_dim, embedding_dim * 2) | |
| self.norm = nn.LayerNorm(embedding_dim, norm_eps, norm_elementwise_affine) | |
| def forward(self, x: torch.Tensor, temb: torch.Tensor) -> torch.Tensor: | |
| scale, shift = self.linear(self.silu(temb)).chunk(2, dim=1) | |
| return self.norm(x) * (1 + scale[:, None]) + shift[:, None] | |
| class BasicTransformerBlock(nn.Module): | |
| def __init__( | |
| self, | |
| dim: int, | |
| num_attention_heads: int, | |
| attention_head_dim: int, | |
| dropout: float = 0.0, | |
| cross_attention_dim: Optional[int] = None, | |
| activation_fn: str = "geglu", | |
| attention_bias: bool = False, | |
| upcast_attention: bool = False, | |
| norm_elementwise_affine: bool = True, | |
| norm_type: str = "layer_norm", | |
| norm_eps: float = 1e-5, | |
| final_dropout: bool = False, | |
| positional_embeddings: Optional[str] = None, | |
| num_positional_embeddings: Optional[int] = None, | |
| ff_inner_dim: Optional[int] = None, | |
| ff_bias: bool = True, | |
| attention_out_bias: bool = True, | |
| ): | |
| super().__init__() | |
| if positional_embeddings and num_positional_embeddings is None: | |
| raise ValueError("num_positional_embeddings is required for positional embeddings") | |
| self.norm_type = norm_type | |
| self.pos_embed = ( | |
| SinusoidalPositionalEmbedding(dim, max_seq_length=num_positional_embeddings) | |
| if positional_embeddings == "sinusoidal" | |
| else None | |
| ) | |
| self.norm1 = ( | |
| AdaLayerNorm(dim) | |
| if norm_type == "ada_norm" | |
| else nn.LayerNorm(dim, elementwise_affine=norm_elementwise_affine, eps=norm_eps) | |
| ) | |
| self.attn1 = Attention( | |
| query_dim=dim, | |
| heads=num_attention_heads, | |
| dim_head=attention_head_dim, | |
| dropout=dropout, | |
| bias=attention_bias, | |
| cross_attention_dim=cross_attention_dim, | |
| upcast_attention=upcast_attention, | |
| out_bias=attention_out_bias, | |
| ) | |
| self.norm3 = nn.LayerNorm(dim, norm_eps, norm_elementwise_affine) | |
| self.ff = FeedForward( | |
| dim, | |
| dropout=dropout, | |
| activation_fn=activation_fn, | |
| final_dropout=final_dropout, | |
| inner_dim=ff_inner_dim, | |
| bias=ff_bias, | |
| ) | |
| self.final_dropout = nn.Dropout(dropout) if final_dropout else None | |
| def forward( | |
| self, | |
| hidden_states: torch.Tensor, | |
| encoder_hidden_states: Optional[torch.Tensor] = None, | |
| encoder_attention_mask: Optional[torch.Tensor] = None, | |
| temb: Optional[torch.Tensor] = None, | |
| ) -> torch.Tensor: | |
| norm_hidden_states = ( | |
| self.norm1(hidden_states, temb) if self.norm_type == "ada_norm" else self.norm1(hidden_states) | |
| ) | |
| if self.pos_embed is not None: | |
| norm_hidden_states = self.pos_embed(norm_hidden_states) | |
| attention_output = self.attn1( | |
| norm_hidden_states, | |
| encoder_hidden_states=encoder_hidden_states, | |
| attention_mask=encoder_attention_mask, | |
| ) | |
| if self.final_dropout is not None: | |
| attention_output = self.final_dropout(attention_output) | |
| hidden_states = attention_output + hidden_states | |
| if hidden_states.ndim == 4: | |
| hidden_states = hidden_states.squeeze(1) | |
| hidden_states = self.ff(self.norm3(hidden_states)) + hidden_states | |
| if hidden_states.ndim == 4: | |
| hidden_states = hidden_states.squeeze(1) | |
| return hidden_states | |
| class DiT(ModelMixin, ConfigMixin): | |
| _supports_gradient_checkpointing = True | |
| def __init__( | |
| self, | |
| num_attention_heads: int = 8, | |
| attention_head_dim: int = 64, | |
| output_dim: int = 26, | |
| num_layers: int = 12, | |
| dropout: float = 0.1, | |
| attention_bias: bool = True, | |
| activation_fn: str = "gelu-approximate", | |
| num_embeds_ada_norm: Optional[int] = 1000, | |
| upcast_attention: bool = False, | |
| norm_type: str = "ada_norm", | |
| norm_elementwise_affine: bool = False, | |
| norm_eps: float = 1e-5, | |
| max_num_positional_embeddings: int = 512, | |
| compute_dtype: torch.dtype = torch.float32, | |
| final_dropout: bool = True, | |
| positional_embeddings: Optional[str] = "sinusoidal", | |
| interleave_self_attention: bool = False, | |
| cross_attention_dim: Optional[int] = None, | |
| **kwargs, | |
| ): | |
| super().__init__() | |
| self.attention_head_dim = attention_head_dim | |
| self.inner_dim = self.config.num_attention_heads * self.config.attention_head_dim | |
| self.gradient_checkpointing = False | |
| self.timestep_encoder = TimestepEncoder(self.inner_dim) | |
| self.transformer_blocks = nn.ModuleList( | |
| [ | |
| BasicTransformerBlock( | |
| self.inner_dim, | |
| self.config.num_attention_heads, | |
| self.config.attention_head_dim, | |
| dropout=self.config.dropout, | |
| activation_fn=self.config.activation_fn, | |
| attention_bias=self.config.attention_bias, | |
| upcast_attention=self.config.upcast_attention, | |
| norm_type=norm_type, | |
| norm_elementwise_affine=self.config.norm_elementwise_affine, | |
| norm_eps=self.config.norm_eps, | |
| positional_embeddings=positional_embeddings, | |
| num_positional_embeddings=self.config.max_num_positional_embeddings, | |
| final_dropout=final_dropout, | |
| cross_attention_dim=( | |
| None if index % 2 == 1 and interleave_self_attention else cross_attention_dim | |
| ), | |
| ) | |
| for index in range(self.config.num_layers) | |
| ] | |
| ) | |
| self.norm_out = nn.LayerNorm(self.inner_dim, elementwise_affine=False, eps=1e-6) | |
| self.proj_out_1 = nn.Linear(self.inner_dim, 2 * self.inner_dim) | |
| self.proj_out_2 = nn.Linear(self.inner_dim, self.config.output_dim) | |
| def forward( | |
| self, | |
| hidden_states: torch.Tensor, | |
| encoder_hidden_states: torch.Tensor, | |
| timestep: Optional[torch.LongTensor] = None, | |
| return_all_hidden_states: bool = False, | |
| encoder_attention_mask: Optional[torch.Tensor] = None, | |
| ): | |
| time_embedding = self.timestep_encoder(timestep) | |
| hidden_states = hidden_states.contiguous() | |
| encoder_hidden_states = encoder_hidden_states.contiguous() | |
| all_hidden_states = [hidden_states] | |
| for index, block in enumerate(self.transformer_blocks): | |
| self_attention = index % 2 == 1 and self.config.interleave_self_attention | |
| hidden_states = block( | |
| hidden_states, | |
| encoder_hidden_states=None if self_attention else encoder_hidden_states, | |
| encoder_attention_mask=None if self_attention else encoder_attention_mask, | |
| temb=time_embedding, | |
| ) | |
| all_hidden_states.append(hidden_states) | |
| shift, scale = self.proj_out_1(F.silu(time_embedding)).chunk(2, dim=1) | |
| hidden_states = self.norm_out(hidden_states) * (1 + scale[:, None]) + shift[:, None] | |
| output = self.proj_out_2(hidden_states) | |
| return (output, all_hidden_states) if return_all_hidden_states else output | |
| class SinusoidalPositionalEncoding(nn.Module): | |
| def __init__(self, embedding_dim: int): | |
| super().__init__() | |
| self.embedding_dim = embedding_dim | |
| def forward(self, timesteps: torch.Tensor) -> torch.Tensor: | |
| timesteps = timesteps.float() | |
| half_dim = self.embedding_dim // 2 | |
| exponent = -torch.arange(half_dim, dtype=torch.float, device=timesteps.device) * ( | |
| torch.log(torch.tensor(10000.0, device=timesteps.device)) / half_dim | |
| ) | |
| frequencies = timesteps.unsqueeze(-1) * exponent.exp() | |
| return torch.cat([torch.sin(frequencies), torch.cos(frequencies)], dim=-1) | |
| class CategorySpecificLinear(nn.Module): | |
| def __init__(self, num_categories: int, input_dim: int, output_dim: int): | |
| super().__init__() | |
| self.num_categories = num_categories | |
| self.W = nn.Parameter(0.02 * torch.randn(num_categories, input_dim, output_dim)) | |
| self.b = nn.Parameter(torch.zeros(num_categories, output_dim)) | |
| def forward(self, x: torch.Tensor, category_ids: torch.Tensor) -> torch.Tensor: | |
| if category_ids is None: | |
| raise ValueError("embodiment_id (B,) is required") | |
| return torch.bmm(x, self.W[category_ids]) + self.b[category_ids].unsqueeze(1) | |
| class CategorySpecificMLP(nn.Module): | |
| def __init__(self, num_categories: int, input_dim: int, hidden_dim: int, output_dim: int): | |
| super().__init__() | |
| self.layer1 = CategorySpecificLinear(num_categories, input_dim, hidden_dim) | |
| self.layer2 = CategorySpecificLinear(num_categories, hidden_dim, output_dim) | |
| def forward(self, x: torch.Tensor, category_ids: torch.Tensor) -> torch.Tensor: | |
| return self.layer2(F.relu(self.layer1(x, category_ids)), category_ids) | |
| class MultiEmbodimentActionEncoder(nn.Module): | |
| def __init__(self, action_dim: int, hidden_size: int, num_embodiments: int): | |
| super().__init__() | |
| self.W1 = CategorySpecificLinear(num_embodiments, action_dim, hidden_size) | |
| self.W2 = CategorySpecificLinear(num_embodiments, 2 * hidden_size, hidden_size) | |
| self.W3 = CategorySpecificLinear(num_embodiments, hidden_size, hidden_size) | |
| self.pos_encoding = SinusoidalPositionalEncoding(hidden_size) | |
| def forward( | |
| self, | |
| actions: torch.Tensor, | |
| timesteps: torch.Tensor, | |
| embodiment_id: torch.Tensor, | |
| ) -> torch.Tensor: | |
| batch_size, horizon, _ = actions.shape | |
| if timesteps.dim() != 1 or timesteps.shape[0] != batch_size: | |
| raise ValueError("timesteps must have shape (B,)") | |
| timesteps = timesteps.unsqueeze(1).expand(-1, horizon) | |
| action_embedding = self.W1(actions, embodiment_id) | |
| time_embedding = self.pos_encoding(timesteps).to(dtype=action_embedding.dtype) | |
| hidden = self.W2(torch.cat([action_embedding, time_embedding], dim=-1), embodiment_id) | |
| return self.W3(hidden * torch.sigmoid(hidden), embodiment_id) | |
| class MiniCPMV_VLA_ActionHead(nn.Module): | |
| """80-D, 32-embodiment action head for MiniCPM-VLA.""" | |
| def __init__( | |
| self, | |
| hidden_size: int = 1024, | |
| action_dim: int = 80, | |
| state_dim: int = 80, | |
| action_horizon: int = 30, | |
| num_inference_timesteps: int = 4, | |
| num_target_vision_tokens: int = 32, | |
| max_seq_len: int = 1024, | |
| num_timestep_buckets: int = 1000, | |
| max_num_embodiments: int = 32, | |
| ): | |
| super().__init__() | |
| self.hidden_size = hidden_size | |
| self.input_embedding_dim = 768 | |
| self.model = DiT( | |
| input_embedding_dim=768, | |
| attention_head_dim=64, | |
| num_attention_heads=12, | |
| cross_attention_dim=1024, | |
| dropout=0.2, | |
| final_dropout=True, | |
| interleave_self_attention=True, | |
| norm_type="ada_norm", | |
| num_layers=16, | |
| output_dim=1024, | |
| positional_embeddings=None, | |
| ) | |
| self.action_dim = action_dim | |
| self.state_dim = state_dim | |
| self.action_horizon = action_horizon | |
| self.num_inference_timesteps = num_inference_timesteps | |
| self.max_num_embodiments = max_num_embodiments | |
| self.multi_embodiment = True | |
| self.proprio_inject = "concat" | |
| self.state_encoder = None | |
| self.action_encoder = MultiEmbodimentActionEncoder( | |
| action_dim=action_dim + state_dim, | |
| hidden_size=self.input_embedding_dim, | |
| num_embodiments=max_num_embodiments, | |
| ) | |
| self.action_decoder = CategorySpecificMLP( | |
| num_categories=max_num_embodiments, | |
| input_dim=self.model.config.output_dim, | |
| hidden_dim=hidden_size, | |
| output_dim=action_dim, | |
| ) | |
| self.future_tokens = nn.Embedding(num_target_vision_tokens, self.input_embedding_dim) | |
| nn.init.normal_(self.future_tokens.weight, mean=0.0, std=0.02) | |
| self.position_embedding = nn.Embedding(max_seq_len, self.input_embedding_dim) | |
| nn.init.normal_(self.position_embedding.weight, mean=0.0, std=0.02) | |
| self.num_timestep_buckets = num_timestep_buckets | |
| def _encode_action_tokens( | |
| self, | |
| noisy_actions: torch.Tensor, | |
| state: torch.Tensor, | |
| timesteps: torch.Tensor, | |
| embodiment_id: torch.Tensor, | |
| ) -> torch.Tensor: | |
| if state is None: | |
| raise ValueError("state is required because PROPRIO_INJECT=concat") | |
| state = state.expand(-1, noisy_actions.shape[1], -1) | |
| inputs = torch.cat([noisy_actions, state], dim=-1) | |
| return self.action_encoder(inputs, timesteps, embodiment_id) | |
| def _build_sequence(self, action_features: torch.Tensor) -> torch.Tensor: | |
| future_tokens = self.future_tokens.weight.unsqueeze(0).expand(action_features.shape[0], -1, -1) | |
| return torch.cat((future_tokens, action_features), dim=1) | |
| def _predict( | |
| self, | |
| noisy_actions: torch.Tensor, | |
| vl_embs: torch.Tensor, | |
| state: torch.Tensor, | |
| timesteps: torch.Tensor, | |
| embodiment_id: torch.Tensor, | |
| ) -> torch.Tensor: | |
| features = self._encode_action_tokens(noisy_actions, state, timesteps, embodiment_id) | |
| position_ids = torch.arange(features.shape[1], dtype=torch.long, device=features.device) | |
| features = features + self.position_embedding(position_ids).unsqueeze(0) | |
| output = self.model( | |
| hidden_states=self._build_sequence(features), | |
| encoder_hidden_states=vl_embs, | |
| timestep=timesteps, | |
| ) | |
| return self.action_decoder(output, embodiment_id)[:, -self.action_horizon :] | |
| def predict_action( | |
| self, | |
| vl_embs: torch.Tensor, | |
| state: torch.Tensor, | |
| embodiment_id: torch.Tensor, | |
| ) -> torch.Tensor: | |
| actions = torch.zeros( | |
| (vl_embs.shape[0], self.action_horizon, self.action_dim), | |
| dtype=vl_embs.dtype, | |
| device=vl_embs.device, | |
| ) | |
| noise = torch.randn_like(actions) | |
| for step in range(self.num_inference_timesteps, 0, -1): | |
| time = step / float(self.num_inference_timesteps) | |
| timestep = min(int(time * self.num_timestep_buckets), self.num_timestep_buckets - 1) | |
| timesteps = torch.full( | |
| (vl_embs.shape[0],), | |
| timestep, | |
| device=vl_embs.device, | |
| dtype=torch.long, | |
| ) | |
| noisy_actions = time * noise + (1 - time) * actions | |
| actions = self._predict(noisy_actions, vl_embs, state, timesteps, embodiment_id) | |
| return actions | |