from __future__ import annotations import torch from src.diffusion.noise_schedule import NoiseSchedule, extract def predict_x0_from_eps( z_t: torch.Tensor, t: torch.Tensor, eps: torch.Tensor, schedule: NoiseSchedule, ) -> torch.Tensor: """ Recover clean latent z_0 from epsilon prediction """ sqrt_recip_alpha_bar = extract( schedule.sqrt_recip_alphas_cumprod, t, z_t.shape, ) sqrt_recipm1_alpha_bar = extract( schedule.sqrt_recipm1_alphas_cumprod, t, z_t.shape, ) return sqrt_recip_alpha_bar * z_t - sqrt_recipm1_alpha_bar * eps def predict_eps_from_x0( z_t: torch.Tensor, t: torch.Tensor, z_0: torch.Tensor, schedule: NoiseSchedule, ) -> torch.Tensor: """ Recover epsilon from clean latent z_0 and noisy latent z_t. eps = (z_t - sqrt(alpha_bar_t) * z_0) / sqrt(1 - alpha_bar_t) """ sqrt_alpha_bar = extract( schedule.sqrt_alphas_cumprod, t, z_t.shape, ) sqrt_one_minus_alpha_bar = extract( schedule.sqrt_one_minus_alphas_cumprod, t, z_t.shape, ) return (z_t - sqrt_alpha_bar * z_0) / sqrt_one_minus_alpha_bar def get_v_target( z_0: torch.Tensor, eps: torch.Tensor, t: torch.Tensor, schedule: NoiseSchedule, ) -> torch.Tensor: """ Compute v-prediction target. v-prediction target: v = sqrt(alpha_bar_t) * eps - sqrt(1 - alpha_bar_t) * z_0 """ sqrt_alpha_bar = extract( schedule.sqrt_alphas_cumprod, t, z_0.shape, ) sqrt_one_minus_alpha_bar = extract( schedule.sqrt_one_minus_alphas_cumprod, t, z_0.shape, ) v = sqrt_alpha_bar * eps - sqrt_one_minus_alpha_bar * z_0 return v def predict_x0_from_v( z_t: torch.Tensor, t: torch.Tensor, v: torch.Tensor, schedule: NoiseSchedule, ) -> torch.Tensor: """ Recover clean latent z_0 from v prediction defs: z_t = a * z_0 + b * eps v = a * eps - b * z_0 a = sqrt(alpha_bar_t) b = sqrt(1 - alpha_bar_t) it gives z_0 = a * z_t - b * v """ sqrt_alpha_bar = extract( schedule.sqrt_alphas_cumprod, t, z_t.shape, ) sqrt_one_minus_alpha_bar = extract( schedule.sqrt_one_minus_alphas_cumprod, t, z_t.shape, ) z_0 = sqrt_alpha_bar * z_t - sqrt_one_minus_alpha_bar * v return z_0 def predict_eps_from_v( z_t: torch.Tensor, t: torch.Tensor, v: torch.Tensor, schedule: NoiseSchedule, ) -> torch.Tensor: """ Recover epsilon from v prediction defs: z_t = a * z_0 + b * eps v = a * eps - b * z_0 it gives eps = b * z_t + a * v """ sqrt_alpha_bar = extract( schedule.sqrt_alphas_cumprod, t, z_t.shape, ) sqrt_one_minus_alpha_bar = extract( schedule.sqrt_one_minus_alphas_cumprod, t, z_t.shape, ) eps = sqrt_one_minus_alpha_bar * z_t + sqrt_alpha_bar * v return eps def model_output_to_x0_and_eps( model_output: torch.Tensor, z_t: torch.Tensor, t: torch.Tensor, schedule: NoiseSchedule, prediction_type: str = "v", ) -> tuple[torch.Tensor, torch.Tensor]: """ Convert model output into both: z_0 prediction epsilon prediction """ prediction_type = prediction_type.lower() if prediction_type in {"v", "v_prediction"}: z_0 = predict_x0_from_v( z_t=z_t, t=t, v=model_output, schedule=schedule, ) eps = predict_eps_from_v( z_t=z_t, t=t, v=model_output, schedule=schedule, ) elif prediction_type in {"eps", "epsilon"}: eps = model_output z_0 = predict_x0_from_eps( z_t=z_t, t=t, eps=eps, schedule=schedule, ) elif prediction_type in {"x0", "sample"}: z_0 = model_output eps = predict_eps_from_x0( z_t=z_t, t=t, z_0=z_0, schedule=schedule, ) else: raise ValueError( f"Unknown prediction_type={prediction_type}. " "Use 'v', 'eps', or 'x0'." ) return z_0, eps def get_training_target( z_0: torch.Tensor, eps: torch.Tensor, t: torch.Tensor, schedule: NoiseSchedule, prediction_type: str = "v", ) -> torch.Tensor: """ Return the target the U-Net should learn For our project, default is: prediction_type = "v" Then target is: v = sqrt(alpha_bar_t) * eps - sqrt(1 - alpha_bar_t) * z_0 """ prediction_type = prediction_type.lower() if prediction_type in {"v", "v_prediction"}: return get_v_target( z_0=z_0, eps=eps, t=t, schedule=schedule, ) if prediction_type in {"eps", "epsilon"}: return eps if prediction_type in {"x0", "sample"}: return z_0 raise ValueError( f"Unknown prediction_type={prediction_type}. " "Use 'v', 'eps', or 'x0'." )