Skip to content

More stable logistic_lpdf/lcdf/lccdf computation - #3398

Open
avehtari wants to merge 10 commits into
developfrom
improve-logistic_lpdf-derivative-stability
Open

avehtari wants to merge 10 commits into
developfrom
improve-logistic_lpdf-derivative-stability

Conversation

@avehtari

@avehtari avehtari commented Sep 13, 2026

Copy link
Copy Markdown
Member

herbie program https://herbie.uwplse.org/ detects and proposes fixes for inaccurate floating-point expressions. I have used it for loo and posterior, and now tested for Stan math distributions. herbie can provide a list with assessment on accuracy improvement. In some cases there can be high relative error near 0, which doesn't have practical relevance for Stan. Instead of including all suggestions, I check that the current version may have big error or over/underflow with feasible input. The fixes have been created with help from Sol. I have checked that the fixes make sense and there are sensible regression test.

Summary

logistic_lpdf

The logistic_lpdf location derivative separately exponentiated mu / sigma and y / sigma.
When mu / sigma > log(DBL_MAX / 2), this could produce an infinite or NaN gradient even though
the log density and true gradient remained finite. For example, y = 711, mu = 710, and
sigma = 1 returned a NaN location gradient instead of tanh(0.5).

The denominator can overflow before 2 * exp(m) does leading to big errors. For example: m = log(DBL_MAX / 2) - 1e-6 and d = 0.1 old gradient is 1.0 while exact gradient is tanh(0.05) = 0.04995837495788

  • Before any intermediate overflow, error is often small.
  • Denominator overflow can produce a large but finite error first.
  • Numerator overflow then produces -Inf or NaN.
  • The transition is abrupt, which is particularly problematic for HMC trajectories.

The new tanh(d / 2) / sigma form depends only on the standardized difference and remains bounded by
1 / sigma.

logistic_lccdf

For logistic_lccdf(z | 0, 1):

z Current value Stable value Current gradient Stable gradient
30 -30.0010 -30 -1.00102 -1
34 -33.9642 -34 -0.96484 -1
35 -34.9450 -35 -0.94652 -1
36 -36.0437 -36 -1.04462 -1
36.5 -36.0437 -36.5 -0.63359 -1
36.736 -36.0437 -36.736 -0.50040 -1
36.74 -Inf -36.74 -Inf -1

The error is already substantial before saturation, and happens with values of z that are not completely unfeasible.

After fix

  • Value uses log1m_inv_logit(z).
  • Gradients inv_logit(z) / sigma.

logistic_lcdf

For logistic_lcdf(z | 0, 1):

z Current value Stable value
-730 -729.9999998 -730
-740 -739.997421 -740
-742 -742.042177 -742
-744 -743.746925 -744
-745 -744.440072 -745
-745.2 -Inf -745.2

The LCDF gradient remains exactly 1 through the subnormal region, then becomes NaN when
exp(z) underflows completely. This one is least likely to happen.

  • After fix
    • Value uses log_inv_logit(z).
    • Gradients inv_logit(-z) / sigma.

Tests

  • Regression tests added. CPU tests test against known values, OpenCL tests test against CPU.
  • 9 of 16 new CPU tests and 4 of 38 OpenCL tests fail on develop and pass with this PR.

Side Effects

Removes the following comment from the logistic_lccdf and logistic_lcdf code (as the derivatives are simpler and more stable after this PR)

// TODO(Andrew) Further simplify derivatives and log-scale below

Release notes

  • Improved stability of logistic_lpdf, logistic_lccdf and logistic_lcdf derivatives.

Checklist

  • Copyright holder: Aki Vehtari

    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

    • unit tests pass (to run, use: ./runTests.py test/unit)
    • header checks pass, (make test-headers)
    • dependencies checks pass, (make test-math-dependencies)
    • docs build, (make doxygen)
    • code passes the built in C++ standards checks (make cpplint)
  • the code is written in idiomatic C++ and changes are documented in the doxygen

  • the new changes are tested

@avehtari avehtari changed the title More stable logistic_lpdf derivative More stable logistic_lpdf/lcdf/lccdf derivatives Sep 13, 2026
@avehtari avehtari changed the title More stable logistic_lpdf/lcdf/lccdf derivatives More stable logistic_lpdf/lcdf/lccdf computation Sep 13, 2026
@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.31 8.26 1.01 0.62% faster
stat_comp_benchmarks/benchmarks/gp_pois_regr/gp_pois_regr.stan 2.24 2.2 1.02 1.75% faster
stat_comp_benchmarks/benchmarks/eight_schools/eight_schools.stan 0.06 0.06 1.02 1.48% faster
stat_comp_benchmarks/benchmarks/irt_2pl/irt_2pl.stan 4.84 4.73 1.02 2.32% faster
stat_comp_benchmarks/benchmarks/garch/garch.stan 0.43 0.42 1.01 0.77% faster
stat_comp_benchmarks/benchmarks/low_dim_gauss_mix/low_dim_gauss_mix.stan 2.75 2.67 1.03 2.95% faster
stat_comp_benchmarks/benchmarks/pkpd/sim_one_comp_mm_elim_abs.stan 0.25 0.24 1.02 1.74% faster
stat_comp_benchmarks/benchmarks/pkpd/one_comp_mm_elim_abs.stan 19.34 19.0 1.02 1.76% faster
stat_comp_benchmarks/benchmarks/gp_regr/gen_gp_data.stan 0.02 0.02 0.99 -1.11% slower
stat_comp_benchmarks/benchmarks/gp_regr/gp_regr.stan 0.1 0.1 1.04 3.57% faster
stat_comp_benchmarks/benchmarks/sir/sir.stan 66.74 65.12 1.02 2.42% faster
stat_comp_benchmarks/benchmarks/arK/arK.stan 1.56 1.51 1.04 3.39% faster
stat_comp_benchmarks/benchmarks/low_dim_corr_gauss/low_dim_corr_gauss.stan 0.01 0.01 1.0 0.1% faster
stat_comp_benchmarks/benchmarks/arma/arma.stan 0.28 0.28 1.03 2.7% faster
performance.compilation 242.67 239.05 1.02 1.49% faster
Mean result: 1.0177607799093016

Jenkins Console Log
Jenkins Build Stages
Commit hash: 462bc7269ad3260b4564ce2d4c0e4e32b24944aa

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:                                 997.515
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

@avehtari

Copy link
Copy Markdown
Member Author

Follow-up

logistic_cdf carried the same exp(logistic_lpdf(y, mu, sigma)) / Pn pattern
this PR removed from the log-scale functions, so it is converted too — the
partials now accumulate inv_logit(-z) / sigma and are rescaled by the cdf
product. The quotient was 0 / 0 once Pn underflowed (NaN at z = -746 on
the CPU, and from z ≈ -709 on the device, where exp(-z) overflows first).

logistic_lpdf kept the old 2 / (1 + exp(z)) - 1 form for the y partial
while the mu partial moved to tanh(z / 2). Since d/dy == -d/dmu exactly,
that left the two disagreeing near y == mu, where the exp form loses all
relative precision:

z 2 / (1 + exp(z)) - 1 -tanh(z / 2) rel err
1e-12 -5.00044450291e-13 -5.0e-13 8.9e-05
1e-14 -4.88498130835e-15 -5.0e-15 2.3e-02
1e-16 +0.0 -5.0e-17 1.0

All three partials are now built from one tanh(z / 2) / sigma, which also
drops an exp from the OpenCL kernel.

Two smaller fixes in the same files:

  • logistic_lccdf returned from inside the accumulation loop on y == INFTY,
    keeping the partials already accumulated for the preceding elements. Both
    infinity scans now run before the loop.
  • The OpenCL logistic_lcdf and logistic_cdf computed 0 * INFTY = NaN for
    the scale partial at y == INFTY, where prim skips the element outright.

Tests: mu != 0 / sigma != 1 cases so the scale partial is not aliased by
sigma == 1; a z = 30 case for lccdf, where the old cancellation was
already 0.1 % off well before it returned -Inf; a y ≈ mu case asserting
y.adj() == -mu.adj() bit-for-bit; a vectorised case for the mu edge's
container path; and test/unit/math/rev/prob/logistic_cdf_test.cpp.

@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.55% faster
stat_comp_benchmarks/benchmarks/gp_regr/gp_regr.stan 0.17 0.16 1.02 1.72% faster
stat_comp_benchmarks/benchmarks/garch/garch.stan 0.87 0.88 0.99 -0.54% slower
stat_comp_benchmarks/benchmarks/low_dim_corr_gauss/low_dim_corr_gauss.stan 0.01 0.01 0.95 -4.84% slower
stat_comp_benchmarks/benchmarks/eight_schools/eight_schools.stan 0.11 0.11 0.99 -0.86% slower
stat_comp_benchmarks/benchmarks/low_dim_gauss_mix/low_dim_gauss_mix.stan 8.33 8.39 0.99 -0.66% slower
stat_comp_benchmarks/benchmarks/gp_pois_regr/gp_pois_regr.stan 3.68 3.69 1.0 -0.27% slower
stat_comp_benchmarks/benchmarks/pkpd/one_comp_mm_elim_abs.stan 32.8 32.93 1.0 -0.39% slower
stat_comp_benchmarks/benchmarks/pkpd/sim_one_comp_mm_elim_abs.stan 0.38 0.38 1.0 -0.44% slower
stat_comp_benchmarks/benchmarks/irt_2pl/irt_2pl.stan 6.63 6.62 1.0 0.11% faster
stat_comp_benchmarks/benchmarks/sir/sir.stan 137.75 135.43 1.02 1.69% faster
stat_comp_benchmarks/benchmarks/arK/arK.stan 3.22 3.21 1.0 0.4% faster
stat_comp_benchmarks/benchmarks/arma/arma.stan 0.59 0.6 1.0 -0.46% slower
stat_comp_benchmarks/benchmarks/low_dim_gauss_mix_collapse/low_dim_gauss_mix_collapse.stan 25.25 25.11 1.01 0.57% faster
performance.compilation 376.96 391.95 0.96 -3.98% slower
Mean result: 0.9953912496749436

Jenkins Console Log
Jenkins Build Stages
Commit hash: 4b4fb7b315c6e633d2de11a303b7b17201fbfc2b

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:                                 1496.334
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

@andrjohns andrjohns left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

A couple more areas which can be moved to the log-scale for a bit more precision. I've only made the comments on the prim impls, but the same applies to all

Comment thread stan/math/prim/prob/logistic_lcdf.hpp Outdated
+= -(y_dbl - mu_dbl) * sigma_inv_vec
* exp(logistic_lpdf(y_dbl, mu_dbl, sigma_dbl)) / Pn;
if constexpr (is_any_autodiff_v<T_y, T_loc, T_scale>) {
const T_partials_return deriv = inv_logit(-scaled_diff) * sigma_inv_vec;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This step is still subject to underflow for very large scaled_diff (i.e., very small sigma), which would round the derivative to 0.

You can keep things on the log-scale for a bit longer using log1m_inv_logit:

const T_partials_return log_sigma = log(sigma_vec);
const T_partials_return log_deriv = log1m_inv_logit(scaled_diff) - log_sigma;
const T_partials_return deriv = exp(log_deriv);

if constexpr (is_autodiff_v<T_y>) {
  partials<0>(ops_partials)[n] += deriv;
}
if constexpr (is_autodiff_v<T_loc>) {
  partials<1>(ops_partials)[n] -= deriv;
}
if constexpr (is_autodiff_v<T_scale>) {
  partials<2>(ops_partials)[n] -= sign(scaled_diff) * exp(log(abs(scaled_diff)) + log_deriv);
}

With corresponding re-ordering/changes for the LCCDF impls

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This and other issues may have been not included in the PR as some cases are quite narrow. The following is Claude assisted

This case needs σ < 2.3e-16 at z = 745, tightening to σ < 3e-40 at z = 800, and the largest gradient recoverable anywhere in the window is 8.9e-16 (beyond that 1/σ overflows). Note log_inv_logit(z) — the value the lcdf accumulates — also reaches exactly 0 at the same threshold where inv_logit(-z) does, so this recovers the slope of a numerically flat region.

But since you asked, I made a simpler faster solution. In your proposal log1m_inv_logit is a second log1p_exp on top of the one the lcdf already computes for the value, plus log(sigma) and exp. Measured on the derivative factor alone: 3.4 ns/element currently, 16.0 ns for your proposal. Behind value_of_rec(scaled_diff) > 700 it's 3.7 ns, about 10%, and that's a well-predicted branch. So the common path keeps inv_logit(-z) * inv_sigma and only the underflowing case takes the log route.

Left the scale partial as scaled_diff * deriv. Where deriv underflows we have |z| > 745, so z * deriv is larger than deriv and can't underflow if deriv didn't. The log form there costs a log, exp, sign and abs for no gain, and log(abs(0)) at z = 0 relies on sign(0) to clean up the resulting 0 * -inf.

Tests at σ = 1e-300, z = 800, one per function. Each asserts inv_logit(-z) == 0 at the test point, so they fail on the previous code.

Comment thread stan/math/prim/prob/logistic_lpdf.hpp Outdated
Comment thread stan/math/prim/prob/logistic_lpdf.hpp Outdated
Comment on lines +70 to 72
const T_partials_return Pn = inv_logit(scaled_diff);

P *= Pn;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This can use the same log-calculations as the lcdf (see below), accumulating on the log scale and exponentiating the result (for the derivatives as well)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Claude assisted answer:

Measured against a long double reference, exp(Σ log inv_logit) is 749 ulp at N=100, z=−0.5 against 3.0 for the product, and 641 vs 1.0 at N=10, z=−70 — exp amplifies the summed log by |log P|·eps. It also doesn't extend the range: both forms underflow at the same points, including for a single observation at z = −745.2. inv_logit has no cancellation to avoid, unlike the difference in exp_mod_normal_cdf where the log scale genuinely buys something.

The rescaling partials *= cdf does drop the case where the cdf underflows but cdf × ∂log F/∂θ is representable. That's the same σ < 1e-16 window as the tail fix, and there the cdf returns exactly 0, so logistic_lcdf is the right function. If you want this to be changes, it needs sign tracking to exponentiate each partial separately.

Comment thread stan/math/prim/prob/logistic_cdf.hpp Outdated
EXPECT_DOUBLE_EQ(0.0, cdf.val());
EXPECT_DOUBLE_EQ(0.0, y.adj());
EXPECT_DOUBLE_EQ(0.0, mu.adj());
EXPECT_DOUBLE_EQ(0.0, sigma.adj());

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

With the extra log-scale changes for the derivatives above, the adjoint for sigma here ends up being (barely) non-zero (~7.76e-322) instead of underflowing

@avehtari

Copy link
Copy Markdown
Member Author

I had tested only that OpenCL code compiles and the code matches CPU code, but it turns out the compiler can optimize things so that the accuracy drops, so I'll run additional OpenCL tests tomorrow

@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.97 -2.66% slower
stat_comp_benchmarks/benchmarks/gp_regr/gp_regr.stan 0.16 0.16 1.0 -0.39% slower
stat_comp_benchmarks/benchmarks/garch/garch.stan 0.87 0.86 1.01 0.88% faster
stat_comp_benchmarks/benchmarks/low_dim_corr_gauss/low_dim_corr_gauss.stan 0.01 0.01 1.02 1.57% faster
stat_comp_benchmarks/benchmarks/eight_schools/eight_schools.stan 0.11 0.11 1.0 0.28% faster
stat_comp_benchmarks/benchmarks/low_dim_gauss_mix/low_dim_gauss_mix.stan 8.42 8.36 1.01 0.71% faster
stat_comp_benchmarks/benchmarks/gp_pois_regr/gp_pois_regr.stan 3.69 3.7 1.0 -0.07% slower
stat_comp_benchmarks/benchmarks/pkpd/one_comp_mm_elim_abs.stan 32.83 33.79 0.97 -2.93% slower
stat_comp_benchmarks/benchmarks/pkpd/sim_one_comp_mm_elim_abs.stan 0.38 0.38 0.99 -0.89% slower
stat_comp_benchmarks/benchmarks/irt_2pl/irt_2pl.stan 6.86 6.86 1.0 0.02% faster
stat_comp_benchmarks/benchmarks/sir/sir.stan 138.66 135.79 1.02 2.07% faster
stat_comp_benchmarks/benchmarks/arK/arK.stan 3.17 3.21 0.99 -1.4% slower
stat_comp_benchmarks/benchmarks/arma/arma.stan 0.59 0.59 1.01 1.34% faster
stat_comp_benchmarks/benchmarks/low_dim_gauss_mix_collapse/low_dim_gauss_mix_collapse.stan 25.02 25.31 0.99 -1.13% slower
performance.compilation 385.92 386.62 1.0 -0.18% slower
Mean result: 0.9983483632939933

Jenkins Console Log
Jenkins Build Stages
Commit hash: 98c64f32efad1c4821e30c9f76801a0d0c89d086

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:                                 1497.618
CPU max MHz:                             4114.4229
CPU min MHz:                             1500.0000
BogoMIPS:                                7189.39
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 Old microcode:             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

@avehtari

avehtari commented Sep 17, 2026

Copy link
Copy Markdown
Member Author

I ran units tests only today on a Tesla V100 (OpenCL 3.0 CUDA) on the Aalto Triton cluster. All four test suites pass: 28 tests.

I also compared the device results against the CPU results directly, because the tests compare them only to a tolerance. The device results and the CPU results agree to 2.0 to 2.6 ulp (which is negligible difference)

@WardBrian

Copy link
Copy Markdown
Member

CI should have a GPU available, what did you try that didn’t work as you expected?

@avehtari

Copy link
Copy Markdown
Member Author

I should have written that I had not run OpenCL unit tests when I first submitted this PR, although I had checked "unit tests pass".

The additional tests comparing the accuracy between CPU and GPU with millions of evaluation points are not in the unit tests, and I did run them because in erfcx PR all unit tests did pass, but comparing CPU and GPU results did reveal that OpenCL compiler had optimized one part so that the accuracy went from 5ulp to 500ulp.

@avehtari avehtari mentioned this pull request Sep 18, 2026
4 tasks
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.

4 participants