SPFlow 1.1.0: Building Sum-Product Networks on PyTorch
Sum Product Flow: An Easy and Extensible Library for Sum-Product Networks
At a glance
- What is it?
- SPFlow is an Apache-2.0 Python library for constructing sum-product networks and probabilistic circuits with exact inference. It suits probabilistic modelling work where you need tractable marginals, conditionals and most probable explanations, and it expects you to understand decomposability and scope discipline before you build anything non-trivial.
- Who is it for?
- Adopt SPFlow if you need exact marginals, conditionals or most probable explanations from a circuit you can also inspect as a graph, and if your team is comfortable reasoning about scopes and decomposability. Do not adopt it if you want a black-box density estimator to drop into a pipeline, or if you need a compiled inference engine rather than PyTorch autograd.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 155 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
What SPFlow is for, and who it is actually aimed at
The problem SPFlow addresses is specific: probabilistic models that give you exact answers. A sum-product network, or more generally a probabilistic circuit, is structured so that marginalisation, conditioning and most probable explanation queries all run in polynomial time. That is a different guarantee from what a variational autoencoder or a normalising flow offers, where the likelihood is either approximate or expensive, and where conditional queries usually mean more sampling. SPFlow's README states the library provides exact probabilistic inference for marginals, conditionals and most probable explanations, and that the models remain expressive while doing so. That combination is the whole pitch. If your task is density estimation on tabular data with missing values, or you need a conditional distribution you can evaluate analytically, the tractability constraint is worth the structural discipline it imposes. The audience is narrower than the topic list suggests. The topics include deep-learning and tensorflow-models, but the README says the library is built on PyTorch, and the example code imports torch, not tensorflow. The tensorflow-models topic appears to be a leftover from the project's earlier history. Practitioners coming from a standard deep learning background will find the module API familiar in shape (leaf modules, product modules, sum modules) but unfamiliar in constraint: the graph is not free-form, and the README annotates the product construction with the comment that products combine disjoint scopes, calling out decomposability directly in the example. That comment is the clearest signal of who the library is for. It is for people who already know why decomposability matters, or who are willing to learn it from the guides.
The two construction paths: a DSL and explicit modules
SPFlow offers two ways to build a circuit, and they operate at different levels of control. The first is a domain-specific language exposed through spflow.dsl. The README's quick start shows a single expression: a weighted sum of two Gaussian-product branches, written as 0.4 * Normal(0) * Normal(1) + 0.6 * Normal(0) * Normal(1), wrapped in a with dsl() block. The expression is then materialised by calling .build() on it, which returns an executable circuit. The multiplication operator maps to product nodes and addition maps to sum nodes, with the numeric coefficients becoming the sum weights. This is compact, and for a two-variable mixture it is clearly faster to write than the module API. The second path is explicit: you instantiate leaf modules such as Normal and Categorical, wire them through Product and Sum modules, and pass Scope objects to declare which feature indices each leaf covers. The README's longer example builds a three-feature circuit with indices for X, Z1 and Z2, using out_channels=2 on the leaves and specifying K=3 for the categorical leaves. The scopes are declared as Scope([z1_idx]) and similar, and the comments restate the structural rule: products combine disjoint scopes, sums mix alternatives with identical scope. The two paths are not equivalent in what they expose. The DSL hides scope assignment, which is convenient until it is not. The module API makes scope explicit, which is what you want when a circuit has repeated features across branches, as the example does with X appearing in both the left and right branches. A reader who only ever uses the DSL will not see the scope machinery, and that is where most structural bugs in a circuit live.
Data flow from leaves to root, and what the shape attributes tell you
The mechanism is a bottom-up evaluation. Leaf modules hold distributions over individual features. Product modules combine inputs over disjoint scopes. Sum modules mix inputs that share the same scope. In the README's explicit example, the left branch is described as Z1 times a sum over X and Z2, and the right branch as Z2 times a sum over Z1 and X, with a root Sum mixing the two branches at out_channels=1. Evaluation is a forward pass: data goes in at the leaves, and log_likelihood returns a per-row score. The README shows the expected data shape as (N, D), and the example constructs each feature column separately so that categorical dimensions receive integer values, then stacks them with torch.stack along dim=1. That detail matters because the categorical leaf expects valid integer codes, and the example converts them to float before stacking. Sampling runs in the other direction: root.sample(num_samples=5) draws unconditional samples, and the README prints samples.shape alongside root.out_shape, data.shape and ll.shape. Those four prints are the practical debugging surface. If out_shape does not match what you expect from the leaf and channel configuration, the wiring is wrong, and you find that out before training rather than after. The out_channels parameter appears on both leaves and sums and controls how many parallel channels a module carries, which is how the library represents mixtures with multiple components at a given node. The root uses out_channels=1, collapsing the mixture to a single output distribution per row.
Installation and the commands that appear in the material
Installation is a single command: pip install spflow. The README states Python 3.10+ is required, and the badge in the header confirms that floor. Development setup from source is not documented in the README body; it points to CONTRIBUTING.md for the contributor workflow. The README also references a VERSIONING.md file describing semantic versioning and a deprecation policy, and a RELEASE.md runbook for maintainers. The versioning badge in the header links to semver.org, and the release history is consistent with that: v1.0.1, v1.0.2 and v1.1.0 all landed within roughly a day of each other in March 2026, with v1.1.0 following shortly after. That clustering looks like a patch-and-release sequence rather than three independent feature releases. The README states that SPFlow 1.1.0 builds on the PyTorch-based rewrite introduced in 1.0.0, so the 1.x line is a rewrite rather than an incremental continuation. Anyone with code written against a pre-1.0 version should treat the migration as a rewrite, not a version bump, and the README text is truncated at exactly the point where it would describe the current release in more detail. Optional scikit-learn compatible wrappers exist and are documented in a separate guide, so the sklearn integration is not part of the base install path described in the README. Visualisation is available through spflow.utils.visualization.visualize, which takes an output_path and a format argument; the README examples pass format="svg" and write files named dsl-structure and structure.
Where the structure requirement becomes a real constraint
The limitation is the same property that makes the library work. Exact inference depends on the circuit satisfying structural properties, and the README names decomposability explicitly in a code comment rather than in a dedicated section. Products must combine disjoint scopes. Sums must mix inputs with identical scope. If you violate either, you do not get a runtime error that explains the problem; you get a circuit whose inference results are not what the tractability guarantee promises. That is a genuine failure mode, and the material does not describe a validation layer that catches it for you. The second constraint is the observation shape. The README's example builds each feature column separately and stacks them, with a comment that categorical dimensions need valid integer values. Feeding a categorical column as continuous floats is a silent correctness problem, not a crash. Third, the library is a PyTorch library. GPU acceleration comes through PyTorch, and so does the dependency surface. If your deployment target cannot carry a PyTorch runtime, or your inference service is written in something else, SPFlow is the wrong tool regardless of how well the model fits. There is no separate compiled inference path described in the README. Finally, the README's coverage of missing data is a single bullet: full support for missing data is listed as a feature, with no mechanism described in the body text. How missing values are represented, and whether they are marginalised or imputed, is not stated in the material available here. That is a gap worth resolving against the user guide before you rely on it.
How SPFlow differs from a general probabilistic programming system
The closest comparison is a probabilistic programming language such as PyMC or Stan. Those systems let you write an arbitrary generative model in a modelling language and then choose an inference algorithm, typically a form of MCMC or variational inference. The model is expressive without structural restriction, and the inference is approximate or asymptotically exact at a cost that scales with the difficulty of the posterior. SPFlow inverts both halves. You accept a structural restriction on the graph, and in exchange the inference is exact and runs in polynomial time by construction. The README frames this directly, describing the models as enabling tractable inference while maintaining expressive power. That is a real trade, not a marketing claim, and it cuts both ways. With PyMC you can write a model whose structure you never have to justify. With SPFlow you must justify the structure, because a product of two leaves over the same feature is not a modelling choice the library will quietly accept. The second comparison is to a standard neural density estimator. A normalising flow gives you exact likelihood evaluation and fast sampling, but conditional queries generally require additional machinery, and marginalisation over a subset of variables is not free. SPFlow's README lists marginals, conditionals and most probable explanations as first-class operations. If most probable explanation queries are part of your workload, that is the differentiator. If you only ever need log-likelihood, the structural overhead of a circuit buys you less.
Maintenance cost, versioning and the licence
The repository is not archived, and the last push timestamp is 2026-04-14, which is after the v1.1.0 release in March 2026. The presence of CONTRIBUTING.md, VERSIONING.md and RELEASE.md, plus CI workflows for building and publishing to PyPI and a Codecov badge, indicates a maintained project with a documented release process rather than a research dump. The versioning guide is described as covering semantic versioning and a deprecation policy, so API removals should be announced rather than silent, though the README does not state the length of any deprecation window. Upgrade cost is dominated by the 1.0.0 rewrite. The README says 1.1.0 builds on that rewrite, which means the cost of moving from a pre-1.0 release is a port, not a dependency bump. Within the 1.x line, the three releases in March 2026 landed close together, which suggests patches rather than breaking changes, but that is an inference from timestamps and not something the material confirms. The licence is Apache-2.0, which permits commercial use and modification and includes a patent grant. Apache-2.0 also requires that you preserve attribution and licence notices in distributions, and that you state significant changes if you modify the files. This is a description of the licence terms, not legal advice; check the terms against your own distribution model. The README's own header links to the LICENSE file on the main branch.
Editorial conclusion
Adopt SPFlow if you need exact marginals, conditionals or most probable explanations from a circuit you can also inspect as a graph, and if your team is comfortable reasoning about scopes and decomposability. Do not adopt it if you want a black-box density estimator to drop into a pipeline, or if you need a compiled inference engine rather than PyTorch autograd. Before committing, verify the PyTorch version pin in the packaging metadata, check that the spflow.zoo models match your data types, and confirm the deprecation policy in VERSIONING.md covers the API surface you intend to use.
Community notes