From f4939b74d7fe987aab6f7d15447a350130dffd88 Mon Sep 17 00:00:00 2001 From: Gary Wolfman Date: Fri, 3 Jul 2026 16:33:38 -0400 Subject: [PATCH 1/6] perf: raise kAvx512 thresholds for Laplace and Uniform (issue #50) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Laplace PDF/LogPDF: 64 → 25000 Laplace CDF: 1024 → 20000 Uniform PDF/LogPDF: 50000 → NEVER Uniform CDF: 128 → 50000 The Laplace PDF/LogPDF thresholds of 64 were at the profiler measurement floor (documented as a warm-pool artefact in the sha-1b564ec bundles). The pylibstats scipy comparison benchmark confirms a throughput trough at N=10k (121M vs 229M at N=5k), with recovery stable from N=20-30k onwards. Uniform PDF/LogPDF thresholds of 50000 matched the observed trough exactly: 1.3G/s collapses to 438M/s at N=50k and does not recover to pre-cliff levels within the 1k-100k sweep. The threshold was already flagged as a bimodal override artefact in the table comments. Uniform CDF threshold of 128 caused an identical cliff at N=10k (833M → 109M). CDF recovery stabilises around N=40-50k. Gaussian PDF/LogPDF and Exponential PDF anomalies (N=90k and N=50k cliffs respectively) are tracked in issue #50 pending strategy_profile validation — those thresholds are NOT changed here. Co-Authored-By: Oz --- include/core/dispatch_thresholds.h | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/include/core/dispatch_thresholds.h b/include/core/dispatch_thresholds.h index 907a7593..1fee5023 100644 --- a/include/core/dispatch_thresholds.h +++ b/include/core/dispatch_thresholds.h @@ -381,7 +381,12 @@ constexpr ArchTable kAvx2 = {{ // combined {64,NEVER,NEVER,NEVER,64,256}; 50/50 finite/NEVER split with // anti-correlated pool state — conservative = NEVER) constexpr ArchTable kAvx512 = {{ - /* UNIFORM(0) */ {50000, 50000, 128}, // CDF: 256→128 + /* UNIFORM(0) */ {NEVER, NEVER, 50000}, // PDF/LogPDF: 50k→NEVER (bimodal override + // artefact; trivial SIMD path; parallel + // never recovers pre-cliff throughput in + // 1k-100k sweep — see issue #50); + // CDF: 128→50000 (N=10k cliff: 833M→109M; + // recovery stable at N=40-50k) /* GAUSSIAN(1) */ {1000000, 400000, 25000}, // LogPDF bimodal override; CDF: 50k→25k /* EXPONENTIAL(2) */ {250000, 400000, 250000}, // LogPDF: NEVER→400k; PDF/CDF reduced /* DISCRETE(3) */ {150000, 150000, NEVER}, // PDF: held 150000 (512 was @@ -402,7 +407,11 @@ constexpr ArchTable kAvx512 = {{ /* NEGATIVE_BINOMIAL(15) */ {NEVER, NEVER, 512}, // CDF: 2048→512 /* GEOMETRIC(16) */ {NEVER, NEVER, 512}, // new: PDF/LogPDF NEVER; CDF 512 (6-run set // with NegBinomial; max of lower cluster) - /* LAPLACE(17) */ {64, 64, 1024}, // new: PDF/LogPDF at floor; CDF 1024 + /* LAPLACE(17) */ {25000, 25000, 20000}, // PDF/LogPDF: 64→25000 (profiler floor + // artefact; N=10k trough confirmed by + // pylibstats scipy benchmark sweep; + // recovery stable at N=20-30k — see + // issue #50); CDF: 1024→20000 (same) /* CAUCHY(18) */ {2000000, 750000, NEVER}, // new: PDF 2M; LogPDF 750k; CDF NEVER // (6-run set with StudentT CDF; 50/50 // split → conservative) From 0936dc1c3678e49d0639ab03cfdabde8269f227f Mon Sep 17 00:00:00 2001 From: Gary Wolfman Date: Fri, 3 Jul 2026 16:52:01 -0400 Subject: [PATCH 2/6] perf: refine Laplace and Uniform kAvx512 thresholds (issue #50) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Laplace PDF: 25000 → 35000 Laplace LogPDF: 25000 → 50000 Uniform CDF: 50000 → NEVER Laplace PDF/LogPDF: the first pass (25k) moved the dispatch penalty from N=10k to N=25k rather than eliminating it. A targeted 5k-resolution sweep confirmed: - LogPDF at N=25k: 433M → 170M (nearly as bad as original) - Threshold only amortises at N=45-50k; raised to 50k - PDF dip milder; 35k clears it with no visible overhead Uniform CDF: raising from 50k to NEVER revealed the N=50k cliff is an L2→L3 cache boundary (N=45k at 878M fits in L2; N=50k at 800KB two-array footprint does not). The previous 50k threshold was adding parallel overhead on top of a cache miss (463M), making it worse than SIMD-only (552M at N=50k with NEVER). No threshold can fix a cache capacity limit. Co-Authored-By: Oz --- include/core/dispatch_thresholds.h | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/include/core/dispatch_thresholds.h b/include/core/dispatch_thresholds.h index 1fee5023..5dc647ed 100644 --- a/include/core/dispatch_thresholds.h +++ b/include/core/dispatch_thresholds.h @@ -381,12 +381,12 @@ constexpr ArchTable kAvx2 = {{ // combined {64,NEVER,NEVER,NEVER,64,256}; 50/50 finite/NEVER split with // anti-correlated pool state — conservative = NEVER) constexpr ArchTable kAvx512 = {{ - /* UNIFORM(0) */ {NEVER, NEVER, 50000}, // PDF/LogPDF: 50k→NEVER (bimodal override - // artefact; trivial SIMD path; parallel - // never recovers pre-cliff throughput in - // 1k-100k sweep — see issue #50); - // CDF: 128→50000 (N=10k cliff: 833M→109M; - // recovery stable at N=40-50k) + /* UNIFORM(0) */ {NEVER, NEVER, NEVER}, // PDF/LogPDF: NEVER (trivial SIMD path; + // parallel never recovers to SIMD throughput + // in 1k-100k sweep; see issue #50). + // CDF: 128→50k→NEVER (50k still too early: + // 960M at N=45k drops to 463M at N=50k and + // does not recover within measured range) /* GAUSSIAN(1) */ {1000000, 400000, 25000}, // LogPDF bimodal override; CDF: 50k→25k /* EXPONENTIAL(2) */ {250000, 400000, 250000}, // LogPDF: NEVER→400k; PDF/CDF reduced /* DISCRETE(3) */ {150000, 150000, NEVER}, // PDF: held 150000 (512 was @@ -407,11 +407,13 @@ constexpr ArchTable kAvx512 = {{ /* NEGATIVE_BINOMIAL(15) */ {NEVER, NEVER, 512}, // CDF: 2048→512 /* GEOMETRIC(16) */ {NEVER, NEVER, 512}, // new: PDF/LogPDF NEVER; CDF 512 (6-run set // with NegBinomial; max of lower cluster) - /* LAPLACE(17) */ {25000, 25000, 20000}, // PDF/LogPDF: 64→25000 (profiler floor - // artefact; N=10k trough confirmed by - // pylibstats scipy benchmark sweep; - // recovery stable at N=20-30k — see - // issue #50); CDF: 1024→20000 (same) + /* LAPLACE(17) */ {35000, 50000, 20000}, // PDF: 64→25k→35k (mild N=25k dip 233M→184M; + // recovers by N=30k; 35k clears it). + // LogPDF: 64→25k→50k (severe N=25k dip + // 433M→170M; only amortises at N=45-50k; + // see issue #50). + // CDF: 1024→20k (minor; threshold fires at + // N=20k but overhead amortises by N=30k) /* CAUCHY(18) */ {2000000, 750000, NEVER}, // new: PDF 2M; LogPDF 750k; CDF NEVER // (6-run set with StudentT CDF; 50/50 // split → conservative) From e59107609af9a002fb58c536df55d58216449186 Mon Sep 17 00:00:00 2001 From: Gary Wolfman Date: Fri, 3 Jul 2026 16:59:48 -0400 Subject: [PATCH 3/6] docs: add dispatch calibration, cache hierarchy, and accuracy findings SIMD_OPTIMIZATION_REFERENCE.md: - Detecting threshold miscalibration via external benchmarks: the trough-at-threshold signature, dispatch vs cache boundary effect, profiler floor artefacts, and NEVER threshold criteria. Documented using the v2.0.2 Laplace/Uniform recalibration as worked examples. - Cache hierarchy effects: Zen4 L2/L3/DRAM boundaries and their impact on throughput measurements at different batch sizes. - Cross-architecture accuracy differences: Bessel Tier 1 vs Tier 2 selection, VonMises accuracy implication, scipy version ruling. - Known structural performance ceilings: VonMises CDF (scalar integration), Cauchy CDF (arctan vs incomplete-beta delegation), Binomial (lgamma scalar loop, PMF summation). References issues #47, #48, #51, #52. SIMD_BENCHMARK_RESULTS.md: - v2.0.2 section: machines, peak throughput table, speedup ratios vs scipy at N=100k, full accuracy table with Bessel-tier split for VonMises, and benchmark command reference. Co-Authored-By: Oz --- docs/SIMD_BENCHMARK_RESULTS.md | 71 +++++++++++++++++ docs/SIMD_OPTIMIZATION_REFERENCE.md | 119 ++++++++++++++++++++++++++++ 2 files changed, 190 insertions(+) diff --git a/docs/SIMD_BENCHMARK_RESULTS.md b/docs/SIMD_BENCHMARK_RESULTS.md index d50a469d..7edac561 100644 --- a/docs/SIMD_BENCHMARK_RESULTS.md +++ b/docs/SIMD_BENCHMARK_RESULTS.md @@ -14,6 +14,77 @@ Current release validation targets: Record new release measurements here after each real-machine validation pass. +## v2.0.2 scipy comparison benchmark + +Cross-library throughput and accuracy comparison run via `pylibstats/benchmarks/scipy_comparison.py` (pylibstats v0.3.2, scipy 1.18.0, numpy 2.4.4). Results capture the state after the `perf/dispatch-threshold-recalibration` threshold updates. + +### Machines + +| Machine | CPU | SIMD | OS | Python | +|---|---|---|---|---| +| Asus TUF A16 | AMD Ryzen 7 7445HS (Zen 4) | AVX-512 | Windows 11 | 3.12.10 | +| MacBook Pro 14,1 (2017) | Intel Core i7-7820HQ (Kaby Lake) | AVX2+FMA | macOS Tahoe | 3.14.6 | + +### Peak throughput highlights (pylibstats absolute, at optimal batch size) + +| Distribution / Op | Zen4 AVX-512 | Kaby Lake AVX2+FMA | Zen4 peak N | +|---|---|---|---| +| Exponential log_PDF | 2.2G/s | 490M/s | 30k | +| Uniform log_PDF | 1.5G/s | 1.1G/s | 20–30k | +| Gaussian log_PDF | 2.1G/s | 760M/s | 25k | +| Exponential PDF | 993M/s | 303M/s | 30k | +| Uniform PDF | 1.5G/s | 966M/s | 20–30k | + +All peak measurements were taken at sizes below the L2→L3 boundary (~50k elements on Zen4; see `SIMD_OPTIMIZATION_REFERENCE.md §Cache hierarchy effects`). + +### Throughput ratios vs scipy (selected, at N=100k) + +Speedup ratios vary with N. N=100k is representative for sustained throughput above the L3 cache fill threshold on both machines. + +| Distribution | Op | Zen4 | Kaby Lake | +|---|---|---|---| +| Exponential | log_PDF | 29× | 11× | +| Gaussian | log_PDF | 6× | 22× | +| Gamma | log_PDF | 18× | 9× | +| VonMises | log_PDF | 25× | 24× | +| StudentT | PDF | 35× | 23× | +| Uniform | PDF | 19× | 25× | +| Cauchy | CDF | 0.2× | 1.1× | | +| VonMises | CDF | 0.1× | 0.1× | +| Binomial | CDF | 0.9× | 0.4× | + +Negative ratios (<1×) indicate distributions with structural algorithm limitations; see `SIMD_OPTIMIZATION_REFERENCE.md §Known structural performance ceilings`. + +### Accuracy vs scipy (max relative error, N=50k uniform grid) + +All values are bit-identical between Zen4 and Kaby Lake **except VonMises PDF/LogPDF**, which differ due to the Bessel function Tier 1/Tier 2 selection: + +| Distribution | pdf | log_pdf | cdf | +|---|---|---|---| +| Gaussian | 1.0×10⁻¹⁵ | 4.4×10⁻¹⁶ | 9.7×10⁻¹⁵ | +| Exponential | 2.2×10⁻¹⁶ | 0 | 1.5×10⁻¹⁴ | +| Laplace | 0 | 1.4×10⁻¹³ | 0 | +| Uniform | 0 | 0 | 2.2×10⁻¹⁶ | +| StudentT | 1.5×10⁻¹⁵ | 5.4×10⁻¹⁶ | 2.7×10⁻⁹ | +| Gamma | 2.0×10⁻¹⁵ | 3.3×10⁻¹⁶ | 1.8×10⁻⁹ | +| LogNormal | 7.3×10⁻¹⁵ | 2.7×10⁻¹⁵ | 2.6×10⁻⁷ | +| VonMises (Zen4/MSVC) | 8.4×10⁻¹⁶ | 4.9×10⁻¹¹ | 4.6×10⁻⁶ | +| VonMises (Kaby Lake/AppleClang) | 2.3×10⁻⁹ | 3.3×10⁻⁹ | 4.6×10⁻⁶ | + +The StudentT/Gamma CDF errors (~10⁻⁹) and LogNormal CDF error (~10⁻⁷) are consistent between machines and reflect approximation limits in the regularised incomplete-beta and erfc paths, not SIMD errors. See issues #49 and related. + +### Benchmark command + +```bash +# From the pylibstats repository root: +python benchmarks/scipy_comparison.py --sizes 1000,10000,100000,1000000 + +# Fine-grained sweep to detect dispatch threshold issues (issue #50 methodology): +python benchmarks/scipy_comparison.py --sizes 1000,5000,10000,20000,30000,40000,50000,60000,70000,80000,90000,100000 +``` + +--- + ## v1.5.x historical summary v1.5.0 introduced native transcendentals on AVX2, NEON, and AVX-512 and changed `simd_verification` to report per-operation-type geometric means rather than one composite number. diff --git a/docs/SIMD_OPTIMIZATION_REFERENCE.md b/docs/SIMD_OPTIMIZATION_REFERENCE.md index 95c0d4ac..09cfb8aa 100644 --- a/docs/SIMD_OPTIMIZATION_REFERENCE.md +++ b/docs/SIMD_OPTIMIZATION_REFERENCE.md @@ -21,6 +21,125 @@ Resolved work from v1.4.0 through v2.0.0: | NEON | Apple Silicon | M1 validation path | | AVX-512 | AMD Zen 4 / supported Intel | Windows validation path | +## Detecting threshold miscalibration via external benchmarks + +`scripts/PROFILING_METHOD.md` documents the authoritative `strategy_profile`-based calibration procedure. External language bindings (e.g. `pylibstats`) provide a complementary and faster signal: run a throughput sweep across a dense size grid and look for throughput troughs. + +### The trough-at-threshold signature + +When a dispatch threshold is too low, the parallel strategy fires before its overhead is amortised. The benchmark exposes this as a V-shaped throughput trough whose minimum occurs at the threshold value: + +- Throughput rises with N up to just below the threshold (pure SIMD). +- At the threshold, throughput drops sharply as threading overhead dominates. +- Throughput recovers as N grows and parallel work amortises the overhead. + +Example from the v2.0.2 AVX-512 recalibration (perf/dispatch-threshold-recalibration): + +| Distribution / Op | Threshold | Trough N | Trough throughput | Pre-trough | +|---|---|---|---|---| +| Laplace log_PDF | 64 (profiler floor) | 10k | 129M/s | 229M/s at N=5k | +| Laplace log_PDF | 25k (first pass) | 25k | 170M/s | 433M/s at N=20k | +| Laplace log_PDF | 50k (final) | none | — | monotone above N=50k | +| Uniform CDF | 128 | 10k | 109M/s | 833M/s at N=5k | +| Uniform PDF/LogPDF | 50k | 50k | 438M/s | 1.3G/s at N=40k | + +The Laplace log_PDF case illustrates the diagnostic loop: a first-pass threshold of 25k moved the trough from N=10k to N=25k rather than eliminating it. A finer sweep (5k resolution) pinpointed that the threshold only amortises at N=45–50k, motivating the final value of 50k. + +### Dispatch effect vs cache boundary effect + +A cache hierarchy boundary produces a visually similar throughput drop but cannot be eliminated by adjusting thresholds: + +- **Dispatch effect**: trough disappears when threshold is set to NEVER. +- **Cache boundary**: trough persists even with NEVER threshold. + +Example: Uniform CDF on Zen4 (AVX-512) shows a throughput drop from ~880M/s at N=45k to ~550M/s at N=50k with NEVER threshold. This is the L2→L3 boundary: two-array footprint (input + output) of 45k doubles = 720KB (fits in Zen4 L2), 50k doubles = 800KB (does not). No threshold change can raise throughput above the L2 ceiling at sizes that exceed L2 capacity. + +A 50k threshold previously placed parallel overhead on top of a cache miss (463M/s instead of 552M/s). Setting NEVER removes the overhead and exposes the true cache-limited floor. + +### Profiler floor artefacts in the threshold table + +A threshold of 64 in `kAvx512` or any architecture table is a strong signal that the profiler measurement floor was hit rather than a real crossover being measured. See `scripts/PROFILING_METHOD.md §Timer jitter and the sub-64 measurement floor` for the mechanism. When a 64 threshold produces a trough in an external benchmark, replace it with a measured amortisation point from either a fine-grained sweep or a fresh `strategy_profile --large` run. + +In the v2.0.2 recalibration, `kAvx512` Laplace PDF/LogPDF thresholds of 64 were both documented as profiler floor artefacts and confirmed by the external benchmark trough at N=10k. + +### NEVER thresholds for trivial SIMD operations + +Distributions whose per-element SIMD cost is very low (Uniform, Laplace at small N) may show that parallel never sustains an advantage within practical batch sizes. For these, NEVER is correct regardless of what the profiler reports at its measurement ceiling. Indicators: + +- Parallel never recovers to pre-threshold SIMD throughput across a 1k–100k sweep. +- The distribution performs arithmetic or a single transcendental per element with no iterative path. +- The trough-at-threshold depth exceeds 50% throughput loss. + +For AVX-512 v2.0.2, Uniform PDF, Uniform LogPDF, and Uniform CDF are all NEVER. + +--- + +## Cache hierarchy effects on batch throughput + +AVX-512's higher per-element throughput means the working set grows faster than on AVX2 machines. The L2→L3 and L3→DRAM transitions are therefore more visible in fine-grained throughput sweeps. + +Zen4 (Ryzen 7 7445HS) cache topology: +- L2: ~1MB per core (effective user-data capacity ~800KB) +- L3: 16MB shared +- DRAM: DDR5 + +Observed thresholds in the v2.0.2 benchmark sweep (two-array footprint = input + output): + +| Transition | Two-array size | Approx N (doubles) | Typical throughput drop | +|---|---|---|---| +| L2 → L3 | ~800KB | ~50k | 30–60% for compute-bound distributions | +| L3 → DRAM | ~16MB | ~1M | 30–70% for compute-bound distributions | + +The L2 boundary is architecture-specific and must not be assumed to hold on AVX2 (Kaby Lake) or NEON (M1). Throughput comparisons across architectures should be made at sizes that fit in each machine's L2 to avoid confounding compute throughput with memory bandwidth. + +--- + +## Cross-architecture accuracy differences + +### Bessel function tier selection + +`include/core/bessel.h` provides two tiers for `bessel_i0`, `bessel_i1`, and `log_bessel_i0`: + +- **Tier 1** (MSVC/GCC/Clang with `LIBSTATS_HAS_CXX17_BESSEL`): delegates to `std::cyl_bessel_i` (C++17 §29.9.3). Achieves <1 ULP against scipy for κ=2. +- **Tier 2** (AppleClang/macOS, `LIBSTATS_HAS_CXX17_BESSEL` not defined): A&S §9.8.1–9.8.4 polynomial approximation. Documented precision: <1.6×10⁻⁷. + +The tier selection is automatic: CMakeLists.txt probes for `std::cyl_bessel_i` and defines `LIBSTATS_HAS_CXX17_BESSEL` only when available. AppleClang does not implement C++17 special functions as of Xcode 16. + +### Implication for VonMises accuracy + +VonMises PDF and LogPDF accuracy differs between MSVC and AppleClang builds because the normalisation constant `log(2π·I₀(κ))` is computed via different Bessel tiers. This shows up as: + +| Platform | VonMises pdf max rel err | VonMises log_pdf max rel err | +|---|---|---| +| Zen4 / MSVC (Tier 1) | ~1 ULP (~8×10⁻¹⁶) | ~5×10⁻¹¹ | +| Kaby Lake / AppleClang (Tier 2) | ~2×10⁻⁹ | ~3×10⁻⁹ | + +The `vector_cos` implementations between AVX2 and AVX-512 are algorithmically identical (same 7-term FMA Horner polynomial), confirmed by cross-machine analysis. The accuracy difference is entirely in the scalar Bessel normalisation path, not in any SIMD kernel. See issue #47 for the proposed Tier 2 upgrade. + +### Scipy version independence + +All other distributions (Gaussian, Exponential, Laplace, Gamma, etc.) produce bit-identical or near-identical accuracy results across Zen4 and Kaby Lake with the same scipy version. The VonMises accuracy difference was initially suspected to be a scipy version artefact (1.17.1 vs 1.18.0); upgrading to 1.18.0 on Zen4 left the Zen4 accuracy unchanged, ruling out scipy. + +--- + +## Known structural performance ceilings + +These distributions have throughput limitations that are inherent to their algorithms, not to dispatch thresholds or SIMD kernel quality: + +### VonMises CDF + +The CDF has no closed form. The current implementation is a scalar integration loop. Throughput: ~200–900k elements/second vs ~30–50M/s for scipy (which uses Cephes adaptive quadrature, also scalar, but more efficient). No SIMD kernel or threshold change will address this. See issue #51 for a precomputed table design. + +### Cauchy CDF + +Cauchy is implemented as a delegation wrapper over StudentT(ν=1). The CDF therefore evaluates the regularised incomplete-beta function rather than the trivial closed form `arctan((x-x₀)/γ)/π + 0.5`. This makes Cauchy CDF 2–5× slower than scipy on Zen4 where scipy vectorises the arctan path. See issue #48. + +### Binomial CDF and PDF + +Binomial PMF uses lgamma log-space evaluation in a scalar loop. There is no SIMD log-gamma primitive (`vector_lgamma` is deferred). Binomial CDF uses PMF summation rather than the regularised incomplete-beta approach used by scipy. Both paths cap throughput at ~5–16M elements/second regardless of batch size or architecture. See issue #52. + +--- + ## Deferred work ### vector_floor and vector_blend From 41c840cba65ae1aa68da0838ce08058ab8955228 Mon Sep 17 00:00:00 2001 From: Gary Wolfman Date: Fri, 3 Jul 2026 19:35:20 -0400 Subject: [PATCH 4/6] perf: recalibrate kNeon thresholds for Laplace, Rayleigh, LogNormal PDF (issue #50) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Laplace PDF: 6144 → 35000 Rayleigh PDF: 10000 → 20000 LogNormal PDF: 10000 → 25000 All three had genuine dispatch troughs (minimum at N=10k, above the M1 L1→L2 boundary at N≈4k) confirmed via the pylibstats scipy_comparison benchmark sweep. Laplace PDF required three passes (25k, 30k, 35k). At 25k and 30k the parallel entry point was still below the VECTORIZED level — threading overhead not yet amortised. At 35k the parallel entry (+20% vs last VECTORIZED point) confirmed amortisation. See clean-entry criterion in SIMD_OPTIMIZATION_REFERENCE.md. LogNormal PDF trough extended to N=15k (deeper than N=10k), motivating the higher threshold of 25k over the naive 20k. Key M1-specific finding: the L1→L2 cache boundary at N≈4096 elements produces a systematic throughput drop at N≈5k across all operations, independent of dispatch threshold. Operations with kNeon threshold=64 (profiler floor) show N=5k drops that are purely cache-driven; no threshold recalibration is warranted for those. Docs: added §L1 data cache boundary on NEON/M1, §Clean-entry criterion for iterative calibration (SIMD_OPTIMIZATION_REFERENCE.md), and NEON threshold recalibration sweep section (SIMD_BENCHMARK_RESULTS.md). Closes #50 for NEON/M1. AVX-512 Laplace/Uniform corrections already encoded in prior commits on this branch. Co-Authored-By: Oz --- docs/SIMD_BENCHMARK_RESULTS.md | 37 ++++++++++++++++++++++++ docs/SIMD_OPTIMIZATION_REFERENCE.md | 25 ++++++++++++++++ include/core/dispatch_thresholds.h | 45 +++++++++++++++++------------ 3 files changed, 89 insertions(+), 18 deletions(-) diff --git a/docs/SIMD_BENCHMARK_RESULTS.md b/docs/SIMD_BENCHMARK_RESULTS.md index 7edac561..8659c564 100644 --- a/docs/SIMD_BENCHMARK_RESULTS.md +++ b/docs/SIMD_BENCHMARK_RESULTS.md @@ -85,6 +85,43 @@ python benchmarks/scipy_comparison.py --sizes 1000,5000,10000,20000,30000,40000, --- +## NEON threshold recalibration sweep + +Sweep run via `pylibstats/benchmarks/scipy_comparison.py` (pylibstats v0.3.2, scipy 1.17.1, numpy 2.4.4) to identify and resolve `kNeon` dispatch threshold floor artefacts. Branch: `perf/dispatch-threshold-recalibration`. + +### Machine + +| Machine | CPU | SIMD | OS | Python | +|---|---|---|---|---| +| MacBook Pro (14,1, 2017) | Apple M1 | NEON | macOS | 3.14.6 | + +### Key finding: L1 cache boundary at N ≈ 5k + +M1 L1 data cache is 64KB per performance core. Two-array footprint (input + output doubles) exceeds L1 at N ≈ 4096 elements, producing a systematic throughput trough at N ≈ 5k across all operations regardless of threshold. Operations with `kNeon` threshold=64 (profiler floor) have parallel always active; their N=5k troughs are entirely cache-driven. See `SIMD_OPTIMIZATION_REFERENCE.md §L1 data cache boundary on NEON/M1`. + +### Threshold corrections + +Three operations showed genuine dispatch troughs with minimum at N=10k (above the L1 boundary): + +| Distribution / Op | Old kNeon | New kNeon | Evidence | +|---|---|---|---| +| Laplace PDF | 6144 (floor artefact) | 35000 | Trough at N=10k (268M/s vs 330M/s VECTORIZED); parallel entry first exceeds VECTORIZED at N=35k (+20%) | +| Rayleigh PDF | 10000 | 20000 | Trough at N=10k (179M/s vs 214M/s VECTORIZED); recovery at N=15k; 20k conservative safe zone | +| LogNormal PDF | 10000 | 25000 | Trough extends N=10k–15k (180M/s vs 207M/s VECTORIZED); recovery at N=20k; 25k conservative safe zone | + +### Benchmark commands + +```bash +# From the pylibstats repository root: +# Standard sweep to identify candidates: +python benchmarks/scipy_comparison.py --sizes 1000,5000,10000,20000,30000,40000,50000,60000,70000,80000,90000,100000 + +# Focused 5k-resolution sweep for trough pinpointing and threshold verification: +python benchmarks/scipy_comparison.py --sizes 1000,5000,10000,15000,20000,25000,30000,35000,40000,45000,50000 +``` + +--- + ## v1.5.x historical summary v1.5.0 introduced native transcendentals on AVX2, NEON, and AVX-512 and changed `simd_verification` to report per-operation-type geometric means rather than one composite number. diff --git a/docs/SIMD_OPTIMIZATION_REFERENCE.md b/docs/SIMD_OPTIMIZATION_REFERENCE.md index 09cfb8aa..4b6b2762 100644 --- a/docs/SIMD_OPTIMIZATION_REFERENCE.md +++ b/docs/SIMD_OPTIMIZATION_REFERENCE.md @@ -62,6 +62,17 @@ A threshold of 64 in `kAvx512` or any architecture table is a strong signal that In the v2.0.2 recalibration, `kAvx512` Laplace PDF/LogPDF thresholds of 64 were both documented as profiler floor artefacts and confirmed by the external benchmark trough at N=10k. +### L1 data cache boundary on NEON/M1 + +The M1 performance core has a 64KB L1 data cache. The two-array footprint (input + output doubles) crosses the L1→L2 boundary at N ≈ 64KB ÷ 16 bytes = 4096 elements. Throughput sweeps on M1 show a systematic drop at N ≈ 5k regardless of dispatch threshold — this mimics a dispatch trough but is cache-driven and cannot be fixed by raising thresholds. + +Two indicators distinguish L1 cache effects from dispatch troughs: + +- **Trough position**: L1 boundary troughs are pinned at N ≈ 5k on M1 regardless of threshold. A genuine dispatch trough minimum shifts to match the threshold value. +- **NEVER test**: set the threshold to NEVER and re-sweep. A dispatch trough disappears; an L1 cache trough persists. + +Operations with `kNeon` threshold=64 (profiler floor artefact) have parallel always active; their N=5k throughput drop is entirely the L1→L2 transition and does not warrant recalibration. See `SIMD_BENCHMARK_RESULTS.md §NEON threshold recalibration sweep` for a worked example. + ### NEVER thresholds for trivial SIMD operations Distributions whose per-element SIMD cost is very low (Uniform, Laplace at small N) may show that parallel never sustains an advantage within practical batch sizes. For these, NEVER is correct regardless of what the profiler reports at its measurement ceiling. Indicators: @@ -72,6 +83,20 @@ Distributions whose per-element SIMD cost is very low (Uniform, Laplace at small For AVX-512 v2.0.2, Uniform PDF, Uniform LogPDF, and Uniform CDF are all NEVER. +### Clean-entry criterion for iterative calibration + +When iterating threshold values, the target state is a parallel entry that exceeds the last VECTORIZED point in the sweep. If the first benchmark measurement at the new threshold T falls below the VECTORIZED level just below T, the parallel path has not yet amortised its threading overhead — raise the threshold further. + +Example from the NEON Laplace PDF recalibration (perf/dispatch-threshold-recalibration, M1): + +| Threshold | Last VECTORIZED | Parallel entry (N=T) | Entry delta | +|---|---|---|---| +| 25k | 327M/s at N=20k | 316M/s | −3% (parallel losing — raise threshold) | +| 30k | 385M/s at N=25k | 363M/s | −6% (parallel losing — raise threshold) | +| 35k | 383M/s at N=30k | 460M/s | +20% (parallel winning — correct threshold) | + +At 25k and 30k the parallel entry was below VECTORIZED, meaning the threshold fired before overhead amortised. At 35k parallel immediately exceeded VECTORIZED; 35k is the encoded value. + --- ## Cache hierarchy effects on batch throughput diff --git a/include/core/dispatch_thresholds.h b/include/core/dispatch_thresholds.h index 5dc647ed..7d11aeb9 100644 --- a/include/core/dispatch_thresholds.h +++ b/include/core/dispatch_thresholds.h @@ -162,11 +162,16 @@ constexpr ArchTable kNeon = {{ /* BETA(7) */ {NEVER, NEVER, 256}, // PDF/LogPDF: BEST=VECTORIZED all 3 runs at // 2M /* CHI_SQUARED(8) */ {512, 128, 64}, - /* LOG_NORMAL(9) */ {10000, 64, 256}, + /* LOG_NORMAL(9) */ {25000, 64, 256}, // PDF: 10000→25000 (pylibstats sweep: trough at + // N=10k–15k; parallel beats VECTORIZED reliably + // from N=20k; 25k is conservative safe zone) /* PARETO(10) */ {75000, 50000, 50000}, /* WEIBULL(11) */ {25000, 50000, 50000}, - /* RAYLEIGH(12) */ {10000, 25000, 25000}, // LogPDF: bimodal {25k,25k,64}; upper - // pair→25k + /* RAYLEIGH(12) */ {20000, 25000, 25000}, // PDF: 10000→20000 (pylibstats sweep: + // trough at N=10k; parallel beats + // VECTORIZED at N=15k; 20k is conservative + // safe zone). LogPDF: bimodal {25k,25k,64}; + // upper pair→25k /* VON_MISES(13) */ {100000, 500000, 128}, /* BINOMIAL(14) */ {NEVER, NEVER, NEVER}, // CDF: prior 64 reversed; // BEST=VECTORIZED/SCALAR @@ -174,8 +179,11 @@ constexpr ArchTable kNeon = {{ // BEST=VECTORIZED/SCALAR /* GEOMETRIC(16) */ {NEVER, NEVER, NEVER}, // PDF/LogPDF VECTORIZED; CDF SCALAR all 3 // runs - /* LAPLACE(17) */ {6144, 64, 256}, // new: PDF upper pair {6k,6k}; LogPDF floor; CDF - // consistent + /* LAPLACE(17) */ {35000, 64, 256}, // PDF: 6144→25000→30000→35000 (pylibstats sweep: + // trough at N=10k; oscillatory recovery; 30k + // entry dip 363M vs VECTORIZED 385M (6%); 35k + // trial to see if entry dip clears further). + // LogPDF: floor artefact (64); CDF: consistent /* CAUCHY(18) */ {25000, 50000, 512}, // new: PDF {10k,10k,25k}→25k; LogPDF // {25k,25k,50k}→50k }}; @@ -381,12 +389,13 @@ constexpr ArchTable kAvx2 = {{ // combined {64,NEVER,NEVER,NEVER,64,256}; 50/50 finite/NEVER split with // anti-correlated pool state — conservative = NEVER) constexpr ArchTable kAvx512 = {{ - /* UNIFORM(0) */ {NEVER, NEVER, NEVER}, // PDF/LogPDF: NEVER (trivial SIMD path; - // parallel never recovers to SIMD throughput - // in 1k-100k sweep; see issue #50). - // CDF: 128→50k→NEVER (50k still too early: - // 960M at N=45k drops to 463M at N=50k and - // does not recover within measured range) + /* UNIFORM(0) */ {NEVER, NEVER, + NEVER}, // PDF/LogPDF: NEVER (trivial SIMD path; + // parallel never recovers to SIMD throughput + // in 1k-100k sweep; see issue #50). + // CDF: 128→50k→NEVER (50k still too early: + // 960M at N=45k drops to 463M at N=50k and + // does not recover within measured range) /* GAUSSIAN(1) */ {1000000, 400000, 25000}, // LogPDF bimodal override; CDF: 50k→25k /* EXPONENTIAL(2) */ {250000, 400000, 250000}, // LogPDF: NEVER→400k; PDF/CDF reduced /* DISCRETE(3) */ {150000, 150000, NEVER}, // PDF: held 150000 (512 was @@ -407,13 +416,13 @@ constexpr ArchTable kAvx512 = {{ /* NEGATIVE_BINOMIAL(15) */ {NEVER, NEVER, 512}, // CDF: 2048→512 /* GEOMETRIC(16) */ {NEVER, NEVER, 512}, // new: PDF/LogPDF NEVER; CDF 512 (6-run set // with NegBinomial; max of lower cluster) - /* LAPLACE(17) */ {35000, 50000, 20000}, // PDF: 64→25k→35k (mild N=25k dip 233M→184M; - // recovers by N=30k; 35k clears it). - // LogPDF: 64→25k→50k (severe N=25k dip - // 433M→170M; only amortises at N=45-50k; - // see issue #50). - // CDF: 1024→20k (minor; threshold fires at - // N=20k but overhead amortises by N=30k) + /* LAPLACE(17) */ {35000, 50000, 20000}, // PDF: 64→25k→35k (mild N=25k dip + // 233M→184M; recovers by N=30k; 35k clears + // it). LogPDF: 64→25k→50k (severe N=25k dip + // 433M→170M; only amortises at N=45-50k; + // see issue #50). + // CDF: 1024→20k (minor; threshold fires at + // N=20k but overhead amortises by N=30k) /* CAUCHY(18) */ {2000000, 750000, NEVER}, // new: PDF 2M; LogPDF 750k; CDF NEVER // (6-run set with StudentT CDF; 50/50 // split → conservative) From 64756a6c91daff1a599038cdd7aebd76b7bba875 Mon Sep 17 00:00:00 2001 From: Gary Wolfman Date: Fri, 3 Jul 2026 21:57:43 -0400 Subject: [PATCH 5/6] perf: recalibrate kAvx2/kAvx thresholds for Kaby Lake AVX2+FMA (issue #50) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Seven kAvx2 corrections confirmed via pylibstats scipy_comparison sweep (three passes: 1k–100k dense, 50k–150k at 10k, 90k–160k at 5k). kAvx2 changes: Uniform CDF: 128 → NEVER (trough N=5k 105M; parallel never recovers within practical range) Gaussian PDF: 50000 → 130000 (trough N=50k–75k; clean entry N=130k 369M > VECTORIZED 347M) Gaussian LogPDF: 64 → 20000 (floor artefact; trough N=7.5k; clean entry N=20k 404M) Exponential PDF: 25000 → 120000 (trough N=25k–35k; clean entry N=120k 280M > VECTORIZED 240M) Exponential LogPDF: 64 → 25000 (floor artefact; trough N=5k 117M; clean entry from N=25k–30k) Exponential CDF: 25000 → 100000 (trough N=25k–30k; clean entry confirmed N=100k 230M > 204M) Laplace LogPDF: 64 → 25000 (floor artefact; trough N=5k 107M; clean entry from N=25k) kAvx derived from kAvx2 (Sandy Bridge/Ivy Bridge; L1/L2 same as kAvx2): ÷2 for transcendental-heavy ops (exp-based: Gaussian/Exponential PDF/CDF) ÷1.5 for polynomial-only ops (Gaussian LogPDF, Exponential LogPDF, Laplace LogPDF): FMA absence causes smaller cost delta than for exp. kNone: comment-only update — corrects 'L1d boundary' claim (actual L1 boundary is N≈2048; 8192 is within L2 range) and notes T3=16384 sits at the L2→L3 boundary, the same failure mode fixed by NEVER in kAvx2/kAvx512 Uniform (issue #50). Numbers unchanged; no profiling hardware available. Co-Authored-By: Oz --- include/core/dispatch_thresholds.h | 65 +++++++++++++++++++++++++----- 1 file changed, 54 insertions(+), 11 deletions(-) diff --git a/include/core/dispatch_thresholds.h b/include/core/dispatch_thresholds.h index 7d11aeb9..ae98ce04 100644 --- a/include/core/dispatch_thresholds.h +++ b/include/core/dispatch_thresholds.h @@ -226,9 +226,19 @@ constexpr ArchTable kNeon = {{ // Note: Binomial/NegBinomial/Geometric PDF/LogPDF remain NEVER. // SSE2 delegates to kAvx; both updated together. constexpr ArchTable kAvx = {{ - /* UNIFORM(0) */ {NEVER, NEVER, 64}, - /* GAUSSIAN(1) */ {25000, 64, 4096}, // CDF: 10000→4096 (kAvx2=8192÷2) - /* EXPONENTIAL(2) */ {12500, 64, 12500}, // PDF/CDF: 25000→12500 (kAvx2=25k÷2) + /* UNIFORM(0) */ {NEVER, NEVER, NEVER}, // CDF: 64→NEVER (kAvx2=NEVER) + /* GAUSSIAN(1) */ {65000, 13000, 4096}, // PDF: 25k→65k (kAvx2=130k÷2; + // exp-heavy, ÷2 correct). + // LogPDF: 64→13k (kAvx2=20k÷1.5; + // polynomial-only, FMA delta small; + // same L1/L2 boundaries as kAvx2). + // CDF: unchanged (kAvx2=8192÷2) + /* EXPONENTIAL(2) */ {60000, 17000, 50000}, // PDF: 12.5k→60k (kAvx2=120k÷2; + // exp-heavy, ÷2 correct). + // LogPDF: 64→17k (kAvx2=25k÷1.5; + // linear only, FMA delta small). + // CDF: 12.5k→50k (kAvx2=100k÷2; + // exp-heavy, ÷2 correct) /* DISCRETE(3) */ {50000, 50000, 512}, // PDF: kAvx2=50k; CDF: 1024÷2=512 /* POISSON(4) */ {128, 128, 128}, /* GAMMA(5) */ {12500, 256, 64}, // PDF: 10000→12500; LogPDF: kAvx2=512÷2 @@ -243,7 +253,11 @@ constexpr ArchTable kAvx = {{ /* BINOMIAL(14) */ {NEVER, NEVER, 128}, // CDF: held from kAvx512=128 /* NEGATIVE_BINOMIAL(15) */ {NEVER, NEVER, 256}, // CDF: held; kNeon/kAvx2=NEVER /* GEOMETRIC(16) */ {NEVER, NEVER, NEVER}, - /* LAPLACE(17) */ {64, 64, 128}, // CDF: kAvx2=256÷2=128 + /* LAPLACE(17) */ {64, 17000, 128}, // LogPDF: 64→17k (kAvx2=25k÷1.5; + // fabs+linear, no transcendental; + // FMA delta small; same L1/L2 + // boundaries as kAvx2). + // CDF: kAvx2=256÷2=128 /* CAUCHY(18) */ {37500, 37500, 64}, // PDF/LogPDF: kAvx2=75k÷2 }}; @@ -296,9 +310,27 @@ constexpr ArchTable kAvx = {{ // - VonMises LogPDF: 500000 → 400000 ({400k,250k,250k} → max=400k) // - VonMises CDF: 128 → 256 ({64,64,256} → max=256) constexpr ArchTable kAvx2 = {{ - /* UNIFORM(0) */ {NEVER, NEVER, 128}, - /* GAUSSIAN(1) */ {50000, 64, 8192}, - /* EXPONENTIAL(2) */ {25000, 64, 25000}, + /* UNIFORM(0) */ {NEVER, NEVER, NEVER}, // CDF: 128→NEVER (pylibstats sweep: + // trough at N=5k 105M vs 292M; + // parallel never recovers within + // practical range; see issue #50) + /* GAUSSIAN(1) */ {130000, 20000, 8192}, // PDF: 50k→100k→130k (trough N=50k-75k + // 341M→240M; parallel first exceeds + // VECTORIZED at N=130k 368M; + // sweep 2 confirmed; see issue #50). + // LogPDF: 64→20k (floor artefact; + // trough N=7.5k 140M; clean entry + // at N=20k 404M; see issue #50) + /* EXPONENTIAL(2) */ {120000, 25000, 100000}, // PDF: 25k→75k→120k (trough N=25k-35k; + // 267M→151M; first clean entry at + // N=120k 280M > 240M VECTORIZED; + // sweep 2 confirmed; see issue #50). + // LogPDF: 64→25k (floor artefact; + // trough N=5k 117M; clean entry + // from N=25k-30k). + // CDF: 25k→100k (trough N=25k-30k; + // 249M→139M; clean entry confirmed + // at N=100k 277M; see issue #50) /* DISCRETE(3) */ {50000, 50000, 1024}, /* POISSON(4) */ {128, 128, 256}, /* GAMMA(5) */ {25000, 512, 64}, // PDF: warm-pool override; see note above @@ -313,7 +345,9 @@ constexpr ArchTable kAvx2 = {{ /* BINOMIAL(14) */ {NEVER, NEVER, NEVER}, /* NEGATIVE_BINOMIAL(15) */ {NEVER, NEVER, NEVER}, /* GEOMETRIC(16) */ {NEVER, NEVER, NEVER}, - /* LAPLACE(17) */ {64, 64, 256}, + /* LAPLACE(17) */ {64, 25000, 256}, // LogPDF: 64→25k (floor artefact; + // trough N=5k 107M; clean entry from + // N=25k; see issue #50) /* CAUCHY(18) */ {75000, 75000, 128}, }}; @@ -485,12 +519,21 @@ constexpr std::size_t sse2_parallel_threshold(DistributionType dist, OperationTy // NegBinomial, VonMises (cos + cached Bessel Z). // // T2 — 8192: elementary transcendental distributions. Per-element cost -// ~20–50 ns (exp, log, erf). Amortises at ~80000 elements; 8192 is the -// L1d cache boundary and a conservative lower bound. +// ~20–50 ns (exp, log, erf). Amortises at ~80000 elements; 8192 is within +// the L2 cache range for double-precision two-array operations (L1d boundary +// is at N ≈ 2048 = 32KB ÷ 16B; L2→L3 boundary is at N ≈ 16384 = 256KB ÷ 16B). +// 8192 is therefore a conservative lower bound, set below the true amortisation +// point (~80k elements) but in a well-cached regime (L2-resident). // Distributions: Gaussian, Exponential, LogNormal, Weibull, Rayleigh, Pareto. // // T3 — 16384: arithmetic/bandwidth-limited distributions. Per-element cost -// ~2–5 ns. Amortises at ~800000 elements; 16384 is conservative. +// ~2–5 ns. The true parallel amortisation point is ~800k+ elements; 16384 is +// a placeholder only. Note: 16384 sits at the L2→L3 cache boundary (two-array +// footprint = 256KB = L2 capacity), which compounds dispatch overhead with a +// cache-tier transition — the same failure mode seen on kAvx512 Uniform (issue +// #50). On SIMD-capable hardware, Uniform and Discrete are NEVER in kAvx2 and +// kAvx512 for this reason. T3 should likely be NEVER for no-SIMD builds too; +// update if strategy_profile on no-SIMD hardware confirms. // Distributions: Uniform, Discrete. // // GEOMETRIC: T1 (delegates to NegBinomial — lgamma + incomplete beta). From 0c8dae83916fa4d5a2625dd172330e0bc96a804f Mon Sep 17 00:00:00 2001 From: Gary Wolfman Date: Sat, 4 Jul 2026 10:01:57 -0400 Subject: [PATCH 6/6] perf: document PB2/TDP interaction in kAvx512; close #50 Gaussian/Exponential items MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cold-machine targeted sweep (2026-07-04) on idle Asus TUF A16 (overnight sleep, single session, CPU temp ≤50°C) confirms the N=90k Gaussian and N=40-50k Exponential cliffs seen in earlier loaded-machine runs are caused by AMD Precision Boost 2 stepping down from boost frequency (~4.5-5 GHz) to TDP-limited sustained frequency (~3.0-3.5 GHz) once PPT budget is exhausted under sustained 100% CPU load. This is a power constraint, not thermal: temperature remained well below the ~90°C throttle threshold throughout. PB2 step-down timing is non-deterministic, so the dip falls at whichever distribution happens to be measured at that moment — confirmed by dips appearing at different N values (75k, 95k, 110k) in the cold-machine run rather than consistently at N=90k. Cold-machine results: Gaussian PDF N=90k: 658M/s (loaded: 186M/s) — no cliff Gaussian LogPDF N=90k: 1.7G/s (loaded: 212M/s) — no cliff Exponential PDF N=90k: 539M/s — no sustained valley kAvx512 Gaussian (1M/400k) and Exponential (250k/400k/250k) thresholds are correct; no numeric changes required. Added section-header note and inline comments documenting the PB2/TDP mechanism and mitigation (use targeted single-distribution --sizes runs for future sweeps). Closes #50 Gaussian/Exponential open items. Co-Authored-By: Oz --- include/core/dispatch_thresholds.h | 35 ++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/include/core/dispatch_thresholds.h b/include/core/dispatch_thresholds.h index ae98ce04..7716f08f 100644 --- a/include/core/dispatch_thresholds.h +++ b/include/core/dispatch_thresholds.h @@ -422,6 +422,28 @@ constexpr ArchTable kAvx2 = {{ // - Cauchy CDF: NEVER → NEVER (new; 6-run set with StudentT CDF: // combined {64,NEVER,NEVER,NEVER,64,256}; 50/50 finite/NEVER split with // anti-correlated pool state — conservative = NEVER) +// +// AMD Precision Boost 2 / TDP interaction with multi-distribution sweeps: +// The Zen4 Ryzen 7 7445HS boosts to ~4.5-5.0 GHz for short workloads and steps +// down to a TDP-limited sustained frequency (~3.0-3.5 GHz) once package power +// (PPT) budget is exhausted under sustained 100% CPU load. This is a power +// constraint, not thermal: CPU temperature during benchmark runs was measured at +// ≤50°C (far below the ~90°C throttle threshold), but CPU utilisation pinned to +// 100% repeatedly, exhausting the PPT budget. PB2 frequency reduction is abrupt +// and non-deterministic in timing, so whichever distribution happens to be +// measured when PB2 steps down receives anomalously low throughput. +// This was investigated for Gaussian PDF/LogPDF (observed cliff at N=90k in a +// loaded run) and Exponential PDF (shallow valley at N=40-50k). +// Targeted sweeps on an idle machine (overnight sleep, single open session) show: +// Gaussian PDF N=90k: 658M/s (loaded run: 186M/s) — no cliff +// Gaussian LogPDF N=90k: 1.7G/s (loaded run: 212M/s) — no cliff +// Exponential PDF N=90k: 539M/s — no sustained valley +// Random per-run dips at different N values (75k, 95k, 110k etc.) within a +// single sweep confirm non-deterministic PB2 step-down, not dispatch effects. +// Conclusion: Gaussian PDF/LogPDF thresholds (1M/400k) and Exponential PDF +// threshold (250k) are correct; no changes required. Confirmed 2026-07-04. +// Mitigation for future sweeps: use targeted single-distribution --sizes runs +// rather than full-suite runs to keep sustained CPU load below PPT threshold. constexpr ArchTable kAvx512 = {{ /* UNIFORM(0) */ {NEVER, NEVER, NEVER}, // PDF/LogPDF: NEVER (trivial SIMD path; @@ -430,8 +452,17 @@ constexpr ArchTable kAvx512 = {{ // CDF: 128→50k→NEVER (50k still too early: // 960M at N=45k drops to 463M at N=50k and // does not recover within measured range) - /* GAUSSIAN(1) */ {1000000, 400000, 25000}, // LogPDF bimodal override; CDF: 50k→25k - /* EXPONENTIAL(2) */ {250000, 400000, 250000}, // LogPDF: NEVER→400k; PDF/CDF reduced + /* GAUSSIAN(1) */ {1000000, 400000, 25000}, // LogPDF bimodal override; CDF: 50k→25k. + // PDF/LogPDF thresholds confirmed correct + // via cold-machine targeted sweep + // (2026-07-04): N=90k cliff in loaded runs + // is AMD Precision Boost expiry, not + // dispatch — see section header note above. + /* EXPONENTIAL(2) */ {250000, 400000, 250000}, // LogPDF: NEVER→400k; PDF/CDF reduced. + // PDF threshold confirmed correct via + // cold-machine sweep (2026-07-04): shallow + // N=40-50k valley in loaded runs is + // Precision Boost expiry — see above. /* DISCRETE(3) */ {150000, 150000, NEVER}, // PDF: held 150000 (512 was // profiling-order warm-pool; aligns with // LogPDF same runs); CDF: 75k→NEVER