FlowMatching Estimator¶
Flow-matching posterior estimation with a truncated-prior proposal.
Overview¶
FlowMatching learns a conditional flow q_c(u | x) and a marginal flow
q_m(u) over the standard-normal latent space u of the prior. Each flow is a
velocity field v(w, t, s), an MLP trained by flow matching: it regresses the
straight-line bridge from N(0, I) at t = 0 to the data at t = 1. Draws
come from integrating the ODE forward with Euler steps. Densities come from
the continuous change of variables, with the ODE run backward.
Key features:
- Exact per-round whitening. Each flow works in the frame
wof a ZCA whitener that is refit to the round's training data, so the velocity field always sees a unit-scale target, however far the data has contracted. The latent-frame NLL includes the whitener's log-determinant, so rounds with different whiteners are still compared correctly. - Moving averages. The evaluated, published and saved networks are exponential moving averages of the trained ones.
- Truncated-prior proposal. Proposals are prior samples truncated to a region read off the conditional flow. A hysteretic two-region ladder moves that region once per round (below).
- No importance weights for the posterior. The training data is the
truncated prior, so the conditional flow itself estimates the posterior.
Posterior samples are conditional-flow draws cut at its
x_sigmacontour.
Note
FlowMatching requires a TransformedPrior such as Product;
it works in the "standard_normal" mode.
The proposal: a region ladder¶
A region is a level set {ln q_c(u) > thr} of a conditional flow at the
observation, intersected with its parent region. A region is frozen when it is
minted. It keeps copies of the flows it was read off, the embedded observation
and its threshold, so it keeps meaning the same thing as the networks change.
Two nested regions are live:
- the inner region I must contain the
x_sigmamass of the current conditional flow; - the outer region O is what proposals sample, so the training data reaches beyond I.
Wider regions wait on a stack. After every round, the train actor measures
m_out, the current conditional flow's mass outside I, and moves the ladder:
| Condition | Action |
|---|---|
m_out > leak(x_sigma) |
EXPAND: I ← O, O ← pop the stack (or the prior) |
| otherwise | mint a candidate at leak(x_sigma + delta_sigma) inside I |
candidate mass ≤ vratio × mass of I (with v_min_ess) |
CONTRACT: push O, O ← I, I ← candidate |
| otherwise | hold |
leak(x) is the two-sided normal tail mass: x = 3 means 2.7e-3 of the mass
may lie outside I. A region is sampled by importance sampling. The sampler
draws from the region's marginal flow, keeps the draws inside the region,
weights them by prior over marginal flow, and resamples without replacement,
so no simulation is spent twice. The pool of weighted draws is extended until
its effective sample size is ess_factor times the draws taken from it, so
proposals stay faithful samples of the truncated prior even where the
marginal flow has a hole.
The ladder needs the node's conditions at the observation. The train actor gets them at launch; evidence derived from observed nodes is simulated once from the observation. Without them, e.g. in amortized runs, the proposal stays the prior.
The ladder logs [mint] and [ladder] lines to graph/<node>/train/output.log,
and ladder:* metrics. The sample actor logs proposal:acceptance,
proposal:eps (the ESS per accepted draw) and proposal:pool_ess.
Reference settings¶
The defaults are the settings of the reference run that produced the O1b
region of the LDC MBHB study (t7b_s4k_b8k_max64k_reject_essgate: 9
parameters, 84 rounds, 3.3e5 simulations on one A100). Its buffer policy maps
onto falcon's buffer as follows:
| Reference (O1b) | falcon |
|---|---|
| drop samples outside the new outer region, oldest first, down to a floor of 8000 | discard_samples: true (default) with buffer.min_samples: 8000 |
| stop generating while 64000 samples are inside the region | buffer.max_samples: 64000, buffer.simulate_when_full: false |
| 20% validation, handled like the training set | buffer.validation_fraction: 0.2 |
| 4000 new simulations per round, drawn from the new outer region | wait_for_sims: 4000 (see Training Loop) |
Keeping the buffer inside the outer region matters: the whitener can only zoom in as far as the training data has contracted.
Configuration¶
estimator:
_target_: falcon.estimators.FlowMatching
max_epochs: 200
val_every_epochs: 3 # validation solves the density ODE
patience_epochs: 12
lr: 3.0e-4
embedding:
_target_: model.E
_input_: [x]
hidden: 256
layers: 4
See examples/01_minimal/config_flow_matching.yml for a complete example.
Configuration Reference¶
Networks and training¶
| Parameter | Type | Default | Description |
|---|---|---|---|
hidden |
int | 512 | Velocity-field MLP width |
layers |
int | 6 | Velocity-field MLP hidden layers |
time_dim |
int | 32 | Number of Fourier time features (even) |
layernorm |
bool | false | LayerNorm after every hidden layer |
lr |
float | 3e-4 | Learning rate; reset at the start of every round |
betas |
tuple | (0.9, 0.9) | AdamW betas |
grad_clip |
float | 1.0 | Gradient-norm clip, per flow (0 = off) |
ema_decay |
float | 0.995 | Decay of the moving averages, per step |
embedding_epochs |
int | 10 | Train the embedding only in the first N epochs of each round, then keep it fixed (null = throughout) |
time_late_k, time_late_mix |
float | 8.0, 0.5 | Training times: a mix share drawn from k t^(k-1), which favours the sharp end of the path |
sample_steps |
int | 128 | Euler steps per draw |
density_steps |
int | 32 | ODE steps per density |
div_probes |
int | 4 | Hutchinson probes for proposal and posterior densities (0 = exact trace; validation is always exact) |
eval_chunk |
int | 16384 | Rows per forward pass when sampling or evaluating densities |
wait_for_sims |
int | 0 | After a round, wait until this many samples were simulated from the proposal it published before the next round |
val_batch_size |
int | 16384 | Batch size of validation and of the discard sweep. The density ODE is bound by its number of small steps, not by the rows, so one large batch costs about as much as one of 128 |
embed_chunk |
int | 512 | Rows per forward pass of the embedding outside training steps |
Region ladder¶
| Parameter | Type | Default | Description |
|---|---|---|---|
x_sigma |
float | 3.0 | Mass the inner region must hold, in sigmas; also the posterior truncation |
delta_sigma |
float | 1.0 | Dead band: new inner regions are minted at x_sigma + delta_sigma |
min_keep_frac |
float | 0.01 | At least this share of a region's own marginal-flow draws fall inside it |
vratio |
float | 0.8 | Contract only if the candidate has at most this share of the inner region's prior mass |
v_min_ess |
float | 1000 | Effective sample size that mass ratio must carry |
v_max_draws |
int | 1048576 | Maximum marginal-flow draws spent on it |
chain_depth |
int | 1 | Ancestors tested for region membership (-1 = all) |
n_region |
int | 65536 | Draws per pass when minting or sampling a region |
n_mout |
int | 65536 | Draws for m_out |
max_sample_passes |
int | 64 | Maximum passes per proposal request |
ess_factor |
float | 4.0 | Extend the pool of region draws until its ESS is this many times the draws taken from it |
readout_draws |
int | 65536 | Draws that set the posterior truncation |
The defaults are the O1b settings (above), sized for an A100; on a CPU,
reduce hidden, layers and the draw counts.
The training loop parameters (max_epochs, patience_epochs,
val_every_epochs, max_rounds, patience_rounds, prior_rounds,
batch_size, lr_decay_factor, lr_patience_epochs, cache_on_device,
max_cache_samples) are as in Flow; see
Training Loop. discard_samples defaults to true here:
accepted rounds discard samples outside the outer region.
Training details¶
- Network groups. The first group is the conditional flow together with
the embedding, judged on
nll. The second is the marginal flow, judged onnll_aux. Both are latent-frame NLLs with the exact trace. Early stopping watchesloss, the conditional NLL without the rare rows whose ODE ran away. The training loss is the flow-matching loss, so thetrain_lossandval_lossin the log are on different scales;val_fmis the validation flow-matching loss. - Per-sample results. The latent parameters of a sample never change, and
its summary only changes while the embedding trains. Both are kept on the
device under the sample's buffer id, so after
embedding_epochsa training step no longer runs the embedding or touches the data arrays, and validation runs the embedding only for samples it has not seen since the embedding last moved. - Whitener frames. Every round refits each flow's whitener to the round's training data before the first epoch. A flow that was not promoted in the previous round keeps the frame of the best network it resumes from. Once the proposal region has settled, rounds can alternate between a rejected round in a refit frame and an accepted round in the held one; the O1b reference run did this from round ~50 on.
- Discretisation. Densities and draws come from fixed-step Euler integration. When the conditional flow must contract its target strongly, the density at the default steps is biased high by a fraction of a nat. The whitener's zoom, as the proposal region contracts, keeps that contraction moderate.
Class Reference¶
FlowMatching
¶
FlowMatching(*, max_epochs=200, lr=0.0003, embedding=None, device=None, batch_size=128, val_batch_size=16384, embed_chunk=512, wait_for_sims=0, patience_epochs=12, val_every_epochs=3, max_rounds=None, patience_rounds=10, prior_rounds=0, cache_on_device=False, max_cache_samples=0, discard_samples=True, hidden=512, layers=6, time_dim=32, layernorm=False, betas=(0.9, 0.9), grad_clip=1.0, lr_decay_factor=1.0, lr_patience_epochs=8, ema_decay=0.995, embedding_epochs=10, time_late_k=8.0, time_late_mix=0.5, sample_steps=128, density_steps=32, div_probes=4, eval_chunk=16384, x_sigma=3.0, delta_sigma=1.0, min_keep_frac=0.01, vratio=0.8, v_min_ess=1000, v_max_draws=1048576, chain_depth=1, n_region=65536, n_mout=65536, max_sample_passes=64, ess_factor=4.0, readout_draws=65536)
Bases: TorchModel
Flow-matching posterior estimation with a truncated-prior proposal.
Works in the standard-normal latent space of a TransformedPrior (e.g.
Product). Training runs in rounds (see RoundTrainer); every round
refits the whitener of each flow to the round's training data, except for
a flow that was not promoted in the previous round, which keeps the frame
it was trained in.
Network groups: the conditional flow with the embedding (primary, judged
on the validation NLL nll) and the marginal flow (nll_aux). Both
NLLs are latent-frame densities, comparable across whitener refits. Early
stopping watches loss, the conditional NLL without the rare rows whose
ODE ran away. The training loss is the flow-matching loss. The evaluated,
published and saved networks are exponential moving averages of the
trained ones, and every round resumes training from them.
The proposal is the prior truncated to the outer region of the region
ladder (see falcon.estimators.region_ladder), which moves once per
round in the train actor. It needs the node's conditions at the
observation; without them (e.g. amortized runs) it stays the prior.
The defaults are the settings of the reference run that produced the O1b
region of the LDC MBHB study (t7b_s4k_b8k_max64k_reject_essgate). Its
buffer policy corresponds to discard_samples: true with
buffer.min_samples as the floor, buffer.max_samples as the cap,
buffer.simulate_when_full: false and buffer.validation_fraction:
0.2; its 4000 simulations per round, drawn from the new outer region,
correspond to wait_for_sims: 4000.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
max_epochs
|
int
|
Maximum epochs per round. |
200
|
lr
|
float
|
Learning rate; reset at the start of every round. |
0.0003
|
embedding
|
Embedding config dict (with |
None
|
|
device
|
Optional[str]
|
Device string (e.g. |
None
|
batch_size
|
int
|
Mini-batch size. |
128
|
val_batch_size
|
Optional[int]
|
Batch size of validation and of the discard sweep
( |
16384
|
embed_chunk
|
int
|
Rows per forward pass of the embedding outside training steps. |
512
|
wait_for_sims
|
int
|
After each round, wait until this many samples have been simulated since the round published its proposal, so the next round trains on data from the new proposal region (0 = start the next round at once). |
0
|
patience_epochs
|
int
|
End the round once the best validation loss is this many epochs old (checked at validations). |
12
|
val_every_epochs
|
int
|
Validate after every N-th epoch (and at the last epoch); validation solves the density ODE, so it is not cheap. |
3
|
max_rounds
|
Optional[int]
|
Maximum number of rounds ( |
None
|
patience_rounds
|
int
|
Stop training after this many rejected rounds in a row. |
10
|
prior_rounds
|
int
|
Rounds that simulate from the prior before switching to the learned proposal. |
0
|
cache_on_device
|
bool
|
Cache training data on the estimator device. |
False
|
max_cache_samples
|
int
|
Cap on cached training samples (0 = all). |
0
|
discard_samples
|
bool
|
After accepted rounds, discard samples outside the
outer region (oldest first, never below |
True
|
hidden
|
int
|
Width of the velocity-field MLP. |
512
|
layers
|
int
|
Hidden layers of the velocity-field MLP. |
6
|
time_dim
|
int
|
Number of Fourier time features (even). |
32
|
layernorm
|
bool
|
LayerNorm after every hidden layer. |
False
|
betas
|
tuple
|
AdamW beta coefficients. |
(0.9, 0.9)
|
grad_clip
|
float
|
Gradient-norm clip, separately for the conditional flow with the embedding and for the marginal flow (0 = off). |
1.0
|
lr_decay_factor
|
float
|
LR decay factor for the plateau scheduler (1.0 = no decay). |
1.0
|
lr_patience_epochs
|
int
|
Epochs without validation improvement before LR decay. |
8
|
ema_decay
|
float
|
Decay of the exponential moving averages, per step. |
0.995
|
embedding_epochs
|
Optional[int]
|
Train the embedding only in the first N epochs of
each round, then keep it fixed ( |
10
|
time_late_k
|
float
|
Exponent of the late-time tilt of the training times. |
8.0
|
time_late_mix
|
float
|
Fraction of training times drawn from the tilt. |
0.5
|
sample_steps
|
int
|
Euler steps when drawing from a flow. |
128
|
density_steps
|
int
|
ODE steps when evaluating a density. |
32
|
div_probes
|
int
|
Hutchinson probes for the divergence in proposal and posterior densities (0 = exact trace); validation always uses the exact trace. |
4
|
eval_chunk
|
int
|
Rows per forward pass when sampling or evaluating densities. |
16384
|
x_sigma
|
float
|
The inner region must contain this many sigmas of the conditional flow's mass (as two-sided normal tail mass: 3 means 2.7e-3 may lie outside); also the posterior truncation. |
3.0
|
delta_sigma
|
float
|
Half-width of the ladder's dead band; new inner regions
are minted at |
1.0
|
min_keep_frac
|
float
|
At least this fraction of a region's own marginal-flow draws must fall inside it. |
0.01
|
vratio
|
float
|
Contract only if the candidate has at most this fraction of the inner region's prior mass. |
0.8
|
v_min_ess
|
float
|
Effective sample size the mass ratio must carry to contract. |
1000
|
v_max_draws
|
int
|
Maximum marginal-flow draws spent on that ratio. |
1048576
|
chain_depth
|
int
|
Ancestors tested when checking membership in a region (-1 = all). |
1
|
n_region
|
int
|
Draws per pass when minting or sampling a region. |
65536
|
n_mout
|
int
|
Draws for the mass outside the inner region. |
65536
|
max_sample_passes
|
int
|
Maximum passes when filling a proposal request. |
64
|
ess_factor
|
float
|
Extend the pool of weighted region draws until its effective sample size is this many times the draws taken from it. |
4.0
|
readout_draws
|
int
|
Draws used to set the posterior truncation. |
65536
|
Source code in src/falcon/estimators/flow_matching.py
413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 | |
build
¶
Source code in src/falcon/estimators/flow_matching.py
train_step
¶
Source code in src/falcon/estimators/flow_matching.py
val_step
¶
Source code in src/falcon/estimators/flow_matching.py
on_train_start
¶
Source code in src/falcon/estimators/flow_matching.py
on_round_end
¶
Source code in src/falcon/estimators/flow_matching.py
RegionLadder
¶
Inner region I, outer region O and a stack of wider regions.
Regions are records (plain values and numpy trees) keyed by id; the prior
is the permanent region "prior", which is never stored.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
LadderConfig
|
Ladder parameters. |
required |
load_pair
|
Callable[[Dict[str, Any]], FlowPair]
|
Builds the |
required |
param_dim
|
int
|
Dimension of the latent space. |
required |
device
|
Device of the flows. |
required |
cut
¶
Membership of latent points in a region, including its parents up to depth.
A region is the intersection of its own level set with its parent, so
a point can only be in a child if it is in the parent; depth (the
chain_depth by default, -1 for all) limits how many ancestors are
tested. Ancestors are evaluated only on the survivors.
mass_outside
¶
The current conditional flow's mass outside a region.
The draws come from the mixture Q = (q_c + q_m) / 2 and are weighted by q_c / Q (at most 2): the marginal half populates the outside densely, so a tail mass of 1e-6 is an average of many small terms instead of a count of rare ones.
mint
¶
Threshold and prior mass of a candidate region at target_leak inside parent.
The threshold is the target_leak quantile of ln q_c over draws
from the mixture Q = (q_c + q_m) / 2, weighted by q_c / Q. It is then
lowered, if necessary, so that at least min_keep_frac of the
marginal flow's draws survive the cut, which widens the region (the
safe direction) and keeps sampling it affordable.
The prior mass V of the level set and the ratio R of the
candidate's mass inside the parent to the parent's mass are estimated
on marginal-flow draws weighted by prior / q_m; more draws are added
until R carries v_min_ess or v_max_draws is reached.
Returns:
| Type | Description |
|---|---|
Optional[Dict[str, float]]
|
The candidate's statistics, or None if no draw had a finite density. |
step
¶
Move the ladder once, with the current best flows; returns the action.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
current
|
FlowPair
|
The current best flows at the observation. |
required |
trees
|
Dict[str, Any]
|
What a region minted now stores to rebuild |
required |
Returns:
| Type | Description |
|---|---|
str
|
|
sample
¶
n latent draws from the prior truncated to O, with their log density.
Draws are taken without replacement from a pool of weighted draws of
O, which lives until O changes. Before taking them, the pool
is extended until its effective sample size is ess_factor times
everything taken from it so far, this request included (at most
max_sample_passes passes; a pool that misses the target stops
enforcing it).