Quickstart#

Install#

From the project root:

pixi run setup

If you are not using pixi:

pip install -e .

Core deterministic flow matching#

import torch
import nami

field = nami.VelocityField(dim=8)
x_target = torch.randn(32, 8)
x_source = torch.randn_like(x_target)

loss = nami.fm_loss(field, x_target, x_source)
loss.backward()

Stochastic flow matching (linear-path MVP)#

import torch
import nami

field = nami.VelocityField(dim=8)
x_target = torch.randn(32, 8)
x_source = torch.randn_like(x_target)

loss = nami.stochastic_fm_loss(
    field,
    x_target,
    x_source,
    gamma=nami.BrownianGamma(),
)

Deterministic parity with ZeroGamma#

import torch
import nami

field = nami.VelocityField(dim=8)
x_target = torch.randn(32, 8)
x_source = torch.randn_like(x_target)

det = nami.fm_loss(field, x_target, x_source, reduction="none")
stoch = nami.stochastic_fm_loss(
    field,
    x_target,
    x_source,
    gamma=nami.ZeroGamma(),
    reduction="none",
)
assert torch.allclose(det, stoch, atol=1e-6)

Conditional generation pattern#

import torch
from torch import nn
import nami

class ConditionalField(nn.Module):
    def __init__(self, dim: int, context_dim: int):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(dim + context_dim + 1, 128),
            nn.SiLU(),
            nn.Linear(128, dim),
        )

    @property
    def event_ndim(self) -> int:
        return 1

    def forward(self, x: torch.Tensor, t: torch.Tensor, c: torch.Tensor | None = None) -> torch.Tensor:
        t_exp = t.unsqueeze(-1).expand(*x.shape[:-1], 1)
        inputs = [x, t_exp]
        if c is not None:
            inputs.append(c)
        return self.net(torch.cat(inputs, dim=-1))

field = ConditionalField(dim=8, context_dim=4)
base = nami.StandardNormal(event_shape=(8,))
solver = nami.RK4(steps=32)
fm = nami.FlowMatching(field, base, solver)

context = torch.randn(16, 4)
process = fm(context)
samples = process.sample((1,))  # shape: (1, 16, 8)

Diffusion process#

Nami supports score-based diffusion models. The same field architecture can be reused, just swap in a noise schedule and an SDE solver.

import nami

# same field architecture works
model = nami.VelocityField(dim=8)
schedule = nami.VPSchedule(beta_min=0.1, beta_max=20.0)
solver = nami.EulerMaruyama(steps=100)

diffusion = nami.Diffusion(
    model=model,
    schedule=schedule,
    solver=solver,
    parameterization="eps",  # or "score", "x0"
    event_shape=(8,),
)

process = diffusion(None)
samples = process.sample((64,))

Transforms between parameterisations#

import nami

score_model = nami.ScoreFromNoise(eta_model, nami.BrownianGamma())
drift_model = nami.DriftFromVelocityScore(v_model, score_model, nami.BrownianGamma())
mirror_velocity = nami.MirrorVelocityFromScore(score_model, nami.BrownianGamma())

Build and serve docs locally#

# one-shot HTML build
pixi run docs

# live docs server with auto rebuild
pixi run docs-serve