HARSHIT-hash-07
feat: integrated cloud-based diffusion inference module
17f1f54
Raw
History Blame Contribute Delete
13.9 kB
# coding: utf-8
import math
import torch
import torch.nn.functional as F
from collections import namedtuple
from torch import nn
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
from ACD_Denoiser import ACD_Denoiser
from ID import ID
from helpers import make_joint_channel_masks # <-- needs to exist
__all__ = ["ACD"]
ModelPrediction = namedtuple('ModelPrediction', ['pred_noise', 'pred_x_start'])
def exists(x):
return x is not None
def default(val, d):
if exists(val):
return val
return d() if callable(d) else d
def extract(a, t, x_shape):
"""
extract the appropriate t index for a batch of indices
a: [T]
t: [B] (long)
return: [B, 1, 1, ...] broadcasting shape to x_shape
"""
batch_size = t.shape[0]
out = a.gather(-1, t) # [B]
return out.reshape(batch_size, *((1,) * (len(x_shape) - 1)))
def cosine_beta_schedule(timesteps, s=0.008):
"""Cosine schedule (https://openreview.net/forum?id=-NEXDKk8gZ)"""
steps = timesteps + 1
x = torch.linspace(0, timesteps, steps, dtype=torch.float64)
alphas_cumprod = torch.cos(((x / timesteps) + s) / (1 + s) * math.pi * 0.5) ** 2
alphas_cumprod = alphas_cumprod / alphas_cumprod[0]
betas = 1 - (alphas_cumprod[1:] / alphas_cumprod[:-1])
return torch.clip(betas, 0, 0.999).float()
class ACD(nn.Module):
def __init__(self, args, trg_vocab):
super().__init__()
# -------- Schedules (two-rate: Body vs Hand) --------
timesteps = args["diffusion"].get('timesteps', 1000)
sampling_timesteps = args["diffusion"].get('sampling_timesteps', 5)
hand_beta_scale = args["diffusion"].get('hand_beta_scale', 0.6) # <1 => hands corrupted slower
base_betas = cosine_beta_schedule(timesteps) # Body schedule
betas_B = base_betas
betas_H = (base_betas * hand_beta_scale).clamp(max=0.999)
def cumprod_stats(betas):
alphas = 1. - betas
ac = torch.cumprod(alphas, dim=0) # ᾱ_t
return {
"betas": betas,
"alphas_cumprod": ac,
"sqrt_ac": torch.sqrt(ac),
"sqrt_1m_ac": torch.sqrt(1. - ac),
"sqrt_recip_ac": torch.sqrt(1. / ac),
"sqrt_recipm1_ac": torch.sqrt(1. / ac - 1),
}
stats_B = cumprod_stats(betas_B)
stats_H = cumprod_stats(betas_H)
# Register buffers (Body: *_B, Hand: *_H)
for k, v in stats_B.items():
self.register_buffer(f"{k}_B", v)
for k, v in stats_H.items():
self.register_buffer(f"{k}_H", v)
# Keep some of the original single-schedule artifacts (not strictly required for DDIM)
# but harmless to have around
self.num_timesteps = int(betas_B.shape[0])
self.sampling_timesteps = default(sampling_timesteps, self.num_timesteps)
assert self.sampling_timesteps <= self.num_timesteps
self.is_ddim_sampling = self.sampling_timesteps < self.num_timesteps
self.ddim_sampling_eta = 1.0
# misc flags from original
self.self_condition = False
self.scale = args["diffusion"].get('scale', 1.0)
self.box_renewal = True
self.use_ensemble = True
# -------- Denoiser --------
self.ACD_Denoiser = ACD_Denoiser(
num_layers=args["diffusion"].get('num_layers', 2),
num_heads=args["diffusion"].get('num_heads', 4),
hidden_size=args["diffusion"].get('hidden_size', 512),
ff_size=args["diffusion"].get('ff_size', 512),
dropout=args["diffusion"].get('dropout', 0.1),
emb_dropout=args["diffusion"].get("embeddings", {}).get('dropout', 0.1),
vocab_size=len(trg_vocab),
freeze=False,
trg_size=args.get('trg_size', 150),
decoder_trg_trg_=True
)
# ---------- Group-aware helpers ----------
def _sigmas_pair(self, t, x_shape):
"""
Return σ_t^B, σ_t^H as [B] scalars:
σ_t^C := 1 - ᾱ_t^C
"""
acB = extract(self.alphas_cumprod_B, t, x_shape).squeeze(-1).squeeze(-1) # [B]
acH = extract(self.alphas_cumprod_H, t, x_shape).squeeze(-1).squeeze(-1) # [B]
return (1. - acB).float(), (1. - acH).float()
def predict_noise_from_start_grouped(self, x_t, t, x0):
"""
ε̂ = (sqrt(1/ᾱ_t) * x_t - x0) / sqrt(1/ᾱ_t - 1)
but mixed per-channel using hand/body masks.
"""
mask_body, mask_hand = make_joint_channel_masks(device=x_t.device) # [1,1,150]
sr_B = extract(self.sqrt_recip_ac_B, t, x_t.shape) # [B,1,1]
srm1_B = extract(self.sqrt_recipm1_ac_B, t, x_t.shape)
sr_H = extract(self.sqrt_recip_ac_H, t, x_t.shape)
srm1_H = extract(self.sqrt_recipm1_ac_H, t, x_t.shape)
sr = sr_B * (~mask_hand) + sr_H * mask_hand # [B,1,150]
srm1 = srm1_B * (~mask_hand) + srm1_H * mask_hand
return (sr * x_t - x0) / srm1
# ---------- Core model calls ----------
def model_predictions(self, x, encoder_output, t, src_mask, trg_mask):
"""
Given current noisy sample x (B,T,150), return:
- pred_noise (ε̂)
- x_start (x̂₀)
"""
x_t = ID(x) # (B,T,50*7) iconicity / dir+len expansion
x_t = x_t / self.scale
# Optional: condition denoiser with σ^B, σ^H
sigma_B, sigma_H = self._sigmas_pair(t, x.shape) # [B], [B]
# Call denoiser; pass σ if its forward supports it
try:
pred_pose = self.ACD_Denoiser(
encoder_output=encoder_output,
trg_embed=x_t,
src_mask=src_mask,
trg_mask=trg_mask,
t=t,
sigma_B=sigma_B,
sigma_H=sigma_H
)
except TypeError:
# Backward-compatible: older denoiser without sigma args
pred_pose = self.ACD_Denoiser(
encoder_output=encoder_output,
trg_embed=x_t,
src_mask=src_mask,
trg_mask=trg_mask,
t=t
)
x_start = pred_pose * self.scale
pred_noise = self.predict_noise_from_start_grouped(x, t, x_start)
return ModelPrediction(pred_noise, x_start)
# ---------- Sampling ----------
def ddim_sample(self, encoder_output, input_3d, src_mask, trg_mask, sampling_steps: int = None):
"""
DDIM sampling with group-wise mixed alphas for Body vs Hands.
sampling_steps: override self.sampling_timesteps at runtime (optional).
"""
dev = encoder_output.device # always use actual tensor device (not module-level var)
batch = encoder_output.shape[0]
shape = (batch, input_3d.shape[1], 150)
total_timesteps = self.num_timesteps
_sampling_timesteps = sampling_steps if sampling_steps is not None else self.sampling_timesteps
eta = self.ddim_sampling_eta
# [-1, 0, 1, 2, ..., T-1] when _sampling_timesteps == total_timesteps
times = torch.linspace(-1, total_timesteps - 1, steps=_sampling_timesteps + 1)
times = list(reversed(times.int().tolist()))
time_pairs = list(zip(times[:-1], times[1:])) # [(T-1, T-2), ..., (0, -1)]
img = torch.randn(shape, device=dev) # FIXED: use dev
x_start = None
preds_all = []
mask_body, mask_hand = make_joint_channel_masks(device=dev)
for time, time_next in time_pairs:
time_cond = torch.full((batch,), time, device=dev, dtype=torch.long) # FIXED: use dev
preds = self.model_predictions(
x=img, encoder_output=encoder_output, t=time_cond,
src_mask=src_mask, trg_mask=trg_mask
)
pred_noise, x_start = preds.pred_noise.float(), preds.pred_x_start
preds_all.append(x_start)
if time_next < 0:
img = x_start
continue
# Per-group alphas
alpha_B = self.alphas_cumprod_B[time]
alpha_n_B = self.alphas_cumprod_B[time_next]
alpha_H = self.alphas_cumprod_H[time]
alpha_n_H = self.alphas_cumprod_H[time_next]
sigma_B = eta * ((1 - alpha_B / alpha_n_B) * (1 - alpha_n_B) / (1 - alpha_B)).sqrt()
sigma_H = eta * ((1 - alpha_H / alpha_n_H) * (1 - alpha_n_H) / (1 - alpha_H)).sqrt()
c_B = (1 - alpha_n_B - sigma_B ** 2).sqrt()
c_H = (1 - alpha_n_H - sigma_H ** 2).sqrt()
# Mix per channel
alpha_n_mix = alpha_n_B.sqrt() * (~mask_hand) + alpha_n_H.sqrt() * mask_hand # [1,1,150]
c_mix = c_B * (~mask_hand) + c_H * mask_hand
sigma_mix = sigma_B * (~mask_hand) + sigma_H * mask_hand
noise = torch.randn_like(img)
img = x_start * alpha_n_mix + c_mix * pred_noise + sigma_mix * noise
return preds_all
# ---------- Training-time forward noising ----------
# def q_sample(self, x_start, t, noise=None):
# """
# Group-aware forward diffusion:
# x_t[C] = sqrt(ᾱ_t^C) * x_0[C] + sqrt(1-ᾱ_t^C) * ε[C]
# where C in {Body, Hand}.
# """
# if noise is None:
# noise = torch.randn_like(x_start)
# mask_body, mask_hand = make_joint_channel_masks(device=x_start.device) # [1,1,150]
# sqrt_ac_B = extract(self.sqrt_ac_B, t, x_start.shape)
# sqrt_1m_B = extract(self.sqrt_1m_ac_B, t, x_start.shape)
# sqrt_ac_H = extract(self.sqrt_ac_H, t, x_start.shape)
# sqrt_1m_H = extract(self.sqrt_1m_ac_H, t, x_start.shape)
# sqrt_ac = sqrt_ac_B * (~mask_hand) + sqrt_ac_H * mask_hand # [B,1,150]
# sqrt_1m = sqrt_1m_B * (~mask_hand) + sqrt_1m_H * mask_hand
# return sqrt_ac * x_start + sqrt_1m * noise
def q_sample(self, x_start, t, noise=None):
"""
Group-aware forward diffusion for TRAINING (x_start: [T,150]).
Mix Body/Hand scalars over channels -> [1,150], so broadcasting keeps shape [T,150].
"""
if noise is None:
noise = torch.randn_like(x_start)
device = x_start.device
# masks: originally [1,1,150] -> squeeze time axis to [1,150]
_, mask_hand_150 = make_joint_channel_masks(device=device) # [1,1,150] (bool)
mask_hand = mask_hand_150.squeeze(1).to(dtype=x_start.dtype) # [1,150] (float)
mask_body = 1.0 - mask_hand # [1,150]
# per-step scalars come out as [1,1]; let them broadcast to [1,150]
sqrt_ac_B = extract(self.sqrt_ac_B, t, x_start.shape) # [1,1]
sqrt_1m_B = extract(self.sqrt_1m_ac_B, t, x_start.shape) # [1,1]
sqrt_ac_H = extract(self.sqrt_ac_H, t, x_start.shape) # [1,1]
sqrt_1m_H = extract(self.sqrt_1m_ac_H, t, x_start.shape) # [1,1]
# mix to per-channel [1,150], then broadcast with [T,150] -> [T,150]
sqrt_ac = sqrt_ac_B * mask_body + sqrt_ac_H * mask_hand # [1,150]
sqrt_1m = sqrt_1m_B * mask_body + sqrt_1m_H * mask_hand # [1,150]
return sqrt_ac * x_start + sqrt_1m * noise # [T,150]
# ---------- Top-level forward ----------
def forward(self, encoder_output, input_3d, src_mask, trg_mask, is_train):
# Inference: return last x̂₀ from DDIM
if not is_train:
results = self.ddim_sample(
encoder_output=encoder_output, input_3d=input_3d,
src_mask=src_mask, trg_mask=trg_mask
)
return results[self.sampling_timesteps - 1]
# Training: sample t and noise target poses, predict x̂₀
x_poses, noises, t = self.prepare_targets(input_3d) # x_t, ε, t
x_poses = x_poses.float()
x_poses = ID(x_poses) # iconicity expansion (B,T,50*7)
t = t.squeeze(-1)
# Optional σ conditioning (same as in model_predictions)
sigma_B, sigma_H = self._sigmas_pair(t, x_poses.shape)
try:
pred_pose = self.ACD_Denoiser(
encoder_output=encoder_output,
trg_embed=x_poses,
src_mask=src_mask,
trg_mask=trg_mask,
t=t,
sigma_B=sigma_B,
sigma_H=sigma_H
)
except TypeError:
pred_pose = self.ACD_Denoiser(
encoder_output=encoder_output,
trg_embed=x_poses,
src_mask=src_mask,
trg_mask=trg_mask,
t=t
)
return pred_pose
# ---------- Target prep (unchanged API, but uses group-aware q_sample) ----------
def prepare_diffusion_concat(self, pose_3d):
"""
Sample a single t and produce (x_t, noise, t) for one sample sequence.
"""
t = torch.randint(0, self.num_timesteps, (1,), device=device).long()
noise = torch.randn(pose_3d.shape[0], 150, device=device)
x_start = pose_3d * self.scale
x = self.q_sample(x_start=x_start, t=t, noise=noise) # group-aware
x = x / self.scale
return x, noise, t
def prepare_targets(self, targets):
"""
For a batch of sequences: return stacked x_t, ε, t.
"""
diffused_poses = []
noises = []
ts = []
for i in range(0, targets.shape[0]):
targets_per_sample = targets[i] # [T,150]
d_poses, d_noise, d_t = self.prepare_diffusion_concat(targets_per_sample)
diffused_poses.append(d_poses)
noises.append(d_noise)
ts.append(d_t)
return torch.stack(diffused_poses), torch.stack(noises), torch.stack(ts)