Skip to content

WIP: Major upgrade - #182

Draft
oschulz wants to merge 122 commits into
mainfrom
major-upgrade
Draft

oschulz wants to merge 122 commits into
mainfrom
major-upgrade

Conversation

@oschulz

@oschulz oschulz commented Aug 23, 2026

Copy link
Copy Markdown
Collaborator

Written by Claude (Fable 5.1, high effort) on behalf of the maintainer.

Work in progress. Breaking major upgrade of MeasureBase: batching built into the foundation, GPU (CUDA, JLArrays) and Reactant support, and a rebuilt transport and random variate layer.

The approach, concepts, extension points, layout rules, the list of changes relative to main, and the open decisions are documented in redesign.md in the repository root. That file is kept up to date while the branch evolves and will be removed before the merge.

In short:

  • Batched kernels (batched_logdensityof_impl, batched_transport_to_std/_from_std, batched_rand_impl) are the primary extension points; single variates are batches with zero batch dimensions.
  • Kernels route by variate rank (mspace_ndims), sizes are optional declarations. Composed measures consume flat variate streams with the with-rest protocol.
  • Transport goes through promoted standard measures; random variates are generated in flat batches on a GenContext compute unit.
  • Products over arrays of isbits marginals are stored as struct arrays so that fused kernels run on devices and under Reactant.
  • Distributions wrapped via asmeasure get device-friendly kernels: the main univariate families, MvNormal and Dirichlet evaluate, transport and draw in batches on GPUs and under Reactant (with a few upstream limits noted in redesign.md).
  • The Reactant tests run as part of the suite where Reactant is supported (added on demand), test/cuda is an opt-in runner.

Still to do before merging: the open decisions listed in redesign.md, a docs pass, NEWS, history curation and a version bump.

https://claude.ai/code/session_01L5yPndh2K6u14z3CwF9YXf

oschulz added 30 commits July 5, 2026 12:38
Shouldn't call Core.Compiler.return_type directly in many places.
Will be used a lot when bridging from Distributions to MeasureBase.
Currently unused and undocumented, can add it back later when needed.
Created by generative AI.
Assisted by generative AI.
Unused and untested.

(cherry picked from commit 1d27188)
Not used currently.

(cherry picked from commit 9844583)
A rebase can easily be written explicitly.

(cherry picked from commit fb3c98c)
`mintegral` should be used instead to express posteriors.

(cherry picked from commit 3c61180)
To be re-introduced in sub-module MeasureOperators.

(cherry picked from commit 0cdca3d)
Working notes on the batched-first approach, its concepts, extension
points and layout rules, the changes relative to master, device
verification, known limitations and open decisions, to guide reviews and
next steps while the branch evolves. To be removed before the merge.

Created by generative AI.
@oschulz oschulz changed the title Major upgrade WIP: Major upgrade Sep 18, 2026
@oschulz

oschulz commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator Author

Note that that is very much in flux still, with a lot of AI-generated code (with carefuly guidance) and will still receive both significant changes and then manual review, polish, some renames, etc.

CC @cscherrer

Reactant only supports 64-bit Linux and macOS, so runtests.jl adds it
on the fly where supported (as MGVI does) and includes the Reactant
tests from test/test_reactant.jl instead of a separate test project.

Created by generative AI.
Leading singleton dimensions of summed batches are dropped by reshaping,
since dropdims on static matrices doesn't infer on Julia 1.13. The
common variate rank of superposition components is folded pairwise and
density measures evaluate array variates directly, which Julia 1.10
needs to infer these kernels. The allocation tests call the kernels
directly, local closures allocate inside test sets on Julia 1.10. The
docs include the MeasureOperators docstrings and all cross-references
resolve.

Created by generative AI.
The main univariate families (Normal, Uniform, Exponential, Logistic,
Cauchy, Laplace, LogNormal, Weibull, Gamma, Beta, Poisson, Bernoulli)
get branch-free density formulas, support masks and transports to the
standard measure matching their shape, so batches evaluate on GPUs and
under Reactant. MvNormal works on column batches through its Cholesky
factor (kept lazy, Cholesky.L scalar-indexes on GPUs) and Dirichlet
through stick-breaking Beta transports with cumulative sums and
products along the variate dimension, both with Adapt rules. Incomplete
gamma and beta functions go through hooks whose ForwardDiff and
ChainRules derivatives come from the densities. Draws use the
Distributions samplers on the CPU and the standard transports on other
compute units. Batched densities of univariate wrappers broadcast their
point kernels, only array-variate wrappers evaluate via logpdf.

Beta and Dirichlet transports stay on the CPU (SpecialFunctions'
incomplete beta functions don't compile for GPUs or Reactant), MvNormal
and Dirichlet parameters stay constants under Reactant.

Created by generative AI.
Pkg is a test dependency, the on-demand Reactant install needs it. The
allocation tests measure inside a function, since @allocated at top
level reports a boxed result on Julia 1.10.

Created by generative AI.
All allocation checks go through allocations_of in test/testutils.jl,
which warms up and measures inside a function, since @allocated at top
level reports a boxed result on Julia 1.10.

Created by generative AI.
FixedSizeArrays becomes a test dependency: fixed-size inputs to
densities and transports give fixed-size outputs, since the kernels
allocate via similar.

Created by generative AI.
Variates of the right shape never throw. Densities are -Inf outside the
support, including infinite values and non-integers for counting-based
measures, transports are NaN outside the support of the source measure,
and wrong shapes throw an ArgumentError from checked_arg. The standard
measure transports, Half and the wrapped distribution families mask
their results and use abs, clamp and min guards so that the formulas
stay total on the CPU and identical on devices. Infinite values lie
outside the support of wrapped univariate distributions. Documented in
logdensityof, transport_to and checked_arg, with tests through the
combinators on CPU and JLArrays.

Created by generative AI.
Created by generative AI.
The marginalization map is built with the PropSelFunction constructor,
whose type parameters changed in PropertyFunctions 0.3.

Created by generative AI.
The base measure of a superposition sums the base measures of all
components unconditionally, so the Radon-Nikodym derivative of a
component with respect to it must count every base measure with mass at
the point, not only those of components whose support contains it.
Otherwise densities came out too large where a component vanishes, e.g.
by log(2) for a superposition of a normal and a uniform measure outside
the unit interval.

Created by generative AI.
AsMeasure, Dirac, product measures and transport functions compare by
value, so they hash by value as well, which keeps equal measures with
array-valued parameters usable as dictionary keys and cache witnesses.

Created by generative AI.
The output size of a pushforward is learned from a test value whenever
the origin's variates have a fixed layout, not only when the origin has
a flat size. Pushforwards of tuple products (such as unshaped measures)
thereby declare their variate rank, so their batched kernels and the
transport broadcast hook no longer fall back to pointwise evaluation.

Created by generative AI.
The check whether all marginals of a tuple product have fixed stream
sizes is folded pairwise over the marginal types, so that it infers as
a constant and the types of pushforwards of such products stay
inferrable.

Created by generative AI.
The fixed-stream-size check and the output size of pushforwards are
type-level information, declared non-differentiable so that Zygote can
differentiate through the construction of pushforwards of tuple
products.

Created by generative AI.
On the floating-point grid the endpoints of the unit interval stand for
their nearest interior points: uniform inputs are clamped into the open
interval (from the smallest normal float, since devices may flush
subnormals, to the grid point below one) before quantiles, and tail
probabilities of the direct log-space conversions between standard
measures never underflow to zero. Inputs outside the unit interval stay
NaN. This is the null-set convention BAT has used in practice, applied
once in MeasureBase for the standard measures, Half, the wrapped
families, Dirichlet and the generic logistic path; the bounds are
non-differentiable helpers so that autodiff passes through them.

Created by generative AI.
The private static helpers duplicated tools that StaticThings now
provides: `_size_dims`, `_reshape_batch`, the leading-dimension family
(`_sum_leading_dims`, `_drop_leading_dims`, `_merge_leading_dims`,
`_all_leading_dims`), `_get_or_view`/`_split_after` for vectors, the
type-level `_static_axes_size`, and the pairwise folds over tuple types
of products and superpositions. They are gone in favour of `size_dims`,
`maybestatic_reshape`, `sum_leading_dims`, `drop_leading_dims`,
`merge_leading_dims`, `all_leading_dims`, `maybestatic_view`, `split_at`,
`axes2size(::Type)`, `static_all` and `static_reduce`. Hand-rolled
`map(dynamic, ...)` became `asnonstatic` and `_both` Static's `&`.

`mspace_ndims` now follows from `maybestatic_length` and returns an
`IntegerLike`. `Static.StaticInt` is not an `Integer`, so the dispatch
that consumes it takes `IntegerLike` and `_unit_dof` compares static
ranks instead of testing for a literal zero.

Requires StaticThings 0.3, whose `maybestatic_reshape` no longer turns
arbitrary arrays into static arrays.

Created by generative AI.
The multiplicity of the with-rest protocol and the batch size of random
variate generation were `Dims`, which turned every statically sized
power into a dynamic one as soon as it was consumed from a stream. Both
are `SizeLike` now and stay static: `_chunk_rows` multiplies with
`size2length`, powers hand their `pwr_size` to their base unchanged,
combined measures and tuple products split and reshape their rows with
`_chunk_rows` and `maybestatic_reshape`, and `_fixed_stream_length` keeps
static lengths. The static split of static vectors is therefore reachable
through with-rest.

Bulk draws of a fully static batch size return static arrays on the CPU,
so `rand` of a statically sized power, product or bind allocates nothing.
Other compute units keep allocating their own arrays.

Standard measures check their variates directly: going through their base
measures makes the inliner cut the recursion, and the resulting call
boxes the variate of every scalar marginal of a tuple product.

Created by generative AI.
A hierarchical model over a named tuple of scalar and static-array
marginals (a bind of a named tuple product with a kernel whose marginals
depend on the primary variate but whose sizes don't) checks that random
variates, transports to and from a static standard power and densities
are type stable and allocation free, plus static powers, nested static
powers and batches of static variates.

`test/transport.jl` imports `MeasureBase` itself, so that it runs on its
own and not only as part of the suite.

Created by generative AI.
`mspace_ndims` returned a `StaticInt` where it followed from a variate
size and an `Int` where a measure type declared it, which made
`mspace_ndims(typeof(μ))` and `mspace_ndims(μ)` disagree and forced
`IntegerLike` on the dispatch that consumes them. Ranks are `Int`s again,
constant-folded through the type-level definitions, and staticness comes
from `_static_ndims` as before.

Follows StaticThings' rename of the mapped tuple-type fold.

Created by generative AI.
Two stream kernels splatted the multiplicity into a size tuple, which
only works for tuples, not for the `StaticArrays.Size` that `SizeLike`
also allows. They take `size_dims` of it now, like the other kernels do.

Created by generative AI.
`_map` passed the mapped function on unspecialized, so drawing the
marginals of a tuple or named tuple product ran through a dynamic call
and boxed every scalar variate. `map` over a named tuple splats its
values on top of that, mapping over the values avoids it. `rand` of a
product of scalar measures, and of a bind over one, allocates nothing
now.

Created by generative AI.
Power kernels name the measure again when a variate isn't an array,
instead of letting StaticThings report a dimension mismatch. The shape
checks of powers take `Dims` consistently, the dead `_split_after` for
tuples with a dynamic split point and the dead `_array_product_kernel`
for dynamic ranks are gone, the `StdMeasure` docstring states that
variates of standard measures are `Real`s, and the redesign notes name
the new multiplicity type.

Created by generative AI.
Adds static stream multiplicities (as a tuple of static integers and as a
`StaticArrays.Size`) through the batched with-rest transport and density
kernels of static powers and combined measures, the batched transports
and `logdensities` on a static batch with the broadcast hook, and
allocation checks for `rand`. The nested-power test checks the layout
instead of pinning the container type.

Created by generative AI.
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