Skip to content

Stable logsumexp softmax gradients without rewrites - #2313

Open
ricardoV94 wants to merge 3 commits into
pymc-devs:mainfrom
ricardoV94:stable_logsumexp_softmax_grads
Open

Stable logsumexp softmax gradients without rewrites#2313
ricardoV94 wants to merge 3 commits into
pymc-devs:mainfrom
ricardoV94:stable_logsumexp_softmax_grads

Conversation

@ricardoV94

@ricardoV94 ricardoV94 commented Jul 29, 2026

Copy link
Copy Markdown
Member
  • Make logsumexp and logaddexp stable by construction
  • Only exclude symbolic_op_recognition inside SymbolicOp inner graphs
  • Recognize log_softmax and rewrite exp(log_softmax) -> softmax

Related to pymc-devs/pymc-extras#720
Related to #2289

Gotta bite the bullet and make logsumexp/logaddexp eager SymbolicRVOp so their gradients are stable.
Also figure out a more clean way to avoid recursion in rewrites but still handle OFG(naive_graph)

Baking an OpFromGraph inner graph disabled recognition rewrites wholesale, to
stop a SymbolicOp such as Softmax from re-creating itself inside its own body.
That is too coarse: it also stripped recognition from ordinary OpFromGraphs, so
a naive exp(x) / sum(exp(x)) that evaluates correctly at the top level returned
nan once wrapped in one.

Only SymbolicOps can recurse this way, so exclude recognition just for them.
pt.logsumexp built a bare log(sum(exp(x))) and left the max subtraction to
local_log_sum_exp. That rewrite runs after grad, so the gradient was built from
the raw form as exp(x) / sum(exp(x)), which overflows past log(float64 max) and
is 0/0 for very negative x. Issue pymc-devs#2288 only addressed the forward value.

Add a LogSumExp SymbolicOp that subtracts the maximum as built, and route
logsumexp and logaddexp through it. Differentiating the stabilized body is
already stable, so no pullback override is needed. Inlined at specialize by
late_inline_OpFromGraph, as for XLogY.

local_log_sum_exp and local_log_add_exp now emit that Op rather than expanding
the stable form inline, so hand-written and helper-built forms share one
representation. They are tagged symbolic_op_recognition, which keeps them from
firing inside LogSumExp's own body, and registered at stabilize only: at
specialize they would re-wrap what late_inline_OpFromGraph had just inlined.
@ricardoV94
ricardoV94 force-pushed the stable_logsumexp_softmax_grads branch from bbd2bd4 to 26f8fa7 Compare July 29, 2026 11:23
@ricardoV94
ricardoV94 force-pushed the stable_logsumexp_softmax_grads branch from 26f8fa7 to e88dd42 Compare July 29, 2026 11:25
@ricardoV94 ricardoV94 changed the title stable logsumexp softmax grads Stable logsumexp softmax gradients without rewrites Jul 29, 2026
@maresb

maresb commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

Is this type of rework similar in philosophy (but different mechanism) to my preliminary Taylor series remainder stuff? There we need to take care because differentiating naturally constructs things that catastrophically cancel, and does so in a sort of worst-case way. So it's a regime where you effectively need to guard against symbolic differentiation in order to protect numerical stability.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants