Skip to main content

OTP-FM: Multimarginal flow matching (FM) with optimal transport potentials (OTP) (ICML 2026)


Paper • Overview • Why OTP-FM? • Installation • Quick start • Tutorials • Documentation • Citation


OTP-FM: Multimarginal flow matching (FM) with optimal transport potentials (OTP)

Python 3.10+ CI coverage Code style: black Ruff pre-commit

A PyTorch library for training flow matching models with intermediate marginal constraints enforced using "optimal transport potentials". Includes code for reproducing Kansal et. al., ICML 2026.

Overview

OTP-FM extends vanilla conditional flow matching (CFM) between endpoint marginals to incorporate intermediate marginal constraints as well. We do so by modifying the dynamic optimal transport problem to incorporate potential energy terms corresponding to these intermediate marginals and updating the CFM targets based on the resulting dynamics.

Why OTP-FM?

  • Flexibility in the choice of potentials and temporal dynamics
  • Efficient training for a variety of potentials; in particular, linear time training with the $\mathcal W_2^\infty$ (W2Inf) potential
  • Stable training using the OTPFM curriculum
  • SOTA results in multimarginal inference tasks

Check out Quick Start and Tutorials to see it in action.

Installation

For Users (pip)

# Core package
pip install otpfm

# With W2Potential support (requires POT library)
pip install otpfm[w2]

To run experiments or develop (pixi)

Pixi is a fast conda-like package manager. Install it first:

curl -sSf https://pixi.sh/install.sh | bash

Then set up the environment:

git clone https://github.com/Bexorg-Inc/OTP-FM.git
cd otpfm
pixi install
pixi shell

Both the otpfm and experiments packages will be installed.

Quick Start

import torch
from collections import OrderedDict
from otpfm import OTPFM, Curriculum
from otpfm.potentials import W2InfPotential

# Training data: samples from each marginal (batch_size, num_marginals, dim)
# For K=2 intermediate times: [source, t=0.33, t=0.67, target]
xs = torch.randn(64, 4, 2)

# Define K = 2 intermediate marginal potentials
tks = [0.33, 0.67]  # Intermediate time points
potentials = OrderedDict({
    tks[0]: W2InfPotential(tk=tks[0], strength=100.0, lambda_type='gaussian', width=0.2),
    tks[1]: W2InfPotential(tk=tks[1], strength=100.0, lambda_type='gaussian', width=0.2),
})

# Create model
model = OTPFM(
    d=2,                          # Data dimension
    tks=tks,                      # Intermediate time points
    potentials=potentials,        # OT potentials
    flownet_args={
        'hidden_dim': 128,
        'num_hidden_layers': 2,
    }
)

optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
n_epochs = 100
iterations_per_epoch = 50

# This controls transition from vanilla flow matching (alpha=0) to full OTP-FM (alpha=1)
otp_alpha_schedule = Curriculum(total_iterations=n_epochs * iterations_per_epoch)  # Sigmoid schedule by default

iterations = 0
model.train()
for epoch in range(n_epochs):
    for batch_idx in range(iterations_per_epoch):
        otp_alpha = otp_alpha_schedule(iterations)

        # Forward pass
        loss = model.forward_with_loss(xs, otp_alpha=otp_alpha)

        # Backward pass
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        # Update EMA model
        model.update_ema()
        iterations += 1

# Sample trajectories
model.eval()
x0 = torch.randn(100, 2)  # Initial samples
with torch.no_grad():
    trajectories, times = model.sample(x0, n_steps=10)

Tutorials

Customization

Potential Types

OTP-FM supports multiple potentials based on different statistical distances:

from otpfm.potentials import (
    W2InfPotential,          # Random coupling between samples; fastest and default recommendation
    W2Potential,             # Exact Wasserstein-2 (requires pot); usually better to use W2InfPotential with pre-computed OT couplings
    MMDRBFPotential,         # MMD with RBF kernel
    KLPotential,             # KL divergence with score estimation
)

Spatiotemporal dynamics

Spatial and temporal dynamics can be tuned by changing the strengths, widths, and shapes of the potentials, e.g.:

tks = [0.1, 0.5, 0.7]  # Intermediate time points
potentials = OrderedDict({
    tks[0]: W2InfPotential(tk=tks[0], strength=500.0, lambda_type='box', width=0.1),
    tks[1]: W2InfPotential(tk=tks[1], strength=400.0, lambda_type='triangle', width=0.2),
    tks[2]: W2InfPotential(tk=tks[2], strength=100.0, lambda_type='gaussian', width=0.05),
})

Custom Velocity Networks

You can provide your own velocity network:

import torch.nn as nn

class MyVelocityNet(nn.Module):
    def forward(self, x, t1, dt):
        """
        Args:
            x: (batch, d) positions
            t1: (batch,) start times
            dt: (batch,) time intervals
        Returns:
            v: (batch, d) velocities
        """
        # Your implementation
        pass

model = OTPFM(
    d=2,
    tks=[0.5],
    potentials=potentials,
    flownet=MyVelocityNet()  # Custom network
)

Citation

If you use this code in your research, please cite:

Coming soon.

Reproducing Experiments

For reproducing OTP-FM experiments from the ICML paper, see REPRODUCIBILITY.md. For details on our benchmarking of previous methods, see https://github.com/rkansal47/OTP-FM-benchmarking.

Metadata

Release files for otpfm 1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for otpfm 1.0
File Size Uploaded Provenance Actions
otpfm-1.0.tar.gz 180.5 kB With provenance Details for otpfm-1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for otpfm 1.0
File Interpreter ABI Platform Actions
otpfm-1.0-py3-none-any.whl Python 3 none any Details for otpfm-1.0-py3-none-any.whl

Total release size: 317.1 kB

Release files / otpfm-1.0.tar.gz

Download URL otpfm-1.0.tar.gz
Size 180.5 kB
Tags Source
SHA-256 checksum
How to use checksums
a6a66c94dbc93bdc51e15215f475b705fd32c0195e7818321dedd6eb33aa83d8
BLAKE2b-256 checksum
How to use checksums
7228fc28f8209a6f694a86aa5df2ec6d1430526c8508d0957319b9e0a81f9fa3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 3, 2026.

Transparency log

Release files / otpfm-1.0-py3-none-any.whl

Download URL otpfm-1.0-py3-none-any.whl
Size 136.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0f73bfe2eb221d00cfa47fc3cdf18d37e50bb37982168888af3bee61943c36f0
BLAKE2b-256 checksum
How to use checksums
1f07d2c9b05cf9b4a0a7a7b762f7fd56b90ad50b3b06e03c476e28767974653e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 3, 2026.

Transparency log

Security for otpfm 1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for otpfm-1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jun 3, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page