cs686's picture
Deploy GEM-X ZeroGPU motion capture
49d36c0 verified
Raw
History Blame Contribute Delete
57.1 kB
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
import os
import time
import numpy as np
import pytorch_lightning as pl
import torch
import torch.nn as nn
from hydra.utils import instantiate
from timm.models.vision_transformer import Mlp
from transformers import T5EncoderModel, T5Tokenizer
from gem.datasets.metrosim.metrosim import nvskel77to33_idx
from gem.network import stats_compose
from gem.network.base_arch.transformer.layer import BasicBlock, zero_module
from gem.utils.body_params import get_pred_body_params_global, get_pred_body_params_incam
from gem.utils.cam_utils import (
compute_bbox_info_bedlam,
perspective_projection,
safely_render_x3d_K,
)
from gem.utils.ckpt_compat import remap_legacy_state_dict
from gem.utils.geo_transform import get_bbx_xys, normalize_kp2d
from gem.utils.net_utils import length_to_mask
from gem.utils.pylogger import Log
from gem.utils.soma_augment import (
get_invisible_legs_mask,
get_visible_mask,
get_wham_aug_kp3d,
randomly_modify_hands_legs,
)
from gem.utils.soma_utils.soma_layer import SomaLayer
from gem.utils.tools import Timer
reproj_z_thr = 0.3
class GEM(pl.LightningModule):
def __init__(
self,
pipeline,
optimizer=None,
scheduler=None,
model_cfg=None,
ignored_weights_prefix=None,
):
super().__init__()
self.pipeline = instantiate(pipeline, _recursive_=False)
self.endecoder = self.pipeline.endecoder
self.optimizer = instantiate(optimizer, _partial_=True)
self.model_cfg = model_cfg
self.scheduler = scheduler
self.enable_test_time_opt = model_cfg.get("enable_test_time_opt", False)
self.train_modes = model_cfg.get("train_modes", [])
if isinstance(self.train_modes, str):
self.train_modes = [self.train_modes]
self.music_mask_prob = model_cfg.get("music_mask_prob", 0.0)
self.audio_mask_prob = model_cfg.get("audio_mask_prob", 0.0)
# Options
if ignored_weights_prefix is None:
ignored_weights_prefix = [
"pipeline.endecoder",
"pipeline.denoiser3d.endecoder",
"pipeline._soma_body_model",
"endecoder",
"body_model",
"soma",
"nvhuman",
"feature_extractor",
]
self.ignored_weights_prefix = ignored_weights_prefix
# The test step is the same as validation
self.test_step = self.predict_step = self.validation_step
self.timing = os.environ.get("DEBUG_TIMING", "FALSE") == "TRUE"
self.body_model_type = "soma"
self.body_model = SomaLayer(
data_root="inputs/soma_assets",
low_lod=True,
device="cuda",
identity_model_type="mhr",
mode="warp",
)
self.remove_fingers = self.pipeline.args.get("remove_fingers", False)
obs_num_joints = 33 if self.remove_fingers else 77
if "text_encoder" in model_cfg:
self.max_text_len = model_cfg.text_encoder.max_text_len
self.use_text_encoder = True
if model_cfg.text_encoder.get("load_llm", False):
llm_version = model_cfg.text_encoder.llm_version
self.max_text_len = model_cfg.text_encoder.max_text_len
text_encoder, self.tokenizer = self.load_and_freeze_llm(llm_version)
self.text_encoder = [text_encoder.cuda()]
else:
self.text_encoder = self.tokenizer = None
else:
self.use_text_encoder = False
self.f_condition_dim = {
"obs": (obs_num_joints, 3),
"f_cliffcam": (3,),
"f_cam_angvel": (6,),
"f_cam_t_vel": (3,),
"f_imgseq": (1024,),
# "encoded_music": 438,
"encoded_music": (self.pipeline.args.encoded_music_dim,),
"encoded_audio": (128,),
"observed_motion_3d": (151,),
}
self.not_add_features = [
"obs",
"f_cliffcam",
"f_cam_angvel",
"f_cam_t_vel",
"f_imgseq",
"observed_motion_3d",
"multi_text_embed",
"encoded_music",
"encoded_audio",
]
dropout = self.pipeline.args_denoiser3d.get("dropout", 0.1)
latent_dim = self.pipeline.args_denoiser3d.get("latent_dim", 512)
self.latent_dim = latent_dim
if "obs" in self.pipeline.args.in_attr:
self.learned_pos_linear = nn.Linear(2, 32)
self.learned_pos_params = nn.Parameter(
torch.randn(obs_num_joints, 32), requires_grad=True
)
self.embed_noisyobs = Mlp(
obs_num_joints * 32,
hidden_features=latent_dim * 2,
out_features=latent_dim,
drop=dropout,
)
if "f_cliffcam" in self.pipeline.args.in_attr:
self.cliffcam_embedder = nn.Sequential(
nn.Linear(self.f_condition_dim["f_cliffcam"][0], latent_dim),
nn.SiLU(),
nn.Dropout(dropout),
zero_module(nn.Linear(latent_dim, latent_dim)),
)
if "f_imgseq" in self.pipeline.args.in_attr:
self.imgseq_embedder = nn.Sequential(
nn.LayerNorm(self.f_condition_dim["f_imgseq"][0]),
zero_module(nn.Linear(self.f_condition_dim["f_imgseq"][0], latent_dim)),
)
if "f_cam_angvel" in self.pipeline.args.in_attr:
self.cam_angvel_embedder = nn.Sequential(
nn.Linear(self.f_condition_dim["f_cam_angvel"][0], latent_dim),
nn.SiLU(),
nn.Dropout(dropout),
zero_module(nn.Linear(latent_dim, latent_dim)),
)
if "f_cam_t_vel" in self.pipeline.args.in_attr:
self.cam_t_vel_embedder = nn.Sequential(
nn.Linear(self.f_condition_dim["f_cam_t_vel"][0], latent_dim),
nn.SiLU(),
nn.Dropout(dropout),
zero_module(nn.Linear(latent_dim, latent_dim)),
)
if "encoded_music" in self.pipeline.args.in_attr:
self.music_embedder = Mlp(
self.f_condition_dim["encoded_music"][0],
hidden_features=latent_dim * 2,
out_features=latent_dim,
drop=dropout,
)
self.music_mask_prob = model_cfg.music_mask_prob
if "encoded_audio" in self.pipeline.args.in_attr:
self.audio_encoder = torch.nn.Sequential(
BasicBlock(1, 32, 15, 5),
BasicBlock(32, 32, 15, 6),
BasicBlock(32, 32, 15, 1),
BasicBlock(32, 64, 15, 5),
BasicBlock(64, 64, 15, 1),
BasicBlock(64, 128, 15, 4),
)
self.audio_embedder = nn.Sequential(
nn.LayerNorm(self.f_condition_dim["encoded_audio"][0]),
zero_module(nn.Linear(self.f_condition_dim["encoded_audio"][0], latent_dim)),
)
self.audio_mask_prob = model_cfg.audio_mask_prob
if "multi_text_embed" in self.pipeline.args.in_attr:
multi_text_module_cfg = model_cfg.get("multi_text_module_cfg", {})
text_embed_dim = multi_text_module_cfg.get("text_embed_dim", 1024)
self.multi_text_embedder = nn.Linear(text_embed_dim, latent_dim)
encoder_layer = nn.TransformerEncoderLayer(
d_model=latent_dim, # Input dimension
nhead=multi_text_module_cfg.get("nhead", 8), # Number of attention heads
dim_feedforward=multi_text_module_cfg.get("dim_feedforward", 2048),
dropout=dropout,
batch_first=True,
)
self.multi_text_transformer = nn.TransformerEncoder(
encoder_layer, num_layers=multi_text_module_cfg.get("num_layers", 3)
)
self.condition_source = {
"image": ["f_imgseq"],
"2d": ["obs", "f_cliffcam"],
"camera": ["f_cam_angvel", "f_cam_tvel"],
"audio": ["encoded_audio"],
"music": ["encoded_music"],
}
if self.model_cfg.normalize_cam_angvel:
cam_angvel_stats = stats_compose.cam_angvel["manual"]
self.register_buffer(
"cam_angvel_mean",
torch.tensor(cam_angvel_stats["mean"]),
persistent=False,
)
self.register_buffer(
"cam_angvel_std",
torch.tensor(cam_angvel_stats["std"]),
persistent=False,
)
# Load normalizer stats
self.normalizer_stats = {}
if "norm_attr_stats" in self.model_cfg:
for key, stats_path in self.model_cfg.norm_attr_stats.items():
self.normalizer_stats[key] = torch.load(
stats_path, map_location="cpu", weights_only=False
)
self.no_exist_keys = ["obs", "observed_motion_3d", "multi_text_embed"]
if self.model_cfg.use_cond_exists_as_input:
if self.model_cfg.cond_merge_strategy == "add":
self.cond_exists_embedder = nn.ModuleDict()
for k in self.pipeline.args.in_attr:
if k not in self.no_exist_keys:
self.cond_exists_embedder[k] = nn.Sequential(
nn.Linear(latent_dim + 1, latent_dim),
nn.SiLU(),
zero_module(nn.Linear(latent_dim, latent_dim)),
)
elif self.model_cfg.cond_merge_strategy == "concat":
raise NotImplementedError("Concat is not implemented")
def normalize_attr(self, x, key):
"""Normalize input tensor using stored statistics"""
mean = self.normalizer_stats[key]["mean"].to(x)
std = self.normalizer_stats[key]["std"].to(x)
return (x - mean) / std
def load_and_freeze_llm(self, llm_version):
tokenizer = T5Tokenizer.from_pretrained(llm_version)
model = T5EncoderModel.from_pretrained(llm_version)
# Freeze llm weights
model.eval()
for p in model.parameters():
p.requires_grad = False
return model, tokenizer
def encode_text(self, raw_text, has_text=None):
# raw_text - list (batch_size length) of strings with input text prompts
device = next(self.parameters()).device
if self.tokenizer is None or self.text_encoder is None:
batch_size = len(raw_text)
max_text_len = getattr(self, "max_text_len", 16)
text_dim = 1024
denoiser = getattr(getattr(self.pipeline, "denoiser3d", None), "denoiser", None)
if denoiser is not None and hasattr(denoiser, "encoded_text_dim"):
text_dim = denoiser.encoded_text_dim
encoded_text = torch.zeros(
(batch_size, max_text_len, text_dim),
device=device,
dtype=torch.float32,
)
if has_text is not None:
no_text = ~has_text.to(device)
encoded_text[no_text] = 0
return encoded_text
with torch.no_grad():
with torch.cuda.amp.autocast(enabled=False):
max_text_len = self.max_text_len
encoded = self.tokenizer.batch_encode_plus(
raw_text,
return_tensors="pt",
padding="max_length",
max_length=max_text_len,
truncation=True,
)
# We expect all the processing is done in GPU.
input_ids = encoded.input_ids.to(device)
attn_mask = encoded.attention_mask.to(device)
with torch.no_grad():
output = self.text_encoder[0](input_ids=input_ids, attention_mask=attn_mask)
encoded_text = output.last_hidden_state.detach()
encoded_text = encoded_text[:, :max_text_len]
attn_mask = attn_mask[:, :max_text_len]
encoded_text *= attn_mask.unsqueeze(-1)
if has_text is not None:
no_text = ~has_text
encoded_text[no_text] = 0
return encoded_text
def generate_mask(self, mask_cfg, orig_mask, length):
_cfg = mask_cfg
mask = torch.ones_like(orig_mask)
njoints = orig_mask.shape[-1]
drop_prob = _cfg.get("drop_prob", 0.0)
if drop_prob <= 0:
return mask
max_num_drops = _cfg.get("max_num_drops", 1)
min_drop_nframes = _cfg.get("min_drop_nframes", 1)
max_drop_nframes = _cfg.get("max_drop_nframes", 30)
joint_drop_prob = _cfg.get("joint_drop_prob", 0.0)
for i in range(orig_mask.shape[0]):
mlen = length[i].item()
if np.random.rand() < drop_prob:
num_drops = np.random.randint(1, max_num_drops + 1)
for _ in range(num_drops):
drop_len = np.random.randint(min_drop_nframes, min(max_drop_nframes, mlen) + 1)
drop_start = np.random.randint(0, max(mlen - drop_len, 1))
if joint_drop_prob > 0:
drop_joints = np.random.rand(njoints) < joint_drop_prob
mask[i, drop_start : drop_start + drop_len, drop_joints] = False
else:
mask[i, drop_start : drop_start + drop_len] = False
# Apply kinematic dependency propagation so that dropping a parent joint
# also drops its children (prevents anatomically impossible visibility patterns).
if joint_drop_prob > 0 and njoints == 17:
COCO17_TREE = [
[5, 6],
0,
0,
1,
2,
-1,
-1,
5,
6,
7,
8,
-1,
-1,
11,
12,
13,
14,
15,
15,
15,
16,
16,
16,
]
for child in range(njoints):
parent = COCO17_TREE[child]
if parent == -1:
continue
if isinstance(parent, list):
mask[..., child] *= mask[..., parent[0]] * mask[..., parent[1]]
else:
mask[..., child] *= mask[..., parent]
elif joint_drop_prob > 0:
assert njoints == 77, f"njoints: {njoints} is not supported for body mask"
# NVSKEL33 tree: derived from NVHUMAN77_TREE via nvskel77to33_idx,
# following the parent chain up to the nearest included ancestor.
SKELETON_TREE = [
-1,
0,
1,
2,
3,
4,
5,
6,
6,
6,
6,
3,
11,
12,
13,
14,
15,
16,
17,
14,
19,
20,
21,
22,
14,
24,
25,
26,
27,
14,
29,
30,
31,
32,
14,
34,
35,
36,
37,
3,
39,
40,
41,
42,
43,
44,
45,
42,
47,
48,
49,
50,
42,
52,
53,
54,
55,
42,
57,
58,
59,
60,
42,
62,
63,
64,
65,
0,
67,
68,
69,
70,
0,
72,
73,
74,
75,
]
for child in range(njoints):
parent = SKELETON_TREE[child]
if parent == -1:
continue
if isinstance(parent, list):
mask[..., child] *= mask[..., parent[0]] * mask[..., parent[1]]
else:
mask[..., child] *= mask[..., parent]
return mask
def training_step(self, batch, batch_idx):
def append_mode_to_loss(outputs, mode, suffix=""):
if suffix != "":
suffix = f"_{suffix}"
for k in list(outputs.keys()):
if "_loss" in k or k in {"loss"}:
outputs[f"Loss_{mode}{suffix}/{k}"] = outputs.pop(k)
return outputs
outputs = {"loss": 0}
with Timer("train_step", enabled=self.timing):
for mode in self.train_modes:
self.prepare_batch(batch, mode) # set "obs" (2d keypoints)
outputs_mode = self.train_step(batch, batch_idx, mode=mode)
outputs["loss"] += outputs_mode["loss"]
append_mode_to_loss(outputs_mode, mode)
outputs.update(outputs_mode)
if mode == "regression" and "diffusion" in self.train_modes:
batch["regression_outputs"] = outputs_mode.copy()
# Log
log_kwargs = {
"on_epoch": True,
"prog_bar": True,
"logger": True,
"sync_dist": True,
"batch_size": outputs["batch_size"],
}
self.log("train/loss", outputs["loss"], **log_kwargs)
for k, v in outputs.items():
if "_loss" in k:
self.log(f"{k}", v, **log_kwargs)
return outputs
def prepare_batch(self, batch, mode):
target_x = self.endecoder.encode(batch) # (B, L, C)
batch["sample_indices_dict"] = self.endecoder.obs_indices_dict
if mode == "diffusion":
target_x[batch["mask"]["2d_only"]] = batch["regression_outputs"]["model_output"][
"pred_x_start"
][batch["mask"]["2d_only"]]
else:
target_x[batch["mask"]["2d_only"]] = 0
valid_mask = batch["mask"]["valid"]
target_x_mask = torch.ones_like(target_x).bool()
target_x_mask[batch["mask"]["2d_only"]] = False
global_obs_idx = self.endecoder.get_obs_indices("global_orient")
global_sidx = (
int(global_obs_idx[0])
if isinstance(global_obs_idx, tuple)
else int(global_obs_idx[0].item())
)
target_x_mask[batch["mask"]["spv_incam_only"], :, global_sidx:] = False
target_x_mask = target_x_mask & valid_mask[:, :, None]
batch["target_x"] = target_x
batch["target_x_mask"] = target_x_mask
batch["device"] = batch["target_x"].device
batch["B"], batch["L"] = B, L = batch["target_x"].shape[:2]
if "text_embed" in batch:
batch["encoded_text"] = batch["text_embed"].cuda()
elif self.use_text_encoder:
batch["encoded_text"] = self.encode_text(batch["caption"], batch["has_text"])
# Create augmented noisy-obs : gt_j3d(coco17)
with torch.cuda.amp.autocast(enabled=False):
with torch.no_grad():
output = self.body_model(**batch["soma_params_c"])
gt_verts, gt_j3d = output["vertices"], output["joints"]
root_ = gt_j3d[:, :, [0], :]
batch["gt_j3d"] = gt_j3d
batch["gt_cr_77"] = gt_j3d - root_
batch["gt_c_verts437"] = gt_verts
batch["gt_cr_verts437"] = gt_verts - root_
# compute bbx_xys from GT Vertices
i_x2d = safely_render_x3d_K(gt_verts, batch["K_fullimg"], thr=0.3)
det_kp2d = batch["kp2d"]
assert det_kp2d.ndim == 4 and det_kp2d.shape[-1] == 3, f"det_kp2d.shape: {det_kp2d.shape}"
det_kp2d_conf = det_kp2d[..., 2]
batch["det_kp2d_conf"] = det_kp2d_conf
det_kp2d = det_kp2d[..., :2]
det_kp2d_mask = det_kp2d_conf > 0.5
bbx_xys = get_bbx_xys(i_x2d, do_augment=True)
bbx_xys_detected = get_bbx_xys(det_kp2d, det_kp2d_mask, do_augment=True)
bbx_xys[batch["mask"]["2d_only"]] = bbx_xys_detected[batch["mask"]["2d_only"]]
# NOTE: Using dataset-provided bbx_xys when available; falling back to keypoint-derived bbx_xys.
if False: # trust image bbx_xys seems better
batch["bbx_xys"] = bbx_xys
else:
mask_bbx_xys = batch["mask"]["bbx_xys"]
batch["bbx_xys"][~mask_bbx_xys] = bbx_xys[~mask_bbx_xys].to(batch["bbx_xys"])
with torch.cuda.amp.autocast(enabled=False):
_gt_kp2d = perspective_projection(
gt_j3d, batch["K_fullimg"]
) # (B, L, J, 2) # noqa: F841
# Augment obs on the FULL joint set (77 for soma) BEFORE any
# 77->33 slicing, since aug utilities only support num_J in {17, 77}.
noisy_j3d = gt_j3d + get_wham_aug_kp3d(
gt_j3d.shape[:2], num_J=gt_j3d.shape[-2], device=gt_j3d.device
)
obs_i_j2d = perspective_projection(noisy_j3d, batch["K_fullimg"]) # (B, L, J, 2)
noisy_det_j2d = det_kp2d.clone()
if gt_j3d.shape[-2] in (17, 77):
aug = get_wham_aug_kp3d(
noisy_det_j2d.shape[:2],
num_J=gt_j3d.shape[-2],
device=noisy_det_j2d.device,
)[..., :2]
f = torch.tensor([1024.0, 1024.0]).to(aug) / 4.0
aug *= f * self.model_cfg.kp2d_noise_scale
noisy_det_j2d = noisy_det_j2d + aug
# Use some detected vitpose (presave data)
prob = 0.5
mask_real_vitpose = (torch.rand(batch["B"]).to(obs_i_j2d) < prob) * batch["mask"]["vitpose"]
mask_real_vitpose = mask_real_vitpose | batch["mask"]["2d_only"]
assert batch["mask"]["2d_only"].sum() == 0, batch["mask"]["2d_only"].sum()
obs_i_j2d[mask_real_vitpose] = noisy_det_j2d[mask_real_vitpose]
obs_i_j2d = randomly_modify_hands_legs(obs_i_j2d, num_J=obs_i_j2d.shape[-2])
j2d_visible_mask = get_visible_mask(
gt_j3d.shape[:2], num_J=gt_j3d.shape[-2], device=gt_j3d.device
) # (B, L, J)
j2d_visible_mask = j2d_visible_mask & batch["mask"]["has_2d_mask"][:, :, None]
j2d_visible_mask[mask_real_vitpose] *= det_kp2d_mask[mask_real_vitpose]
close_mask = (noisy_j3d[..., 2] < 0.3) & (~mask_real_vitpose)[:, None, None]
j2d_visible_mask[close_mask] = False # Set close-to-image-plane points as invisible
legs_invisible_mask = get_invisible_legs_mask(
gt_j3d.shape[:2],
num_J=j2d_visible_mask.shape[-1],
device=gt_j3d.device,
) # (B, L, J)
j2d_visible_mask[legs_invisible_mask] = False
if "mask_cfg" in self.model_cfg:
mask = self.generate_mask(self.model_cfg.mask_cfg, j2d_visible_mask, batch["length"])
j2d_visible_mask = j2d_visible_mask & mask
if "body_mask_cfg" in self.model_cfg:
mask = self.generate_mask(
self.model_cfg.body_mask_cfg, j2d_visible_mask, batch["length"]
)
j2d_visible_mask = j2d_visible_mask & mask
occluded_img_mask = j2d_visible_mask.sum(dim=-1) <= 3
f_cliffcam = compute_bbox_info_bedlam(batch["bbx_xys"], batch["K_fullimg"]) # (B, L, 3)
f_cliffcam[occluded_img_mask] = 0
batch["f_cliffcam"] = f_cliffcam
condition_mask = dict()
condition_mask["has_img_mask"] = batch["mask"]["has_img_mask"] & (~occluded_img_mask)
condition_mask["has_2d_mask"] = batch["mask"]["has_2d_mask"] & (~occluded_img_mask)
condition_mask["has_cam_mask"] = batch["mask"]["has_cam_mask"].clone()
condition_mask["has_audio_mask"] = batch["mask"]["has_audio_mask"].clone()
condition_mask["has_music_mask"] = batch["mask"]["has_music_mask"].clone()
batch["condition_mask"] = condition_mask
obs_kp2d = torch.cat(
[obs_i_j2d, j2d_visible_mask[:, :, :, None].float()], dim=-1
) # (B, L, J, 3)
obs = normalize_kp2d(obs_kp2d, batch["bbx_xys"]) # (B, L, J, 3)
obs[~j2d_visible_mask] = 0 # if not visible, set to (0,0,0)
j2d_visible_mask[~batch["mask"]["valid"]] = False
batch["obs"] = obs
condition_mask["j2d_visible_mask"] = j2d_visible_mask
batch["obs"][~batch["mask"]["valid"]] = 0
if "static_gt" not in batch:
batch["static_gt"] = self.endecoder.get_static_gt(
batch, self.pipeline.args.static_conf.vel_thr
) # (B, L, 6)
batch["static_gt_mask"] = ~batch["mask"]["invalid_contact"]
f_cam_angvel = batch["cam_angvel"]
if self.model_cfg.normalize_cam_angvel:
f_cam_angvel = (f_cam_angvel - self.cam_angvel_mean) / self.cam_angvel_std
batch["f_cam_angvel"] = f_cam_angvel
for k in self.normalizer_stats:
if k in batch:
batch[k] = self.normalize_attr(batch[k], k)
def create_condition_mask(self, batch, cond_mask_cfg, mode, train, first_k_frames=None):
B, L = batch["B"], batch["L"]
device = batch["device"]
has_text = batch["has_text"]
condition_mask = batch["condition_mask"]
has_img_mask = condition_mask["has_img_mask"].clone()
has_2d_mask = condition_mask["has_2d_mask"].clone()
has_cam_mask = condition_mask["has_cam_mask"].clone()
has_audio_mask = condition_mask["has_audio_mask"].clone()
has_music_mask = condition_mask["has_music_mask"].clone()
j2d_visible_mask = condition_mask["j2d_visible_mask"].clone()
if train:
regression_no_img_mask = cond_mask_cfg.get("regression_no_img_mask", False)
mask_text_prob = cond_mask_cfg.get("mask_text_prob", {}).get(mode, 0.0)
mask_img_prob = cond_mask_cfg.get("mask_img_prob", 0.0)
mask_cam_prob = cond_mask_cfg.get("mask_cam_prob", 0.0)
mask_f_imgseq_prob = cond_mask_cfg.get("mask_f_imgseq_prob", 0.0)
if mask_text_prob > 0:
mask_text = (torch.rand(batch["B"]) < mask_text_prob).to(device)
batch["text_mask"] = mask_text
else:
batch["text_mask"] = None
if batch.get("text_mask", None) is not None:
batch["has_text"][batch["text_mask"]] = False
if regression_no_img_mask and mode == "regression":
mask_img_prob = 0
mask_f_imgseq_prob = 0
has_2d_mask[~batch["mask"]["2d_only"]] = True
if mask_img_prob > 0:
mask_img = (has_text[:, None] | has_audio_mask | has_music_mask) & (
torch.rand(batch["B"]) < mask_img_prob
).to(device)[:, None]
has_img_mask = has_img_mask & ~mask_img
has_2d_mask = has_2d_mask & ~mask_img
j2d_visible_mask = j2d_visible_mask & ~mask_img[..., None]
if mask_cam_prob > 0:
mask_cam = (has_text[:, None] | has_music_mask | has_audio_mask) & (
torch.rand(batch["B"]) < mask_cam_prob
).to(device)[:, None]
has_cam_mask = has_cam_mask & ~mask_cam
has_music_mask = (
has_music_mask & (torch.rand((B,), device=device) > self.music_mask_prob)[:, None]
)
has_audio_mask = (
has_audio_mask & (torch.rand((B,), device=device) > self.audio_mask_prob)[:, None]
)
j2d_visible_mask = j2d_visible_mask & has_2d_mask[:, :, None]
has_2d_mask = j2d_visible_mask.sum(dim=-1) > 3
f_condition_exists = dict()
for k in self.condition_source["image"]:
f_condition_exists[k] = has_img_mask.clone()
for k in self.condition_source["2d"]:
if k == "obs":
f_condition_exists[k] = j2d_visible_mask.clone()
else:
f_condition_exists[k] = has_2d_mask.clone()
for k in self.condition_source["camera"]:
f_condition_exists[k] = has_cam_mask.clone()
for k in self.condition_source["audio"]:
f_condition_exists[k] = has_audio_mask.clone()
for k in self.condition_source["music"]:
f_condition_exists[k] = has_music_mask.clone()
if train and mask_f_imgseq_prob > 0:
mask_f_imgseq = (torch.rand(batch["B"]) < mask_f_imgseq_prob).to(device)
f_condition_exists["f_imgseq"] = f_condition_exists["f_imgseq"] & (~mask_f_imgseq)
# randomly set null condition
skip_keys = self.pipeline.args.get("skip_keys_for_null_condition", [])
uncond_prob = self.pipeline.args.get("uncond_prob", 0.1)
if train and not self.pipeline.args.get("disable_random_null_condition", False):
for k in self.pipeline.args.in_attr:
if k in skip_keys:
continue
mask = torch.rand(f_condition_exists[k].shape[:2]) < uncond_prob
f_condition_exists[k][mask] = False
f_cond_dict = {}
f_uncond_dict = {}
f_uncond_exists = {k: f_condition_exists[k].clone() for k in f_condition_exists}
f_empty_dict = {}
f_empty_exists = {k: torch.zeros_like(f_condition_exists[k]) for k in f_condition_exists}
length = batch["length"]
end_fr = first_k_frames if first_k_frames is not None else None
if first_k_frames is not None:
length = length.clamp(max=first_k_frames)
for k in self.pipeline.args.in_attr:
if k == "obs":
obs = batch["obs"][:, :end_fr]
B, L, J, C = obs.shape
assert J in (17, 33, 77) and C == 3
obs = obs.clone()
obs = obs * j2d_visible_mask[:, :, :, None]
visible_mask = obs[..., [2]] > 0.5 # (B, L, J, 1)
obs[~visible_mask[..., 0]] = 0 # set low-conf to all zeros
f_obs = self.learned_pos_linear(obs[..., :2]) # (B, L, J, 32)
# Demo parity: legacy path feeds 77-joint vitpose and slices to 33 here.
if self.remove_fingers and J == 77:
f_obs = f_obs[:, :, nvskel77to33_idx, :]
visible_mask = visible_mask[:, :, nvskel77to33_idx, :]
f_obs = (
f_obs * visible_mask
+ self.learned_pos_params.repeat(B, L, 1, 1) * ~visible_mask
) # (B, L, J, 32)
f_obs_empty = self.learned_pos_params.repeat(B, L, 1, 1)
f_obs = self.embed_noisyobs(f_obs.view(B, L, -1)) # (B, L, J*32) -> (B, L, C)
f_obs_empty = self.embed_noisyobs(
f_obs_empty.view(B, L, -1)
) # (B, L, J*32) -> (B, L, C)
f_cond_dict["obs"] = f_obs
f_uncond_dict["obs"] = f_obs
f_empty_dict["obs"] = f_obs_empty
elif k == "f_cliffcam":
f_cliffcam = batch["f_cliffcam"][:, :end_fr] # (B, L, 3)
f_cliffcam = self.cliffcam_embedder(f_cliffcam)
mask = f_condition_exists[k][:, :, None]
f_cond_dict["f_cliffcam"] = f_cliffcam * mask.float()
f_uncond_dict["f_cliffcam"] = f_cliffcam * mask.float()
f_empty_dict["f_cliffcam"] = torch.zeros_like(f_cliffcam)
elif k == "f_cam_angvel":
f_cam_angvel = batch["f_cam_angvel"][:, :end_fr] # (B, L, 6)
f_cam_angvel = self.cam_angvel_embedder(f_cam_angvel)
mask = f_condition_exists[k][:, :, None]
f_cond_dict["f_cam_angvel"] = f_cam_angvel * mask.float()
f_uncond_dict["f_cam_angvel"] = f_cam_angvel * mask.float()
f_empty_dict["f_cam_angvel"] = torch.zeros_like(f_cam_angvel)
elif k == "f_cam_t_vel":
f_cam_t_vel = batch["f_cam_t_vel"][:, :end_fr] # (B, L, 3)
f_cam_t_vel = self.cam_t_vel_embedder(f_cam_t_vel)
mask = f_condition_exists[k][:, :, None]
f_cond_dict["f_cam_t_vel"] = f_cam_t_vel * mask.float()
f_uncond_dict["f_cam_t_vel"] = f_cam_t_vel * mask.float()
f_empty_dict["f_cam_t_vel"] = torch.zeros_like(f_cam_t_vel)
elif k == "f_imgseq":
f_imgseq = batch["f_imgseq"][:, :end_fr] # (B, L, C)
f_imgseq = self.imgseq_embedder(f_imgseq)
mask = f_condition_exists[k][:, :, None]
f_cond_dict["f_imgseq"] = f_imgseq * mask.float()
f_uncond_dict["f_imgseq"] = f_imgseq * mask.float()
f_empty_dict["f_imgseq"] = torch.zeros_like(f_imgseq)
elif k == "encoded_music":
if "music_embed" in batch:
f_encoded_music = batch["music_embed"][:, :end_fr] # (B, L, C)
f_encoded_music = self.music_embedder(f_encoded_music)
mask = f_condition_exists[k][:, :, None]
f_cond_dict["encoded_music"] = f_encoded_music * mask.float()
else:
f_cond_dict["encoded_music"] = torch.zeros(B, L, self.latent_dim).to(
batch["device"]
)
f_uncond_dict["encoded_music"] = torch.zeros(B, L, self.latent_dim).to(
batch["device"]
)
f_uncond_exists["encoded_music"] = torch.zeros_like(
f_condition_exists["encoded_music"]
)
f_empty_dict["encoded_music"] = torch.zeros(B, L, self.latent_dim).to(
batch["device"]
)
elif k == "encoded_audio":
if "audio_array" in batch:
encoded_audio = (
self.audio_encoder(batch["audio_array"].cuda().unsqueeze(1))
.transpose(1, 2)
.contiguous()
)[:, :end_fr]
mask = f_condition_exists[k][:, :, None]
encoded_audio = self.audio_embedder(encoded_audio)
f_cond_dict["encoded_audio"] = encoded_audio * mask.float()
else:
f_cond_dict["encoded_audio"] = torch.zeros(B, L, self.latent_dim).to(
batch["device"]
)
f_uncond_dict["encoded_audio"] = torch.zeros(B, L, self.latent_dim).to(
batch["device"]
)
f_uncond_exists["encoded_audio"] = torch.zeros_like(
f_condition_exists["encoded_audio"]
)
f_empty_dict["encoded_audio"] = torch.zeros(B, L, self.latent_dim).to(
batch["device"]
)
elif k == "observed_motion_3d":
motion_mask_3d = batch.get(
"motion_mask_3d",
torch.zeros_like(batch["observed_motion_3d"]),
)[:, :end_fr]
f_observed_motion_3d = torch.cat(
[batch["observed_motion_3d"][:, :end_fr], motion_mask_3d],
dim=-1,
)
f_observed_motion_3d = self.observed_motion_3d_embedder(f_observed_motion_3d)
f_cond_dict["observed_motion_3d"] = f_observed_motion_3d
f_uncond_dict["observed_motion_3d"] = torch.zeros_like(f_observed_motion_3d)
f_empty_dict["observed_motion_3d"] = torch.zeros_like(f_observed_motion_3d)
else:
raise AssertionError(f"Unknown condition key: {k}")
if k not in self.not_add_features:
f_cond_dict[k] = self.add_feature_embedders[k](batch[k][:, :end_fr])
if self.model_cfg.use_cond_exists_as_input:
if k not in self.no_exist_keys:
if k == "obs":
exist_mask = f_condition_exists[k][:, :end_fr]
exist_mask = exist_mask.sum(dim=-1, keepdim=True) > 0
uncond_exist_mask = f_uncond_exists[k][:, :end_fr]
uncond_exist_mask = uncond_exist_mask.sum(dim=-1, keepdim=True) > 0
empty_exist_mask = f_empty_exists[k][:, :end_fr]
empty_exist_mask = empty_exist_mask.sum(dim=-1, keepdim=True) > 0
else:
exist_mask = f_condition_exists[k][:, :end_fr, None]
uncond_exist_mask = f_uncond_exists[k][:, :end_fr, None]
empty_exist_mask = f_empty_exists[k][:, :end_fr, None]
f_cond_dict[k] = torch.cat(
[
f_cond_dict[k],
exist_mask.float(),
],
dim=-1,
)
f_cond_dict[k] = self.cond_exists_embedder[k](f_cond_dict[k])
f_uncond_dict[k] = torch.cat(
[f_uncond_dict[k], uncond_exist_mask.float()],
dim=-1,
)
f_uncond_dict[k] = self.cond_exists_embedder[k](f_uncond_dict[k])
f_empty_dict[k] = torch.cat(
[f_empty_dict[k], empty_exist_mask.float()],
dim=-1,
)
f_empty_dict[k] = self.cond_exists_embedder[k](f_empty_dict[k])
f_cond = sum(f_cond_dict.values())
f_uncond = sum(f_uncond_dict.values())
f_empty = sum(f_empty_dict.values())
batch["f_cond"] = f_cond
batch["f_uncond"] = f_uncond
batch["f_empty"] = f_empty
if batch.get("text_mask", None) is not None:
batch["encoded_text"] = batch["encoded_text"] * (
1 - batch["text_mask"][:, None, None].float()
)
vis_mask = length_to_mask(length, f_cond.shape[1])[:, :end_fr] # (B, L)
motion = batch["target_x"] * vis_mask[..., None]
batch["motion"] = motion[:, :end_fr]
return batch
def train_step(self, batch, batch_idx, mode):
batch = batch.copy()
for k, v in batch.items():
if isinstance(v, torch.Tensor):
batch[k] = v.detach().clone()
cond_mask_cfg = self.model_cfg.get("condition_mask", {})
batch = self.create_condition_mask(batch, cond_mask_cfg, mode, train=True)
# Forward and get loss
outputs = self.pipeline.forward(
batch,
train=True,
global_step=self.trainer.global_step,
mode=mode,
normalizer_stats=self.normalizer_stats,
)
outputs["batch_size"] = batch["B"]
return outputs
def validation_step(self, batch, batch_idx, dataloader_idx=0):
test_mode = batch["meta"][0].get("mode", "default")
return self.validation(batch, test_mode, batch_idx, dataloader_idx)
def validation(self, batch, test_mode, batch_idx, dataloader_idx=0):
# Options & Check
try:
stage = self.trainer.state.stage
global_step = self.trainer.global_step
except Exception:
stage = "test"
global_step = 0
do_postproc = stage == "test" # Only apply postproc in test
do_flip_test = "flip_test" in batch
do_postproc_not_flip_test = do_postproc and not do_flip_test # later pp when flip_test
# ROPE inference
obs = normalize_kp2d(batch["kp2d"], batch["bbx_xys"])
B, L = obs.shape[:2]
if "mask" in batch:
mask = batch["mask"]
if isinstance(mask, dict):
mask = mask["valid"]
obs[0, ~mask[0]] = 0
test_mode = batch["meta"][0].get("mode", "default")
batch_ = {
"length": batch["length"],
"obs": obs,
"bbx_xys": batch["bbx_xys"],
"K_fullimg": batch["K_fullimg"],
"cam_angvel": batch["cam_angvel"].clone(),
"f_cam_angvel": batch["cam_angvel"].clone(),
"f_imgseq": batch["f_imgseq"],
"caption": batch.get("caption", [""] * B),
"has_text": batch.get("has_text", torch.zeros(B).to(obs.device).bool()),
# "eval_gen_only": eval_gen_only,
"mode": test_mode,
"meta": batch["meta"],
"B": batch["B"],
"L": obs.shape[1],
"device": obs.device,
"target_x": torch.zeros(B, L, self.endecoder.get_motion_dim()).to(obs.device),
"mask": batch["mask"],
"sample_indices_dict": self.endecoder.obs_indices_dict,
}
if "music_embed" in batch:
batch_["music_embed"] = batch["music_embed"]
if "audio_array" in batch:
batch_["audio_array"] = batch["audio_array"]
det_kp2d = batch["kp2d"]
det_kp2d_conf = det_kp2d[..., 2]
j2d_visible_mask = det_kp2d_conf > 0.5
f_cliffcam = compute_bbox_info_bedlam(batch_["bbx_xys"], batch_["K_fullimg"]) # (B, L, 3)
batch_["f_cliffcam"] = f_cliffcam
condition_mask = dict()
condition_mask["has_img_mask"] = batch["mask"]["has_img_mask"]
condition_mask["has_2d_mask"] = batch["mask"]["has_2d_mask"]
condition_mask["has_cam_mask"] = batch["mask"]["has_cam_mask"].clone()
condition_mask["has_audio_mask"] = batch["mask"]["has_audio_mask"].clone()
condition_mask["has_music_mask"] = batch["mask"]["has_music_mask"].clone()
condition_mask["j2d_visible_mask"] = j2d_visible_mask
batch_["condition_mask"] = condition_mask
if self.model_cfg.normalize_cam_angvel:
batch_["f_cam_angvel"] = (
batch_["f_cam_angvel"] - self.cam_angvel_mean
) / self.cam_angvel_std
if "text_embed" in batch:
batch_["encoded_text"] = batch["text_embed"].cuda()
elif self.use_text_encoder:
batch_["encoded_text"] = self.encode_text(batch["caption"], batch["has_text"])
if test_mode == "infilling":
batch["target_x"] = self.endecoder.encode(batch) # (B, L, C)
rng = np.random.RandomState(batch["meta"][0].get("eval_seed", 7) + batch_idx)
assert "motion_3d_mask_cfg" in self.model_cfg
all_mask_types = [
x for x in self.model_cfg.motion_3d_mask_cfg.mask_types if x != "no_mask"
]
use_mask_type = all_mask_types[batch_idx % len(all_mask_types)]
mask_res = self.generate_motion_3d_mask(
self.model_cfg.motion_3d_mask_cfg,
batch["target_x"],
batch["length"],
rng=rng,
use_mask_type=use_mask_type,
)
batch_.update(mask_res)
if "inpainting_3d" in self.model_cfg:
batch_["observed_motion_3d"] = self.endecoder.encode(batch)
motion_mask_3d = torch.zeros_like(batch_["observed_motion_3d"]).cuda()
L = batch["length"][0]
keyframes = [i for i in range(L)]
if self.model_cfg["inpainting_3d"]["mode"] == "body_pose_dense":
motion_mask_3d[:, :, : 126 + 10] = 1
elif self.model_cfg["inpainting_3d"]["mode"] == "body_pose_root_rot_dense":
motion_mask_3d[:, :, : 126 + 10 + 12] = 1
elif self.model_cfg["inpainting_3d"]["mode"] == "body_pose_root_rot_keyframe2":
keyframes = [
0,
np.random.choice(keyframes[L // 2 :], 1)[0],
] # start and random end
motion_mask_3d[:, keyframes, : 126 + 10 + 12] = 1
elif self.model_cfg["inpainting_3d"]["mode"] == "body_pose_root_rot_keyframe5":
keyframes = [int((L - 1) * i / 4) for i in range(5)]
motion_mask_3d[:, keyframes, : 126 + 10 + 12] = 1
elif self.model_cfg["inpainting_3d"]["mode"] == "root_rot_vel_dense":
motion_mask_3d[:, :, 126:] = 1
else:
raise ValueError(
f"Unknown inpainting mode [{self.model_cfg['inpainting_3d']['mode']}]"
)
batch_["motion_mask_3d"] = motion_mask_3d
batch["keyframes"] = keyframes
for k in self.normalizer_stats:
if k in batch_:
batch_[k] = self.normalize_attr(batch_[k], k)
batch_ = self.create_condition_mask(batch_, cond_mask_cfg=None, mode=None, train=False)
outputs = self.pipeline.forward(
batch_,
train=False,
postproc=do_postproc_not_flip_test,
global_step=global_step,
test_mode=test_mode,
)
pred_body_params_global = get_pred_body_params_global(outputs)
if pred_body_params_global is not None:
outputs["pred_body_params_global"] = {
k: v[0] for k, v in pred_body_params_global.items()
}
pred_body_params_incam = get_pred_body_params_incam(outputs)
if pred_body_params_incam is not None:
outputs["pred_body_params_incam"] = {k: v[0] for k, v in pred_body_params_incam.items()}
if test_mode == "infilling":
outputs.update(mask_res)
if do_flip_test:
flip_test = batch["flip_test"]
obs = normalize_kp2d(flip_test["kp2d"], flip_test["bbx_xys"])
if "mask" in batch:
mask = batch["mask"]
if isinstance(mask, dict):
mask = mask["valid"]
obs[0, ~mask[0]] = 0
batch_ = {
"length": batch["length"],
"obs": obs,
"bbx_xys": flip_test["bbx_xys"],
"K_fullimg": batch["K_fullimg"],
"cam_angvel": flip_test["cam_angvel"].clone(),
"f_cam_angvel": flip_test["cam_angvel"].clone(),
"f_imgseq": flip_test["f_imgseq"],
"caption": flip_test.get("caption", [""] * B),
"has_text": flip_test.get("has_text", torch.zeros(B).to(obs.device).bool()),
"meta": batch["meta"],
"B": batch["B"],
"L": obs.shape[1],
"device": obs.device,
"target_x": torch.zeros(B, L, self.endecoder.get_motion_dim()).to(obs.device),
"mask": batch["mask"],
"sample_indices_dict": self.endecoder.obs_indices_dict,
}
det_kp2d = flip_test["kp2d"]
det_kp2d_conf = det_kp2d[..., 2]
j2d_visible_mask = det_kp2d_conf > 0.5
f_cliffcam = compute_bbox_info_bedlam(
batch_["bbx_xys"], batch_["K_fullimg"]
) # (B, L, 3)
batch_["f_cliffcam"] = f_cliffcam
condition_mask = dict()
condition_mask["has_img_mask"] = batch["mask"]["has_img_mask"]
condition_mask["has_2d_mask"] = batch["mask"]["has_2d_mask"]
condition_mask["has_cam_mask"] = batch["mask"]["has_cam_mask"].clone()
condition_mask["has_audio_mask"] = batch["mask"]["has_audio_mask"].clone()
condition_mask["has_music_mask"] = batch["mask"]["has_music_mask"].clone()
condition_mask["j2d_visible_mask"] = j2d_visible_mask
batch_["condition_mask"] = condition_mask
if self.model_cfg.normalize_cam_angvel:
batch_["f_cam_angvel"] = (
batch_["f_cam_angvel"] - self.cam_angvel_mean
) / self.cam_angvel_std
for k in self.normalizer_stats:
if k in batch_:
batch_[k] = self.normalize_attr(batch_[k], k)
if "text_embed" in batch:
batch_["encoded_text"] = batch["text_embed"].cuda()
elif self.use_text_encoder:
batch_["encoded_text"] = self.encode_text(batch["caption"], batch["has_text"])
batch_ = self.create_condition_mask(batch_, cond_mask_cfg=None, mode=None, train=False)
# Flip-test forward pass (SOMA flip-merging not supported; result unused)
self.pipeline.forward(batch_, train=False, global_step=global_step, test_mode=test_mode)
return outputs
@torch.no_grad()
def predict(self, data, static_cam=False, postproc=True):
now = time.time()
# ROPE inference
test_mode = data["meta"][0].get("mode", "default")
if self.endecoder.obs_indices_dict is None:
self.endecoder.build_obs_indices_dict()
batch = {
"length": data["length"][None].cuda(),
"obs": normalize_kp2d(data["kp2d"], data["bbx_xys"])[None].cuda(),
"bbx_xys": data["bbx_xys"][None].cuda(),
"K_fullimg": data["K_fullimg"][None].cuda(),
"cam_angvel": data["cam_angvel"][None].cuda(),
"f_cam_angvel": data["cam_angvel"][None].cuda(),
"cam_tvel": data["cam_tvel"][None].cuda(),
"R_w2c": data["R_w2c"][None].cuda(),
"f_imgseq": data["f_imgseq"][None].cuda(),
# "text_embed": data["text_embed"][None].cuda(),
"has_text": data["has_text"].cuda(),
"B": 1,
"L": data["f_imgseq"].shape[0],
"mode": test_mode,
"target_x": torch.zeros(
1, data["f_imgseq"].shape[0], self.endecoder.get_motion_dim()
).cuda(),
"sample_indices_dict": self.endecoder.obs_indices_dict,
}
if "music_embed" in data:
batch["music_embed"] = data["music_embed"][None].cuda()
if "audio_array" in data:
batch["audio_array"] = data["audio_array"][None].cuda()
if "fast_rollout" in data:
batch["fast_rollout"] = data["fast_rollout"]
batch["device"] = batch["f_imgseq"].device
if "meta" in data:
batch["meta"] = data["meta"]
else:
batch["meta"] = None
if "text_embed" in batch:
batch["encoded_text"] = batch["text_embed"].cuda()
else:
if "caption" in data:
batch["caption"] = [data["caption"]]
else:
batch["caption"] = [""]
batch["has_text"] = torch.tensor([True])
batch["encoded_text"] = self.encode_text(batch["caption"], batch["has_text"])
batch["f_cliffcam"] = compute_bbox_info_bedlam(batch["bbx_xys"], batch["K_fullimg"]).cuda()
condition_mask = dict()
condition_mask["has_img_mask"] = data["mask"]["has_img_mask"][None].cuda()
condition_mask["has_2d_mask"] = data["mask"]["has_2d_mask"][None].cuda()
condition_mask["has_cam_mask"] = data["mask"]["has_cam_mask"][None].cuda().clone()
condition_mask["has_audio_mask"] = data["mask"]["has_audio_mask"][None].cuda().clone()
condition_mask["has_music_mask"] = data["mask"]["has_music_mask"][None].cuda().clone()
kp2d_conf = data["kp2d"][..., 2][None].cuda()
condition_mask["j2d_visible_mask"] = kp2d_conf > 0.5
batch["condition_mask"] = condition_mask
if self.model_cfg.normalize_cam_angvel:
batch["f_cam_angvel"] = (
batch["f_cam_angvel"] - self.cam_angvel_mean
) / self.cam_angvel_std
for k in self.normalizer_stats:
if k in batch:
batch[k] = self.normalize_attr(batch[k], k)
if "multi_text_data" in batch["meta"][0]:
if "text_embed" not in batch["meta"][0]["multi_text_data"]:
multi_text_data = batch["meta"][0]["multi_text_data"]
num_text = len(multi_text_data["caption"])
text_embed = self.encode_text(
multi_text_data["caption"], torch.tensor([True] * num_text)
)
batch["meta"][0]["multi_text_data"]["text_embed"] = text_embed
batch = self.create_condition_mask(batch, cond_mask_cfg=None, mode=None, train=False)
if self.pipeline.args.infer_version == 3:
postproc = False
else:
postproc = postproc
Log.debug(f"Preproc taken: {time.time() - now}")
now = time.time()
outputs = self.pipeline.forward(
batch,
train=False,
postproc=postproc,
static_cam=static_cam,
test_mode=test_mode,
)
pred = {
"body_params_global": {k: v[0] for k, v in outputs["pred_body_params_global"].items()},
"body_params_incam": {k: v[0] for k, v in outputs["pred_body_params_incam"].items()},
"K_fullimg": data["K_fullimg"],
"net_outputs": outputs, # intermediate outputs
}
Log.debug(f"Demo taken: {time.time() - now}")
return pred
def configure_optimizers(self):
params = []
for _, v in self.named_parameters():
if v.requires_grad:
params.append(v)
optimizer = self.optimizer(params=params)
if self.scheduler is None or self.scheduler["scheduler"] is None:
return optimizer
scheduler = dict(self.scheduler)
scheduler["scheduler"] = instantiate(scheduler["scheduler"], optimizer=optimizer)
return [optimizer], [scheduler]
def load_state_dict(self, state_dict, strict=True):
"""Filter intentionally dropped prefixes when loading checkpoints."""
filtered_state_dict = {
k: v
for k, v in state_dict.items()
if not any(k.startswith(prefix) for prefix in self.ignored_weights_prefix)
}
incompatible = super().load_state_dict(filtered_state_dict, strict=False)
real_missing = [
k
for k in incompatible.missing_keys
if not any(k.startswith(prefix) for prefix in self.ignored_weights_prefix)
]
real_unexpected = [
k
for k in incompatible.unexpected_keys
if not any(k.startswith(prefix) for prefix in self.ignored_weights_prefix)
]
if real_missing:
Log.warn(f"Missing keys: {real_missing}")
if real_unexpected:
Log.warn(f"Unexpected keys: {real_unexpected}")
if strict and (real_missing or real_unexpected):
raise RuntimeError(
f"Error(s) in loading state_dict: missing={real_missing}, unexpected={real_unexpected}"
)
return incompatible
# ============== Utils ================= #
def on_save_checkpoint(self, checkpoint) -> None:
for ig_keys in self.ignored_weights_prefix:
for k in list(checkpoint["state_dict"].keys()):
if k.startswith(ig_keys):
checkpoint["state_dict"].pop(k)
def load_pretrained_model(self, ckpt_path):
"""Load pretrained checkpoint, and assign each weight to the corresponding part."""
Log.info(f"[PL-Trainer] Loading ckpt: {ckpt_path}")
ckpt = torch.load(ckpt_path, "cpu")
state_dict, compat_report = remap_legacy_state_dict(ckpt["state_dict"])
if compat_report["stripped_model_prefix"] or compat_report["renamed"] > 0:
Log.info(
f"[CKPT Compat] stripped_model_prefix={compat_report['stripped_model_prefix']}, "
f"remapped_keys={compat_report['renamed']}"
)
missing, unexpected = self.load_state_dict(state_dict, strict=False)
real_missing = []
for k in missing:
ignored_when_saving = any(
k.startswith(ig_keys) for ig_keys in self.ignored_weights_prefix
)
if not ignored_when_saving:
real_missing.append(k)
if len(real_missing) > 0:
Log.warn(f"Missing keys: {real_missing}")
if len(unexpected) > 0:
Log.warn(f"Unexpected keys: {unexpected}")
return ckpt