Source code for nemo.collections.audio.parts.submodules.flow

# Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES.  All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from abc import ABC, abstractmethod
from typing import Literal, Tuple

import einops
import torch

from nemo.collections.common.parts.utils import mask_sequence_tensor
from nemo.utils import logging

ESTIMATOR_TARGET = Literal['conditional_vector_field', 'data']


[docs] class ConditionalFlow(ABC): """ Abstract class for different conditional flow-matching (CFM) classes Time horizon is [time_min, time_max (should be 1)] every path is "conditioned" on endpoints of the path endpoints are just our paired data samples subclasses need to implement mean, std, and vector_field """ def __init__(self, time_min: float = 1e-8, time_max: float = 1.0): self.time_min = time_min self.time_max = time_max
[docs] @abstractmethod def mean(self, *, time: torch.Tensor, x_start: torch.Tensor, x_end: torch.Tensor) -> torch.Tensor: """ Return the mean of p_t(x | x_start, x_end) at time t """ pass
[docs] @abstractmethod def std(self, *, time: torch.Tensor, x_start: torch.Tensor, x_end: torch.Tensor) -> torch.Tensor: """ Return the standard deviation of p_t(x | x_start, x_end) at time t """ pass
[docs] @abstractmethod def vector_field( self, *, time: torch.Tensor, x_start: torch.Tensor, x_end: torch.Tensor, point: torch.Tensor ) -> torch.Tensor: """ Compute the conditional vector field v_t( point | x_start, x_end) """ pass
@staticmethod def _broadcast_time(time: torch.Tensor, n_dim: int) -> torch.Tensor: """ Broadcast time tensor to the desired number of dimensions """ if time.ndim == 1: target_shape = ' '.join(['B'] + ['1'] * (n_dim - 1)) time = einops.rearrange(time, f'B -> {target_shape}') return time
[docs] def generate_time(self, batch_size: int, rng: torch.random.Generator = None) -> torch.Tensor: """ Randomly sample a batchsize of time_steps from U[self.time_min, self.time_max] Supports an external random number generator for better reproducibility """ return torch.rand((batch_size,), generator=rng) * (self.time_max - self.time_min) + self.time_min
[docs] def sample(self, *, time: torch.Tensor, x_start: torch.Tensor, x_end: torch.Tensor) -> torch.Tensor: """ Generate a sample from p_t(x | x_start, x_end) at time t. Note that this implementation assumes all path marginals are normally distributed. """ time = self._broadcast_time(time, n_dim=x_start.ndim) mean = self.mean(time=time, x_start=x_start, x_end=x_end) std = self.std(time=time, x_start=x_start, x_end=x_end) return mean + std * torch.randn_like(mean)
[docs] def flow( self, *, time: torch.Tensor, x_start: torch.Tensor, x_end: torch.Tensor, point: torch.Tensor ) -> torch.Tensor: """ Compute the conditional flow phi_t( point | x_start, x_end). This is an affine flow. """ mean = self.mean(time=time, x_start=x_start, x_end=x_end) std = self.std(time=time, x_start=x_start, x_end=x_end) return mean + std * (point - x_start)
[docs] class OptimalTransportFlow(ConditionalFlow): """The OT-CFM model from [Lipman et at, 2023] Every conditional path the following holds: p_0 = N(x_start, sigma_start) p_1 = N(x_end, sigma_end), mean(x, t) = (time_max - t) * x_start + t * x_end (linear interpolation between x_start and x_end) std(x, t) = (time_max - t) * sigma_start + t * sigma_end Every conditional path is optimal transport map from p_0(x_start, x_end) to p_1(x_start, x_end) Marginal path is not guaranteed to be an optimal transport map from p_0 to p_1 To get the OT-CFM model from [Lipman et at, 2023] just pass zeroes for x_start To get the I-CFM model, set sigma_min=sigma_max To get the rectified flow model, set sigma_min=sigma_max=0 Args: time_min: minimum time value used in the process time_max: maximum time value used in the process sigma_start: the standard deviation of the initial distribution sigma_end: the standard deviation of the target distribution """ def __init__( self, time_min: float = 1e-8, time_max: float = 1.0, sigma_start: float = 1.0, sigma_end: float = 1e-4 ): super().__init__(time_min=time_min, time_max=time_max) self.sigma_start = sigma_start self.sigma_end = sigma_end logging.debug('Initialized %s with', self.__class__.__name__) logging.debug('\ttime_min: %s', self.time_min) logging.debug('\ttime_max: %s', self.time_max) logging.debug('\tsgima_start: %s', self.sigma_start) logging.debug('\tsigma_end: %s', self.sigma_end)
[docs] def mean(self, *, x_start: torch.Tensor, x_end: torch.Tensor, time: torch.Tensor) -> torch.Tensor: return (self.time_max - time) * x_start + time * x_end
[docs] def std(self, *, x_start: torch.Tensor, x_end: torch.Tensor, time: torch.Tensor) -> torch.Tensor: return (self.time_max - time) * self.sigma_start + time * self.sigma_end
[docs] def vector_field( self, *, x_start: torch.Tensor, x_end: torch.Tensor, time: torch.Tensor, point: torch.Tensor, eps: float = 1e-6, ) -> torch.Tensor: time = self._broadcast_time(time, n_dim=x_start.ndim) if self.sigma_start == self.sigma_end: return x_end - x_start num = self.sigma_end * (point - x_start) - self.sigma_start * (point - x_end) denom = (1 - time) * self.sigma_start + time * self.sigma_end return num / (denom + eps)
[docs] class ConditionalFlowMatchingSampler(ABC): """ Abstract class for different sampler to solve the ODE in CFM Args: estimator: the NN-based conditional vector field estimator num_steps: How many time steps to iterate in the process time_min: minimum time value used in the process time_max: maximum time value used in the process """ def __init__( self, estimator: torch.nn.Module, num_steps: int = 5, time_min: float = 1e-8, time_max: float = 1.0, ): self.estimator = estimator self.num_steps = num_steps self.time_min = time_min self.time_max = time_max @property def time_step(self): return (self.time_max - self.time_min) / self.num_steps
[docs] @abstractmethod def forward( self, state: torch.Tensor, estimator_condition: torch.Tensor, state_length: torch.Tensor ) -> Tuple[torch.Tensor, torch.Tensor]: pass
[docs] class ConditionalFlowMatchingEulerSampler(ConditionalFlowMatchingSampler): """ The Euler Sampler for solving the ODE in CFM on a uniform time grid """ def __init__( self, estimator: torch.nn.Module, num_steps: int = 5, time_min: float = 1e-8, time_max: float = 1.0, estimator_target: ESTIMATOR_TARGET = 'conditional_vector_field', flow: ConditionalFlow = None, ): super().__init__( estimator=estimator, num_steps=num_steps, time_min=time_min, time_max=time_max, ) self.estimator_target = estimator_target if self.estimator_target == 'data' and flow is None: raise ValueError('Flow is required for estimator_target=data') self.flow = flow logging.debug('Initialized %s with', self.__class__.__name__) logging.debug('\tnum_steps: %s', self.num_steps) logging.debug('\ttime_min: %s', self.time_min) logging.debug('\ttime_max: %s', self.time_max) logging.debug('\testimator_target: %s', self.estimator_target) logging.debug('\tflow: %s', self.flow) def __call__(self, *args, **kwargs): return self.forward(*args, **kwargs)
[docs] @torch.inference_mode() def forward( self, state: torch.Tensor, estimator_condition: torch.Tensor, state_length: torch.Tensor ) -> Tuple[torch.Tensor, torch.Tensor]: time_steps = torch.linspace(self.time_min, self.time_max, self.num_steps + 1) if state_length is not None: state = mask_sequence_tensor(state, state_length) init_state = state.clone() for t in time_steps[:-1]: time = t * torch.ones(state.shape[0], device=state.device) if estimator_condition is None: estimator_input = state else: estimator_input = torch.cat([state, estimator_condition], dim=1) if self.estimator_target == 'conditional_vector_field': vector_field, _ = self.estimator(input=estimator_input, input_length=state_length, condition=time) state = state + vector_field * self.time_step elif self.estimator_target == 'data': endpoint, _ = self.estimator(input=estimator_input, input_length=state_length, condition=time) vector_field = self.flow.vector_field(time=time, x_start=init_state, x_end=endpoint, point=state) state = state + vector_field * self.time_step if state_length is not None: state = mask_sequence_tensor(state, state_length) return state, state_length