Skip to content

Compute logvarexp/logstdexp gradients in the log domain - #5

Merged
cossio merged 8 commits into
mainfrom
chainrules-numerics
Jul 29, 2026
Merged

Compute logvarexp/logstdexp gradients in the log domain#5
cossio merged 8 commits into
mainfrom
chainrules-numerics

Conversation

@cossio

@cossio cossio commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Follow-up to the post-merge Codex review of #3. Of its four comments, two were real and are fixed here; two were false positives.

Fixed

  • Gradient underflow (P1). _∂x_logvarexp squared exp.(x .- logmean) .- 1 directly, so for nearly equal entries the denominator underflowed to zero and the logvarexp/logstdexp rules returned Inf/NaN even though the primal and the true gradient are representable. Reproduced with x = [-1e-200, 1e-200]: primal ≈ −920.3, gradient came out [-Inf, Inf] instead of ≈ ±1e200. The gradient ratio is now formed in the log domain via logsubexp/logsumexp. Regression tests added.
  • Thunked cotangents (P2). The pullback now calls unthunk on the incoming cotangent before broadcasting. (Broadcasting already unthunks implicitly via Broadcast.broadcastable(::AbstractThunk), so this is hardening rather than a bug fix.)
  • Complex-input claim (P2). The rules only cover AbstractArray{<:Real}, while logmeanexp itself accepts complex arrays. The manual and changelog now state the real-array restriction instead of advertising unqualified AD support.

Not fixed (false positive)

  • "Handle scalar dims before iterating": all(d ≤ ndims(x) for d in dims) with dims::Int is fine — numbers are iterable in Julia (all(d <= 2 for d in 1) == true), and the 330 tests in that loop already ran green in CI.

Verification

  • Pkg.test() passes on Julia 1.12.6, including new "extreme values" regression tests (finite gradients with correct magnitude at x = [-1e-200, 1e-200] for both rrule and frule).
  • Runic 1.7 --check passes.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KmceQuDSTogkuoRwSd48aj

Post-merge review of #3 flagged that squaring exp(xᵢ) - m outside the
log domain can underflow for nearly equal entries, returning Inf/NaN
gradients where the true gradient is representable (e.g.
x = [-1e-200, 1e-200] has gradient ±1e200). Rewrite _∂x_logvarexp with
logsubexp/logsumexp so the ratio is formed in the log domain.

Also unthunk the incoming cotangent in the shared pullback, and
document that the rules cover real arrays only (complex arrays,
supported by logmeanexp itself, are not covered).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmceQuDSTogkuoRwSd48aj
@cossio
cossio marked this pull request as ready for review July 29, 2026 10:36
Copilot AI review requested due to automatic review settings July 29, 2026 10:36
@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (0ffc002) to head (320f816).

Additional details and impacted files
@@            Coverage Diff            @@
##              main        #5   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files            2         2           
  Lines           59        59           
=========================================
  Hits            59        59           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f361cae057

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ext/LogStatFunctionsChainRulesCoreExt.jl Outdated
Codex review flagged that x .+ l .- S carries the input's common offset
through each term, losing low bits at large magnitudes. Centering on
logmean is not enough: logmean itself carries an ulp(offset)-scale
rounding that shifts all centered values, and against a BigFloat
reference both formulations lose all accuracy at Float64 offsets ~1e15
(and ~27% error at Float32 offsets ~1e5).

Instead center on maximum(x; dims), which subtracts exactly, and form
the log-mean from the centered values in O(1) arithmetic. The log(n)
term is folded into lm before subtracting from t so tiny spreads near
zero are not absorbed by O(1) intermediates. Apply the same idea to
the logmeanexp gradient by normalizing max-centered exponentials
(softmax) instead of dividing exp(x - logmean) by n.

Regression tests compare gradients at offsets 1e12 and 1e15 (chosen so
the shift is exactly representable) against the unshifted gradients.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmceQuDSTogkuoRwSd48aj

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 00ba70d471

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ext/LogStatFunctionsChainRulesCoreExt.jl Outdated
Comment thread ext/LogStatFunctionsChainRulesCoreExt.jl Outdated
Compute the centered log-mean as log1p(mean(expm1(t))) instead of
logsumexp(t) - log(n): the latter rounds through O(1) intermediates and
absorbs offsets below their epsilon, zeroing the gradient of the
maximal entry for nearly equal inputs (Codex review on 00ba70d). With
log1p the regression case x = [-1e-200, 1e-200] now yields both
components ±1e200; the test asserts the full gradient.

Also document why the gradients differentiate under the assumption
logmean == logmeanexp(x; dims) rather than honoring an
arbitrary supplied logmean: ChainRules pullbacks cannot attribute
tangents to keyword arguments, so treating the cache kwarg as an
independent constant would silently drop the mean's x-dependence from
end-to-end gradients.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmceQuDSTogkuoRwSd48aj

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 924eaca3f1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ext/LogStatFunctionsChainRulesCoreExt.jl Outdated
In low precision with large reductions, sum(expm1.(t)) can round to
exactly -n (e.g. Float16 with 4095 entries of -1 pairwise-summing to
-4096), driving log1p(s / n) to -Inf and the gradients to NaN. Since
the max entry contributes exp(0) = 1, mean(exp.(t)) >= 1/n and
-log(n) is a true lower bound for lm — and in the rounding regime the
max dominates, so the floor is also the correct value. Regression
tests cover the Float16 case for rrule and frule.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmceQuDSTogkuoRwSd48aj

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1b8d41114a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ext/LogStatFunctionsChainRulesCoreExt.jl
Comment thread ext/LogStatFunctionsChainRulesCoreExt.jl Outdated
Two more Codex-flagged Float16 corner cases:

- expm1(d) can overflow (d ≤ log(n) reaches 11.09 at n = 65504 while
  Float16 tops out at e^11.04), turning the gradient into NaN. Compute
  log(abs(expm1(d))) directly as max(d, 0) + log1mexp(-abs(d)) without
  materializing expm1(d).
- Individually tiny expm1 terms round to -1 at the element level, so a
  collectively significant tail (e.g. 4095 entries of -8.5) vanished
  from the centered mean and tail gradients came out zero. Accumulate
  the expm1 terms in at least Float64 (BigFloat stays BigFloat).

Regression tests cover both cases; the -log(n) floor stays as a
belt-and-braces guard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmceQuDSTogkuoRwSd48aj

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: aec6c513d1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ext/LogStatFunctionsChainRulesCoreExt.jl Outdated
float(eltype(t)) throws for arrays with abstract element type (e.g.
Vector{Real}), which match the rules' signature and worked with the
earlier fully elementwise implementation. Narrow the centered values
back to input precision per element with oftype.(float.(t), ...) —
deriving types from values, as the package's own helpers do — instead
of computing a type from the declared eltype.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmceQuDSTogkuoRwSd48aj

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bde39f95c0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ext/LogStatFunctionsChainRulesCoreExt.jl Outdated
Narrowing d = t - lm to input precision rounded centered offsets below
the type's resolution to zero (Float16 nearly adjacent subnormals gave
zero gradients where +-2^14 is exactly representable). Keep the whole
ratio widened and apply the oftype-on-values narrowing only to the
final result. Regression test covers the subnormal Float16 case for
logvarexp and logstdexp.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmceQuDSTogkuoRwSd48aj
Comment thread ext/LogStatFunctionsChainRulesCoreExt.jl Outdated
Comment thread ext/LogStatFunctionsChainRulesCoreExt.jl Outdated
Comment thread ext/LogStatFunctionsChainRulesCoreExt.jl Outdated
Review pass against unneeded complexity. The max-centering, Float64
widening, -log(n) floor, log1mexp overflow guard, and oftype narrowing
defended Float16 arrays with tens of thousands of elements and Float64
log-values around 1e15 — regimes that don't occur in practice and where
the primal functions are at their own representational limits anyway.

Keep the one load-bearing fix: the log-domain ratio (the original
Inf/NaN underflow for nearly equal entries), centered on the logmean
the rules already compute. Use LogExpFunctions.softmax for the
logmeanexp gradient instead of a hand-rolled helper, and trim comments.
Drop the tests that only exercised the removed machinery.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KmceQuDSTogkuoRwSd48aj

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 320f816220

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ext/LogStatFunctionsChainRulesCoreExt.jl
@cossio
cossio merged commit 566e1e8 into main Jul 29, 2026
9 checks passed
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.

2 participants