2025-09-07 14:59:17 +08:00
|
|
|
import torch
|
2026-02-03 11:35:25 +08:00
|
|
|
|
2025-09-07 14:59:17 +08:00
|
|
|
import torch.nn as nn
|
2026-02-03 11:35:25 +08:00
|
|
|
|
|
|
|
|
from typing import Union
|
|
|
|
|
import math
|
|
|
|
|
|
|
|
|
|
from diffusers.schedulers.scheduling_ddpm import DDPMScheduler
|
2025-09-07 14:59:17 +08:00
|
|
|
from torch.distributions import Beta
|
2026-02-03 11:35:25 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def print_rank_last(message):
|
|
|
|
|
"""If distributed is initialized, print only on last rank."""
|
|
|
|
|
if torch.distributed.is_initialized():
|
|
|
|
|
if torch.distributed.get_rank() == (torch.distributed.get_world_size() - 1):
|
|
|
|
|
print(message, flush=True)
|
|
|
|
|
else:
|
|
|
|
|
print(message, flush=True)
|
2025-09-07 14:59:17 +08:00
|
|
|
|
2025-09-11 13:18:33 +08:00
|
|
|
|
2025-09-07 14:59:17 +08:00
|
|
|
class Normalizer(nn.Module):
|
2026-02-03 11:35:25 +08:00
|
|
|
@classmethod
|
|
|
|
|
def from_ckpt(cls, ckpt_path):
|
|
|
|
|
instance = cls.__new__(cls)
|
|
|
|
|
nn.Module.__init__(instance)
|
2025-09-11 13:18:33 +08:00
|
|
|
|
2026-02-03 11:35:25 +08:00
|
|
|
instance.min = nn.ParameterDict()
|
|
|
|
|
instance.delta = nn.ParameterDict()
|
|
|
|
|
instance.min_key = "min"
|
|
|
|
|
instance.delta_key = "delta"
|
2025-09-11 13:18:33 +08:00
|
|
|
|
2026-02-03 11:35:25 +08:00
|
|
|
ckpt = torch.load(ckpt_path, map_location="cpu")
|
2025-10-24 17:29:12 +08:00
|
|
|
|
2026-02-03 11:35:25 +08:00
|
|
|
for key, value in ckpt.items():
|
|
|
|
|
# Parse key: "min.robot_name" -> prefix="min", name="robot_name"
|
|
|
|
|
try:
|
|
|
|
|
prefix, name = key.split(".", 1)
|
|
|
|
|
if hasattr(instance, prefix):
|
|
|
|
|
getattr(instance, prefix)[name] = nn.Parameter(
|
|
|
|
|
value, requires_grad=False
|
|
|
|
|
)
|
|
|
|
|
print("prefix", prefix)
|
|
|
|
|
print("name", name)
|
|
|
|
|
except ValueError:
|
|
|
|
|
continue
|
2025-09-11 13:18:33 +08:00
|
|
|
|
2026-02-03 11:35:25 +08:00
|
|
|
return instance
|
|
|
|
|
|
|
|
|
|
def __init__(
|
|
|
|
|
self, action_statistic_dof, dof_config, min_key="min", delta_key="delta"
|
|
|
|
|
):
|
2025-09-07 14:59:17 +08:00
|
|
|
super(Normalizer, self).__init__()
|
|
|
|
|
|
2026-02-03 11:35:25 +08:00
|
|
|
self.min_key = min_key
|
|
|
|
|
self.delta_key = delta_key
|
2025-09-11 13:18:33 +08:00
|
|
|
|
2026-02-03 11:35:25 +08:00
|
|
|
action_statistic = {}
|
2025-09-07 14:59:17 +08:00
|
|
|
for robot_name in action_statistic_dof.keys():
|
|
|
|
|
action_statistic[robot_name] = {}
|
|
|
|
|
all_dof_min = []
|
|
|
|
|
all_dof_delta = []
|
|
|
|
|
for k in dof_config:
|
|
|
|
|
if k in action_statistic_dof[robot_name]:
|
2026-02-03 11:35:25 +08:00
|
|
|
if (
|
|
|
|
|
min_key in action_statistic_dof[robot_name][k]
|
|
|
|
|
and delta_key in action_statistic_dof[robot_name][k]
|
|
|
|
|
):
|
|
|
|
|
all_dof_min.extend(action_statistic_dof[robot_name][k][min_key])
|
|
|
|
|
all_dof_delta.extend(
|
|
|
|
|
action_statistic_dof[robot_name][k][delta_key]
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
if robot_name == "x2_normal" or "libero" in robot_name:
|
|
|
|
|
print_rank_last(
|
|
|
|
|
f"Normalizer (Warning): min_key {min_key} or delta_key {delta_key} "
|
|
|
|
|
)
|
|
|
|
|
print_rank_last(
|
|
|
|
|
f"not in action_statistic_dof[{robot_name}][{k}], use default min 0.0 and delta 1.0"
|
|
|
|
|
)
|
|
|
|
|
all_dof_min.extend([0.0] * dof_config[k])
|
|
|
|
|
all_dof_delta.extend([1.0] * dof_config[k])
|
2025-09-07 14:59:17 +08:00
|
|
|
else:
|
2026-02-03 11:35:25 +08:00
|
|
|
if robot_name == "x2_normal" or "libero" in robot_name:
|
|
|
|
|
print_rank_last(
|
|
|
|
|
f"Normalizer (Warning): Action {k} not in action_statistic_dof for {robot_name}, use default min 0.0 and delta 1.0"
|
|
|
|
|
)
|
2025-09-07 14:59:17 +08:00
|
|
|
all_dof_min.extend([0.0] * dof_config[k])
|
|
|
|
|
all_dof_delta.extend([1.0] * dof_config[k])
|
2026-02-03 11:35:25 +08:00
|
|
|
all_dof_min = torch.tensor(all_dof_min)
|
|
|
|
|
all_dof_delta = torch.tensor(all_dof_delta)
|
|
|
|
|
action_statistic[robot_name][min_key] = all_dof_min
|
|
|
|
|
action_statistic[robot_name][delta_key] = all_dof_delta
|
2025-09-11 13:18:33 +08:00
|
|
|
|
|
|
|
|
self.min = nn.ParameterDict(
|
|
|
|
|
{
|
2026-02-03 11:35:25 +08:00
|
|
|
k: nn.Parameter(action_statistic[k][min_key], requires_grad=False)
|
2025-09-11 13:18:33 +08:00
|
|
|
for k in action_statistic.keys()
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
self.delta = nn.ParameterDict(
|
|
|
|
|
{
|
2026-02-03 11:35:25 +08:00
|
|
|
k: nn.Parameter(action_statistic[k][delta_key], requires_grad=False)
|
2025-09-11 13:18:33 +08:00
|
|
|
for k in action_statistic.keys()
|
|
|
|
|
}
|
|
|
|
|
)
|
2025-09-07 14:59:17 +08:00
|
|
|
|
2026-02-03 11:35:25 +08:00
|
|
|
for k, v in action_statistic.items():
|
|
|
|
|
print_rank_last(
|
|
|
|
|
f"Normalizer: {k} min {action_statistic[k][min_key]} delta {action_statistic[k][delta_key]}"
|
|
|
|
|
)
|
2025-09-11 13:18:33 +08:00
|
|
|
|
2026-02-03 11:35:25 +08:00
|
|
|
def normalize_data(self, xs, dataset_names):
|
2025-09-07 14:59:17 +08:00
|
|
|
new_xs = []
|
|
|
|
|
dataset_names = [name for name in dataset_names if name != "x2_multimodal"]
|
2026-02-03 11:35:25 +08:00
|
|
|
for x, dataset_name in zip(xs, dataset_names):
|
|
|
|
|
x = (x - self.min[dataset_name]) / (self.delta[dataset_name])
|
2025-09-07 14:59:17 +08:00
|
|
|
x = x * 2 - 1
|
|
|
|
|
x = torch.clamp(x, -1, 1)
|
|
|
|
|
new_xs.append(x)
|
|
|
|
|
new_xs = torch.stack(new_xs)
|
|
|
|
|
return new_xs
|
|
|
|
|
|
|
|
|
|
def unnormalize_data(self, xs, dataset_names, dof_mask=None):
|
|
|
|
|
new_xs = []
|
|
|
|
|
dataset_names = [name for name in dataset_names if name != "x2_multimodal"]
|
|
|
|
|
dof_mask = dof_mask if dof_mask is not None else [None] * len(xs)
|
|
|
|
|
for x, dataset_name, mask in zip(xs, dataset_names, dof_mask):
|
|
|
|
|
x = (x + 1) / 2
|
|
|
|
|
if mask is not None:
|
|
|
|
|
mask = mask[0].bool()
|
|
|
|
|
action_space_delta = self.delta[dataset_name][mask]
|
|
|
|
|
action_space_min = self.min[dataset_name][mask]
|
|
|
|
|
else:
|
|
|
|
|
action_space_delta = self.delta[dataset_name]
|
|
|
|
|
action_space_min = self.min[dataset_name]
|
|
|
|
|
x = x * action_space_delta + action_space_min
|
|
|
|
|
new_xs.append(x)
|
|
|
|
|
new_xs = torch.stack(new_xs)
|
|
|
|
|
return new_xs
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SinusoidalPosEmb(nn.Module):
|
2026-02-03 11:35:25 +08:00
|
|
|
def __init__(self, dim: int, min_period: float = 4e-3, max_period: float = 4.0):
|
2025-09-07 14:59:17 +08:00
|
|
|
super().__init__()
|
|
|
|
|
self.dim = dim
|
2026-02-03 11:35:25 +08:00
|
|
|
if dim % 2 != 0:
|
|
|
|
|
raise ValueError(f"embedding_dim ({dim}) must be divisible by 2")
|
|
|
|
|
self.min_period = min_period
|
|
|
|
|
self.max_period = max_period
|
2025-09-07 14:59:17 +08:00
|
|
|
|
|
|
|
|
def forward(self, x):
|
|
|
|
|
device = x.device
|
|
|
|
|
half_dim = self.dim // 2
|
|
|
|
|
emb = math.log(10000) / (half_dim - 1)
|
2026-02-03 11:35:25 +08:00
|
|
|
emb = torch.exp(
|
|
|
|
|
torch.arange(half_dim, device=device, dtype=torch.float32) * -emb
|
|
|
|
|
)
|
2025-09-07 14:59:17 +08:00
|
|
|
emb = x[:, None] * emb[None, :]
|
|
|
|
|
emb = torch.cat((emb.sin(), emb.cos()), dim=-1)
|
|
|
|
|
return emb
|
|
|
|
|
|
|
|
|
|
|
2026-02-03 11:35:25 +08:00
|
|
|
class Downsample1d(nn.Module):
|
|
|
|
|
def __init__(self, dim):
|
|
|
|
|
super().__init__()
|
|
|
|
|
self.conv = nn.Conv1d(dim, dim, 3, 2, 1)
|
|
|
|
|
|
|
|
|
|
def forward(self, x):
|
|
|
|
|
return self.conv(x)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Upsample1d(nn.Module):
|
|
|
|
|
def __init__(self, dim):
|
|
|
|
|
super().__init__()
|
|
|
|
|
self.conv = nn.ConvTranspose1d(dim, dim, 4, 2, 1)
|
|
|
|
|
|
|
|
|
|
def forward(self, x):
|
|
|
|
|
return self.conv(x)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Conv1dBlock(nn.Module):
|
2025-09-07 14:59:17 +08:00
|
|
|
"""
|
2026-02-03 11:35:25 +08:00
|
|
|
Conv1d --> GroupNorm --> Mish
|
2025-09-07 14:59:17 +08:00
|
|
|
"""
|
2025-09-11 13:18:33 +08:00
|
|
|
|
2026-02-03 11:35:25 +08:00
|
|
|
def __init__(self, inp_channels, out_channels, kernel_size, n_groups=8):
|
2025-09-07 14:59:17 +08:00
|
|
|
super().__init__()
|
2025-09-11 13:18:33 +08:00
|
|
|
|
2026-02-03 11:35:25 +08:00
|
|
|
self.block = nn.Sequential(
|
|
|
|
|
nn.Conv1d(
|
|
|
|
|
inp_channels, out_channels, kernel_size, padding=kernel_size // 2
|
|
|
|
|
),
|
|
|
|
|
nn.GroupNorm(n_groups, out_channels),
|
|
|
|
|
nn.Mish(),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def forward(self, x):
|
|
|
|
|
return self.block(x)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ConditionalResidualBlock1D(nn.Module):
|
|
|
|
|
def __init__(self, in_channels, out_channels, cond_dim, kernel_size=3, n_groups=8):
|
|
|
|
|
super().__init__()
|
|
|
|
|
|
|
|
|
|
self.blocks = nn.ModuleList(
|
|
|
|
|
[
|
|
|
|
|
Conv1dBlock(in_channels, out_channels, kernel_size, n_groups=n_groups),
|
|
|
|
|
Conv1dBlock(out_channels, out_channels, kernel_size, n_groups=n_groups),
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# FiLM modulation https://arxiv.org/abs/1709.07871
|
|
|
|
|
# predicts per-channel scale and bias
|
|
|
|
|
cond_channels = out_channels * 2
|
|
|
|
|
self.out_channels = out_channels
|
|
|
|
|
self.cond_encoder = nn.Sequential(
|
|
|
|
|
nn.Mish(),
|
|
|
|
|
nn.Linear(cond_dim, cond_channels),
|
|
|
|
|
nn.Dropout(0.1),
|
|
|
|
|
nn.Unflatten(-1, (-1, 1)),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# make sure dimensions compatible
|
|
|
|
|
self.residual_conv = (
|
|
|
|
|
nn.Conv1d(in_channels, out_channels, 1)
|
|
|
|
|
if in_channels != out_channels
|
|
|
|
|
else nn.Identity()
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def forward(self, x, cond):
|
|
|
|
|
"""
|
|
|
|
|
x : [ batch_size x in_channels x horizon ]
|
|
|
|
|
cond : [ batch_size x cond_dim]
|
|
|
|
|
|
|
|
|
|
returns:
|
|
|
|
|
out : [ batch_size x out_channels x horizon ]
|
|
|
|
|
"""
|
|
|
|
|
out = self.blocks[0](x)
|
|
|
|
|
embed = self.cond_encoder(cond)
|
|
|
|
|
|
|
|
|
|
embed = embed.reshape(embed.shape[0], 2, self.out_channels, 1)
|
|
|
|
|
scale = embed[:, 0, ...]
|
|
|
|
|
bias = embed[:, 1, ...]
|
|
|
|
|
out = scale * out + bias
|
|
|
|
|
|
|
|
|
|
out = self.blocks[1](out)
|
|
|
|
|
out = out + self.residual_conv(x)
|
|
|
|
|
return out
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ConditionalUnet1D(nn.Module):
|
|
|
|
|
def __init__(
|
|
|
|
|
self,
|
|
|
|
|
input_dim,
|
|
|
|
|
global_cond_dim,
|
|
|
|
|
diffusion_step_embed_dim=256,
|
|
|
|
|
down_dims=[256, 512, 1024],
|
|
|
|
|
# down_dims=[512, 1024, 2048],
|
|
|
|
|
kernel_size=5,
|
|
|
|
|
n_groups=8,
|
|
|
|
|
):
|
|
|
|
|
"""
|
|
|
|
|
input_dim: Dim of actions.
|
|
|
|
|
global_cond_dim: Dim of global conditioning applied with FiLM
|
|
|
|
|
in addition to diffusion step embedding. This is usually obs_horizon * obs_dim
|
|
|
|
|
diffusion_step_embed_dim: Size of positional encoding for diffusion iteration k
|
|
|
|
|
down_dims: Channel size for each UNet level.
|
|
|
|
|
The length of this array determines numebr of levels.
|
|
|
|
|
kernel_size: Conv kernel size
|
|
|
|
|
n_groups: Number of groups for GroupNorm
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
super().__init__()
|
|
|
|
|
all_dims = [input_dim] + list(down_dims)
|
|
|
|
|
start_dim = down_dims[0]
|
|
|
|
|
|
|
|
|
|
dsed = diffusion_step_embed_dim
|
|
|
|
|
diffusion_step_encoder = nn.Sequential(
|
|
|
|
|
SinusoidalPosEmb(dsed),
|
|
|
|
|
nn.Linear(dsed, dsed * 4),
|
|
|
|
|
nn.Mish(),
|
|
|
|
|
nn.Linear(dsed * 4, dsed),
|
|
|
|
|
)
|
|
|
|
|
cond_dim = dsed + global_cond_dim
|
|
|
|
|
|
|
|
|
|
in_out = list(zip(all_dims[:-1], all_dims[1:]))
|
|
|
|
|
mid_dim = all_dims[-1]
|
|
|
|
|
self.mid_modules = nn.ModuleList(
|
|
|
|
|
[
|
|
|
|
|
ConditionalResidualBlock1D(
|
|
|
|
|
mid_dim,
|
|
|
|
|
mid_dim,
|
|
|
|
|
cond_dim=cond_dim,
|
|
|
|
|
kernel_size=kernel_size,
|
|
|
|
|
n_groups=n_groups,
|
|
|
|
|
),
|
|
|
|
|
ConditionalResidualBlock1D(
|
|
|
|
|
mid_dim,
|
|
|
|
|
mid_dim,
|
|
|
|
|
cond_dim=cond_dim,
|
|
|
|
|
kernel_size=kernel_size,
|
|
|
|
|
n_groups=n_groups,
|
|
|
|
|
),
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
down_modules = nn.ModuleList([])
|
|
|
|
|
for ind, (dim_in, dim_out) in enumerate(in_out):
|
|
|
|
|
is_last = ind >= (len(in_out) - 1)
|
|
|
|
|
down_modules.append(
|
|
|
|
|
nn.ModuleList(
|
|
|
|
|
[
|
|
|
|
|
ConditionalResidualBlock1D(
|
|
|
|
|
dim_in,
|
|
|
|
|
dim_out,
|
|
|
|
|
cond_dim=cond_dim,
|
|
|
|
|
kernel_size=kernel_size,
|
|
|
|
|
n_groups=n_groups,
|
|
|
|
|
),
|
|
|
|
|
ConditionalResidualBlock1D(
|
|
|
|
|
dim_out,
|
|
|
|
|
dim_out,
|
|
|
|
|
cond_dim=cond_dim,
|
|
|
|
|
kernel_size=kernel_size,
|
|
|
|
|
n_groups=n_groups,
|
|
|
|
|
),
|
|
|
|
|
Downsample1d(dim_out) if not is_last else nn.Identity(),
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
up_modules = nn.ModuleList([])
|
|
|
|
|
for ind, (dim_in, dim_out) in enumerate(reversed(in_out[1:])):
|
|
|
|
|
is_last = ind >= (len(in_out) - 1)
|
|
|
|
|
up_modules.append(
|
|
|
|
|
nn.ModuleList(
|
|
|
|
|
[
|
|
|
|
|
ConditionalResidualBlock1D(
|
|
|
|
|
dim_out * 2,
|
|
|
|
|
dim_in,
|
|
|
|
|
cond_dim=cond_dim,
|
|
|
|
|
kernel_size=kernel_size,
|
|
|
|
|
n_groups=n_groups,
|
|
|
|
|
),
|
|
|
|
|
ConditionalResidualBlock1D(
|
|
|
|
|
dim_in,
|
|
|
|
|
dim_in,
|
|
|
|
|
cond_dim=cond_dim,
|
|
|
|
|
kernel_size=kernel_size,
|
|
|
|
|
n_groups=n_groups,
|
|
|
|
|
),
|
|
|
|
|
Upsample1d(dim_in) if not is_last else nn.Identity(),
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
final_conv = nn.Sequential(
|
|
|
|
|
Conv1dBlock(start_dim, start_dim, kernel_size=kernel_size),
|
|
|
|
|
nn.Conv1d(start_dim, input_dim, 1),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
self.diffusion_step_encoder = diffusion_step_encoder
|
|
|
|
|
self.up_modules = up_modules
|
|
|
|
|
self.down_modules = down_modules
|
|
|
|
|
self.final_conv = final_conv
|
|
|
|
|
|
|
|
|
|
# print("number of parameters: {:e}".format(
|
|
|
|
|
# sum(p.numel() for p in self.parameters()))
|
|
|
|
|
# )
|
|
|
|
|
|
|
|
|
|
def forward(
|
|
|
|
|
self,
|
|
|
|
|
sample: torch.Tensor,
|
|
|
|
|
timestep: Union[torch.Tensor, float, int],
|
|
|
|
|
global_cond=None,
|
|
|
|
|
):
|
|
|
|
|
"""
|
|
|
|
|
x: (B,T,input_dim)
|
|
|
|
|
timestep: (B,) or int, diffusion step
|
|
|
|
|
global_cond: (B,global_cond_dim)
|
|
|
|
|
output: (B,T,input_dim)
|
|
|
|
|
"""
|
|
|
|
|
# (B,T,C)
|
|
|
|
|
sample = sample.moveaxis(-1, -2)
|
|
|
|
|
# (B,C,T)
|
|
|
|
|
|
|
|
|
|
# 1. time
|
|
|
|
|
timesteps = timestep
|
|
|
|
|
if not torch.is_tensor(timesteps):
|
|
|
|
|
# TODO: this requires sync between CPU and GPU. So try to pass timesteps as tensors if you can
|
|
|
|
|
timesteps = torch.tensor(
|
|
|
|
|
[timesteps], dtype=torch.long, device=sample.device
|
|
|
|
|
)
|
|
|
|
|
elif torch.is_tensor(timesteps) and len(timesteps.shape) == 0:
|
|
|
|
|
timesteps = timesteps[None].to(sample.device)
|
|
|
|
|
# broadcast to batch dimension in a way that's compatible with ONNX/Core ML
|
|
|
|
|
timesteps = timesteps.expand(sample.shape[0])
|
|
|
|
|
|
|
|
|
|
global_feature = self.diffusion_step_encoder(timesteps)
|
|
|
|
|
|
|
|
|
|
if global_cond is not None:
|
|
|
|
|
global_feature = torch.cat([global_feature, global_cond], axis=-1)
|
|
|
|
|
x = sample
|
|
|
|
|
h = []
|
|
|
|
|
for idx, (resnet, resnet2, downsample) in enumerate(self.down_modules):
|
|
|
|
|
x = resnet(x, global_feature)
|
|
|
|
|
x = resnet2(x, global_feature)
|
|
|
|
|
h.append(x)
|
|
|
|
|
x = downsample(x) # bs, 2048, 5
|
|
|
|
|
|
|
|
|
|
for mid_module in self.mid_modules:
|
|
|
|
|
x = mid_module(x, global_feature) # bs, 2048, 5
|
|
|
|
|
|
|
|
|
|
for idx, (resnet, resnet2, upsample) in enumerate(self.up_modules):
|
|
|
|
|
x = torch.cat((x, h.pop()), dim=1)
|
|
|
|
|
x = resnet(x, global_feature)
|
|
|
|
|
x = resnet2(x, global_feature)
|
|
|
|
|
x = upsample(x) # bs, 512, 20
|
|
|
|
|
|
|
|
|
|
x = self.final_conv(x) # bs, 14, 20
|
|
|
|
|
|
|
|
|
|
# (B,C,T)
|
|
|
|
|
x = x.moveaxis(-1, -2)
|
|
|
|
|
# (B,T,C)
|
|
|
|
|
return x
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class DP_Action_head(nn.Module):
|
|
|
|
|
def __init__(
|
|
|
|
|
self,
|
|
|
|
|
action_dim=14,
|
|
|
|
|
transformer_dim=896,
|
|
|
|
|
global_cond_dim=1806,
|
|
|
|
|
load_pretrained=True,
|
|
|
|
|
):
|
|
|
|
|
super().__init__()
|
|
|
|
|
self.action_dim = action_dim
|
|
|
|
|
self.transformer_dim = transformer_dim
|
|
|
|
|
self.load_pretrained = load_pretrained
|
|
|
|
|
self.noise_scheduler = DDPMScheduler(
|
|
|
|
|
num_train_timesteps=132,
|
|
|
|
|
beta_schedule="squaredcos_cap_v2",
|
|
|
|
|
clip_sample=True,
|
|
|
|
|
prediction_type="epsilon",
|
|
|
|
|
)
|
|
|
|
|
if self.load_pretrained:
|
|
|
|
|
self.global_cond_dim = global_cond_dim
|
|
|
|
|
self.condition_proj = nn.Sequential(
|
|
|
|
|
nn.Linear(self.transformer_dim, 2 * self.transformer_dim),
|
|
|
|
|
nn.ReLU(),
|
|
|
|
|
nn.Linear(2 * self.transformer_dim, 2 * self.global_cond_dim),
|
|
|
|
|
nn.ReLU(),
|
|
|
|
|
nn.Linear(2 * self.global_cond_dim, self.global_cond_dim),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
self.noise_pred_net = ConditionalUnet1D(
|
|
|
|
|
input_dim=self.action_dim,
|
|
|
|
|
# down_dims=[256,512,1024],
|
|
|
|
|
down_dims=[512, 1024, 2048],
|
|
|
|
|
global_cond_dim=self.global_cond_dim,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# load pretrained model
|
|
|
|
|
action_pretrained_path = "/x2robot/liangyuxin/workspace/DiffusionPolicy/big_mix_0718_mn/30_noise_pred_net.pth"
|
|
|
|
|
print("load noise_pred_net from:", action_pretrained_path, flush=True)
|
|
|
|
|
self.noise_pred_net.load_state_dict(torch.load(action_pretrained_path))
|
|
|
|
|
else:
|
|
|
|
|
self.noise_pred_net = ConditionalUnet1D(
|
|
|
|
|
input_dim=self.action_dim,
|
|
|
|
|
down_dims=[256, 512, 1024],
|
|
|
|
|
global_cond_dim=self.transformer_dim,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def forward(self, naction, condition, sample_times):
|
|
|
|
|
bs = naction.shape[0]
|
|
|
|
|
noise_shape = (
|
|
|
|
|
naction.shape[0] * sample_times,
|
|
|
|
|
naction.shape[1],
|
|
|
|
|
naction.shape[2],
|
|
|
|
|
)
|
|
|
|
|
noise = torch.randn(noise_shape, device=naction.device)
|
|
|
|
|
naction = (
|
|
|
|
|
naction.unsqueeze(1)
|
|
|
|
|
.repeat(1, sample_times, 1, 1)
|
|
|
|
|
.reshape(bs * sample_times, naction.shape[1], naction.shape[2])
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
timesteps = torch.randint(
|
|
|
|
|
0,
|
|
|
|
|
self.noise_scheduler.config.num_train_timesteps,
|
|
|
|
|
(bs * sample_times,),
|
|
|
|
|
device=naction.device,
|
|
|
|
|
).long()
|
|
|
|
|
condition = condition.to(self.condition_proj[0].weight.data.dtype)
|
|
|
|
|
if self.load_pretrained:
|
|
|
|
|
condition = self.condition_proj(condition)
|
|
|
|
|
|
|
|
|
|
noisy_actions = self.noise_scheduler.add_noise(naction, noise, timesteps)
|
|
|
|
|
noise_pred = self.noise_pred_net(
|
|
|
|
|
noisy_actions, timesteps, global_cond=condition
|
|
|
|
|
)
|
|
|
|
|
return noise, noise_pred
|
|
|
|
|
|
|
|
|
|
@torch.no_grad()
|
|
|
|
|
def predict(self, condition, naction=None):
|
|
|
|
|
bs = condition.shape[0]
|
|
|
|
|
condition = condition.to(self.condition_proj[0].weight.data.dtype)
|
|
|
|
|
if self.load_pretrained:
|
|
|
|
|
condition = self.condition_proj(condition)
|
|
|
|
|
|
|
|
|
|
if naction is not None:
|
|
|
|
|
noise_shape = (naction.shape[0], naction.shape[1], naction.shape[2])
|
|
|
|
|
else:
|
|
|
|
|
noise_shape = (bs, 16, self.action_dim) # tobe parameterized
|
|
|
|
|
noise = torch.randn(noise_shape, device=condition.device)
|
|
|
|
|
naction_pred = noise
|
|
|
|
|
# init scheduler
|
|
|
|
|
self.noise_scheduler.set_timesteps(
|
|
|
|
|
self.noise_scheduler.config.num_train_timesteps
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
for k in self.noise_scheduler.timesteps:
|
|
|
|
|
# predict noise
|
|
|
|
|
noise_pred = self.noise_pred_net(
|
|
|
|
|
sample=naction_pred, timestep=k, global_cond=condition
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# inverse diffusion step (remove noise)
|
|
|
|
|
naction_pred = self.noise_scheduler.step(
|
|
|
|
|
model_output=noise_pred, timestep=k, sample=naction_pred
|
|
|
|
|
).prev_sample
|
|
|
|
|
|
|
|
|
|
return naction, naction_pred
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ActionProcessor(nn.Module):
|
|
|
|
|
def __init__(self, config):
|
|
|
|
|
super().__init__()
|
|
|
|
|
self.config = config
|
2025-09-07 14:59:17 +08:00
|
|
|
self.dof_config = config.dof_config
|
|
|
|
|
self.agent_pos_config = config.agent_pos_config
|
|
|
|
|
self.action_dim = sum([v for k, v in self.dof_config.items()])
|
|
|
|
|
self.propri_dim = sum([v for k, v in self.agent_pos_config.items()])
|
|
|
|
|
|
2026-02-03 11:35:25 +08:00
|
|
|
print_rank_last(
|
|
|
|
|
f"self.dof_config: {self.dof_config}; action_dim: {self.action_dim}; self.agent_pos_config: {self.agent_pos_config}; propri_dim: {self.propri_dim}"
|
|
|
|
|
)
|
2025-09-11 13:18:33 +08:00
|
|
|
|
2026-02-03 11:35:25 +08:00
|
|
|
self.action_hidden_size = config.action_hidden_size
|
|
|
|
|
self.state_hidden_size = config.state_hidden_size
|
2025-09-07 14:59:17 +08:00
|
|
|
self.hidden_size = config.hidden_size
|
|
|
|
|
|
2026-02-03 11:35:25 +08:00
|
|
|
if not self.config.use_state_string_representation:
|
|
|
|
|
if self.config.proj_with_mask:
|
|
|
|
|
self.propri_proj = nn.Linear(
|
|
|
|
|
self.propri_dim * 2, self.state_hidden_size, bias=False
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
self.propri_proj = nn.Linear(
|
|
|
|
|
self.propri_dim, self.state_hidden_size, bias=False
|
|
|
|
|
)
|
2025-09-07 14:59:17 +08:00
|
|
|
|
2026-02-03 11:35:25 +08:00
|
|
|
# noise scheduler configing
|
|
|
|
|
if getattr(self.config, "use_flow_action_expert", True):
|
|
|
|
|
noise_scheduler_config = config.noise_scheduler
|
|
|
|
|
self.beta_alpha = noise_scheduler_config.get("beta_alpha", 1.5)
|
|
|
|
|
self.beta_beta = noise_scheduler_config.get("beta_beta", 1.0)
|
|
|
|
|
self.s = noise_scheduler_config.get("s", 0.999)
|
|
|
|
|
alpha_tensor = torch.tensor(self.beta_alpha, dtype=torch.float32).to("cuda")
|
|
|
|
|
beta_tensor = torch.tensor(self.beta_beta, dtype=torch.float32).to("cuda")
|
|
|
|
|
self.beta_dist = Beta(alpha_tensor, beta_tensor)
|
|
|
|
|
self.time_embed = SinusoidalPosEmb(self.action_hidden_size)
|
2025-09-07 14:59:17 +08:00
|
|
|
|
2026-02-03 11:35:25 +08:00
|
|
|
# project to hidden space
|
|
|
|
|
if self.config.proj_with_mask:
|
|
|
|
|
self.w1 = nn.Linear(
|
|
|
|
|
self.action_dim * 2, self.action_hidden_size, bias=False
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
self.w1 = nn.Linear(
|
|
|
|
|
self.action_dim, self.action_hidden_size, bias=False
|
|
|
|
|
)
|
|
|
|
|
if not self.config.use_adarms:
|
|
|
|
|
self.w2 = nn.Linear(
|
|
|
|
|
self.action_hidden_size * 2, self.action_hidden_size, bias=False
|
|
|
|
|
)
|
|
|
|
|
self.w3 = nn.Linear(
|
|
|
|
|
self.action_hidden_size, self.action_hidden_size, bias=False
|
|
|
|
|
)
|
|
|
|
|
self.act_fn = nn.SiLU()
|
|
|
|
|
else:
|
|
|
|
|
self.time_mlp_in = nn.Linear(
|
|
|
|
|
self.action_hidden_size, self.action_hidden_size
|
|
|
|
|
)
|
|
|
|
|
self.time_mlp_out = nn.Linear(
|
|
|
|
|
self.action_hidden_size, self.action_hidden_size
|
|
|
|
|
)
|
|
|
|
|
self.act_fn = nn.SiLU()
|
2025-09-11 13:18:33 +08:00
|
|
|
|
2026-02-03 11:35:25 +08:00
|
|
|
# project back to action space
|
|
|
|
|
self.action_proj_back = nn.Linear(
|
|
|
|
|
self.action_hidden_size, self.action_dim, bias=False
|
|
|
|
|
)
|
|
|
|
|
self.mse_loss = nn.MSELoss(reduction="none")
|
2025-09-07 14:59:17 +08:00
|
|
|
|
2026-02-03 11:35:25 +08:00
|
|
|
def set_normalizer(self, normalizer_action, normalizer_propri):
|
|
|
|
|
self.normalizer_action = normalizer_action
|
|
|
|
|
self.normalizer_propri = normalizer_propri
|
2025-09-07 14:59:17 +08:00
|
|
|
|
2026-02-03 11:35:25 +08:00
|
|
|
# dataset_name = self.config["data"]["lerobot_config"]["repo_id"]
|
|
|
|
|
# print("normalizer_propri min", self.normalizer_propri.min.__getattr__(dataset_name), flush=True)
|
|
|
|
|
# print("normalizer_propri delta", self.normalizer_propri.delta.__getattr__(dataset_name), flush=True)
|
|
|
|
|
# print("normalizer_action min", self.normalizer_action.min.__getattr__(dataset_name), flush=True)
|
|
|
|
|
# print("normalizer_action delta", self.normalizer_action.delta.__getattr__(dataset_name), flush=True)
|
2025-09-07 14:59:17 +08:00
|
|
|
|
|
|
|
|
def sample_time(self, batch_size, device, dtype):
|
|
|
|
|
"""
|
2026-02-03 11:35:25 +08:00
|
|
|
Sampling Time Step
|
|
|
|
|
Generates random numbers in the range [0, 1] using a Beta distribution, and then scales them.
|
2025-09-11 13:18:33 +08:00
|
|
|
|
2026-02-03 11:35:25 +08:00
|
|
|
Parameters:
|
|
|
|
|
batch_size (int): Batch size
|
|
|
|
|
device: Device type
|
|
|
|
|
dtype: Data type
|
2025-09-11 13:18:33 +08:00
|
|
|
|
2025-09-07 14:59:17 +08:00
|
|
|
Returns:
|
2026-02-03 11:35:25 +08:00
|
|
|
torch.Tensor: Sampled time steps, with shape [batch_size]
|
2025-09-07 14:59:17 +08:00
|
|
|
"""
|
|
|
|
|
sample = self.beta_dist.sample([batch_size]).to(device=device, dtype=dtype)
|
2025-11-07 15:11:49 +08:00
|
|
|
time = (1 - sample) * self.s
|
2025-09-07 14:59:17 +08:00
|
|
|
return time
|
2025-09-11 13:18:33 +08:00
|
|
|
|
|
|
|
|
def proprioception_proj(
|
|
|
|
|
self, proprioception, dataset_names=None, dof_mask=None, use_history=False
|
|
|
|
|
):
|
2025-09-07 14:59:17 +08:00
|
|
|
"""
|
2026-02-03 11:35:25 +08:00
|
|
|
proprioception: [batch_size, 1, action_dim]
|
|
|
|
|
dataset_names: [batch_size]
|
|
|
|
|
dof_mask: [batch_size, action_dim]
|
2025-09-07 14:59:17 +08:00
|
|
|
"""
|
2025-09-11 13:18:33 +08:00
|
|
|
proprioception = proprioception.to(device=self.propri_proj.weight.device).to(
|
|
|
|
|
dtype=self.propri_proj.weight.dtype
|
|
|
|
|
)
|
2025-09-07 14:59:17 +08:00
|
|
|
if dof_mask is not None:
|
2026-02-03 11:35:25 +08:00
|
|
|
if self.config.proj_with_mask:
|
|
|
|
|
proprioception = torch.cat(
|
|
|
|
|
[proprioception, dof_mask], dim=-1
|
|
|
|
|
) # .unsqueeze(1)
|
2025-09-11 13:18:33 +08:00
|
|
|
proprioception = proprioception.to(device=self.propri_proj.weight.device).to(
|
|
|
|
|
dtype=self.propri_proj.weight.dtype
|
|
|
|
|
)
|
2026-02-03 11:35:25 +08:00
|
|
|
proprio_embed = self.propri_proj(
|
|
|
|
|
proprioception
|
|
|
|
|
) # [batch_size, 1, state_hidden_size]
|
|
|
|
|
|
|
|
|
|
if self.state_hidden_size < self.hidden_size:
|
|
|
|
|
# padding to hidden size
|
|
|
|
|
padding_size = self.hidden_size - self.state_hidden_size
|
|
|
|
|
padding = torch.zeros(
|
|
|
|
|
(proprio_embed.shape[0], 1, padding_size),
|
|
|
|
|
device=proprio_embed.device,
|
|
|
|
|
dtype=proprio_embed.dtype,
|
|
|
|
|
)
|
|
|
|
|
proprio_embed = torch.cat([proprio_embed, padding], dim=-1)
|
|
|
|
|
|
|
|
|
|
return proprio_embed # [batch_size, 1, hidden_size]
|
2025-09-07 14:59:17 +08:00
|
|
|
|
|
|
|
|
def forward(self, action_chunk, dataset_names, dof_mask=None):
|
|
|
|
|
"""
|
2026-02-03 11:35:25 +08:00
|
|
|
Parameters:
|
|
|
|
|
action_chunk (torch.Tensor): Action sequence, shape [batch_size, action_chunk_len, action_dim]
|
|
|
|
|
dataset_names: [batch_size]
|
|
|
|
|
dof_mask: [batch_size, action_dim]
|
2025-09-11 13:18:33 +08:00
|
|
|
|
2025-09-07 14:59:17 +08:00
|
|
|
Returns:
|
2026-02-03 11:35:25 +08:00
|
|
|
torch.Tensor: Processed action representation, shape [batch_size, seq_len, hidden_size]
|
2025-09-07 14:59:17 +08:00
|
|
|
"""
|
2026-02-03 11:35:25 +08:00
|
|
|
with torch.autocast("cuda", dtype=torch.float32):
|
|
|
|
|
action_chunk = action_chunk.to(dtype=torch.float32)
|
|
|
|
|
batch_size = action_chunk.shape[0]
|
|
|
|
|
device = action_chunk.device
|
|
|
|
|
dtype = action_chunk.dtype
|
2025-09-07 14:59:17 +08:00
|
|
|
|
2026-02-03 11:35:25 +08:00
|
|
|
# 1. add noise to action_chunk
|
|
|
|
|
noise = torch.randn_like(action_chunk)
|
|
|
|
|
time = self.sample_time(batch_size, device, dtype)
|
|
|
|
|
time_expanded = time.unsqueeze(-1).unsqueeze(-1)
|
|
|
|
|
noisy_action = (1 - time_expanded) * noise + time_expanded * action_chunk
|
|
|
|
|
flow = action_chunk - noise
|
2025-09-07 14:59:17 +08:00
|
|
|
|
2026-02-03 11:35:25 +08:00
|
|
|
# 2. sinusoidal positional encoding for timesteps
|
|
|
|
|
time_embed = self.time_embed(time).to(torch.float32)
|
2025-09-07 14:59:17 +08:00
|
|
|
|
2026-02-03 11:35:25 +08:00
|
|
|
self.noise = noise
|
|
|
|
|
self.noisy_action = noisy_action # for new x-pred
|
2025-09-07 14:59:17 +08:00
|
|
|
|
2026-02-03 11:35:25 +08:00
|
|
|
# 3.action_chunk_nosiy + t_pos_emb -> MLP_act_chunk -> action_chunk_nosiy_emb_with_t (dim=trans * chunk)
|
|
|
|
|
if dof_mask is not None:
|
|
|
|
|
noisy_action = torch.cat([noisy_action, dof_mask], dim=-1)
|
2025-09-11 13:18:33 +08:00
|
|
|
|
2026-02-03 11:35:25 +08:00
|
|
|
noisy_action = noisy_action.to(dtype=self.w1.weight.dtype)
|
|
|
|
|
action_embed = self.w1(noisy_action)
|
2025-09-11 13:18:33 +08:00
|
|
|
|
2026-02-03 11:35:25 +08:00
|
|
|
self.time_expanded = time_expanded # for new x-pred
|
2025-09-11 13:18:33 +08:00
|
|
|
|
2026-02-03 11:35:25 +08:00
|
|
|
if not self.config.use_adarms:
|
|
|
|
|
time_embed = (
|
|
|
|
|
time_embed.unsqueeze(1)
|
|
|
|
|
.repeat(1, action_embed.shape[1], 1)
|
|
|
|
|
.to(dtype=self.w2.weight.dtype)
|
|
|
|
|
)
|
|
|
|
|
concat_embed = torch.cat([action_embed, time_embed], dim=-1)
|
|
|
|
|
concat_embed = self.w2(concat_embed)
|
|
|
|
|
action_time_embed = self.w3(self.act_fn(concat_embed))
|
|
|
|
|
adarms_cond = None
|
|
|
|
|
else:
|
|
|
|
|
time_embed = self.time_mlp_in(time_embed)
|
|
|
|
|
time_embed = self.act_fn(time_embed)
|
|
|
|
|
time_embed = self.time_mlp_out(time_embed)
|
|
|
|
|
time_embed = self.act_fn(time_embed)
|
|
|
|
|
action_time_embed = action_embed
|
|
|
|
|
adarms_cond = time_embed
|
2025-09-07 14:59:17 +08:00
|
|
|
|
2026-02-03 11:35:25 +08:00
|
|
|
if self.action_hidden_size < self.hidden_size:
|
|
|
|
|
# padding to hidden size
|
|
|
|
|
padding_size = self.hidden_size - self.action_hidden_size
|
|
|
|
|
padding = torch.zeros(
|
|
|
|
|
(
|
|
|
|
|
action_time_embed.shape[0],
|
|
|
|
|
action_time_embed.shape[1],
|
|
|
|
|
padding_size,
|
|
|
|
|
),
|
|
|
|
|
device=action_time_embed.device,
|
|
|
|
|
dtype=action_time_embed.dtype,
|
|
|
|
|
)
|
|
|
|
|
action_time_embed = torch.cat([action_time_embed, padding], dim=-1)
|
|
|
|
|
|
|
|
|
|
return action_time_embed, flow, adarms_cond
|
2025-09-11 13:18:33 +08:00
|
|
|
|
2025-09-07 14:59:17 +08:00
|
|
|
def step(self, timestep, noisy_action, dof_mask=None):
|
2026-02-03 11:35:25 +08:00
|
|
|
# noisy_action: bs, pred_horizon, action_dim
|
|
|
|
|
# timestep: bs
|
|
|
|
|
with torch.autocast("cuda", dtype=torch.float32):
|
|
|
|
|
if dof_mask is not None and self.config.proj_with_mask:
|
|
|
|
|
if dof_mask.shape[1] == 1:
|
|
|
|
|
dof_mask = dof_mask.unsqueeze(1).repeat(1, noisy_action.shape[1], 1)
|
|
|
|
|
noisy_action = torch.cat([noisy_action, dof_mask], dim=-1)
|
2025-09-11 13:18:33 +08:00
|
|
|
|
2026-02-03 11:35:25 +08:00
|
|
|
noisy_action = noisy_action.to(dtype=self.w1.weight.dtype)
|
|
|
|
|
time_embed = self.time_embed(timestep).to(torch.float32) # bs,hidden_size
|
|
|
|
|
action_embed = self.w1(noisy_action)
|
2025-09-11 13:18:33 +08:00
|
|
|
|
2026-02-03 11:35:25 +08:00
|
|
|
if not self.config.use_adarms:
|
|
|
|
|
time_embed = time_embed.unsqueeze(1).repeat(1, action_embed.shape[1], 1)
|
|
|
|
|
time_embed = time_embed.to(device=noisy_action.device).to(
|
|
|
|
|
dtype=noisy_action.dtype
|
|
|
|
|
)
|
|
|
|
|
concat_embed = torch.cat([action_embed, time_embed], dim=-1)
|
|
|
|
|
concat_embed = self.w2(concat_embed)
|
|
|
|
|
embed = self.w3(self.act_fn(concat_embed)) # is this right?
|
|
|
|
|
adarms_cond = None
|
|
|
|
|
else:
|
|
|
|
|
time_embed = time_embed.to(dtype=self.time_mlp_in.weight.dtype)
|
|
|
|
|
time_embed = self.time_mlp_in(time_embed)
|
|
|
|
|
time_embed = self.act_fn(time_embed)
|
|
|
|
|
time_embed = self.time_mlp_out(time_embed)
|
|
|
|
|
time_embed = self.act_fn(time_embed)
|
|
|
|
|
embed = action_embed
|
|
|
|
|
adarms_cond = time_embed
|
2025-09-11 13:18:33 +08:00
|
|
|
|
2026-02-03 11:35:25 +08:00
|
|
|
if self.action_hidden_size < self.hidden_size:
|
|
|
|
|
# padding to hidden size
|
|
|
|
|
padding_size = self.hidden_size - self.action_hidden_size
|
|
|
|
|
padding = torch.zeros(
|
|
|
|
|
(embed.shape[0], embed.shape[1], padding_size),
|
|
|
|
|
device=embed.device,
|
|
|
|
|
dtype=embed.dtype,
|
|
|
|
|
)
|
|
|
|
|
embed = torch.cat([embed, padding], dim=-1)
|
2025-09-11 13:18:33 +08:00
|
|
|
|
2026-02-03 11:35:25 +08:00
|
|
|
return embed, adarms_cond
|
2025-09-11 13:18:33 +08:00
|
|
|
|
2026-02-03 11:35:25 +08:00
|
|
|
def flow_loss(
|
|
|
|
|
self,
|
|
|
|
|
action_hidden_states,
|
|
|
|
|
flow,
|
|
|
|
|
action_chunk,
|
|
|
|
|
dof_mask=None,
|
|
|
|
|
flow_loss_mask=None,
|
|
|
|
|
):
|
|
|
|
|
with torch.autocast("cuda", dtype=torch.float32):
|
|
|
|
|
action_pred = self.action_proj_back(
|
|
|
|
|
action_hidden_states[:, : self.action_hidden_size]
|
|
|
|
|
)
|
|
|
|
|
v_pred = action_pred
|
|
|
|
|
loss = self.mse_loss(v_pred, flow)
|
|
|
|
|
if dof_mask is not None:
|
|
|
|
|
dof_mask = dof_mask.reshape(-1, dof_mask.shape[-1])
|
|
|
|
|
loss = loss * dof_mask
|
2025-09-11 13:18:33 +08:00
|
|
|
|
2026-02-03 11:35:25 +08:00
|
|
|
if flow_loss_mask is not None:
|
|
|
|
|
flow_loss_mask = (
|
|
|
|
|
flow_loss_mask.unsqueeze(-1)
|
|
|
|
|
.reshape(-1, 1)
|
|
|
|
|
.expand(-1, loss.shape[-1])
|
|
|
|
|
)
|
|
|
|
|
loss = loss * flow_loss_mask
|
2025-09-11 13:18:33 +08:00
|
|
|
return loss
|