Skip to content

Newton optimizer: handle flat directions without producing NaN - #3429

Open
SteveBronder wants to merge 10 commits into
developfrom
fix/newton-flat-direction-3425
Open

Newton optimizer: handle flat directions without producing NaN#3429
SteveBronder wants to merge 10 commits into
developfrom
fix/newton-flat-direction-3425

Conversation

@SteveBronder

Copy link
Copy Markdown
Collaborator

Submission Checklist

  • Run unit tests: ./runTests.py src/test/unit
  • Run cpplint: make cpplint
  • Declare copyright holder and open-source license: see below

Summary

Fixes #3425.

make_negative_definite_and_solve divided the gradient projection by fabs(eigenvalue) with no guard against zero or near zero values. This could lead to some directions of the gradient and hessian being flat and causing NaN values to return. Now we check that the absolute of the eigen value is greater than a tolerance defined by the an epsilon scaled by the overall maximum eigenvalue. We reject non-finite step directions, candidate points, and objective values in newton_step instead of accepting them. And the newton service layer not returns error_codes::SOFTWARE with TERM_LSFAIL when the final log density or parameters are not finite.

Adds a flat_target test model plus unit tests at the solve, step, and service layers that reproduce the reported NaN.

Documentation

Updated docs for make_negative_definite_and_solve to reflect the change.

Copyright and Licensing

Please list the copyright holder for the work you are submitting (this will be you or your assignee, such as a university or company): Steve Bronder

By submitting this pull request, the copyright holder is agreeing to license the submitted work under the following licenses:

Fixes #3425.

make_negative_definite_and_solve divided the gradient projection by
fabs(eigenvalue) with no zero guard. For a target that is flat along a
direction the gradient and Hessian are both zero, so the step was 0/0
and the resulting NaN parameters were accepted by the line search and
reported by the service as a successful run.

- Drop eigen-directions whose magnitude is negligible relative to the
  largest eigenvalue, as in a pseudo-inverse, so the step is finite.
- Reject non-finite step directions, candidate points, and objective
  values in newton_step instead of accepting them.
- Have the newton service return error_codes::SOFTWARE with
  TERM_LSFAIL when the final log density or parameters are not finite.

Adds a flat_target test model plus unit tests at the solve, step, and
service layers that reproduce the reported NaN.
@WardBrian

Copy link
Copy Markdown
Member

Seems similar to #3309 which @nhuurre had some thoughts on, might be a good reviewer

Comment thread src/stan/optimization/newton.hpp Outdated
vector_d eigenprojections = eigenvectors.transpose() * g;
double max_abs_eigenvalue = eigenvalues.cwiseAbs().maxCoeff();
double tolerance
= max_abs_eigenvalue * H.rows() * std::numeric_limits<double>::epsilon();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Where does this number come from? Is machine epsilon the appropriate baseline here? Why does it depend on the number of rows? Maybe this is related to the numerical precision of the solver...

Also you should handle the case where all eigenvalues are zero. Set some minimum absolute tolerance.

Comment thread src/stan/optimization/newton.hpp Outdated
eigenprojections[i] = -eigenprojections[i] / fabs(eigenvalues[i]);
double abs_eigenvalue = std::fabs(eigenvalues[i]);
if (abs_eigenvalue <= tolerance) {
eigenprojections[i] = 0;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If target is flat then gradient is zero and what you substitute for the eigenvalue doesn't matter, as long as it's finite. But it's also possible that the target is linear, and if so, the gradient is nonzero while the hessian is still zero. I think you'd want nonzero movement in that case. So instead of zero you should use inverse tolerance. Unlike pseudo-inverse, such "saturating inverse" is continuous.

Suggested change
eigenprojections[i] = 0;
eigenprojections[i] = -eigenprojections[i] / tolerance;

@SteveBronder SteveBronder Sep 9, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Talking to claude I had it cite all of it's choices and explain things. I've cleanup up Claude's explanation below which I think makes sense.


Your suggestion divides by tolerance, which at that point was n * eps * max(abs(lambda)). That gives a step along a flat direction of roughly 1e15 times the gradient. Nocedal and Wright discuss exactly this on pp. 49-50 of Numerical Optimization (2nd ed., §3.4, "Eigenvalue Modification"): replacing small eigenvalues with a $\delta$ near machine precision produces a step that "is nearly parallel to q3 ... and quite long. Although f decreases along the direction pk, its extreme length violates the spirit of Newton's method." Their suggested magnitude is $\delta = \sqrt u$.

So the code now floors every eigenvalue magnitude rather than branching, and the floor is $\sqrt u$-scaled:

const double sqrt_eps = std::sqrt(std::numeric_limits<double>::epsilon());
double max_abs_eigenvalue = eigenvalues.cwiseAbs().maxCoeff();
double delta = std::fmax(sqrt_eps * max_abs_eigenvalue, sqrt_eps);
for (int i = 0; i < g.size(); i++) {
  eigenprojections[i]
      = -eigenprojections[i] / std::fmax(std::fabs(eigenvalues[i]), delta);
}

This is the max(abs(pivot), delta) floor from the modified Cholesky factorization, eq. 3.49 on p. 54 of the same book (credited there to Gill, Murray, and Wright, with Moré and Sorensen for the bounded condition number), applied in the eigenbasis. Written that way it is the "PT-inverse" of Paternain, Mokhtari, and Ribeiro, SIAM J. Optim. 29(1) 2019, Definition 2.1 (arXiv:1707.08028 (https://arxiv.org/abs/1707.08028)): $|\lambda_{ii}|$ if $|\lambda_{ii}| \ge m$, else m. It is continuous, as you say, and it also bounds the effective condition number of the solve by $1 / \sqrt u$ ~ 6.7e7, which the old cutoff never did.

On the value of the floor:

  • The relative term follows the $\delta = \sqrt u$ on p. 49. R's nlm (the Dennis and Schnabel minimizer in src/appl/uncmin.c (https://raw.githubusercontent.com/wch/r-source/trunk/src/appl/uncmin.c)) uses the same rule: tol = sqrt(epsm) with pivots floored at tol*diagmx, the square root of epsilon times the largest diagonal entry.
  • The absolute term is the minimum absolute tolerance you asked for in the other comment, so an all-zero Hessian gives a finite direction. I dropped the numeric_limits::min() clamp because dividing a moderate gradient by it overflows to infinity. Precedent for an absolute clamp on curvature: Ceres' min_lm_diagonal = 1e-6 on diag(J'J) (include/ceres/solver.h (https://raw.githubusercontent.com/ceres-solver/ceres-solver/master/include/ceres/solver.h)) and Ipopt's min_hessian_perturbation (options (https://coin-or.github.io/Ipopt/OPTIONS.html)). Using √u for the absolute term makes it agree with the relative term when the largest eigenvalue is 1, the "well scaled" assumption Nocedal and Wright use on p. 196.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Your suggestion divides by tolerance, which at that point was n * eps * max(abs(lambda)). That gives a step along a flat direction of roughly 1e15 times the gradient.

Claude seems to forget that I had also questioned whether that tolerance was appropriate.
And 1e15 times zero ("flat gradient") is still zero.

But yes, this all makes sense.

Precedent for an absolute clamp on curvature: Ceres' min_lm_diagonal = 1e-6 on diag(J'J) and Ipopt's min_hessian_perturbation.

Feels like this sentence cuts off early? Maybe should be

and Ipopt's min_hessian_perturbation = 1e-20 on the iteration matrix.

Comment thread src/stan/optimization/newton.hpp Outdated
* @return true if all elements are finite
*/
template <typename Vec>
inline bool all_finite(const Vec& v) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This looks like a reimplementation of stan::math::is_scal_finite

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

any comment?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Sorry! yes I need to fixup a few things . We can totally remove this function

@nhuurre

nhuurre commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

By the way, and this is a pre-existing issue, but even a simple model like

parameters {
  vector[3] x;
}
model {
  x ~ normal(0, [10,1,0.1]');
}

takes 100 iterations, which is completely unreasonable behavior for a Newton solver. Apparently step size is 1.90735e-06 for every iteration so something is very wrong.

And that something is stan::model::grad_hess_log_prob. I didn't look into the details, but I do know that a finite-difference algorithm should divide by epsilon, like, at some point, and this one never does.
If I change these "multiply-by-half_epsilon" to "divide-by-half_epsilon" (which is the smallest change that makes the algorithm look like it could be correct), stepsize recovers to 1 and the solver converges in "only" 37 iterations.

stan::model::grad_hess_log_prob is not used by anything else since Laplace sampler gets the Hessian from stan::math::internal::finite_diff_hessian_auto. (I would have thought that that internal namespace meant it's not used outside of math but whatever)
If I change Newton solver to use stan::math::internal::finite_diff_hessian_auto then it converges in 2 iterations, with stepsize 1 for both. This is how Newton should behave on a multinormal target.

@WardBrian

Copy link
Copy Markdown
Member

stan::model::grad_hess_log_prob is not used by anything else since Laplace sampler gets the Hessian from stan::math::internal::finite_diff_hessian_auto. (I would have thought that that internal namespace meant it's not used outside of math but whatever)

That ship may have sailed... Even within math, it's not used by any other code, so I'm not sure why it (or finite_diff_hessian_times_vector_auto, which was just following the pattern) is in internal

If I change Newton solver to use stan::math::internal::finite_diff_hessian_auto then it converges in 2 iterations, with stepsize 1 for both. This is how Newton should behave on a multinormal target.

This sounds like it would be worthy of it's own PR

@SteveBronder

Copy link
Copy Markdown
Collaborator Author

stan::model::grad_hess_log_prob is not used by anything else since Laplace sampler gets the Hessian from stan::math::internal::finite_diff_hessian_auto. (I would have thought that that internal namespace meant it's not used outside of math but whatever)

That ship may have sailed... Even within math, it's not used by any other code, so I'm not sure why it (or finite_diff_hessian_times_vector_auto, which was just following the pattern) is in internal

For the record, anything inside of math's internal namespace has zero API guarantee aka the math library can change these on a whim with no version notice. This is what Eigen does as well (and partly why upgrading Eigen is such a hassle for us)

…st normal double

Match the premultiplied threshold used by Eigen's rank-revealing
decompositions on master: Higham's backward error bound with a factor
of 4 covering the constant, and a lower clamp at
numeric_limits<double>::min() so an all-zero Hessian yields a positive
cutoff. Adds tests for the cutoff boundary and for an all-zero Hessian
with a nonzero gradient.
@WardBrian

Copy link
Copy Markdown
Member

Yeah I understand that. These functions were added entirely on their own with no direct usages in math, so I suspect the internal was more due to a lack of confidence or something to discourage use rather than an API stability concern

Replace the rank cutoff in make_negative_definite_and_solve with a
saturating inverse: each eigenvalue magnitude is floored at
delta = max(sqrt(u) * max|lambda|, sqrt(u)) before inverting, following
the modified-Newton floor in Nocedal and Wright, Numerical Optimization,
2nd ed., Sec. 3.4 (eq. 3.49; delta of order sqrt(u), p. 49).

Dropping small eigenvalues stalled on targets with a nonzero gradient
but no curvature, and rounding noise in the finite-difference Hessian
could still be inverted into steps of order 1e19. The floor is
continuous in the eigenvalues and bounds the effective condition number
of the solve by 1 / sqrt(u).

Adds a linear_target test model and tests at the solve, step, and
service layers: small eigenvalues are floored rather than dropped, the
solve is continuous across the old cutoff, and a linear target moves
uphill by a bounded step without reporting convergence.
@stan-buildbot

Copy link
Copy Markdown
Contributor
Name Old Result New Result Ratio Performance change( 1 - new / old )
stat_comp_benchmarks/benchmarks/gp_regr/gen_gp_data.stan 0.04 0.04 1.01 0.7% faster
stat_comp_benchmarks/benchmarks/gp_regr/gp_regr.stan 0.17 0.17 1.02 1.58% faster
stat_comp_benchmarks/benchmarks/garch/garch.stan 0.88 0.86 1.03 2.91% faster
stat_comp_benchmarks/benchmarks/low_dim_corr_gauss/low_dim_corr_gauss.stan 0.02 0.02 1.01 0.71% faster
stat_comp_benchmarks/benchmarks/eight_schools/eight_schools.stan 0.11 0.11 1.0 0.3% faster
stat_comp_benchmarks/benchmarks/low_dim_gauss_mix/low_dim_gauss_mix.stan 8.35 8.02 1.04 3.96% faster
stat_comp_benchmarks/benchmarks/gp_pois_regr/gp_pois_regr.stan 3.73 3.7 1.01 0.88% faster
stat_comp_benchmarks/benchmarks/pkpd/one_comp_mm_elim_abs.stan 32.87 32.62 1.01 0.76% faster
stat_comp_benchmarks/benchmarks/pkpd/sim_one_comp_mm_elim_abs.stan 0.38 0.39 0.98 -1.87% slower
stat_comp_benchmarks/benchmarks/irt_2pl/irt_2pl.stan 6.65 6.6 1.01 0.74% faster
stat_comp_benchmarks/benchmarks/sir/sir.stan 136.54 130.8 1.04 4.2% faster
stat_comp_benchmarks/benchmarks/arK/arK.stan 3.21 3.22 1.0 -0.11% slower
stat_comp_benchmarks/benchmarks/arma/arma.stan 0.59 0.6 0.99 -1.07% slower
stat_comp_benchmarks/benchmarks/low_dim_gauss_mix_collapse/low_dim_gauss_mix_collapse.stan 25.06 25.65 0.98 -2.34% slower
performance.compilation 378.73 390.71 0.97 -3.16% slower
Mean result: 1.0059106860439384

Jenkins Console Log
Jenkins Build Stages
Commit hash: e1ee0a7f31086a9e7cf83189e98c1a2c4a0a8136

Machine information
Distributor ID:	Ubuntu
Description:	Ubuntu 20.04.3 LTS
Release:	20.04
Codename:	focal

CPU:

Architecture:                            x86_64
CPU op-mode(s):                          32-bit, 64-bit
Byte Order:                              Little Endian
Address sizes:                           52 bits physical, 57 bits virtual
CPU(s):                                  192
On-line CPU(s) list:                     0-191
Thread(s) per core:                      2
Core(s) per socket:                      48
Socket(s):                               2
NUMA node(s):                            2
Vendor ID:                               AuthenticAMD
CPU family:                              25
Model:                                   17
Model name:                              AMD EPYC 9474F 48-Core Processor
Stepping:                                1
Frequency boost:                         enabled
CPU MHz:                                 1494.681
CPU max MHz:                             4114.4229
CPU min MHz:                             1500.0000
BogoMIPS:                                7189.04
Virtualization:                          AMD-V
L1d cache:                               3 MiB
L1i cache:                               3 MiB
L2 cache:                                96 MiB
L3 cache:                                512 MiB
NUMA node0 CPU(s):                       0-47,96-143
NUMA node1 CPU(s):                       48-95,144-191
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Mitigation; Safe RET
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; usercopy/swapgs barriers and __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; Enhanced / Automatic IBRS; IBPB conditional; STIBP always-on; PBRSB-eIBRS Not affected; BHI Not affected
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Mitigation; Clear CPU buffers
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Mitigation; IBPB before exit to userspace
Flags:                                   fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc rep_good amd_lbr_v2 nopl xtopology nonstop_tsc cpuid extd_apicid aperfmperf rapl pni pclmulqdq monitor ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand lahf_lm cmp_legacy svm extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw ibs skinit wdt tce topoext perfctr_core perfctr_nb bpext perfctr_llc mwaitx cpb cat_l3 cdp_l3 hw_pstate ssbd mba perfmon_v2 ibrs ibpb stibp ibrs_enhanced vmmcall fsgsbase bmi1 avx2 smep bmi2 erms invpcid cqm rdt_a avx512f avx512dq rdseed adx smap avx512ifma clflushopt clwb avx512cd sha_ni avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local user_shstk avx512_bf16 clzero irperf xsaveerptr rdpru wbnoinvd amd_ppin cppc arat npt lbrv svm_lock nrip_save tsc_scale vmcb_clean flushbyasid decodeassists pausefilter pfthreshold avic v_vmsave_vmload vgif x2avic v_spec_ctrl vnmi avx512vbmi umip pku ospke avx512_vbmi2 gfni vaes vpclmulqdq avx512_vnni avx512_bitalg avx512_vpopcntdq la57 rdpid overflow_recov succor smca fsrm flush_l1d debug_swap

G++:

g++ (Ubuntu 9.4.0-1ubuntu1~20.04) 9.4.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Clang:

clang version 10.0.0-4ubuntu1 
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin

@stan-buildbot

Copy link
Copy Markdown
Contributor
Name Old Result New Result Ratio Performance change( 1 - new / old )
stat_comp_benchmarks/benchmarks/gp_regr/gen_gp_data.stan 0.04 0.04 0.99 -0.9% slower
stat_comp_benchmarks/benchmarks/gp_regr/gp_regr.stan 0.17 0.16 1.02 1.68% faster
stat_comp_benchmarks/benchmarks/garch/garch.stan 0.87 0.85 1.01 1.47% faster
stat_comp_benchmarks/benchmarks/low_dim_corr_gauss/low_dim_corr_gauss.stan 0.02 0.02 0.96 -4.3% slower
stat_comp_benchmarks/benchmarks/eight_schools/eight_schools.stan 0.11 0.11 1.01 1.27% faster
stat_comp_benchmarks/benchmarks/low_dim_gauss_mix/low_dim_gauss_mix.stan 8.32 8.01 1.04 3.71% faster
stat_comp_benchmarks/benchmarks/gp_pois_regr/gp_pois_regr.stan 3.72 3.7 1.01 0.65% faster
stat_comp_benchmarks/benchmarks/pkpd/one_comp_mm_elim_abs.stan 33.02 32.28 1.02 2.24% faster
stat_comp_benchmarks/benchmarks/pkpd/sim_one_comp_mm_elim_abs.stan 0.38 0.39 0.97 -2.88% slower
stat_comp_benchmarks/benchmarks/irt_2pl/irt_2pl.stan 6.62 6.67 0.99 -0.76% slower
stat_comp_benchmarks/benchmarks/sir/sir.stan 137.45 133.66 1.03 2.76% faster
stat_comp_benchmarks/benchmarks/arK/arK.stan 3.22 3.22 1.0 0.25% faster
stat_comp_benchmarks/benchmarks/arma/arma.stan 0.59 0.6 0.99 -0.85% slower
stat_comp_benchmarks/benchmarks/low_dim_gauss_mix_collapse/low_dim_gauss_mix_collapse.stan 24.98 25.6 0.98 -2.45% slower
performance.compilation 390.33 388.98 1.0 0.34% faster
Mean result: 1.0019337532567159

Jenkins Console Log
Jenkins Build Stages
Commit hash: 7e5ee47a0634cc5f5f59c91e7e28b9f4519385fb

Machine information
Distributor ID:	Ubuntu
Description:	Ubuntu 20.04.3 LTS
Release:	20.04
Codename:	focal

CPU:

Architecture:                            x86_64
CPU op-mode(s):                          32-bit, 64-bit
Byte Order:                              Little Endian
Address sizes:                           52 bits physical, 57 bits virtual
CPU(s):                                  192
On-line CPU(s) list:                     0-191
Thread(s) per core:                      2
Core(s) per socket:                      48
Socket(s):                               2
NUMA node(s):                            2
Vendor ID:                               AuthenticAMD
CPU family:                              25
Model:                                   17
Model name:                              AMD EPYC 9474F 48-Core Processor
Stepping:                                1
Frequency boost:                         enabled
CPU MHz:                                 1499.401
CPU max MHz:                             4114.4229
CPU min MHz:                             1500.0000
BogoMIPS:                                7189.04
Virtualization:                          AMD-V
L1d cache:                               3 MiB
L1i cache:                               3 MiB
L2 cache:                                96 MiB
L3 cache:                                512 MiB
NUMA node0 CPU(s):                       0-47,96-143
NUMA node1 CPU(s):                       48-95,144-191
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Mitigation; Safe RET
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; usercopy/swapgs barriers and __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; Enhanced / Automatic IBRS; IBPB conditional; STIBP always-on; PBRSB-eIBRS Not affected; BHI Not affected
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Mitigation; Clear CPU buffers
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Mitigation; IBPB before exit to userspace
Flags:                                   fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc rep_good amd_lbr_v2 nopl xtopology nonstop_tsc cpuid extd_apicid aperfmperf rapl pni pclmulqdq monitor ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand lahf_lm cmp_legacy svm extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw ibs skinit wdt tce topoext perfctr_core perfctr_nb bpext perfctr_llc mwaitx cpb cat_l3 cdp_l3 hw_pstate ssbd mba perfmon_v2 ibrs ibpb stibp ibrs_enhanced vmmcall fsgsbase bmi1 avx2 smep bmi2 erms invpcid cqm rdt_a avx512f avx512dq rdseed adx smap avx512ifma clflushopt clwb avx512cd sha_ni avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local user_shstk avx512_bf16 clzero irperf xsaveerptr rdpru wbnoinvd amd_ppin cppc arat npt lbrv svm_lock nrip_save tsc_scale vmcb_clean flushbyasid decodeassists pausefilter pfthreshold avic v_vmsave_vmload vgif x2avic v_spec_ctrl vnmi avx512vbmi umip pku ospke avx512_vbmi2 gfni vaes vpclmulqdq avx512_vnni avx512_bitalg avx512_vpopcntdq la57 rdpid overflow_recov succor smca fsrm flush_l1d debug_swap

G++:

g++ (Ubuntu 9.4.0-1ubuntu1~20.04) 9.4.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Clang:

clang version 10.0.0-4ubuntu1 
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin

@SteveBronder SteveBronder self-assigned this Sep 9, 2026
@stan-buildbot

Copy link
Copy Markdown
Contributor
Name Old Result New Result Ratio Performance change( 1 - new / old )
stat_comp_benchmarks/benchmarks/gp_regr/gp_regr.stan 0.22 0.23 0.98 -1.91% slower
stat_comp_benchmarks/benchmarks/gp_regr/gen_gp_data.stan 0.06 0.06 1.01 0.86% faster
stat_comp_benchmarks/benchmarks/low_dim_gauss_mix/low_dim_gauss_mix.stan 6.4 6.37 1.0 0.43% faster
stat_comp_benchmarks/benchmarks/low_dim_corr_gauss/low_dim_corr_gauss.stan 0.02 0.02 1.01 0.59% faster
stat_comp_benchmarks/benchmarks/irt_2pl/irt_2pl.stan 9.05 9.12 0.99 -0.76% slower
stat_comp_benchmarks/benchmarks/gp_pois_regr/gp_pois_regr.stan 4.49 4.49 1.0 0.07% faster
stat_comp_benchmarks/benchmarks/sir/sir.stan 168.25 166.93 1.01 0.78% faster
stat_comp_benchmarks/benchmarks/garch/garch.stan 0.89 0.9 0.99 -1.36% slower
stat_comp_benchmarks/benchmarks/arma/arma.stan 0.7 0.71 0.99 -0.86% slower
stat_comp_benchmarks/benchmarks/pkpd/one_comp_mm_elim_abs.stan 42.67 41.71 1.02 2.24% faster
stat_comp_benchmarks/benchmarks/pkpd/sim_one_comp_mm_elim_abs.stan 0.59 0.59 1.0 0.09% faster
stat_comp_benchmarks/benchmarks/low_dim_gauss_mix_collapse/low_dim_gauss_mix_collapse.stan 21.12 21.31 0.99 -0.91% slower
stat_comp_benchmarks/benchmarks/eight_schools/eight_schools.stan 0.11 0.11 1.02 1.64% faster
stat_comp_benchmarks/benchmarks/arK/arK.stan 3.19 3.21 0.99 -0.62% slower
performance.compilation 385.58 381.9 1.01 0.95% faster
Mean result: 1.0009454316604391

Jenkins Console Log
Jenkins Build Stages
Commit hash: c12533ce4ebb7044a5ecf19294233ce32c2d9524

Machine information
Distributor ID:	Ubuntu
Description:	Ubuntu 20.04.3 LTS
Release:	20.04
Codename:	focal

CPU:

Architecture:                            x86_64
CPU op-mode(s):                          32-bit, 64-bit
Byte Order:                              Little Endian
Address sizes:                           43 bits physical, 48 bits virtual
CPU(s):                                  256
On-line CPU(s) list:                     0-255
Thread(s) per core:                      2
Core(s) per socket:                      64
Socket(s):                               2
NUMA node(s):                            2
Vendor ID:                               AuthenticAMD
CPU family:                              23
Model:                                   49
Model name:                              AMD EPYC 7742 64-Core Processor
Stepping:                                0
Frequency boost:                         enabled
CPU MHz:                                 1497.227
CPU max MHz:                             3416.0681
CPU min MHz:                             1500.0000
BogoMIPS:                                4491.55
Virtualization:                          AMD-V
L1d cache:                               4 MiB
L1i cache:                               4 MiB
L2 cache:                                64 MiB
L3 cache:                                512 MiB
NUMA node0 CPU(s):                       0-63,128-191
NUMA node1 CPU(s):                       64-127,192-255
Vulnerability Gather data sampling:      Not affected
Vulnerability Indirect target selection: Not affected
Vulnerability Itlb multihit:             Not affected
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Not affected
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Mitigation; untrained return thunk; SMT enabled with STIBP protection
Vulnerability Spec rstack overflow:      Mitigation; Safe RET
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; usercopy/swapgs barriers and __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; Retpolines; IBPB conditional; STIBP always-on; RSB filling; PBRSB-eIBRS Not affected; BHI Not affected
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Not affected
Vulnerability Vmscape:                   Mitigation; IBPB before exit to userspace
Flags:                                   fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc cpuid extd_apicid aperfmperf rapl pni pclmulqdq monitor ssse3 fma cx16 sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c rdrand lahf_lm cmp_legacy svm extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw ibs skinit wdt tce topoext perfctr_core perfctr_nb bpext perfctr_llc mwaitx cpb cat_l3 cdp_l3 hw_pstate ssbd mba ibrs ibpb stibp vmmcall fsgsbase bmi1 avx2 smep bmi2 cqm rdt_a rdseed adx smap clflushopt clwb sha_ni xsaveopt xsavec xgetbv1 xsaves cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local clzero irperf xsaveerptr rdpru wbnoinvd amd_ppin arat npt lbrv svm_lock nrip_save tsc_scale vmcb_clean flushbyasid decodeassists pausefilter pfthreshold avic v_vmsave_vmload vgif v_spec_ctrl umip rdpid overflow_recov succor smca sev sev_es

G++:

g++ (Ubuntu 9.4.0-1ubuntu1~20.04) 9.4.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Clang:

clang version 10.0.0-4ubuntu1 
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin

@SteveBronder

Copy link
Copy Markdown
Collaborator Author

By the way, and this is a pre-existing issue, but even a simple model like

If I'm being honest, there are a lot of funky things in this newton optimizer

Compute the Hessian with stan::math::internal::finite_diff_hessian_auto,
a central difference of the gradient with a per-coordinate step size.
This costs 2n + 1 gradient evaluations per step instead of the 4n + 1
of the four-point stencil in grad_hess_log_prob, and matches what
laplace_sample already uses.

Evaluate the log density with propto = false so the backtracking line
search can run on plain doubles instead of a reverse-mode pass whose
gradient was discarded. The returned log density therefore includes
constant terms.

Work in Eigen vectors directly, dropping the element-wise copies, and
forward output_stream to the log density evaluations instead of
ignoring it.
@SteveBronder
SteveBronder force-pushed the fix/newton-flat-direction-3425 branch from f6dbb1a to 9f77b74 Compare September 9, 2026 20:42
@stan-buildbot

Copy link
Copy Markdown
Contributor
Name Old Result New Result Ratio Performance change( 1 - new / old )
stat_comp_benchmarks/benchmarks/low_dim_gauss_mix_collapse/low_dim_gauss_mix_collapse.stan 8.44 8.31 1.02 1.57% faster
stat_comp_benchmarks/benchmarks/gp_pois_regr/gp_pois_regr.stan 2.3 2.23 1.03 3.07% faster
stat_comp_benchmarks/benchmarks/eight_schools/eight_schools.stan 0.06 0.06 0.99 -0.92% slower
stat_comp_benchmarks/benchmarks/irt_2pl/irt_2pl.stan 5.15 4.85 1.06 5.91% faster
stat_comp_benchmarks/benchmarks/garch/garch.stan 0.44 0.42 1.05 4.73% faster
stat_comp_benchmarks/benchmarks/low_dim_gauss_mix/low_dim_gauss_mix.stan 2.76 2.71 1.02 1.78% faster
stat_comp_benchmarks/benchmarks/pkpd/sim_one_comp_mm_elim_abs.stan 0.25 0.24 1.04 4.01% faster
stat_comp_benchmarks/benchmarks/pkpd/one_comp_mm_elim_abs.stan 19.55 19.11 1.02 2.25% faster
stat_comp_benchmarks/benchmarks/gp_regr/gen_gp_data.stan 0.03 0.03 1.0 0.46% faster
stat_comp_benchmarks/benchmarks/gp_regr/gp_regr.stan 0.11 0.1 1.12 10.7% faster
stat_comp_benchmarks/benchmarks/sir/sir.stan 66.9 68.89 0.97 -2.98% slower
stat_comp_benchmarks/benchmarks/arK/arK.stan 1.55 1.55 1.0 0.47% faster
stat_comp_benchmarks/benchmarks/low_dim_corr_gauss/low_dim_corr_gauss.stan 0.01 0.01 0.94 -6.35% slower
stat_comp_benchmarks/benchmarks/arma/arma.stan 0.29 0.28 1.02 1.98% faster
performance.compilation 239.85 242.91 0.99 -1.28% slower
Mean result: 1.0188207469418098

Jenkins Console Log
Jenkins Build Stages
Commit hash: ab7ce750b67aaf45c7f7782a18c2eda05121bc19

Machine information
Distributor ID:	Ubuntu
Description:	Ubuntu 20.04.3 LTS
Release:	20.04
Codename:	focal

CPU:

Architecture:                            x86_64
CPU op-mode(s):                          32-bit, 64-bit
Byte Order:                              Little Endian
Address sizes:                           46 bits physical, 48 bits virtual
CPU(s):                                  80
On-line CPU(s) list:                     0-79
Thread(s) per core:                      2
Core(s) per socket:                      20
Socket(s):                               2
NUMA node(s):                            2
Vendor ID:                               GenuineIntel
CPU family:                              6
Model:                                   85
Model name:                              Intel(R) Xeon(R) Gold 6248 CPU @ 2.50GHz
Stepping:                                7
CPU MHz:                                 999.941
CPU max MHz:                             3900.0000
CPU min MHz:                             1000.0000
BogoMIPS:                                5000.00
Virtualization:                          VT-x
L1d cache:                               1.3 MiB
L1i cache:                               1.3 MiB
L2 cache:                                40 MiB
L3 cache:                                55 MiB
NUMA node0 CPU(s):                       0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,64,66,68,70,72,74,76,78
NUMA node1 CPU(s):                       1,3,5,7,9,11,13,15,17,19,21,23,25,27,29,31,33,35,37,39,41,43,45,47,49,51,53,55,57,59,61,63,65,67,69,71,73,75,77,79
Vulnerability Gather data sampling:      Mitigation; Microcode
Vulnerability Indirect target selection: Mitigation; Aligned branch/return thunks
Vulnerability Itlb multihit:             KVM: Mitigation: Split huge pages
Vulnerability L1tf:                      Not affected
Vulnerability Mds:                       Not affected
Vulnerability Meltdown:                  Not affected
Vulnerability Mmio stale data:           Mitigation; Clear CPU buffers; SMT vulnerable
Vulnerability Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Mitigation; Enhanced IBRS
Vulnerability Spec rstack overflow:      Not affected
Vulnerability Spec store bypass:         Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:                Mitigation; usercopy/swapgs barriers and __user pointer sanitization
Vulnerability Spectre v2:                Mitigation; Enhanced / Automatic IBRS; IBPB conditional; PBRSB-eIBRS SW sequence; BHI SW loop, KVM SW loop
Vulnerability Srbds:                     Not affected
Vulnerability Tsa:                       Not affected
Vulnerability Tsx async abort:           Mitigation; TSX disabled
Vulnerability Vmscape:                   Mitigation; IBPB before exit to userspace
Flags:                                   fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc art arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch cpuid_fault epb cat_l3 cdp_l3 intel_ppin ssbd mba ibrs ibpb stibp ibrs_enhanced tpr_shadow flexpriority ept vpid ept_ad fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid cqm mpx rdt_a avx512f avx512dq rdseed adx smap clflushopt clwb intel_pt avx512cd avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local dtherm ida arat pln pts hwp hwp_act_window hwp_epp hwp_pkg_req vnmi pku ospke avx512_vnni md_clear flush_l1d arch_capabilities

G++:

g++ (Ubuntu 9.4.0-1ubuntu1~20.04) 9.4.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Clang:

clang version 10.0.0-4ubuntu1 
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin

@SteveBronder

Copy link
Copy Markdown
Collaborator Author

@WardBrian so the cmdstan test is failing, but imo I think we should make this change.

The difference is that we now use propto=false in newton. This is because we only need the gradient and hessian calculation once, while most of the time is spent in a loop where we use doubles everywhere. If we do propto=true then the call with just doubles returns 0. So that means we need to use var types in the loop when we have no intention of looking at the gradient. I think we should change / remove the cmdstan test and leave propto=false here. Thoughts?

@WardBrian

Copy link
Copy Markdown
Member

It will be a bit odd that newton and the other optimizers wouldn't agree with each other on a model where they all find the same mode but report a different lp__. But I could get over that.

There is also the option of using var types but not actually calling gradient, which would still be better than what the current code does.

@nhuurre

nhuurre commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

There is also the option of using var types but not actually calling gradient

That's what stan::model::log_prob_propto does.
I'm not sure how expensive calculating all the proportionality constants is compared to the extra cost of the autodiff forward pass. Likely depends on the model; e.g. gaussian constants are cheap, binomial quite expensive.

@SteveBronder

Copy link
Copy Markdown
Collaborator Author

I'm not sure how expensive calculating all the proportionality constants is compared to the extra cost of the autodiff forward pass. Likely depends on the model; e.g. gaussian constants are cheap, binomial quite expensive.

I think compared to the forward pass I would not imagine them to be very expensive. For instance normal_lpdf is pretty simple

  if constexpr (include_summand<propto>::value) {
    logp += NEG_LOG_SQRT_TWO_PI * N;
  }
  // actually idt this one will even go off because of T_scale
  if constexpr (include_summand<propto, T_scale>::value) {
    logp -= sum(log(sigma_val)) * N / math::size(sigma);
  }

I think many of them are kind of like this.

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.

Newton optimizer produces NaN parameters for a target with a flat direction, no error raised

5 participants