BioNeMo Inference Runtime (BioIR) uses a Torch-native runtime for iterative
generative sampling. Model samplers own model-specific conditioning and
prediction calls, while _torch/sampling owns reusable rollout contracts,
iteration, random state, hooks, and integration algorithms.
flowchart LR
M["Model<br/>Boltz / OpenFold3 / Protenix"]
S["Model-specific sampler<br/>conditioning, reference draw, schedule, predict()"]
R["GenerativeRunner<br/>seeded step loop"]
I["Concrete integrator<br/>AF3 / Boltz EDM<br/>flow matching"]
T["DenoiseIntegratorTemplate.step()<br/>fixed skeleton:<br/>prepare → hooks → predict → hooks → complete"]
D["Denoiser / velocity network<br/>model-owned nn.Module"]
O["Sampled coordinates or product-space state"]
M --> S --> R --> I --> O
I -.->|"extends fixed skeleton"| T
I -->|"sampler predict()"| D
The Template Method separates the invariant step() skeleton from
algorithm- and model-specific operations. Each concrete integrator implements
those operations directly. OpenFold3 and Protenix use AF3EDMIntegrator with
tensor state, Boltz uses BoltzEDMIntegrator with structured state,
and FlowMatchingIntegrator provides the default product-space flow.
The top-level model owns trunk execution, model configuration, checkpoint
loading, and the denoising neural network. The denoiser remains an nn.Module
and is injected into the model-specific sampler:
diffusion_module = DiffusionModule(config=...)
diffusion_sampler = ModelDiffusionSampler(
config=...,
diffusion_module=diffusion_module,
)This keeps neural weights separate from rollout orchestration and allows optimized submodules to be replaced through the model registry.
The sampler adapts the model to the shared runtime. It:
- prepares model-specific conditioning and masks;
- draws the initial state the rollout starts from;
- creates the rollout plan and
SamplingContext; - binds invariant tensors into a
predict(model_input, time)closure; - selects the concrete integrator and hooks;
- decodes the final runtime state into the model's public output.
The expensive denoiser call remains model-specific. The step loop does not.
GenerativeRunner owns algorithm-independent rollout mechanics:
- initialize state through the integrator;
- iterate for
integrator.num_steps(plan); - update
context.step_index; - honor cancellation;
- return
SamplingResultwith the final state and seed.
The runner does not know whether a prediction is denoised coordinates, velocity, token logits, or a model-specific structured object.
DenoiseIntegratorTemplate fixes the denoiser-driven step order:
prepare step
-> before-denoise hooks
-> extract model input and time
-> predict
-> attach prediction
-> after-denoise hooks
-> complete mathematical update
Concrete integrators implement this contract directly:
AF3EDMIntegratorimplements the AF3-style first-order EDM algorithm over coordinate tensors for OpenFold3 and Protenix.BoltzEDMIntegratorimplements the same template for Boltz's structured state, including atom masks, previous denoised coordinates, and token representations.FlowMatchingIntegratorimplements deterministic Euler over a dict of per-modality tensors with linear-interpolant clean endpoints.
None reimplements step(). Each supplies the template's typed operations:
initialization, step preparation, model inputs, prediction attachment, and the
mathematical update.
DenoiseHookPipeline applies ordered extensions around each prediction call.
Boltz uses hooks for:
- random rigid augmentation;
- churn-noise injection;
- potential guidance and particle resampling;
- token-representation accumulation;
- optional rigid alignment.
Hook constructors own rollout-local configuration and mutable state. Random
operations receive the explicit generator from SamplingContext.
The EDM implementation follows the first-order portion of Algorithm 2 from Karras et al., Elucidating the Design Space of Diffusion-Based Generative Models:
The initial coordinates are sampled at the largest noise level:
Each sampling step temporarily adds noise, invokes the denoiser, and advances toward the next noise level:
In the implementation, noise_scale supplies the noise multiplier and
step_scale supplies the Euler-step multiplier.
This is the AF3/Boltz first-order adaptation, not the paper's full Heun
sampler. It intentionally omits Algorithm 2's second denoiser evaluation and
second-order correction. Churn gating, step_scale, rigid augmentation, and
model hooks follow model-specific inference algorithms.
OpenFold3 and Protenix use:
EDMRolloutPlan;AF3EDMIntegrator;- a model-specific
predictclosure around theirDiffusionModule.
Their differences remain in denoiser signatures, conditioning, schedule termination, and optional caches.
Boltz uses:
EDMRolloutPlanwith an appended terminal zero;BoltzEDMIntegrator;- a model-specific ordered hook pipeline;
- a chunked
predictclosure around its injectedDiffusionModule.
This preserves steering, resampling, alignment, token accumulation, and sample chunking without duplicating the EDM loop.
Flow matching transports a reference sample to a data sample along a time-parameterized path. For the linear (conditional optimal-transport) interpolant used here,
the network predicts the velocity v = dx_t/dt. One interval is an Euler step,
and the clean endpoint implied by a velocity follows from differentiating the
interpolant:
Time increases toward data at t = 1, the opposite direction to EDM's
decreasing noise levels. The clean endpoint is both a rollout output and, when
self_condition is enabled, the next step's self-conditioning input; it is
zero-filled before the first prediction, so self-conditioning is inactive on
step 0.
Sampling is product-space: a model may evolve several modalities on their own
time grids through one shared network evaluation, so states, times, and
velocities are keyed by modality name. FlowMatchingRolloutPlan requires every
modality to use the same number of intervals.
The rollout needs a sample at t = 0 before the first interval. The shared
runtime does not draw it. Shapes, padding, and centering are model-specific in
a way generic code cannot guess, so FlowMatchingIntegrator accepts an
initial_value supplied by the model.
This keeps the two ends of the path in different layers: _torch/sampling owns
the transport from t = 0 to t = 1, and the model owns what t = 0 means.
bionemo_ir/_torch/sampling/
contracts.py shared types, plans, context, and protocols
runner.py algorithm-independent rollout loop
denoise_integrator.py invariant denoiser-step ordering
hooks.py ordered before/after-denoise hooks
edm.py EDM schedule, math, and tensor integrator
flow_matching.py flow schedule, math, and product-space integrator
Model-specific rollout code lives directly beside its model.