Conversation
…alars With fvar<var> or fvar<fvar<var>> coefficients the GLM lpmfs compute their partials in var / fvar<var>, and the Eigen products x * beta and x^T * theta_derivative then run as coefficient loops that put two autodiff nodes per matrix entry on the tape (2 n K each). Calling multiply instead resolves to the reverse-mode overload (one node, dense product inside the callback) or the forward-mode overload, so one second-order sweep of a GLM log density adds O(n) nodes instead of O(n K). Measured on an embedded-Laplace hierarchical GLM (n = 30..400 rows, 49 coefficients): 214 -> 22 nodes per row per second-order sweep, 508 -> 131 per third-order sweep, laplace_marginal 2.5-3.3x faster; values and gradients unchanged at every autodiff order. categorical_logit_glm_lpmf with a matrix beta did not compile with forward-mode scalars before (mixed-scalar matrix-matrix product in Eigen's GEMM kernel); the products on the x-partials path go through multiply as well, and the mix test gains a three-category case. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…logit_glm_lpmf multiply of a compile-time row vector and a compile-time column vector is a dot product returning a scalar, so the single-row broadcast case combined with a one-column beta (a vector) had nothing to call rowwise() on. That branch is cheap and keeps the Eigen product, as the single-row branches of the other GLM densities do. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Not sure why the CI is failing, I think jenkins is running out of memory or something like that. |
|
Yeah we just moved our jenkins over to a new set of servers recently so there are some hiccups I'm looking at it now |
|
I think the meta here is that we really want |
|
Yes, I agree, this feels a bit like hacking and it's quite non-obvious to a new developer why this is even a semantic difference in the first place. If there was a more conceptual way to improve this that would be great, but it's probably outside my ability to address at this point. I just noticed this is a way I could speed-up an application I'm building on top of stan that has a hierarchical GLM model, so this has a practical motivation for me and I went for the approach I could do myself. |
|
Yes tbc this is a good PR and once I restart the jenkins I think this is good to merge as is. We have not done the matrix multiply overloads because it requires messing with a lot of Eigens internals. I'm taking a crack at that, but for now I think what you have here is best |
AI use disclosure: The code as well as this commentary was done with the help of claude Fable 5.1
Summary
The GLM log densities compute their two design-matrix products,
x * betaandx^T * theta_derivative, withmultiplyinstead of Eigen'soperator*. With forward-mode coefficients (Hessian-vector products, the third-order term of the embedded Laplace approximation) this puts one autodiff node per result coefficient on the tape instead of two per matrix entry, which makes such sweeps several times faster.Why it matters: the partials are computed in
partials_return_t, which isvarforfvar<var>inputs, and Eigen evaluates adouble-by-varproduct coefficient by coefficient,2 n Knodes per product on every sweep.multiplyis resolved by argument-dependent lookup at instantiation (reverse-mode overload forvarpartials, forward-mode overload forfvar<var>partials), soprimgains no dependency onrev. Fordoubleandvarinputs theprimoverload returns the same lazy product as before.categorical_logit_glm_lpmfwith a matrixbetadid not compile withfvar<var>at all (Eigen's GEMM kernel cannot mix scalar types), which the existing one-column test never exercised; itsx-partials products are switched as well.Measured on an embedded-Laplace hierarchical GLM (49 coefficients, 30 to 400 observations per group): 214 to 22 nodes per observation per second-order sweep, 508 to 131 per third-order sweep,
laplace_marginal3.1 to 3.8 times faster. Values and gradients match develop to 12 digits at every autodiff order.Tests
test/unit/math/mix/prob/glm_forward_mode_tape_test.cpp(new): onefvar<var>sweep of five GLM densities must add fewer than40 nnodes to the tape; before the change it added about4 n K + 20 n.categorical_logit_glm_lpmf_test.cpp: new three-categoryexpect_adcase, which did not compile before.primandmixGLM tests are unchanged.Side Effects
None for
doubleandvararguments. With forward-mode arguments the reverse-modemultiplycopies the design matrix into the arena once per call (n Kdoubles), which is what replaces the2 n Knode allocations.Release notes
GLM log densities evaluated with forward-mode scalars (nested autodiff, for example the embedded Laplace approximation) now create O(n) instead of O(n K) autodiff nodes per sweep.
categorical_logit_glm_lpmfnow compiles with forward-mode coefficients for more than one category.Checklist
Copyright holder: Jachym Barvinek
The copyright holder is typically you or your assignee, such as a university or company. By submitting this pull request, the copyright holder is agreeing to the license the submitted work under the following licenses:
- Code: BSD 3-clause (https://opensource.org/licenses/BSD-3-Clause)
- Documentation: CC-BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
the basic tests are passing
./runTests.py test/unit)make test-headers)make test-math-dependencies)make doxygen)make cpplint)the code is written in idiomatic C++ and changes are documented in the doxygen
the new changes are tested