perf(solvers): davidson-native few-k thread split — measured non-win, shipped as opt-in - #472
Closed
wladerer wants to merge 3 commits into
Closed
perf(solvers): davidson-native few-k thread split — measured non-win, shipped as opt-in#472wladerer wants to merge 3 commits into
wladerer wants to merge 3 commits into
Conversation
Add an outer/BLAS thread split to the native kernel so a solve with fewer k-points than cores can thread each k's dense subspace algebra inside OpenBLAS across the otherwise-idle cores, instead of stranding them. The mode gate lives in the Python adapter (GRADWAVE_NATIVE_KMODE=auto|kpar| fewk, GRADWAVE_NATIVE_FEWK_OUTER for A/B): auto picks fewk when 2*nk<=nthreads, else kpar. kpar is byte-identical to the historical single-argument kernel (outer=nthreads, blas=1). The C loop uses `if(outer_threads>1)` so outer=1 runs the k loop serially in the master thread, keeping the multithreaded BLAS a plain level-1 region (no nesting). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018j6BBnPtBSjkrnASSc52mT
The few-k thread split is memory-bandwidth-bound at nk=8 (Si-64 real Hamiltonian, asus 16 threads): ms/solve 44507 (kpar) ~= 44062 (outer8/blas2) < 47079 (outer4/blas4) < 71820 (outer2/blas8). The k-parallel kpar path already saturates the memory system, so handing stranded cores to OpenBLAS does not help. auto therefore always resolves to kpar (zero regression); fewk stays an explicit GRADWAVE_NATIVE_KMODE=fewk opt-in for A/B and future higher-bandwidth hardware. Adds mode-gate unit tests and a fewk-vs-eager SCF correctness test; documents the knobs and the measured verdict. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018j6BBnPtBSjkrnASSc52mT
…M108) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018j6BBnPtBSjkrnASSc52mT
Owner
Author
|
Parked per the no-complexity-for-no-wins rule: the few-k thread split is a measured monotonic non-win (kpar 16×1 = 44507 ms/solve; 8×2 tie; 4×4 0.95×; 2×8 0.62×; 1×16 aborted-worse; eigenvalues bit-identical, zero fallbacks). Mechanism: the Si-64 dense-subspace wall is memory-bandwidth-bound, not core-starved — nixpkgs OpenBLAS TSQR regresses past ~11 threads and kpar already ran at ~3.4 effective cores of 8 active. Branch perf/native-largenb kept as the ready-made A/B harness if high-bandwidth hardware (H100 HBM) materializes. Full record in the session memory. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
davidson-native for few-k large-nb — MEASURED NEGATIVE, shipped as opt-in
Campaign: exploit the cores that the native Davidson kernel strands when
nk < nthreads(e.g. Si-64 at nk=8 on a 16/22-core box). The kernel runs#pragma omp parallelover k withopenblas_set_num_threads(1), so at nk=8 ituses 8 cores and leaves the rest idle while each k's dense subspace algebra
(QR / Rayleigh-Ritz GEMMs) runs single-threaded. Target ceiling was ~1.7×
(the ~43% dense-algebra share of the Si-64 SCF wall).
Verdict: the lever is a non-win. The per-k dense algebra is
memory-bandwidth-bound, so the k-parallel path already saturates the memory
system; handing the stranded cores to OpenBLAS does not accelerate it, and
splitting k into fewer parallel waves to give each more BLAS threads makes it
monotonically worse. Shipped anyway as an explicit opt-in —
autoalwaysresolves to
kpar, so no solve regresses relative to the pre-split kernel.What changed
davidson_native.c): the three k-loops take anouter_threads/blas_threadssplit.if(outer_threads>1)meansouter=1runs the k loopserially in the master thread, keeping the multithreaded BLAS a plain level-1
OMP region (no nesting).
kpar(outer=nthreads, blas=1) is byte-identical tothe old single-argument kernel.
native_davidson.py):_resolve_thread_split+ theGRADWAVE_NATIVE_KMODE(auto|kpar|fewk) /GRADWAVE_NATIVE_FEWK_OUTERgate.auto→kpar(measured-safe default).fewkis opt-in for A/B and futurehigher-bandwidth hardware.
Measurements (asus, all under the bench-lock protocol)
OpenBLAS scaling at Si-64 shapes (USE_OPENMP build, m≈15k, dim=308):
OpenBLAS plateaus at ~4 threads and QR regresses past ~11 — there is no
single-call speedup to harvest from the idle cores.
Nested OMP dense-wall microbench (nk=8, one iteration-equivalent):
The 8-way k-parallel
kparpath (8×1) is already optimal; every reallocationof cores toward BLAS is neutral-to-catastrophic — the classic signature of a
bandwidth-bound workload.
Isolated real-Hamiltonian solve — Si-64, nk=8, nb=154 (16 threads):
captured mid-SCF from the actual
BatchedHamiltonian. Run at ecut=15 Ry(m=8480) because the box has only 14 GB RAM and the 30 Ry config (m≈24k)
swaps at ~5 min/iter — see the hardware note below.
Monotonic: giving each k more BLAS threads (fewer k in parallel) is strictly
slower.
kparwins. Eigenvalues bit-identical across every split (ΔEv = 0),so the split is a pure threading change — no numerical effect.
QE reference for the same Si-64 box/pseudos: 87 s full SCF.
Regression guard
autoresolves tokparfor every nk (few-k and many-k alike), so theal-4^3-mesh (nk=36) and al-8^3-mesh (nk≥60) paths are byte-identical to the
pre-split kernel:
_resolve_thread_split(36,16) == (16,1)etc. — covered by thenew
test_resolve_thread_split_gate.Engagement verification (mandatory)
Every timed arm wrapped
registry.get("davidson-native")in a counting spy andasserted
fallbacks == 0(viadiagnostics["fallback_reason"]) and the actual(outer_threads, blas_threads)used per solve — the split shown in each row isthe kernel's own report, not the requested value.
GRADWAVE_EIGENSOLVERpinnedin the shell (read call-time since #469). Native engaged on every arm, zero
eager fallbacks. Smoke-verified separately on al4 (nk=36).
What remains of the 1.7× ceiling
Nothing reachable by adding cores. The 43% dense-algebra share is
memory-bandwidth-bound (and the few-k large-nb regime is memory-capacity-
bound on a 14 GB box). The ceiling would only move by cutting memory traffic —
lower-precision subspace storage, or a communication-avoiding/blocked QR that
touches the tall-skinny factors fewer times — not by threading. The
fewklever is retained for hardware with materially more memory bandwidth (H100 HBM,
or a many-channel server), where the OpenBLAS plateau may sit higher.
Hardware caveat
The full-SCF Si-64 arm (eager baseline ≈2258 s from the campaign premise, and
current-native full SCF) could not be timed cleanly: asus has 14 GB RAM and the
30 Ry Si-64 working set (~10 GB native, more eager) swaps hard (~5 min/iter).
The isolated-solve table is the direct measurement of the lever; the per-solve
kparnumber is current-native's real behaviour on the true Hamiltonian.🤖 Generated with Claude Code
https://claude.ai/code/session_018j6BBnPtBSjkrnASSc52mT