Spaces:
Running on Zero
Running on Zero
| # Copyright (c) 2025 Hansheng Chen | |
| import sys | |
| import torch | |
| import mmcv | |
| from copy import deepcopy | |
| from functools import partial | |
| from mmgen.models.architectures.common import get_module_device | |
| from mmgen.models.builder import MODULES | |
| from . import GaussianFlow | |
| from .piflow_policies import POLICY_CLASSES, GMFlowPolicy | |
| from lakonlab.utils import module_eval | |
| class PiFlowImitationBase(GaussianFlow): | |
| def __init__(self, *args, policy_type='GMFlow', policy_kwargs=None, **kwargs): | |
| super().__init__(*args, **kwargs) | |
| assert policy_type in POLICY_CLASSES, \ | |
| f'Invalid policy: {policy_type}. Supported policies are {list(POLICY_CLASSES.keys())}.' | |
| self.policy_type = policy_type | |
| self.policy_class = partial( | |
| POLICY_CLASSES[policy_type], **policy_kwargs | |
| ) if policy_kwargs else POLICY_CLASSES[policy_type] | |
| def policy_rollout( | |
| self, | |
| x_t_start: torch.Tensor, # (B, C, *, H, W) | |
| sigma_t_start: torch.Tensor, # (B, 1, *, 1, 1) | |
| raw_t_start: torch.Tensor, # (B, ) | |
| raw_t_end: torch.Tensor, # (B, ) | |
| total_substeps: int, | |
| policy, | |
| seq_len=None): | |
| num_batches = x_t_start.size(0) | |
| ndim = x_t_start.dim() | |
| raw_t_start = raw_t_start.reshape(num_batches, *((ndim - 1) * [1])) | |
| raw_t_end = raw_t_end.reshape(num_batches, *((ndim - 1) * [1])) | |
| delta_raw_t = raw_t_start - raw_t_end | |
| num_substeps = (delta_raw_t * total_substeps).round().to(torch.long).clamp(min=1) | |
| substep_size = delta_raw_t / num_substeps | |
| max_num_substeps = num_substeps.max() | |
| raw_t = raw_t_start | |
| sigma_t = sigma_t_start | |
| x_t = x_t_start | |
| for substep_id in range(max_num_substeps.item()): | |
| u = policy.pi(x_t, sigma_t) | |
| raw_t_minus = (raw_t - substep_size).clamp(min=0) | |
| sigma_t_minus = self.timestep_sampler.warp_t(raw_t_minus, seq_len=seq_len) | |
| x_t_minus = x_t + u * (sigma_t_minus - sigma_t) | |
| active_mask = num_substeps > substep_id | |
| x_t = torch.where(active_mask, x_t_minus, x_t) | |
| sigma_t = torch.where(active_mask, sigma_t_minus, sigma_t) | |
| raw_t = torch.where(active_mask, raw_t_minus, raw_t) | |
| x_t_end = x_t | |
| sigma_t_end = sigma_t | |
| t_end = sigma_t_end.flatten() * self.num_timesteps | |
| return x_t_end, sigma_t_end, t_end | |
| def policy_average_u( | |
| self, | |
| x_t_start: torch.Tensor, # (B, C, *, H, W) | |
| sigma_t_start: torch.Tensor, # (B, 1, *, 1, 1) | |
| raw_t_start: torch.Tensor, # (B, ) | |
| raw_t_end: torch.Tensor, # (B, ) | |
| total_substeps: int, | |
| policy, | |
| seq_len=None, | |
| eps=1e-4): | |
| num_batches = x_t_start.size(0) | |
| ndim = x_t_start.dim() | |
| is_small_length = torch.round((raw_t_start - raw_t_end) * total_substeps) < 2 | |
| pred_mean_u = pred_local_u = None | |
| if not is_small_length.all(): # mean velocity over the rollout length | |
| x_t_end, sigma_t_end, _ = self.policy_rollout( | |
| x_t_start, sigma_t_start, raw_t_start, raw_t_end, total_substeps, | |
| policy, seq_len=seq_len) | |
| pred_mean_u = (x_t_start - x_t_end) / (sigma_t_start - sigma_t_end).clamp(min=eps) | |
| if is_small_length.any(): # numerically stable local velocity | |
| pred_local_u = policy.pi(x_t_start, sigma_t_start) | |
| if pred_mean_u is None: | |
| pred_u = pred_local_u | |
| elif pred_local_u is None: | |
| pred_u = pred_mean_u | |
| else: | |
| pred_u = torch.where( | |
| is_small_length.reshape(num_batches, *((ndim - 1) * [1])), pred_local_u, pred_mean_u) | |
| return pred_u | |
| def get_shape_info(x): | |
| x_t_dst_shape = x.size() | |
| bs = x_t_dst_shape[0] | |
| ndim = len(x_t_dst_shape) | |
| seq_len = x.shape[2:].numel() | |
| return ndim, bs, seq_len | |
| def piid_segment( | |
| self, teacher, policy, x_t_src, raw_t_src, sigma_t_src, teacher_ratio, segment_size, | |
| teacher_kwargs, get_x_t_dst=False): | |
| eps = self.train_cfg.get('eps', 1e-4) | |
| total_substeps = self.train_cfg.get('total_substeps', 128) | |
| num_intermediate_states = self.train_cfg.get('num_intermediate_states', 2) | |
| window_substeps = self.train_cfg.get('window_substeps', 0) | |
| device = x_t_src.device | |
| ndim, bs, seq_len = self.get_shape_info(x_t_src) | |
| if not isinstance(segment_size, torch.Tensor): | |
| segment_size = torch.tensor( | |
| [segment_size], dtype=torch.float32, device=device) | |
| # window size ∆τ ≈ window_substeps / total_substeps | |
| num_substeps = (segment_size * total_substeps).round().to(torch.long).clamp(min=1) | |
| substep_size = segment_size / num_substeps | |
| window_size = torch.minimum(window_substeps * substep_size, segment_size) | |
| raw_t_dst = raw_t_src - segment_size | |
| policy_detached = policy.detach() | |
| if isinstance(policy_detached, GMFlowPolicy): | |
| gm_dropout = self.train_cfg.get('gm_dropout', 0.0) | |
| policy_detached.dropout_(gm_dropout) | |
| # time sampling for scheduled trajectory mixing | |
| assert not self.timestep_sampler.logit_normal_enable | |
| student_intervals = torch.rand( | |
| (bs, num_intermediate_states), device=device | |
| ) * ((1 - teacher_ratio) * (segment_size - window_size).unsqueeze(-1)) | |
| student_intervals = torch.sort(student_intervals, dim=-1)[0] | |
| student_intervals = torch.diff(student_intervals, dim=-1, prepend=torch.zeros((bs, 1), device=device)) | |
| teacher_intervals = torch.rand((bs, num_intermediate_states - 1), device=device) | |
| teacher_intervals = torch.sort(teacher_intervals, dim=-1)[0] | |
| teacher_intervals = torch.diff( | |
| teacher_intervals, dim=-1, | |
| prepend=torch.zeros((bs, 1), device=device), | |
| append=torch.ones( | |
| (bs, 1), device=device) | |
| ) * (teacher_ratio * (segment_size - window_size).unsqueeze(-1)) | |
| x_t = x_t_src | |
| raw_t = raw_t_src | |
| sigma_t = sigma_t_src | |
| all_pred_u = [] | |
| all_tgt_u = [] | |
| all_timesteps = [] | |
| for teacher_step_id in range(num_intermediate_states): | |
| raw_t_a = (raw_t - student_intervals[:, teacher_step_id]).clamp(min=0) | |
| raw_t_b = (raw_t_a - teacher_intervals[:, teacher_step_id]).clamp(min=0) | |
| with torch.no_grad(), module_eval(teacher): | |
| x_t_a, sigma_t_a, t_a = self.policy_rollout( | |
| x_t, sigma_t, raw_t, raw_t_a, total_substeps, | |
| policy_detached, seq_len=seq_len) | |
| tgt_u = teacher(return_u=True, x_t=x_t_a, t=t_a, **teacher_kwargs) | |
| all_tgt_u.append(tgt_u) | |
| all_timesteps.append(t_a) | |
| pred_u = self.policy_average_u( | |
| x_t_a, sigma_t_a, raw_t_a, raw_t_b - window_size, total_substeps, | |
| policy, seq_len=seq_len, eps=eps) | |
| all_pred_u.append(pred_u) | |
| sigma_t_b = self.timestep_sampler.warp_t(raw_t_b, seq_len=seq_len).reshape(bs, *((ndim - 1) * [1])) | |
| x_t = x_t_a + tgt_u * (sigma_t_b - sigma_t_a) | |
| raw_t = raw_t_b | |
| sigma_t = sigma_t_b | |
| loss_kwargs = dict( | |
| u_t_pred=torch.cat(all_pred_u, dim=0), | |
| u_t=torch.cat(all_tgt_u, dim=0), | |
| timesteps=torch.cat(all_timesteps, dim=0) | |
| ) | |
| loss = self.flow_loss(loss_kwargs) | |
| if get_x_t_dst: | |
| with torch.no_grad(): | |
| x_t_dst, _, _ = self.policy_rollout( | |
| x_t, sigma_t, raw_t, raw_t_dst, total_substeps, | |
| policy_detached, seq_len=seq_len) | |
| else: | |
| x_t_dst = None | |
| return loss, x_t_dst, raw_t_dst | |
| def forward_test( | |
| self, x_0=None, noise=None, guidance_scale=None, | |
| test_cfg_override=dict(), show_pbar=False, **kwargs): | |
| x_t_src = torch.randn_like(x_0) if noise is None else noise | |
| num_batches = x_t_src.size(0) | |
| seq_len = x_t_src.shape[2:].numel() # h * w or t * h * w | |
| ori_dtype = x_t_src.dtype | |
| device = x_t_src.device | |
| x_t_src = x_t_src.float() | |
| ndim = x_t_src.dim() | |
| assert ndim in [4, 5], f'Invalid x_t_src shape: {x_t_src.shape}. Expected 4D or 5D tensor.' | |
| cfg = deepcopy(self.test_cfg) | |
| cfg.update(test_cfg_override) | |
| total_substeps = cfg.get('total_substeps', self.num_timesteps) | |
| eps = cfg.get('eps', 1e-4) | |
| nfe = cfg['nfe'] | |
| final_step_size_scale = max(cfg.get('final_step_size_scale', 1.0), eps) | |
| base_segment_size = 1 / (nfe - 1 + final_step_size_scale) | |
| raw_t_src = torch.ones((num_batches,), dtype=torch.float32, device=device) | |
| sigma_t_src = self.timestep_sampler.warp_t(raw_t_src, seq_len=seq_len).reshape( | |
| num_batches, *((ndim - 1) * [1])) | |
| t_src = sigma_t_src.flatten() * self.num_timesteps | |
| if show_pbar: | |
| pbar = mmcv.ProgressBar(self.distill_steps) | |
| # ========== Main sampling loop ========== | |
| for step_id in range(nfe): | |
| is_final_step = step_id == nfe - 1 | |
| if is_final_step: | |
| segment_size = base_segment_size * final_step_size_scale | |
| else: | |
| segment_size = base_segment_size | |
| raw_t_dst = raw_t_src - segment_size | |
| denoising_output = self.pred(x_t_src, t_src, **kwargs) | |
| policy = self.policy_class( | |
| denoising_output, x_t_src, sigma_t_src, eps=eps) | |
| if isinstance(policy, GMFlowPolicy) and not is_final_step: | |
| temperature = cfg.get('temperature', 1.0) | |
| policy.temperature_(temperature) | |
| x_t_dst, sigma_t_dst, t_dst = self.policy_rollout( | |
| x_t_src, sigma_t_src, raw_t_src, raw_t_dst, total_substeps, | |
| policy, seq_len=seq_len) | |
| x_t_src = x_t_dst | |
| raw_t_src = raw_t_dst | |
| sigma_t_src = sigma_t_dst | |
| t_src = t_dst | |
| if show_pbar: | |
| pbar.update() | |
| if show_pbar: | |
| sys.stdout.write('\n') | |
| return x_t_src.to(ori_dtype) | |
| class PiFlowImitation(PiFlowImitationBase): | |
| def sample_t(self, num_batches, ndim, seq_len=None, device=None): | |
| eps = self.train_cfg.get('eps', 1e-4) | |
| nfe = self.train_cfg['nfe'] | |
| final_step_size_scale = max(self.train_cfg.get('final_step_size_scale', 1.0), eps) | |
| one_minus_final_scale = 1 - final_step_size_scale | |
| base_segment_size = 1 / (nfe - one_minus_final_scale) | |
| final_step_size = final_step_size_scale * base_segment_size | |
| raw_t = self.timestep_sampler( | |
| num_batches, warp_t=False, scale_t=False, device=device).clamp(min=eps) | |
| raw_t_src_idx = torch.ceil( | |
| raw_t / base_segment_size + one_minus_final_scale).clamp(min=1) | |
| if isinstance(nfe, torch.Tensor): | |
| raw_t_src_idx = torch.minimum(raw_t_src_idx, nfe) | |
| else: | |
| raw_t_src_idx = raw_t_src_idx.clamp(max=nfe) | |
| raw_t_src = ((raw_t_src_idx - one_minus_final_scale) * base_segment_size).clamp(min=eps, max=1) | |
| is_final_step = raw_t_src_idx == 1 | |
| segment_size = torch.where( | |
| is_final_step, final_step_size, base_segment_size) | |
| sigma_t_src = self.timestep_sampler.warp_t(raw_t_src, seq_len=seq_len).reshape( | |
| num_batches, *((ndim - 1) * [1])) | |
| t_src = sigma_t_src.flatten() * self.num_timesteps | |
| return raw_t_src, sigma_t_src, t_src, segment_size | |
| def forward_train(self, x_0, teacher=None, teacher_kwargs=dict(), running_status=None, **kwargs): | |
| device = get_module_device(self) | |
| num_batches = x_0.size(0) | |
| seq_len = x_0.shape[2:].numel() # h * w or t * h * w | |
| ndim = x_0.dim() | |
| assert ndim in [4, 5], f'Invalid x_0 shape: {x_0.shape}. Expected 4D or 5D tensor.' | |
| num_decay_iters = self.train_cfg.get('num_decay_iters', 0) | |
| if num_decay_iters > 0: | |
| teacher_ratio = 1 - min(running_status['iteration'], num_decay_iters) / num_decay_iters | |
| log_vars = dict(teacher_ratio=teacher_ratio) | |
| else: | |
| teacher_ratio = 0.0 | |
| log_vars = dict() | |
| raw_t_src, sigma_t_src, t_src, segment_size = self.sample_t( | |
| num_batches, ndim, seq_len=seq_len, device=device) | |
| noise = torch.randn_like(x_0) | |
| x_t_src, _, _ = self.sample_forward_diffusion(x_0, t_src, noise) | |
| denoising_output = self.pred(x_t_src, t_src, **kwargs) | |
| policy = self.policy_class(denoising_output, x_t_src, sigma_t_src) | |
| loss_diffusion, _, _ = self.piid_segment( | |
| teacher, policy, x_t_src, raw_t_src, sigma_t_src, teacher_ratio, segment_size, | |
| teacher_kwargs) | |
| loss = loss_diffusion | |
| log_vars.update(self.flow_loss.log_vars) | |
| log_vars.update(loss_diffusion=float(loss_diffusion)) | |
| return loss, log_vars | |
| class PiFlowImitationDataFree(PiFlowImitationBase): | |
| is_multistep = True | |
| def forward_initialize( | |
| self, x_0, teacher=None, teacher_kwargs=dict(), running_status=None, **kwargs): | |
| device = get_module_device(self) | |
| num_batches = x_0.size(0) # x_0 is a dummy input | |
| num_decay_iters = self.train_cfg.get('num_decay_iters', 0) | |
| if num_decay_iters > 0: | |
| teacher_ratio = 1 - min(running_status['iteration'], num_decay_iters) / num_decay_iters | |
| log_vars = dict(teacher_ratio=teacher_ratio) | |
| else: | |
| teacher_ratio = 0.0 | |
| log_vars = dict() | |
| x_t_src = torch.randn_like(x_0) | |
| raw_t_src = torch.ones((num_batches,), dtype=torch.float32, device=device) | |
| step_states = dict( | |
| step_id=0, | |
| terminate=False, | |
| detachable=True, | |
| teacher_ratio=teacher_ratio, | |
| x_t_src=x_t_src, | |
| raw_t_src=raw_t_src, | |
| ) | |
| return step_states, log_vars | |
| def forward_train( | |
| self, x_0, step_states=None, teacher=None, teacher_kwargs=dict(), running_status=None, **kwargs): | |
| step_id = step_states['step_id'] | |
| teacher_ratio = step_states['teacher_ratio'] | |
| x_t_src = step_states['x_t_src'] | |
| raw_t_src = step_states['raw_t_src'] | |
| num_batches = x_t_src.size(0) | |
| seq_len = x_t_src.shape[2:].numel() | |
| ndim = x_t_src.dim() | |
| assert ndim in [4, 5], f'Invalid x_t_src shape: {x_t_src.shape}. Expected 4D or 5D tensor.' | |
| eps = self.train_cfg.get('eps', 1e-4) | |
| nfe = self.train_cfg['nfe'] | |
| final_step_size_scale = max(self.train_cfg.get('final_step_size_scale', 1.0), eps) | |
| base_segment_size = 1 / (nfe - 1 + final_step_size_scale) | |
| is_final_step = step_id == nfe - 1 | |
| if is_final_step: | |
| segment_size = base_segment_size * final_step_size_scale | |
| else: | |
| segment_size = base_segment_size | |
| sigma_t_src = self.timestep_sampler.warp_t(raw_t_src, seq_len=seq_len).reshape( | |
| num_batches, *((ndim - 1) * [1])) | |
| t_src = sigma_t_src.flatten() * self.num_timesteps | |
| denoising_output = self.pred(x_t_src, t_src, **kwargs) | |
| policy = self.policy_class(denoising_output, x_t_src, sigma_t_src) | |
| step_loss_diffusion, x_t_dst, raw_t_dst = self.piid_segment( | |
| teacher, policy, x_t_src, raw_t_src, sigma_t_src, teacher_ratio, segment_size, | |
| teacher_kwargs, get_x_t_dst=True) | |
| loss_diffusion = step_loss_diffusion * segment_size # Weighing by segment size | |
| loss = loss_diffusion | |
| log_vars = {k: v * segment_size for k, v in self.flow_loss.log_vars.items()} | |
| log_vars.update({ | |
| 'loss_diffusion': float(loss_diffusion), | |
| f'loss_diffusion_step{step_id}': float(step_loss_diffusion) | |
| }) | |
| if step_id < nfe - 1: | |
| step_states.update( | |
| step_id=step_id + 1, | |
| x_t_src=x_t_dst, | |
| raw_t_src=raw_t_dst) | |
| else: | |
| step_states.update(terminate=True) | |
| return loss, log_vars, step_states | |
| def forward(self, x_0=None, return_step_states=False, **kwargs): | |
| if return_step_states: | |
| return self.forward_initialize(x_0=x_0, **kwargs) | |
| else: | |
| return super().forward(x_0=x_0, **kwargs) | |