Spaces:
Running on Zero
Running on Zero
| # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | |
| # SPDX-License-Identifier: Apache-2.0 | |
| import torch | |
| import torch.nn.functional as F | |
| from typing import Optional | |
| from lipforcing.networks.noise_schedule import BaseNoiseSchedule | |
| from lipforcing.utils import expand_like | |
| def denoising_score_matching_loss( | |
| pred_type: str, | |
| net_pred: torch.Tensor, | |
| x0: torch.Tensor = None, | |
| eps: torch.Tensor = None, | |
| noise_scheduler: Optional[BaseNoiseSchedule] = None, | |
| t: Optional[torch.Tensor] = None, | |
| ) -> torch.Tensor: | |
| """Compute the denoising diffusion objective. | |
| Forward process: | |
| x_t = alpha_t * x_0 + sigma_t * eps | |
| Note: We currently don't add any loss weighting for simplicity. In the future, we may include | |
| time-dependent weighting (e.g., by SNR, variance schedule). | |
| Args: | |
| pred_type (str): Prediction type: 'x0', 'eps', 'v', 'flow'. | |
| net_pred (torch.Tensor): The network output, and its meaning is determined by pred_type. | |
| noise_scheduler (BaseNoiseSchedule): Noise scheduler. | |
| x0 (torch.Tensor): The clean data x_0. | |
| eps (torch.Tensor): The epsilon used to compute noised data. | |
| t (torch.Tensor): The target data t. | |
| Raises: | |
| NotImplementedError: If an unknown pred_type is used. | |
| Returns: | |
| loss (torch.Tensor): The denoising diffusion loss. | |
| """ | |
| if pred_type == "x0": | |
| assert x0 is not None, "x0 cannot be None" | |
| loss = F.mse_loss(x0, net_pred, reduction="mean") | |
| elif pred_type == "eps": | |
| assert eps is not None, "eps cannot be None" | |
| loss = F.mse_loss(eps, net_pred, reduction="mean") | |
| elif pred_type == "v": | |
| assert x0 is not None and eps is not None and t is not None, "x0, eps, t should not be None" | |
| assert noise_scheduler is not None, "noise_scheduler should not be None" | |
| alpha_t = expand_like(noise_scheduler.alpha(t), x0).to(device=x0.device, dtype=x0.dtype) | |
| sigma_t = expand_like(noise_scheduler.sigma(t), x0).to(device=x0.device, dtype=x0.dtype) | |
| v = alpha_t * eps - sigma_t * x0 | |
| loss = F.mse_loss(v, net_pred, reduction="mean") | |
| elif pred_type == "flow": | |
| assert x0 is not None and eps is not None, "x0 and eps cannot be None" | |
| flow_velocity = eps - x0 | |
| loss = F.mse_loss(flow_velocity, net_pred, reduction="mean") | |
| else: | |
| raise NotImplementedError(f"Unknown prediction type {pred_type}") | |
| return loss | |
| def variational_score_distillation_loss( | |
| gen_data: torch.Tensor, | |
| teacher_x0: torch.Tensor, | |
| fake_score_x0: torch.Tensor, | |
| additional_scale: Optional[torch.Tensor] = None, | |
| reduction: str = "mean", | |
| ) -> torch.Tensor: | |
| """ | |
| Compute the variational score distillation loss. | |
| Args: | |
| gen_data (torch.Tensor): generated data | |
| teacher_x0 (torch.Tensor): x0-prediction from the teacher | |
| fake_score_x0 (torch.Tensor): x0-prediction from the fake score | |
| additional_scale (Optional[torch.Tensor]): Additional scale parameter for the VSD loss. | |
| reduction: "mean" (default) returns a scalar; "none" returns a [B] | |
| per-sample loss (mean over non-batch dims). Re-DMD's reward path | |
| uses "none" so `exp(beta * r_i)` can couple to `L_i` per-sample | |
| before the final batch reduction. | |
| Returns: | |
| loss (torch.Tensor): The variational score distillation loss. | |
| """ | |
| dims = tuple(range(1, teacher_x0.ndim)) | |
| with torch.no_grad(): | |
| # Perform weight calculation in fp32 for numerical stability | |
| original_dtype = gen_data.dtype | |
| gen_data_fp32 = gen_data.float() | |
| teacher_x0_fp32 = teacher_x0.float() | |
| # Compute weight in fp32 to avoid numerical instability | |
| diff_abs_mean = (gen_data_fp32 - teacher_x0_fp32).abs().mean(dim=dims, keepdim=True) | |
| w_fp32 = 1 / (diff_abs_mean + 1e-6) | |
| # Apply additional scale if provided | |
| if additional_scale is not None: | |
| w_fp32 *= expand_like(additional_scale.float(), w_fp32) | |
| # Convert weight back to original precision | |
| w = w_fp32.to(dtype=original_dtype) | |
| vsd_grad = (fake_score_x0 - teacher_x0) * w | |
| pseudo_target = gen_data - vsd_grad | |
| if reduction == "mean": | |
| loss = 0.5 * F.mse_loss(gen_data, pseudo_target, reduction="mean") | |
| elif reduction == "none": | |
| per_element = 0.5 * (gen_data - pseudo_target).pow(2) | |
| loss = per_element.mean(dim=dims) | |
| else: | |
| raise ValueError(f"reduction must be 'mean' or 'none', got {reduction!r}") | |
| return loss | |
| def gan_loss_generator(fake_logits: torch.Tensor, reduction: str = "mean") -> torch.Tensor: | |
| """ | |
| Compute the GAN loss for the generator | |
| Args: | |
| fake_logits (torch.Tensor): The logits for the fake data, shape [B, D]. | |
| reduction: 'mean' returns scalar, 'none' returns per-sample [B]. | |
| Returns: | |
| gan_loss (torch.Tensor): The GAN loss for the generator. | |
| """ | |
| assert fake_logits.ndim == 2, f"fake_logits has shape {fake_logits.shape}" | |
| if reduction == "none": | |
| gan_loss = F.softplus(-fake_logits).mean(dim=-1) # [B] | |
| elif reduction == "mean": | |
| gan_loss = F.softplus(-fake_logits).mean() | |
| else: | |
| raise ValueError(f"reduction must be 'mean' or 'none', got {reduction!r}") | |
| return gan_loss | |
| def gan_loss_discriminator(real_logits: torch.Tensor, fake_logits: torch.Tensor) -> torch.Tensor: | |
| """ | |
| Compute the GAN loss for the discriminator | |
| Args: | |
| real_logits (torch.Tensor): The logits for the real data. | |
| fake_logits (torch.Tensor): The logits for the fake data. | |
| Returns: | |
| gan_loss (torch.Tensor): The GAN loss for the discriminator. | |
| """ | |
| assert fake_logits.ndim == 2, f"fake_logits has shape {fake_logits.shape}" | |
| assert real_logits.ndim == 2, f"real_logits has shape {real_logits.shape}" | |
| gan_loss = F.softplus(fake_logits).mean() + F.softplus(-real_logits).mean() | |
| return gan_loss | |