Flow Matching Sampling in TRELLIS.2: How the Velocity-Based Diffusion Architecture Works
Flow matching sampling in TRELLIS.2 trains a neural network to predict the velocity field of a forward diffusion process, then integrates that field backward via Euler steps to generate samples from pure noise.
TRELLIS.2 implements a velocity-based diffusion model that replaces traditional denoising objectives with flow matching. According to the microsoft/TRELLIS.2 source code, this architecture learns to predict how data points move through time rather than how to remove noise, enabling more direct and efficient sampling.
What Is Flow Matching in TRELLIS.2?
Flow matching formulates generative modeling as learning a velocity field (v(x, t)) that describes the instantaneous direction of data transformation. In trellis2/trainers/flow_matching/flow_matching.py, the FlowMatchingTrainer class implements this by:
- Defining a forward diffusion process that linearly interpolates between clean data (x_0) and Gaussian noise (z)
- Computing the analytical velocity target (v = (1-\sigma_{\text{min}})z - x_0)
- Training the denoiser to predict this velocity through mean-squared error
This differs from score-based or DDPM approaches that predict noise or score functions. The velocity formulation provides a direct ODE description of the generative process.
Forward Diffusion: Creating Noisy Training Data
The forward diffusion in TRELLIS.2 follows a linear interpolation schedule controlled by sigma_min. In FlowMatchingTrainer.diffuse() (lines 69-88), the noisy sample (x_t) at time (t \in [0,1]) is computed as:
[ x_t = (1-t)x_0 + \bigl(\sigma_{\text{min}} + (1-\sigma_{\text{min}})t\bigr)z ]
Where:
- (x_0) is the clean latent tensor
- (z \sim \mathcal{N}(0, I)) is standard Gaussian noise
- (\sigma_{\text{min}}) (default 1e-5) prevents numerical issues at (t=0)
from trellis2.trainers.flow_matching.flow_matching import FlowMatchingTrainer
# Example: forward diffusion during training
trainer = FlowMatchingTrainer(
models={"denoiser": model},
dataset=dataset,
sigma_min=1e-5,
t_schedule={"name": "logitNormal", "args": {"mean": 0.0, "std": 1.0}}
)
# The trainer automatically:
# 1. Samples timestep t ~ logit-normal distribution
# 2. Creates x_t via diffuse()
# 3. Computes velocity target v via get_v()
The time schedule is configurable. FlowMatchingTrainer.sample_t() (lines 134-140) implements logit-normal sampling by default, which concentrates training effort near the middle of the trajectory where the velocity field changes most rapidly.
Velocity Target Computation
The velocity target is derived analytically from the forward diffusion equation. In FlowMatchingTrainer.get_v() (lines 100-105):
[ v = \frac{dx_t}{dt} = (1-\sigma_{\text{min}})z - x_0 ]
Notice this does not depend on (t)—the velocity is constant in time for each sample, making the ODE straight-line in (x)-space. This simplification is specific to the linear interpolation schedule and enables efficient training.
The loss function in FlowMatchingTrainer.training_losses() (lines 62-73) is straightforward MSE between predicted and target velocity:
def training_losses(self, x_0, cond=None):
t = self.sample_t(x_0.shape[0]) # Sample timesteps
x_t = self.diffuse(x_0, t) # Forward diffusion
v_target = self.get_v(x_0, x_t, t) # Analytical velocity
v_pred = self.models["denoiser"](x_t, t, cond) # Network prediction
return {"loss": F.mse_loss(v_pred, v_target)}
Euler Integration for Sampling
Sampling reverses the flow by integrating (\frac{dx}{dt} = -v(x,t)) from (t=1) (pure noise) to (t=0) (clean data). The FlowEulerSampler in trellis2/pipelines/samplers/flow_euler.py implements first-order Euler integration:
Single Euler Step
FlowEulerSampler.sample_once() (lines 79-82) performs one update:
[ x_{t_{\text{prev}}} = x_t - (t - t_{\text{prev}}) \cdot \hat v(x_t, t)
```python
from trellis2.pipelines.samplers.flow_euler import FlowEulerSampler
# Initialize from Gaussian noise
noise = torch.randn(batch_size, channels, height, width)
sampler = FlowEulerSampler(sigma_min=1e-5)
# The sampler automatically:
# 1. Creates linear time grid: t = [1.0, 0.98, ..., 0.0]
# 2. At each step, calls model to predict velocity
# 3. Updates state via Euler rule
result = sampler.sample(model, noise, steps=50)
generated = result.samples # Final x_0
Full Sampling Loop
FlowEulerSampler.sample() (lines 94-126) implements the complete reverse integration:
def sample(self, model, noise, steps=50, guidance_strength=0.0, **kwargs):
sample = noise
t_seq = np.linspace(1, 0, steps + 1) # Time from noise to data
for t, t_prev in zip(t_seq[:-1], t_seq[1:]):
# Predict x_0, epsilon, and velocity from current state
pred_x_0, pred_eps, pred_v = self._get_model_prediction(
model, sample, t, cond
)
# Euler integration step
sample = sample - (t - t_prev) * pred_v
return SampleResult(samples=sample, pred_x_0=pred_x_0, pred_eps=pred_eps)
The method _get_model_prediction() recovers (x_0) and (\epsilon) from the predicted velocity using the algebraic relationships defined by the forward diffusion, which is useful for intermediate visualization and guidance.
Classifier-Free Guidance Extensions
TRELLIS.2 provides two enhanced samplers for conditional generation:
FlowEulerCfgSampler
Inherits from ClassifierFreeGuidanceSamplerMixin (trellis2/trainers/flow_matching/mixins/classifier_free_guidance.py). This sampler computes conditional and unconditional velocities, then combines them:
[ \hat v_{\text{guided}} = \hat v_{\text{uncond}} + g \cdot (\hat v_{\text{cond}} - \hat v_{\text{uncond}}) ]
Where (g) is the guidance_strength parameter.
from trellis2.trainers.flow_matching.flow_matching import FlowMatchingCFGTrainer
cfg_trainer = FlowMatchingCFGTrainer(
models=trained_models,
p_uncond=0.1 # 10% chance to drop conditioning during training
)
sampler = cfg_trainer.get_sampler() # Returns FlowEulerCfgSampler
result = sampler.sample(
cfg_trainer.models["denoiser"],
noise,
cond=positive_condition,
neg_cond=negative_condition, # Unconditional or negative prompt
steps=50,
guidance_strength=3.0
)
FlowEulerGuidanceIntervalSampler
Restricts classifier-free guidance to a specific timestep interval, applying standard sampling outside that range. This balances sample quality with computational efficiency.
Key Implementation Files
| File | Purpose | Key Classes/Functions |
|---|---|---|
trellis2/trainers/flow_matching/flow_matching.py |
Core trainer with diffusion, loss, and sampler factory | FlowMatchingTrainer, diffuse(), get_v(), training_losses() |
trellis2/pipelines/samplers/flow_euler.py |
Euler ODE solver for sampling | FlowEulerSampler, sample(), sample_once() |
trellis2/pipelines/samplers/base.py |
Abstract sampler interface | Sampler base class |
trellis2/trainers/flow_matching/mixins/classifier_free_guidance.py |
CFG functionality | ClassifierFreeGuidanceSamplerMixin |
trellis2/models/sparse_structure_flow.py |
Example velocity-predicting architecture | SparseStructureFlow |
Why Flow Matching for 3D Generation?
TRELLIS.2's choice of flow matching over traditional diffusion reflects several advantages for structured latent generation:
- Straight-line trajectories: The constant-velocity ODE enables fewer sampling steps (25-50) compared to curved diffusion paths
- Deterministic sampling: No stochasticity in the base sampler, enabling reproducible generation
- Direct velocity supervision: The network learns a geometrically interpretable quantity rather than abstract scores
The linear interpolation schedule specifically suits TRELLIS.2's sparse structured latents, where maintaining geometric consistency across the trajectory is critical for coherent 3D output.
Summary
- Flow matching in TRELLIS.2 trains a velocity-predicting network rather than a denoiser, with the objective defined in
FlowMatchingTrainer.training_losses(). - Forward diffusion mixes clean data and noise via linear interpolation in
diffuse(), with velocity targets computed analytically inget_v(). - Sampling integrates the learned velocity field backward using first-order Euler steps in
FlowEulerSampler.sample(). - Classifier-free guidance is available through
FlowEulerCfgSampler, mixing conditional and unconditional predictions. - The complete pipeline from training to inference is contained in
trellis2/trainers/flow_matching/andtrellis2/pipelines/samplers/.
Frequently Asked Questions
What is the difference between flow matching and standard diffusion in TRELLIS.2?
Standard diffusion predicts noise or score functions and requires stochastic sampling with many steps. TRELLIS.2's flow matching predicts velocity along straight-line trajectories, enabling deterministic sampling with 25-50 Euler steps. The training objective is MSE on velocity rather than noise, implemented in FlowMatchingTrainer.training_losses().
Why does the velocity target not depend on timestep t?
The velocity (v = (1-\sigma_{\text{min}})z - x_0) is constant because TRELLIS.2 uses a linear interpolation schedule for forward diffusion. This design choice simplifies both training and sampling—trajectories are straight lines in data space, making Euler integration exact for the ODE and reducing numerical error.
How many sampling steps does TRELLIS.2 require?
The default configuration uses 50 Euler steps, though the sampler accepts any steps parameter. Flow matching's straight-line trajectories allow quality generation with as few as 25 steps, compared to 1000+ steps often required by DDPM. The FlowEulerSampler linearly spaces timesteps from 1.0 to 0.0 regardless of step count.
Can I use other ODE solvers with TRELLIS.2's flow matching?
The base FlowEulerSampler implements first-order Euler integration. While the repository does not include higher-order solvers, the velocity field formulation is compatible with Runge-Kutta or Heun methods—you would subclass Sampler from trellis2/pipelines/samplers/base.py and implement the alternative integration scheme using the same denoiser interface.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →