| |
|
|
| |
|
|
| |
| |
| |
| |
| |
| |
|
|
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
|
|
|
|
| |
|
|
| |
|
|
| |
| |
| |
| |
| |
| |
|
|
| |
| |
|
|
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| import torch |
| import torch.nn as nn |
| import numpy as np |
| import logging |
| from copy import deepcopy |
| from torch import Tensor |
| from diffusion_policy.model.common.mlp import MLP, ResidualMLP |
| from diffusion_policy.model.diffusion.positional_embedding import SinusoidalPosEmb |
| from diffusion_policy.model.common.modules import SpatialEmb, RandomShiftsAug |
| from diffusion_policy.model.common.vit import VitEncoder |
| from diffusion_policy.model.flow.mlp_flow import NoisyFlowMLP, ExploreNoiseNet |
| log = logging.getLogger(__name__) |
| import einops |
| from typing import Tuple, List |
|
|
| class ShortCutFlowMLP(nn.Module): |
| def __init__( |
| self, |
| horizon_steps, |
| action_dim, |
| cond_dim, |
| td_emb_dim=16, |
| mlp_dims=[256, 256], |
| cond_mlp_dims=None, |
| activation_type="Mish", |
| out_activation_type="Identity", |
| use_layernorm=False, |
| residual_style=False, |
| embed_combination_type='add' |
| ): |
| super().__init__() |
| self.td_emb_dim = td_emb_dim |
| self.act_dim_total = action_dim * horizon_steps |
| self.horizon_steps = horizon_steps |
| self.action_dim = action_dim |
| self.cond_dim=cond_dim |
| self.activation_type=activation_type |
| self.out_activation_type=out_activation_type |
| self.time_embed_activation=nn.Mish() |
| self.use_layernorm=use_layernorm |
| self.residual_style=residual_style |
| candidate_embed_combination_types=['add', 'multiply', 'concate'] |
| if embed_combination_type not in candidate_embed_combination_types: |
| raise ValueError(f"embed_combination_type must be one of {candidate_embed_combination_types} but received {embed_combination_type}!") |
| self.embed_combination_type=embed_combination_type |
| |
| |
| self.map_noise = SinusoidalPosEmb(td_emb_dim) |
| |
| self.t_emb = nn.Sequential( |
| nn.Linear(2 * td_emb_dim, td_emb_dim), |
| self.time_embed_activation, |
| nn.Linear(td_emb_dim, td_emb_dim) |
| ) |
| |
| |
| if cond_mlp_dims: |
| self.cond_emb = MLP( |
| [cond_dim] + cond_mlp_dims, |
| activation_type=activation_type, |
| out_activation_type="Identity", |
| ) |
| self.cond_enc_dim = cond_mlp_dims[-1] |
| else: |
| self.cond_enc_dim = cond_dim |
| if embed_combination_type in ['add', 'multiply'] and td_emb_dim !=self.cond_enc_dim: |
| raise ValueError(f"To add or multiply td_embed with cond_embed you must make td_emb_dim={td_emb_dim} == self.cond_enc_dim={self.cond_enc_dim}") |
| |
| |
| |
| model = ResidualMLP if residual_style else MLP |
| if self.embed_combination_type =='concate': |
| input_dim = action_dim * horizon_steps + self.cond_enc_dim + td_emb_dim |
| elif self.embed_combination_type =='add' or 'multiply': |
| input_dim = action_dim * horizon_steps + td_emb_dim |
| else: |
| raise ValueError(f"Unsupported embed_combination_type={self.embed_combination_type}") |
| self.vel_head = model( |
| [input_dim] + mlp_dims + [self.act_dim_total], |
| activation_type=activation_type, |
| out_activation_type=out_activation_type, |
| use_layernorm=use_layernorm, |
| ) |
|
|
| def forward( |
| self, |
| action: Tensor, |
| time: Tensor, |
| dt: Tensor, |
| cond: dict, |
| output_embedding=False |
| ): |
| """ |
| Inputs: |
| action: (B, Ta, Da) - Current action trajectory |
| time: (B,) - Current noise level t |
| cond: (B, Do) - Condition (e.g., flattened state) |
| dt: (B,) - Step size |
| |
| Outputs: |
| velocity: (B, Ta, Da) - Predicted velocity |
| """ |
| B, Ta, Da = action.shape |
|
|
| |
| action_flat = action.view(B, -1) |
| |
| |
| t_emb = self.map_noise(time.view(B, 1)).view(B, self.td_emb_dim) |
| dt_emb = self.map_noise(dt.view(B, 1)).view(B, self.td_emb_dim) |
| td_emb = self.t_emb(torch.cat([t_emb, dt_emb], dim=1)) |
|
|
| |
| state = cond["state"].view(B, -1) |
| cond_emb = self.cond_emb(state) if hasattr(self, "cond_emb") else state |
| |
| if self.embed_combination_type=='add': |
| emb = td_emb + cond_emb |
| elif self.embed_combination_type=='multiply': |
| emb = td_emb * cond_emb |
| elif self.embed_combination_type =='concate': |
| emb=torch.cat([td_emb, cond_emb], dim=-1) |
| |
| vel_flat = self.vel_head(torch.cat([action_flat, emb], dim=-1)) |
| if output_embedding: |
| return vel_flat.view(B, Ta, Da), td_emb, cond_emb |
| return vel_flat.view(B, Ta, Da) |
| |
| def sample_action(self,cond:dict,inference_steps:int,clip_intermediate_actions:bool,act_range:List[float], z:Tensor=None,save_chains:bool=False): |
| """ |
| simply return action via integration (Euler's method). the initial noise could be specified. |
| when `save_chains` is True, also return the denoising trajectory. |
| """ |
| B = cond['state'].shape[0] |
| device=cond['state'].device |
|
|
| x_hat:Tensor=z if z is not None else torch.randn(B, self.horizon_steps, self.action_dim, device=device) |
| if save_chains: |
| x_chain=torch.zeros((B, inference_steps+1, self.horizon_steps, self.action_dim), device=device) |
| dt = (1 / inference_steps) * torch.ones_like(x_hat, device=device) |
| steps = torch.linspace(0, 1-1/inference_steps, inference_steps, device=device).repeat(B, 1) |
| for i in range(inference_steps): |
| t = steps[:, i] |
| dt_batch = (1 / inference_steps) * torch.ones(B, device=device) |
| vt = self.forward(action=x_hat, time=t, dt=dt_batch, cond=cond, output_embedding=False) |
| x_hat += vt * dt |
| if clip_intermediate_actions or i == inference_steps-1: |
| x_hat = x_hat.clamp(*act_range) |
| if save_chains: |
| x_chain[:, i+1] = x_hat |
| if save_chains: |
| return x_hat, x_chain |
| return x_hat |
|
|
|
|
| class ShortCutFlowViT(nn.Module): |
| """With ViT backbone and Transformer-based shortcut flow |
| |
| |
| **Architecture**: |
| |
| camera pixels -> aug-> backbone-> visual_feature - | |
| cat->cond_embed->cond_embedding->| |
| proprioception-> prop embedder -> prop_embedding - | | |
| + or * --> cond_td_embedding-| |
| t -> -> t_embedding -> | | |
| map_noise td_embed --> td_embedding->| | |
| step -> -> dt_embedding-> cat --> vel_head --> vel |
| | |
| action --> (omitted) --> act_embedding -| |
| (projection + positional embedding) |
| """ |
| def __init__( |
| self, |
| backbone:VitEncoder, |
| action_dim, |
| horizon_steps, |
| prop_dim, |
| img_cond_steps=1, |
| td_emb_dim=16, |
| |
| |
| |
| mlp_dims=[256,256], |
| cond_mlp_dims=None, |
| activation_type="Mish", |
| out_activation_type="Identity", |
| use_layernorm=False, |
| residual_style=False, |
| dropout=0.0, |
| visual_feature_dim=128, |
| num_img=1, |
| augment=False, |
| spatial_emb=0, |
| embed_combination_type='add' |
| ): |
| super().__init__() |
| |
| |
| self.action_dim = action_dim |
| self.horizon_steps = horizon_steps |
| self.act_dim_total = action_dim * horizon_steps |
| |
| |
| self.prop_dim = prop_dim |
| self.img_cond_steps = img_cond_steps |
| |
| |
| candidate_embed_combination_types = ['add', 'multiply', 'concate'] |
| if embed_combination_type not in candidate_embed_combination_types: |
| raise ValueError(f"embed_combination_type must be one of {candidate_embed_combination_types}, got {embed_combination_type}") |
| self.embed_combination_type = embed_combination_type |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| self.td_emb_dim = td_emb_dim |
| self.map_noise = SinusoidalPosEmb(td_emb_dim) |
| self.time_embed_activation=nn.Mish() |
| self.td_emb = nn.Sequential( |
| nn.Linear(2 * td_emb_dim, td_emb_dim), |
| self.time_embed_activation, |
| nn.Linear(td_emb_dim, td_emb_dim) |
| ) |
| |
| |
| if cond_mlp_dims: |
| self.prop_emb = MLP( |
| [prop_dim] + cond_mlp_dims, |
| activation_type=activation_type, |
| out_activation_type="Identity", |
| ) |
| self.prop_embed_dim = cond_mlp_dims[-1] |
| else: |
| self.prop_embed_dim = prop_dim |
| |
| |
| self.backbone = backbone |
| self.num_img = num_img |
| self.augment = augment |
| if augment: |
| self.aug = RandomShiftsAug(pad=4) |
| |
| if spatial_emb > 0: |
| assert spatial_emb > 1, "spatial_emb must be > 1" |
| if num_img == 2: |
| self.compress1 = SpatialEmb( |
| num_patch=self.backbone.num_patch, |
| patch_dim=self.backbone.patch_repr_dim, |
| prop_dim=prop_dim, |
| proj_dim=spatial_emb, |
| dropout=dropout, |
| ) |
| self.compress2 = deepcopy(self.compress1) |
| elif num_img == 1: |
| self.compress = SpatialEmb( |
| num_patch=self.backbone.num_patch, |
| patch_dim=self.backbone.patch_repr_dim, |
| prop_dim=prop_dim, |
| proj_dim=spatial_emb, |
| dropout=dropout, |
| ) |
| else: |
| raise NotImplementedError(f"num_img={num_img} not supported (only 1 or 2)") |
| self.visual_feature_dim = spatial_emb * num_img |
| else: |
| self.visual_feature_dim = visual_feature_dim |
| self.compress = nn.Sequential( |
| nn.Linear(self.backbone.repr_dim, visual_feature_dim), |
| nn.LayerNorm(visual_feature_dim), |
| nn.Dropout(dropout), |
| nn.ReLU(), |
| ) |
| self.visuomotor_feature_dim = self.visual_feature_dim + self.prop_embed_dim |
| |
| if embed_combination_type in ['add', 'multiply']: |
| |
| self.cond_embed=nn.Sequential( |
| nn.Linear(self.visuomotor_feature_dim, td_emb_dim*2), |
| nn.ReLU(), |
| nn.Linear(td_emb_dim*2, td_emb_dim), |
| ) |
| self.cond_enc_dim=td_emb_dim |
| else: |
| self.cond_enc_dim=self.visuomotor_feature_dim |
| |
| |
| |
| |
| |
| |
|
|
| |
| vel_head_model = ResidualMLP if residual_style else MLP |
| if self.embed_combination_type =='concate': |
| input_dim = action_dim * horizon_steps + self.cond_enc_dim + td_emb_dim |
| elif self.embed_combination_type =='add' or 'multiply': |
| input_dim = action_dim * horizon_steps + self.cond_enc_dim |
| else: |
| raise ValueError(f"Unsupported embed_combination_type={self.embed_combination_type}") |
| output_dim = action_dim * horizon_steps |
| self.vel_head = vel_head_model( |
| [input_dim] + mlp_dims + [output_dim], |
| activation_type=activation_type, |
| out_activation_type=out_activation_type, |
| use_layernorm=use_layernorm, |
| ) |
| |
| def forward( |
| self, |
| action, |
| time, |
| d, |
| cond, |
| output_embedding=False, |
| ): |
| """ |
| Inputs: |
| action: (B, Ta, Da) - Action trajectories |
| time: (B,) or float - Flow time |
| d: (B,) or float - Step size |
| cond: dict with keys 'state' and 'rgb' |
| state: (B, To, Do) - Proprioceptive states |
| rgb: (B, To, C, H, W) - RGB images |
| output_embedding: whether also return td_embedding and condition embedding |
| Outputs: |
| velocity: (B, Ta, Da) - Predicted velocities |
| """ |
| B, Ta, Da = action.shape |
| _, T_rgb, C, H, W = cond["rgb"].shape |
| |
| |
| action_embed = action.view(B, -1) |
| |
| |
| |
| |
| |
| |
| |
| t_emb = self.map_noise(time.view(B, 1)).view(B, self.td_emb_dim) |
| d_emb = self.map_noise(d.view(B, 1)).view(B, self.td_emb_dim) |
| td_emb = self.td_emb(torch.cat([t_emb, d_emb], dim=1)) |
| |
| |
| state = cond["state"].view(B, -1) |
| prop_emb = self.prop_emb(state) if hasattr(self, "prop_emb") else state |
| |
| |
| rgb = cond["rgb"][:, -self.img_cond_steps:] |
| if self.num_img > 1: |
| rgb = rgb.reshape(B, T_rgb, self.num_img, 3, H, W) |
| rgb = einops.rearrange(rgb, "b t n c h w -> b n (t c) h w") |
| elif self.num_img == 1: |
| rgb = einops.rearrange(rgb, "b t c h w -> b (t c) h w") |
| else: |
| raise ValueError(f"self.num_img={self.num_img} < 1") |
| rgb = rgb.float() |
| if self.num_img == 2: |
| rgb1, rgb2 = rgb[:, 0], rgb[:, 1] |
| if self.augment: |
| rgb1 = self.aug(rgb1) |
| rgb2 = self.aug(rgb2) |
| visual_feat1 = self.backbone.forward(rgb1) |
| visual_feat1 = self.compress1.forward(visual_feat1, cond["state"].view(B, -1)) if hasattr(self, 'compress1') else self.compress(visual_feat1.flatten(1, -1)) |
| visual_feat2 = self.backbone.forward(rgb2) |
| visual_feat2 = self.compress2.forward(visual_feat2, cond["state"].view(B, -1)) if hasattr(self, 'compress2') else self.compress(visual_feat2.flatten(1, -1)) |
| visual_feat = torch.cat([visual_feat1, visual_feat2], dim=-1) |
| elif self.num_img == 1: |
| if self.augment: |
| rgb = self.aug(rgb) |
| visual_feat = self.backbone.forward(rgb) |
| if isinstance(self.compress, SpatialEmb): |
| visual_feat = self.compress.forward(visual_feat, cond["state"].view(B, -1)) |
| else: |
| visual_feat = self.compress(visual_feat.flatten(1, -1)) |
| else: |
| raise NotImplementedError(f"num_img={self.num_img} not supported") |
| |
| |
| if self.embed_combination_type == 'add' or 'multiply': |
| cond_emb = self.cond_embed(torch.cat([visual_feat, prop_emb], dim=-1)) |
| else: |
| cond_emb = torch.cat([visual_feat, prop_emb], dim=-1) |
| |
| |
| if self.embed_combination_type == 'add': |
| td_cond_emb = td_emb + cond_emb |
| elif self.embed_combination_type == 'multiply': |
| td_cond_emb = td_emb * cond_emb |
| elif self.embed_combination_type == 'concate': |
| td_cond_emb = torch.cat([td_emb, cond_emb], dim=-1) |
| |
| emd=torch.cat([action_embed, td_cond_emb], dim=-1) |
| |
| |
| |
| |
| |
| velocity = self.vel_head(emd) |
| if output_embedding: |
| return velocity.view(B, Ta, Da), td_emb, cond_emb |
| return velocity.view(B, Ta, Da) |
| |
| def sample_action(self,cond:dict,inference_steps:int,clip_intermediate_actions:bool,act_range:List[float], z:Tensor=None,save_chains:bool=False): |
| """ |
| simply return action via integration (Euler's method). the initial noise could be specified. |
| when `save_chains` is True, also return the denoising trajectory. |
| """ |
| B = cond['state'].shape[0] |
| device=cond['state'].device |
|
|
| x_hat:Tensor=z if z is not None else torch.randn(B, self.horizon_steps, self.action_dim, device=device) |
| if save_chains: |
| x_chain=torch.zeros((B, inference_steps+1, self.horizon_steps, self.action_dim), device=device) |
| dt = (1 / inference_steps) * torch.ones_like(x_hat, device=device) |
| steps = torch.linspace(0, 1-1/inference_steps, inference_steps, device=device).repeat(B, 1) |
| for i in range(inference_steps): |
| t = steps[:, i] |
| dt_batch=(1 / inference_steps)* torch.ones(B, device=device) |
| vt = self.forward(action=x_hat, time=t, dt=dt_batch, cond=cond, output_embedding=False) |
| x_hat += vt * dt |
| if clip_intermediate_actions or i == inference_steps-1: |
| x_hat = x_hat.clamp(*act_range) |
| if save_chains: |
| x_chain[:, i+1] = x_hat |
| if save_chains: |
| return x_hat, x_chain |
| return x_hat |
|
|
|
|
| class NoisyShortCutFlowMLP(NoisyFlowMLP): |
| def __init__( |
| self, |
| policy:ShortCutFlowMLP, |
| denoising_steps:int, |
| learn_explore_noise_from:int, |
| inital_noise_scheduler_type:str, |
| min_logprob_denoising_std:float, |
| max_logprob_denoising_std:float, |
| learn_explore_time_embedding:bool, |
| time_dim_explore:int, |
| use_time_independent_noise:bool, |
| device, |
| noise_hidden_dims=None, |
| activation_type='Tanh', |
| ): |
| super().__init__( |
| policy, |
| denoising_steps, |
| learn_explore_noise_from, |
| inital_noise_scheduler_type, |
| min_logprob_denoising_std, |
| max_logprob_denoising_std, |
| learn_explore_time_embedding, |
| time_dim_explore, |
| use_time_independent_noise, |
| device, |
| noise_hidden_dims, |
| activation_type |
| ) |
| self.policy:ShortCutFlowMLP |
| |
| |
| def init_exploration_noise_net(self): |
| if self.use_time_independent_noise: |
| |
| |
| noise_input_dim = self.policy.cond_enc_dim |
| |
| if not self.noise_hidden_dims: |
| self.noise_hidden_dims = [16] |
| else: |
| if self.learn_explore_time_embedding: |
| noise_input_dim = self.time_dim_explore + self.policy.cond_enc_dim |
| self.time_embedding_explore = nn.Embedding(num_embeddings=self.denoising_steps, |
| embedding_dim = self.time_dim_explore, |
| device=self.device) |
| else: |
| |
| |
| noise_input_dim = self.policy.td_emb_dim + self.policy.cond_enc_dim |
| |
| if not self.noise_hidden_dims: |
| self.noise_hidden_dims = [int(np.sqrt(noise_input_dim**2 + self.policy.act_dim_total**2))] |
| |
| self.explore_noise_net=ExploreNoiseNet(in_dim=noise_input_dim, |
| out_dim=self.policy.act_dim_total, |
| logprob_denoising_std_range=[self.min_logprob_denoising_std, self.max_logprob_denoising_std], |
| device=self.device, |
| hidden_dims=self.noise_hidden_dims, |
| activation_type=self.noise_activation_type) |
|
|
| |
| def forward( |
| self, |
| action, |
| time, |
| dt, |
| cond, |
| learn_exploration_noise=False, |
| step=-1, |
| verbose=False, |
| **kwargs, |
| )->Tuple[Tensor, Tensor]: |
| """ |
| inputs: |
| x: (B, Ta, Da) |
| time: (B,) floating point in {0,1/2,1/4,1/8,...1/2^n} shortcut flow time |
| cond: dict with key state/rgb; more recent obs at the end |
| state: (B, To, Do) |
| step: (B,) torch.tensor, optional, flow matching denoising step, from 0 to denoising_steps-1 |
| *here, B is the n_envs |
| outputs: |
| vel [B, Ta, Da] |
| noise_std [B, Ta x Da] |
| """ |
| B = action.shape[0] |
| |
| vel, td_emb, cond_emb = self.policy.forward(action, time, dt, cond, output_embedding=True) |
| |
| |
| if self.initial_noise_scheduler_type=='const' or step < self.learn_explore_noise_from: |
| noise_std = self.logprob_noise_levels[:, step].repeat(B,1) |
| else: |
| if self.use_time_independent_noise: |
| noise_feature = cond_emb |
| else: |
| if self.learn_explore_time_embedding: |
| step_ts = torch.tensor(step, device = self.device).repeat(B) |
| time_emb_explore = self.time_embedding_explore(step_ts) |
| noise_feature = torch.cat([time_emb_explore, cond_emb], dim=-1) |
| else: |
| noise_feature = torch.cat([td_emb.detach(), cond_emb], dim=-1) |
| |
| noise_std = self.explore_noise_net.forward(noise_feature=noise_feature) |
| |
| if verbose: |
| log.info(f"step={step}, learnable noise = {noise_std.mean()}") |
| if verbose: |
| log.info(f"step={step}, set to learn from {self.learn_explore_noise_from}, will learn exploration noise ? {step >= self.learn_explore_noise_from}, noise_std={noise_std.mean()}require_grad={noise_std.requires_grad}") |
| |
| return vel, noise_std if learn_exploration_noise else noise_std.detach() |
|
|
| class NoisyVisionShortCutFlowMLP(NoisyShortCutFlowMLP): |
| def __init__( |
| self, |
| policy:ShortCutFlowViT, |
| denoising_steps:int, |
| learn_explore_noise_from:int, |
| inital_noise_scheduler_type:str, |
| min_logprob_denoising_std:float, |
| max_logprob_denoising_std:float, |
| learn_explore_time_embedding:bool, |
| time_dim_explore:int, |
| use_time_independent_noise:bool, |
| device, |
| noise_hidden_dims=None, |
| activation_type='Tanh', |
| ): |
| super().__init__( |
| policy, |
| denoising_steps, |
| learn_explore_noise_from, |
| inital_noise_scheduler_type, |
| min_logprob_denoising_std, |
| max_logprob_denoising_std, |
| learn_explore_time_embedding, |
| time_dim_explore, |
| use_time_independent_noise, |
| device, |
| noise_hidden_dims, |
| activation_type, |
| ) |
| self.policy:ShortCutFlowViT |
| |
| def forward( |
| self, |
| action, |
| time, |
| cond, |
| learn_exploration_noise=False, |
| step=-1, |
| verbose=False, |
| **kwargs, |
| )->Tuple[Tensor, Tensor]: |
| """ |
| inputs: |
| x: (B, Ta, Da) |
| time: (B,) floating point in {0,1/2,1/4,1/8,...1/2^n} shortcut flow time |
| cond: dict with key state/rgb; more recent obs at the end |
| state: (B, To, Do) |
| step: (B,) torch.tensor, optional, flow matching inference step, from 0 to denoising_steps-1 |
| *here, B is the n_envs |
| outputs: |
| vel [B, Ta, Da] |
| noise_std [B, Ta x Da] |
| """ |
| B = action.shape[0] |
| |
| dt = torch.full((B,), 1.0 / self.denoising_stepss, device=self.device) |
| |
| vel, td_emb, cond_emb = self.policy.forward(action, time, dt, cond, output_embedding=True) |
| |
| |
| if self.initial_noise_scheduler_type=='const' or step < self.learn_explore_noise_from: |
| noise_std = self.logprob_noise_levels[:, step].repeat(B,1) |
| else: |
| if self.use_time_independent_noise: |
| noise_feature = cond_emb |
| else: |
| if self.learn_explore_time_embedding: |
| step_ts = torch.tensor(step, device = self.device).repeat(B) |
| time_emb_explore = self.time_embedding_explore(step_ts) |
| noise_feature = torch.cat([time_emb_explore, cond_emb], dim=-1) |
| else: |
| noise_feature = torch.cat([td_emb.detach(), cond_emb], dim=-1) |
| noise_std = self.explore_noise_net.forward(noise_feature=noise_feature) |
| return vel, noise_std if learn_exploration_noise else noise_std.detach() |
|
|