Skip to content

NN build system: characterization tests + component registry (steps 1-2)#390

Draft
max-dax wants to merge 9 commits into
hackathon-1from
nn-build-system
Draft

NN build system: characterization tests + component registry (steps 1-2)#390
max-dax wants to merge 9 commits into
hackathon-1from
nn-build-system

Conversation

@max-dax

@max-dax max-dax commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

First two steps of the NN build-system refactor, tracking hackathon/NN_Build_System_Design.md. Draft so the interfaces are visible early; steps 3+ (dict batches, per-architecture dim inference replacing autocomplete_model_kwargs, new config schema) will land here as they are ready.

Step 1 — characterization tests (tests/core/test_build_model.py)

Pins current behavior of the build path before it moves: build_model_from_kwargs dispatch, autocomplete_model_kwargs (plain + GNPE), end-to-end forward passes for all three model types, GNPE added_context embedding, the unconditional flow (models-as-priors path), SVD initial-weight seeding, old nsf+embedding schema mapping, save/rebuild round trip.

Documents (without fixing) a latent bug on main: get_theta_embedding_net reads frequencies from the wrong level of theta_embedding_kwargs, so any nonzero value crashes the FMPE/score build — the fmpe example only works because it uses frequencies: 0.

Step 2 — registry + NeuralDistribution rename

  • dingo/core/registry.py: generic Registry with four-step name resolution — registered short name → installed entry points (group dingo.architectures) → dotted import path → /path/to/file.py:Class. Third-party architectures no longer require editing dingo source (pip plugin or plain file).
  • The three model types register themselves; build_model_from_kwargs dispatches via the registry instead of the hardcoded models_dict. A file-path plugin distribution works end-to-end (tested).
  • Name lookup is case-sensitive; the historical case-insensitivity for built-in type names moved into update_model_config (the back-compat boundary), so old checkpoints still load.
  • BasePosteriorModelNeuralDistribution: "posterior" was too narrow — the same class models priors/proposals (e.g. the unconditional flow). A lasting alias BasePosteriorModel keeps existing imports and other branches working.

Compatibility

Old checkpoints and settings load unchanged (pinned by the step-1 tests, which pass unmodified against step 2). tests/core: 67 passed.

Next (step 3) — for reviewers to weigh in on

The metadata contract this build system will write — inference_parameters / context_parameters / standardization — is exactly what FlowFactor.from_model on sampler-revamp consumes; the concrete schema will be proposed here and needs sign-off from the sampler group.

🤖 Generated with Claude Code

@max-dax

max-dax commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator Author

Added the concrete metadata-contract proposal (the "(1)" item from the PR description): commit 4456a03 adds inference_parameters / context_parameters / standardization as properties on NeuralDistribution — the supported interface for samplers, working on old checkpoints unchanged.

Full proposal for the sampler group (incl. the step-3 dict-batch call-signature migration plan and the exact factors.py before/after): hackathon/NN_Sampler_Interface.md. Group A sign-off on that doc is the remaining coordination point.

max-dax and others added 3 commits July 12, 2026 17:43
Pin current behavior ahead of the NN build-system refactor (hackathon/
NN_Build_System_Design.md, step 1): build_model_from_kwargs dispatch,
autocomplete_model_kwargs dimensional completion (plain + GNPE),
end-to-end forward passes for all three posterior model types,
GNPE added_context embedding, unconditional flow, SVD initial-weight
seeding, old nsf+embedding schema mapping, and save/rebuild round trip.

Also documents (without fixing) a latent bug: get_theta_embedding_net
reads 'frequencies' from the wrong settings level, so any nonzero value
crashes the FMPE/score build.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Step 2 of the NN build-system refactor (hackathon/NN_Build_System_Design.md):

- dingo/core/registry.py: generic Registry with four-step name resolution
  (registered name -> entry points ('dingo.architectures') -> dotted import
  path -> 'file.py:Class'), plus the NEURAL_DISTRIBUTIONS / EMBEDDING_NETS /
  CONTEXT_MERGERS instances. Third-party architectures no longer require
  editing dingo source.
- The three model types register themselves; build_model_from_kwargs
  dispatches via the registry instead of the hardcoded models_dict, so
  posterior_model_type can also name a plugin (tested end-to-end with a
  file-path plugin).
- Name lookup is now case-sensitive; the historical case-insensitivity for
  built-in type names moved into update_model_config (the back-compat
  boundary), so old checkpoints still load.
- BasePosteriorModel -> NeuralDistribution ('posterior' was too narrow: the
  same class models priors/proposals, e.g. the unconditional flow). Lasting
  alias BasePosteriorModel kept for existing imports and branches.

Behavior pinned by tests/core/test_build_model.py is unchanged (all pass).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
inference_parameters / context_parameters / standardization as properties:
the interface FlowFactor.from_model (sampler-revamp) consumes, so samplers
stop dict-spelunking metadata. Reads the model's own train_settings['data']
(matching core/samplers.py semantics, also for unconditional models); works
on old checkpoints unchanged. Proposal doc: hackathon/NN_Sampler_Interface.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
max-dax and others added 3 commits July 12, 2026 18:25
Batches are now dicts of named tensors end to end (NN_Build_System_Design
§4.4): SelectKeys replaces UnpackDict at the end of the training transform
chain, train/test epochs move a dict to device, and the NeuralDistribution
methods sample / sample_and_log_prob / log_prob / loss take a single context
dict (None for unconditional models) instead of positional *context tensors.

FlowWrapper and ContinuousFlow route context[k] for k in context_keys into
the embedding network, so tensor ordering is declared once at build time
instead of being implied by the loader's key order; a missing key raises a
ValueError naming it. Tensors stay positional inside the network modules.
autocomplete_model_kwargs reads the named sample, which removes the
GNPE-proxy detection via IndexError and the overloaded third data slot.
Sampler call sites and SampleDataset build the context dict directly.

The sampler-side dict signature is part 2 of the NN <-> sampler interface
(hackathon/NN_Sampler_Interface.md §2); the sampler-revamp call-site updates
follow once both branches are on hackathon-1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New model-settings schema (NN_Build_System_Design §4.3/4.5/4.7): a model
declares distribution / embedding_net / context_merger, each a registry type
plus a flat kwargs dict. Embedding networks follow a small contract
(input_keys, output_dim, complete_settings): dense_svd (the classic
LinearProjectionRB + DenseResidualNet stack) and the concat context merger
are the first registered components. NeuralDistribution.build_embedding_net
assembles them generically, so initialize_network loses its
embedding_kwargs-presence branches, and any embedding composes with any
distribution type.

complete_model_settings replaces autocomplete_model_kwargs: each architecture
infers its own input dims from a named sample batch, the generic theta/context
dims are computed once, and the completed settings are saved in the checkpoint
so loading never needs a data sample. Dimensions in user settings and unused
context mergers are errors. The V_rb_list deepcopy hack is gone: initial
weights are extra constructor kwargs and never touch the saved settings.
save_model now enforces that the standardization covers inference and context
parameters (NN_Sampler_Interface §1).

update_model_config remains the single back-compat boundary: it maps the old
posterior_model_type / posterior_kwargs / embedding_kwargs schema (and the
older nsf+embedding form) onto the new one, including added_context ->
concat merger; old checkpoints build state-dict-identical networks (pinned
by test). Deleted along the way: the never-wired embedding_net_builder
parameter, create_nsf_wrapped, create_nsf_with_rb_projection_embedding_net.
Example configs, pipe defaults, and the architecture docs notebook use the
new schema.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Folds the training side of the chained-NPE branch into the conditioning
design (NN_Build_System_Design §4.5): user-declared
data.conditioning_parameters feed the same context_parameters list as GNPE
proxies (two sources, one declared contract), the ContextMergerMLP lands as
the registered "mlp" context merger (mixes context through a learned MLP so
context_dim does not grow; dimensions inferred at completion instead of
hand-written input_dim), and data.time_alignment=True trains on
time-aligned data by skipping the per-detector time shift in
ProjectOntoDetectors, with a validation guard on the required conditioning.
Tests ported from the chained branch. Its inference side is a
factorized-sampler chain (Group A) and is not built here.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@max-dax

max-dax commented Jul 12, 2026

Copy link
Copy Markdown
Collaborator Author

Step 3 landed: dict batches, registry-built embeddings, autocomplete_model_kwargs deleted

Three new commits (f917def2, d54a1d33, d2f9315a) complete step 3 of the build-system design (§6). All 321 tests in tests/core + tests/gw pass.

Note on the rebase: the branch was rebased onto the updated hackathon-1. #387 had added a smaller tests/core/test_build_model.py; the conflict was resolved in favor of the more comprehensive version from this branch. One #387 test asserting case-insensitive dispatch was dropped deliberately: per the step-2 decision, registry lookups are case-sensitive, and historical case-tolerance lives only in update_model_config.

1. f917def2 — dict batches (design §4.4, interface doc §2)

  • The training transform chain ends in SelectKeys (dict-preserving) instead of UnpackDict (positional flattening); loaders yield {"inference_parameters": ..., "waveform": ..., "context_parameters": ...}.
  • NeuralDistribution.sample / sample_and_log_prob / log_prob / loss take a single context: dict | None instead of positional *context — exactly the signature proposed to Group A in NN_Sampler_Interface.md §2.
  • FlowWrapper and ContinuousFlow route context[k] for k in context_keys into the embedding network once, at the model boundary; tensors stay positional inside modules. A missing context key raises a ValueError naming it — no more silent dependence on the loader's key order.
  • This kills the GNPE-proxy detection via try/except IndexError and the overloaded data_sample[2] slot (which the transformer branch reused for position tensors — the two were mutually exclusive by accident).
  • Call sites updated: core/samplers.py (plain + GNPE), SampleDataset for unconditional training.

2. d54a1d33 — new schema + registries; autocomplete_model_kwargs deleted (design §4.3/4.5/4.7/4.8)

New model-settings schema, each component a registry type plus a flat kwargs dict:

model:
  distribution:    {type: normalizing_flow, kwargs: {num_flow_steps: 30, ...}}
  embedding_net:   {type: dense_svd, kwargs: {output_dim: 128, ...}}
  # context_merger: {type: concat}   # added automatically when the data has context_parameters
  • Embedding contract (core/nn/enets.py module docstring): input_keys, output_dim, complete_settings(settings, sample_batch). First registered components: dense_svd (LinearProjectionRB + DenseResidualNet) and the concat context merger. Any embedding now composes with any distribution type — transformer×FMPE will need zero extra wiring.
  • complete_model_settings replaces autocomplete_model_kwargs: each architecture infers its own input dims from the named sample batch; generic theta_dim/context_dim are computed once; completed settings are saved in the checkpoint so loading never needs a data sample. Dimensions in user YAML are an error; a context_merger with no context parameters in the data is an error (no silent drops).
  • initialize_network loses its embedding_kwargs-presence branches; NeuralDistribution.build_embedding_net assembles embedding + merger generically. The V_rb_list deepcopy hack is gone — initial weights are extra constructor kwargs, never in saved settings.
  • save_model enforces the parameter contract promised to samplers (interface doc §1): standardization must cover inference ∪ context parameters, for built-ins and plugins alike.
  • Back-compat: update_model_config is the single boundary and maps all old schemas forward (nsf+embedding, posterior_model_type/posterior_kwargs/embedding_kwargs, added_context → concat merger, input_dimtheta_dim). Old checkpoints build state-dict-identical networks — pinned by test_old_schema_builds_state_dict_compatible_network and an actual old-format .pt load round trip (test_load_old_schema_checkpoint_file).
  • Deleted: the never-wired embedding_net_builder parameter, create_nsf_wrapped, create_nsf_with_rb_projection_embedding_net. Example configs, pipe defaults, and the architecture docs notebook use the new schema (old YAML still works through the shim).

3. d2f9315a — chained-NPE training-side conditioning (design §4.5)

  • data.conditioning_parameters feed the same context_parameters list as GNPE proxies — two sources, one declared contract.
  • ContextMergerMLP ported as the registered mlp merger: context mixed in through a learned MLP so context_dim doesn't grow; its dims are inferred at completion (no hand-written input_dim), output_dim defaults to the embedding's.
  • data.time_alignment: true trains on time-aligned data via ProjectOntoDetectors(apply_time_shift=False), with the validation guard (requires ra/dec/geocent_time as conditioning, forbids them as inference targets, incompatible with gnpe_time_shifts). Tests ported from the chained branch. The chained inference side is a factorized-sampler chain (Group A's domain) and is not built here.

Deliberately deferred

  • FlowFactor/GNPEFlowFactor call sites (interface doc §2, impacts 1–3): factors.py is not in hackathon-1 yet, so those edits can't land in this PR. Nothing on sampler-revamp breaks in the meantime; we'll do that migration as promised once the branches meet.
  • Step 4 (architecture-owned SVD init — the initial_weights threading and the trainer's svd gate still exist, marked with a TODO) and steps 5–6 (transformer merge) follow.

Review pointers

  • The schema mapping: core/utils/backward_compatibility.py::update_model_config
  • The autocomplete replacement: core/posterior_models/build_model.py::complete_model_settings
  • The embedding contract + registered components: core/nn/enets.py
  • Checkpoint compatibility tests: tests/core/test_build_model.py (old-schema section)

🤖 Generated with Claude Code

max-dax and others added 3 commits July 12, 2026 20:22
Data-driven weight initialization moves behind two hooks on the embedding
contract (NN_Build_System_Design §4.6): init_data_spec() declares the data
variation a network wants (noise on/off, network formatting, pinned prior
parameters, sample count), and initialize_weights(batches, out_dir) consumes
a matching batch iterator. DenseSVDEmbedding implements them, absorbing the
SVD math from build_svd_for_embedding_network: it collects per-block strains,
builds one SVD basis per block, saves validation diagnostics, drops the
zero rows outside its input range, and seeds the projection layer.

The trainer no longer knows about the SVD: prepare_training_new walks
pm.network.modules(), answers any module's spec via the new
initialization_dataloader (a transform-stack variation using the existing
omit_transforms mechanism), and lets the module initialize itself — plugin
architectures get data-driven initialization without touching dingo.
Seeding triggers on svd.num_training_samples, so loading a saved model never
re-runs it. Deleted: build_svd_for_embedding_network, the initial_weights
threading through NeuralDistribution/build_model_from_kwargs, and the
"embedding network is assumed to have an SVD projection layer" special case.

SVDBasis moves from dingo/gw/SVD.py to dingo/core/SVD.py — it was always
domain-agnostic (numpy/scipy only) and core may not import from gw; the old
module remains as a re-export shim.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Ports the transformer branch (origin/transformer, stage 1 of the T1 merge)
onto the registry build system. TransformerEmbedding registers as
"transformer": tokenized strain -> Tokenizer (shared DenseResidualNet with
GLU position-conditioning) -> TransformerEncoder -> CLS/average pooling ->
optional final net. complete_settings infers the tokenizer input dims and
num_blocks from a named sample batch (position is its own batch entry now,
instead of overloading the GNPE-proxy slot). Because the seam is generic,
transformer x flow-matching works with zero extra wiring (pinned by test),
and context parameters compose via the concat merger - both impossible on
the original branch. StrainTokenization lands as a data transform, wired
into set_train_transforms and GWSampler via the tokenization data settings;
core samplers accept dict outputs from transform_pre.

DenseResidualNet is unified rather than duplicated: the branch's
implementation (per-block GLU context, optional layer_norm, arbitrary
leading batch dims) moves to core/nn/resnet.py and replaces the
glasflow-block-based version in enets.py - MyResidualBlock is a strict
superset of glasflow's ResidualBlock with identical parameter names and
forward pass (pinned by test), so old checkpoints load unchanged. Only the
rq-coupling conditioner is genuinely a different architecture; per the
team decision it is selected by an explicit conditioner_type setting
(glasflow_residual default | dense_residual with optional layer_norm), not
a layer_norm flag. layer_norm threads through the cfnets builders, and
ContinuousFlow no longer shares a mutable-default Identity module.

Not ported (deliberate): the advertised-but-dead augmentation config keys
(drop_detectors, drop_frequency_range - stage 2, from dingo-t1), the
allow_tf32 pop-and-discard, the embedding_type branches (registry handles
dispatch), and create_nsf_with_transformer_embedding_net. The
ConcatContextMerger now routes multi-input embeddings. Registration of
built-in architectures moves to dingo/core/nn/__init__.py. Adds
examples/transformer_model and the branch's transformer / resnet /
tokenization / sampler-transform tests, adapted to the registry API.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Stage 2 of the transformer merge, from origin/dingo-t1 (build-system step 6).

Training-side augmentation for tokenized models, wired via data.tokenization:
DropDetectors, DropFrequenciesToUpdateRange (f_cut), DropFrequencyInterval
(mask_interval), DropRandomTokens, and NormalizePosition, all marking tokens
in drop_token_mask that the transformer then does not attend to. Ported
near-verbatim with two deviations: DropDetectors gains unbatched-sample
support (our pipeline feeds single samples; the other drop transforms already
handled this - covered by the ported no-batch test), and DropRandomTokens
loses the increase_p_until_epoch ramp, which is dead on dingo-t1 as well
(the epoch injection into samples is commented out there).

Inference side: GWSampler gains a suppress property ([f_lo, f_hi] or
per-detector dict) with real validation (dingo-t1 left it as an unimplemented
warning stub): it requires a tokenized model trained with drop augmentation
and an in-domain interval. Frequency-range updates and suppression for
tokenized models go through the ported UpdateFrequencyRange transform (masks
tokens instead of zeroing strain data), with NormalizePosition applied after
mask updates, matching training. Unchanged frequency bounds are passed as
None so the domain default does not needlessly mask the zero-padded final
token. UpdateFrequencyRange gets the test dingo-t1 left as a TODO.

PoolingTransformer (sinusoidal MultiPositionalEncoding + average pooling)
registers as the "pooling_transformer" embedding, composing with any
distribution through the generic build path. The transformer example config
now demonstrates the augmentation settings. Not ported (out of step-6 scope,
separate research features on dingo-t1): pretraining
(enets_pretraining/pretraining_model), custom schedulers/AMP trainer changes,
mixed ASD datasets, TimeShiftStrainGrid, BlockEncoding, skymaps.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant