fix NaN mixture log_prob gradient at zero weights - #2224
Conversation
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>
|
Hi @esennesh, thank you. Do you have an MRE where this bug was appearing? |
The below should trigger it. That's minimal, of course. My actual use-case was a Gaussian mixture model in which the means and weights were dynamically determined by upstream latent variables, so that sometimes zero weights would occur in some components and sometimes not. Since we're working in Jax, the dynamic control flow to just leave out all zero-weight components is a bit finicky, if it's possible. |
|
@esennesh, thanks for the MRE. I have modified it a little. import jax
import jax.numpy as jnp
import numpyro.distributions as dist
def log_prob(probs):
mixing_distribution = dist.Categorical(probs=probs, validate_args=True)
component_distributions = dist.Normal(
loc=jnp.array([0.0, 1.0, 2.0]),
scale=1.0,
validate_args=True,
)
return dist.MixtureSameFamily(
mixing_distribution,
component_distributions,
validate_args=True,
).log_prob(0.3)
log_prob_jit = jax.jit(log_prob)
mixing_probs = jnp.array([0.0, 0.5, 0.5])
log_prob_val = log_prob(mixing_probs)
print(log_prob_val)
log_prob_val = log_prob_jit(mixing_probs)
print(log_prob_val)
with jax.debug_nans(True):
grad_log_prob_val = jax.grad(log_prob)(mixing_probs)
print(grad_log_prob_val)
with jax.debug_nans(True):
grad_log_prob_val = jax.grad(log_prob_jit)(mixing_probs)
print(grad_log_prob_val)It is reproducing the same error. This is the output before fix: and after fix: I assume the numerical answers are correct. And the fix I have proposed is, def _to_logits_multinom(probs: ArrayLike) -> ArrayLike:
- minval = jnp.finfo(jnp.result_type(probs)).min
- return jnp.clip(jnp.log(probs), minval)
+ safe_probs = jnp.where(probs > 0, probs, 1.0)
+ safe_log_probs = jnp.where(probs > 0, jnp.log(safe_probs), -jnp.inf)
+ return safe_log_probs@juanitorduz what are your thoughts? |
|
Do I understand correctly that the issue can be addressed with simpler change? |
Yes. The MRE does pass with the little change. Although I have not tested it on the rest of the test suite. |
|
Hi @esennesh, have you had a chance to try my fix in your workflow to see if it is working? |
|
Doing that tonight or tomorrow morning, most likely.
…On Sat, Aug 1, 2026, 6:52 AM Meesum Qazalbash ***@***.***> wrote:
*Qazalbash* left a comment (pyro-ppl/numpyro#2224)
<#2224 (comment)>
Hi @esennesh <https://github.com/esennesh>, have you had a chance to try
my fix in your workflow to see if it is working?
—
Reply to this email directly, view it on GitHub
<#2224?email_source=notifications&email_token=AAFRWFNCDBPQL2S5WL4ZVA35HXYYJA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKMJVGE3TCMJQGU32M4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJLDGN5XXIZLSL5RWY2LDNM#issuecomment-5151711057>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAFRWFMD6J6HP4GT5LANXHD5HXYYJAVCNFSNUABFKJSXA33TNF2G64TZHMYTOMBVHAYDKNBQHNEXG43VMU5TIOJTGQZDMNBTHAZ2C5QC>
.
Triage notifications, keep track of coding agent tasks and review pull
requests on the go with GitHub Mobile for iOS
<https://github.com/notifications/mobile/ios/AAFRWFMDOTK5OR4AUMEHEFD5HXYYJA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKMJVGE3TCMJQGU32M4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJKTGN5XXIZLSL5UW64Y>
and Android
<https://github.com/notifications/mobile/android/AAFRWFPCHQPKH4RWXICNZAD5HXYYJA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKMJVGE3TCMJQGU32M4TFMFZW63VHNVSW45DJN5XKKZLWMVXHJLTGN5XXIZLSL5QW4ZDSN5UWI>.
Download it today!
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
_MixtureBase.log_probwrote the density in log-weight form vialog_softmax(mixing.logits) + log p_k. For a probs-parameterized mixingCategoricalwith 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.