MiniCPM-RobotManip / action_head.py
JunyuZ's picture
Initial commit
7f5d27f
Raw
History Blame Contribute Delete
16.2 kB
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
@register_to_config
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 :]
@torch.no_grad()
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