Repository navigation
Conversation
|
This duplicates a bunch of the implementation over in #3405, so will make some suggestions/comments on that one and then rebase this once that PR lands |
|
I guess we should coordinate more as I also was preparing PR for std_normal and normal |
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.515 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 reviewed this PR (with help from Claude). Looks great Accuracy against mpmathWorst ulp over 601 points per range:
For comparison, develop's worst in-range relative error is 7.6e-06 to 5.6e-05, which is 3.4e+10 to 2.5e+11 ulp, across every branch except the far Mills tail. So this PR improves the gradient by about ten orders of magnitude.
|
| range (scaled) | CPU | GPU |
|---|---|---|
| far tail [-40, -10] | 1.16 | 1.35 |
| Cody [-10, -4] | 1.10 | 2.21 |
| Cody edge [-4, -2.5] | 1.06 | 1.03 |
| interior [-2.5, -0.1] | 1.11 | 1.93 |
Ratios above 1 mean the direct erfcx call is faster.
|
awesome! thanks @andrjohns for putting this together, it's much needed |
Two more functions get the benefit with no extra workTwo functions that benefit from this
One function benefits from erfcx directly and is not covered here
return std_normal_lcdf(-x) - std_normal_lpdf(x);It calls
The loss grows without bound. |
|
@andrjohns this is very cool! Because of how tied in this and #3405 are do you want to handle the #3405 review and then I'll review this once that is in? |
Yep sounds like a plan to me! |
|
@andrjohns ping me when this is ready for review |
|
@andrjohns is this ready for me to look at? |
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: 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: 1496.992 CPU max MHz: 3416.0681 CPU min MHz: 1500.0000 BogoMIPS: 4491.85 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 Old microcode: 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 |
|
This PR fixes #2881 |
|
This PR also implements most of #3364: the |
Evaluating the *_val inputs in normal_lcdf_impl, lognormal_lcdf_impl and exp_mod_normal_lcdf_impl lets expression and plain arguments share their downstream instantiations. Peak g++ -O0 memory compiling the generated expression tests drops from 33 GB to 9 GB for each exp_mod_normal lcdf/lccdf/cdf and by about 35% for the normal and lognormal ones, which pushed the expr CI pod past its 64 GiB limit.
std_normal_lcdf_impl read y_val without to_ref, so a double expression argument was evaluated by check_not_nan and again by the kernel. This failed the Prim expression tests for std_normal_lcdf, std_normal_lccdf and std_normal_cdf.
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.587 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 |
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.584 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 |
The
|
| range | before | after |
|---|---|---|
| [-300, -40] | 2.73 / 1.33 | 2.73 / 1.33 |
| [-40, -5.66] | 2.93 / 1.38 | 2.93 / 1.38 |
| [-5.66, -0.66] | 2.46 / 7.01 | 2.46 / 6.50 |
| [-0.66, 0.66] | 2.93 / 4.01 | 6.43 / 2.74 |
| [0.62, 0.70] (boundary) | 4.58 / 3.66 | 8.77 / 3.14 |
| [0.66, 5.66] | 11.43 / 10.56 | 11.98 / 10.56 |
| [5.66, 37] | 512.84 / 512.48 | 509.62 / 512.48 |
Both lower tails give bitwise identical results. In the Cody middle and tail ranges the erfcx evaluation order rounds differently, and the worst errors move by at most 1.3 ulp in either direction.
In the central range the gradient improves from 4.0 to 2.7 ulp, but the value gets worse for 0 <= z < 0.66: up to 8.8 ulp on this grid and 9.6 ulp on a denser grid of 20001 points, against 4.6 before. There log(1/2) - z^2/2 + log(erfcx) adds terms of size 0.9 and 0.6 to reach about -0.3, and all three summation orders give the same worst case. 9.6 ulp is a relative error of 1e-15. I accept it for the speed gain below: many distributions call std_normal_lcdf, and most calls fall in the central and middle ranges.
Speed
Setup: Xeon E5-2680 v3, one core, performance governor, g++ 14.3.0, Stan default flags. I timed internal::std_normal_lcdf_value_grad<true> (value and gradient) and <false> (value only) on double, this PR's head against the commit, 3 runs × 3 rounds each with the two binaries alternated. Time relative to before (below 1 is faster):
| range | gradient and value | value only |
|---|---|---|
| [-40, -5.66] | 1.02 | 1.02 |
| [-5.66, -0.66] | 0.88 | 0.86 |
| [-0.66, 0.66] | 0.66 | 0.83 |
| [0.66, 5.66] | 0.90 | 0.91 |
| [5.66, 37] | 0.77 | 0.77 |
| [-6, 6] uniform | 0.87 | 0.89 |
The round-to-round spread was at most 3.2 %, and 6.6 % in the deep tail, whose code did not change. The middle ranges gain from the paired Horner evaluation in #3405. Above 5.66 the x^2 form of the Cody tail saves one division compared with (1 + u C(u)) / (sqrt(pi) x), u = 1/x^2. In the central range the gradient drops its exp call. I did not time the OpenCL code; the device function evaluates the same rationals as before, and its central branch also drops the exp.
Tests
I tested this PR's head 686fc67 plus the commit:
- CPU, 20 suites, all pass:
primandmixstd_normal_lcdf_impl_test,prim/rev/fwd/mixerfcx_test,primandmixPhi_test, and thenormal,std_normal,lognormal,exp_mod_normalandskew_normalsuites that call the kernel. - OpenCL on a Tesla V100, 11 suites, all pass:
opencl/revstd_normal_lcdf,std_normal_lccdf,normal_lcdf,normal_lccdf,lognormal_lcdf,Phi,inv_Phi,skew_normal_lpdf,exp_mod_normal_lcdf,exp_mod_normal_lpdf, andelt_function_cl_test. - clang-format 10 and cpplint (with the filters of
make/cpplint) report nothing on the changed files.
|
| x | develop | this PR before | after bf5861a |
|---|---|---|---|
| [-38.5, -30) | (cut off at -37.5) | 1822 | 1704 |
| [-30, -20) | 1224 | 922 | 1224 |
| [-20, -10) | 470 | 440 | 470 |
| [-10, -5) | 118 | 93 | 118 |
| [-5, -1) | 766133 | 19.1 | 30.5 |
| [-1, 0) | 3.2 | 4.6 | 3.0 |
| [0, 1) | 1.3 | 2.6 | 1.3 |
| [1, 8.5) | 1.2 | 1.3 | 1.2 |
In the lower tail every version loses about x^2 / 2 ulp, because the argument x / sqrt(2) is rounded before erfc sees it, and the exp(log Phi) form has the same limit. The new form stays within a factor 1.6 of the exp(log Phi) form there and is better above -1.
Speed
Same setup as above, Phi(double), 3 runs × 3 rounds with the variants interleaved in one binary. Time relative to develop (below 1 is faster):
| range | this PR before | after bf5861a |
|---|---|---|
| [-0.66, 0.66] | 4.39 | 1.05 |
| [-3, 3] | 1.88 | 0.99 |
| [-5, 5] | 1.58 | 0.97 |
| [-37, -5] | 1.91 | 0.99 |
| [5, 8] | 1.42 | 0.95 |
The round-to-round spread was at most 2.4 %. I also tried erf above -0.67 and erfc below; it ran at the same speed, so I kept the single formula.
Tests
On this PR's head 5f33a10 plus bf5861a:
- CPU, 22 suites, all pass: the 20 listed in my previous comment, plus
primandmixinv_Phi_test. That includes this PR'sPhi_test, with its 1e-14 relative checks at -5 and -3,Phi(-40) == 0and the tail values from R. - OpenCL on a Tesla V100, the same 11 suites as before, all pass, including
opencl/revPhi_testandinv_Phi_test. - clang-format 10 and cpplint report nothing on the changed files.
|
| function | z | value | d/dy |
|---|---|---|---|
std_normal_cdf |
[-38, -30) | 1500 / 950 / 1500 | 1000 / 950 / 1.7 |
std_normal_cdf |
[-10, -3) | 3.5e5 / 51 / 89 | 65 / 51 / 1.5 |
std_normal_cdf |
[-3, 3) | 60 / 6.3 / 9.8 | 6.3 / 3.3 / 3.2 |
normal_cdf |
[-30, -10) | 1700 / 1200 / 1100 | 1700 / 1200 / 990 |
normal_cdf |
[-10, -3) | 3.5e5 / 120 / 160 | 63 / 120 / 87 |
normal_cdf |
[-3, 3) | 59 / 5 / 11 | 10 / 4.6 / 5.9 |
lognormal_cdf |
[-30, -10) | 1700 / 1200 / 1100 | returns 0 / 1200 / 990 |
lognormal_cdf |
[-10, -3) | 62 / 120 / 150 | 64 / 110 / 85 |
lognormal_cdf |
[-3, 3) | 12 / 4.7 / 10 | 9.2 / 3.2 / 4.9 |
The d/dmu and d/dsigma errors have the same size as d/dy, except near z = 0, where the lognormal_cdf d/dsigma crosses zero; its absolute error there is 2e-18 in all three versions. The commit keeps the fixes of the original PR: develop's 3.5e5 ulp values in std_normal_cdf and normal_cdf, and the zero lognormal_cdf gradients below z = -10. No result is NaN or infinite. Compared with exp(lcdf), the values are up to 2 times less accurate, from the rounding of the erfc argument that Phi has too, and the gradients are equal or better.
Speed
Same machine and flags as for Phi, three trees alternated, 3 runs × 3 rounds. K is the number of elements per call; with var, all arguments are var and one reverse pass covers 4096 elements. Time relative to develop, original PR / after 0d4ac36:
| function | range | K | double |
var |
|---|---|---|---|---|
std_normal_cdf |
[-3, 3] | 1 | 1.92 / 0.98 | 1.11 / 1.02 |
std_normal_cdf |
[-3, 3] | 64 | 1.27 / 0.97 | 0.89 / 0.95 |
std_normal_cdf |
[-8, -3] | 1 | 1.48 / 0.89 | 1.02 / 1.02 |
normal_cdf |
[-3, 3] | 1 | 1.72 / 1.02 | 1.06 / 1.01 |
normal_cdf |
[-3, 3] | 64 | 1.10 / 0.91 | 1.19 / 1.10 |
normal_cdf |
[-8, -3] | 1 | 1.45 / 0.90 | 0.98 / 0.96 |
lognormal_cdf |
[-3, 3] | 1 | 1.50 / 1.02 | 1.05 / 1.06 |
lognormal_cdf |
[-3, 3] | 64 | 0.85 / 0.70 | 1.50 / 1.44 |
lognormal_cdf |
[-8, -3] | 1 | 1.45 / 1.01 | 1.02 / 1.03 |
The round-to-round spread reached 14 % in some rows, so differences below about 0.1 are not significant. With double arguments all three are back at develop speed. One slowdown is left: lognormal_cdf of a vector with var arguments takes about 1.5 times as long as on develop, with either form, so the extra time is not in the erfc or exp(lcdf) code. I have not found where it is.
Tests
On this PR's head bf5861a plus 0d4ac36:
- CPU, 19 unit suites, all pass:
primandmixstd_normal_cdf_testandnormal_cdf_test,mixlognormal_cdf_test(with this PR's tail andy = 0checks), and thestd_normal_lcdf_impl,Phi,inv_Phi, lcdf, lccdf,exp_mod_normal_cdfandskew_normalsuites from before. - The generated
test/probtests of the three cdfs (runTests.py test/prob/normal test/prob/lognormal test/prob/std_normal -f _cdf_0): 17 test programs, 12000 cases, all pass. - OpenCL on a Tesla V100:
opencl/revstd_normal_cdf_test,normal_cdf_test,lognormal_cdf_testandPhi_testpass. They compare the OpenCL results with the changed CPU code. - A compile check instantiates the three functions with
fvar<fvar<var>>,std::vector<var>, Eigen vectors offvar<double>,var_valuecolumn and row vectors, and mixed scalar and vector arguments. - clang-format 10 and cpplint report nothing on the changed files.
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: 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: 1496.647 CPU max MHz: 3416.0681 CPU min MHz: 1500.0000 BogoMIPS: 4491.85 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 Old microcode: 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 |
Temporaries that are read more than once are now evaluated onceThe vector In To find the lcdf slowdown, I timed every commit of this PR with vectors of 1000 elements and ns per element,
9a8deff also made Speed of this PR compared with develop and with the table in the descriptionIn short, the PR as opened made the lcdfs faster than develop but All tables give the time relative to develop: develop is 1.00, and a value below 1.00 means faster. The setup follows the description: vectors of N = 1000,
What the columns show:
For N = 100 and 10000 the "now" values are within about 0.1 of these; at N = 100000 some Why the description shows larger gainsThe absolute times show where the difference is. ns per element,
The PR as opened ran at about the same speed on both machines (0.8 to 1.1 times the EPYC time), but develop took 1.7 to 3.1 times as long on the machine of the description. The largest factors (2.8 to 3.1) are for On such a platform the
Only The new form is more accurate in the lower tail. Worst relative error of
In the tail the only error that grows with |
|
9a8deff also made A first version also evaluated Time relative to develop (develop = 1.00, below 1.00 is faster), Xeon Gold 6248, g++ 14.3, vectors of N = 1000,
I also timed the OpenCL device functions on a Tesla V100, with each work item walking through the input range in registers and a baseline kernel subtracted. The device
|
| function | region | value | dy | dsigma | dlambda |
|---|---|---|---|---|---|
| lcdf | left tail (z <= -30) |
2.9e5 ulp | 3.4e7 | 3.4e7 | 1.5e13 |
| lcdf | small v (<= 1e-4) |
non-finite (10 points) | |||
| lccdf | small v |
1.4e12 | 1.3e12 | 1.3e12 | 6.0e2 |
In the left tail both terms are of size z^2/2; for small v they are nearly equal. At (z, v) = (-1e4, 0.01) the lcdf value is about -5e7 with an absolute error of 1.6e-3.
With u = -z / sqrt(2), t = u + v / sqrt(2), E = erfcx and g = exp(-z^2/2) / 2, Phi(z) = g E(u) and exp(v^2/2 - v z) Phi(z - v) = g E(t), so F = g (E(u) - E(t)) and the z^2/2 terms cancel exactly. All partials are ratios of E(u), E(t), E'(t) and E(u) - E(t). The difference E(u) - E(t) is computed directly when E(t) < 0.99 E(u), and otherwise from a series in the derivatives of erfcx at the midpoint, or for arguments of at least 4 from the difference of the Cody tail rational; the rounded t - u is never used. For z > 0 the code uses F = 1 - (Phi(-z) + b) where F >= 1/4, and a form with two positive terms where F < 1/4. The three functions share one scalar loop.
Worst error over the value and the four partials after the change, no non-finite result: 510 ulp (lcdf) and 480 ulp (lccdf) on the grid above, and 640 and 900 ulp on a denser grid of 2500 points with |z| in [1e-4, 100] and v in [1e-10, 100]. One exception: dsigma near z = 1/v at large v (3.9e3 ulp at (0.0316, 31.6)), where this partial has a true zero.
The change also makes the functions faster. Time relative to develop, AMD EPYC 9965, vectors of N = 1000, bulk: z on [-3, 3], wide: z on [-30, 30]:
| function | inputs | before double |
after double |
before var |
after var |
|---|---|---|---|---|---|
| lcdf | bulk | 1.43 | 0.80 | 1.37 | 0.94 |
| lcdf | wide | 2.06 | 0.94 | 1.76 | 1.09 |
| lccdf | bulk | 1.47 | 0.88 | 1.38 | 0.99 |
| lccdf | wide | 2.17 | 0.79 | 1.91 | 0.89 |
| cdf | bulk | 1.68 | 0.93 | 1.53 | 1.00 |
| cdf | wide | 2.52 | 1.15 | 2.03 | 1.15 |
A Xeon Gold 6230 gives the same pattern. The cdf is exp(lcdf); develop computes it from erf directly, which is not accurate in the tails.
The OpenCL code still uses the previous form. A port of the new algorithm to the device is in testing, and I will add it as a separate commit.
OpenCL port of the
|
| function | inputs | before double |
after double |
before var |
after var |
|---|---|---|---|---|---|
| lcdf | bulk | 0.86 | 0.79 | 0.80 | 0.89 |
| lcdf | wide | 0.58 | 0.52 | 0.64 | 0.85 |
| lccdf | bulk | 1.13 | 0.99 | 1.09 | 1.21 |
| lccdf | wide | 1.23 | 1.14 | 1.07 | 1.39 |
| cdf | bulk | 1.19 | 1.09 | 1.05 | 1.19 |
| cdf | wide | 1.28 | 1.20 | 1.10 | 1.51 |
The var path is slower than before because each of the four wrappers runs the whole device function, and the device compiler does not merge the four copies. The extra cost is 1 to 2 ns per element. A dedicated kernel that computes all four outputs in one pass would remove it, but it would not follow the pattern of the other distributions, so I did not write one.
|
@SteveBronder I'm now done improving this PR |
|
Thanks! I'm running some benchmarks locally to see what is going on in more detail |
Fixes #2881. Implements most of #3364: the
normal,lognormalandexp_mod_normallcdf/lccdfthrough the sharedstd_normal_lcdfimplementation, and the OpenCL part.Summary
This PR extracts the key value and gradient calculations from the
std_normal_lcdfheader into reusable functions that other normal-cdf-related functions can delegate to, removing a lot of duplicated/inconsistent implementations.The value and gradient calculations are also updated to replace the taylor expansions with the Cody rational approximations (previously only used for the tail).
Two other bugs were found & fixed for
var_value<Eigen::VectorXd>handling:as_array_or_scalar()now returns an array unchanged instead of re-wrapping itref_type_ifkeeps an rvaluearena_matrixby value rather than referencing a dead temporaryThe valid ranges of inputs (before over/underflow) across all functions are expanded:
While performance is either unchanged or significantly improved across inputs:
Full timing comparisons
double (values only)
var_value (Eigen::Matrix<var_value> y, var_value parameters, gradient)
var_valueEigen::Matrix (var_valueEigen::VectorXd y, var_value parameters, gradient)
fvar (tangent)
Tests
Additional tests exercising the increased numerical stability are added, as well new
mixtests for the distributionsSide Effects
N/A
Release notes
Increased numerical stability and performance of the
normal,std_normal,lognormalandexp_mod_normal(LC)CDF functionsChecklist
Copyright holder: (fill in copyright holder information)
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