Skip to content

Bugfix/null mixture-weights grad#2221

Closed
esennesh wants to merge 3 commits into
pyro-ppl:masterfrom
esennesh:bugfix/null_mixture_weights_grad
Closed

Bugfix/null mixture-weights grad#2221
esennesh wants to merge 3 commits into
pyro-ppl:masterfrom
esennesh:bugfix/null_mixture_weights_grad

Conversation

@esennesh

Copy link
Copy Markdown
Contributor

So basically, there's a bugfix and a mild improvement/feature here:

  • The bugfix is that the distribution internals were taking the log of probs in Categorical, even when the probabilities were specified in linear space and one of them could be a hard zero. This was causing some NaN issues.
  • This was particularly an issue in mixture distributions, where we were taking the softmax of the log of the probs, which of course assumes they were actually specified as logits in the first place.
  • The minor feature is to unwrap Independent distributions when they're given as mixture components.

esennesh and others added 3 commits July 17, 2026 13:27
Introduce a shared `_CategoricalBase` for `CategoricalProbs` and
`CategoricalLogits`, holding the common `mean`/`variance`/`support`/
`enumerate_support` keyed off a `_param` hook (the raw stored parameter,
so shape/dtype queries never trigger a probs<->logits conversion).

Each subclass exposes `probs` without routing through the log-probs:
`CategoricalProbs` returns the stored weights directly (exact zeros
preserved), `CategoricalLogits` returns `softmax(logits)`. This gives a
mixing distribution a `probs` property that is safe to differentiate at
exact-zero weights, which the mixture log_prob fix relies on.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
_MixtureBase.log_prob wrote the density in log-weight form via
log_softmax(mixing.logits) + log p_k. For a probs-parameterized mixing
Categorical with an exact-zero weight, logits takes log(0): the forward
value was masked to finite but the VJP evaluated 1/0 = inf, producing a
NaN gradient into every parameter feeding the weights.

Compute log_prob as a numerically stable weighted logsumexp instead,
  log p(x) = m + log sum_k w_k exp(log p_k(x) - m),
reading mixing.probs (linear weights, never log(w_k)). This is exact in
both value and gradient: d/dw_k = p_k(x) / sum_j w_j p_j(x) stays finite
at w_k == 0, and d/d(log p_k) is the responsibility. The direct sum is
used rather than jax.nn.logsumexp's `b=`, which would silently zero the
gradient of any exactly-zero-weight component. Out-of-support components
(log p_k = -inf) drop out with an exact zero, and an all-out-of-support
value yields -inf with a zero (not NaN) gradient.

A private _bare_component_log_probs returns log p_k(x); component_log_probs
keeps its documented contract, now shared in _MixtureBase as
log(mixing.probs) + bare.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A mixture must evaluate every component's density at the same value, so the
component support must be identical across components and independent of their
parameters. The previous guard required the support itself to be a
ParameterFreeConstraint, which rejected event-wrapped components such as
Normal(...).to_event(1) whose support is Independent(Real(), 1) -- even though an
Independent wrapper only reinterprets batch dims as event dims and introduces no
parameter dependence.

Unwrap any Independent layers and require the *base* support to be parameter-free
instead. This enables per-element vector mixtures (e.g. a per-pixel RGB mixture
whose assignment is shared across channels) with no other changes: mixture_dim,
_bare_component_log_probs, component_sample and mean are already event-aware.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@esennesh esennesh closed this Jul 17, 2026
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