BaseEstimator¶
The model contract that every estimator implements, independent of the network framework.
Overview¶
An estimator node runs the same model class in two Ray actors:
- the train actor (
<node>/train) drives it throughfalcon.core.round_trainer.RoundTrainer, which owns the round loop, the round counters, the best state and the checkpoint; - the sample actor (
<node>) drives it throughfalcon.core.model_sampler.ModelSampler, which installs the best state and falls back to the prior until one exists.
The model keeps its networks as mutable state on self and knows nothing
about rounds, publishing or Ray. The two sides exchange the best state as a
state tree: a nested dict of numpy arrays and plain values
(falcon.core.state_io). The same tree is written to
graph/<node>/best_state.npz.
Methods, grouped by who calls them:
| Called by | Methods |
|---|---|
| both | setup, groups, built, build, import_state |
| train actor | init_from_batch, train_step, evaluate, discard_mask, on_round_start, on_train_start, on_validation_end, on_round_end, set_observations, snapshot, restore, export_state, export_proposal |
| sample actor | sample_prior, sample |
| both, optional | import_proposal |
Rules that let any framework implement the contract:
snapshot()/restore()are fast in-memory copies with opaque handles;export_state()returns copies as numpy; nothing outside the model keeps references to the model's own arrays;- sampling draws its randomness from the
rngargument.
A model whose proposal is more than its networks keeps a proposal state.
FlowMatching, for example, keeps its region ladder there. The optional
hooks for it are all no-ops by default:
on_train_start(batches)receives the round's training set after the best network's baseline validation, before the first epoch;on_round_end(promoted)runs after the acceptance test, with the best state installed, and returns True when the proposal state changed;set_observations(conditions)gives the train actor the node's conditions at the observation;export_proposal(full)/import_proposal(tree)move the proposal state as a state tree. The trainer publishes the sampling subset (full=False) whenever it changes and saves the full state inbest_state.npz.
Torch estimators derive from falcon.estimators.TorchModel, which implements
the state handling for nn.Module network groups; Flow, FlowMatching
and GaussianFullCov are examples.
Writing a JAX estimator¶
JAX functions are pure, so a JAX model keeps its latest state on self and
replaces it after each compiled step. With buffer donation the step reuses the
old buffers, so memory does not double; old arrays then become invalid, and
snapshot() must copy:
import jax, jax.numpy as jnp, numpy as np, optax
from falcon.core.base_estimator import BaseEstimator
class JaxGaussian(BaseEstimator):
def build(self, init_tree):
self.params = init_params(init_tree) # pytree on the device
self.opt = optax.adamw(self.lr)
self.opt_state = self.opt.init(self.params)
self._step = jax.jit(make_step(self.opt), donate_argnums=(0, 1))
def groups(self):
return {"model": "loss"}
@property
def built(self):
return hasattr(self, "params")
def train_step(self, batch):
batch = {k: jnp.asarray(v.cpu().numpy()) for k, v in batch.items()}
self.params, self.opt_state, loss = self._step(self.params, self.opt_state, batch)
return {"loss": float(loss)}
def snapshot(self, group):
return jax.tree.map(jnp.copy, self.params) # donation invalidates old arrays
def restore(self, group, handle):
self.params = jax.tree.map(jnp.copy, handle)
def export_state(self, group):
return {"params": flatten_to_numpy(self.params)} # nested dict of np.ndarray
def import_state(self, group, tree):
self.params = unflatten_from_numpy(tree["params"])
def sample(self, rng, num_samples, conditions, mode):
key = jax.random.key(int(rng.integers(2**63 - 1)))
...
Set XLA_PYTHON_CLIENT_PREALLOCATE=false through the node's
ray.runtime_env.env_vars when a JAX actor shares its GPU with other actors;
JAX otherwise reserves most of the GPU memory at start-up.
Class Reference¶
BaseEstimator
¶
Bases: ABC
Model of one estimator family, used unchanged by both actors of a node.
The train actor (<node>/train) drives it through RoundTrainer, the
sample actor (<node>) through ModelSampler. The model holds its
networks as mutable state on self and knows nothing about rounds,
publishing or Ray; each actor calls only the methods it needs.
Lifecycle:
__init__: pure config storage (flat YAML kwargs). The round-loop parameters are read from attributes named like theRoundConfigfields.setup(): runtime wiring inside the actor (simulator, keys, device).build(init_tree): creates the networks. The trainer gets the tree frominit_from_batch()on its first batch; the sampler gets it from the published state.
State rules, so that any framework can implement this:
snapshot()/restore()keep and restore fast in-memory copies of a network group; the handles are opaque.export_state()returns copies as numpy trees;import_state()installs such a tree. Nothing outside the model keeps references to the model's own arrays.- Randomness in sampling comes from the
rngargument.
Example::
graph.add_node("z", estimator=Flow(max_epochs=300, net_type="nsf"))
setup
¶
Wire up runtime components inside the actor.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
simulator_instance
|
Live prior/simulator of the node. |
required | |
theta_key
|
str
|
Name of the node being estimated. |
required |
condition_keys
|
List[str]
|
Evidence and scaffold node names. |
required |
Source code in src/falcon/core/base_estimator.py
set_num_threads
¶
Limit the CPU threads of the network framework in this process.
Called by the actor, whose CPU budget is shared with the node's other
actor. Frameworks that honour OMP_NUM_THREADS at import need
nothing here.
Source code in src/falcon/core/base_estimator.py
batch_keys
¶
Buffer keys the training batches must contain.
groups
abstractmethod
¶
Network groups mapped to their validation metric; the first is primary.
Groups are compared and promoted independently; a round is accepted when the primary group improves.
Source code in src/falcon/core/base_estimator.py
init_from_batch
abstractmethod
¶
build
abstractmethod
¶
snapshot
abstractmethod
¶
restore
abstractmethod
¶
export_state
abstractmethod
¶
import_state
abstractmethod
¶
load_legacy
¶
Read a checkpoint written before best_state.npz existed.
Returns a tree in the published format, or None.
train_step
abstractmethod
¶
evaluate
abstractmethod
¶
Validation metrics for one batch, without updating anything.
Must include "loss" (early stopping) and the metric of every group (acceptance test).
discard_mask
abstractmethod
¶
Discard test for one batch with the primary group; True = discard.
Called after an accepted round, when the primary group equals the best network.
on_round_start
¶
Called at the start of every round after the first, once the best state has been restored (e.g. reset the learning rate).
on_validation_end
¶
Called after each validation within a round (e.g. LR scheduler step).
Returns:
| Type | Description |
|---|---|
Optional[Dict[str, float]]
|
Optional dict of extra metrics for the epoch summary line. |
Source code in src/falcon/core/base_estimator.py
on_train_start
¶
Called once per round before the first epoch, with the round's training batches.
Runs after the best network was validated on the round's data, so data-dependent set-up done here (e.g. refitting a normalisation to the whole training set) does not affect that baseline. The batches are not shuffled.
Source code in src/falcon/core/base_estimator.py
on_round_end
¶
Called after the acceptance test, with the best state of every group installed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
promoted
|
Dict[str, bool]
|
Whether each group was promoted in this round. |
required |
Returns:
| Type | Description |
|---|---|
bool
|
True if the proposal state changed and must be published. |
Source code in src/falcon/core/base_estimator.py
set_observations
¶
Condition values at the observation, given to the train actor before training.
Only called when every condition of the node is fixed by the observations; evidence derived from them is already simulated.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
conditions
|
Conditions
|
Condition arrays (numpy) with a leading dimension of 1. |
required |
Source code in src/falcon/core/base_estimator.py
export_proposal
¶
Proposal state that is not part of any network group; None if there is none.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
full
|
bool
|
Everything needed to resume training (for the checkpoint) instead of only what sampling needs (for the sample actors). |
required |
Source code in src/falcon/core/base_estimator.py
import_proposal
¶
sample_prior
abstractmethod
¶
sample
abstractmethod
¶
Samples from the networks: {"value": ndarray, "log_prob": ndarray}.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rng
|
Generator
|
Source of randomness for this call. |
required |
num_samples
|
int
|
Number of samples. |
required |
conditions
|
Conditions
|
Condition arrays (numpy); a leading dimension of 1 is broadcast over all samples. |
required |
mode
|
str
|
|
required |
Source code in src/falcon/core/base_estimator.py
RoundConfig
dataclass
¶
RoundConfig(max_epochs=100, patience_epochs=16, val_every_epochs=1, max_rounds=None, patience_rounds=10, prior_rounds=0, batch_size=128, val_batch_size=None, wait_for_sims=0, max_cache_samples=0, cache_on_device=False, discard_samples=False)
Parameters of the round-based training loop (see docs/training.md).
TorchModel
¶
Bases: BaseEstimator
Implements the state and sampling plumbing of BaseEstimator for torch.
Subclasses register their networks with _set_groups() in build()
and implement:
init_from_batch()/build()train_step()/val_step()/discard_test()_sample_prior()/_sample()
val_step, discard_test and _sample run without gradients, and
sampling runs with torch's random state seeded from the given rng.
discard_test
¶
Boolean mask (tensor or array) of length len(batch); True = discard.