GOAL — full GPU execution of the AOM/moment screening engine (2026-06-08 →)

Objective (user-set): make the three screening layers run fully on GPU, with a measured before/after at each step.

  1. Moments on GPU — base moments XᵀX/XᵀY built and kept device-resident, no per-candidate host round-trip.

  2. AOM on GPU — the per-candidate operator→moment transform and the Ridge GCV solve on the device, batched over candidates.

  3. PLS tuning loop on GPU — the screen batched over chain × candidate × fold in one launch (folds already batched), fed device-resident moments.

Measured priority (5b-0, nsys on BERRY p=2101, 71.3 s) — see fused_moment_executor_design.md

Rank

Host hotspot

Share

GPU target

1

n4m_householder_qrRidge GCV solve (CPU, O(p³/n³), single-thread)

37%

cuSOLVER SPD Cholesky/eigh

2

synchronous cudaMemcpy (888 blocking copies, 4.59 s)

~20% samples

device residency / pinned mem

3

CPU OpenBLAS matmuls

~6%

route to cuBLAS / device moments

cuda_dispatch::gemm (moment transform)

2.6%

minor — batch later

The draft “fused IKPLS scorer” and “dense moment transform” were not the bottleneck (Codex-flagged, profile-confirmed). Reordered accordingly.

Blocks (ordered by measured value; each behind the green gate, ABI 1.22.0, fp64 host-equivalence)

  • B1 — Ridge GCV solve → GPU (cuSOLVER). ✅ DONE (2026-06-08). Device SPD Cholesky (spd_solve) on the 3 dual ridge sites, host fallback, equivalence-tested. 3.83× total on BERRY/COLZA/LUCAS (BERRY 7.59×), RMSEP preserved, green gate green, ABI 1.22.0. See worklog 2026-06-08.

  • B2 — Device-resident dual-Ridge (upload K once). ✅ DONE (2026-06-08). Per-fold prepare_dual_ridge uploads K once + reuse-buffers Cholesky + add_scaled_identity λ-shift. 1.07× incremental (cumulative 4.22×), RMSEP bit-identical, green gate green, ABI 1.22.0. Targeted the ridge-solve copies; broader moment copies → B3.

  • B3 — Device moment/gram build. ⚙️ KEPT as a size-gated env opt-in (2026-06-08). The un-gated drop-in regressed (0.96×: the moment build is host-OpenBLAS-optimal and XᵀX is consumed on host, so GPU-only-build adds a D2H), so it is gated: build_moments_device/build_gram_device run only when n·p² N4M_CUDA_MOMENT_MIN_PRODUCT (default 15e9; 0=always GPU, higher=always host). Correct, bit-equivalent, equivalence-tested; a wash on consumer GPUs but a potential win on strong-fp64 datacenter GPUs (A100/H100) — zero cost when off. Default = host build. See worklog. (The operator-transform route Fable’s trace expected is dead code, gated off p>1024 — not the target.)

  • B4 — (not pursued) Full device-resident moment chain + device-pointer PLS scorer entry. The investigation (incl. LUCAS_SOC: moment build ~1% of a 2402s fit) showed the moment build is never the pipeline bottleneck at any scale, so B4 would not help either; the captured win is B1+B2 (4.22×). Pursue only if a future profile / different hardware justifies it.

Process (multi-agent, user-set)

  • Fable 5 (claude --model claude-fable-5): block design/roadmap + code review.

  • Codex (codex exec): implementation under Fable’s design + Opus’s directives.

  • Opus (orchestrator): directives, final diff review, build + green gate (n4m_tests/n4m_internal_tests CUDA & dev-release, catalog/ABI checks), and the before/after benchmark on BERRY/COLZA/LUCAS (/tmp/bench_5b_baseline.csv is the “before”). Corrects when needed.

Green gate per block: CUDA n4m_c + n4m_tests + n4m_internal_tests; dev-release n4m_c; catalog/scripts/validate.py --check-references --strict-abi, reconcile_abi.py --check; git diff --check. ABI stays 1.22.0 (internal only).