Build1 publisherNot yet confirmed elsewhere3 min readPublished
sbi swaps three network-spec routes for typed configs, so bad settings fail where you write them
A GSoC 2026 rewrite retires the factory function whose signature was the union of every model's settings. The price is a rewrite for anyone who picked a network by string or factory call.
The Engineer · Build desk
What happened
- sbi's network specification moved from strings and factory functions to typed configuration objects, one class per model, across every estimator family.
- A misspelled field, a bad value, or a setting the model does not have now raises before training starts, as TypeError, ValueError and TypeError respectively.
- String arguments still work but emit a FutureWarning naming the replacement class, and hand-written custom modules are untouched.
- The work landed in five pull requests, from groundwork in #1872 to the mixed density estimators for MNLE and MNPE in #1912.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- cost Two of the three old ways to pick a network now carry a rewrite; only sites that already passed a hand-written module get through free, and the warning rather than the code sets the deadline.
- capability A frozen dataclass can be dropped into a run record verbatim, so the network specification becomes part of an experiment's provenance instead of a closure nobody can read back.
- constraint One class per model means the library can no longer accept a keyword merely because a sibling model wants it, and any setting shared across models has to be declared in each class that honours it.
- precedent Naming builder arguments by role rather than by theta and x is now the convention any later estimator family inherits, including the vector-field one.
A dropped keyword is the kind of bug that lets the job finish. The old factory functions accepted any keyword handed to them, because their signature was the union of every setting any model in the family might want, and anything the chosen model did not use was discarded on the way to the network [2][3]. Training completes, the log looks like the script, and what comes back is a network that differs from the one written down. The only symptom is a posterior that is worse than it should be, which is indistinguishable from a hard inference problem.
Splitting that union into one frozen dataclass per model moves the check to the line where the setting is typed [1][4]. The report lists three ways of being wrong, all of which now raise before training: an unknown field name, a bad value for a known field, and a setting that belongs to a sibling model rather than this one [5]. Two of those are the constructor refusing an argument it has no slot for, and one is validation of the value itself [21]. None of them require a GPU to discover.
The most instructive engineering in the write-up is a removal. The first design had `build()` take a `BuildContext` object carrying shape, device and z-scoring statistics; the contributor reports that mentor Jan Teusen noticed the parameter was never read, since everything in it could be derived from the training batch already being passed, so it was cut as a premature abstraction [17]. That abstraction was killed by implementing it rather than by arguing about it, which is the cheaper of the two ways to find out.
The NLE extension is where the naming stopped being cosmetic. NPE models parameters given data and NLE models data given parameters, so the same builder serves opposite roles, and a signature reading `build(batch_theta, batch_x)` is correct for one family and backwards for the other [11]. Backwards here means the standardization lands on the wrong variable, which trains without complaint. The fix was to name the arguments by role, `build(batch_input, batch_condition)`, and leave the trainer to decide which is which so the config never has to know [18].
One caveat on provenance: this is the contributor's own final work product post for the project, mentored by Jan Teusen and Nicholas Junge under NumFOCUS and the sbi-dev/sbi sub-organisation [19], and it quantifies neither how much downstream code the deprecation warning touches nor when the string route stops working [6]. The claim being made is narrow and checkable, though. Configuration that the model cannot honour used to reach the network and vanish; now it does not compile past the constructor.
What to watch
- Whether a release is named for removing the string route, or the FutureWarning sits open-ended and downstream code never moves.
Clarity's read
What the record supports and how the coverage leans. The claims behind it follow.
Reality
- Evidence56
- Adoption24
- Hype gap+16
- Incentives66
- Confidence47
Claim ledger
Ranked by verification strength, evidence, and original report placement.
- [1]
The Google Summer of Code 2026 project replaced sbi's old string and factory-function interface with typed configuration objects, one class per model, across every estimator family in the library.
- [2]
The factory function returned an opaque closure that could not be inspected, printed or serialized, and it accepted any keyword given to it.
- [3]
Factory functions spanned a whole family of models, so their signature was the union of every setting any model might want, and a setting the chosen model did not use was quietly discarded.
- [4]
The new config objects are frozen dataclasses, so they print cleanly, can be logged in an experiment record, and their fields autocomplete in an editor.
- [5]
Three distinct failures now occur before training: a misspelled field name such as NSFConfig(hiden_features=64) raises TypeError; a misspelled value such as NSFConfig(z_score_input="strucured") raises ValueError; and a setting the model does not have, such as MAFConfig(num_bins=8), raises TypeError.
- [6]
Strings still work and emit a FutureWarning that names the class to switch to, and custom modules still work.
- [7]
PR #1872 laid the groundwork: the shared base class, the supporting types, and a protocol rename to free up the name "Builder" for the new objects.
- [8]
PR #1877 added the first real builder and #1882 wired it into the NPE trainers, with the deprecation path for strings.
- [9]
PR #1904 extended the builder to NLE.
- [10]
PR #1912 added the mixed density estimators for MNLE and MNPE, where part of the data is discrete.
- [11]
NPE models the parameters given the data and NLE models the data given the parameters, so the same builder serves opposite roles; a signature of build(batch_theta, batch_x) reads correctly for one and backwards for the other, which is how a bug arises where the standardization lands on the wrong variable.
- [12]
sbi does Bayesian inference for simulators with no writable likelihood: you supply a simulator and a prior, run simulations, and a neural network learns the posterior over the simulator's parameters from those runs.
- [13]
sbi implements NPE, NLE, NRE, FMPE, NPSE and mixed variants, and every one of them trains some kind of neural density estimator.
- [14]
The old API had three ways to specify a network, grown separately: a string such as density_estimator="nsf"; a factory function such as posterior_nn(model="nsf", hidden_features=64); and a custom module for full control.
- [15]
The string route was easy but gave no way to set hyperparameters.
- [16]
There was no type information anywhere in the old interface, so editors could not autocomplete and type checkers could not help.
- [17]
The original design had build() take a BuildContext object carrying shape, device and z-scoring statistics; during implementation mentor Jan noticed the parameter was never actually read because everything it held could be derived from the training batch already being passed in, so it was cut as a premature abstraction.
- [18]
build() became build(batch_input, batch_condition), where input is whatever the model is modelling and condition is whatever it conditions on, with the trainer deciding which is which so the config never has to know; the decision carried cleanly through every family that came after, including the vector-field family that landed months later.
- [19]
The work is presented as the contributor's final work product for GSoC 2026, under organisation NumFOCUS and sub-organisation sbi-dev/sbi, mentored by Jan Teusen (@janfb) and Nicholas Junge (@nicholasjng).
- [20]
The report enumerates five merged pull requests for the migration: #1872, #1877, #1882, #1904 and #1912.
- [21]
Of the three pre-training failures, two surface as TypeError from the dataclass constructor rejecting a field it does not define, and one as ValueError from validating the value of a field that does exist.
- [22]
Two of the three old specification routes now require migration (the string route, which warns, and the factory function, which the config objects replace); only hand-written custom modules are unaffected.
Sources
1 independent publisher whose own reporting we read for this story.
- dev.toWrapping Up My GSoC 2026 Journey with sbi
1 article · August 23, 2026
Topics and entities
Follow any of these and your For You feed starts watching them — no settings page required.