Conversation
…om:stan-dev/math into improve-logistic_lpdf-derivative-stability
Jenkins Console Log Machine informationDistributor 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 |
…om:stan-dev/math into improve-logistic_lpdf-derivative-stability
Follow-up
All three partials are now built from one Two smaller fixes in the same files:
Tests: |
Jenkins Console Log Machine informationDistributor 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
left a comment
There was a problem hiding this comment.
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
| += -(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; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
| const T_partials_return Pn = inv_logit(scaled_diff); | ||
|
|
||
| P *= Pn; |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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.
| 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()); |
There was a problem hiding this comment.
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
Co-authored-by: Andrew Johnson <andrew.johnson@arjohnsonau.com>
|
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 |
Jenkins Console Log Machine informationDistributor 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 |
|
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) |
|
CI should have a GPU available, what did you try that didn’t work as you expected? |
|
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. |
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_lpdfThe
logistic_lpdflocation derivative separately exponentiatedmu / sigmaandy / sigma.When
mu / sigma > log(DBL_MAX / 2), this could produce an infinite orNaNgradient even thoughthe log density and true gradient remained finite. For example,
y = 711,mu = 710, andsigma = 1returned aNaNlocation gradient instead oftanh(0.5).The denominator can overflow before
2 * exp(m)does leading to big errors. For example:m = log(DBL_MAX / 2) - 1e-6andd = 0.1old gradient is1.0while exact gradient istanh(0.05) = 0.04995837495788-InforNaN.The new
tanh(d / 2) / sigmaform depends only on the standardized difference and remains bounded by1 / sigma.logistic_lccdfFor
logistic_lccdf(z | 0, 1):z-Inf-InfThe error is already substantial before saturation, and happens with values of
zthat are not completely unfeasible.After fix
log1m_inv_logit(z).inv_logit(z) / sigma.logistic_lcdfFor
logistic_lcdf(z | 0, 1):z-InfThe LCDF gradient remains exactly
1through the subnormal region, then becomesNaNwhenexp(z)underflows completely. This one is least likely to happen.log_inv_logit(z).inv_logit(-z) / sigma.Tests
Side Effects
Removes the following comment from the
logistic_lccdfandlogistic_lcdfcode (as the derivatives are simpler and more stable after this PR)Release notes
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
./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