AOM / Moment Integration Worklog¶
2026-06-08 - B3: device moment/gram build — un-gated reverted; size-gated kept as opt-in¶
Negative result, kept as the record. Multi-agent: Fable 5 designed
(_b3_fable_design.md), Codex implemented, Fable + Opus reviewed (“ship” — the code
was correct), Opus benched + reverted.
Hypothesis: the post-B2 #1 CUDA cost (cudaMemcpy 1.66s) was the materialized moment
build (compute_moments XᵀX/XᵀY/YᵀY) + dual gram (Xw·Xwᵀ) re-uploading the n×p design
per GEMM. Implemented build_moments_device/build_gram_device (upload X once,
symmetric products reuse the device pointer, cublasDgemm bit-equivalent to the
wrapper) as drop-ins in compute_from_contiguous and prepare_ridge_dual_design.
The implementation was correct — green gate green, both equivalence tests passed on
GPU, RMSEP bit-identical, Fable + Opus reviews clean. But the before/after benchmark
showed a REGRESSION: total 59.9s → 62.3s = 0.96×, BERRY 11.9→15.2 = 0.78×, only LUCAS
(p=4200) marginally faster. Root cause: the moment build was already on host OpenBLAS
(multi-threaded, transfer-free) and recompute_centered_moments consumes XᵀX on the
host, so routing the build to the GPU only added an H2D(X) + the unavoidable
D2H(XᵀX) the host path never paid. Reverted to the post-B2 state.
Lesson: only a fully device-resident build→center→consume chain (deferred B4) could win on moments, and it is marginal (host build is already fast) + high-risk. Reviews verify correctness; only the before/after bench catches a perf regression. Captured GPU wins are B1 (ridge solve) + B2 (dual-ridge residency); moment algebra stays on host.
Follow-up — “do large datasets benefit?” → size-gated B3, KEPT as an opt-in. A micro-benchmark of the isolated XᵀX build (incl. H2D+D2H) showed the GPU is faster, growing with size: BERRY(6.3G n·p²) 1.18×, LUCAS_pH(21G) 1.10×, LUCAS_SOC(108G) 1.64×, synth(48G) 1.82×. A controlled full-pipeline before/after (host vs GPU moment build, B1+B2 held constant) showed it is a wash on this consumer hardware: brix GPU 8.07s ≈ host 8.4s (noise); and LUCAS_SOC’s host fit is 2402s of which the moment build is ~1% (the rest is the n=6111 ridge/PLS, already on GPU via B1/B2) — the isolated 1.6× does not translate because the moment build is never the pipeline bottleneck on a 4090/5090.
But the device build (build_moments_device/build_gram_device) is correct,
bit-equivalent, equivalence-tested, and size-gated so it never regresses (host below
n·p² = N4M_CUDA_MOMENT_MIN_PRODUCT, default 15e9; set 0 to always use GPU). It is KEPT
as an env-controlled opt-in: on a strong-fp64 datacenter GPU (A100/H100, ~10–30× the
4090/5090 fp64 throughput) or for very large/many moment builds it can be a real win, at
zero cost when off. Default gate 15e9 leaves the moment build on host except n·p²≥15e9
(LUCAS-scale), where it is a wash on consumer GPUs and bit-equivalent; set the env var
higher to force host everywhere, or 0 to force GPU. B1+B2 (4.22×) remains the captured
win on the measured hardware.
2026-06-08 - B2: Device-resident dual-Ridge (upload K once, reuse buffers)¶
Second GPU-roadmap block. Fable designed (_b2_fable_design.md), Codex implemented,
Fable review (_b2_fable_review.md: “ship”) + Opus review + bench.
Post-B1 nsys re-profile showed the dual ridge solve re-uploaded the n×n gram K per
(λ×fold) — but K = design.K is identical across the λ grid (only +λI differs).
Changes (internal; ABI 1.22.0): add_scaled_identity kernel (on-device A[i·n+i]+=λ,
bit-identical to host); cuda_dispatch::{PreparedDualRidge, prepare_dual_ridge, dual_ridge_solve, destroy_dual_ridge} (upload K + col-major B0 once/fold, per-λ D2D +
diag-add + reuse a thread-local potrf/potrs workspace, pristine B0 preserved, ~1e-12
parity); sweep.cpp per-fold PreparedDualGpu handles (mirroring
ridge_moment_eigen_paths) + solve_dual_alpha routing the 3 dual consumers with the
unchanged B1 solve_dual_spd fallback; PreparedDualGpu forward-declared so the
non-CUDA build compiles. Validation: equivalence test on GPU ≤1e-8 + not-PD→1;
n4m_tests 351 (CUDA+dev); catalog/ABI 702/702, symbols internal. Before/after vs
post-B1: 63.9s → 59.9s = 1.07× (cumulative orig→B2 4.22×, BERRY 8.90×), RMSEP
bit-identical.
2026-06-08 - B1: Ridge GCV solve on GPU (cuSOLVER SPD Cholesky)¶
First GPU-roadmap block (GPU_ROADMAP_GOAL.md). Fable designed (_b1_fable_design.md),
Codex implemented, Fable + Opus reviewed (_b1_fable_review.md: “fix-then-ship” — one
finding, an out-of-scope gemm scratch-arena change, reverted by Opus).
nsys profiling found the #1 host hotspot = n4m_householder_qr (37% of CPU samples) —
the Ridge GCV solve (CPU Householder QR, O(n³)). For wide p (p>n) every fold solves the
dual n×n SPD system (K+λI)α=Y (SPD for λ>0). Changes (internal; ABI 1.22.0):
cuda_dispatch::spd_solve (cuSOLVER potrf+potrs, A symmetric uploaded as-is, B/X
col-major, devInfo after both, returns 0/1-not-PD/2-runtime); cusolverDnHandle_t on
the CublasState singleton; CUDA::cusolver linked on n4m_c/n4m_c_static;
sweep.cpp solve_dual_spd (GPU when ridge_cuda_dual_enabled: n≥256 +
N4M_CUDA_RIDGE_DISABLE kill-switch) with host solve_square_qr fallback, swapped only
the 3 dual sites (primal/k×k-inverse/ridge.cpp stay host — Cholesky rejects non-SPD).
Equivalence test vs host QR ≤1e-8 + rank-deficient→1. Validation: n4m_internal_tests
(SPD test on GPU) + n4m_tests 351 (CUDA+dev); catalog --check-references/--strict-abi
PASS, reconcile_abi 702/702, spd_solve absent from the dynamic symbol table.
Before/after on BERRY/COLZA/LUCAS: 253.0s → 63.9s = 3.96× (BERRY 8.13×); RMSEP
bit-identical (BERRY/COLZA) / 5.6e-14 (LUCAS).
2026-06-06 - PLS moment lower-prefix batch recovery for public sweeps¶
Purpose:
Keep public PLS exact-CV sweep paths on the shared host/CUDA prefix route when the requested maximum component is rank-deficient but a lower requested component remains valid.
Changes:
score_pls1_moment_sweep()andrun_moment_sweep()now mirror the many-chain AOM score-only path: after a max-prefix failure they first try to recover a lower requested prefix throughfit_pls1_moment_prefixes_for_foldsacross all folds, then only run fold-local fallback attempts for components above that recovered prefix.Added a small shared counter helper for fold-batch PLS prefix fits and removed the now-unused per-job recovery helper.
The public score semantics are unchanged: recovered lower components keep finite exact moment CV scores, failed later components stay
inf, and no materialized fold designs are introduced.Extended the live one-GPU CUDA route test so degenerate
sweep_run()andpls_cross_validate()calls prove the recovered component-1 prefix uses CUDA parallel-fold counters while only the failed higher component adds one host fallback attempt.
Validation:
build/dev-releasen4m_candn4m_internal_tests: PASS.build/cuda-onn4m_c: PASS../build/dev-release/cpp/tests/n4m_internal_tests: PASS.PYTHONPATH=bindings/python/src N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so /home/delete/.venv/bin/python -m pytest bindings/python/tests/test_moment_model_wrappers.py -q:83 passed.PYTHONPATH=bindings/python/src N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so /home/delete/.venv/bin/python -m pytest bindings/python/tests/test_aom_staged_campaign.py -q:16 passed.CUDA_VISIBLE_DEVICES=0 PYTHONPATH=bindings/python/src N4M_LIB_PATH=build/cuda-on/cpp/src/libn4m.so /home/delete/.venv/bin/python -m pytest bindings/python/tests/test_moment_model_wrappers.py::test_cuda_pls_many_batched_precedes_parallel_and_legacy_overrides -q:1 passed.
Follow-up:
This is a real exact-CV route hardening/perf slice for public sweeps and the PLS CV reference hook. It is still not the full fused/batched IKPLS many-chain/many-fold executor or the complete CUDA cartesian grinder.
2026-06-06 - AOM/moment inventory objective guard¶
Purpose:
Add a direct guard for the user-facing objective: the public AOM and moment facades must advertise the reusable winner surfaces, the global configurable screen/refit campaigns, the winning staged presets, direct moment heads, moment stack and CPU/CUDA capability metadata.
Changes:
Strengthened
bindings/python/tests/test_aom_moment_facade.pywith explicit inventory checks for:screen_refit_campaign,moment_fast_screen_refit_campaign,staged_chain_campaign,NativeAOMStagedChainCampaignRegressor,NativeAOMSavgolFocusRegressor,NativeAOMStrictFamilyLiteRegressor,aom_chain_fixed_fit_run,NativeAOMFixedCandidateRegressor, direct Ridge/PLS/PCR/CPPLS/weighted/robust/Ridge-PLS/continuum/ECR heads,moment_stack,NativeMomentStackRegressor, and the source-free CPU/CUDA backend recommendation helper.The guard asserts these inventory rows expose CPU and CUDA-build capability flags and reuse metadata, so a future facade cleanup cannot silently hide the deployable surfaces while lower-level imports still work.
Validation:
test_aom_moment_facade.py:28 passed.
Follow-up:
This improves completion evidence for the public API surface. It does not implement the future fused/batched Ridge/PLS many-chain executor.
2026-06-06 - Legacy catalog validator accepts current AOM/moment schema¶
Purpose:
Keep the old
catalog/scripts/validate_catalog.pygate green while the catalog transition still keepscatalog/methods.yamlas an auditable legacy source beside the split per-method files.
Changes:
Updated
catalog/schema/method_v1.jsonso the legacy schema matches the current split method schema for Python-backed orchestration methods:abi_symbolsmay be empty when native building blocks are catalogued separately, andparity.tolerancesis an accepted migrated tolerance block.Added a regression test in
bindings/python/tests/test_catalog_python_bindings.pythat runscatalog/scripts/validate_catalog.pyagainst the current repo.
Validation:
catalog/scripts/validate_catalog.py: PASS.catalog/scripts/validate.py: PASS.catalog/scripts/split_legacy_methods.py --check: PASS.test_catalog_python_bindings.py:12 passed.git diff --check: PASS.
Follow-up:
This closes a catalog gate mismatch. It does not change the remaining performance-only fused/batched Ridge/PLS many-chain executor gap.
2026-06-06 - Sweep and crossover timing artifacts refreshed to ABI 1.22¶
Purpose:
Remove the last stale ABI metadata from committed moment/AOM sweep timing evidence after the native AOM/moment route changes.
Changes:
Regenerated the moment sweep and stack timing artifacts against ABI 1.22.0:
moment_sweep_timing.csv,moment_sweep_timing_cuda_smoke.csv,moment_sweep_timing_cuda_many_batched_smoke.csv,moment_sweep_timing_parallel_flag_smoke.csv,moment_sweep_timing_min_device_smoke.csv,moment_stack_timing.csv, andmoment_stack_timing_cuda_smoke.csv.Regenerated the AOM sweep timing artifacts against ABI 1.22.0:
aom_sweep_timing.csv,aom_sweep_timing_cuda_smoke.csv, andaom_sweep_timing_batch_counter_smoke.csv.Regenerated the live CPU/CUDA crossover artifacts
moment_gpu_crossover.csvandmoment_gpu_crossover.mdwith one visible GPU, comparing CPU, CUDA default, and CUDA many-batched PLS profiles.Strengthened artifact guards so the refreshed sweep/stack/crossover CSVs must report ABI 1.22.0, expected CPU/CUDA library paths, host-vs-device PLS route counters, AOM exact/proxy batch counters, and the source-free crossover shape/profile matrix.
Validation:
py_compilepassed forbindings/python/tests/test_aom_moment_cuda_smoke_artifacts.py.Full
test_aom_moment_cuda_smoke_artifacts.pypassed (43 passed).
Follow-up:
This closes stale timing evidence for the current release slice. The fused many-chain IKPLS/Ridge grinder remains the true performance gap.
2026-06-06 - Strict AOM portfolio timing artifacts refreshed to ABI 1.22¶
Purpose:
Remove stale ABI metadata from committed strict AOM timing evidence after the latest native AOM/moment route changes.
Changes:
Regenerated the strict AOM portfolio CPU and CUDA timing artifacts against ABI 1.22.0:
aom_preprocess_timing(.csv|_cuda_smoke.csv),aom_selector_timing(.csv|_cuda_smoke.csv),aom_ridge_blender_timing(.csv|_cuda_smoke.csv),aom_operator_pls_stack_timing(.csv|_cuda_smoke.csv),aom_robust_hpo_timing(.csv|_cuda_smoke.csv), all strict AOM superblock/chain timing pairs, andaom_staged_chain_campaign_timing(.csv|_cuda_smoke.csv).Strengthened artifact guards so these AOM artifacts must report ABI 1.22.0, the expected CPU/CUDA library path, native+sklearn replay rows where applicable, and CPU-vs-CUDA PLS route counters.
Validation:
Full
test_aom_moment_cuda_smoke_artifacts.pypassed (36 passed).
Follow-up:
Moment sweep/stack/crossover artifacts still contain older historical ABI metadata and remain a separate refresh slice.
2026-06-06 - Strict AOM portfolio CPU timing artifacts¶
Purpose:
Close the release-readiness evidence gap where several catalogued strict AOM portfolio methods had committed CUDA smoke timing artifacts but no committed CPU/dev-release timing pair.
Changes:
Generated CPU timing CSVs on
build/dev-releasefor:aom_preprocess_timing.csv,aom_ridge_superblock_timing.csv,aom_ridge_active_superblock_timing.csv,aom_ridge_mkl_superblock_timing.csv,aom_pls_superblock_timing.csv,aom_ridge_pls_superblock_timing.csv,aom_chain_ridge_pls_timing.csv,aom_ridge_global_timing.csvandaom_staged_chain_campaign_timing.csv.Added release guards proving the new CPU artifacts use the dev-release library, cover native+sklearn replay where applicable, keep PLS host counters nonzero and CUDA counters at zero, and preserve the staged campaign invariant
selection_uses_test_set=False.
Validation:
Full
test_aom_moment_cuda_smoke_artifacts.pypassed (34 passed).
2026-06-06 - Staged real-cohort resume rejects stale CSV schemas¶
Purpose:
Prevent resumed staged AOM benchmark campaigns from appending current telemetry rows into older CSV artifacts whose headers pre-date the latest Ridge/PLS route counters.
Changes:
Added a header compatibility check in
benchmarks/cross_binding/run_aom_staged_real_cohort.pybefore appending to an existing output CSV.Existing compatible outputs still append normally; incompatible historical outputs now fail fast with a message telling the caller to use a fresh
--outputor explicitly migrate the artifact.Added focused tests for rejecting a stale header and accepting the current header.
Validation:
Targeted staged real-cohort resume tests passed (
2 passed, 22 deselected).Full
test_aom_benchmark_tools.pypassed (24 passed).Python
py_compileon the touched benchmark/test files passed.
2026-06-06 - Moment facade exposes full reusable AOM surface¶
Purpose:
Make
n4m.momenta self-contained facade for the reusable AOM/moment methods, not only the low-level moment helpers and a subset of presets.
Changes:
Re-exported native AOM preprocess/profile/chain sweep functions from
n4m.moment:aom_preprocess,aom_global_select,aom_per_component_select,aom_sweep_run,aom_chain_sweep_run,aom_robust_hpo,aom_ridge_blenderandaom_operator_pls_stack.Re-exported the missing reusable AOM sklearn wrappers in
n4m.moment:NativeAOMChainSweepRegressor,NativeAOMScreenRefitRegressor,NativeAOMOperatorPLSStackRegressor,NativeAOMRidgeBlenderRegressor,NativeAOMRobustHPORegressor,NativeAOMPLSRegressor,NativeAOMSweepRegressorandNativePOPPLSRegressor.Added corresponding
available_methods()rows for the fit/predict surfaces so the moment facade advertises native AOM sweeps, generic screen-refit, Ridge blender, operator PLS stack, robust HPO, AOM-PLS and POP-PLS alongside the existing moment-specific presets.Corrected the
pls_cross_validateinventory role to be a secondary helper oversweep_runrather than a second primaryutilities.sweepcatalog binding.
Validation:
test_aom_moment_facade.pypassed (26 passed).Targeted facade/inventory wrapper selection passed (
27 passed, 82 deselected).Full dev-release
test_moment_model_wrappers.pypassed (83 passed).Full dev-release
test_aom_staged_campaign.pypassed (16 passed).Python
py_compileon the touched facade/test files passed.CUDA facade smoke passed on
CUDA_VISIBLE_DEVICES=0.
Follow-up:
This improves method discoverability/reuse only. It does not implement the fused/batched Ridge/PLS many-chain executor.
2026-06-06 - Force-moments Ridge banded cap extends to p=512¶
Purpose:
Broaden strict Ridge moment screens for underdetermined local-operator chains without changing the pragmatic CPU
autoroute.
Changes:
Added a force-only Ridge banded moment feature cap:
kMaxForcedBandedRidgeMomentFeatures = 512.can_use_banded_operator_moment_ridge_features()and the Ridge moment transformer now use that larger cap only whenmoment_policy="force_moments".The normal Ridge banded cap remains
p <= 256; CPUautostill chooses the exact materialized dual-Ridge scorer for wide underdetermined rows.Added
test_aom_ridge_force_moments_extends_wide_banded_cap, covering afinite_differencechain atn=40, p=320, positive lambdas, zero materialized candidates, exact RMSE agreement against materialized scoring, unchanged CPUautomaterialization, and full/refit output after a moment-only candidate screen.
Validation:
Rebuilt
build/dev-releasen4m_c.Rebuilt
build/cuda-onn4m_c.Targeted Ridge force-moments pytest:
2 passed, 81 deselected.Full dev-release wrapper test file:
test_moment_model_wrappers.pypassed (83 passed).Dev-release
n4m_internal_testspassed.Manual one-GPU CUDA smoke with
CUDA_VISIBLE_DEVICES=0on the samen=40, p=320finite-difference Ridge screen reports two candidates, zero materialized candidates and one Ridge moment score batch with eight jobs.
Follow-up:
This closes part of the “very wide Ridge” force-screen gap. It is not the fused/batched Ridge/PLS many-chain executor, and Ridge moment screens beyond
p=512remain intentionally unsupported for now.
2026-06-06 - Force-moments bypasses CPU wide Ridge materialization heuristic¶
Purpose:
Keep
moment_policy="force_moments"strict for Ridgescore_onlyscreens when Ridge moments are mathematically admissible but the CPU wide-Ridge performance heuristic would normally choose materialization.
Changes:
In
run_aom_ridge_operator_moment_sweep, the score-only Ridge batch path now honors the already selected forced moment route before applyingshould_materialize_cpu_wide_ridge().This is limited to the Ridge moment score batch. Non-forced routing keeps the existing CPU wide materialization heuristic, and full/refit runs still use the existing selected-chain final-fit path.
Added
test_aom_ridge_force_moments_bypasses_cpu_wide_materialization_heuristic, coveringn=40, p=64, positive Ridge lambdas and zero materialized candidates. The test also checks exact RMSE agreement against the explicit materialized screen.
Validation:
Rebuilt
build/dev-releasen4m_c.Rebuilt
build/cuda-onn4m_c.Targeted force-moments pytest:
2 passed, 80 deselected.Full dev-release wrapper test file:
test_moment_model_wrappers.pypassed (82 passed).Manual repro for the prior
n=40, p=48,cv=4, Ridge-onlyforce_moments,score_only=Truefailure now returns two Ridge moment candidates, zero materialized candidates and one score batch with eight jobs.Manual one-GPU CUDA smoke with
CUDA_VISIBLE_DEVICES=0reports the same Ridge moment route counters onbuild/cuda-on.
Follow-up:
This fixes a forced-screen route inconsistency. It is still not the deferred fused/batched Ridge/PLS many-chain executor.
2026-06-06 - Force-moments bypasses CPU wide PLS materialization heuristic¶
Purpose:
Make
moment_policy="force_moments"mean “try the admissible operator-moment route” even when the CPU wide-PLS heuristic would normally materialize for speed.
Changes:
In
run_aom_chain_sweep, CPU wide Ridge/PLS materialization heuristics are disabled under the explicitforce_momentspolicy before route selection.This unblocks exact-CV PLS AOM moment screens for wider strict-moment chains on CPU. A previously rejected identity PLS screen at
n=120, p=32,cv=4,pls_components=[1,2,3]now stays on the PLS moment score-batch route with zero materialized candidates.Added
test_aom_pls_force_moments_bypasses_cpu_wide_materialization_heuristic.
Validation:
Rebuilt
build/dev-release,build/cuda-on,build/omp-onandbuild/blas-onn4m_c.Targeted pytest:
3 passed, 78 deselectedfor the new force-moments guard plus existing BLAS/OMP score-parity guards.Manual dev-release width sweep over
p=16,32,48,64,80,96now returns exact PLS moment score-batch rows withn_materialized_candidates=0.Manual one-GPU CUDA smoke at
p=96withCUDA_VISIBLE_DEVICES=0,cuda_pls_min_device_features=1andcuda_pls_parallel_folds=Truereports four CUDA PLS fold jobs and zero materialized candidates.Manual dev/OMP/BLAS smoke at
p=96reports matching selected scores and zero materialized candidates.
Follow-up:
This is a semantics/coverage fix for forced moment screens, not the full fused many-chain IKPLS executor.
2026-06-06 - PLS cross-validate reference ABI artifacts¶
Purpose:
Pin the reserved
n4m.pls_cross_validate/n4m_pls_cross_validatereference surface with small CPU/CUDA smoke timing artifacts before the future grouped/fused PLS grinder exists.
Changes:
Added
bench_pls_cross_validate_timing.py, which times full and score-onlyn4m.pls_cross_validatecalls across three synthetic shapes.Each row compares the public PLS CV hook against
n4m.sweep_run(heads=("pls",))on the same folds/component grid and records max absolute candidate-score, OOF-prediction and prediction deltas.Regenerated CPU and one-GPU CUDA smoke CSVs:
pls_cross_validate_timing.csvandpls_cross_validate_timing_cuda_smoke.csv.Added artifact guards proving CPU rows use host exact-CV routing while CUDA rows use the device PLS CV/final-fit route with parallel folds enabled, many-batched off and numerical-zero equivalence deltas.
Documented the benchmark command in
benchmarks/cross_binding/README.md.
Validation:
test_aom_moment_cuda_smoke_artifacts.py:25 passed.Python
py_compileon the new benchmark and artifact test passed.git diff --checkpassed.
Follow-up:
This is a reference ABI/timing hook only. The real open item remains the fused/batched host/device IKPLS-style executor for many preprocessing chains, folds and candidates.
2026-06-06 - Staged benchmark PLS score-mode switch¶
Purpose:
Make staged benchmark campaigns directly compare exact-CV PLS screens against explicit GCV-proxy first-pass screens without code edits, so screen-recall studies can be run incrementally from the standard runners.
Changes:
Added
--pls-score-mode {cv,gcv_proxy}torun_aom_staged_real_cohort.pyandbench_aom_staged_chain_campaign_timing.py.Forwarded the mode to
n4m.aom_staged_chain_campaignand persisted it in real-cohort CSV rows, diagnostics JSON runner metadata and staged timing CSV rows.Updated benchmark README guidance:
cvremains default exact-CV screen;gcv_proxyis explicit for proxy-vs-exact recall/timing campaigns, while retained-candidate refit remains exact-CV.
Validation:
test_aom_benchmark_tools.py:21 passed.Synthetic staged timing smoke with
--pls-score-mode gcv_proxywrote a CSV row containingpls_score_mode=gcv_proxy.py_compile and
git diff --checkpassed.
2026-06-06 - CUDA facade smoke covers PLS exact preset¶
Purpose:
Make the new public PLS exact-CV screen/refit preset prove the same CPU/GPU facade and CUDA-route readiness as the existing AOM/moment reusable presets.
Changes:
Extended
aom_moment_cuda_facade_smoke.pyto assertNativeAOMMomentPLSExactScreenRefitRegressoraliases through bothn4m.momentandn4m.aom.Added a tiny exact-CV PLS screen/refit fit on one visible GPU, checking
pls_score_mode="cv",refit_pls_score_mode="cv", zero GCV-proxy fits, CUDA device CV counters for both screen and refit, and zero host PLS CV fits.Regenerated
aom_moment_cuda_facade_smoke.jsonwith ABI1.22.0and added artifact assertions for the newpls_exact_screen_refit_estimatorsection.
Validation:
Regenerated the CUDA facade smoke artifact through
build/cuda-onwithCUDA_VISIBLE_DEVICES=0.test_aom_moment_cuda_smoke_artifacts.py:22 passed.py_compile and
git diff --checkpassed.
2026-06-06 - PLS exact-CV screen/refit reusable preset¶
Purpose:
Expose a simple PLS-only end-user preset for exact-CV preprocessing screens, separate from the existing PLS GCV-proxy screen preset, so screen-recall audits and reuse experiments can opt into exact train-CV ranking directly.
Changes:
Added
NativeAOMMomentPLSExactScreenRefitRegressor, a PLS-onlyNativeAOMScreenRefitRegressorpreset withpls_score_mode="cv",ridge_lambdas=(),heads=("pls",),moment_policy="force_moments"and prefix-aware chain ordering.Exported the preset through
n4m,n4m.sklearn,n4m.aomandn4m.moment; addedmoment_pls_exact_screen_refitinventory entries beside the existing PLS GCV and Ridge exact presets.Updated method docs, coverage matrix and catalog notes to distinguish PLS GCV-proxy -> exact-refit from PLS exact-screen -> exact-refit.
Validation:
Targeted wrapper/facade pytest, py_compile and diff checks run in the corresponding patch validation.
2026-06-06 - PLS exact batch fallback prefix reuse¶
Purpose:
Reduce the cost of the robust PLS exact score-only fallback after a global batched prefix fit fails on a rank-deficient late component.
Changes:
score_pls1_moment_sweeps_score_onlynow builds a unique descending list of requested component prefixes and, in the fallback path, tries only the largest still-needed prefix for each chain/fold job before descending.When a lower prefix is recovered, all requested components at or below that prefix reuse the same
RidgeMomentFitprefix vector. Components above the recovered prefix are marked failed/inf, matching the previous exact-CV fallback semantics.Already-failed component candidates are not retried on later folds.
Counters now report actual fallback prefix-fit attempts. In mixed batches, a healthy job whose max prefix succeeds after the global batch failed pays one fit per fold rather than one fit per component per fold.
No ABI or public Python surface change.
Validation:
Rebuilt dev-release
n4m_candn4m_internal_tests.Rebuilt CUDA
n4m_candn4m_internal_testswithCUDA_VISIBLE_DEVICES=0.Dev and CUDA
n4m_internal_testspassed.Targeted dev pytest over PLS fallback/reference degeneracy tests:
3 passed.Targeted one-GPU CUDA pytest over the same plus many-batched precedence:
4 passed.Full dev-release
test_moment_model_wrappers.py:80 passed.
2026-06-06 - Fixed-candidate CUDA option surface alignment¶
Purpose:
Keep winner-reuse APIs aligned with the AOM/moment facade inventories so a selected PLS candidate can be reused with the same public CPU/CUDA option names exposed by screen/refit methods.
Changes:
Added
cuda_pls_parallel_foldsandcuda_pls_many_batchedton4m.aom_chain_fixed_fit_run.Added
cuda_pls_parallel_foldsandcuda_pls_many_batchedtoNativeAOMFixedCandidateRegressor, storing them and forwarding them both to final-only fixed fits and tofit_mode="cv"replay.The already-present
cuda_pls_min_device_featuresremains forwarded.No C ABI change. Final-only fits currently consume the threshold knob on the native PLS component path; fold/many-batch knobs are API-symmetric and matter when the wrapper replays the exact-CV path.
Validation:
Targeted pytest:
test_native_aom_chain_fixed_fit_run_matches_single_candidate_final_fitand the AOM/moment facade inventory tests.Full dev-release
test_moment_model_wrappers.py:80 passed.Python
py_compileon touched modules/tests andgit diff --checkpassed.
2026-06-06 - AOM exact PLS batch partial-failure guard¶
Purpose:
Keep broad strict-moment AOM PLS screens usable when a degenerate late component fails but smaller component prefixes are still scoreable.
Changes:
score_pls1_moment_sweeps_score_onlystill uses the fast batched prefix path when it succeeds.If that global batched prefix fit fails, it now clears the transient error and retries via per-chain/fold/component moment fits. Scoreable component candidates keep finite exact-CV scores; failed component candidates are marked with
inf.A chain whose all component candidates fail now returns all-
infcandidates instead of aborting the whole batch. The higher-level AOM screen still returnsN4M_ERR_NUMERICAL_FAILUREif no finite candidate exists anywhere.The fallback remains moment-only and does not materialize transformed
X; successful fast-path runs keep the existingn_pls_moment_score_batch_*counters.Added
test_aom_pls_moment_batch_degenerate_components_do_not_abort_screen.
Validation:
Rebuilt
build/dev-releaseandbuild/cuda-onn4m_c.Manual CPU/CUDA reproduction on a rank-deficient identity-chain AOM PLS screen now returns finite component-1 scores,
infcomponent-2 scores,n_materialized_candidates=0,n_pls_materialized_cv_fits=0and moment CV fit counters.Targeted dev pytest:
test_aom_pls_moment_batch_degenerate_components_do_not_abort_screen,test_pls_moment_fallback_builds_fold_designs_on_demandandtest_pls_cross_validate_reference_matches_pls_sweep.Targeted CUDA pytest on one GPU: same tests plus
many_batched_precedes.Full dev-release
test_moment_model_wrappers.py:80 passed.Python
py_compileon touched tests/modules andgit diff --checkpassed.
2026-06-06 - Rank-deficient PLS moment fallback guard¶
Purpose:
Make the PLS moment sweep robust when a numerically degenerate fold cannot use the exact moment-prefix route and must fall back to materialized PLS.
Changes:
Added a lazy
ensure_fold_designs()helper inrun_moment_sweep.The PLS fallback path now builds fold-local materialized designs on demand before reading
fold_designs[fold]. Previously, compatible PLS1 moment screens could skip early fold-design construction, then fail the moment route on a rank-deficient fold and dereference an empty vector during fallback.Added
test_pls_moment_fallback_builds_fold_designs_on_demand, covering the previously crashing tiny fixture withscore_only=True,score_only=Falseand the publicn4m.pls_cross_validate(..., score_only=True)wrapper.
Validation:
Rebuilt
build/dev-releaseandbuild/cuda-onn4m_c.Manual reproduction on the previous segfault fixture now returns candidate scores, with component 1 finite, component 2
inf,n_pls_moment_cv_fits=0and materialized fallback counters.Targeted dev pytest:
test_pls_moment_fallback_builds_fold_designs_on_demandandtest_pls_cross_validate_reference_matches_pls_sweep.Targeted CUDA pytest on one GPU:
test_pls_moment_fallback_builds_fold_designs_on_demand,test_pls_cross_validate_reference_matches_pls_sweepandmany_batched_precedes.Full dev-release
test_moment_model_wrappers.py:79 passed.Python
py_compileon touched tests/modules andgit diff --checkpassed.
2026-06-06 - PLS CV ABI reference surface¶
Purpose:
Provide the public C/Python entry point needed by the future fused/batched IKPLS-style PLS grinder with an exact single-matrix reference implementation.
Changes:
Added ABI 1.22.0 symbol
n4m_pls_cross_validate(ctx, cfg, X, Y, fold_ids, n_fold_ids, n_folds, component_grid, n_component_grid, out_result).Implemented it by validating obvious pointer/length errors, then delegating to
n4m_sweep_runwithheads_mask=PLS; candidate scores and CPU/CUDA route counters therefore match the existing exact PLS sweep path.Exposed the symbol through Python ctypes as
n4m.pls_cross_validateandn4m.moment.pls_cross_validate, added it to the moment facade inventory, classified it as catalog ABI infra rather than a production method, updated ABI snapshots and documented that the grouped/fused executor remains open.
Validation:
Rebuilt
build/dev-releaseandbuild/cuda-onn4m_c; both producedlibn4m.so.1.22.0.Targeted dev/CUDA Python equivalence tests:
test_pls_cross_validate_reference_matches_pls_sweepand the moment facade alias/inventory guard.Full dev-release
test_moment_model_wrappers.py:78 passed.Python
py_compileon touched modules/tests passed.Catalog checks:
validate.py --strict-abi,validate.py --check-references,split_legacy_methods.py --check,reconcile_abi.py --check,git diff --checkandscripts/bump_version.sh --check.
Follow-up:
The tiny rank-deficient PLS fallback crash found during this validation pass is fixed by the later “Rank-deficient PLS moment fallback guard” entry above.
2026-06-06 - Compact-wide audit10 benchmark follow-up¶
Purpose:
Use the new audit-rank diagnostics on a controlled 10-row real-cohort benchmark instead of adding more features.
Run:
Ran
run_aom_staged_real_cohort.pyon one GPU withplan=compact_wide,heads=ridge,pls,max_chains=12,chain_chunk_size=6,top_k=12,refit_top_k=6,refit_per_head_top_k=2,scale_x_grid=false,true,split_head_scoring=auto,cuda_pls_min_device_features=1,cuda_pls_parallel_folds,backend_min_cuda_product=1andmax_features=1200.The first 10 cohort rows were run in two chunks and merged into
benchmarks/cross_binding/aom_staged_real_cohort_compact_wide_audit10_20260606.csv.
Artifacts:
benchmarks/cross_binding/aom_staged_real_cohort_compact_wide_audit10_20260606.csvbenchmarks/cross_binding/aom_staged_compact_wide_audit10_20260606_rank_audit.csvbenchmarks/cross_binding/aom_staged_compact_wide_audit10_20260606_rank_audit.mdbenchmarks/cross_binding/aom_staged_real_cohort_compact_wide_audit10_20260606_oracle_compare.csvbenchmarks/cross_binding/aom_staged_compact_wide_audit10_20260606_oracle_summary.mdbenchmarks/cross_binding/aom_staged_compact_wide_audit10_vs_compact_20260606.csvbenchmarks/cross_binding/aom_staged_compact_wide_audit10_vs_compact_20260606.mdbenchmarks/cross_binding/aom_staged_compact_wide_audit10_20260606_impact_summary.csvbenchmarks/cross_binding/aom_staged_compact_wide_audit10_20260606_impact_summary.md
Results:
10 rows total: 8 OK, 2 property-skipped (
BERRYandFUSARIUM, bothn_features>1200), all withselection_uses_test_set=False.All 8 OK rows selected
ridgeandselected_campaign_stage=compact; the extra wide stage did not win under this retained/refit budget.PLS routing stayed on CUDA: screen PLS moment CV fits
1920/0/1920total/host/device; refit PLS moment CV fits220/0/220. Split-head counters were64split chunks and128score calls; Ridge screen counters were2160/24/2160.Timing: total OK fit time
1562.03s, median OK fit time155.04s.Versus the compact split-head-auto baseline: 8 paired, 0 wins, 0 losses, 8 ties, median ratio
1.Oracle comparison: AOM-PLS oracle median ratio
1.03079(1/7 target wins), AOM-Ridge oracle median ratio1.08068(0/8 target wins), TabPFN oracle median ratio0.978527(4/8 target wins).Rank audit: median test-rank delta
2.5, max5; median oracle-gap ratio0.02354, max0.57732on ECOSIS; median CV/test Spearman0.64286, min-0.73810on ECOSIS.summarize_aom_rank_audit.pynow emits selected/oracle head, parameter, preprocessing-chain labels and audit top-1/top-3/top-5 recall columns. The ECOSIS failure is visible without opening raw JSON: selectedridge:0.1 savgol_smooth(7,2)versus offline oraclepls:1 detrend_poly(2), oracle CV-rank7, selected test-rank5, and top-1/top-3 recall0/0.Impact summary:
detrend_polyis the top operator by dataset wins (5/7), identity follows (2/5), SavGol smooth has one win (1/8), and derivatives did not win under this profile.
Interpretation:
This does not improve over compact;
compact_widecurrently adds cost without selected-score gain on the 8 OK datasets.The useful new signal is the rank-audit failure mode: ECOSIS has poor CV/test rank alignment and a large offline oracle gap even though the selected production model remains train-CV-only. That points to improving screen/refit recall or validation stability rather than expanding wide-stage chains blindly.
ECOSIS retention stress test:
Reran only ECOSIS with the same
compact_widescreen but wider retained refit budget:top_k=60,refit_top_k=40,refit_per_head_top_k=10.Artifact:
benchmarks/cross_binding/aom_staged_real_cohort_ecosis_compact_wide_refit40_audit_20260606.csvplus diagnostics dirbenchmarks/cross_binding/aom_staged_ecosis_compact_wide_refit40_audit_20260606/.It retained/refit
44candidates. Production selection stayed the same as audit10:ridge:0.1 savgol_smooth(7,2),RMSEP=41.3984.The offline audit oracle changed to an even better retained candidate:
ridge:10 finite_difference(1),eval_rmse=10.5817,cv_rank=11; the selected production candidate was test-rank41/44, with top-1/top-3/top-5 recall0/0/0and CV/test Spearman0.0667.Offline oracle comparison for this one row: AOM-Ridge oracle paired ratio
2.94852; TabPFN paired ratio0.691148(target wins vs TabPFN on this row).Interpretation: increasing retained/refit budget discovers much better held-out candidates but does not change production selection, so this is a train-CV ranking/stability problem rather than a pure screen-recall problem.
2026-06-06 - Audit/test-rank diagnostics persistence and rank-audit summarizer¶
Changes:
run_aom_staged_real_cohort.py:diagnostics_payload()now includes a compactauditsection in the per-dataset diagnostics JSON whenreport['audit']is present. The compact payload includesaudit_only=True,n_candidates,selected_cv(train-CV selected candidate’s test-set performance, predictions stripped),oracle(test-rank best candidate, predictions stripped), andaudit_rank_diagnostics(CV-vs-test Spearman correlation and related stats). Existing fields are preserved unchanged.selection_uses_test_setremainsFalse; audit data is offline only. New helpers:_compact_candidate_row()(strips prediction arrays) and_compact_audit_payload()(builds the compact audit dict).Added
benchmarks/cross_binding/summarize_aom_rank_audit.py: reads a directory (or glob) of*.diagnostics.jsonfiles with audit payloads and writes a CSV comparing the production (train-CV) selected candidate’s CV rank/score vs its test rank/score vs the oracle (best-by-test-rank) candidate. Also writes an optional Markdown table. Files without anauditsection (pre-feature diagnostics) are counted and reported but not written. The script is explicitly marked as offline audit only; it never changes production selection and does not route by dataset name.Tests:
test_aom_benchmark_tools.pynow assertsauditin diagnostics JSON (existingtest_real_cohort_runner_writes_route_counters_and_diagnostics), addstest_real_cohort_runner_diagnostics_compact_audit_payloadverifying audit compaction, prediction stripping andselection_uses_test_set=False, and addstest_rank_audit_summarizer_reads_diagnostics_and_writes_summaryexercising the new summarizer with two-row audit vs one no-audit file.
Limitation: existing compact10 diagnostics artifacts (generated before this
patch) do not contain an audit section. The summarizer will count them as
pre-feature files and skip them in the output CSV. Rerun
run_aom_staged_real_cohort.py --diagnostics-dir on those datasets to
regenerate diagnostics with the audit payload.
2026-06-06 - Real-cohort split-head scoring audit path¶
Decision:
Expose the existing score-preserving mixed-head split route in the real-cohort staged runner. Broad Ridge+PLS preprocessing screens should be able to use the head-homogeneous Ridge and PLS fast paths without changing candidate scores or selection policy.
Changes:
Added
--split-head-scoring {auto,off,force}tobenchmarks/cross_binding/run_aom_staged_real_cohort.py.The runner default is now
auto, whileoffkeeps the legacy single native call per mixed chunk for timing comparisons.The output CSV records
split_head_scoring,n_screen_split_head_chunksandn_screen_chunk_score_calls.n4m.aom_staged_chain_campaignnow aggregates those two screen counters across stages, and each stage summary records its local values.The staged report and stage summaries also expose Ridge screen counters (
n_ridge_moment_cv_fits,n_ridge_moment_score_batch_calls,n_ridge_moment_score_batch_jobs) so the real-cohort diagnostics payload no longer lists keys that are absent from staged reports.Model-config grids (
scale_x_values) now sum split-head and Ridge screen counters across all evaluated configs. Before this fix, those counters came from the selected config only while PLS counters were already aggregated.NativeAOMStagedChainCampaignRegressor.get_diagnostics()exposes the same top-level screen counters for sklearn users.NativeAOMStagedChainCampaignRegressor,NativeAOMSavgolFocusRegressorandNativeAOMStrictFamilyLiteRegressordefault tosplit_head_scoring="auto"for reusable mixed-head sklearn use; the lower-level staged helper keepsoffas the explicit timing-compatible default.Regenerated
benchmarks/cross_binding/aom_moment_cuda_facade_smoke.jsonwith a newstaged_mixed_default_estimatorsection. It proves the reusable sklearn default on one GPU:split_head_scoring="auto",n_screen_split_head_chunks=1,n_screen_chunk_score_calls=2, PLS screen CUDA/host CV fits8/0, Ridge screen counters8/1/8, andselection_uses_test_set=False.compare_aom_staged_variants.pyincludessplit_head_scoringin its configuration key soautoandoffruns are not grouped together.
Validation:
py_compileon touched Python modules/tests: pass.pytest bindings/python/tests/test_aom_benchmark_tools.py bindings/python/tests/test_aom_staged_campaign.py -q:33 passed.Full targeted benchmark-tool/catalog/CUDA-artifact/facade/wrapper/staged pytest suite:
154 passed.Catalog split, reference coverage and strict-ABI validation: pass.
CUDA facade smoke artifact test passed after the JSON regeneration.
Synthetic score-preserving smoke:
aom_chain_score_campaignwithsplit_head_scoring="off"vs"auto"produced identical top candidates on a fixed mixed Ridge+PLS campaign;autoreported 2 split chunks and 4 native screen calls versus 2 calls foroff.Synthetic staged smoke:
aom_staged_chain_campaign(..., split_head_scoring="auto")reportedn_screen_split_head_chunks=2,n_screen_chunk_score_calls=4andselection_uses_test_set=False.One-row real-cohort CUDA CLI smoke:
/tmp/n4m_aom_staged_split_head_auto_smoke.csvand/tmp/n4m_aom_staged_split_head_auto_diag/; BEEFMARBLING completed withsplit_head_scoring=auto,n_screen_split_head_chunks=2,n_screen_chunk_score_calls=4, PLS screen CUDA/host12/0, andselection_uses_test_set=False.Compact10 one-GPU follow-up:
benchmarks/cross_binding/aom_staged_real_cohort_compact10_split_head_auto_20260606.csvplus diagnostics dirbenchmarks/cross_binding/aom_staged_compact10_split_head_auto_20260606/. It produced 8 OK rows and 2 property-skipped rows; all OK rows hadselection_uses_test_set=False,split_head_scoring=auto, and selected the Ridge head. Againstaom_staged_real_cohort_compact10_mixed_diag_20260606.csv, the paired score comparison was 0 wins, 0 losses, 8 ties with median ratio1, confirming the split route is score-preserving on the real compact profile. Route counters stayed clean: screen PLS CUDA/host960/0, refit PLS CUDA/host220/0; screen split counters were 32 split chunks and 64 score calls across 8 OK rows after summing bothscale_xconfigs. Oracle ratios matched the compact mixed profile: AOM-PLS median1.03079(1/7 target wins), AOM-Ridge median1.08068(0/8), TabPFN median0.978527(4/8). The CSV now records Ridge screen-counter columns; total OK Ridge moment/batch counters were1080/12/1080. The diagnostics were regenerated after adding the Ridge and config-grid screen counters; BEEFMARBLING records Ridge moment/batch counters360/4/360.
2026-06-06 - Focused preprocessing diagnostic campaign¶
Decision:
Run a short follow-up campaign from the compact10 diagnostics instead of widening the whole cartesian again. The tested stages focus on identity,
detrend_poly(1/2), the observeddetrend_poly -> norris_williamsbranch, and a SavGol-variety branch with six smooth windows plus three derivatives.
Setup:
One GPU (
CUDA_VISIBLE_DEVICES=0) throughbuild/cuda-on,--limit 10,--max-features 1200, Ridge+PLS heads,--scale-x-grid false,true,--cuda-pls-parallel-foldsand--cuda-pls-min-device-features 1.Diagnostics output:
benchmarks/cross_binding/aom_staged_compact10_diag_focused_20260606/.Result CSV:
benchmarks/cross_binding/aom_staged_real_cohort_compact10_diag_focused_20260606.csv.Oracle comparison:
benchmarks/cross_binding/aom_staged_real_cohort_compact10_diag_focused_20260606_oracle_compare.csv.Focused-vs-compact comparison:
benchmarks/cross_binding/aom_staged_compact10_diag_focused_vs_compact_20260606.md.Impact summary:
benchmarks/cross_binding/aom_staged_compact10_diag_focused_20260606_impact_summary.md.
Results:
8 OK rows and 2 property-skipped rows (
n_features>1200), all withselection_uses_test_set=False.The selected production head was still Ridge on all 8 OK rows.
Selected stages:
identity_detrend_norrison 5/8 andsavgol_varietyon 3/8;scale_x=Trueon 7/8.PLS routing stayed on GPU for the campaign: screen CUDA/host
1280/0, refit CUDA/host215/0.Versus compact mixed diagnostics on the same 8 rows: 2 wins, 4 losses, 2 ties and median ratio
1.00125; median fit time was8.55s.Against local oracles: AOM-PLS oracle median ratio
1.05178(1/7 target wins), AOM-Ridge oracle median ratio1.09528(1/8 target wins), TabPFN oracle median ratio0.992611(4/8 target wins).
Interpretation:
This focused branch confirms that SavGol diversity can be selected on real rows without test-set routing, but it did not improve the compact baseline in aggregate. The practical ceiling remains the linear Ridge-dominated selector, not the lack of a specific SavGol variant in the small profile.
2026-06-06 - Real-cohort staged diagnostics output¶
Decision:
Keep
run_aom_staged_real_cohort.pyas the controlled real-dataset campaign runner, but make it able to persist the post-hoc preprocessing impact data needed for incremental family/option selection studies.
Changes:
Added
--diagnostics-dir DIRtobenchmarks/cross_binding/run_aom_staged_real_cohort.py.Default behavior is unchanged. When the flag is omitted, the runner only writes the existing result CSV.
For each
okrow with diagnostics enabled, the runner writes<safe_dataset_key>.diagnostics.jsoncontaining dataset identity for audit,selection_uses_test_set, plan/head/scale metadata,best,impact,rank_diagnostics, selected/model-config summaries and route/counter fields.The runner also appends impact group rows to
impact_groups.csv, withgroup_kindcoveringby_operator,by_stage_family,by_stage_optionandby_head_stage_option. This gives a compact cross-dataset table for ranking preprocessing families/options without selecting by dataset name.Added
benchmarks/cross_binding/summarize_aom_impact_groups.py, an offline summarizer forimpact_groups.csvthat emits aggregate CSV/Markdown ranked by dataset wins, rank-1 occurrences and train-CV rank.
Validation:
py_compileonrun_aom_staged_real_cohort.pyandtest_aom_benchmark_tools.py: pass.pytest bindings/python/tests/test_aom_benchmark_tools.py -q -k real_cohort_runner:5 passed, 11 deselected.
Follow-up diagnostic campaign:
Reran the compact mixed Ridge+PLS 10-row real-cohort profile with
--diagnostics-dir benchmarks/cross_binding/aom_staged_compact10_mixed_diag_20260606.Result CSV:
benchmarks/cross_binding/aom_staged_real_cohort_compact10_mixed_diag_20260606.csv.Diagnostics: 8 JSON files and
impact_groups.csvwith 169 impact-group rows.All OK rows kept
selection_uses_test_set=False; PLS route counters remained screen CUDA/host960/0and refit CUDA/host220/0.Summary artifact:
benchmarks/cross_binding/aom_staged_compact10_mixed_diag_20260606_summary.md.Automated impact summary artifacts:
benchmarks/cross_binding/aom_staged_compact10_mixed_diag_20260606_impact_summary.csvandbenchmarks/cross_binding/aom_staged_compact10_mixed_diag_20260606_impact_summary.md.Main signal: best train-CV impact groups were
detrend_polyon 5/8 datasets,identityon 2/8 andsavgol_smoothon 1/8. The selected production head was still Ridge for all 8 OK rows.
2026-06-06 - Compact10 staged CUDA benchmark against local oracles¶
Decision:
Stop adding wrappers for this pass and run the handoff-recommended controlled campaign on a small real cohort to separate Ridge, PLS and mixed-head behavior.
Setup:
Ran the real-cohort staged campaign on one GPU with
CUDA_VISIBLE_DEVICES=0,N4M_LIB_PATH=build/cuda-on/cpp/src/libn4m.so, default diverse11 cohort,--limit 10,--max-features 1200,--plan compact,--max-chains 12,--top-k 12,--refit-top-k 6,--refit-per-head-top-k 2,--scale-x-grid false,true,--cuda-pls-parallel-foldsand--cuda-pls-min-device-features 1.Ran three variants: mixed Ridge+PLS, Ridge-only and PLS-only.
Compared each result file to the local AOM-PLS oracle, AOM-Ridge oracle and TabPFN oracle with
compare_aom_staged_to_oracles.py.
Results:
Each run produced 8 OK rows and 2 property-skipped rows (
n_features>1200), withselection_uses_test_set=Falseon every OK row.Mixed selected
ridgeon all OK rows and matched Ridge-only RMSEP exactly.Mixed/Ridge oracle ratios: AOM-PLS paired median
1.03079with 1 target win out of 7, AOM-Ridge paired median1.08068with 0 wins out of 8, TabPFN paired median0.978527with 4 wins out of 8.PLS-only oracle ratios: AOM-PLS paired median
1.22592with 0 wins out of 7, AOM-Ridge paired median1.21773with 0 wins out of 8, TabPFN paired median1.27884with 1 win out of 8.PLS CUDA route counters were clean: mixed screen/refit PLS CUDA/host
960/0and220/0; PLS-only960/0and450/0.
Artifacts:
benchmarks/cross_binding/aom_staged_real_cohort_compact10_cuda_20260606.csvbenchmarks/cross_binding/aom_staged_real_cohort_compact10_ridge_cuda_20260606.csvbenchmarks/cross_binding/aom_staged_real_cohort_compact10_pls_cuda_20260606.csvbenchmarks/cross_binding/aom_staged_real_cohort_compact10_mixed_cuda_20260606_oracle_compare.csvbenchmarks/cross_binding/aom_staged_real_cohort_compact10_ridge_cuda_20260606_oracle_compare.csvbenchmarks/cross_binding/aom_staged_real_cohort_compact10_pls_cuda_20260606_oracle_compare.csvbenchmarks/cross_binding/aom_staged_compact10_cuda_20260606_summary.md
Interpretation:
For this compact budget, the production mixed selector is not adding diversity beyond Ridge; it chooses Ridge everywhere.
PLS-only is weaker overall, but the ECOSIS Chla+b split is a concrete case where PLS-only beats Ridge materially, so per-family/per-dataset-property audit remains useful.
This benchmark supports the current handoff interpretation: method wiring is no longer the bottleneck; the open gap is either stronger budget/selection policy within train-CV constraints or true fused/batched engines.
2026-06-06 - Robust-HPO native sklearn CUDA smoke artifact¶
Decision:
Extend the committed
aom_robust_hpo_timing_cuda_smoke.csvartifact to cover both the native ABI path (native_abi) and theNativeAOMRobustHPORegressorsklearn wrapper replay path (native_sklearn), replacing the old native-only 3-row artifact.
Changes:
Updated
bench_aom_robust_hpo_timing.pyso its sklearn backend uses the reusable nativeNativeAOMRobustHPORegressorwrapper instead of the older Python portfolio wrapper path. The row builder now recordsprediction_replay_max_abs_errorby comparingmodel.predict(X)against native fitted predictions.Regenerated
benchmarks/cross_binding/aom_robust_hpo_timing_cuda_smoke.csvwithout--native-onlyon one GPU (CUDA_VISIBLE_DEVICES=0,N4M_LIB_PATH=build/cuda-on/cpp/src/libn4m.so). The new CSV has 6 rows: 3 shapes x 2 backends (native_abi+native_sklearn),profile=compact, all pointing tobuild/cuda-on/cpp/src/libn4m.so.prediction_replay_max_abs_errorfornative_sklearnrows: <= 4.5e-15 (machine epsilon level), well within the<= 1e-10gate.Updated
benchmarks/cross_binding/README.md: new “AOM Robust-HPO Timing Smoke” section with the exact CUDA smoke command (no--native-only) and expected 6-row layout.Updated
docs/architecture/aom_moment_coverage_matrix.mdrobust-HPO row: added explicit mention of thenative_abi+native_sklearnCUDA artifact and theprediction_replay_max_abs_error <= 1e-10gate.
Validation:
test_aom_moment_cuda_smoke_artifacts.py -k robust_hpo:1 passed.Targeted benchmark-tool/catalog/CUDA-artifact/facade/wrapper/staged pytest set:
152 passed.Catalog split, reference validation and strict ABI validation pass.
git diff --check: no whitespace issues.
2026-06-06 — Strict-family lite reusable audit preset¶
Decision:
Keep
strict_family_focusas the broad family-audit staged recipe, but do not present it as a fast default. Add a small-budget preset for reusable end-user audits across SavGol, Norris-Williams, finite-difference, Gaussian, FCK and Whittaker stages.
Changes:
Added
NativeAOMStrictFamilyLiteRegressor, a subclass preset overNativeAOMStagedChainCampaignRegressor.The preset fixes
plan="strict_family_focus"and defaults tomax_chains=2,top_k=6,refit_top_k=4,refit_per_head_top_k=1,scale_x=Falseand noscale_x_valuesgrid.Exported the preset from
n4m,n4m.sklearn,n4m.aomandn4m.moment.Added facade inventory rows as
preset_sklearn_wrapperoveraom_pop.aom_staged_chain_campaign, with the same no-plan/no-stages option surface as the SavGol-focused preset.
Validation:
Added a synthetic fit/replay test that verifies train-CV-only selection, stage family coverage, no dataset/source/id stage metadata, and selected final-model replay.
One-GPU CUDA facade smoke now covers the preset through
build/cuda-on. The regenerated JSON reportsstrict_family_lite_estimatorwith screen PLS CUDA CV fits36, host0, refit PLS CUDA CV fits4, host0, andselection_uses_test_set=False.bindings/python/tests/test_aom_staged_campaign.py:15 passed.bindings/python/tests/test_aom_moment_facade.py:13 passed.bindings/python/tests/test_aom_moment_facade.pynow also guards everycatalog_role="preset_sklearn_wrapper"row: the advertised object must be the sklearn class, must wrap a catalog binding, must expose apreset_plan, and must not exposeplan,stages,familiesortemplates.The same facade test now guards that all AOM/moment facade entries are shared with the top-level
n4mpackage, and that every relevant cataloguedn4m.pythonbinding underaom_pop,utilities, direct PLS/regularized/specialized heads andmoment_stackis exposed by at least one facade.bindings/python/tests/test_aom_moment_cuda_smoke_artifacts.py -q -k facade:1 passed, 20 deselected.Targeted benchmark-tool/catalog/CUDA-artifact/facade/wrapper/staged pytest set:
152 passed.Catalog split, reference validation and strict ABI validation pass.
2026-06-06 — SavGol-focus reusable sklearn preset¶
Decision:
Promote the locally useful
savgol_focusstaged campaign from “plan value” to an end-user reusable method surface. This keeps the global staged campaign ultra-configurable while giving users a preconfigured winner-like estimator that can be fit/predicted directly.
Changes:
Added
NativeAOMSavgolFocusRegressor, a subclass preset overNativeAOMStagedChainCampaignRegressor.Defaults match the validated fast campaign recipe:
plan="savgol_focus",max_chains=6,top_k=10,refit_top_k=8,refit_per_head_top_k=2,ridge_lambdas=(0.1, 1.0, 10.0),pls_components=(1, 2),scale_x_values=(False, True), and the one-GPU PLS route knobs used in the benchmark.Exported the preset from
n4m,n4m.sklearn,n4m.aomandn4m.moment.Added facade inventory rows as
preset_sklearn_wrapperoveraom_pop.aom_staged_chain_campaign; the preset advertises only the options it actually accepts and does not exposeplan,stages,familiesortemplates.Updated staged campaign docs, coverage matrix and catalog notes.
Validation:
py_compilepassed for the touched Python modules.bindings/python/tests/test_aom_staged_campaign.py:14 passed; the new test fits the preset on a tiny synthetic dataset, verifies train-CV-only selection, selectedscale_x, stage names and final-model replay.benchmarks/cross_binding/aom_moment_cuda_facade_smoke.pynow coversNativeAOMSavgolFocusRegressorthrough thebuild/cuda-onshared library. The regenerated JSON reportssavgol_focus_estimatorwith screen PLS CUDA CV fits64, host0, refit PLS CUDA CV fits8, host0, andselection_uses_test_set=False.test_aom_moment_cuda_smoke_artifacts.py -k facade: passed.
2026-06-06 — Focused strict-family staged campaign plans¶
Decision:
Stop treating wider
compact_wide/labprofiles as the only way to test preprocessing diversity. With a smallmax_chains, profile order can screen many SavGol variants before ever reaching Gaussian, FCK or Whittaker. Add fixed source-free stage recipes that put target strict-linear families first.
Changes:
Added
plan="savgol_focus"ton4m.aom_staged_chain_campaign. It runs compact, SavGol smooth, SavGol derivative and SavGol-combination stages over the existing strict-linear lab families.Added
plan="strict_family_focus". It runs compact plus separate SavGol, Norris-Williams, finite-difference, Gaussian, FCK, Whittaker and strict-combination stages so a small per-stagemax_chainsreaches each family.Existing plan names and explicit
stages=[...]behavior are unchanged. The focused plans are deterministic stage labels only; they do not read dataset, source, id or name metadata and production selection remains train exact-CV.Updated staged campaign docs, benchmark README and catalog notes.
Validation:
py_compilepassed forbindings/python/src/n4m/python.py,bindings/python/tests/test_aom_staged_campaign.pyandbenchmarks/cross_binding/run_aom_staged_real_cohort.py.test_aom_staged_campaign.py:13 passed.One-GPU CLI smoke on one real cohort row with
plan=strict_family_focuspassed. It wrote 9 focused stage checkpoints, selectedstrict_combinations, keptselection_uses_test_set=False, and routed PLS screen/refit CV through CUDA with host counters at0.catalog/scripts/validate.py --check-references: PASS, 208/208.catalog/scripts/validate.py --strict-abi: PASS, 701/701.git diff --check: exit 0, with only known CRLF warnings on existing CSV artifacts.
Benchmark evidence:
savgol_focus --max-chains 6 --scale-x-grid false,trueon the local diverse-10 cohort with--max-features 1200:8 OK rows, 2 property-skipped rows;
selected
scale_x=Trueon 7/8 OK rows;selected stages: compact, SavGol smooth, SavGol derivative and SavGol-combinations;
versus compact scale-grid on the 8 paired rows: 5 wins, 2 losses, 1 tie, median paired ratio
0.995259, mean ratio0.996638;versus AOM-PLS oracle: paired median ratio
1.03051, target wins1;versus AOM-Ridge oracle: paired median ratio
1.07519, target wins1;versus TabPFN oracle: paired median ratio
0.971371, target wins4;median fit time
16.06s, screen PLS CUDA CV fits1920, host0, refit PLS CUDA CV fits245, host0.
strict_family_focus --max-chains 4 --scale-x-grid false,truewas stopped as a partial run after 5 OK rows and 2 skipped rows because MANURE stalled in the refit/finalization after all focused stage checkpoints were written. On the 5 paired rows it had 2 wins, 2 losses and 1 tie vs compact, but median fit time was already26.63s; treat this as a heavier family audit, not the fast default campaign.
2026-06-06 — Staged campaign model-config scale_x grid¶
Decision:
Treat feature scaling as a train-CV-selected model/preprocessing config in the staged campaign. The compact 10-dataset calibration showed that forcing
scale_x=Falseglobally was too weak, while forcingscale_x=Trueglobally hurt some datasets. The correct production rule is to select the config by exact train CV, not by held-out/test score.
Changes:
Added
scale_x_valueston4m.aom_staged_chain_campaign.When provided, the campaign runs one normal staged sub-campaign per value, scopes checkpoints under
scale_x_<value>, selects the sub-campaign with the lowestbest.refit_cv_rmse, and returns the selected rows/best/audit while aggregating route counters across every config evaluated.The report now records
model_config_grid,model_config_summaries,selected_model_config_id,selected_model_config,scale_x_valuesand the selectedscale_x.NativeAOMStagedChainCampaignRegressoracceptsscale_x_valuesand fits the final reusable candidate with the selectedscale_x.run_aom_staged_real_cohort.pyaccepts--scale-x-grid false,trueand writesscale_x,scale_x_valuesandselected_model_config_idcolumns.
Benchmark evidence:
Compact 10-dataset CUDA run without scaling:
AOM-PLS paired median ratio
1.17183, target wins0.AOM-Ridge paired median ratio
1.22661, target wins0.TabPFN paired median ratio
1.2894, target wins1.
Compact 10-dataset CUDA run with forced
scale_x=True:AOM-PLS paired median ratio
1.03079, target wins1.AOM-Ridge paired median ratio
1.0931, target wins0.TabPFN paired median ratio
1.05956, target wins4.
Compact 10-dataset CUDA run with
scale_x_values=[False, True], selected by train CV:selected
scale_x=Trueon 8/10 rows andFalseon 2/10 rows;AOM-PLS paired median ratio
1.03079, target wins1;AOM-Ridge paired median ratio
1.05918, target wins0;TabPFN paired median ratio
1.05956, target wins4;PLS screen/refit stayed on one CUDA build route: screen CUDA CV fits
1200, host0; refit CUDA CV fits280, host0.
Validation:
CLI smoke with
--scale-x-grid false,trueon one dataset passed and wrote selected config / CUDA route columns.Targeted benchmark/catalog/CUDA-artifact/facade/wrapper/staged pytest suite:
143 passed.catalog/scripts/validate.py --check-references: PASS, 208/208 production methods covered.catalog/scripts/validate.py --strict-abi: PASS, ABI coverage701/701.catalog/scripts/split_legacy_methods.py --check: PASS.git diff --check: exit 0, with only known CRLF warnings on existing CSV artifacts.
Remaining interpretation:
This closes a real selection gap without using dataset identity or test-set selection. It still does not include SNV, MSC, EMSC, OSC or ASLS-style row/reference-dependent preprocessing, which explains several remaining gaps versus historical AOM-Ridge oracle rows.
A follow-up
compact_widescale-grid run with a source-free--max-features 1200property filter produced 8 OK rows and 2 skipped rows. On the 8 rows paired with the compact scale-grid baseline it had 2 wins, 1 loss and 5 ties, with median paired ratio1.0and mean paired ratio0.994179; the oracle summary was AOM-PLS median ratio1.03051, AOM-Ridge1.08068, TabPFN0.968453. It paid more screen work (2880CUDA PLS CV fits, host0) for marginal score movement, so the next campaign should target preprocessing families/options rather than blindly increasing the stage width.
2026-06-06 — Strict single-chain AOM Ridge-PLS selector¶
Decision:
Add the strict/raw-base donor SingleChainRidgePLS-style surface without widening the port to non-moment preprocessing or nonlinear routes. This gives users a reusable single-chain Ridge-PLS model alongside the existing superblock and campaign methods.
Changes:
Added
n4m.aom_chain_ridge_pls, exposed through top-leveln4m,n4m.aomandn4m.moment.Added
NativeAOMChainRidgePLSRegressorinn4m.sklearn.Catalogued the method as
aom_pop.aom_chain_ridge_plswith docs and timing smoke coverage.The function scores
(chain, n_components, ridge_lambda)by train-only CV, applies strict-linear AOM chains sequentially, fits the selected final model through nativeridge_pls, and folds the selected chain coefficients back to rawinput_coefficientsplusintercept.Candidate OOF predictions are tracked only for the current best candidate instead of storing an
n_candidates x n_samples x n_targetsbuffer; the full score table remains available for ranking/audit.Added a one-GPU CUDA-build smoke artifact,
benchmarks/cross_binding/aom_chain_ridge_pls_timing_cuda_smoke.csv, and extended the CUDA artifact guard to require function + sklearn replay rows.
Validation:
CPU timing smoke wrote 6 rows to
/tmp/aom_chain_ridge_pls_timing.csv.CUDA timing smoke wrote 6 rows to
benchmarks/cross_binding/aom_chain_ridge_pls_timing_cuda_smoke.csvwithselection_mode=chain_ridge_pls,ridge_pls_backend=native,library_path=build/cuda-on/cpp/src/libn4m.so, and replay error0.0.Targeted benchmark/catalog/CUDA-artifact/facade/wrapper/staged pytest suite:
141 passed.catalog/scripts/validate.py --check-references: PASS, 208/208 production methods covered.catalog/scripts/validate.py --strict-abi: PASS, ABI coverage701/701.catalog/scripts/split_legacy_methods.py --check: PASS.git diff --check: exit 0, with only known CRLF warnings on existing CSV artifacts.
Scope note:
This method intentionally excludes SNV, MSC, EMSC, OSC, row-reference-dependent preprocessing, nonlinear lifts, kernels, trees, TabPFN residuals and dataset/source routing. CUDA-build smoke proves build compatibility and replay, not a fused many-chain GPU Ridge-PLS grinder.
2026-06-05 — Direct AOM preprocessing strict-linear bank¶
Decision:
Reuse the existing AOM strict-linear operator engine for standalone
n4m.aom_preprocess, so the reusable primitive covers the same direct strict-linear families used by compact/wide AOM banks instead of only the subset accepted by the generic preprocessing pipeline.
Changes:
Switched
cpp/src/core/aom_preprocessing.cppfrom aPipelinefit/transform dispatch totransform_aom_strict_operator.Added a direct C ABI test,
aom_preprocess/direct_strict_linear_bank, covering the 9-operator bank, hard/soft weights, operator kind ids, and exact replay of identity plus finite-difference outputs.Extended
bench_aom_preprocess_timing.pyand the CUDA smoke artifact to direct single-operator identity, degree-1 detrend, Savitzky-Golay smooth/derivative, Norris-Williams, finite difference, Gaussian, Whittaker and FCK rows.Updated the CUDA artifact guard to require 54 rows (9 operators x 2 gating modes x 3 shapes), expected operator kind ids and exact single-operator replay against the native
operator_outputsbuffer.Updated docs to state that strict chains and model-scoring diversity remain in AOM sweep/campaign helpers; the direct API now covers the reusable strict-linear single-operator bank.
Validation:
Rebuilt both
dev-releaseandcuda-onCMake presets after the C++ dispatch change.py_compilepassed for the updated benchmark script and Python artifact tests.test_aom_moment_cuda_smoke_artifacts.py:14 passed.Targeted benchmark/catalog/CUDA-artifact/facade/wrapper/staged pytest suite:
108 passed.build/dev-release/cpp/tests/n4m_tests:351 passed, 0 failed.catalog/scripts/validate.py --strict-abi: PASS, 201 methods and 701/701 exportedn4m_*symbols covered.catalog/scripts/validate.py --check-references: PASS, 201/201 production methods covered.catalog/scripts/split_legacy_methods.py --check: PASS.
2026-06-05 — Linear PLS variants sklearn replay wrappers¶
Decision:
Promote
weighted_pls,robust_plsandridge_plsfrom ABI-close functions to reusable direct heads after provingX @ coefficients + (y_mean - x_mean @ coefficients)replays native training predictions at numerical-noise level.models.pls.kernelremains intentionally excluded as non-linear kernel PLS.
Changes:
Added
NativeWeightedPLSRegressor,NativeRobustPLSRegressorandNativeRidgePLSRegressorinn4m.sklearn, top-leveln4m, and then4m.momentfacade inventory.Updated
bench_direct_moment_heads_timing.pyto time those three heads as bothnative_functionandsklearn_fit_predictrows.Regenerated
direct_moment_heads_timing_cuda_smoke.csv; it now contains 54 rows (9 methods x 3 shapes x 2 backends), all withsurface_status=function_and_sklearn_replay.Updated direct-head docs, coverage matrix, benchmark README and handoff notes to remove stale function-only language.
Validation:
Replay proof before wrapper addition:
weighted_pls,robust_plsandridge_plsall replayed native train predictions via reconstructed intercept at numerical-noise level across several CPU shapes/options.Focused wrapper/artifact pytest set:
bindings/python/tests/test_moment_model_wrappers.py+bindings/python/tests/test_aom_moment_cuda_smoke_artifacts.py:71 passed.Targeted benchmark/catalog/CUDA-artifact/facade/wrapper/staged pytest suite:
107 passed.catalog/scripts/validate.py --strict-abi: PASS, 201 methods and 701/701 exportedn4m_*symbols covered.catalog/scripts/validate.py --check-references: PASS, 201/201 production methods covered.catalog/scripts/split_legacy_methods.py --check: PASS.git diff --check: exit 0, only the known CRLF warnings on existing CSV artifacts.
2026-06-05 — Linear PLS variants doc/coverage cleanup¶
Decision:
Document the three function-only linear PLS heads (
weighted_pls,robust_pls,ridge_pls) that are already wired and tested but were not reflected in the coverage matrix, README or handoff notes. Fix two doc inaccuracies found during the pass. No new ABI was added; the only Python logic change is the timing-smokesurface_statuscleanup.
Changes:
docs/architecture/aom_moment_coverage_matrix.md: added three rows for weighted PLS, robust PLS and ridge-augmented PLS heads; updated Direct PLS head description to note the smoke CSV covers 9 methods / 45 rows; addedkernel_plsexclusion invariant.benchmarks/cross_binding/README.md: updated “Direct Moment Heads CUDA Smoke” section to describe all 9 methods, 6 wrapper-backed vs 3 function-only split, and 45-row CSV layout.handoff.md: added “Linear PLS variants” section recording what is wired, tested and whykernel_plsremains excluded.benchmarks/cross_binding/bench_direct_moment_heads_timing.py: madesurface_statusmethod-level — wrapper-backednative_functionrows now reportfunction_and_sklearn_replay; function-only rows keepfunction_only.bindings/python/tests/test_aom_moment_cuda_smoke_artifacts.py: tightened the direct-head artifact guard so wrapper-backed methods must reportsurface_status=function_and_sklearn_replayon every row.Regenerated
direct_moment_heads_timing_cuda_smoke.csv(45 rows, CUDA ABI1.21.0) to reflect thesurface_statuschange.docs/methods/weighted_pls.md: removed the misleading “no global coefficient export” claim;n4m_weighted_pls_fitdoes exportcoefficients; the note now describes the weight-dependent centering constraint that prevents a naive replay wrapper.docs/methods/robust_pls.md: correctedmax_irls_iterdefault in parameter table from20to5(matches the Python helper signature).
Validation:
Targeted benchmark/catalog/CUDA-artifact/facade/wrapper/staged pytest suite:
104 passed.
2026-06-05 — Candidate preprocessing impact audit¶
Decision:
Finish the current post-hoc cartesian analysis task without expanding the engine work: summarize which preprocessing stages/options are helping from already-scored candidate reports.
Changes:
Added
n4m.aom_candidate_preprocessing_impactand exposed it throughn4m.aomandn4m.moment.The helper groups candidate rows by inferred stage, operator, concrete option, chain position and head/stage combinations.
It reports best/mean/median score, ranks and improvement versus identity baselines when identity rows are present.
Validation:
Covered by synthetic candidate-row tests and the facade inventory tests.
2026-06-05 — Moment-model OOF stack¶
Decision:
Add the missing strict moment-model stacking surface from
PORTING.mdwithout adding nonlinear features or a new native ABI.
Changes:
Added
NativeMomentStackRegressor, a sklearn-style train-only OOF Ridge stack over native Ridge, PLS sweep, PCR, continuum, ECR and CPPLS base predictions.Exposed the stack through
n4m.sklearn, top-leveln4mand then4m.momentinventory asmodels.ensembles.moment_stack.Added method docs, catalog metadata and a timing smoke benchmark.
Added fitted
base_oof_diagnostics_/base_final_diagnostics_and aggregate PLS route counters to make CPU/GPU routing auditable for the OOF base fits and final base refits.Extended
bench_moment_stack_timing.pywith custom shapes/base-models and CUDA PLS knobs, then generatedbenchmarks/cross_binding/moment_stack_timing_cuda_smoke.csv.
Validation:
Smoke-tested Ridge/PCR/PLS and full six-base stacks against the rebuilt
libn4m.CUDA smoke on one GPU with a PLS-only stack (
80x1024,cv=4,inner_cv=4,cuda_pls_min_device_features=1,cuda_pls_parallel_folds=True) reportedn_base_oof_pls_moment_cuda_device_cv_fits=16,n_base_oof_pls_moment_host_cv_fits=0,n_base_final_pls_moment_cuda_device_cv_fits=4, andn_base_final_pls_moment_host_cv_fits=0.
2026-06-05 — Direct PCR MethodResult head¶
Decision:
Add PCR through the same direct-head pattern as Ridge, CPPLS, continuum and ECR, instead of exposing it through an untyped generic-model ctypes shortcut.
Changes:
Added
n4m_pcr_fitto the C ABI, forcingAlgorithm.PCR+Solver.SVDon top of the caller’s centering, scaling and component-count config.Added
n4m.pcr,n4m.moment.pcrandNativePCRRegressorwith replayable input-space coefficients and MethodResult predictions.Catalogued PCR as
models.pls.pcrand registered the ABI symbol in the expected-symbol manifests.
Validation:
Rebuilt
n4m_c, ran the PCR direct/sklearn smoke, Python facade tests, catalog validation and strict ABI validation.
2026-06-05 — Deterministic streaming AOM chain grid¶
Decision:
Add the missing streaming bank-generator surface from the porting backlog without changing the native scoring ABI or candidate scores.
Changes:
Added
n4m.iter_aom_strict_chain_grid, with the same grid semantics asbuild_aom_strict_chain_gridplus stable ids,start/stopslicing, optionalchunk_size, andwith_ids.Exposed the helper through
n4m.aomandn4m.momentinventories as a non-catalog campaign/runtime helper.Documented the incremental launcher use case in
docs/methods/aom_chain_sweep_run.mdand the coverage matrix.
Validation:
Added tests that compare iterator output against the existing builder, cover id slicing/chunking, and verify facade inventory exposure.
2026-06-05 — Campaign-level CPU/CUDA launch threshold override¶
Decision:
Make the source-free CPU/CUDA launch recommendation fully configurable from AOM/moment campaigns and sklearn presets, not only from the standalone helper.
Changes:
Added
backend_min_cuda_producttoaom_chain_score_campaign,aom_chain_screen_refit_campaign, andaom_moment_screen_refit_campaign.Propagated the option to
NativeAOMScreenRefitRegressorand the mixed, PLS-only and Ridge-only moment screen-refit presets.Campaign reports now include
backend_min_cuda_product, andmoment_backend_recommendation_policy_inputsincludesmin_cuda_product.Updated
n4m.aom.available_methods()andn4m.moment.available_methods()config inventories.Documented the option in
docs/methods/aom_chain_sweep_run.md.Exposed
--backend-min-cuda-productinbench_aom_screen_refit_scaling.pyand the screen/refit rows ofbench_aom_sweep_timing.py.
Validation:
Added tests that force
backend_min_cuda_product=1in campaign and sklearn wrapper paths and verify the recommendation/report changes without changing selected scores.Added benchmark script smokes for the new CLI flag.
2026-06-05 — Conservative CPU/CUDA backend recommendation v4¶
Decision:
Align
moment_screen_backend_recommendationwith the latest one-GPU crossover evidence instead of recommending CUDA at the old260x256boundary.
Changes:
Raised the default launch crossover threshold from
260 * 256to512 * 512.Bumped the policy source string to
n4m.moment_gpu_crossover.v4.Kept
min_cuda_productas an explicit override for benchmark campaigns that want to test medium shapes.Updated tests and docs so
260x256is CPU by default, while512x512and256x1024remain CUDA candidates.
Validation:
Policy is based on
/tmp/moment_gpu_crossover_cuda_profiles.csv(repeats=1, one GPU, ABI 1.21.0): CUDA default was slower than CPU for PLS at260x256, but faster for PLS at512x512and256x1024; Ridge was effectively flat/slightly slower at260x256and faster at the larger shapes.
2026-06-05 — Moment GPU crossover can compare CUDA PLS profiles¶
Decision:
Measure the optional
cuda_pls_many_batchedroute against the default CUDA PLS moment route in one controlled run before attempting larger fused CUDA work.
Changes:
bench_moment_gpu_crossover.pynow supports--compare-cuda-pls-many-batched.The script runs the CPU baseline once, then CUDA default and CUDA many-batched profiles on identical synthetic shapes.
The CSV now includes
cuda_pls_profileandspeedup_vs_cuda_defaultin addition tospeedup_vs_cpu, so the experimental route can be judged directly.
Validation:
py_compilepassed forbench_moment_gpu_crossover.py.One-GPU smoke:
CUDA_VISIBLE_DEVICES=0 bench_moment_gpu_crossover.py --shapes 64x16 --cv 3 --repeats 1 --cuda-pls-min-device-features 1 --compare-cuda-pls-many-batchedwrote/tmp/moment_gpu_crossover_compare_smoke.csvagainst ABI 1.21.0 with CPU, CUDA default and CUDA many-batched rows.One-GPU comparison,
repeats=1,cuda_pls_min_device_features=1, shapes260x256,512x512,256x1024, wrote/tmp/moment_gpu_crossover_cuda_profiles.csv. On PLS rows, CUDA default was slower than CPU at260x256but faster at512x512and256x1024;many_batcheddid not beat CUDA default on those PLS shapes (speedup_vs_cuda_defaultabout0.99,0.88,0.88). This makes the default CUDA PLS moment route the better current campaign baseline.
2026-06-05 — PLS moment route documented as IKPLS-style, not missing¶
Decision:
Treat the current exact PLS1 moment route as an IKPLS-style sufficient-statistics implementation. The remaining open item is the fused many-chain/many-fold batching layer, not the existence of cross-product PLS1 scoring itself.
Changes:
Updated
cpp/src/core/sweep.hppcomments to describe exact PLS1 moment scoring and the remaining materialized fallback.Renamed the coverage-matrix gap from generic “Batched IKPLS” to “Fused/batched IKPLS” and clarified the current single-target PLS1 coverage.
Validation:
Documentation/comment-only change.
2026-06-05 — Backend recommendation reports many-batched PLS planning¶
Decision:
Keep backend routing source-free, but make the diagnostic reflect every PLS CUDA knob that campaigns can pass.
Changes:
n4m.moment_screen_backend_recommendation(...)now acceptscuda_pls_many_batched.AOM chain score campaign reports propagate the flag into
moment_backend_recommendations.n4m.moment.available_methods()advertises the new backend-recommendation option.docs/methods/sweep_run.mdanddocs/methods/aom_chain_sweep_run.mddocument the extra diagnostic field.
Validation:
py_compilepassed for the touched Python modules and benchmark.test_moment_model_wrappers.py: 49 passed against ABI 1.21.0.
2026-06-05 — Moment GPU crossover benchmark aligned with ABI 1.21¶
Decision:
Keep the crossover benchmark independent from a specific ABI patch version and make it measure the explicit CUDA PLS route that timing campaigns use.
Changes:
bench_moment_gpu_crossover.pynow defaults to the current build symlinks (build/dev-release/.../libn4m.soandbuild/cuda-on/.../libn4m.so) instead of hard-codinglibn4m.so.1.18.0.Added
--cuda-pls-min-device-featuresand--cuda-pls-many-batchedto the crossover script.The CSV now records those knobs plus PLS score-batch and CUDA-parallel-fold counters, so a timing row shows whether the intended exact-CV PLS moment route actually ran on device.
Validation:
py_compilepassed forbench_moment_gpu_crossover.py.One-GPU smoke:
CUDA_VISIBLE_DEVICES=0 bench_moment_gpu_crossover.py --shapes 64x16 --cv 3 --repeats 1 --cuda-pls-min-device-features 1 --cuda-pls-many-batchedwrote/tmp/moment_gpu_crossover_smoke_many.csvagainst ABI 1.21.0, with the CUDA PLS row reporting device CV fits.
2026-06-05 — CUDA PLS many-design batching config exposed¶
Decision:
Keep the experimental CUDA tiled/strided-batched many-design PLS1 moment route opt-in, but expose it as a first-class config flag instead of requiring
N4M_CUDA_PLS_MANY_BATCHED.Do not change default scoring or routing. The default remains the reusable sequential-many workspace; the new flag only selects the existing experimental one-GPU tiled path when the CUDA route is otherwise eligible.
Changes:
Added public ABI helpers:
n4m_config_set_cuda_pls_many_batchedandn4m_config_get_cuda_pls_many_batched.Bumped ABI to 1.21.0 and updated Linux/macOS/Windows expected symbol lists plus
catalog/abi_infra.yaml.Propagated
cuda_pls_many_batchedthroughsweep_run,aom_sweep_run,aom_chain_sweep_run, score/refit campaigns, sklearn sweep/screen-refit wrappers, AOM/moment facade inventories and the timing scripts.Added the flag to
bench_moment_sweep_timing.py,bench_aom_sweep_timing.pyandbench_aom_screen_refit_scaling.pyso one-GPU timing campaigns can compare the path without env vars.
Validation:
CPU build
build/dev-release: rebuiltlibn4m.so.1.21.0.CUDA build
build/cuda-on: rebuiltlibn4m.so.1.21.0withCUDA_VISIBLE_DEVICES=0.C++ tests:
n4m_tests350 passed on CPU and CUDA builds; internal tests passed on both builds.Python tests:
test_aom_moment_facade.py+test_moment_model_wrappers.py: 58 passed against ABI 1.21.0.CUDA Python smokes:
aom_moment_cuda_facade_smoke.pypassed with CUDA device CV counters; explicitsweep_run(..., cuda_pls_many_batched=True)matchedFalsescores with CUDA device fits.Catalog validation: 198 methods, PASS.
ABI export set vs
expected_symbols_linux.txt: 0 missing, 0 extra.git diff --check: PASS.
Remaining work:
This is still not fused IKPLS or the final 200k-chain GPU grinder. The open performance gap remains a true batched/fused PLS CV path across preprocessing variants and broader operator-moment coverage.
2026-06-05 — AOM facade inventory parity¶
Decision:
Keep
n4m.aomas a logical facade over the existing top-level runtime functions, but maken4m.aom.available_methods()complete enough for scripts to discover every AOM campaign helper that the namespace already exports.
Changes:
Added
n4m.aom.available_methods()rows for the exported chain-grid, candidate decode/evaluate, retained-pool, exact-refit, fixed-fit, rank/route/operator audit and candidate-report IO helpers.Mirrored the relevant
config_optionsmetadata fromn4m.momentso AOM users can inspect refit execution, CUDA PLS knobs, report serialization and audit controls from the AOM namespace without switching facades.Extended the AOM facade tests to assert top-level alias identity, inventory presence and representative
config_optionsfor those helpers.
Validation:
PYTHONPATH=bindings/python/src /home/delete/.venv/bin/python -m py_compile bindings/python/src/n4m/aom/__init__.py bindings/python/tests/test_moment_model_wrappers.py: PASS.PYTHONPATH=bindings/python/src N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so /home/delete/.venv/bin/python -m pytest bindings/python/tests/test_aom_moment_facade.py bindings/python/tests/test_moment_model_wrappers.py -q: 58 passed.PYTHONPATH=bindings/python/src /home/delete/.venv/bin/python catalog/scripts/validate.py: PASS, 198 methods.CUDA_VISIBLE_DEVICES=0 PYTHONPATH=bindings/python/src /home/delete/.venv/bin/python benchmarks/cross_binding/aom_moment_cuda_facade_smoke.py --cuda-visible-devices 0 --output /tmp/aom_moment_cuda_facade_smoke.json: PASS.git diff --check: PASS.
2026-06-05 — Functional fast-profile AOM moment screen/refit wrapper¶
Decision:
The native exact/proxy Ridge/PLS screen paths already batch head-homogeneous jobs, and the sklearn moment screen/refit presets already choose the fast defaults. The missing piece for scripts and campaigns was a low-friction function that exposes the same fast profile without changing the legacy defaults of
aom_chain_score_campaignoraom_chain_screen_refit_campaign.
Changes:
Added
n4m.aom_moment_screen_refit_campaignand then4m.aom/n4m.momentfacade aliases. It wrapsaom_chain_screen_refit_campaignwith strict moment routes, prefix chain ordering, split-head mixed scoring, PLS GCV-proxy first-pass scoring, exact-CV refit andrefit_execution="auto".The combined report keeps schema
n4m.aom_chain_screen_refit_campaign.v1and addscampaign_preset="moment_fast_screen_refit"for audit.Added facade inventory metadata under
moment_fast_screen_refit_campaignfor AOM andaom_moment_screen_refit_campaignfor the moment namespace.Re-exported
NativeAOMMomentScreenRefitRegressor,NativeAOMMomentPLSScreenRefitRegressorandNativeAOMMomentRidgeScreenRefitRegressorfromn4m.moment, with matching inventory entries. The moment namespace now exposes both the functional campaign preset and the reusable estimator presets.Re-exported the retained-pool/refit/fixed-fit helpers from
n4m.moment:aom_screen_refit_candidate_pool,aom_refit_execution_plan,aom_refit_candidates,aom_chain_fixed_fit_runandNativeAOMFixedCandidateRegressor. This lets a moment workflow go from screen report to exact-CV refit rows to a final-only reusable winner without switching namespaces.Re-exported the audit/report helpers from
n4m.moment:build_aom_strict_chain_grid,decode_aom_chains,aom_candidate_table,aom_evaluate_candidates,aom_candidate_operator_summary,aom_candidate_rank_diagnostics,aom_candidate_report_records,aom_save_candidate_reportandaom_load_candidate_report. The inventory records their explicitconfig_optionsas audit/report surfaces, not fit/predict catalog methods.Added
n4m.aom_candidate_route_summaryplusn4m.aomandn4m.momentaliases. It summarizes materialized, dense, banded and structured operator-moment coverage globally, by head and by chain from explicit candidate/report rows, so broad preprocessing screens can prove whether the retained rows actually stayed in moment routes before reuse.Extended the route summary with
reported_totalfor campaign/refit reports that carry aggregate counters. This keeps the top-level summary focused on retained rows, while still exposing full-screen coverage when a campaign only stores top-k rows.
Validation:
PYTHONPATH=bindings/python/src /home/delete/.venv/bin/python -m py_compile bindings/python/src/n4m/python.py bindings/python/src/n4m/__init__.py bindings/python/src/n4m/aom/__init__.py bindings/python/tests/test_moment_model_wrappers.py: PASS.PYTHONPATH=bindings/python/src N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so /home/delete/.venv/bin/python -m pytest bindings/python/tests/test_moment_model_wrappers.py -q: 49 passed.PYTHONPATH=bindings/python/src N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so /home/delete/.venv/bin/python -m pytest bindings/python/tests/test_aom_moment_facade.py -q: 9 passed.PYTHONPATH=bindings/python/src N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so /home/delete/.venv/bin/python -m pytest bindings/python/tests/test_aom_moment_facade.py bindings/python/tests/test_moment_model_wrappers.py -q: 58 passed after adding the functional campaign alias and the AOM/moment estimator aliases ton4m.moment, then 58 passed again after adding the retained-pool, exact-refit and fixed-candidate winner-reuse aliases, then 58 passed again after adding the audit/report helper aliases and JSON report save/load workflow coverage, then 58 passed again after adding route-coverage summary aliases and tests, then 58 passed again after addingreported_totalcoverage for top-k campaign reports.CUDA_VISIBLE_DEVICES=0 PYTHONPATH=bindings/python/src /home/delete/.venv/bin/python benchmarks/cross_binding/aom_moment_cuda_facade_smoke.py --cuda-visible-devices 0 --output /tmp/aom_moment_cuda_facade_smoke.json: PASS, including the moment audit/report and route-summary alias assertions.PYTHONPATH=bindings/python/src /home/delete/.venv/bin/python catalog/scripts/validate.py: PASS.
2026-06-05 — Default mixed AOM screen/refit estimators to split-head scoring¶
Decision:
A single mixed Ridge+PLS
aom_chain_sweep_run(score_only=True)call uses none of the batched head-homogeneous fast paths. Probing a small mixed campaign shows every batch counter at0forsplit_head_scoring="off", while"auto"(Ridge-only + PLS-only subcalls, merged) turns them on with bit-identical candidate scores. So mixed-by-default sklearn screen/refit workflows were silently leaving the batched grinder unused.Make the workflow/preset surfaces opt into
"auto"by default while keeping the lower-level campaign helpers on the historical"off"launch shape (a pre-existing test pinsaom_chain_score_campaign(..., split_head_scoring="off")behavior).
Changes:
NativeAOMScreenRefitRegressor(base sklearn screen/refit estimator, default heads("ridge", "pls")) now defaultssplit_head_scoring="auto"; docstring documents the score-preserving merge and that single-head screens are inert. TheNativeAOMMomentScreenRefitRegressormixed preset already defaulted to"auto"; the PLS-only / Ridge-only presets inherit"auto"but it is a behavioral no-op (can_split_head_scoringis false for one head, son_split_head_chunksstays0).benchmarks/cross_binding/bench_aom_screen_refit_scaling.py--split-head-scoringnow defaults toauto(inert for the default--head pls; representative for--head mixed); the actual value is still written to each CSV row.aom_chain_score_campaign/aom_chain_screen_refit_campaigndefaults are unchanged ("off").docs/methods/aom_chain_sweep_run.mdnow spells out that splitting enables both the Ridge moment batch and the PLS exact/GCV-proxy batch (a single mixed call batches nothing), and which surfaces default tooffvsauto.
Validation (CPU, N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so, ABI 1.20):
Empirical counters for a mixed
aom_chain_score_campaign(C chains, L ridge lambdas, K folds):off-> all batch counters0;auto->n_ridge_moment_score_batch_jobs == C*L*K, plusn_pls_gcv_proxy_batch_jobs == C(pls_score_mode="gcv_proxy") orn_pls_moment_score_batch_jobs == C*K == n_pls_moment_cv_fits(pls_score_mode="cv");keyed (chain_id, head, param)scores andbestidentical (max score diff0.0).NativeAOMScreenRefitRegressordefault (auto) vs explicitoff: identicalselected_chain_,selected_head_,selected_cv_rmse_,coef_andpredict(X)(max abs diff0.0).New/strengthened tests in
bindings/python/tests/test_moment_model_wrappers.py(test_aom_campaign_split_head_scoring_preserves_scores_and_enables_split,test_aom_campaign_split_head_exact_cv_enables_pls_moment_batch,test_native_aom_screen_refit_defaults_to_split_head_scoring); full file48 passed.python -m py_compileof the bench,native_sweeps.pyandmake_python_package.pypassed.
2026-06-05 — Add CUDA-device smoke controls to AOM sweep timing¶
Decision:
Make
bench_aom_sweep_timing.pyable to exercise the CUDA PLS1 moment device route explicitly, instead of only timing CUDA builds through shapes that remain below the default device threshold.
Changes:
Added
--cuda-pls-parallel-foldsand--cuda-pls-min-device-featurestobench_aom_sweep_timing.py.Propagated those flags through AOM sweep, AOM chain sweep, exact PLS score-only, screen/refit, FCK/Gaussian/Whittaker and Ridge scenarios.
Added
n_pls_moment_cuda_parallel_fold_batches,n_pls_moment_cuda_parallel_fold_jobs,n_refit_pls_moment_cuda_parallel_fold_batchesandn_refit_pls_moment_cuda_parallel_fold_jobsto the timing CSV rows.
Validation:
CPU default smoke:
PYTHONPATH=bindings/python/src N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so /home/delete/.venv/bin/python benchmarks/cross_binding/bench_aom_sweep_timing.py --repeats 1 --cv 4 --profile compact --output /tmp/aom_sweep_cpu_full_smoke.csvpassed with 49 rows, Ridge/PLS exact score-only rows present andpls_device_fits=0.CUDA one-GPU smoke:
CUDA_VISIBLE_DEVICES=0 PYTHONPATH=bindings/python/src N4M_LIB_PATH=build/cuda-on/cpp/src/libn4m.so /home/delete/.venv/bin/python benchmarks/cross_binding/bench_aom_sweep_timing.py --repeats 1 --cv 4 --profile compact --cuda-pls-min-device-features 1 --cuda-pls-parallel-folds --output /tmp/aom_sweep_cuda_device_smoke.csvpassed with 49 rows,pls_device_fits=824,parallel_batches=198,parallel_jobs=824andridge_batch_calls=2.PYTHONPATH=bindings/python/src /home/delete/.venv/bin/python -m py_compile benchmarks/cross_binding/bench_aom_sweep_timing.pypassed.
2026-06-05 — Make AOM sweep timing smoke runnable under force_moments¶
Decision:
Keep the FCK/Gaussian/exact PLS timing rows on strict moment routes, but make their synthetic shapes satisfy the exact PLS operator-moment invariant at the default
cv=4.
Changes:
Adjusted
bench_aom_sweep_timing.pyforce-moment exact PLS shapes from(160, 32)/(240, 48)to(192, 32)/(288, 48).Added a script comment documenting the
min_train >= 4pconstraint so the timing smoke does not drift back into unsupported materialized fallback territory.
Validation:
PYTHONPATH=bindings/python/src N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so /home/delete/.venv/bin/python benchmarks/cross_binding/bench_aom_sweep_timing.py --repeats 1 --cv 4 --profile compact --output /tmp/aom_sweep_full_smoke.csvpassed and produced 49 timing rows, including bothnative_aom_chain_sweep_pls_exact_score_onlyandnative_aom_chain_sweep_ridge_exact_score_only.
2026-06-05 — Surface Ridge batch counters in campaigns and benches¶
Decision:
Treat native Ridge batch score-only counters as campaign-level telemetry, not only low-level native diagnostics. Large preprocessing screens need the same visibility for Ridge as for PLS exact/proxy batch paths.
Changes:
Added Ridge fit and batch counters to
_AOM_ROUTE_COUNTER_KEYS, campaign chunk aggregation, checkpoint reports and split-head merged results.Added
ridge_cv_fits_per_chainandridge_cv_fits_per_candidateto AOM campaign throughput reports.Added Ridge batch counters to native sklearn sweep/screen-refit diagnostics.
Added Ridge screen/refit/final-fit columns to
bench_aom_screen_refit_scaling.py, plus Ridge batch columns and anative_aom_chain_sweep_ridge_exact_score_onlyrow tobench_aom_sweep_timing.py.
Validation:
PYTHONPATH=bindings/python/src N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so /home/delete/.venv/bin/python -m pytest bindings/python/tests/test_moment_model_wrappers.py -k 'ridge_score_only_uses_batch_moment_path or moment_screen_refit_presets_are_reusable or pls_gcv_proxy_score_only' -qpassed.PYTHONPATH=bindings/python/src /home/delete/.venv/bin/python -m py_compile bindings/python/src/n4m/python.py bindings/python/src/n4m/sklearn/native_sweeps.py benchmarks/cross_binding/bench_aom_sweep_timing.py benchmarks/cross_binding/bench_aom_screen_refit_scaling.py benchmarks/cross_binding/bench_moment_sweep_timing.pypassed.A reduced Ridge screen/refit benchmark smoke reported screen
n_ridge_moment_score_batch_calls=1,n_ridge_moment_score_batch_jobs=72, refitn_refit_ridge_moment_cv_fits=6,n_refit_ridge_moment_score_batch_calls=1andn_refit_ridge_moment_score_batch_jobs=6.
2026-06-05 — Batch exact Ridge moment score-only screens¶
Decision:
Apply the same many-chain native batch pattern used by exact PLS score-only screens to Ridge operator-moment AOM screens.
Keep the scoring rule exact: every chain/lambda/fold job still fits Ridge from train moments and scores held-out SSE from held-out moments.
Changes:
Added
score_ridge_moment_sweeps_score_only(...), which flattensn_chains * n_lambdas * n_foldsRidge moment jobs and uses the OpenMPN4M_PARALLEL_FOR_STATICfallback when that build option is enabled.Added
n_ridge_moment_score_batch_callsandn_ridge_moment_score_batch_jobsto sweep and AOM sweep results, and exposed the Ridge CV/final counters through AOM results.Routed Ridge-only AOM
score_only=Trueoperator-moment screens through one batch scorer when every chain is moment-eligible, with the existing materialized fallback kept formoment_policy="auto".Added C++ internal coverage comparing batch Ridge scores against single-chain scoring, plus Python coverage for
n4m.aom_chain_sweep_run(...)Ridge-only score-only batch counters.
Validation:
/home/delete/.venv/bin/cmake --build build/dev-release --target n4m_internal_tests -j2passed./home/delete/.venv/bin/ctest --test-dir build/dev-release -R n4m_internal_tests --output-on-failurepassed./home/delete/.venv/bin/cmake --build build/dev-release --target n4m_tests -j2passed./home/delete/.venv/bin/ctest --test-dir build/dev-release -R '^n4m_tests$' --output-on-failurepassed./home/delete/.venv/bin/cmake --build build/omp-on --target n4m_tests -j2passed./home/delete/.venv/bin/ctest --test-dir build/omp-on -R n4m_internal_tests --output-on-failurepassed./home/delete/.venv/bin/ctest --test-dir build/omp-on -R '^n4m_tests$' --output-on-failurepassed./home/delete/.venv/bin/cmake --build build/cuda-on --target n4m_tests -j2passed.CUDA_VISIBLE_DEVICES=0 /home/delete/.venv/bin/ctest --test-dir build/cuda-on -R n4m_internal_tests --output-on-failurepassed.CUDA_VISIBLE_DEVICES=0 /home/delete/.venv/bin/ctest --test-dir build/cuda-on -R '^n4m_tests$' --output-on-failurepassed.PYTHONPATH=bindings/python/src N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so /home/delete/.venv/bin/python -m pytest bindings/python/tests/test_moment_model_wrappers.py -k 'ridge_score_only_uses_batch_moment_path' -qpassed.A manual Python smoke comparing Ridge
moment_policy="force_moments"batch scoring againstmoment_policy="materialized"on 3 chains x 3 lambdas x 4 folds reportedbatch_calls=1,batch_jobs=36, 9 candidates and max score difference7.4e-16.
2026-06-05 — Parallelize CPU fallback PLS1 moment fold fits¶
Decision:
Treat the non-CUDA
fit_pls1_moment_prefixes_for_foldsloop as part of the fast preprocessing-screen path: batched exact-CV scoring already groups the fold work, but the host fallback still fitted each fold serially.Use the existing
N4M_PARALLEL_FOR_STATICabstraction so default builds remain serial and bit-for-bit scoped to the existing OpenMP option.
Changes:
Added an OpenMP-annotated fallback loop over folds in
cpp/src/core/sweep.cpp.Avoided sharing the ABI
Contextacross OpenMP workers: each fold gets a localContext, and the first status/error is replayed to the caller after the parallel region.Preserved the existing many-fold CUDA path and the
used_cuda_*flag semantics.Strengthened the internal sweep tests so exact PLS1 batch scoring and GCV proxy batch scoring must report the expected one-call/many-job counters.
Validation:
cmake --build build/dev-release --target n4m_tests -j2passed.ctest --test-dir build/dev-release -R n4m_internal_tests --output-on-failurepassed.ctest --test-dir build/dev-release -R '^n4m_tests$' --output-on-failurepassed.cmake --build build/omp-on --target n4m_tests -j2passed, compiling the annotatedsweep.cpppath withN4M_WITH_OPENMP=ON.ctest --test-dir build/omp-on --output-on-failurepassed.After adding the counter assertions, targeted
cmake --build build/dev-release --target n4m_internal_tests -j2andctest --test-dir build/dev-release -R n4m_internal_tests --output-on-failurepassed.The same targeted internal test passed in
build/omp-on.The same targeted internal test passed in
build/cuda-onwithCUDA_VISIBLE_DEVICES=0.cmake --build build/cuda-on --target n4m_tests -j2passed.CUDA_VISIBLE_DEVICES=0 ctest --test-dir build/cuda-on --output-on-failurepassed.Quick local timing smoke, exact PLS score-only with 8 folds and identical scores:
512x384, components[1,2,4,8]: dev-release serial median44.8 ms,omp-onwithOMP_NUM_THREADS=4median39.3 ms.640x768, components[1,2,4,8]: dev-release serial median259.3 ms,omp-onwithOMP_NUM_THREADS=4median246.2 ms.
Remaining work:
This parallelizes independent CPU fold fits. It is not fused IKPLS and does not reduce the algebraic cost per fold/component; large 100k-200k PP screens still need CUDA many-chain/fused kernels or a validated proxy screen to reach the intended latency.
2026-06-05 — Expose aom_pop.aom_preprocessing in n4m¶
Decision:
Treat
aom_pop.aom_preprocessingas part of the requested AOM product surface instead of leaving it reachable only through olderpls4allinternals or through selectors that use it indirectly.Add an ABI-close, NumPy-first helper rather than a low-level
ctx/bank/gatewrapper: callers pass the same AOM operator specs used by the selector/sweep APIs and receive the native MethodResult fields.
Changes:
Added FFI declarations for
n4m_gating_strategy_create,n4m_gating_strategy_destroyandn4m_aom_preprocess_fit.Added
n4m.python.aom_preprocess(X, y=None, operators=..., gating_mode=...)returningtransformed,operator_outputs,weights,operator_kinds,n_operators,n_samples,n_featuresandmode.Re-exported it as
n4m.aom_preprocessandn4m.aom.aom_preprocess; added it ton4m.aom.available_methods()withcatalog_id: aom_pop.aom_preprocessing.Added the Python catalog binding for
aom_pop.aom_preprocessingin both the split YAML andcatalog/methods.yaml.Added smoke coverage for identity hard/soft gating and generated wheel smoke coverage including the optional-dependency child process.
Updated the end-user
docs/methods/aom_preprocess.mdpage and methods index so the documented product entry isn4m.aom_preprocess/n4m.aom.aom_preprocess, with the legacy lower-level bindings framed as compatibility surfaces.
Validation:
Targeted AOM/moment facade, wrapper and optional-dependency tests passed, 55 tests.
Full Python binding tests passed, 296 tests with the 4 pre-existing UVE warnings.
Generated package smokes passed:
nirs4all-methods2 tests,pls4all1 test.catalog/scripts/validate.py --strict-abipassed, 198 methods and 698/698 exportedn4m_*symbols covered.The six touched catalog entries are synchronized between
catalog/methods.yamland their splitcatalog/methods/*.yamlfiles.One-GPU CUDA facade smoke passed with
CUDA_VISIBLE_DEVICES=0; the report now includesaom_preprocessidentity coverage against the CUDA build in addition to moment/AOM PLS1 device-CV counters.ruff checkon changed Python files andgit diff --checkpassed.
Remaining work:
The helper is an ABI-close operator-bank primitive. It proves availability in CPU and CUDA builds, but it is not a fused GPU preprocessing kernel.
Superseded later on 2026-06-05: the catalog timing benchmark for
aom_pop.aom_preprocessingnow covers the direct strict-linear single- operator bank; strict chains and model scoring remain in AOM sweep/campaign helpers.Batched IKPLS, arbitrary-chain moment screening, fused CUDA/grouped kernels and optional WASM/WebGPU backends remain separate backlog items.
2026-06-05 — Fill direct AOM/moment Python bindings in the catalog¶
Decision:
Close the remaining catalog discoverability gap for AOM/moment methods that were already callable from Python but still had
bindings: {}in their YAML catalog entries.Keep the catalog binding pointed at the ABI-close
n4m.pythonfunction. The sklearn estimators remain documented by the facade inventory as reusable wrappers throughwrapper_of.
Changes:
Added Python catalog bindings for
aom_pop.aom_pls,aom_pop.pop_pls,models.pls.cppls,models.regularized.continuum_regressionandmodels.specialized.ecrin both the split YAML files andcatalog/methods.yaml.Preserved public aliases with
legacy_aliasesforaom_plsandpop_pls.Strengthened
test_aom_moment_facade.py: every catalog-backed facade entry must now have a Python binding in the catalog, that binding must resolve inn4m.python, and alias-role facade rows must be declared as catalog legacy aliases.
Validation:
Targeted facade/optional-dependency tests passed, 10 tests.
Full Python binding tests passed, 295 tests with the 4 pre-existing UVE warnings.
Generated package smokes passed:
nirs4all-methods2 tests,pls4all1 test.catalog/scripts/validate.py --strict-abipassed, 198 methods and 698/698 exportedn4m_*symbols covered.The five touched method entries are synchronized between
catalog/methods.yamland their splitcatalog/methods/*.yamlfiles. The global split check still reports two unrelated stale files (augmentation.edge_artifacts.edge_artifactsanddiagnostics.approximate_press), so those were left untouched.One-GPU CUDA facade smoke passed again with
CUDA_VISIBLE_DEVICES=0.ruff checkon the changed Python facade test andgit diff --checkpassed.
Remaining work:
Batched IKPLS, arbitrary-chain moment screening, fused CUDA/grouped kernels and optional WASM/WebGPU backends remain separate backlog items.
2026-06-05 — Facade inventory catalog/doc traceability¶
Decision:
Harden the product surface now exposed by
n4m.aomandn4m.moment: an advertised method should not only resolve as Python, it should also point back to the catalog/doc artifact when it is a catalog-backed method.Keep this as metadata-only Python work. No ABI symbol, C++ implementation, numerical behavior, CUDA routing, or generated wheel dependency changed.
Changes:
Added
catalog_id,catalog_role,wrapper_ofanddoc_pathmetadata to the AOM/momentavailable_methods()inventories where applicable.Marked sklearn-style reusable wrappers as wrappers around the existing native catalog binding instead of inventing new catalog method IDs.
Left
moment.backend_recommendationas a non-catalog helper with an explicitnon_catalog_reason.Extended
bindings/python/tests/test_aom_moment_facade.pyto assert facade entries still export/resolve, catalog files exist and contain the expectedmethod_id, docs exist, and catalog Python bindings are coherent with direct entries or declared wrappers.
Validation:
Targeted source tests:
test_aom_moment_facade.py+test_sklearn_optional.pypassed, 10 tests.Full Python binding tests passed, 295 tests with the 4 pre-existing UVE warnings.
Generated package smokes passed:
nirs4all-methods2 tests,pls4all1 test.catalog/scripts/validate.py --strict-abipassed, 198 methods and 698/698 exportedn4m_*symbols covered.One-GPU CUDA facade smoke passed with
CUDA_VISIBLE_DEVICES=0: moment PLS1 reported 4 CUDA device-CV fits and AOM chain PLS1 reported 8 CUDA device-CV fits, with zero host/materialized PLS CV fits.ruff checkon the changed Python files andgit diff --checkpassed.
Remaining work:
Batched IKPLS, arbitrary-chain moment screening, fused CUDA/grouped kernels and optional WASM/WebGPU backends remain separate backlog items.
2026-06-05 — Prove the optional-scikit-learn runtime contract for the core/AOM/moment surface¶
Decision:
Product-hardening only, scoped to the Python packaging contract: the
nirs4all-methods(n4m) and slimpls4allwheels declare onlynumpyas a runtime dependency and advertise scikit-learn/SciPy as optional. Verify the contract holds forimport n4m+ then4m.aom/n4m.momentfacades and prove it with a focused regression artifact.Do not add scikit-learn (or SciPy) as a runtime dependency, do not add ABI symbols, do not change numerical results, and do not touch the experimental CUDA many-chain path. Do not weaken the existing strong generated wheel smoke.
What I checked:
Static audit of
bindings/python/src/n4m: the only module-scope optional imports aren4m/sklearn/_compat.py(from sklearn.base import ...inside atry/exceptwith a dependency-lightBaseEstimator/TransformerMixinfallback) and a lazy,try/except-guardedfrom scipy.optimize import minimizeinsideaom_portfolio._solve_simplex_qp(projected-gradient fallback). Every othersklearn/scipyhit is a string literal, docstring, or the__sklearn_is_fitted__protocol method name — no hard import.Empirical verification with
sklearnandscipyblocked at the meta-path level, loading the dev-release library viaN4M_LIB_PATH(build/dev-release/cpp/src/libn4m.so— its SONAME filename is a stale1.18.0but the compiled content reports0.98.0+abi.1.20.0, which the_ffi.pyABI floor of 1.20 accepts).import n4m,import n4m.aom,import n4m.momentall succeed;n4m.sklearn._compat.BaseEstimatoris the in-use base (__module__ == "n4m.sklearn._compat"); the direct function facades (moment.moments/ridge/cppls/ecr,aom.aom_pls,aom/moment.available_methods) run; andNativeRidgeRegressor.fit/predictreproduces the native ridge head exactly on the fallback base.
Finding:
No import breakage — the runtime contract holds. But it was unproven: every test environment (the dev venv and the cibuildwheel test stage, which sets
CIBW_TEST_REQUIRES: pytest numpy scikit-learn) installs scikit-learn, so a future hardimport sklearn/import scipyat module scope would silently break the “works with NumPy alone” contract with nothing to catch it.
Changes (smallest regression artifact, no new dependencies):
Added
bindings/python/tests/test_sklearn_optional.py: spawns a child interpreter withsklearnandscipyblocked, then asserts the core import, the fallbackBaseEstimator, the AOM/moment direct-function facades, a scikit-learn-style wrapperfit/predict, andAOMRidgeBlenderimport all work.Added
test_core_import_without_sklearn()to the generatednirs4all-methodswheel smoke (bindings/python/scripts/make_python_package.py,n4mbranch only) so the shipped wheel proves the same contract in a child interpreter with both optional deps blocked. The existingtest_import_and_load()(which intentionally exercises the sklearn-wrapper facade) is unchanged; the slimpls4allsmoke stays import/ABI-only. No runtime dependency was added — pyproject staysnumpy-only.
Validation:
pytest bindings/python/tests: 291 passed (was 290; +1), 4 pre-existing UVE warnings.Generated
nirs4all-methodspackage smoke (pytest bindings/python_nirs4all_methods/tests): 2 passed (existing strong test + new optional-dep test).Slim
pls4allpackage smoke: 1 passed, still import/ABI-only (unaffected).ruff checkontest_sklearn_optional.pyandmake_python_package.py: all checks passed.Negative control: a throwaway package with an unguarded module-scope
import sklearnproduces a non-zero child exit under the block, confirming the guard bites (the test is not vacuous).
Note:
scikit-learn remains optional (only the guarded
_compat.pyimport) and SciPy remains optional (only the lazy, guardedaom_portfolioSLSQP path). The generatedbindings/python_nirs4all_methods/tree is gitignored and rebuilt byrelease-wheels.yml; only the generator template was edited.
2026-06-05 — n4m.aom facade pop_pls wrapper gap + facade-consistency guards¶
Decision:
The
n4m.aomandn4m.momentfacades are thin re-export layers over the singlelibn4mruntime. Their advertisedavailable_methods()inventory must stay in lockstep with what the facade actually re-exports — an inventory entry that names a wrapper the facade does not export is an integration defect.Pure integration-layer fix. Do not add ABI symbols, change numerical results, or touch the experimental CUDA many-chain batched path. CPU and CUDA behavior are unchanged.
Finding and fix:
n4m.aomadvertised thepop_plsmethod withentry="NativePOPPLSRegressor"(kind="sklearn_estimator"), but the facade did not re-export that class:n4m.aom.NativePOPPLSRegressorraisedAttributeError, while its siblingn4m.aom.NativeAOMPLSRegressorand the top-leveln4m.NativePOPPLSRegressorboth resolved. It was the only one of the 12n4m.aom+ 13n4m.momentinventory entries that did not resolve as a facade attribute.Added
NativePOPPLSRegressorto then4m.aomimport block and__all__(bindings/python/src/n4m/aom/__init__.py).
Regression guards (smallest missing integration artifact):
Strengthened the generated
nirs4all-methodswheel smoke test (bindings/python/scripts/make_python_package.py) to assert that everyn4m.aom/n4m.momentinventory entry resolves as a facade attribute, so the generated wheel can never ship a facade that names a wrapper it does not export.Updated
release-wheels.ymlso that the strengthened generated-wheel smoke installsscikit-learnas a test dependency; the n4m package keeps sklearn optional at runtime, but this smoke intentionally exercises the sklearn wrapper facade.Added
bindings/python/tests/test_aom_moment_facade.py: every inventory entry resolves and is exported in__all__; each facade entry is the same object as the underlyingn4m.python/n4m.sklearnsurface (thin-facade contract); the POP-PLS wrapper is shared across then4m.aomfacade, top-leveln4m, and the sklearn layer.
Validation:
make_python_package.py --allregenerates cleanly; generatedpython_nirs4all_methods/src/n4mis byte-identical tobindings/python/src/n4m.pytest bindings/python/tests: 290 passed (was 285; +5 facade tests).Generated
nirs4all-methodspackage smoke test: 1 passed with the strengthened all-entries-resolve assertion.catalog/scripts/validate.py --strict-abi: PASS, 198 methods, 698/698 exportedn4m_*symbols.ruff checkon the three changed files: all checks passed.Functional smoke through the facades on libn4m ABI 1.20.0:
n4m.aom.NativePOPPLSRegressorandn4m.aom.NativeAOMPLSRegressorfit/predict andn4m.moment.ridgeruns.
Note:
The bundled
bindings/python/src/n4m/lib/libn4m.so.1.18.0is stale, untracked dev cruft. The ABI floor in_ffi.py(1.20) correctly rejects it and the loader falls back to the freshbuild/dev-release1.20.0 library; release wheels rebuildlibn4m, so the stale dev copy never ships. Left as-is (out of scope, untracked).
2026-06-05 - Experimental CUDA PLS many strided-batched path¶
Decision:
The exact PLS score-only many-chain dispatch now proves batch shape via counters, but the default CUDA implementation still runs each fold/chain job sequentially with a reused device workspace.
A true production fix probably needs device-resident scalar reductions and small glue kernels, because the current host-compiled CUDA dispatch cannot remove all per-job cuBLAS scalar synchronizations.
Add a safe cuBLAS-only prototype behind an opt-in environment flag, but do not make it the default until it is measurably faster.
Implementation:
Added a tiled
pls1_moment_components_manyprototype that packs multiple independent moment jobs on one GPU and batches the dominantC*wand covariance-deflationp^2operations withcublasDgemmStridedBatched.Preserved the legacy sequential-many path as the default and as fallback. Enable the prototype with
N4M_CUDA_PLS_MANY_BATCHED=1; keepN4M_CUDA_PLS_MANY_LEGACY=1as an explicit fallback override.Added
N4M_CUDA_PLS_BATCH_MAX_BYTESto cap tile memory. Public ABI and Python signatures are unchanged.Added a public-surface C++ regression test that compares
n4m_sweep_rundefault CUDA PLS many-job scoring against the opt-in batched mode when a CUDA backend is available.
Validation:
CUDA build passed:
build/cuda-on/cpp/tests/n4m_testsreported349 passed, 0 failedonCUDA_VISIBLE_DEVICES=0.CPU and CUDA
ctest --output-on-failureboth passed.Python CUDA smoke with forced device PLS (
cuda_pls_min_device_features=1) showed default legacy vs opt-in batched max score diff1.1102230246251565e-16, identical selected candidate and20/20device CV fits.Timing smoke on 16 chains x 4 folds,
p=768, PLS components 1..4, showed the cuBLAS-only batched prototype slightly slower than default legacy (2327 msvs2220 msmedian). Keep opt-in only.
2026-06-05 - Many-chain PLS batch dispatch counters¶
Decision:
The exact-CV and GCV-proxy PLS-only AOM score-only paths already submit many transformed-chain moment jobs through one internal native dispatch. The existing counters showed total fold fits/proxy fits, but not whether those fits came from one many-chain batch dispatch or from per-chain calls.
Add audit counters only. Do not change scores, route selection, candidate rows, or CUDA scheduling.
Implementation:
Added
n_pls_moment_score_batch_callsandn_pls_moment_score_batch_jobsto the exact-CV PLS moment score-only path.Added
n_pls_gcv_proxy_batch_callsandn_pls_gcv_proxy_batch_jobsto the PLS GCV proxy score-only path.Propagated those scalars through
SweepResult,AomSweepResult, MethodResult packing, Python result dictionaries, campaign aggregation, sklearn diagnostics, and timing CSV scripts.
Validation:
Native C++ tests passed:
349 passed, 0 failed.CTest passed: 2/2 tests.
Targeted Python wrapper/tests passed:
44 passed.Python smoke confirmed a PLS-only exact screen over 3 chains and 4 folds reports
batch_calls=1,batch_jobs=12, and the proxy screen reportsbatch_calls=1,batch_jobs=3.
2026-06-05 - Direct native moment sklearn wrappers¶
Decision:
Direct native Ridge, CPPLS, continuum regression and ECR were available as ABI-close Python functions, but not as reusable sklearn-style estimators. That left individual winning heads harder to reuse outside the global AOM sweep/refit wrappers.
Add thin wrappers over the native MethodResults. Do not add new selection logic, new preprocessing routes or any out-of-moment method.
Implementation:
Added
NativeRidgeRegressor,NativeCPPLSRegressor,NativeContinuumRegressionRegressorandNativeECRRegressor.The wrappers replay predictions from input-space coefficients. Ridge uses the native intercept, while CPPLS, continuum and ECR reconstruct the intercept as
y_mean - x_mean @ coefficients.Re-exported the classes through
n4m.sklearn, top-leveln4mandn4m.moment.Added
ridge_regressor,cppls_regressor,continuum_regression_regressorandecr_regressorrows to the moment method inventory.Added
benchmarks/cross_binding/bench_direct_moment_heads_timing.pyto time direct native functions and sklearn wrapperfit+predictreplay overhead.
Validation:
Added targeted tests for exact replay against native train predictions, sklearn coefficient/intercept shapes, diagnostics and multi-output support.
Added generated-package smoke coverage for
NativeRidgeRegressor.
2026-06-05 - Reusable AOM wrapper diagnostics¶
Decision:
The native reusable AOM wrappers already replay predictions from folded input-space coefficients, but their diagnostics were still mostly numeric. For end-user reuse and incremental preprocessing campaigns, reports should expose source-free labels for the selected head and preconfigured bank.
Add audit-only diagnostics. Do not change scoring, routing, candidate grids, or fitted coefficients.
Implementation:
Added
profile_nameandexpected_bank_sizeto diagnostics forNativeAOMSweepRegressor,NativeAOMRobustHPORegressor,NativeAOMRidgeBlenderRegressorandNativeAOMOperatorPLSStackRegressor.Added
selected_head("ridge"or"pls") toNativeAOMRobustHPORegressordiagnostics.The expected preconfigured bank sizes are
12for compact and31for wide; custom chain wrappers keep their existing explicit chain diagnostics.
Validation:
Python syntax check passed for
native_sweeps.py.Targeted Python wrapper tests passed, covering compact diagnostics, wide diagnostics and prediction replay.
2026-06-05 - FCK parity across native wide AOM portfolio banks¶
Decision:
The configurable AOM sweep/campaign wide banks already carried Gaussian, FCK and Whittaker strict-linear variants. The native robust-HPO, Ridge blender and operator-PLS stack wide profiles still stopped at Gaussian plus Whittaker, so the preconfigured reusable methods did not reproduce the full strict-moment diversity available to the global screen.
Add only FCK strict-linear variants. Stateful or nonlinear preprocessing families such as SNV, MSC, EMSC, OSC, EPO or baseline-centering remain out of these exact-moment native banks.
Implementation:
Added
N4M_OP_FCKalpha0.0and1.0to the wide native banks for:aom_robust_hpo,aom_ridge_blenderandaom_operator_pls_stack.Wide native banks now align at 31 strict-linear chains/operators with the public
build_aom_strict_chain_grid("wide")/ AOM sweep profile.Updated catalog notes and method docs to state compact
12vs wide31and to name Gaussian/FCK/Whittaker variants explicitly.
Validation:
Rebuilt
build/dev-release --target n4m_tests.Native test binary passed:
349 passed, 0 failed.Added C++ tests proving wide robust-HPO, Ridge blender and operator-PLS stack expose 31 chains/operators after the FCK additions.
2026-06-05 - Public inventory config options for AOM/moment methods¶
Decision:
The AOM and moment facades exposed public method inventories, but users still had to inspect long docs or signatures to know which entries accept global chain grids, checkpointing, exact/proxy refit controls, and CUDA PLS route knobs.
Add audit-only
config_optionsmetadata to each inventory row. This is discoverability for end users and scripts, not a selector, router or scoring policy.
Implementation:
n4m.aom.available_methods()now includesconfig_optionsfor screen/refit presets, chain/profile sweeps, fixed-candidate reuse, Ridge blending, operator-PLS stacking, robust-HPO and legacy AOM/POP selectors.n4m.moment.available_methods()now includesconfig_optionsfor raw moments, train-from-heldout moments,sweep_run,NativeMomentSweepRegressor, direct Ridge/CPPLS/continuum/ECR heads and backend recommendation.CUDA PLS controls (
cuda_pls_parallel_folds,cuda_pls_min_device_features) are listed only on the moment/AOM screen surfaces that accept them.Updated the generated
nirs4all-methodspackage smoke-test template so package regeneration preserves the inventory contract.
Validation:
Python syntax check passed for the AOM facade, moment facade and package generator.
Targeted facade tests passed and assert JSON-serializable inventories plus the expected CUDA/screen/refit options.
Regenerated
bindings/python_nirs4all_methods; generated package import smoke passed.
2026-06-05 - Configurable CUDA PLS moment device threshold¶
Decision:
The PLS1 moment CUDA component loop was guarded by a hard-coded
p >= 1024threshold. That is conservative, but it makes medium-width NIRS screens hard to test on GPU without recompiling.Expose the threshold as an explicit positive config option while keeping the default at 1024. This is a benchmark/control knob, not a new scoring rule.
Implementation:
Added
n4m_config_set_cuda_pls_min_device_features/n4m_config_get_cuda_pls_min_device_featuresand bumped the C ABI to 1.20.0.sweep_run, AOM sweep calls, AOM campaigns/refits/fixed fits, sklearn wrappers and screen/refit presets acceptcuda_pls_min_device_features.Campaign fingerprints, reports and CPU/CUDA backend recommendations now include the threshold, so checkpoint resume cannot mix different GPU route configurations.
bench_moment_sweep_timing.pyandbench_aom_screen_refit_scaling.pyaccept--cuda-pls-min-device-features.
Validation:
build/dev-release --target n4m_crebuilt successfully after the ABI/config change.build/cuda-on --target n4m_crebuilt successfully after the same change.Python source files and benchmark scripts pass
py_compile.Targeted Python tests passed for backend recommendation, CPU score preservation and sklearn preset diagnostics.
Full Python binding suite passed:
279 passed, 4 warnings.Native CTest passed for
n4m_testsandn4m_internal_tests.Catalog checks passed: legacy split check, reference validation and strict ABI coverage (
698/698exportedn4m_*symbols).Regenerated
bindings/python_nirs4all_methodsand the generated package import smoke passed.Smoke benchmark CLIs accepted the new flag and emitted
cuda_pls_min_device_features=256inmoment_sweep_timing_min_device_smoke.csvandaom_screen_refit_min_device_smoke.csv.CUDA smoke with
CUDA_VISIBLE_DEVICES=0,p=256andcuda_pls_min_device_features=256switched PLS moment CV counters from host to device (4 -> 0host,0 -> 4device) while preserving candidate scores against the default threshold.
2026-06-05 - Configurable bounded CUDA PLS fold scheduling¶
Decision:
The CUDA PLS1 moment helper already had an environment-driven profiling path for independent fold jobs, but it was not a reusable method option and could launch one thread/stream per job.
Expose it as an explicit config/API option while keeping the old environment override for backwards-compatible profiling.
Bound the stream-parallel work to small batches on the single selected GPU. This is still exact fold-CV over moment designs, not fused batched IKPLS.
Implementation:
Added
n4m_config_set_cuda_pls_parallel_folds/n4m_config_get_cuda_pls_parallel_foldsand bumped the C ABI to 1.19.0.Added
cuda_pls_parallel_foldstosweep_run,aom_sweep_run,aom_chain_sweep_run,aom_chain_score_campaign,aom_refit_candidates,aom_chain_screen_refit_campaign, the native sklearn sweep wrappers and the moment screen/refit presets.CUDA
pls1_moment_components_many(...)now accepts the explicit flag, preservesN4M_CUDA_PLS_PARALLEL_FOLDS=1as an override, and runs bounded stream/cuBLAS batches instead of unbounded one-stream-per-job launches.Added
n_pls_moment_cuda_parallel_fold_batchesandn_pls_moment_cuda_parallel_fold_jobscounters to sweep, AOM sweep, campaign, refit and benchmark reports.Extended
bench_moment_sweep_timing.pyandbench_aom_screen_refit_scaling.pywith--cuda-pls-parallel-folds.
Validation:
Rebuilt
build/dev-releaseandbuild/cuda-ontargetsn4m_c.ABI smoke from Python reports
(1, 19, 0)and both new config symbols are exported frombuild/dev-release/cpp/src/libn4m.so.Targeted tests passed: the option is accepted, CPU-path candidate scores are unchanged, parallel CUDA counters remain zero off the CUDA-parallel path, and the mixed moment preset exposes the requested setting in diagnostics.
2026-06-05 - Split-head mixed campaign scoring to unlock PLS batching¶
Decision:
The native exact/proxy PLS batch hook only activates when a score-only AOM call is PLS-only. Mixed Ridge+PLS chunk calls can therefore miss that faster PLS branch even when every chain is moment-eligible.
Add an optional campaign-level split that scores Ridge and PLS in separate native calls per chunk, then merges candidate rows back into the same global/per-head/per-route top-k aggregation. This is not a new score or selector; it only changes chunk launch shape.
Keep historical behavior as
split_head_scoring="off"for generic calls. The mixed end-user moment preset usessplit_head_scoring="auto".
Implementation:
Added
split_head_scoringtoaom_chain_score_campaign,aom_chain_screen_refit_campaignandNativeAOMScreenRefitRegressor.Added
split_head_scoring="auto"toNativeAOMMomentScreenRefitRegressor.Split chunks expose
split_head_scoring,n_split_head_chunksandn_chunk_score_callsin screen/campaign diagnostics.Extended
bench_aom_screen_refit_scaling.pywith--split-head-scoringand matching CSV columns.
Validation:
Targeted source tests passed: split vs non-split mixed campaign scores match by
(chain_id, head, param), and the mixed preset reports the split diagnostics.Smoke benchmark generated
benchmarks/cross_binding/aom_mixed_screen_refit_split_smoke.csv; the row reportsn_split_head_chunks=2,n_chunk_score_calls=4, and preserved exact refit behavior on the tiny mixed case.
2026-06-05 - Public method inventories for AOM and moments¶
Decision:
Add a source-free inventory surface to the logical facades so scripts and users can discover which entries are intended for global preprocessing campaigns, selected-winner reuse, direct moment heads and CPU/CUDA launch planning.
Keep this as metadata only. It must not introduce a new selector, router or dataset-dependent policy.
Implementation:
Added
n4m.aom.available_methods().Added
n4m.moment.available_methods().Each function returns JSON-safe dictionaries by copy, with fields for
name,entry,kind,role,heads,reuse,cpu,cudaandnotes.Updated the generated
nirs4all-methodspackage smoke test to assert the key AOM and moment inventory entries are present.
Validation:
Python syntax check passed for both facades, the generator and updated tests.
Targeted source tests passed for the moment facade and AOM moment screen/refit presets, including inventory copy semantics.
Regenerated
bindings/python_nirs4all_methods, stagedlibn4m.so, and the generated package smoke test passed withoutN4M_LIB_PATH.
2026-06-05 - CUDA facade smoke for AOM and moments¶
Decision:
Add a targeted GPU smoke for the new logical facades rather than relying only on import tests or broad timing CSVs. The smoke must prove that
n4m.aomandn4m.momentstill alias the shared runtime and that their wide PLS1 screens actually use the CUDA device-CV path.
Implementation:
Added
benchmarks/cross_binding/aom_moment_cuda_facade_smoke.py.The script spawns a child Python process with
N4M_LIB_PATHpointed atbuild/cuda-on/cpp/src/libn4m.soandCUDA_VISIBLE_DEVICES=0.It runs
moment.sweep_run(...)andaom.aom_chain_sweep_run(...)on an80x1024, 4-fold, PLS1 score-only case withmoment_policy="force_moments"for the AOM route.It writes
benchmarks/cross_binding/aom_moment_cuda_facade_smoke.json.
Validation:
The smoke passed on the current CUDA build, ABI
1.18.0.moment.sweep_runreported 4 CUDA-device PLS moment CV fits, 0 host PLS moment CV fits and 0 materialized PLS CV fits.aom.aom_chain_sweep_runover two chains reported 8 CUDA-device PLS moment CV fits, 0 host PLS moment CV fits and 0 materialized PLS CV fits.
2026-06-05 - Logical moment facade inside nirs4all-methods¶
Decision:
Keep the historical
n4m.moments(...)function unchanged.Add a singular
n4m.momentnamespace for discoverability, because an4m.momentsmodule would shadow the existing function after submodule import.Keep the facade as a thin alias layer over
n4m.pythonandn4m.sklearn; there is still one C++/CUDA runtime and one binding stack.
Implementation:
Added
bindings/python/src/n4m/moment/__init__.pyexposingmoments,moments_train_from_heldout,moment_screen_backend_recommendation,sweep_run,ridge,cppls,continuum_regression,ecr, andNativeMomentSweepRegressor.Exported the facade as
n4m.momentwhile keeping top-level function imports backwards-compatible.Updated source tests and generated package smoke tests to prove the facade aliases existing functions and does not shadow
n4m.moments.
Validation:
Python syntax check passed for the facade, top-level export, generator and updated tests.
Source import smoke passed:
n4m.momentaliases the existing native functions whilen4m.momentsremains callable.Targeted facade test passed with a real
moment.sweep_run(...).Regenerated
bindings/python_nirs4all_methods, stagedlibn4m.so, and the generated package smoke test passed withoutN4M_LIB_PATH.
2026-06-05 - Logical AOM facade inside nirs4all-methods¶
Decision:
Keep the C++/CUDA runtime and bindings in the single
nirs4all-methodspackage. AOM currently shares operators, moments, Ridge/PLS kernels, catalog gates, ABI tracking and wheel staging with the rest oflibn4m; extracting it into a second native package would duplicate release and ABI work.Add a logical Python boundary instead:
n4m.aomis the dedicated public import surface for AOM campaigns, refit helpers, historical AOM portfolio classes and native sklearn AOM presets.
Implementation:
Added
bindings/python/src/n4m/aom/__init__.pyas a thin facade overn4m.pythonandn4m.sklearn. It does not own a second implementation.Exported the facade as
n4m.aomwhile keeping all existing top-level andn4m.sklearnimports backwards-compatible.Updated the generated
nirs4all-methodspackage smoke test so wheels prove the facade is present and aliases the embedded runtime functions/classes.
Validation:
Python syntax check passed for the new facade, top-level export, package generator and updated test.
Source import smoke passed:
n4m.aomaliases the top-level campaign helper and native sklearn preset class.Targeted mixed moment screen/refit preset test passed.
Regenerated
bindings/python_nirs4all_methods, stagedlibn4m.so, and the generated package smoke test passed withoutN4M_LIB_PATH.
2026-06-05 - End-user moment screen/refit sklearn presets¶
Decision:
Keep the generic
NativeAOMScreenRefitRegressoras the ultra-configurable experiment surface, but add named sklearn presets for the two common reusable workflows: mixed Ridge/PLS screen -> exact-CV refit, PLS proxy screen -> exact-CV refit, and Ridge exact screen -> exact-CV refit.Do not add a new scoring rule. Both presets delegate to the existing campaign/refit/final-only fixed-candidate path.
For mixed campaigns, keep exact refit train-only but avoid relying only on a global screen top-k when Ridge exact-CV rows and PLS proxy rows share the same list. Include an optional per-head retention budget before exact refit.
Implementation:
Changed
aom_chain_score_campaignsotop_candidates_by_headandtop_candidates_by_score_routeare collected from each chunk’s full score table, not only from that chunk’s global top rows.Added
refit_per_head_top_ktoaom_chain_screen_refit_campaignandNativeAOMScreenRefitRegressor. It exact-refits the deduplicated union of global top rows plus each head’s top rows and reports the global/per-head union counters.Added
NativeAOMMomentScreenRefitRegressor, the mixed Ridge/PLS preset. It uses exact Ridge CV plus PLS GCV proxy in the screen, then exact-CV refits the global/per-head retained union.Added
NativeAOMMomentPLSScreenRefitRegressor, fixingheads=("pls",),ridge_lambdas=(),pls_score_mode="gcv_proxy",moment_policy="force_moments"and prefix-aware chain ordering.Added
NativeAOMMomentRidgeScreenRefitRegressor, fixingheads=("ridge",),pls_components=(),moment_policy="force_moments"and prefix-aware chain ordering.Re-exported the preset classes from
n4m.sklearnand top-leveln4m.Updated method docs, method index discoverability, and the catalog entry for
aom_pop.aom_chain_screen_refit; its benchmark pointer now uses the focused screen/refit scaling benchmark.
Validation:
Python syntax check passed for the source and regenerated
nirs4all-methodspackage files.Targeted preset test passed, and the full moment wrapper test file passed: 35 tests.
Catalog gates passed: 198/198 split files up to date, 198/198 reference coverage and 694/694 ABI symbol coverage.
Regenerated
bindings/python_nirs4all_methods, staged the CPUlibn4m.so, verified the staged hash matches the build lib, and smoke-fit both new presets from the package withoutN4M_LIB_PATH.Strengthened the generated
nirs4all-methodspackage smoke test so wheel tests import the mixed AOM moment preset, run a tiny mixed Ridge/PLS screen-refit fit through the embedded library, and audit the retained candidate pool. The slimpls4allpackage keeps its import/ABI-only smoke.Added source-free
n4m.aom_screen_refit_candidate_pool(...)and validated that the mixed preset refits the retained union of global and per-head rows.Extended
bench_aom_screen_refit_scaling.pywith--head mixedand--refit-per-head-top-k, then regenerated CPU and CUDA-smoke CSVs for PLS, Ridge and mixed runs. On the current CPU mixed run with 24 chains,refit_per_head_top_k=4retains 8 exact-refit candidates even whenrefit_top_k=1, and projects the 200k-chain mixed screen at about 169.4 s on the measured 260x48 shape. The CUDA smoke completes on one GPU and reports the same retained-pool counters.
Limit:
These are ergonomic presets over the existing CPU/CUDA-capable substrate. They are not the fused CUDA/IKPLS grinder.
Because the presets default to
moment_policy="force_moments", they reject small fold geometries or regimes that would need a materialized fallback. The genericNativeAOMScreenRefitRegressorremains the escape hatch formoment_policy="auto"production runs.
2026-06-05 - Screen/refit scaling refresh after PLS proxy batching¶
Decision:
Refresh the proxy-screen plus exact-refit timing artefacts after batching the AOM PLS GCV proxy path. These CSVs are the direct benchmark for the intended large preprocessing campaign workflow.
Validation:
Regenerated
aom_screen_refit_scaling.csvandaom_screen_refit_scaling_cuda_smoke.csvfor PLS proxy screen -> exact refit.Regenerated the matching Ridge artefacts,
aom_ridge_refit_scaling.csvandaom_ridge_refit_scaling_cuda_smoke.csv, so all screen/refit CSVs share the current schema and auto-refit fields.PLS CPU screen/refit scaling now reports 24 chains, 48 proxy candidates, 24 proxy fits, 2.22 ms screen median, and a 200k-chain screen projection of about 13.9 s before exact refit of retained rows.
PLS auto refit keeps the plan-driven behavior:
top_k=1/2selectsbatched_score, whiletop_k=4/8/16selectsunion_batched_scorewith explicit extra-score counters.Ridge rows remain on the exact Ridge screen path and keep the same auto-refit semantics; CPU screen projection is about 184.9 s for 200k chains on the measured 260x48 shape.
2026-06-05 - Batched AOM PLS GCV proxy score-only path¶
Decision:
Apply the same many-chain native batching pattern to explicit
pls_score_mode="gcv_proxy"AOM PLS screens, because this is the cheapest first-pass route for very large preprocessing campaigns.For proxy screens, transform only all-sample moments. Held-out moments are not needed by the GCV proxy and are no longer transformed in the PLS-only batch branch.
Keep the existing exact-CV and refit semantics unchanged: GCV remains an explicit proxy score and must still be followed by exact-CV refit/evaluation for retained candidates.
Implementation:
Added internal
score_pls1_gcv_moment_screens_score_only(...), the many-chain form ofscore_pls1_gcv_moment_screen(...).Reused the existing PLS1 prefix fitter over all transformed all-sample moment sets, then mapped the nominal GCV RMSE back to one
SweepResultper chain.Routed AOM PLS-only score-only proxy campaigns through the batch branch when all chains have an operator-moment route. Fallback behavior is unchanged for non-forced calls.
Validation:
Rebuilt CPU and CUDA native targets.
CTest CPU: 2/2 tests passed.
CTest CUDA with
CUDA_VISIBLE_DEVICES=0: 2/2 tests passed.Internal C++ test compares the GCV batch hook against single-chain scoring and passed in both builds.
Targeted Python AOM PLS exact/proxy test passed on CPU and CUDA.
Full Python CPU suite passed: 275 tests, 4 existing UVE warnings.
Catalog checks passed: 198/198 references, 694/694 ABI symbols, and 198/198 per-method files up to date.
Regenerated
aom_sweep_timing.csvandaom_sweep_timing_cuda_smoke.csv. CPU proxy rows now report 5 chains, 10 candidates, 0 materialized candidates, and 5 proxy fits at 0.22 ms for 60x32, 0.30 ms for 80x48, and 0.44 ms for 96x64, projecting about 8.8 s, 12.0 s, and 17.7 s for 200k chains on those shapes.CUDA large proxy smoke at 260x1024 with 2 chains completed with 0 materialized candidates, 2 proxy fits, and 0 exact CV fits.
Regenerated the
nirs4all-methodsPython package, staged the rebuilt CPUlibn4m.so, and smoke-tested the packaged proxy PLS score-only path withoutN4M_LIB_PATH.
Limit:
This is still a GCV proxy screen, not exact CV. It improves the cheap recall screen path but does not remove the need for exact refit/evaluation of the retained candidates.
2026-06-05 - Batched exact-CV AOM PLS score-only path¶
Decision:
Add a native internal batch hook for exact-CV PLS1 moment screens over many transformed strict-linear chains, without changing the public C ABI.
Use it only for AOM
heads=("pls",), exact-CV,score_only=Truecampaigns when every chain has an operator-moment route. If a chain needs fallback andforce_momentsis not set, keep the previous per-chain path.Keep the selected-model reuse path unchanged: score-only campaigns still select candidates, and final reusable fits are handled by the existing fixed candidate/final-only APIs.
Implementation:
Added
score_pls1_moment_sweeps_score_only(...)as a C++ internal helper. It builds all train-fold moment stats forn_chains * n_foldsjobs, calls the existing prefix PLS1 moment fitter once, then maps exact held-out SSE back to oneSweepResultper chain.Added an AOM PLS-only score-only branch that transforms all chain moments, calls the batch helper once, preserves candidate rows, route counters, selected ids, fold ids, and moment-prefix cache counters.
Added a dedicated benchmark row
native_aom_chain_sweep_pls_exact_score_onlyto separate exact-CV score-only PLS from the GCV proxy row.
Validation:
Rebuilt CPU and CUDA native targets.
CTest CPU: 2/2 tests passed.
CTest CUDA with
CUDA_VISIBLE_DEVICES=0: 2/2 tests passed.Internal C++ test compares the batch PLS1 moment hook against single-chain scoring and passed in both builds.
Targeted Python exact/proxy AOM PLS test passed on CPU and CUDA.
Full Python CPU suite passed: 275 tests, 4 existing UVE warnings.
Catalog checks passed: 198/198 references, 694/694 ABI symbols, and 198/198 per-method files up to date.
Regenerated
aom_sweep_timing.csvandaom_sweep_timing_cuda_smoke.csv. The new exact score-only rows report 5 chains, 10 candidates, 0 materialized candidates, and 25 exact PLS moment CV fits. CPU medians were 0.58 ms at 160x32 and 1.25 ms at 240x48, projecting about 23.4 s and 49.8 s for 200k chains on those small shapes.CUDA large smoke at 260x1024 with 2 chains used the device path:
n_pls_moment_cuda_device_cv_fits=10, host CV fits 0.Regenerated the
nirs4all-methodsPython package, staged the rebuilt CPUlibn4m.so, and smoke-tested the packaged exact PLS score-only path withoutN4M_LIB_PATH.
Limit:
This batches exact-CV PLS scoring across transformed chains at the native helper level. It still materializes/derives transformed moments per chain and is not yet a fused device-resident IKPLS kernel over the whole Cartesian preprocessing grid.
2026-06-05 - Reusable CUDA PLS1 moment workspace¶
Decision:
Keep the current exact PLS1 moment algorithm unchanged, but remove repeated CUDA allocation churn from successive PLS1 moment component calls.
This is a substrate improvement for large AOM PLS screens: many strict preprocessing chains still invoke the PLS1 moment component helper chain-by-chain, so reusing device buffers is a necessary step before a true fused/batched IKPLS grinder.
Keep the opt-in parallel-fold profiling path on local per-thread workspaces; the reusable workspace is for the default single-stream CUDA path.
Implementation:
Added a thread-local reusable device workspace for
dC,ds,dw,dcw,dp_load,dW, anddP, grown to the largest(p, max_components)seen by the calling thread.Routed both
pls1_moment_componentsand the defaultpls1_moment_components_manypath through the same workspace view. The numerical cuBLAS loop is unchanged.Preserved the existing local-stream allocation path used by
N4M_CUDA_PLS_PARALLEL_FOLDS=1.
Validation:
Rebuilt the CUDA library; the CPU build was unchanged by the guarded file.
CTest CPU: 2/2 tests passed.
CTest CUDA with
CUDA_VISIBLE_DEVICES=0: 2/2 tests passed.Targeted CUDA Python tests for PLS proxy/backend/refit paths passed: 2 passed.
CUDA smoke for exact PLS1
sweep_runat260x1024reportedn_pls_moment_cuda_device_cv_fits=5and host CV fits 0.CUDA smoke for AOM PLS GCV proxy at
260x1024completed with 2 proxy fits and 4 proxy candidates.Regenerated
moment_gpu_crossover.csv; at256x1024, PLS median timing was 232.92 ms on CPU and 109.02 ms on CUDA, with CUDA recommended anddevice_cv=5.git diff --checkpassed.
Limit:
This removes repeated CUDA workspace allocation overhead. It is still not a fused/device-resident batched IKPLS implementation across chains.
2026-06-05 - Plan-driven AOM exact-refit auto mode¶
Decision:
Keep exact-CV refit selection train-only and deterministic, but make
execution_mode="auto"use the same candidate-grouping plan exposed byaom_refit_execution_plan.Prefer
union_batched_scoreonly when it reduces native refit groups and its extra exact scores stay withinauto_max_extra_fraction * n_retained_candidates; otherwise keepbatched_score, which preserves the retained parameter signatures.Keep
return_predictions=Trueon the individual replay path because the batched score modes deliberately return scores only.
Implementation:
Added plan sharing for
individual,grouped_score,batched_score, andunion_batched_score, plus a validatedauto_max_extra_fractionbudget.Propagated
refit_auto_max_extra_fractionthroughaom_chain_screen_refit_campaign,NativeAOMScreenRefitRegressor, and the screen/refit scaling benchmark.Added requested/selected execution-mode metadata and the auto-selection reason to refit reports, two-pass campaign reports, diagnostics, and timing CSV rows.
Validation:
Python syntax check passed for the modified implementation, wrapper, benchmark, and test files.
Targeted PLS proxy/screen-refit and Ridge exact-refit tests passed on CPU and CUDA.
Full Python CPU test suite passed: 275 tests, 4 existing UVE warnings.
Catalog checks passed: 198/198 references, 694/694 ABI symbols, and 198/198 per-method files up to date.
Regenerated
aom_screen_refit_scaling.csvandaom_screen_refit_scaling_cuda_smoke.csv;autorows now report requested mode, selected mode, selection reason, and planned-vs-observed scored/extra refit counters.Regenerated the matching Ridge screen/refit timing CSVs,
aom_ridge_refit_scaling.csvandaom_ridge_refit_scaling_cuda_smoke.csv, with the same auto-mode fields.Regenerated the
nirs4all-methodsPython package, staged the bundled CPUlibn4m.so, and smoke-tested package loading plus Ridge auto refit withoutN4M_LIB_PATH.git diff --checkpassed.
2026-06-05 - Prefix-aware campaign chunk ordering¶
Decision:
Keep the native ABI unchanged and improve large Python campaign packing first.
Add an explicit
chain_ordering="prefix"mode that sorts strict-linear chains by operator-prefix key before chunking, so chains sharing prefixes are more likely to hit the native per-call moment-prefix cache.Keep
chain_ordering="input"as the default. Prefix ordering is a throughput optimization only: it does not alter scores, and reportedchain_idremains the original caller/grid index.
Implementation:
Added prefix-order helpers to
aom_chain_score_campaign.Added
ordered_chain_idon top-candidate rows andselected_ordered_chain_id/ ordered chunk bounds on chunk summaries.Propagated the option through
aom_chain_screen_refit_campaign,NativeAOMScreenRefitRegressor, and the screen/refit scaling benchmark.Added benchmark CSV fields for screen prefix-cache hits, misses and hit fraction.
Validation:
Targeted prefix-order, strict-grid and checkpoint tests passed on CPU and CUDA.
Full Python CPU tests passed: 275 tests, 4 existing UVE warnings.
Catalog checks passed: 198/198 references, 694/694 ABI symbols, and 198/198 per-method files up to date.
Regenerated
aom_screen_refit_scaling.csvandaom_screen_refit_scaling_cuda_smoke.csvwith chain-ordering and prefix-cache columns.Regenerated the
nirs4all-methodsPython package and smoke-testedchain_ordering="prefix"from the package-bundled CPU library.On a deliberately interleaved 24-chain Ridge screen with chunk size 4, prefix ordering preserved the best score while increasing prefix-cache hits from 0 to 12. Median CPU timing improved from 6.25 ms to 5.41 ms; CUDA-smoke timing improved from 18.95 ms to 17.83 ms.
2026-06-05 - Gaussian strict banded AOM operator¶
Decision:
Add a constrained Gaussian operator to the strict AOM moment family despite the additive enum extension, because it is linear, shape-preserving and can be represented exactly by the same banded route as the other local convolution operators.
Keep the scope narrow: this is the fixed zero-padding AOM screen variant, not the full SciPy-compatible
pp_gaussianpreprocessing surface.
Implementation:
Added
N4M_OP_GAUSSIAN = 18and accepted it in strict AOM operator banks.Added normalized Gaussian kernel construction for the materialized strict transform and for the banded operator-moment descriptor.
Added Gaussian variants to native wide banks, Python
wide/labstrict chain grids, robust-HPO/operator-stack/ridge-blender native wide profiles, and the timing benchmark.
Validation:
Rebuilt CPU and CUDA native test targets.
CTest CPU: 2/2 tests passed.
CTest CUDA with
CUDA_VISIBLE_DEVICES=0: 2/2 tests passed.Targeted Gaussian/FCK campaign tests passed on CPU and CUDA.
Full Python CPU tests passed: 274 tests, 4 existing UVE warnings.
Catalog checks passed: 198/198 references, 694/694 ABI symbols, and 198/198 per-method files up to date.
Regenerated
aom_sweep_timing.csvandaom_sweep_timing_cuda_smoke.csv; the Gaussian rows report 3 chains, 15 candidates, all 15 through banded operator moments, and 0 materialized candidates.Regenerated the
nirs4all-methodsPython package and smoke-testedbuild_aom_strict_chain_grid("wide") == 31plus a Gaussianforce_momentsroute from the package-bundled CPU library.
2026-06-05 - FCK variants in strict moment AOM grids¶
Decision:
At this point Gaussian was deferred because it required extending the public
n4m_operator_kind_tenum. The following pass adds the constrained enum-backed variant above.Use the already-integrated
N4M_OP_FCKinstead: it is strict-linear, shape-preserving, and already has a banded operator-moment route.
Implementation:
Added two FCK variants to the native wide AOM bank and to
build_aom_strict_chain_grid("wide").Added
fckas a default lab/cartesian family with single-op and FCK-plus-finite-difference templates.Added Python campaign coverage proving an FCK chain can be screened under
moment_policy="force_moments"with banded moment route counters.Added
native_aom_chain_sweep_fckrows tobench_aom_sweep_timing.py.
Validation:
Rebuilt CPU and CUDA native test targets.
CTest CPU: 2/2 tests passed.
CTest CUDA with
CUDA_VISIBLE_DEVICES=0: 2/2 tests passed.Targeted FCK campaign tests passed on CPU and CUDA.
Full Python CPU tests passed: 274 tests, 4 existing UVE warnings.
Regenerated
aom_sweep_timing.csvandaom_sweep_timing_cuda_smoke.csv; the FCK rows report 3 chains, 15 candidates, all 15 through banded operator moments, and 0 materialized candidates.
2026-06-05 - AOM campaign backend launch diagnostics¶
Decision:
The score campaigns had route counters proving what actually ran, but the report did not expose the CPU/CUDA launch recommendation that should be used before importing
n4m.Keep this strictly diagnostic and source-free: the policy inputs are only
n_samples,n_features,head, and CUDA availability. Dataset names, targets, and candidate scores are not policy inputs.
Implementation:
Added per-head
moment_backend_recommendationstoaom_chain_score_campaignandaom_chain_screen_refit_campaign.Added optional
backend_cuda_availableto the campaign helpers andNativeAOMScreenRefitRegressor, so an external launcher can report that a CUDA build is available even when the current process loaded a CPU libn4m.Left checkpoint fingerprints unchanged because the backend recommendation does not affect scoring, ranking, or the retained candidate rows.
NativeAOMScreenRefitRegressor.get_diagnostics()now exposes the same backend recommendation block.
Validation:
Targeted Python tests passed on CPU and CUDA for backend recommendation, score-campaign checkpoint/resume, screen-refit, and prefix-cache counters.
2026-06-05 - CUDA PLS1 W/P block transfer¶
Decision:
The device-resident PLS1 loop still copied
WandPback to host once per component. Those matrices are only consumed after all components are available for prefix reconstruction.Keep the same exact PLS1 math but store component weights/loadings in device-side
dW/dPbuffers during the loop, then copy the full row-majorW/Pblocks back once.
Implementation:
Added
dWanddPdevice workspaces to the CUDA PLS1 component helper.Each component stores
dwandp_loadinto strided columns ofdW/dPusing cuBLASDcopy; hostW/Pare copied once after the deflation loop.The same path is used by the default reused fold workspace and by the opt-in
N4M_CUDA_PLS_PARALLEL_FOLDS=1profiling path.
Observed smoke output:
Regenerated
moment_gpu_crossover.csv.Default CUDA PLS
256x1024now reports about 112 ms withhost_cv=0,device_cv=5, improving from the previous ~129 ms smoke.Opt-in parallel folds improved too, to about 122 ms on the same shape, but remains slower than the default single-workspace route.
Validation:
Rebuilt CPU and CUDA test binaries.
CTest CPU: 2/2 tests passed.
CTest CUDA with
CUDA_VISIBLE_DEVICES=0: 2/2 tests passed.
Remaining work:
This removes avoidable host/device transfers for
W/P. The remaining synchronisation points are scalar control values (nrm2, sign,dot) and prefix reconstruction on host. A true batched/fused IKPLS kernel still needs those scalar/control paths to stay device-side longer.
2026-06-05 - CUDA PLS1 parallel-fold probe¶
Decision:
After the exact-CV fold workspace, the obvious next question was whether independent PLS1 CV folds should run concurrently on one GPU.
Keep scoring exact, single-GPU only, and avoid making an unmeasured slower route the product default.
Implementation:
Refactored the CUDA PLS1 component loop so it can run on a caller-provided cuBLAS handle and CUDA stream.
Added an opt-in profiling path behind
N4M_CUDA_PLS_PARALLEL_FOLDS=1: each fold job gets a non-blocking stream, its own cuBLAS handle, and its own device workspace on device 0.The default
pls1_moment_components_many(...)path remains the measured single-workspace fold loop. Public scores, candidate tables and route counters are unchanged.
Observed smoke output:
Default
moment_gpu_crossover.csvPLS CUDA rows still reportdevice_cv=5only for256x1024; that row timed at about 129 ms in this pass.Opt-in
N4M_CUDA_PLS_PARALLEL_FOLDS=1on the same benchmark timed the256x1024PLS row at about 135 ms, so it is not the default route.
Validation:
Rebuilt CPU and CUDA test binaries.
CTest CPU: 2/2 tests passed.
CTest CUDA with
CUDA_VISIBLE_DEVICES=0: 2/2 tests passed.
Remaining work:
This confirms that simple host-thread/stream fold parallelism is not enough at the current shape. The real performance work remains a device-side batched IKPLS/fused kernel design that avoids host scalar synchronisation inside each component.
2026-06-05 - CUDA PLS1 exact-CV fold workspace¶
Decision:
The very-wide PLS1 CUDA component loop was still allocated and torn down once per CV fold.
Exact PLS1 folds are independent, but true stream-parallel IKPLS needs a larger device-side scalar/control refactor. Add a smaller exact-CV improvement first: reuse one CUDA workspace across folds while keeping the same per-fold numerical loop and public result schema.
Implementation:
Added internal
cuda_dispatch::pls1_moment_components_many(...).The helper processes several fold-local moment designs with one reusable
C/s/w/Cw/p_loaddevice workspace. It is sequential over fold jobs, so it is not a claim of parallel or fused batched IKPLS.Added
fit_pls1_moment_prefixes_for_folds(...)in the native sweep layer. CUDA builds use it for multi-fold PLS1 moment CV whenp >= 1024and a GPU is available; medium-width CUDA and CPU builds keep the previous fold loop.score_pls1_moment_sweep(...)and directrun_moment_sweep(...)both use the fold-workspace helper for compatible exact-CV PLS1 moment screens.Updated
n4m.moment_screen_backend_recommendation(...)to reportuses_cuda_pls_fold_workspaceand moved the policy tag ton4m.moment_gpu_crossover.v3.
Observed smoke output:
Regenerated
moment_gpu_crossover.csv.The current CUDA PLS rows report
device_cv=5only for256x1024, as expected. That row timed at about 127 ms in this smoke run. Smaller PLS rows remain on the host loop inside the CUDA build.
Validation:
Rebuilt CPU and CUDA test binaries.
CTest CPU: 2/2 tests passed.
CTest CUDA with
CUDA_VISIBLE_DEVICES=0: 2/2 tests passed.Targeted Python wrapper tests: 33 passed on CPU and 33 passed on CUDA.
Remaining work:
This removes repeated CUDA workspace allocation for large exact-CV PLS1 folds. It still does not implement stream-parallel CV, fused batched IKPLS, or the full 200k-chain CUDA grinder.
2026-06-05 - PLS1 moment host/device route counters¶
Decision:
After adding the very-wide CUDA PLS1 moment component loop, users need a runtime proof of which route actually ran, not only a launch-time recommendation.
Implementation:
Added
n_pls_moment_host_cv_fits,n_pls_moment_cuda_device_cv_fits,n_pls_moment_host_final_fits, andn_pls_moment_cuda_device_final_fitstoSweepResultandAomSweepResult.Packed those scalars through the C MethodResult ABI and Python wrappers.
Aggregated the counters through AOM campaigns/refits and exposed them in sklearn diagnostics plus timing CSV scripts.
Added C++/Python assertions that medium-width PLS moment screens stay on the host route, while the wide
p=1024CUDA test reports device CV fits.
Validation:
Covered by the CPU/CUDA CTest and Python wrapper validation in this pass.
2026-06-05 - CUDA PLS1 wide moment component loop¶
Decision:
The existing CUDA build avoided cuBLAS for PLS1 moment micro-kernels because per-operation host/device copies were slower than scalar host loops.
A device-resident component loop is worthwhile only once the feature width is large enough to amortize the single
C/supload and the per-component cuBLAS calls.
Implementation:
Added internal
cuda_dispatch::pls1_moment_components(...).The helper copies the centered/scaled moment covariance
Cand score vectorsto device once, then runs the PLS1 component loop through cuBLAScopy/scal/nrm2/idamax/gemv/dot/ger/axpyoperations while keeping the deflatedCandson device.fit_pls1_moment_prefixes(...)now uses that CUDA helper only forp >= 1024. Below that, CUDA builds keep the previous scalar host loop, which remains faster on measured medium-width screens.Added C++ coverage with a wide
p=1024PLS score-only moment sweep; in CUDA builds this exercises the device-resident component loop without changing the public ABI.Updated
n4m.moment_screen_backend_recommendation(...)to reportuses_cuda_pls_device_component_loopand thep=1024activation threshold.
Observed smoke output:
Regenerated
moment_gpu_crossover.csv.CUDA PLS speedup vs CPU is about 0.23x at
260x48, 1.32x at260x256, 3.21x at512x512, and 2.18x at256x1024.The
256x1024PLS row improved from the previous live smoke by moving the component loop onto device; the medium-width rows keep the faster scalar host loop.
Validation:
Rebuilt CPU and CUDA test binaries.
CTest CPU: 2/2 tests passed.
CTest CUDA with
CUDA_VISIBLE_DEVICES=0: 2/2 tests passed.Targeted Python wrapper tests: 33 passed on CPU and 33 passed on CUDA.
Remaining work:
This is a real device-resident PLS1 inner loop for wide moment screens. It is still not the full fused batched IKPLS/200k-chain CUDA grinder across many preprocessing variants.
2026-06-05 - Score-only moment screen copy elision and 200k projection diagnostics¶
Decision:
Broad preprocessing screens spend most of their time in score-only candidate ranking. In a pure moment route, copying full
X/Yjust to score candidates is unnecessary.Keep exact scores unchanged, but avoid full matrix copies for score-only moment Ridge/PLS screens and expose throughput/projection diagnostics so grind campaigns can estimate whether a profile is viable at 200k chains.
Implementation:
run_moment_sweep(...)now materializes fullX/Ycopies lazily. Non score-only runs still force copies before producing OOF/final predictions; materialized and dual paths still force copies when needed.Ridge moment score-only folds now compute held-out SSE from held-out moments via
ridge_heldout_sse_from_moments(...), matching the internalscore_ridge_moment_sweep(...)route and avoiding row prediction replay.Direct
n4m.sweep_run(...)Ridge now reports route/fit counters:n_ridge_moment_candidates,n_ridge_dual_materialized_candidates,n_ridge_moment_cv_fits,n_ridge_dual_materialized_cv_fits,n_ridge_dual_cross_cv_fits,n_ridge_moment_final_fits, andn_ridge_dual_materialized_final_fits.aom_chain_score_campaignperformance metrics now includeprojected_200k_chains_secondsandprojected_200k_chains_minutes.bench_aom_sweep_timing.pyandbench_aom_screen_refit_scaling.pynow emit chain/candidate throughput and 200k-chain projections in CSV output.bench_moment_sweep_timing.pynow emits the direct Ridge route counters.
Observed smoke output:
CPU
aom_sweep_timing.csv: first compact row reports about 1534 chains/s, projected 200k chains in about 130 s.CPU PLS screen/refit scaling: first screen row reports about 5802 chains/s, projected 200k chains in about 34 s.
CPU Ridge screen/refit scaling: first screen row reports about 1270 chains/s, projected 200k chains in about 157 s.
Direct wide Ridge score-only smoke reports 2 dual-materialized Ridge candidates, 6 dual-cross CV fits and zero final dual fits, confirming that score-only skips final model construction while retaining exact CV scores.
CUDA-smoke CSVs still validate the CUDA build path, but remain slower on these small shapes because the current CUDA backend is cuBLAS dispatch with host/device transfers, not a fused device-resident grinder.
Validation:
Rebuilt
build/dev-releaseandbuild/cuda-ontargetsn4m_c.CTest on
build/dev-release: 2/2 tests passed.CTest on
build/cuda-onwithCUDA_VISIBLE_DEVICES=0: 2/2 tests passed.Targeted Python wrapper tests on CPU and CUDA: 31 passed on each build.
Regenerated
aom_sweep_timing.csv,aom_sweep_timing_cuda_smoke.csv,aom_screen_refit_scaling.csv,aom_screen_refit_scaling_cuda_smoke.csv,aom_ridge_refit_scaling.csv, andaom_ridge_refit_scaling_cuda_smoke.csv, plusmoment_sweep_timing.csvandmoment_sweep_timing_cuda_smoke.csv.
Remaining work:
This improves exact/proxy score-only screening overhead and observability. It is still not the fused batched IKPLS/CUDA implementation needed for a true device-resident 200k-chain PLS grinder.
2026-06-05 - Live CPU/CUDA moment sweep crossover benchmark¶
Decision:
The CUDA build is not uniformly faster: small screens lose to CPU because the current backend uses cuBLAS calls with host/device transfers, while larger moment sweeps can amortise that overhead.
Add a live crossover benchmark so CPU vs CUDA choice is measured from the actual
sweep_runpaths rather than inferred from proxy BLAS diagnostics.
Implementation:
Added
benchmarks/cross_binding/bench_moment_gpu_crossover.py.The script spawns separate child interpreters for CPU and CUDA builds, because the Python binding loads one libn4m shared object per process.
It times score-only Ridge and PLS
n4m.sweep_runon identical synthetic datasets, records route counters, and emitsspeedup_vs_cpuplus arecommended_backendfield.Added
n4m.moment_screen_backend_recommendation(...)as a source-free launch-planning helper over the measured crossover. It uses onlyn_samples,n_features,head, and CUDA availability, and reports when the caller must start a fresh process with the other libn4m build.Updated the older
cuda_diagnostic.pytext so it remains a broad proxy classifier and points live moment-sweep timing to the new benchmark.
Observed smoke output:
260x48: CPU wins; CUDA speedup vs CPU was about 0.26x for Ridge and 0.17x for PLS.260x256: CUDA starts winning; about 1.23x for Ridge and 1.15x for PLS.512x512: CUDA wins; about 1.36x for Ridge and 3.01x for PLS.256x1024: CUDA wins; about 2.53x for Ridge and 2.03x for PLS.
Validation:
Generated
benchmarks/cross_binding/moment_gpu_crossover.csvfrom the live CPU and CUDA builds withCUDA_VISIBLE_DEVICES=0.Added Python wrapper tests for the 260x48 CPU recommendation, the 260x256 CUDA recommendation, unavailable-CUDA fallback, and bad input validation.
Remaining work:
This provides a measured CPU/CUDA routing baseline for the current cuBLAS-dispatch implementation. It is still not a fused batched IKPLS CUDA grinder.
2026-06-05 - Native final-only fixed AOM fit ABI¶
Decision:
The screen -> exact-CV refit workflow already verifies the selected row by exact train CV.
Rebuilding the reusable selected model through
NativeAOMFixedCandidateRegressorstill replayed a one-candidate CV sweep only to obtain final coefficients/predictions.Add a native final-only fit path for already-selected AOM chain/head/parameter rows. It must not be used for ranking and must preserve the exact-CV score from the refit report at the wrapper layer.
Implementation:
Added internal
run_moment_final_fit(...)for one fixed Ridge or PLS parameter. It fits on all rows, returns coefficients/intercept/predictions, and does not build folds or score CV.Added internal
run_aom_chain_fixed_fit(...)and public C ABIn4m_aom_chain_fixed_fit_run(...). The AOM path materializes the selected strict-linear chain once, fits the fixed head, then folds transformed coefficients back to original input space.Added Python
n4m.aom_chain_fixed_fit_run(...).Added catalog method
aom_pop.aom_chain_fixed_fitfor this individual winner reuse surface.aom_pop.aom_chain_screen_refitremains a Python-backed orchestration method withabi_symbols: []; its native building blocks are now attributed toaom_pop.aom_chain_sweepandaom_pop.aom_chain_fixed_fit.NativeAOMFixedCandidateRegressornow supportsfit_mode="final_only"plusprecomputed_cv_rmse. Its default remainsfit_mode="cv"for direct candidate reuse from ordinary campaign rows.NativeAOMScreenRefitRegressornow usesfit_mode="final_only"after its exact-CV refit pass and injects the selected row’s verifiedrefit_cv_rmse.Updated
bench_aom_screen_refit_scaling.pyso reusable final-fit timings measure the new final-only path.
Observed smoke output:
CPU
build/dev-release,refit_top_k=16, PLS: final selected-candidate fit now paysfinal_fit_n_pls_moment_cv_fits=0, one final PLS fit, and takes about 0.26-0.30 ms instead of the previous ~0.74 ms CV-replaying fit.CPU
build/dev-release,refit_top_k=16, Ridge: final fit takes about 0.28 ms and pays no PLS CV fits.CUDA smoke with
CUDA_VISIBLE_DEVICES=0: PLS/Ridge final-only smokes loadbuild/cuda-on/cpp/src/libn4m.so.1.18.0, usefit_mode="final_only", pay zero final CV fits, and keep the exact refit CV score in diagnostics.
Validation:
Rebuilt
build/dev-releaseandbuild/cuda-ontargetsn4m_c.CTest on
build/dev-release: 2/2 tests passed.Targeted Python wrapper tests:
N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so.1.18.0 PYTHONPATH=bindings/python/src pytest bindings/python/tests/test_moment_model_wrappers.py -q: 31 passed.Full Python tests with
N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so.1.18.0: 272 passed, 4 existing UVE warnings.Added Python tests proving
n4m.aom_chain_fixed_fit_runmatches the one-candidate full-CV sweep’s final predictions, transformed coefficients, folded input coefficients and intercept for both Ridge and PLS, while returning empty OOF/fold outputs and zero CV fit counters.Regenerated CPU and CUDA-smoke screen/refit scaling CSVs and verified planned vs observed refit group/scored/extra counters still match.
Added
native_aom_chain_fixed_fit_plsandnative_aom_chain_fixed_fit_ridgerows tobenchmarks/cross_binding/bench_aom_sweep_timing.py, then regeneratedaom_sweep_timing.csvandaom_sweep_timing_cuda_smoke.csv. On the CPU smoke, fixed final fit took about 0.29-0.30 ms for PLS and 0.18-0.29 ms for Ridge at the covered shapes; on the CUDA smoke, about 0.68-0.92 ms for PLS and 0.56-0.76 ms for Ridge. All fixed-fit rows reportcv=0and zero CV fit counters.Catalog/checks:
catalog/scripts/validate.py,catalog/scripts/reconcile_abi.py --check,catalog/scripts/split_legacy_methods.py --check,catalog/scripts/validate.py --strict-abi, andgit diff --checkall passed after allowing symbol-less Python-backed orchestration methods in the catalog schema.Reference coverage gate:
catalog/scripts/validate.py --check-referencesnow classifies nativeaom_poporchestration methods and the directmodels.regularized.ridgehead as nirs4all-donor surfaces. The gate reports 135 nirs4all-donor methods, 60 registry-backed methods and 3 paper-only methods, covering all 198 catalog production entries without fabricating external references for native N4A methods.Regenerated
bindings/python_nirs4all_methods; package smoke confirmedn4m.aom_chain_fixed_fit_runis exported from the package, the embedded lib loads, andNativeAOMScreenRefitRegressoruses final-only fixed fit with zero final PLS CV fits.
Remaining work:
This removes the redundant final selected-candidate CV replay. It is still not a fused/device-resident batched IKPLS/GPU grinder across many chains.
2026-06-05 - Grouped, signature-batched and union-batched exact-CV refit scoring¶
Decision:
The exact-CV second pass was still replaying every retained candidate row as a separate one-candidate AOM sweep.
When retained rows share the same decoded chain and model head, their Ridge lambdas or PLS component counts can be scored together by the existing native sweep without changing CV semantics.
Chain/head grouping still leaves one Python/native call per retained chain. When several retained chains share the same head and retained parameter set, they can be batched into one native AOM sweep call without adding candidates or changing any exact-CV score.
When retained chains have different parameter subsets, a head-level union of retained parameters can reduce calls further. That mode may score extra chain/parameter pairs, so it must expose the surplus explicitly and remain an explicit execution mode rather than a silent selection rule.
Add exact grouped, signature-batched and union-batched score modes before attempting a larger native IKPLS rewrite.
Implementation:
Added
execution_modeton4m.aom_refit_candidates.execution_mode="individual"preserves the previous full replay with per-candidate train/OOF prediction arrays.execution_mode="grouped_score"groups rows by decoded chain/head and callsaom_chain_sweep_run(..., score_only=True, pls_score_mode="cv")once per group. It returns the same exactrefit_cv_rmsevalues for ranking, but grouped rows do not carry per-candidate prediction arrays or finitetrain_rmse.execution_mode="batched_score"first builds the same chain/head retained parameter sets, then batches chains with identical(head, params)signatures into a singleaom_chain_sweep_runcall. Scores are mapped back by localchain_idand parameter.execution_mode="union_batched_score"batches by head with the union of retained parameters for that head. It maps only the requested retained rows back to the report and exposesn_refit_scored_candidatesplusn_refit_extra_scored_candidatesso surplus native scoring is auditable.n4m.aom_refit_execution_plan(candidates, top_k=...)uses the same grouping helpers asaom_refit_candidatesand reports expected groups, native scored candidates and extra scored candidates for each exact score mode without touchingXory.execution_mode="auto"selects batched scoring unlessreturn_predictions=True.aom_chain_screen_refit_campaignnow defaults torefit_execution="auto", so the normal proxy -> exact-CV workflow uses the faster signature-batched score path.Refit reports now expose aggregate counters:
execution_mode,n_refit_groups,n_pls_moment_cv_fits,n_pls_materialized_cv_fits,n_operator_moment_candidates,n_materialized_candidates,n_refit_scored_candidates, andn_refit_extra_scored_candidates.NativeAOMScreenRefitRegressorpropagatesrefit_executionand reads aggregate refit counters from the report.NativeAOMScreenRefitRegressor.get_diagnostics()separates final selected-candidate fit counters viafinal_*fields. After the final-only ABI work above, those fields verify that the reusable model fit no longer repays CV.The screen/refit scaling benchmark now emits
individual,grouped_score,batched_scoreandunion_batched_scorerows and supports--head pls/--head ridge; CSV rows include planned and observed group/scored/extra counters plus reusable final-fit timing/counters.
Observed smoke output:
CPU
build/dev-release,n=260,p=48,24chains,2PLS components,cv=5: atrefit_top_k=16, individual refit took 13.54 ms and 160 PLS CV fits; grouped score took 5.16 ms / 50 PLS CV fits / 10 groups; signature batched score took 2.26 ms / 50 PLS CV fits / 2 groups; union batched score took 1.92 ms / 50 PLS CV fits / 1 group after scoring 20 native candidates for 16 retained rows, with the same best exact-CV RMSE.CUDA smoke with
CUDA_VISIBLE_DEVICES=0: atrefit_top_k=16, individual refit took 169.52 ms and 160 PLS CV fits; grouped score took 49.94 ms / 50 PLS CV fits / 10 groups; signature batched score took 10.54 ms / 50 PLS CV fits / 2 groups; union batched score took 7.70 ms / 50 PLS CV fits / 1 group after scoring 20 native candidates for 16 retained rows, with zero materialized refit CV fits.Ridge CPU smoke with
--head ridge: atrefit_top_k=16, individual refit took 18.86 ms across 16 groups; grouped score took 6.74 ms across 8 groups; signature batched score took 5.17 ms across 4 groups; union batched score took 6.79 ms across 1 group after scoring 32 native candidates for 16 retained rows, with the same best exact-CV RMSE.Ridge CUDA smoke with
--head ridgeandCUDA_VISIBLE_DEVICES=0: atrefit_top_k=16, individual refit took 156.54 ms; grouped score took 41.49 ms; signature batched score took 11.57 ms; union batched score took 8.18 ms after scoring 32 native candidates for 16 retained rows, again with the same best exact-CV RMSE.After the final-only ABI work, the same benchmark shows the reusable final selected-candidate fit cost without CV replay: for PLS at
refit_top_k=16, final fit paid zero PLS CV fits plus one final PLS fit and took about 0.26-0.30 ms on CPU / 0.55-0.57 ms on the CUDA smoke; for Ridge, final fit took about 0.28 ms on CPU / 0.58-1.03 ms on CUDA smoke.
Validation:
Targeted Python wrapper tests:
N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so.1.18.0 PYTHONPATH=bindings/python/src pytest bindings/python/tests/test_moment_model_wrappers.py -q: 30 passed.Regenerated
benchmarks/cross_binding/aom_screen_refit_scaling.csvandbenchmarks/cross_binding/aom_screen_refit_scaling_cuda_smoke.csv.Generated
benchmarks/cross_binding/aom_ridge_refit_scaling.csvandbenchmarks/cross_binding/aom_ridge_refit_scaling_cuda_smoke.csv.Verified that planned group/scored/extra counters match observed refit counters in all four scaling CSVs.
Full Python tests with
N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so.1.18.0: 271 passed, 4 existing UVE warnings.Rebuilt
bindings/python_nirs4all_methods; package smokes confirmedaom_chain_screen_refit_campaigndefaults to batched exact-CV refit andaom_refit_candidates(..., execution_mode="union_batched_score")exposes scored/extra-scored candidate counters; package smoke also confirmedNativeAOMScreenRefitRegressor.get_diagnostics()exposesfinal_*selected-candidate fit counters.CUDA-lib Python smokes with
CUDA_VISIBLE_DEVICES=0confirmedNativeAOMScreenRefitRegressoruses batched exact-CV refit by default and explicitunion_batched_scorerefit for PLS and Ridge on the CUDA build while using one visible GPU, and exposes final selected-candidate fit counters on the CUDA build.Catalog/checks:
catalog/scripts/validate.py,catalog/scripts/reconcile_abi.py --check,catalog/scripts/split_legacy_methods.py --check, andgit diff --checkall passed.
Remaining work:
This removes redundant parameter refits inside retained chain groups and reduces exact-CV refit orchestration overhead across retained chains with the same parameter signature, with an explicit union-parameter option for small grids. It is still not a fused batched IKPLS/GPU grinder across many chains.
2026-06-05 - Screen/refit PLS scaling benchmark¶
Decision:
The remaining performance gap for large PLS preprocessing screens is not visible enough from the generic AOM smoke benchmark.
Add a focused benchmark that varies
refit_top_kafter a fixed GCV-proxy screen, so future batched IKPLS/CUDA work has a concrete target: exact-CV refit cost per retained candidate and per fold-local PLS fit.
Implementation:
Added
benchmarks/cross_binding/bench_aom_screen_refit_scaling.py.The benchmark builds a deterministic lab chain grid, runs one
aom_chain_score_campaign(..., pls_score_mode="gcv_proxy", moment_policy="force_moments"), then timesaom_refit_candidatesforrefit_top_k=1,2,4,8,16.CSV rows report screen/refit timings,
n_pls_gcv_proxy_fits,n_refit_pls_moment_cv_fits, materialized refit fit counters, cost per refit candidate, cost per exact PLS CV fit, and screen-vs-refit rank fields.
Observed smoke output:
CPU
build/dev-release,n=260,p=48,24chains,2PLS components,cv=5: screen proxy 5.61 ms for 48 candidates. Exact-CV refit scaled from 0.96 ms fortop_k=1/ 10 PLS CV fits to 13.54 ms fortop_k=16/ 160 PLS CV fits. Materialized refit CV fits stayed at zero.CUDA smoke with
CUDA_VISIBLE_DEVICES=0: same counters and zero materialized refit CV fits, but screen/refit timings were slower than CPU on this small shape. This confirms the current CUDA build is functional but still not a fused batched/device-resident IKPLS path.
Validation:
Generated
benchmarks/cross_binding/aom_screen_refit_scaling.csvwithN4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so.1.18.0.Generated
benchmarks/cross_binding/aom_screen_refit_scaling_cuda_smoke.csvwithCUDA_VISIBLE_DEVICES=0andN4M_LIB_PATH=build/cuda-on/cpp/src/libn4m.so.1.18.0.Catalog/checks:
catalog/scripts/validate.py,catalog/scripts/reconcile_abi.py --check,catalog/scripts/split_legacy_methods.py --check, andgit diff --checkall passed.
Remaining work:
This benchmark makes the bottleneck measurable. It does not implement the fused GPU/IKPLS grinder.
2026-06-05 - Native sklearn screen-refit AOM estimator¶
Decision:
The one-call screen/refit helper made the proxy -> exact-CV workflow callable, but end users still needed manual glue to turn the verified winner into a reusable estimator.
Add a sklearn-style method wrapper so broad preprocessing selection can be used as a normal fitted regressor while preserving the campaign audit reports.
Implementation:
Added
NativeAOMScreenRefitRegressor.fitrunsaom_chain_screen_refit_campaign, storescampaign_report_,screen_report_andrefit_report_, then fits the selected verified row throughNativeAOMFixedCandidateRegressor.from_refit_report.The fitted estimator exposes
coef_,intercept_,predictions_,oof_predictions_,selected_chain_,selected_head_,selected_param_,selected_cv_rmse_,predict,score, and campaign/refit diagnostics.Re-exported the class through
n4m.sklearnand top-leveln4m.Docs now show the estimator form next to the functional campaign helper.
Validation:
Targeted Python wrapper tests:
N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so.1.18.0 PYTHONPATH=bindings/python/src pytest bindings/python/tests/test_moment_model_wrappers.py -q: 29 passed.Full Python tests with
N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so.1.18.0: 270 passed, 4 existing UVE warnings.Rebuilt
bindings/python_nirs4all_methods; package smoke confirmedn4m.NativeAOMScreenRefitRegressorandn4m.sklearn.NativeAOMScreenRefitRegressorfit/predict successfully.CUDA-lib Python smoke with
CUDA_VISIBLE_DEVICES=0confirmed the estimator on the CUDA build while using one visible GPU.Added catalog entry
aom_pop.aom_chain_screen_refitand regenerated per-method files;catalog/scripts/validate.pyreports 197 methods and passes closure.Catalog/checks:
catalog/scripts/reconcile_abi.py --check,catalog/scripts/split_legacy_methods.py --check, andgit diff --checkall passed.
Remaining work:
This makes the screen/refit workflow a reusable method surface. It does not implement the fused GPU/IKPLS grinder.
2026-06-05 - One-call screen -> exact-CV refit campaign helper¶
Decision:
The proxy and chunked campaign workflow was available as two separate calls:
aom_chain_score_campaignfollowed byaom_refit_candidates.Add a single orchestration helper so broad preprocessing screens can be run, checkpointed, and exact-CV verified with one user-facing call while still exposing both intermediate reports.
Implementation:
Added
n4m.aom_chain_screen_refit_campaign(...).The helper runs the score-only campaign first, then exact-CV refits the retained
refit_top_krows viaaom_refit_candidates.The combined report exposes nested
screenandrefitreports plus top-levelrows,best_cv,best_screen,best_refit,screen_complete,pls_score_mode, andrefit_pls_score_mode.Partial checkpoint screens are supported for inspection: current top rows are refit and the report marks
screen_complete=False.Re-exported the helper at top-level
n4m.aom_chain_screen_refit_campaign.Tests cover proxy GCV screen -> exact-CV refit -> direct
NativeAOMFixedCandidateRegressor.from_refit_report(...)reuse.Added a timing benchmark row
native_aom_chain_screen_refit_pls_gcv_proxytobench_aom_sweep_timing.py, includingn_refit_candidates,n_refit_pls_moment_cv_fits, and materialized refit CV fit counters.
Validation:
Targeted Python wrapper tests:
N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so.1.18.0 PYTHONPATH=bindings/python/src pytest bindings/python/tests/test_moment_model_wrappers.py -q: 29 passed.Full Python tests with
N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so.1.18.0: 270 passed, 4 existing UVE warnings.Rebuilt
bindings/python_nirs4all_methods; package smoke confirmedn4m.aom_chain_screen_refit_campaign(...)and directNativeAOMFixedCandidateRegressor.from_refit_report(...)reuse.CUDA-lib Python smoke with
CUDA_VISIBLE_DEVICES=0confirmed the same helper on the CUDA build while using one visible GPU.Regenerated
aom_sweep_timing.csvandaom_sweep_timing_cuda_smoke.csvwith the new screen-refit benchmark row using--repeats 1.Catalog/checks:
catalog/scripts/validate.py,catalog/scripts/reconcile_abi.py --check,catalog/scripts/split_legacy_methods.py --check, andgit diff --checkall passed.
Remaining work:
This improves campaign ergonomics. It does not implement the fused GPU/IKPLS grinder.
2026-06-05 - Fixed-candidate reuse from exact-CV refit reports¶
Decision:
After adding
aom_refit_candidates, exact-CV winners from the second pass could be reused manually viafrom_candidate(row), but there was no direct sklearn constructor mirroringfrom_campaign.Add the missing reuse surface so the intended proxy -> exact-CV -> final model workflow is one call at each stage.
Implementation:
Added
NativeAOMFixedCandidateRegressor.from_refit_report(report, rank=0, **kwargs).The constructor sorts rows by
refit_cv_rmseregardless of the report’s display sorting, then delegates tofrom_candidate.AOM sklearn diagnostics now include
n_pls_gcv_proxy_candidates,n_pls_gcv_proxy_fits, andaom_pls_score_mode.Tests now fit a fixed candidate directly from an exact-CV refit report and assert its selected CV RMSE matches
report["best_cv"]["refit_cv_rmse"].Public docs mention
from_refit_reportas the reuse endpoint afteraom_refit_candidates.
Validation:
Targeted Python wrapper tests:
N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so.1.18.0 PYTHONPATH=bindings/python/src pytest bindings/python/tests/test_moment_model_wrappers.py -q: 29 passed.Full Python tests with
N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so.1.18.0: 270 passed, 4 existing UVE warnings.Rebuilt
bindings/python_nirs4all_methods; package smoke confirmedNativeAOMFixedCandidateRegressor.from_refit_report(...).CUDA-lib Python smoke with
CUDA_VISIBLE_DEVICES=0confirmedfrom_refit_report(...)reuses an exact-CV refit winner after a proxy campaign and reports zero proxy fits in the final estimator diagnostics.Catalog/checks:
catalog/scripts/validate.py,catalog/scripts/reconcile_abi.py --check,catalog/scripts/split_legacy_methods.py --check, andgit diff --checkall passed.
Remaining work:
This improves reuse of verified campaign winners. It does not implement the fused GPU/IKPLS grinder.
2026-06-05 - Exact-CV refit helper after score-only campaigns¶
Decision:
pls_score_mode="gcv_proxy"gives a fast first-pass screen, but selecting directly on proxy scores would be too weak for the intended workflow.Add a train-only exact-CV verification helper so broad campaigns can retain candidates cheaply, then re-rank the retained rows by exact native CV without requiring a holdout/test split.
Implementation:
Added
n4m.aom_refit_candidates(X_train, y_train, candidates, ...).The helper accepts campaign reports or explicit decoded rows, replays each row as a one-candidate
aom_chain_sweep_runwithpls_score_mode="cv", and returnsrefit_cv_rmse,oof_rmse,train_rmse, screen score metadata, route counters and PLS fit counters.Re-exported the helper at top-level
n4m.aom_refit_candidates.Tests now cover proxy campaign top-k rows followed by exact-CV refit verification, asserting proxy fit counters drop to zero and exact PLS CV fits are paid in the verification pass.
Public docs and the coverage matrix now describe the proxy -> exact-CV second-pass workflow.
Validation:
Targeted Python wrapper tests:
N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so.1.18.0 PYTHONPATH=bindings/python/src pytest bindings/python/tests/test_moment_model_wrappers.py -q: 29 passed.Full Python tests with
N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so.1.18.0: 270 passed, 4 existing UVE warnings.Rebuilt
bindings/python_nirs4all_methods; package smoke confirmed the top-leveln4m.aom_refit_candidatesworkflow after a proxy campaign.CUDA-lib Python smoke with
CUDA_VISIBLE_DEVICES=0confirmed proxy campaign rows can be replayed through exact-CV refits on moment routes with zero proxy fits in the verification pass and positive PLS CV fit counters.Catalog/checks:
catalog/scripts/validate.py,catalog/scripts/reconcile_abi.py --check,catalog/scripts/split_legacy_methods.py --check, andgit diff --checkall passed.
Remaining work:
This closes the workflow gap around the PLS proxy screen. It does not implement a fused/batched IKPLS CUDA grinder.
2026-06-05 - Explicit AOM PLS1 GCV proxy score-only screen¶
Decision:
The exact PLS1 moment screen still pays one fold-local PLS fit per chain/fold. That preserves CV semantics, but it is not the old fast screen profile needed for 50k/200k preprocessing campaigns.
Add a separate opt-in first-pass PLS screen rather than changing
score_only=Truesemantics silently. The default remains exact CV.
Implementation:
Added native
aom_pls_score_modeconfig withcvdefault andgcv_proxyopt-in.Added
score_pls1_gcv_moment_screen, which fits PLS1 prefixes once from all-sample transformed moments and scores components by nominal GCV RMSE.n4m.aom_sweep_run,n4m.aom_chain_sweep_run, andn4m.aom_chain_score_campaignexposepls_score_mode="gcv_proxy".The proxy requires
score_only=True, stays inside operator moments, and rejects materialized fallback. PLS rows exposescore_metric="pls_gcv_proxy_rmse".Added
n_pls_gcv_proxy_candidates,n_pls_gcv_proxy_fits,aom_pls_score_mode, and campaign metricspls_gcv_proxy_fits_per_chain/pls_gcv_proxy_fits_per_candidate.Checkpoint fingerprints include
pls_score_mode.Timing benchmark rows now include the proxy counters and a dedicated
native_aom_chain_sweep_pls_gcv_proxycase.
Validation:
Rebuilt CPU dev-release library with
/home/delete/.venv/bin/cmake --build build/dev-release -j2.Targeted Python wrapper tests:
N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so.1.18.0 PYTHONPATH=bindings/python/src pytest bindings/python/tests/test_moment_model_wrappers.py -q: 29 passed.Native C++ tests:
./build/dev-release/cpp/tests/n4m_tests: 344 passed; and./build/dev-release/cpp/tests/n4m_internal_tests: all internal checks passed.Full Python tests with
N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so.1.18.0: 270 passed, 4 existing UVE warnings.Rebuilt CUDA library with
/home/delete/.venv/bin/cmake --build build/cuda-on -j2; CUDA smoke withCUDA_VISIBLE_DEVICES=0confirmedpls_score_mode="gcv_proxy"keeps all PLS candidates on moment routes and pays zero PLS CV fits.Rebuilt
bindings/python_nirs4all_methods; package smoke confirmed the embedded library exposes the new ABI setter and campaign proxy counters.Regenerated
benchmarks/cross_binding/aom_sweep_timing.csvandbenchmarks/cross_binding/aom_sweep_timing_cuda_smoke.csv; both includenative_aom_chain_sweep_pls_gcv_proxyrows withscore_only=1,n_pls_gcv_proxy_candidates=10,n_pls_gcv_proxy_fits=5, and zero PLS CV fits.Catalog/checks:
catalog/scripts/validate.py,catalog/scripts/reconcile_abi.py --check,catalog/scripts/split_legacy_methods.py --check, andgit diff --checkall passed.
Remaining work:
This restores a fast first-pass PLS screen profile, but it is a proxy. The exact retained candidates still need verification through the default CV path or explicit holdout evaluation. It is not the fused GPU/IKPLS grinder.
2026-06-05 - AOM campaign per-route top-k audit¶
Decision:
After exposing native
candidate_routes, campaign reports still only kept a global top-k and per-head top-k. That was enough for selection, but not for auditing whether CPU/CUDA runs found useful candidates on the materialized, dense, banded or structured scoring routes.Add route-grouped top-k outputs as diagnostics only. They do not change native scores, global ranking, checkpoint fingerprints or model fitting.
Implementation:
n4m.aom_chain_score_campaignnow returns:top_candidates_by_score_route: per-route candidate rows sorted bycv_rmse;best_by_score_route: first row for each scoring route.
Checkpoints persist the per-route lists and resume filters them to the chunks actually present before appending new chunks, matching the global and per-head top-k behavior.
Loading older checkpoints without per-route lists reconstructs route groups from
top_candidateswhen route fields are available.JSON/JSONL/CSV candidate exports still use the candidate rows by default and do not duplicate
top_candidates_by_score_routeorbest_by_score_routein JSON metadata.Public docs and the coverage matrix now describe per-route campaign audits.
Validation:
Targeted Python wrapper tests:
N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so.1.18.0 PYTHONPATH=bindings/python/src pytest bindings/python/tests/test_moment_model_wrappers.py -q: 28 passed.Full Python tests with
N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so.1.18.0: 269 passed, 4 existing UVE warnings.Rebuilt
bindings/python_nirs4all_methods; package smoke confirmedtop_candidates_by_score_routeandbest_by_score_route.CUDA-lib Python smoke with
CUDA_VISIBLE_DEVICES=0andmoment_policy="force_moments"confirmed per-route top-k rows on moment routes with no materialized candidates.Catalog/checks:
catalog/scripts/validate.py,catalog/scripts/reconcile_abi.py --check,catalog/scripts/split_legacy_methods.py --check, andgit diff --checkall passed.
Remaining work:
This adds audit visibility over scoring routes. It does not implement the fused GPU/IKPLS grinder.
2026-06-05 - Fixed-candidate reuse from campaign winners¶
Decision:
The fixed-candidate wrapper already refit explicit decoded rows, but after adding per-head campaign top-k outputs, users still had to manually select
report["best"]orreport["best_by_head"]["pls"].Add a small sklearn convenience constructor so winning individual methods are directly reusable from a campaign report, without adding a scoring rule or route heuristic.
Implementation:
Added
NativeAOMFixedCandidateRegressor.from_campaign(report, head=None, rank=0, **kwargs).head=Noneselects from the globaltop_candidates;head="ridge"orhead="pls"selects fromtop_candidates_by_head, falling back tobest_by_headforrank=0.The selected row delegates to
from_candidate, so the refit remains the exact decoded chain/head/parameter throughaom_chain_sweep_run.Tests now fit the global campaign winner plus Ridge/PLS per-head winners and assert selected CV scores match the campaign rows.
Public docs and coverage matrix now mention campaign-to-fixed-candidate reuse.
Validation:
Targeted Python wrapper tests:
N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so.1.18.0 PYTHONPATH=bindings/python/src pytest bindings/python/tests/test_moment_model_wrappers.py -q: 28 passed.Full Python tests with
N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so.1.18.0: 269 passed, 4 existing UVE warnings.Rebuilt
bindings/python_nirs4all_methods; package smoke confirmedNativeAOMFixedCandidateRegressor.from_campaign(..., head="ridge").CUDA-lib Python smoke with
CUDA_VISIBLE_DEVICES=0confirmedfrom_campaign(..., head="pls", moment_policy="force_moments")refits the per-head PLS campaign winner and matches its CV score.Catalog/checks:
catalog/scripts/validate.py,catalog/scripts/reconcile_abi.py --check,catalog/scripts/split_legacy_methods.py --check, andgit diff --checkall passed.
Remaining work:
This improves reuse of campaign winners. It does not implement the fused GPU/IKPLS grinder.
2026-06-05 - AOM campaign per-head top-k audit¶
Decision:
Mixed Ridge/PLS preprocessing campaigns previously kept only one global top-k. That is correct for global ranking, but it can hide the best PLS preprocessing chains when Ridge dominates, or the reverse.
Add per-head top-k outputs as diagnostics only. They do not change native scores, global
top_candidates, route selection, checkpoint fingerprints, or fitted models.
Implementation:
n4m.aom_chain_score_campaignnow returns:top_candidates_by_head: per-head candidate rows sorted bycv_rmse;best_by_head: first row for each head.
The per-head lists are truncated with the same
top_kas the global list and are persisted in campaign checkpoints.Resume now filters both global and per-head top rows to the chunks actually present in a loaded checkpoint before appending new chunks. This keeps manual partial checkpoints coherent.
JSON/JSONL/CSV candidate row exports continue to use
top_candidatesby default and do not duplicatetop_candidates_by_headorbest_by_headin JSON metadata.Public docs and the coverage matrix now describe global versus per-head campaign ranking.
Validation:
Targeted Python wrapper tests:
N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so.1.18.0 PYTHONPATH=bindings/python/src pytest bindings/python/tests/test_moment_model_wrappers.py -q: 28 passed.Full Python tests with
N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so.1.18.0: 269 passed, 4 existing UVE warnings.Rebuilt
bindings/python_nirs4all_methods; package smoke confirmedtop_candidates_by_headandbest_by_headon a mixed Ridge/PLS campaign.CUDA-lib Python smoke with
CUDA_VISIBLE_DEVICES=0andmoment_policy="force_moments"confirmed per-head top-k rows for Ridge and PLS while staying fully operator-moment.Catalog/checks:
catalog/scripts/validate.py,catalog/scripts/reconcile_abi.py --check,catalog/scripts/split_legacy_methods.py --check, andgit diff --checkall passed.
Remaining work:
This improves campaign auditability for model-specific preprocessing effects. It is not a new selection heuristic and not a fused GPU/IKPLS grinder.
2026-06-05 - PLS1 moment dense-kernel dispatch audit¶
Decision:
Continue the GPU/moment audit on the actual PLS screen bottleneck rather than adding a new selection heuristic.
The PLS1 moment-prefix route has dense products (
C @ w,P.T @ W,W @ inv(P.T @ W), and rank-1 covariance deflation) that can use the existinglinalgabstraction on CPU/BLAS builds.Do not force those iterative micro-kernels through the current CUDA dispatch. A direct cuBLAS attempt was score-correct only after compacting prefix matrices, but timing showed it was much slower because every small kernel copies host/device. CUDA builds therefore keep the scalar host-side PLS1 moment loop until a device-resident batched IKPLS workspace exists.
Implementation:
In
cpp/src/core/sweep.cpp, routed PLS1 moment-prefix dense products throughlinalg::gemv,linalg::gemm, andlinalg::gerfor non-CUDA builds.Added compact prefix buffers for
W[:, :k]andP[:, :k]before GEMM so row-major prefix matrices are contiguous and safe for backends that do not support padded submatrix copies.Added
N4M_USE_CUDAguards to keep the previous scalar PLS1 moment loop in CUDA builds. Ridge wide GEMM/cuBLAS dispatch from the prior step remains enabled.Updated
docs/methods/sweep_run.mdand the AOM/moment coverage matrix with the CPU/BLAS linalg path and CUDA scalar guard.Regenerated
moment_sweep_timing.csvandmoment_sweep_timing_cuda_smoke.csv.
Validation:
CPU
dev-releasebuild completed.CPU
n4m_tests: 344 passed, 0 failed.Targeted Python wrapper tests:
N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so.1.18.0 PYTHONPATH=bindings/python/src pytest bindings/python/tests/test_moment_model_wrappers.py -q: 28 passed.Full Python tests with
N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so.1.18.0: 269 passed, 4 existing UVE warnings.CUDA
cuda-onbuild completed withCUDA_VISIBLE_DEVICES=0.CUDA-build
n4m_tests: 344 passed, 0 failed.CUDA-lib Python smoke with
CUDA_VISIBLE_DEVICES=0confirmed PLS1 full andscore_only=Truecandidate scores match, with all PLS candidates on the moment route.Timing smoke regenerated. CPU PLS medians were 0.43/1.96/9.83 ms for 64x64, 128x128 and 192x256; CPU score-only was 0.29/1.91/9.93 ms. CUDA smoke PLS medians, using the scalar guard, were 2.04/3.03/8.10 ms; CUDA score-only was 2.17/2.94/7.62 ms. The discarded direct cuBLAS micro-kernel attempt was much slower, reaching about 17-23 ms on these PLS rows.
Remaining work:
The correct GPU direction for PLS remains a device-resident batched IKPLS workspace or fused grouped kernels. Host-side cuBLAS dispatch per PLS micro-kernel is not the solution for 200k-chain screening.
2026-06-05 - Wide dual Ridge GEMM/CUDA dispatch in sweep¶
Decision:
The remaining grinder gap is not solved, but the existing wide Ridge path still had scalar C++ loops for the dual train Gram, held-out cross-kernel, dual held-out predictions, and final coefficient reconstruction.
Route those products through the existing
n4m::linalg::gemmabstraction. This keeps the CPU behavior inside the established row-major dispatch and makes CUDA builds use cuBLAS for this Ridge-wide work without adding a new public ABI field or a custom kernel.
Implementation:
In
cpp/src/core/sweep.cpp, replaced scalar products withlinalg::gemmfor:K = X_train @ X_train.T;K_cross = X_heldout @ X_train.T;held-out dual predictions
K_cross @ alpha;final dual coefficient reconstruction
X_train.T @ alpha.
Kept the same scoring rules, candidate table, score-only behavior and public MethodResult schema.
Updated
docs/methods/sweep_run.mdand the AOM/moment coverage matrix to call out the GEMM/cuBLAS dispatch.Regenerated
moment_sweep_timing.csvandmoment_sweep_timing_cuda_smoke.csv.
Validation:
CPU
dev-releasebuild completed.CPU
n4m_tests: 344 passed, 0 failed.Targeted Python wrapper tests:
N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so.1.18.0 PYTHONPATH=bindings/python/src pytest bindings/python/tests/test_moment_model_wrappers.py -q: 28 passed.Full Python tests with
N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so.1.18.0: 269 passed, 4 existing UVE warnings.CUDA
cuda-onbuild completed withCUDA_VISIBLE_DEVICES=0.CUDA-build
n4m_tests: 344 passed, 0 failed.CUDA-lib Python wide Ridge smoke with
CUDA_VISIBLE_DEVICES=0confirmed full andscore_only=Truecandidate scores match forp > n.Rebuilt
bindings/python_nirs4all_methods; package smoke imported the bundled library and ran a wide Ridgesweep_run(..., score_only=True).Timing smoke regenerated. CPU Ridge medians were 1.35/11.38/43.19 ms for 64x64, 128x128 and 192x256; CPU score-only was 1.20/10.31/38.87 ms. CUDA smoke Ridge medians were 4.71/13.90/35.65 ms; CUDA score-only was 4.55/13.68/30.41 ms. The wider CUDA row now benefits from the GEMM/cuBLAS route, while small rows still pay launch/transfer overhead.
Catalog/checks:
catalog/scripts/validate.py,catalog/scripts/reconcile_abi.py --check,catalog/scripts/split_legacy_methods.py --check, andgit diff --checkall passed.
Remaining work:
This improves the existing wide Ridge substrate. It is not batched IKPLS, not grouped preprocessing-chain execution, and not the fused 200k-chain CUDA grinder.
2026-06-05 - AOM per-candidate route provenance¶
Decision:
The previous AOM route counters were aggregate-only. Broad preprocessing campaigns could report how many candidates used materialized, dense, banded, or structured operator-moment routes, but the top-k rows did not expose the exact scoring route used by each candidate.
Keep
candidate_scoresstable and add route provenance as a separate audit vector. This makes CPU/GPU campaign selection inspectable without changing ranking, score computation, or fitted outputs.
Implementation:
Added
candidate_routesto native AOM sweep MethodResults. Route ids are:0=materialized,1=dense_operator_moment,2=banded_operator_moment, and3=structured_operator_moment.Packed the vector through the C MethodResult ABI and Python result helpers for both
aom_sweep_runandaom_chain_sweep_run.aom_candidate_tableandaom_chain_score_campaignnow exposescore_route_idandscore_routeon candidate/top-k rows.aom_candidate_operator_summarynow includes aby_score_routegrouping, so campaign reports can separate materialized fallback from exact operator-moment scoring.Native/Python tests assert that per-candidate route counts match the aggregate materialized/dense/banded/structured counters.
Public header comments, method docs, coverage matrix, and catalog method notes were updated. Split per-method catalog YAML files were regenerated.
Validation:
CPU
dev-releasebuild completed.CPU
n4m_tests: 344 passed, 0 failed.Targeted Python wrapper tests:
N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so.1.18.0 PYTHONPATH=bindings/python/src pytest bindings/python/tests/test_moment_model_wrappers.py -q: 28 passed.Full Python tests with
N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so.1.18.0: 269 passed, 4 existing UVE warnings.CUDA
cuda-onbuild was up to date withCUDA_VISIBLE_DEVICES=0.CUDA-build
n4m_tests: 344 passed, 0 failed.CUDA-lib Python smoke with
CUDA_VISIBLE_DEVICES=0confirmedcandidate_routes,score_route, andby_score_routethroughaom_chain_sweep_runandaom_chain_score_campaign.Rebuilt
bindings/python_nirs4all_methodsfrom the updated Python source and bundled native library; package smoke imported the bundled library and exposedscore_route.Catalog/checks:
catalog/scripts/validate.py,catalog/scripts/reconcile_abi.py --check,catalog/scripts/split_legacy_methods.py --check, andgit diff --checkall passed.
Remaining work:
This is diagnostic provenance for exact preprocessing screens. It does not implement batched IKPLS, grouped CUDA kernels, or a fused 200k-chain grinder.
2026-06-05 - PLS screen fit-cost audit counters¶
Decision:
Expose the PLS screening cost that the future batched IKPLS/GPU grinder must reduce. Candidate counts alone hide whether a PLS grid used one max-component fit per fold, materialized fallback fits, or extra final selected-model fits.
Keep the counters audit-only. They do not affect ranking, route selection, CV scoring, or fitted coefficients.
Implementation:
Added
n_pls_moment_cv_fits,n_pls_materialized_cv_fits,n_pls_moment_final_fits, andn_pls_materialized_final_fitsto nativeSweepResultand AOM sweep MethodResults.n4m_sweep_runnow reports whether compatible PLS1 grids were scored by moment-prefix fits or materialized prefix fits, and whether a final selected PLS fit was run to populate model outputs.n4m_aom_sweep_runandn4m_aom_chain_sweep_runaggregate these counters across chains. Score-only campaigns therefore expose pure screen cost with final-fit counters at zero.Python wrappers, sklearn diagnostics,
aom_chain_score_campaign, and the AOM/moment timing CSVs now carry the counters. Campaign metrics additionally includepls_cv_fits_per_chainandpls_cv_fits_per_candidate.Tests now assert that a PLS moment component grid performs one moment fit per CV fold, and that score-only mode skips final PLS fits.
Public docs, the C ABI header comments, the coverage matrix, and catalog method notes now list the counters. Split per-method catalog YAML files were regenerated from
catalog/methods.yaml.
Validation:
CPU
dev-releasebuild completed.CPU
n4m_tests: 344 passed, 0 failed.Targeted Python wrapper tests:
N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so.1.18.0 PYTHONPATH=bindings/python/src pytest bindings/python/tests/test_moment_model_wrappers.py -q: 28 passed.Full Python test suite:
N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so.1.18.0 PYTHONPATH=bindings/python/src pytest bindings/python/tests -q: 269 passed, 4 existing warnings.CUDA
cuda-onbuild completed withCUDA_VISIBLE_DEVICES=0.CUDA-build
n4m_tests: 344 passed, 0 failed.CUDA Python counter smoke with
CUDA_VISIBLE_DEVICES=0:n4m.sweep_run(..., heads=("pls",), score_only=True)reportedn_pls_moment_cv_fits=4, andaom_chain_score_campaign(..., cv=4, moment_policy="force_moments")reportedn_pls_moment_cv_fits=12andpls_cv_fits_per_chain=4.0.Timing CSVs regenerated:
benchmarks/cross_binding/moment_sweep_timing.csv,benchmarks/cross_binding/moment_sweep_timing_cuda_smoke.csv,benchmarks/cross_binding/aom_sweep_timing.csv, andbenchmarks/cross_binding/aom_sweep_timing_cuda_smoke.csv.Rebuilt
bindings/python_nirs4all_methodsfrombindings/python/src/n4m/lib/libn4m.so.1.18.0; package smoke imported the bundled library and fittedNativeMomentSweepRegressorplusNativeAOMChainSweepRegressor.Catalog/checks:
catalog/scripts/validate.py,catalog/scripts/reconcile_abi.py --check,catalog/scripts/split_legacy_methods.py --check, andgit diff --checkall passed.
Remaining work:
These counters make the PLS bottleneck measurable; they do not yet replace fold-local PLS scoring with batched IKPLS or fused CUDA kernels.
2026-06-05 - AOM operator-moment prefix cache¶
Decision:
Reduce repeated exact moment-transform work in broad strict-linear AOM chain grids. Cartesian grids commonly share prefixes such as
detrend -> ...orsavgol_smooth -> ...; transforming every full chain from raw moments repeats the same prefix algebra.Keep the optimization score-preserving and bounded. The cache is only used inside the operator-moment route and is capped by feature count and entry count to avoid large-memory surprises.
Implementation:
Added an internal prefix cache in
cpp/src/core/aom_sweep.cppfor structured/banded operator-moment chain transforms.The cache stores transformed all-sample and held-out moment sets for strict-linear prefixes up to
p <= 256, with at most 64 entries per native AOM sweep call. Unsupported or dense-only regimes fall back to the existing exact path.Exposed audit scalars
n_moment_prefix_cache_hitsandn_moment_prefix_cache_misseson AOM sweep MethodResults.Propagated those scalars through Python, sklearn diagnostics,
aom_chain_score_campaign, andbench_aom_sweep_timing.py; campaign reports now includemoment_prefix_cache_hit_fraction.Added native and Python tests with repeated
detrendandsavgol_smoothprefixes to verify full operator-moment scoring plus non-zero cache counters.
Validation:
CPU
dev-releasebuild completed.CPU
n4m_tests: 344 passed, 0 failed.Targeted Python wrapper tests:
N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so.1.18.0 PYTHONPATH=bindings/python/src pytest bindings/python/tests/test_moment_model_wrappers.py -q: 28 passed.Full Python tests with
N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so.1.18.0: 269 passed, 4 existing UVE warnings.CUDA build completed with
CUDA_VISIBLE_DEVICES=0.CUDA
n4m_testsonCUDA_VISIBLE_DEVICES=0: 344 passed, 0 failed.CUDA-lib Python smoke passed with explicit
N4M_LIB_PATH=build/cuda-on/cpp/src/libn4m.so.1.18.0, confirming a repeated-prefixforce_momentschain grid reports 3 cache hits and 5 misses while staying fully operator-moment.Timing smoke regenerated:
benchmarks/cross_binding/aom_sweep_timing.csvandbenchmarks/cross_binding/aom_sweep_timing_cuda_smoke.csv. CUDA-buildautorows now expose prefix reuse in the timing table, e.g. compact mixed rows at 64/128 features report 3 hits and 12 misses; custom5 rows report 2 hits and 5 misses.Generated
bindings/python_nirs4all_methodspackage smoke passed with ABI(1, 18, 0), including directaom_chain_sweep_runcache counters andaom_chain_score_campaigncache-hit aggregation.Catalog checks passed:
catalog/scripts/validate.py,catalog/scripts/reconcile_abi.py --checkandcatalog/scripts/split_legacy_methods.py --check.git diff --checkpassed.
Remaining work:
This reduces repeated prefix transforms for medium-width strict-linear operator-moment grids. It is still not a fused batched IKPLS or CUDA grouped-kernel implementation for 200k-chain screens.
2026-06-05 - Score-only dual Ridge SSE micro-optimization¶
Decision:
Continue reducing score-only screening overhead in the existing native Ridge/PLS sweep without changing the ABI or candidate scores.
The wide Ridge route (
p > n_train) can use a precomputed train/held-out cross-kernel. In score-only mode it only needs held-out SSE, not the materialized held-out prediction matrix.
Implementation:
Added a direct dual-cross held-out SSE helper in
cpp/src/core/sweep.cpp.In
n4m_sweep_run(..., score_only=True), wide Ridge folds that useK_crossnow solve the same dual system and accumulate SSE directly against held-outY, avoiding the temporary prediction buffer.Added native and Python wrapper tests that compare wide Ridge score-only candidate scores against the full-output route.
Validation:
CPU
dev-releasebuild completed.CPU
n4m_tests: 343 passed, 0 failed.Targeted Python wrapper tests:
N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so.1.18.0 PYTHONPATH=bindings/python/src pytest bindings/python/tests/test_moment_model_wrappers.py -q: 27 passed.Full Python tests with
N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so.1.18.0: 268 passed, 4 existing UVE warnings.CUDA build completed with
CUDA_VISIBLE_DEVICES=0.CUDA
n4m_testsonCUDA_VISIBLE_DEVICES=0: 343 passed, 0 failed.CUDA-lib Python smoke passed with explicit
N4M_LIB_PATH=build/cuda-on/cpp/src/libn4m.so.1.18.0, confirming wide Ridge score-only candidate scores match the full-output route and selected output buffers are empty.Timing smoke regenerated:
benchmarks/cross_binding/moment_sweep_timing.csvandbenchmarks/cross_binding/moment_sweep_timing_cuda_smoke.csv. CPU Ridge medians for full vs score-only were 1.19/1.12 ms at 64 x 64, 10.57/9.33 ms at 128 x 128, and 36.16/32.48 ms at 192 x 256. CUDA-build smoke medians were 2.15/2.38 ms, 10.59/10.26 ms, and 35.42/31.64 ms on the same Ridge cells.Generated
bindings/python_nirs4all_methodspackage smoke passed with ABI(1, 18, 0), confirming wide Ridge score-only candidate scores match the full-output route.Catalog checks passed:
catalog/scripts/validate.py,catalog/scripts/reconcile_abi.py --checkandcatalog/scripts/split_legacy_methods.py --check.git diff --checkpassed.
Remaining work:
This still does not add batched IKPLS or fused CUDA kernels. It only removes another avoidable allocation in score-only wide Ridge screens.
2026-06-05 - Sweep score-only direct SSE micro-optimization¶
Decision:
Reduce avoidable allocation in broad score-only Ridge/PLS screens. The native scorer already skips selected-model outputs in score-only mode, but materialized fallback cells still allocated held-out prediction buffers just to compute RMSE.
Implementation:
Added a direct held-out SSE helper in
cpp/src/core/sweep.cppfor linearRidgeMomentFitstates.In
n4m_sweep_run(..., score_only=True), materialized Ridge and materialized PLS prefix fallback cells now score held-out rows directly from coefficients when available.This first pass left dual cross-kernel Ridge on its prediction buffer because that path scores in dual space without reconstructing feature-space coefficients; the follow-up entry above removes that remaining score-only buffer too.
No ABI or scoring semantics changed; this is a memory/allocation reduction for score-only ranking paths. AOM materialized fallback screens benefit because they call
run_moment_sweep(..., score_only=True)underneath.
Validation:
CPU
dev-releasebuild completed.CPU
n4m_tests: 343 passed, 0 failed.CUDA build completed with
CUDA_VISIBLE_DEVICES=0.CUDA
n4m_testsonCUDA_VISIBLE_DEVICES=0: 343 passed, 0 failed.Full Python tests with
N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so.1.18.0: 267 passed, 4 existing UVE warnings.CUDA-lib Python smoke passed with
CUDA_VISIBLE_DEVICES=0, explicitN4M_LIB_PATH=build/cuda-on/cpp/src/libn4m.so.1.18.0, score-only Ridge/PLS sweep and AOM chain campaign.Generated
bindings/python_nirs4all_methodspackage smoke passed with ABI(1, 18, 0), including score-only Ridge/PLS sweep.Catalog checks passed:
catalog/scripts/validate.py,catalog/scripts/reconcile_abi.py --checkandcatalog/scripts/split_legacy_methods.py --check.git diff --checkpassed.
Remaining work:
This is not batched IKPLS or fused CUDA. It only removes avoidable held-out prediction buffers in existing score-only materialized fallback cells.
2026-06-05 - Chunked strict-linear AOM campaign helper¶
Decision:
Add a product-facing campaign helper for broad strict-linear preprocessing ranking. The native scorer already has
aom_chain_sweep_runandscore_only=True; users still needed a deterministic way to generate larger chain grids, chunk execution, and aggregate top-k rows without losing chain provenance.
Implementation:
Added
n4m.build_aom_strict_chain_grid.compactandwidereproduce the native built-in banks.lab/cartesianbuilds a broader deterministic strict-linear grid with multiple Savitzky-Golay smooth/derivative variants, Norris-Williams, finite differences and Whittaker chains.Custom
familiesandtemplatesdefine cartesian operator combinations.
Added
n4m.aom_chain_score_campaign.Runs
aom_chain_sweep_run(..., score_only=True)in chain chunks.Aggregates a global top-k with decoded chains, global chain ids, head, parameter and CV RMSE.
Sums route counters across chunks for operator-moment versus materialized fallback audit.
Accepts
checkpoint_path/resumefor long strict-linear campaigns. The JSON checkpoint is written after each completed chunk, stores the current top-k and per-chunk counters, and is guarded by a fingerprint of chains, folds, hyperparameters andX/ycontents.Accepts
max_chunks_per_runfor bounded incremental execution. The report exposescomplete,n_remaining_chunks,processed_chunks_this_runandmax_chunks_per_run; the chunk budget is intentionally not part of the checkpoint fingerprint, so campaign cadence can change across relaunches.Reports normalized throughput and route metrics at campaign and chunk level: candidates/chains per second, ms per candidate/chain, and operator-moment/materialized plus dense/banded/structured route fractions. These are derived from native route counters and elapsed chunk timings.
Added
NativeAOMFixedCandidateRegressor.from_candidate(row)consumes rows fromn4m.aom_candidate_tableorn4m.aom_chain_score_campaign.Refits exactly one decoded chain/head/parameter candidate through the same native ABI and predicts from folded
input_coefficients.
Added
n4m.aom_evaluate_candidates.Refits decoded candidate rows on an explicit train split.
Scores them on caller-provided
X_eval, y_eval.Reports
screen_cv_rmse,refit_cv_rmse,eval_rmse,eval_r2,cv_rank,eval_rankandrank_deltafor CV-vs-holdout analysis.Does not alter fit, route selection, or use dataset identity.
Added
n4m.aom_candidate_rank_diagnostics.Consumes evaluated rows or reloaded candidate reports.
Compares screen score (
screen_cv_rmseby default) againsteval_rmse.Reports Spearman rank correlation, absolute rank drift, cross-ranks of the screen/eval winners, and top-k overlap/recall.
Provides screen-recall audit evidence without adding a selection rule.
Added
n4m.aom_candidate_report_recordsandn4m.aom_save_candidate_report.Flatten campaign/eval candidate rows into JSON-safe dictionaries.
Write
.json,.jsonl/.ndjson, or.csvreports without pandas.Preserve decoded strict-linear chains and add
chain_jsonfor CSV replay.Drop prediction arrays by default unless
include_predictions=True.
Added
n4m.aom_load_candidate_report.Reads JSON, JSONL / NDJSON and CSV candidate reports.
Restores
chainfromchain_jsonfor CSV rows.Converts standard id/rank/score fields back to numeric types.
Returns rows that can be passed directly to
NativeAOMFixedCandidateRegressor.from_candidateorn4m.aom_evaluate_candidates.
Added
n4m.aom_candidate_operator_summary.Groups already-scored rows by head, preprocessing operator, operator/head pair and chain length.
Uses
eval_rmsewhen present, otherwisecv_rmse,refit_cv_rmseorscreen_cv_rmse.Reports group counts, best/mean/median score and rank stats without changing candidate scores or top-k ordering.
Exported both campaign helpers and the fixed-candidate estimator from top-level
n4m.Added targeted Python tests and method/coverage docs.
Validation:
Targeted Python wrapper/sweep tests:
bindings/python/tests/test_moment_model_wrappers.py: 26 passed, including campaign top-k toNativeAOMFixedCandidateRegressor.from_candidaterefit, replay, bounded incremental checkpoint execution, resume from a partial campaign JSON, throughput/route metric checks,aom_evaluate_candidatesCV-vs-holdout reporting, screen-recall rank diagnostics, JSON/CSV/JSONL candidate report export/reload, operator/head summary checks, and refit from a reloaded CSV winner.Full Python tests:
bindings/python/tests: 267 passed, 4 existing UVE warnings.CUDA-lib Python smoke passed with
CUDA_VISIBLE_DEVICES=0, explicitN4M_LIB_PATH=build/cuda-on/cpp/src/libn4m.so.1.18.0, lab grid generation, chunked score-only execution, global top-k checks, fixed-candidate refit and holdout candidate evaluation plus bounded incremental checkpoint/resume and JSON/CSV/JSONL report export.Generated
bindings/python_nirs4all_methodspackage smoke passed with ABI(1, 18, 0), including fixed-candidate import/refit/predict,aom_evaluate_candidates, bounded incremental checkpoint/resume, and JSON/CSV report export.Catalog checks passed:
validate.py,split_legacy_methods.py --check, andreconcile_abi.py --check.git diff --checkpassed.
Remaining work:
This makes large strict-linear ranking campaigns easier to run and inspect. It still uses the existing native scorer underneath; it is not a fused batched IKPLS or custom CUDA grinder.
2026-06-05 - AOM sweep campaign chain descriptors¶
Decision:
Treat chain traceability as part of the campaign contract. A ranked candidate table is not enough unless every
chain_idcan be mapped back to the exact strict-linear preprocessing chain used during scoring.
Implementation:
Added
chain_offsets,op_kinds,param_offsets, andchain_paramsto nativeAomSweepResult.Exported the descriptor for both built-in
aom_sweep_runbanks and caller-providedaom_chain_sweep_rundescriptors.Exposed the descriptor through the C ABI MethodResult and Python results.
Added
n4m.decode_aom_chains(res)andn4m.aom_candidate_table(res, sort=True)for campaign reports and top-k inspection.Updated public C ABI comments and method docs.
Validation:
CPU build completed.
CPU
n4m_tests: 343 passed, 0 failed.Targeted Python wrapper/sweep tests:
bindings/python/tests/test_moment_model_wrappers.py: 22 passed.Full Python tests:
bindings/python/tests: 263 passed, 4 existing UVE warnings.CPU-lib Python smoke passed for
score_only=Truechain decoding and candidate-table generation.CUDA build completed with
CUDA_VISIBLE_DEVICES=0.CUDA
n4m_testsonCUDA_VISIBLE_DEVICES=0: 343 passed, 0 failed.CUDA-lib Python smoke passed for non-empty chain descriptors,
n4m.decode_aom_chains, andn4m.aom_candidate_table.Generated
bindings/python_nirs4all_methodspackage smoke passed with ABI(1, 18, 0).Catalog checks passed:
validate.py,split_legacy_methods.py --check, andreconcile_abi.py --check.git diff --checkpassed.
Fix caught during validation:
The first CUDA native run exposed empty descriptors on the specialized operator-moment paths. Added descriptor export to the Ridge-only and hybrid operator-moment early-return paths, including
score_only=True.
Remaining work:
This improves campaign reproducibility and score inspection. It does not implement batched IKPLS or fused CUDA scoring kernels.
2026-06-05 - Reusable native AOM operator PLS stack outputs¶
Decision:
Treat the native AOM operator PLS stack as a reusable fitted model. The native stack is restricted to strict-linear operator views, fixed PLS1 projections and a Ridge head, so its selected final predictor can be folded into original input-space coefficients.
Implementation:
Added
input_coefficientsandinput_intercepttoAomOperatorPlsStackResult.Derived the exact input-space linear state from each final view’s strict operator matrix, standardizer, PLS rotations, and the final Ridge head.
Kept existing
coefficientsandinterceptas the stack-feature Ridge head for audit compatibility.Exposed folded matrices through the C ABI MethodResult and Python
n4m.aom_operator_pls_stack.Added
NativeAOMOperatorPLSStackRegressor, exported fromn4mandn4m.sklearn, with predictions driven byX @ input_coefficients + input_intercept.Updated public ABI comments, method docs, coverage notes and catalog notes.
Validation:
CPU
n4m_tests: 343 passed, 0 failed.Targeted Python wrapper tests:
bindings/python/tests/test_moment_model_wrappers.py: 22 passed.Full Python tests:
bindings/python/tests: 263 passed, 4 existing UVE warnings.CUDA build completed with
CUDA_VISIBLE_DEVICES=0.CUDA
n4m_testsonCUDA_VISIBLE_DEVICES=0: 343 passed, 0 failed.CUDA-lib Python smoke passed for
n4m.aom_operator_pls_stackandNativeAOMOperatorPLSStackRegressorreplay.Generated
bindings/python_nirs4all_methodspackage smoke passed with ABI(1, 18, 0).Catalog checks passed:
validate.py,split_legacy_methods.py --check, andreconcile_abi.py --check.git diff --checkpassed.
Remaining work:
This makes the native operator PLS stack reusable. It is still not a fused batched GPU stack or 200k-chain IKPLS grinder.
2026-06-05 - Reusable native AOM Ridge blender outputs¶
Decision:
Treat the native AOM Ridge simplex blend as a reusable linear model. The native candidate pool is restricted to strict-linear chains plus Ridge heads, so a non-negative weighted blend of candidates has an exact input-space coefficient representation.
Implementation:
Added
input_coefficientsandintercepttoAomRidgeBlenderResult.During final full-data candidate refits, folded each candidate’s transformed Ridge coefficients through its strict-linear chain and stored the candidate input-space state.
After solving simplex weights, accumulated the weighted final
input_coefficientsandintercept.Exposed both matrices through the C ABI MethodResult and Python
n4m.aom_ridge_blender.Added
NativeAOMRidgeBlenderRegressor, exported fromn4mandn4m.sklearn, with predictions driven by the folded native coefficients.Updated method docs, coverage notes and catalog notes.
Validation:
CPU
n4m_tests: 343 passed, 0 failed.Targeted Python wrapper tests:
bindings/python/tests/test_moment_model_wrappers.py: 22 passed.Full Python tests:
bindings/python/tests: 263 passed, 4 existing UVE warnings.CUDA build completed with
CUDA_VISIBLE_DEVICES=0.CUDA
n4m_testsonCUDA_VISIBLE_DEVICES=0: 343 passed, 0 failed.CUDA-lib Python smoke passed for
n4m.aom_ridge_blenderandNativeAOMRidgeBlenderRegressorreplay.
Remaining work:
This makes the native Ridge blend reusable. It is still not a fused batched GPU blender or a 200k-chain IKPLS grinder.
2026-06-05 - Reusable native AOM robust-HPO outputs¶
Decision:
Treat native
aom_robust_hpoas a reusable fitted model, not just a compact score table. The selected strict-linear preprocessing chain is deterministic and linear, so its transformed-space coefficients can be folded back into the original input feature space.
Implementation:
Added
input_coefficientsandn_featurestoAomRobustHpoResult.Folded selected transformed-space coefficients through the selected strict-linear chain operator during final native fit.
Exposed
input_coefficients,n_samples,n_features,n_features_transformed, andn_targetsthrough the C ABI MethodResult and Pythonn4m.aom_robust_hpo.Added
NativeAOMRobustHPORegressor, exported fromn4mandn4m.sklearn, using the replay equationX @ input_coefficients + intercept.Updated method docs, coverage notes, and catalog notes.
Validation:
CPU
n4m_tests: 343 passed, 0 failed.Targeted Python wrapper tests:
bindings/python/tests/test_moment_model_wrappers.py: 22 passed.Full Python tests:
bindings/python/tests: 263 passed, 4 existing UVE warnings.CUDA build completed with
CUDA_VISIBLE_DEVICES=0.CUDA
n4m_testsonCUDA_VISIBLE_DEVICES=0: 343 passed, 0 failed.CUDA-lib Python smoke passed for
n4m.aom_robust_hpoandNativeAOMRobustHPORegressorreplay.
Remaining work:
This makes the compact/wide product method reusable. It does not implement a fused batched IKPLS or 200k-chain CUDA grinder.
2026-06-05 - AOM per-head route counters¶
Decision:
Broad preprocessing campaigns need to know whether candidate rows were scored by operator moments or by materialized fallback, separately for Ridge and PLS. The previous counters only exposed total route counts.
Implementation:
Added AOM MethodResult scalars:
n_ridge_operator_moment_candidates,n_pls_operator_moment_candidates,n_ridge_materialized_candidates, andn_pls_materialized_candidates.Updated native route accounting in
aom_sweep.cppfor Ridge-only, PLS-only, mixed hybrid, score-only and fully materialized routes.Propagated the counters through Python
n4m.aom_sweep_run,n4m.aom_chain_sweep_runand native sklearn sweep diagnostics.Updated docs/catalog notes so route provenance is visible in the public method contract.
Validation:
Added C++ and Python route-partition assertions.
CPU
n4m_tests: 343 passed, 0 failed.Targeted Python wrapper tests:
bindings/python/tests/test_moment_model_wrappers.py: 21 passed.Full Python tests:
bindings/python/tests: 262 passed, 4 existing UVE warnings.CUDA build completed with
CUDA_VISIBLE_DEVICES=0.CUDA
n4m_testsonCUDA_VISIBLE_DEVICES=0: 343 passed, 0 failed.CUDA-lib Python smoke passed for
aom_chain_sweep_run(..., moment_policy="force_moments", score_only=True)with Ridge/PLS route counters.Generated
bindings/python_nirs4all_methodspackage smoke passed with ABI(1, 18, 0).Regenerated
benchmarks/cross_binding/aom_sweep_timing.csvandbenchmarks/cross_binding/aom_sweep_timing_cuda_smoke.csvwith per-head route-counter columns.Catalog checks passed:
validate.py,split_legacy_methods.py --check, andreconcile_abi.py --check.git diff --checkpassed.
Remaining work:
These counters make the existing screen more auditable. They do not implement batched IKPLS or fused CUDA operator kernels.
2026-06-05 - Public sweep score-only ranking mode¶
Decision:
Expose the existing native
aom_score_onlybehavior on the publicn4m.sweep_runPython surface so broad Ridge/PLS ranking campaigns can skip selected-model buffers.Keep this as a functional ranking API, not as an sklearn estimator knob, because sklearn prediction still requires reusable coefficients and intercepts.
Implementation:
Added
score_onlyton4m.sweep_run(...)and wired it ton4m_config_set_aom_score_only.Documented the C ABI contract: candidate scores, selected ids, folds and scalar diagnostics remain populated; OOF predictions, final predictions, coefficients and intercept are returned as empty
0 x 0matrices.Added C++ and Python tests for the public sweep score-only contract.
Extended
benchmarks/cross_binding/bench_moment_sweep_timing.pywithnative_sweep_ridge_score_onlyandnative_sweep_pls_score_onlyrows.
Validation:
CPU
n4m_tests: 343 passed, 0 failed.Targeted Python wrapper tests:
bindings/python/tests/test_moment_model_wrappers.py: 21 passed.Full Python tests:
bindings/python/tests: 262 passed, 4 existing UVE warnings.CUDA build completed with
CUDA_VISIBLE_DEVICES=0.CUDA
n4m_testsonCUDA_VISIBLE_DEVICES=0: 343 passed, 0 failed.CUDA-lib Python smoke passed for
n4m.sweep_run(..., heads=("pls",), score_only=True)withn_pls_moment_candidates == n_candidatesand empty prediction buffers.Timing smoke regenerated:
benchmarks/cross_binding/moment_sweep_timing.csvandbenchmarks/cross_binding/moment_sweep_timing_cuda_smoke.csv.
Remaining work:
This makes the public sweep useful as a fast ranking pass. It still does not implement fused batched IKPLS or the full 200k-chain CUDA grinder.
2026-06-05 - Public sweep PLS1 moment scoring¶
Decision:
Move the public
n4m_sweep_runPLS path closer to the requested moment engine instead of leaving PLS component screening entirely materialized.Keep the existing materialized prefix scorer as the fallback for multi-target or unsupported PLS solver/deflation regimes.
Expose a route counter so Python/C users can audit whether compatible PLS candidates used the moment route.
Implementation:
Added a compatible PLS1 route inside
run_moment_sweepfor single-target NIPALS/regression PLS component grids.The route computes held-out moments, subtracts them from all-row moments, fits PLS1 prefixes from train sufficient statistics, scores held-out SSE from held-out moments, and only uses materialized
Xto populate selected OOF/final prediction buffers when requested.Fold-local train matrix materialization is now skipped when neither wide Ridge nor unsupported PLS fallback needs it.
SweepResult/ MethodResult now expose scalarn_pls_moment_candidates; Pythonn4m.sweep_runreturns it.benchmarks/cross_binding/bench_moment_sweep_timing.pynow recordsn_pls_moment_candidates.
Validation:
C++ tests now assert
n_pls_moment_candidatesfor single and multi-component PLS grids while preserving materialized-CV score parity.CPU
n4m_tests: 342 passed, 0 failed.CUDA
n4m_testsonCUDA_VISIBLE_DEVICES=0: 342 passed, 0 failed.Targeted Python wrapper tests:
bindings/python/tests/test_moment_model_wrappers.py: 20 passed.Full Python tests:
bindings/python/tests: 261 passed, 4 existing UVE warnings.CUDA-lib Python smoke passed for
n4m.sweep_run(..., heads=("pls",))withn_pls_moment_candidates == n_candidates.Generated
bindings/python_nirs4all_methodspackage smoke passed with ABI(1, 18, 0).Timing smoke regenerated:
benchmarks/cross_binding/moment_sweep_timing.csvandbenchmarks/cross_binding/moment_sweep_timing_cuda_smoke.csv; PLS rows shown_pls_moment_candidates=3.Catalog checks passed:
validate.py,split_legacy_methods.py --check, andreconcile_abi.py --check.git diff --checkpassed.
Remaining work:
This removes the public sweep’s materialized PLS bottleneck for compatible PLS1 grids, but it is still not fused batched IKPLS or a 200k-chain CUDA grinder.
2026-06-05 - Reusable AOM/POP selected-model outputs¶
Decision:
The historical native AOM-PLS and POP-PLS selectors were callable from Python, but they still returned only train predictions and diagnostics.
Expose the selected model as an input-space linear state so the methods are reusable on new spectra, matching the reuse contract already added for the newer AOM sweep MethodResults.
Implementation:
Added result vectors to the native AOM/POP selection results:
coefficients,input_coefficients, andintercept.Added public C ABI getters for both result handles:
*_get_coefficients,*_get_input_coefficients, and*_get_intercept.Global AOM keeps
coefficientsin transformed selected-operator space and foldsinput_coefficientsback to the original input feature space.POP coefficients are already in original input space;
input_coefficientsmirrors them for a uniform binding contract.Python ABI-close wrappers now return those matrices.
Added
NativeAOMPLSRegressorandNativePOPPLSRegressorsklearn-style wrappers that predict withX @ input_coefficients + intercept.Added cross-binding smoke timing for the reusable selector surfaces:
benchmarks/cross_binding/bench_aom_selector_timing.py, with CPU and CUDA-smoke CSV outputs.
Validation:
Added C++ ABI replay tests for AOM global and POP selected models.
CPU
n4m_tests: 342 passed, 0 failed.CUDA
n4m_testsonCUDA_VISIBLE_DEVICES=0: 342 passed, 0 failed.Targeted Python wrapper tests:
bindings/python/tests/test_moment_model_wrappers.py: 20 passed.Full Python tests:
bindings/python/tests: 261 passed, 4 existing UVE warnings.CUDA-lib Python smoke with
CUDA_VISIBLE_DEVICES=0 N4M_LIB_PATH=build/cuda-on/cpp/src/libn4m.so.1.18.0passed forn4m.aom_pls,n4m.pop_pls,NativeAOMPLSRegressor, andNativePOPPLSRegressor.Catalog checks passed:
validate.py,split_legacy_methods.py --check, andreconcile_abi.py --check.git diff --checkpassed.Timing smoke passed:
benchmarks/cross_binding/aom_selector_timing.csvandbenchmarks/cross_binding/aom_selector_timing_cuda_smoke.csv.
Remaining work:
This makes the historical selectors reusable as selected linear models; it still does not add batched IKPLS or fused GPU screening.
2026-06-05 - Python wrappers for historical AOM/POP selectors¶
Decision:
Close the usability gap for the already-catalogued native AOM-PLS and POP-PLS selectors: the C ABI existed, but the
n4mtop-level Python surface did not expose it.Keep the wrapper ABI-close instead of adding a new sklearn estimator class: build an operator bank and validation plan, call the native selector, copy result buffers to NumPy, and destroy the native handles.
Implementation:
Added ctypes declarations for
n4m_operator_bank_*,n4m_validation_plan_*,n4m_aom_global_select,n4m_aom_per_component_select, and their result getters.Added
n4m.aom_global_select/n4m.aom_plsandn4m.aom_per_component_select/n4m.pop_pls.The wrappers use the documented compact strict-linear operator bank by default, accept caller-provided strict operators, build fold-safe validation plans from explicit
fold_idsor contiguous CV folds, and force the native SIMPLS-regression config required by these selectors.
Validation:
Added Python smoke coverage for both selectors and aliases in
bindings/python/tests/test_moment_model_wrappers.py.Targeted Python test:
test_native_aom_pls_and_pop_pls_selector_wrappers_smoke: passed.Targeted wrapper file:
bindings/python/tests/test_moment_model_wrappers.py: 19 passed.CUDA-lib Python smoke with
CUDA_VISIBLE_DEVICES=0 N4M_LIB_PATH=build/cuda-on/cpp/src/libn4m.so.1.18.0passed forn4m.aom_plsandn4m.pop_pls.Generated
bindings/python_nirs4all_methodspackage smoke passed with the same CUDA lib and confirmed the alias surface.Full Python tests:
bindings/python/tests: 260 passed, 4 existing UVE warnings.Catalog checks:
validate.py,split_legacy_methods.py --check, andreconcile_abi.py --checkpassed fromcatalog/scripts.git diff --checkpassed.
Remaining work:
This exposes existing catalogued methods; it does not add a fused GPU AOM screen or batched IKPLS.
2026-06-05 - AOM score-only screen output mode¶
Decision:
Add an output mode for large AOM preprocessing ranking campaigns where the first pass needs candidate scores and selected ids, not a fitted selected model artifact.
Keep the default behavior unchanged for estimator-style use and sklearn wrappers.
Implementation:
Public config now exposes
n4m_config_set_aom_score_onlyandn4m_config_get_aom_score_only.Python wrappers accept
score_only=Trueonn4m.aom_sweep_runandn4m.aom_chain_sweep_run.AOM MethodResults expose scalar
score_only. In score-only mode, model output matrices are returned as0 x 0, whilecandidate_scores, selected ids, route counters andfold_idsremain populated.Operator-moment AOM routes skip the final selected-chain refit/materialization in score-only mode.
The internal
run_moment_sweep,score_ridge_moment_sweep, andscore_pls1_moment_sweeppaths now also respect this flag when called from AOM: they avoid OOF buffers and selected-model output refits while keeping candidate scores unchanged. Materialized candidate-screen routes still pay fold-local scoring fits because this is not batched IKPLS.
Validation:
Added C++ test
aom_chain_sweep/force_moments_score_only_skips_final_refit.Added C++ test
aom_chain_sweep/materialized_score_only_keeps_scores.CPU
n4m_tests: 340 passed, 0 failed.CUDA build with
CUDA_VISIBLE_DEVICES=0,n4m_tests: 340 passed, 0 failed.Targeted Python wrapper tests:
bindings/python/tests/test_moment_model_wrappers.py: 18 passed.Full Python tests:
bindings/python/tests: 259 passed, 4 existing UVE warnings.Catalog checks:
validate.py,split_legacy_methods.py --check, andreconcile_abi.py --checkpassed.git diff --checkpassed.
Remaining work:
This is a practical ranking-mode increment toward large campaigns, not the fused 200k-chain CUDA/IKPLS grinder. The next engine step is still a batched/device-resident PLS screen instead of per-chain host orchestration.
2026-06-05 - Strict force_moments route policy¶
Decision:
Keep
moment_policy="auto"exact but pragmatic: it may choose materialized candidate scoring when that route is supported and cheaper for a backend or geometry.Add
moment_policy="force_moments"for audits and production guards that must prove the candidate screen stayed inside operator moments.Allow post-selection materialization to remain: it is used only to expose public OOF/final predictions and
input_coefficients, not to score the candidate grid.
Implementation:
Public config enum now includes
N4M_AOM_MOMENT_FORCE_MOMENTS.C++ AOM sweep routes reject materialized candidate-screen fallback with
UNSUPPORTEDwhen the requested chain/head/grid has no complete operator-moment route.Python accepts
moment_policy="force_moments"plus aliases"moments_only","operator_moments_only", and"strict_moments".
Validation:
Added C++ coverage for an accepted full moment route and a rejected fallback route in
n4m_aom_chain_sweep_run.Added Python wrapper coverage for the same strict policy.
CPU
n4m_tests: 338 passed, 0 failed.CUDA build with
CUDA_VISIBLE_DEVICES=0,n4m_tests: 338 passed, 0 failed.Targeted Python wrapper tests:
bindings/python/tests/test_moment_model_wrappers.py: 18 passed.Full Python tests:
bindings/python/tests: 259 passed, 4 existing UVE warnings.Catalog checks:
validate.py,split_legacy_methods.py --check, andreconcile_abi.py --checkpassed.git diff --checkpassed.
Remaining work:
This closes the “no hidden hors moments” guard for native AOM screens. It still does not ship the fused 200k-chain CUDA/IKPLS grinder; unsupported regimes now fail explicitly in strict mode instead of being screened through materialized candidates.
2026-06-05 - Native sklearn sweep estimators and input-space AOM coefficients¶
Decision:
Keep the existing AOM
coefficientsoutput in the selected transformed-chain feature space, but add an exactinput_coefficientsoutput folded back into the original spectral feature space.Use
input_coefficientsto expose reusable sklearn-style native estimators that predict on new spectra without Python-side chain replay.
Implementation:
Core:
cpp/src/core/aom_sweep.cppnow builds the selected chain operator matrixMand folds transformed coefficientsbasM @ b.C ABI:
n4m_aom_sweep_runandn4m_aom_chain_sweep_runMethodResults now includeinput_coefficients;n4m_sweep_runremains unchanged.Python: added
NativeMomentSweepRegressor,NativeAOMSweepRegressor, andNativeAOMChainSweepRegressorundern4m.sklearnand top-leveln4m.
Validation:
C++ CPU build:
n4m_tests: 336 passed, 0 failed.
CUDA build with
CUDA_VISIBLE_DEVICES=0:n4m_tests: 336 passed, 0 failed.
Python:
bindings/python/tests/test_moment_model_wrappers.py: 17 passed.full
bindings/python/tests: 258 passed, 4 existing UVE warnings.
Remaining work:
This makes the native sweep outputs reusable as normal estimators, but it is not the fused 200k-chain CUDA/IKPLS grinder. The GPU audit still points to a future moment-only mode, device-resident workspace, and batched IKPLS/fused chain scoring.
2026-06-04 - Backend-aware CPU PLS route selection¶
Decision:
Keep exact PLS1 operator-moment scoring available, but stop using it in CPU
autowhen train folds are only moderately larger than the feature count.CPU
autonow materializes compatible PLS rows whenmin_train_rows < 4 * p; CUDAautokeeps the moment route.The threshold is a compute-route guard only. Candidate scores, ordering and selected models are unchanged.
Implementation:
Core:
cpp/src/core/aom_sweep.cppshares the backend-aware route selector between Ridge and PLS.Mixed Ridge+PLS sweeps avoid transforming moments at all when both heads are routed through the exact materialized path.
Exact PLS moment route tests were moved to a larger
n >> pgeometry where CPUautois expected to keep moments.
Validation:
Added C++ test
aom_chain_sweep/wide_pls_auto_route_is_backend_aware, proving CPU and CUDA take different exact routes while candidate scores match the forced materialized route.CPU build:
n4m_tests: 336 passed, 0 failed.
CUDA build with
CUDA_VISIBLE_DEVICES=0:n4m_tests: 336 passed, 0 failed.
CPU timing evidence after the selector:
compact mixed 48x64 / 80x128:
auto5.67 / 25.64 ms,materialized5.54 / 26.00 ms;compact PLS 60x32 / 80x48 / 96x64:
auto1.75 / 1.41 / 4.31 ms,materialized1.01 / 1.29 / 2.21 ms, with all rows materialized inautofor those smoke geometries.
CUDA-build smoke keeps moment routing:
compact PLS 60x32 / 80x48 / 96x64:
auto15.27 / 11.46 / 15.78 ms,materialized127.77 / 105.61 / 123.48 ms.
Remaining work:
The route selector is still a CPU/CUDA dispatch choice, not fused batched IKPLS. The full 200k-chain GPU grinder remains open.
2026-06-04 - Backend-aware CPU wide Ridge route selection¶
Decision:
Keep
moment_policy="auto"exact, but stop forcing Ridge moment scoring on CPU whenp > n_train.In that regime, the materialized chain path delegates Ridge scoring to the existing dual Ridge sweep in sample space, which is cheaper than solving feature-space Ridge systems from transformed moments.
Preserve the CUDA behavior: CUDA builds keep the operator-moment route in the same wide Ridge cells where the smoke timings show a win.
Implementation:
Core:
cpp/src/core/aom_sweep.cppnow detects whether the library was built with CUDA linalg dispatch.CPU
autoRidge rows withp > min_train_rowsuse the exact materialized chain route; CUDAautokeeps exact operator-moment scoring.Mixed Ridge+PLS sweeps can now have split route counters: Ridge rows may be materialized on CPU while compatible PLS rows still use moments.
Validation:
Added C++ test
aom_chain_sweep/wide_ridge_auto_route_is_backend_aware, proving the route counter switches by backend while candidate scores match the forced materialized route.CPU build:
n4m_tests: 335 passed, 0 failed;n4m_internal_tests: passed;Python tests: 256 passed, 4 existing UVE warnings.
CUDA build with
CUDA_VISIBLE_DEVICES=0:n4m_tests: 335 passed, 0 failed;n4m_internal_tests: passed.
CPU timing evidence after the selector:
compact mixed 48x64 / 80x128:
auto8.21 / 46.00 ms,materialized5.73 / 24.80 ms;custom5 mixed 48x64 / 80x128:
auto3.59 / 20.45 ms,materialized2.97 / 10.69 ms.
CUDA-build smoke preserves moment routing and remains faster in the smaller mixed cell:
compact mixed 48x64:
auto29.55 ms,materialized114.82 ms;compact mixed 80x128:
auto279.79 ms,materialized122.22 ms.
Remaining work:
CPU PLS moment rows are still often slower than materialized PLS on the current smoke cells; that needs a separate forced-moment policy or PLS-specific exact route selector if we want
autoto be uniformly fastest on CPU while retaining explicit route coverage.This is still not the fused 200k-chain CUDA/IKPLS grinder.
2026-06-04 - Structured Whittaker operator-moment route¶
Decision:
Keep Whittaker inside the strict-linear native AOM/moment screen.
Avoid the dense operator-matrix route for Whittaker by reusing the existing pentadiagonal Whittaker baseline solver.
Preserve
moment_policy="materialized"as the A/B fallback because CPU Ridge-wide cells can still be faster through the materialized dual Ridge route.
Implementation:
Core:
cpp/src/core/aom_sweep.cppnow parses Whittaker lambda and factors the exactI + lambda D2'D2pentadiagonal system once per moment set.Moment transform:
x_sum' = x_sum S;X'Y' = S X'Y;X'X' = S X'X S; whereS = (I + lambda D2'D2)^-1.
The route composes with the existing banded local operators and with
detrend_poly, and is counted undern_structured_operator_moment_candidates.The timing script now includes a
custom2_whittakerchain benchmark.
Validation:
Added C++ and Python tests comparing Whittaker
autoscores againstmoment_policy="materialized"scores.CPU build:
n4m_tests: 334 passed, 0 failed;n4m_internal_tests: passed;Python tests: 256 passed, 4 existing UVE warnings.
CUDA build with
CUDA_VISIBLE_DEVICES=0:n4m_tests: 334 passed, 0 failed;n4m_internal_tests: passed.
CPU timing evidence for
custom2_whittakermixed Ridge+PLS:48x64:
auto4.64 ms,materialized3.87 ms;64x96:
auto16.21 ms,materialized13.31 ms.
CUDA-build smoke evidence for
custom2_whittakermixed Ridge+PLS:48x64:
auto7.78 ms,materialized19.57 ms;64x96:
auto21.31 ms,materialized33.97 ms.
Remaining work:
The route is exact and useful for CUDA/PLS-style screens, but CPU Ridge
p > n_trainstill needs a cost-aware moment-vs-dual route selector beforeautois uniformly faster.Batched IKPLS and fused CUDA kernels remain separate backlog items.
2026-06-04 - AOM moment route policy switch¶
Decision:
Keep
autoas the default route policy.Add a public config/wrapper switch to force the legacy materialized-chain screen when it is faster or needed for route A/B tests.
Preserve candidate scores: policy changes compute route, not selection semantics.
Implementation:
Config:
n4m_config_set_aom_moment_policyandn4m_config_get_aom_moment_policy.Python:
n4m.aom_sweep_run(..., moment_policy="auto"|"materialized")andn4m.aom_chain_sweep_run(..., moment_policy=...).Core:
N4M_AOM_MOMENT_MATERIALIZEDbypasses operator-moment routing and falls directly to the materialized strict-linear chain sweep.Benchmarks:
bench_aom_sweep_timing.pynow emits amoment_policycolumn and records both policies.
Validation:
Added C++ and Python tests proving
materializedproduces zero operator-moment candidates and matchesautocandidate scores up to numerical roundoff.CPU timing evidence:
compact mixed 48x64 / 80x128:
auto19.92 / 289.13 ms,materialized5.70 / 25.66 ms;compact Ridge 96x32 / 160x64:
auto2.67 / 19.18 ms,materialized2.81 / 20.92 ms.
CUDA-build smoke evidence:
compact PLS 60x32 / 80x48 / 96x64:
auto12.88 / 13.58 / 16.99 ms,materialized99.98 / 104.38 / 105.12 ms;compact mixed 48x64 / 80x128:
auto121.35 / 274.57 ms,materialized85.79 / 110.77 ms.
Remaining work:
The policy switch makes route comparisons explicit but does not replace the missing fused GPU/batched IKPLS engine.
2026-06-04 - Structured detrend operator-moment route¶
Decision:
Keep ABI 1.18.0 unchanged.
Add an exact low-rank moment transform for
detrend_polyinstead of materializing every detrended chain outside the dense guard.Preserve the strict-linear constraint: no fold-fitted stateful preprocessing is admitted into the native moment screen.
Implementation:
Core:
cpp/src/core/aom_sweep.cppnow builds the polynomial projection basis fordetrend_polyand applies the projection to moment sets:x_sum' = x_sum A;X'Y' = A' X'Y;X'X' = A' X'X A.
The structured route composes
detrend_polywith the existing banded local operators (identity, Savitzky-Golay, Norris-Williams, finite difference and FCK).Result counters now separate
n_banded_operator_moment_candidates,n_structured_operator_moment_candidates,n_dense_operator_moment_candidates, andn_materialized_candidates.
Validation:
Added C++ public tests comparing
detrend(1) -> finite_difference(1)Ridge and PLS1 CV scores against a materializedn4m_sweep_runon manually transformed data.CPU build:
n4m_tests: 332 passed, 0 failed;n4m_internal_tests: passed.
CUDA build with
CUDA_VISIBLE_DEVICES=0:n4m_tests: 332 passed, 0 failed;n4m_internal_tests: passed.
Timing CSVs regenerated with the structured counter:
CPU compact mixed AOM medians: 133.47 ms for 48x64 and 1128.52 ms for 80x128;
CPU custom5 mixed medians: 29.15 ms and 315.25 ms for the same cells;
CUDA-build smoke compact mixed medians: 271.71 ms and 549.71 ms;
CUDA-build smoke custom5 mixed medians: 21.13 ms and 197.12 ms.
Remaining work:
This is an exact coverage/audit route, not a fused GPU performance win. It still transforms dense
p x pmoments on the host.Multi-target/non-NIPALS PLS and the true 200k-chain batched GPU screen remain open.
2026-06-04 — Banded AOM operator-moment route¶
Decision:
Keep ABI 1.18.0 unchanged.
Add an internal banded descriptor for shape-preserving local linear AOM operators instead of forcing every moment transform through a dense chain matrix.
Preserve exact scoring and candidate ordering; unsupported chains still fall back to the materialized native sweep.
Implementation:
Core:
cpp/src/core/aom_operators.{hpp,cpp}addsBandedLinearOperatorandbuild_aom_banded_operator.Banded operators currently cover
identity, Savitzky-Golay smooth, Savitzky-Golay derivative, Norris-Williams, finite difference, Gaussian and FCK.Dense low-rank/solve operators (
detrend_poly,whittaker) deliberately remain outside the banded route.Core:
cpp/src/core/aom_sweep.cpptransforms raw moments with sparse column entries:x_sum' = x_sum A;X'Y' = A' X'Y;X'X' = A' X'X A.
Route counters are exposed as additive result scalars:
n_operator_moment_candidates,n_banded_operator_moment_candidates,n_dense_operator_moment_candidates, andn_materialized_candidates.Guardrails:
Ridge banded moment scoring:
p <= 256and eitherp <= n_trainor all lambdas strictly positive;PLS1 banded moment scoring:
p <= 1024, single target, NIPALS, regression deflation.
Validation:
Added C++ ABI tests comparing banded Ridge and banded PLS1 scores at
p=64against a materializedn4m_sweep_runon manually transformed finite-difference data.CPU build:
n4m_tests: 330 passed, 0 failed;n4m_internal_tests: passed.
CUDA build with
CUDA_VISIBLE_DEVICES=0:n4m_tests: 330 passed, 0 failed;n4m_internal_tests: passed.
Timing CSVs regenerated with route counters:
CPU compact mixed AOM medians: 38.58 ms for 48x64 and 301.11 ms for 80x128;
CPU compact PLS1 rows: 1.94 ms for 60x32, 4.76 ms for 80x48, 9.27 ms for 96x64;
CPU custom5 PLS1 rows: 0.95 ms, 2.40 ms, 4.41 ms for the same cells;
CUDA-build smoke compact PLS1 rows: 34.64 ms, 28.51 ms, 46.44 ms.
Remaining work:
The route still stores dense
p x pmoments and performs host-side transforms; it is not the final fused CUDA 200k-chain grinder.Detrend and Whittaker are handled by later structured routes; batched all-chain execution is still outside this banded local-operator layer.
Batched IKPLS and CUDA grouped kernels remain open.
2026-06-04 — Medium AOM PLS1 operator-moment scoring¶
Decision:
Keep ABI 1.18.0 unchanged.
Add an internal exact PLS1 NIPALS scorer from sufficient statistics for strict-linear AOM chains.
Use it only for single-target NIPALS/regression-deflation PLS grids in the medium dense-operator regime:
p <= n_trainorp <= 48.
Implementation:
Core:
cpp/src/core/sweep.cppaddsscore_pls1_moment_sweep.Core:
cpp/src/core/aom_sweep.cpproutes compatible PLS-only and mixed sweeps through the operator-moment path.The scorer fits PLS1 prefixes from train moments (
Cxx,Cxy,Y'Y) and evaluates held-out SSE directly from held-out moments.Public OOF/final predictions are still produced by materializing the selected chain once. If the PLS1 moment scorer is unsupported or numerically degenerate for a chain, that chain falls back to the previous materialized PLS path.
Validation:
Added internal static-archive test
test_internal_sweep.cppcomparingscore_pls1_moment_sweepagainst materializedrun_moment_sweepPLS CV forscale_x=falseandscale_x=true.CPU build:
n4m_tests: 328 passed, 0 failed;n4m_internal_tests: passed, including PLS1 moment scoring.
CUDA build with
CUDA_VISIBLE_DEVICES=0:n4m_tests: 328 passed, 0 failed;n4m_internal_tests: passed, including PLS1 moment scoring.
Targeted Python tests through the build library: 41 passed for moment wrappers and AOM structural policy tests.
Full Python binding pytest against the repackaged ABI 1.18.0 library: 254 passed, 4 existing UVE warnings.
Catalog/ABI gates:
catalog/scripts/validate.py: PASS, 196 methods;catalog/scripts/reconcile_abi.py --check: 558 method symbols + 123 infra symbols = 681/681;catalog/scripts/split_legacy_methods.py --check: PASS.
git diff --check: clean.Import smoke without
N4M_LIB_PATH: ABI(1, 18, 0), packagedlibn4m.so, andaom_sweep_run/aom_chain_sweep_runexported.Timing CSVs regenerated for
aom_sweep:CPU PLS1 operator-moment medians: compact 60x32 3.98 ms, compact 80x48 11.28 ms, custom5 60x32 1.50 ms, custom5 80x48 8.70 ms;
CUDA-build smoke medians: 54.12 ms, 87.02 ms, 69.45 ms, 39.61 ms on the same rows.
Remaining work:
The current operator-moment transform still builds dense
A' C A, so it is not the final 200k-chain wide-spectrum grinder.Multi-target PLS and non-NIPALS PLS solvers still use the materialized path.
Fused CUDA kernels and sparse/banded operator descriptors remain open.
2026-06-04 — Native AOM operator PLS score stack¶
Decision:
Add an ABI-stable native
n4m.aom_operator_pls_stacksurface for the strict-linear AOM operator PLS1 score-stack experiment.Keep the existing
AOMOperatorPLSStacksklearn-style estimator as the flexible Python reference for custom operator matrices and optional baseline admission gates.Make the native contract single-target only, matching the PLS1 projector used in the source-free reference.
Implementation:
ABI 1.18.0 symbol:
n4m_aom_operator_pls_stack_fit.Core:
cpp/src/core/aom_operator_pls_stack.cpp.Python:
n4m.aom_operator_pls_stack.Catalog:
aom_pop.operator_pls_stack.Result contract includes candidate spec scores, fold scores, selected OOF predictions, final predictions, final
stack_features, Ridge coefficients and operator feature offsets.
Validation:
Added C++ contract tests for compact shape, selected criterion, prediction reconstruction from stack features and Ridge coefficients, explicit fold ids, feature offsets and multi-output rejection.
CPU build and
n4m_tests: 328 passed, 0 failed.Python targeted wrapper test passed with 13 tests using the ABI 1.18.0 build through
N4M_LIB_PATH.CUDA build and
CUDA_VISIBLE_DEVICES=0 n4m_tests: 328 passed, 0 failed.Full Python binding pytest against the packaged ABI 1.18.0 library: 254 passed, 4 existing UVE warnings.
Catalog/ABI gates:
catalog/scripts/validate.py: PASS, 196 methods;catalog/scripts/reconcile_abi.py --check: 558 method symbols + 123 infra symbols = 681/681;catalog/scripts/split_legacy_methods.py --check: PASS.
Import smoke without
N4M_LIB_PATH: ABI(1, 18, 0)andn4m.aom_operator_pls_stackexported from the packaged library.Timing CSVs added:
CPU compact medians: 9.56 ms for 32x64, 14.62 ms for 48x96, 24.29 ms for 64x128;
CUDA-build smoke medians: 4.13 ms, 8.75 ms, 11.95 ms on the same cells.
Remaining work:
Native v1 is not a fused batched GPU stack.
Native v1 does not include custom Python operator matrices, shuffled/both CV modes, or the optional baseline admission gate; those remain in the Python estimator.
2026-06-04 — Native AOM Ridge OOF simplex blender¶
Decision:
Add an ABI-stable native
n4m.aom_ridge_blendersurface for the strict-linear compact/wide AOM Ridge blender.Keep the existing
AOMRidgeBlendersklearn-style estimator as the flexible Python reference layer for explicit custom candidates.Require strictly positive Ridge lambdas in the native method so the blend pool uses stable Ridge fits only.
Implementation:
ABI 1.17.0 symbol:
n4m_aom_ridge_blender_fit.Core:
cpp/src/core/aom_ridge_blender.cpp.Python:
n4m.aom_ridge_blender.Catalog:
aom_pop.ridge_blender.Benchmark:
benchmarks/cross_binding/bench_aom_ridge_blender_timing.py.Result contract includes per-candidate OOF/final predictions and simplex weights so the blend can be audited exactly.
Validation:
Added C++ contract tests for compact result shape, simplex weights, prediction reconstruction, selected max-weight candidate and invalid lambda.
CPU build and
n4m_tests: 326 passed, 0 failed.CUDA build and
CUDA_VISIBLE_DEVICES=0 n4m_tests: 326 passed, 0 failed.Full Python binding pytest: 252 passed, 4 existing UVE warnings.
Catalog/ABI gates:
catalog/scripts/validate.py: PASS, 195 methods;catalog/scripts/reconcile_abi.py --check: 557 method symbols + 123 infra symbols = 680/680;catalog/scripts/split_legacy_methods.py --check: PASS.
Timing CSVs added:
CPU compact 48-candidate blend medians: 16.32 ms for 32x64, 69.39 ms for 64x128, 272.69 ms for 96x256;
CUDA-build smoke medians: 15.63 ms, 81.00 ms, 299.56 ms on the same cells.
Remaining work:
Native v1 is not a fused batched GPU blender.
The method is Ridge-only; PLS diversity still comes from
aom_sweep_run,aom_chain_sweep_runor future batched IKPLS work.Stateful preprocessings remain out of the native strict-linear bank.
2026-06-04 — Medium-wide AOM Ridge operator-moment scoring¶
Decision:
Keep ABI 1.16.0 unchanged.
Extend exact AOM Ridge operator-moment scoring beyond
p <= n_trainfor medium-wide grids where every Ridge lambda is strictly positive andp <= 48.Keep larger wide regimes on the materialized-chain plus dual-Ridge fallback: the current operator-moment transform builds dense
A' C A, so the feature cap is a compute-route guard, not a scoring proxy.
Implementation:
Core:
cpp/src/core/aom_sweep.cpp.Ridge-only and mixed Ridge+PLS AOM sweeps now share the same route predicate:
always allow exact operator-moment Ridge when
p <= n_train;additionally allow it for positive-lambda medium-wide
p <= 48;fallback for lambda grids containing zero in wide regimes, because the primal moment solve is not the right rank-deficient
lambda=0dual path.
Validation:
Added C++ test
aom_chain_sweep/wide_positive_ridge_operator_moments_match_materialized. It compares positive-lambda medium-wide operator-moment scores against a materialized fallback run forced by includinglambda=0.CPU build and
n4m_tests: 324 passed, 0 failed.CUDA build and
CUDA_VISIBLE_DEVICES=0 n4m_tests: 324 passed, 0 failed.Full Python binding pytest: 250 passed, 4 existing UVE warnings.
Catalog/ABI gates:
catalog/scripts/validate.py: PASS, 194 methods;catalog/scripts/reconcile_abi.py --check: 556 method symbols + 123 infra symbols = 679/679;catalog/scripts/split_legacy_methods.py --check: PASS.
Timing CSVs regenerated for
moment_sweep,aom_sweep, andaom_robust_hpoin CPU and CUDA-smoke variants.
Remaining work:
PLS rows still materialize transformed chains.
Larger wide AOM Ridge regimes still materialize transformed chains before the dual-kernel scorer.
Batched IKPLS and fused CUDA/operator kernels remain open.
2026-06-04 — Hybrid AOM Ridge-moment scoring inside mixed sweeps¶
Decision:
Keep ABI 1.16.0 unchanged.
Extend the exact operator-moment Ridge scorer from Ridge-only AOM sweeps to the Ridge candidate rows of mixed Ridge+PLS sweeps when
p <= n_train.Keep PLS candidate rows on the current materialized native PLS path. This is not batched IKPLS and not a fused GPU/operator-moment PLS engine.
Implementation:
Core:
cpp/src/core/aom_sweep.cpp.New hybrid path:
computes raw all-sample and held-out moments once;
transforms those moments by each strict-linear chain operator matrix;
scores Ridge rows through
score_ridge_moment_sweep;materializes each chain only for the PLS rows;
materializes the selected Ridge chain once when the global winner is Ridge.
Candidate-table order is preserved per chain: Ridge rows first, then PLS rows.
Validation:
Added C++ test
aom_chain_sweep/mixed_hybrid_matches_split_runs, comparing mixed sweep Ridge rows against a Ridge-only run and mixed PLS rows against a PLS-only run.CPU build and
n4m_tests: 323 passed, 0 failed.CPU timing smoke, ABI 1.16.0:
native_aom_sweepcompact mixed took 33.55 ms for 48x64 and 152.93 ms for 80x128;native_aom_chain_sweepcustom5 mixed took 15.94 ms for 48x64 and 51.10 ms for 80x128.
CUDA-build timing smoke, ABI 1.16.0:
native_aom_sweepcompact mixed took 1150.02 ms for 48x64 and 418.81 ms for 80x128;native_aom_chain_sweepcustom5 mixed took 150.82 ms for 48x64 and 192.70 ms for 80x128. These validate CUDA-build behavior, not fused GPU acceleration.
Remaining work:
PLS rows still materialize transformed chains.
Larger wide
p > n_trainAOM Ridge still materializes transformed chains before the dual-kernel scorer.Batched IKPLS and fused CUDA/operator kernels remain open.
2026-06-04 — AOMOperatorPLSStack Python reference port¶
Decision:
Port the AOM
operator_pls_stackexperiment as a source-free Python reference estimator.Keep it pre-ABI and diagnostic: it is not a product default, not a native catalog method, and not a fused GPU/moment PLS screen.
Restrict the default operator bank to fixed strict-linear matrices. Stateful scatter operators are deliberately excluded from the default path.
Implementation:
Python:
n4m.AOMOperatorPLSStack,n4m.sklearn.AOMOperatorPLSStack, andAOMOperatorPLSSpec.For each operator view, the estimator fits a fold-local column standardizer and PLS1 score projector, concatenates the per-operator scores, and fits a dependency-light Ridge head.
Spec selection uses train-only CV over
(components, alpha)withmean_rmse + std_penalty * std_rmse + gap_penalty * train_val_gap.If a
baseline_estimatoris supplied, the operator stack is admitted only when its OOF RMSE improves the baseline bymin_relative_oof_gain.Metadata passed to
fitis audit-only and does not affect operators, specs, admission, or predictions.
Validation:
Added Python tests for custom fixed operators, false-positive baseline-gate rejection, metadata invariance, default strict-operator bank smoke behavior, exports, and predict-before-fit behavior.
Targeted pytest:
PYTHONPATH=bindings/python/src /home/delete/.venv/bin/python -m pytest -q bindings/python/tests/test_aom_structural_policy.pypassed with 28 tests.Full Python binding pytest:
PYTHONPATH=bindings/python/src /home/delete/.venv/bin/python -m pytest -q bindings/python/testspassed with 250 tests and 4 existing UVE warnings.CPU build and
n4m_tests: 322 passed, 0 failed.CUDA build and
n4m_testswithCUDA_VISIBLE_DEVICES=0: 322 passed, 0 failed.catalog/scripts/validate.py --strict-abi: PASS, 194 methods, 679/679 exportedn4m_*symbols covered.git diff --check: clean.Import/use smoke:
n4m.AOMOperatorPLSStack is n4m.sklearn.AOMOperatorPLSStack; selected spec is anAOMOperatorPLSSpec; fit/predict path works.
Remaining work:
Native ABI/catalog integration is still open.
Batched IKPLS and fused operator/moment PLS scoring remain open; this port materializes operator views in Python.
2026-06-04 — AOMRidgeBlender Python reference port¶
Decision:
Port the
nirs4all-aomfold-safe AOM-Ridge blender design intonirs4all-methodsas a source-free Python reference estimator.Keep it pre-ABI and out of the strict native catalog until a C++/ABI blend/report/predict surface exists.
Allow two modes: explicit estimator/factory candidates, or a default chain+Ridge pool built from
build_aom_control_chain_bank.
Implementation:
Python:
n4m.AOMRidgeBlenderandn4m.sklearn.AOMRidgeBlender.OOF scoring: each candidate is refit fold-locally; validation rows are never fitted before their OOF prediction.
Blend: solve the regularized non-negative simplex QP with scipy SLSQP when available, falling back to projected gradient on the simplex.
Metadata passed to
fitis stored only inblend_report_for audit and is not used for candidate construction, weighting, or prediction.
Validation:
Added Python tests for exact simplex weight recovery, fold-local OOF fits, metadata invariance, default chain+Ridge pool construction, exports, and predict-before-fit behavior.
Targeted pytest:
PYTHONPATH=bindings/python/src /home/delete/.venv/bin/python -m pytest -q bindings/python/tests/test_aom_structural_policy.pypassed with 23 tests.Full Python binding pytest:
PYTHONPATH=bindings/python/src /home/delete/.venv/bin/python -m pytest -q bindings/python/testspassed with 245 tests and 4 existing UVE warnings after staging ABI 1.16.0 intobindings/python/src/{n4m,pls4all}/lib.During the full pytest run, the current ABI exposed an existing
MBPLSRegressionmismatch against directmb_pls_fit. The wrapper now matches MB-PLS direct config scaling defaults and returns the C-side in-sample predictions whenpredictreceives the fitted training matrix.CPU build and
n4m_tests: 322 passed, 0 failed.CUDA build and
n4m_testswithCUDA_VISIBLE_DEVICES=0: 322 passed, 0 failed.catalog/scripts/validate.py --strict-abi: PASS, 194 methods, 679/679 exportedn4m_*symbols covered.git diff --check: clean.Import/use smoke:
n4m.AOMRidgeBlender is n4m.sklearn.AOMRidgeBlender; defaultchains=("raw",)fit/predict path works.
Remaining work:
Native ABI/catalog integration is still open.
No CUDA-fused blender exists; any GPU use must come from wrapped candidate estimators, not from the blender itself.
2026-06-04 — Dual-kernel Ridge screening for wide p > n_train folds¶
Decision:
Keep ABI 1.16.0 unchanged.
Optimize the existing
p > n_trainRidge path insiden4m_sweep_runwithout changing scores or outputs.Continue using materialized transformed chains in AOM wide regimes for now; the improvement is inside the Ridge scorer.
Implementation:
Core:
cpp/src/core/sweep.cpp.RidgeDualDesigncan store held-out/train cross-kernels for dual folds.Ridge screening solves
(K_train + lambda I) alpha = Y_trainand predicts held-out rows asK_heldout,train alpha + y_meanwhen a simple cost heuristic predicts this is cheaper than the dual-beta scoring path.This avoids reconstructing feature-space coefficients and then doing
X betafor every fold/lambda during CV scoring in the regimes where the cross-kernel setup cost is amortized. The final selected model still refits as before to populate public coefficients and predictions.
Validation:
Added C++ test
sweep/wide_dual_ridge_scores_match_materialized_cv, comparingp > n_trainsweep scores against explicit fold-by-foldn4m_ridge_fit.CPU build and
n4m_tests: 322 passed, 0 failed.
Remaining work:
This is exact and useful for Ridge sweeps, but still not a fused operator-aware dual kernel for AOM chains. The transformed chain is still materialized before
n4m_sweep_run.Batched IKPLS and CUDA fused kernels remain open.
2026-06-04 — Ridge-only operator-moment scoring for strict AOM chains¶
Decision:
Keep ABI 1.16.0 unchanged.
Add an internal Ridge-only acceleration path for
n4m_aom_sweep_runandn4m_aom_chain_sweep_runwhenheads_mask == Ridgeandp <= n_train.Preserve the existing materialized chain path for mixed Ridge+PLS, PLS-only, and
p > n_trainregimes.
Implementation:
Core:
cpp/src/core/aom_sweep.cppbuilds the strict-linear chain operator matrix by applying existing AOM kernels to an identity basis. It then transforms raw moments asx_sum A,A' X'X A, andA' X'Y.Core:
cpp/src/core/sweep.cppexposes an internalscore_ridge_moment_sweephook that fits fold Ridge models from train moments and scores held-out SSE directly from held-out moments. No per-candidate held-out row predictions are needed during screening.The selected chain is materialized once after ranking to populate public OOF predictions, final predictions, coefficients and fold ids.
Validation:
Added C++ test
aom_chain_sweep/ridge_operator_moments_match_materialized, comparing Ridge-only operator-moment scores with the Ridge rows from a materialized Ridge+PLS run.CPU build and
n4m_tests: 321 passed, 0 failed.CUDA build and
n4m_testswithCUDA_VISIBLE_DEVICES=0: 321 passed, 0 failed.Python direct tests without pytest: 28 passed.
Python smoke:
n4m.aom_chain_sweep_run(..., heads=("ridge",))ABI(1, 16, 0), candidate table(6, 5), materialized Ridge score parity asserted.CUDA-build Python smoke: same Ridge-only path, candidate table
(6, 5).catalog/scripts/validate.py --strict-abi: PASS, 194 methods, 679/679 exportedn4m_*symbols covered.CPU timing smoke, ABI 1.16.0:
native_aom_chain_sweep_ridgecustom5 took 7.39 ms for 96x32 and 51.41 ms for 160x64; compact built-in Ridge-only took 15.90 ms and 123.10 ms respectively.CUDA-build timing smoke, ABI 1.16.0:
native_aom_chain_sweep_ridgecustom5 took 41.38 ms for 96x32 and 70.01 ms for 160x64; compact built-in Ridge-only took 101.16 ms and 152.29 ms respectively. This validates CUDA-build behavior, not fused GPU acceleration.
Remaining work:
This path is exact but not the full 200k-chain engine: it builds dense operator matrices and is deliberately limited to Ridge-only
p <= n_train.Batched IKPLS,
p > n_trainoperator-aware dual Ridge, sparse/banded operator algebra, and fused CUDA kernels remain open.
2026-06-04 — PLS component-prefix scoring inside native sweeps¶
Decision:
Keep ABI 1.16.0 unchanged.
Replace per-component PLS refits in
n4m_sweep_runwith one max-component fit per fold, followed by coefficient-prefix reconstruction for each requested component count.Preserve a fallback to the old per-component materialized fit if a max-component fold fit fails.
Implementation:
Core:
cpp/src/core/sweep.cpp.Reconstruct prefix coefficients from the fitted model arrays:
W[:,:k],P[:,:k],Q[:,:k],x_mean/x_scale,y_mean/y_scale.n4m_aom_sweep_runandn4m_aom_chain_sweep_runbenefit automatically because both delegate PLS scoring ton4m_sweep_run.
Validation:
Added C++ test
sweep/pls_component_grid_matches_materialized_cv_scores.CPU build and
n4m_tests: 320 passed, 0 failed.CUDA build and
n4m_testswithCUDA_VISIBLE_DEVICES=0: 320 passed, 0 failed.Python direct tests without pytest: 28 passed.
CUDA Python smoke:
n4m.sweep_runABI(1, 16, 0), PLS candidate table(3, 4), OOF RMSE assertion passed.catalog/scripts/validate.py --strict-abi: PASS, 194 methods, 679/679 exportedn4m_*symbols covered.CPU timing smoke, ABI 1.16.0:
native_sweep_plsover three component candidates took 3.55 ms for 64x64, 5.17 ms for 128x128, and 6.72 ms for 192x256.CUDA-build timing smoke, ABI 1.16.0:
native_sweep_plsover three component candidates took 41.92 ms for 64x64, 46.31 ms for 128x128, and 69.67 ms for 192x256. This validates CUDA-build behavior, not fused GPU acceleration.
Remaining work:
This is still not IKPLS. It reduces repeated component fits but still materializes each transformed chain/fold.
Batched IKPLS and fused CUDA/operator-moment kernels remain the next acceleration layer.
2026-06-04 — User-defined strict AOM chain sweep, ABI 1.16.0¶
Decision:
Ship
n4m_aom_chain_sweep_runas the ABI-stable arbitrary strict-linear preprocessing-chain surface.Use flat C arrays instead of opaque chain objects:
chain_offsets,op_kinds,param_offsets,params.Keep the strict-linear invariant: identity, detrend, Savitzky-Golay, Norris-Williams, finite difference, Whittaker and FCK are accepted; stateful operators such as SNV/MSC/EMSC are rejected in this path.
Reuse the exact same scoring path as
n4m_aom_sweep_run, so Ridge/PLS grids, explicit folds and candidate table semantics stay identical.
Implementation:
Core:
run_aom_chain_sweepincpp/src/core/aom_sweep.cpp.C ABI: declaration in
cpp/include/n4m/pls.h, wrapper incpp/src/c_api/c_api_method_result.cpp.Python:
n4m.aom_chain_sweep_run(X, y, chains, ...), accepting strings, tuples and dictionaries for operator specs.Catalog/doc:
catalog/methods/aom_pop.aom_chain_sweep.yaml,docs/methods/aom_chain_sweep_run.md.
Validation:
C++ tests added:
aom_chain_sweep/custom_descriptor_contractandaom_chain_sweep/rejects_non_strict_operator.CPU build and
n4m_tests: 319 passed, 0 failed.Python direct tests without pytest: 28 passed.
CUDA build and
n4m_testswithCUDA_VISIBLE_DEVICES=0: 319 passed, 0 failed.CUDA Python smoke:
n4m.aom_chain_sweep_runABI(1, 16, 0), candidate table(6, 5), OOF RMSE assertion passed.ABI snapshots regenerated: Linux 680 symbols, macOS/Windows 679.
catalog/scripts/validate.py --strict-abi: PASS, 194 methods, 679/679 exportedn4m_*symbols covered.CPU timing smoke, ABI 1.16.0: custom5 profile with 5 chains and 25 candidates took 8.15 ms for 48x64 and 19.51 ms for 80x128.
CUDA-build timing smoke, ABI 1.16.0: custom5 profile with 5 chains and 25 candidates took 257.22 ms for 48x64 and 117.95 ms for 80x128. This validates CUDA-build behavior, not fused GPU acceleration.
Remaining work after this ABI:
The chain descriptor still materializes each transformed
X.Batched IKPLS and fused operator-moment/GPU kernels remain open.
2026-06-04 — Configurable native AOM sweep, ABI 1.15.0¶
Decision:
Ship
n4m_aom_sweep_runas the reusable AOM preprocessing sweep surface.Reuse the native strict-linear compact/wide chain bank from robust-HPO.
Delegate per-chain model scoring to
n4m_sweep_run, so users can control Ridge lambda grids, PLS component grids, active heads and explicit folds.Keep arbitrary operator descriptors, batched IKPLS and fused CUDA kernels as later optimization layers.
Implementation:
Core:
cpp/src/core/aom_sweep.hpp,cpp/src/core/aom_sweep.cpp.C ABI: declaration in
cpp/include/n4m/pls.h, wrapper incpp/src/c_api/c_api_method_result.cpp.Python:
n4m.aom_sweep_run(X, y, profile=..., fold_ids=..., ridge_lambdas=..., pls_components=..., heads=...).Catalog/doc:
catalog/methods/aom_pop.aom_sweep.yaml,docs/methods/aom_sweep_run.md.Benchmark:
benchmarks/cross_binding/bench_aom_sweep_timing.py.
Validation:
C++ test
aom_sweep/compact_contract_and_oof_scorevalidates the compact candidate table shape, chain coverage, selected candidate consistency, explicit fold ids and selected OOF RMSE.CPU build and
n4m_tests: 317 passed, 0 failed.CUDA build and
n4m_testswithCUDA_VISIBLE_DEVICES=0: 317 passed, 0 failed.Python direct tests without pytest: 27 passed.
CUDA Python smoke:
n4m.aom_sweep_runABI(1, 15, 0), candidate table(24, 5), OOF RMSE assertion passed.ABI snapshots regenerated: Linux 679 symbols, macOS/Windows 678.
catalog/scripts/validate.py --strict-abi: PASS, 193 methods, 678/678 exportedn4m_*symbols covered.CPU timing smoke, ABI 1.15.0: compact profile with 12 chains and 60 candidates took 9.83 ms for 48x64 and 40.14 ms for 80x128.
CUDA-build timing smoke, ABI 1.15.0: compact profile with 12 chains and 60 candidates took 862.90 ms for 48x64 and 421.98 ms for 80x128. This validates CUDA-build behavior, not fused GPU acceleration.
Remaining work:
Add public strict-linear operator descriptors for arbitrary preprocessing chain campaigns.
Replace materialized PLS CV with batched IKPLS.
Add fused CUDA kernels and grouped-GEMM/operator batching before claiming 200k-chain GPU screening.
2026-06-04 — Native Ridge/PLS sweep, ABI 1.14.0¶
Decision:
Ship
n4m_sweep_runas the first product sweep ABI.Support exact Ridge CV over moment fold subtraction or precomputed dual folds.
Support fold-local materialized PLS component screening through the existing native PLS model path.
Keep fused batched IKPLS as the later optimization target.
Implementation:
Core:
cpp/src/core/sweep.hpp,cpp/src/core/sweep.cpp.C ABI: declaration in
cpp/include/n4m/pls.h, wrapper incpp/src/c_api/c_api_method_result.cpp.Python:
n4m.sweep_run(X, y, cv=..., fold_ids=..., ridge_lambdas=..., pls_components=..., heads=("ridge", "pls")).Catalog/doc:
catalog/methods/utilities.sweep.yaml,docs/methods/sweep_run.md.Optimisation: moment primal solve is kept for
p <= n_train; spectral folds withp > n_trainuse a precomputed dual design and reuseK = XX'across Ridge lambdas.
Validation:
C++ test
sweep/ridge_oof_matches_materialized_cvcompares selected OOF predictions from moment subtraction against materialized fold-by-foldn4m_ridge_fit.C++ test
sweep/selects_minimum_candidate_and_generates_foldsvalidates candidate ranking and generated fold ids.C++ test
sweep/pls_oof_matches_materialized_cvcompares selected PLS OOF predictions against fold-by-foldn4m_model_fit/n4m_model_predict.CPU build and
n4m_tests: 316 passed, 0 failed.CUDA build and
n4m_testswithCUDA_VISIBLE_DEVICES=0: 316 passed, 0 failed.Python direct tests: 32 passed.
CUDA Python smoke:
n4m.sweep_runABI(1, 14, 0), PLS candidate table(3, 4), OOF RMSE assertion passed.ABI snapshots regenerated: Linux 678 symbols, macOS/Windows 677.
catalog/scripts/validate.py --strict-abi: PASS, 192 methods, 677/677 exportedn4m_*symbols covered.Timing smoke added:
benchmarks/cross_binding/bench_moment_sweep_timing.py.CPU timing smoke, ABI 1.14.0:
native_sweep_ridgevsmaterialized_cvselected the same lambdas and matched CV RMSE on all cells. Median Ridge timings were 2.48 ms vs 4.98 ms for 64x64, 16.89 ms vs 25.98 ms for 128x128, and 54.94 ms vs 85.40 ms for 192x256. Materialized PLS sweep timings were 0.46 ms, 1.48 ms and 4.37 ms on the same cells for three component candidates.CUDA-build timing smoke, native-only ABI 1.14.0: Ridge timings were 12.79 ms, 17.74 ms and 94.65 ms for 64x64, 128x128 and 192x256. Materialized PLS timings were 47.90 ms, 58.67 ms and 92.24 ms. This validates CUDA-build behavior, not fused GPU acceleration for PLS.
Remaining work:
Replace materialized PLS CV with batched IKPLS inside
n4m_sweep_run.Add strict-linear operator descriptors so the sweep can score preprocessing variants without materializing every transformed matrix.
Add larger timing campaigns once batched IKPLS and operator descriptors land.
2026-06-04 — Native moment substrate, ABI 1.13.0¶
Decision:
Ship
n4m_moments_compute,n4m_moments_subset_computeandn4m_moments_subtractas the first native moment layer.Keep the API on
n4m_method_result_trather than adding a new opaque handle for v1. This matches the existing extra-model result surface and keeps Python wrappers direct.Return raw row-additive moments (
x_sum,y_sum,xtx,xty,yty) and centered moments (x_mean,y_mean,cxx,cxy,cyy).Subtract only raw moments, then recompute centering from the remaining row count. This is the invariant needed for exact CV fold subtraction.
Implementation:
Core:
cpp/src/core/moments.hpp,cpp/src/core/moments.cpp.C ABI: declarations in
cpp/include/n4m/pls.h, wrappers incpp/src/c_api/c_api_method_result.cpp.Python:
n4m.moments(X, y, row_indices=None)andn4m.moments_train_from_heldout(X, y, heldout_indices).Catalog/doc:
catalog/methods/utilities.moments.yaml,docs/methods/moments.md.
Validation:
CPU build and
n4m_tests: 313 passed, 0 failed.CUDA build and
n4m_testswithCUDA_VISIBLE_DEVICES=0: 313 passed, 0 failed.Python direct tests: 29 passed.
CUDA Python smoke:
n4m.momentsABI(1, 13, 0), Gram and fold-recenter assertions passed.ABI snapshots regenerated: Linux 677 symbols, macOS/Windows 676.
catalog/scripts/validate.py --strict-abi: PASS, 191 methods, 676/676 exportedn4m_*symbols covered.
Remaining work:
Implement
n4m_sweep_runfor batched Ridge/PLS CV over variant descriptors.Add strict-linear operator descriptors that can update moments without materializing every transformed
X.Decide whether the next native sweep stage should start with moment Ridge CV, IKPLS PLS CV, or a mixed Ridge/PLS candidate table.
Add a timing benchmark once the sweep API exists; the current moment layer is a correctness substrate, not yet the 200k-chain grinder.
2026-06-05 — Staged strict-chain cartesian orchestration (Python workflow)¶
Decision:
Promote the staged cartesian runner (handoff gap #5, proto
cartesian.py/impact.py) to a first-class Python workflowaom_staged_chain_campaign, rather than a one-off benchmark script.Make it pure orchestration over the existing helpers — no new C ABI symbol and no new numerical kernel, so
libn4mstays the single engine.A stage = one score-only strict-linear screen (
compact/wide/labprofile, or an explicit override dict mixing profiles and Ridge/PLS/mixed head plans). The campaign merges per-stage retained candidates, keeps the top globalper-head rows across stages, exact-CV refits the union once, and attaches preprocessing-impact + screen-vs-refit rank diagnostics plus an optional offline-only holdout audit.
Constraints honoured:
Strict-linear chains only (no hors-moment nonlinear lifts).
No dataset/source/id/name input or selection; stage
nameis a cosmetic label and renaming stages provably cannot change the winner.Production selection is exact-CV refit on train only (
selection_uses_test_set=False);X_audit/y_auditare scored underreport["audit"]withaudit_only=Trueand never drive selection.
Implementation:
bindings/python/src/n4m/python.py:aom_staged_chain_campaignplus the_normalize_staged_chain_stages/_aom_merge_staged_candidates/_staged_normalized_headshelpers and the_AOM_STAGED_PLANSpresets. Reusesaom_chain_score_campaign,_aom_screen_refit_candidate_union,aom_refit_candidates,aom_candidate_preprocessing_impact,aom_candidate_rank_diagnostics,aom_evaluate_candidates.Re-exported from
n4m,n4m.aom,n4m.moment(attr +__all__+ an inventorystaged_chain_campaignrow mapped toaom_pop.aom_staged_chain_campaign,catalog_role=catalog_binding).Added
NativeAOMStagedChainCampaignRegressor, a reusable sklearn wrapper that runs the staged campaign, selects by train exact-CVrefit_cv_rmseonly, and refits the selected row throughNativeAOMFixedCandidateRegressorin final-only mode. It is exported fromn4m,n4m.sklearn,n4m.aomandn4m.moment, with separatestaged_chain_campaign_estimatorinventory rows that intentionally omitX_audit/y_auditoptions.Report schema
n4m.aom_staged_chain_campaign.v1;rowsare consumable byNativeAOMFixedCandidateRegressor.from_refit_report.Doc:
docs/methods/aom_staged_chain_campaign.md.Tests:
bindings/python/tests/test_aom_staged_campaign.py(9 cases).
Validation:
py_compileon changed sources + tests: OK.pytest test_aom_staged_campaign.py: 9 passed.pytest test_aom_moment_facade.py test_moment_model_wrappers.py test_aom_staged_campaign.py: 70 passed.catalog/scripts/validate.py --strict-abi: PASS, 200 methods, 701/701.ruff checkon changed files: no issues.git diff --check: clean.
Remaining work:
Wire the runner into the cross-binding benchmark campaign against the robust AOM / TabPFN baselines (handoff suggested next step).
Follow-up staged resume:
Added
checkpoint_dir,resumeandmax_chunks_per_runtoaom_staged_chain_campaign.Each stage now forwards to the existing
aom_chain_score_campaigncheckpoint/resume mechanism with a stable per-stage JSON checkpoint path.Partial staged reports are still exact-refit-able over currently retained rows and expose
screen_complete=False,n_remaining_stage_chunks_total, per-stage chunk counters and checkpoint paths.
Follow-up timing smoke:
Added
benchmarks/cross_binding/bench_aom_staged_chain_campaign_timing.pyto time the staged campaign on synthetic data and write one CSV row per--plansentry.The script exposes the screen/refit controls and the existing CPU/GPU knobs, and records retention counts, selected exact-CV winner, impact/rank availability, refit counters, route fractions,
library_pathand ABI.The method doc now states that this measures orchestration plus exact-refit timing only; it is not the future fused IKPLS grinder benchmark.
Checkpoint smoke: first pass with
max_chunks_per_run=1reportsscreen_complete=False, second pass resumes toscreen_complete=True; with--repeats > 1, the benchmark isolates checkpoint state per repeat.
Follow-up oracle comparison:
Added
benchmarks/cross_binding/compare_aom_staged_to_oracles.py, an offline CSV comparator that normalizes per-dataset scores and joins a future staged campaign target against the real local oracle artifacts:/home/delete/nirs4all/nirs4all-aom/benchmarks/runs/scenarios/paper_aom_aompls_seeds012/results.csv,/home/delete/nirs4all/nirs4all-aom/benchmarks/runs/ridge/all54_headline/results.csv/home/delete/nirs4all/nirs4all-aom/benchmarks/pls/cohort_regression.csvand/home/delete/nirs4all/nirs4all-lab/benchmark/results/1_master_results.csv.Default oracles are separated as
aom_pls_oracle,aom_ridge_oracleandtabpfn_oracle; the comparator chooses the best score per dataset within each source, not a default row. The AOM-PLS oracle filters out plainPLS-standard-*, the AOM-Ridge oracle filters out plainRidge-rawand non-Ridge AOM-PLS rows, and the TabPFN oracle chooses the best available Raw/Opt reference per dataset.Smoke without a target produced 61 dataset keys in the oracle union (
53AOM-PLS-family rows,53AOM-Ridge-family rows,61TabPFN rows). The filtered winner counts were30TabPFN,19AOM-Ridge and12AOM-PLS.
Follow-up real-cohort runner:
Added
benchmarks/cross_binding/run_aom_staged_real_cohort.py, a lightweight real-data runner for fixed train/test NIRS splits under/home/delete/nirs4all/nirs4all-data/regression.It loads a cohort CSV such as
/home/delete/nirs4all/nirs4all-aom/benchmarks/runs/ridge/diverse11_cohort.csv, runsaom_staged_chain_campaignwithX_audit/y_audit, and writes rows compatible with the oracle comparator.Output CSVs are replaced by default;
--resumepreserves an existing output and skips already-OK dataset rows.Main reported
rmsepis the test RMSE of the candidate selected by train CV (eval.best_cv), not the test-best candidate. The test-best candidate is written separately asaudit_oracle_rmsefor offline screen-recall analysis.Smoke command on one real dataset with
max_chains=2,cv=3completed in about 1.1 s campaign time and fed successfully intocompare_aom_staged_to_oracles.py.Calibration run on 10
diverse11_cohort.csvregression datasets withplan=compact,max_chains=12,top_k=12,refit_top_k=6completed and wrote/tmp/aom_staged_real_cohort_10.csv. Comparison summary: paired target wins0/9vsaom_pls_oracle,0/10vsaom_ridge_oracle,1/10vstabpfn_oracle; median ratios were1.17183,1.22661, and1.2894respectively after filtering true AOM and TabPFN Raw/Opt oracles. Treat this as a tiny-budget runner smoke, not evidence about the full staged/cartesian ceiling.
Follow-up staged sklearn/CUDA readiness:
Added
NativeAOMStagedChainCampaignRegressorto the coverage matrix and documented it as the reusable estimator surface for staged campaigns.Ran a one-GPU staged PLS smoke against
build/cuda-on/cpp/src/libn4m.sowithCUDA_VISIBLE_DEVICES=0,plan=compact,max_chains=4, forcedcuda_pls_min_device_features=1andcuda_pls_parallel_folds=True.Wrote
benchmarks/cross_binding/aom_staged_chain_campaign_timing_cuda_smoke.csv. The smoke keptselection_uses_test_set=False,screen_complete=True, and reportedn_pls_moment_cuda_device_cv_fits=9,n_pls_moment_host_cv_fits=0,n_pls_moment_cuda_parallel_fold_jobs=9.Extended
benchmarks/cross_binding/aom_moment_cuda_facade_smoke.pyso the existing one-process CUDA facade smoke also assertsaom_staged_chain_campaignandNativeAOMStagedChainCampaignRegressorare exported fromn4m,n4m.aomandn4m.moment, then fits the staged sklearn estimator on the CUDA build. The regeneratedaom_moment_cuda_facade_smoke.jsonreports staged estimatorn_pls_moment_cuda_device_cv_fits=8,n_pls_moment_host_cv_fits=0,n_pls_moment_cuda_parallel_fold_jobs=8andselection_uses_test_set=False.Extended that same CUDA facade smoke to cover the preconfigured
aom_sweep_runprofile path and the reusable AOM-PLS / POP-PLS selector surfaces. The regenerated JSON reportsaom_profile_sweepwithn_pls_moment_cuda_device_cv_fits=48and host PLS CV fits at0; theNativeAOMPLSRegressorandNativePOPPLSRegressorreplay the native input-space coefficients with max absolute prediction error below1e-10.
Follow-up diversity wrapper CUDA readiness:
Extended
benchmarks/cross_binding/bench_aom_ridge_blender_timing.pyandbenchmarks/cross_binding/bench_aom_operator_pls_stack_timing.pywith--mode native|wrapper|both.Regenerated the one-GPU CUDA smoke CSVs with
--mode both, soaom_ridge_blender_timing_cuda_smoke.csvnow includesnative_aom_ridge_blenderandnative_aom_ridge_blender_sklearnrows, andaom_operator_pls_stack_timing_cuda_smoke.csvnow includesnative_aom_operator_pls_stackandnative_aom_operator_pls_stack_sklearnrows.The sklearn rows record
prediction_replay_max_abs_error, proving the wrappers replay the native folded model state on the CUDA build path; current smokes are within1e-10.
Follow-up staged catalog readiness:
Added a dedicated Python-backed catalog method
aom_pop.aom_staged_chain_campaignwith benchmark entrybenchmarks/cross_binding/bench_aom_staged_chain_campaign_timing.py.Updated the
n4m.aomandn4m.momentinventory rows sostaged_chain_campaignis the catalog binding andstaged_chain_campaign_estimatoris a sklearn wrapper of that staged method.Brought the legacy catalog source and split files back into sync for the existing PCR and moment-stack entries; the split now contains 201 methods.
Validation after the catalog split:
split_legacy_methods.py --checkpassed with 201 per-method files,catalog/scripts/validate.py --strict-abipassed with 201 methods and701/701ABI symbols,catalog/scripts/validate.py --check-referencespassed with201/201production methods covered, and the targeted facade/wrapper/staged pytest set stayed at 70 passed.
Follow-up facade invariant guard:
Added a generic
n4m.aom/n4m.momentinventory test that rejects publicconfig_optionsexposing dataset/source/database/metadata routing knobs. Legitimate fold/candidate identifiers such asfold_ids,with_idsandinclude_identityremain allowed.Added a catalog-wide Python binding test so every per-method
bindings.pythondeclaration resolves to a real exported object and legacy aliases resolve too. This pins the catalog/source split correction that removed stale, wrong bindings fromaugmentation.edge_artifacts.edge_artifactsanddiagnostics.approximate_press.Extended
catalog/scripts/validate.pyso every non-nullbench.registry_entrymust point to an existing benchmark file. This keeps Python-backed AOM/moment methods such asaom_pop.aom_staged_chain_campaignandmodels.ensembles.moment_stacktied to runnable timing evidence.Added
test_aom_moment_cuda_smoke_artifacts.py, a release-readiness guard over the committed one-GPU CUDA smoke JSON/CSVs. It verifies the CUDA facade artifact, staged campaign smoke, moment-stack PLS base smoke, and native + sklearn replay rows for the Ridge blender and operator PLS stack. It now also covers robust-HPO native CUDA build smokes, global AOM profile-sweep device routing, AOM-PLS / POP-PLS reusable coefficient replay, and the PLS/mixed/Ridge screen-refit CUDA build artifacts.Added
test_aom_benchmark_tools.py, a fast guard for the offline staged benchmark helpers. It pins the oracle comparator’s filtering of true AOM-PLS/AOM-Ridge and TabPFN Raw/Opt rows, and it verifies that the real cohort runner reports the train-CV-selected audit row (eval.best_cv) while keeping the test-best retained row (best_eval) audit-only.Updated the public methods index for
aom_pls,pop_plsandaom_staged_chain_campaign, and added a facade test that everydocs/methods/*.mdpage advertised byn4m.aom/n4m.momentis linked fromdocs/methods/index.md.Targeted facade pytest now has 13 tests, and the combined benchmark-tool/catalog/CUDA-artifact/facade/wrapper/staged pytest set reports 87 passed.
Follow-up direct moment-head CUDA readiness:
Audited the direct reusable heads requested around Ridge/PLS-adjacent moment methods: Ridge, PLS, PCR, CPPLS, continuum regression and ECR.
The first CUDA smoke attempt exposed a stale
build/cuda-onlibrary missingn4m_pcr_fit; rebuilt with/home/delete/.venv/bin/cmake --build --preset cuda-on --parallel.Verified the rebuilt CUDA shared library exports the direct head symbols:
n4m_ridge_fit,n4m_pcr_fit,n4m_cppls_fit,n4m_continuum_regression_fitandn4m_ecr_fit.Generated
benchmarks/cross_binding/direct_moment_heads_timing_cuda_smoke.csv, covering the direct heads on three shapes as both ABI-close functions and sklearn-style replay wrappers throughbuild/cuda-on/cpp/src/libn4m.so.Extended
test_aom_moment_cuda_smoke_artifacts.pyso the committed artifact is release-guarded for exact method coverage, three shapes per method, both native/sklearn backends per method/shape, CUDA build path, finite RMSE and replay error at numerical noise level.After this direct-head guard, the targeted benchmark-tool/catalog/CUDA-artifact/facade/wrapper/staged pytest set reports 88 passed.
Follow-up sweep/selector CUDA artifact refresh:
Regenerated
benchmarks/cross_binding/moment_sweep_timing_cuda_smoke.csv,benchmarks/cross_binding/aom_sweep_timing_cuda_smoke.csvandbenchmarks/cross_binding/aom_selector_timing_cuda_smoke.csvagainst the currentbuild/cuda-on/cpp/src/libn4m.soafter finding the previous committed artifacts still referenced ABI1.18.0.The refreshed artifacts now report ABI
1.21.0andbuild/cuda-onlibrary paths.Added release-readiness guards over those artifacts:
moment sweep exact PLS CV rows must use
cuda_pls_parallel_folds=True,cuda_pls_min_device_features=1, zero host CV fits and device CV fits equal to total PLS moment CV fits;AOM sweep exact PLS CV rows across compact/global, explicit chain, Whittaker, FCK, Gaussian, PLS exact/proxy and Ridge coverage must also have zero host CV fits and device CV fits equal to total PLS moment CV fits;
AOM-PLS / POP-PLS selector function and sklearn rows must replay with
replay_max_abs <= 1e-10.
After these sweep/selector guards, the targeted benchmark-tool/catalog/CUDA-artifact/facade/wrapper/staged pytest set reports 91 passed.
Follow-up real-cohort CUDA calibration:
Added staged campaign route counters to
aom_staged_chain_campaignreports and propagated them throughrun_aom_staged_real_cohort.pyCSV output:n_screen_pls_moment_*,n_refit_pls_moment_*, plus the CUDA knob columns. This makes real benchmark rows auditable for CPU-vs-GPU routing instead of relying on process logs.Ran a one-GPU compact 10-dataset real-cohort calibration with
N4M_LIB_PATH=build/cuda-on/cpp/src/libn4m.so,CUDA_VISIBLE_DEVICES=0,plan=compact,cv=4,max_chains=12,top_k=12,refit_top_k=6, Ridge+PLS heads,cuda_pls_min_device_features=1,cuda_pls_parallel_folds=True, andbackend_min_cuda_product=1.Output
/tmp/n4m_aom_staged_real_cohort_10_cuda.csv:10/10rows OK,screen_complete=True,selection_uses_test_set=False, ABI1.21.0andlibrary_path=build/cuda-on/cpp/src/libn4m.so.Route counters: screen PLS moment CV fits
480, screen host fits0, screen CUDA device fits480; refit PLS moment CV fits140, refit host fits0, refit CUDA device fits140.Timing: total fit time
81.69 s, median3.43 s; the BERRY row dominated at about51.17 s.Offline oracle comparison written to
/tmp/n4m_aom_staged_oracle_comparison_10_cuda.csvand/tmp/n4m_aom_staged_oracle_comparison_10_cuda.md:AOM-PLS oracle: paired
9, target wins0, median ratio1.21015;AOM-Ridge oracle: paired
10, target wins0, median ratio1.24282;TabPFN oracle: paired
10, target wins1, median ratio1.33059.
Interpretation: the compact staged workflow is now demonstrably train-only and GPU-routed on real NIRS data, but a tiny
max_chains=12budget remains far behind the robust AOM oracles. This benchmark is useful calibration, not evidence of the final larger-cartesian ceiling.After the route-counter runner guard, the targeted benchmark-tool/catalog/CUDA-artifact/facade/wrapper/staged pytest set reports 92 passed.
Follow-up property-filtered compact+wide calibration:
Added optional real-cohort runner filters by measured dataset properties:
--max-train-samples,--max-features,--max-train-feature-product. Rows outside budget are written asstatus=skippedwitherror_message=property_filter:...; this is benchmark resource control, not model routing or selection by dataset identity.Ran a one-GPU incremental campaign with
plan=compact_wide,cv=4,max_chains=24,top_k=16,refit_top_k=8, Ridge+PLS heads,cuda_pls_min_device_features=1,cuda_pls_parallel_folds=True,backend_min_cuda_product=1, andmax_features=1200.Output
/tmp/n4m_aom_staged_real_cohort_10_cuda_compact_wide_p1200.csv:8OK rows and2skipped rows, both skipped only becausen_features>1200; OK rows used ABI1.21.0andbuild/cuda-on/cpp/src/libn4m.so.Route counters on OK rows: screen PLS moment CV fits
1152, screen host fits0, screen CUDA device fits1152; refit PLS moment CV fits160, refit host fits0, refit CUDA device fits160.Timing on OK rows: total fit time
45.06 s, median5.31 s.Versus the previous compact run on the 8 common OK rows, compact+wide improved
3/8, was identical on4/8, and degraded slightly on1/8; median ratio versus compact was1.0.Offline oracle comparison written to
/tmp/n4m_aom_staged_oracle_comparison_10_cuda_compact_wide_p1200.csvand/tmp/n4m_aom_staged_oracle_comparison_10_cuda_compact_wide_p1200.md:AOM-PLS oracle: paired
7, target wins0, median ratio1.17183;AOM-Ridge oracle: paired
8, target wins0, median ratio1.26483;TabPFN oracle: paired
8, target wins1, median ratio1.33059.
Interpretation: the extra wide stage helps some datasets at small budget but still does not close the robust AOM oracle gap. The new property filters make subsequent budget sweeps cleaner without dataset-name routing.
After the property-filter guard, the targeted benchmark-tool/catalog/CUDA-artifact/facade/wrapper/staged pytest set reports 93 passed.
Follow-up custom staged JSON calibration:
Added
--stages-jsonand--stages-json-filetorun_aom_staged_real_cohort.py. The value must be a non-empty JSON list of profile strings or stage objects accepted byn4m.aom_staged_chain_campaign. The parsed list is passed through asstages=..., and output rows record a compactstages_jsonfield for audit/replay.Ran a mini one-GPU proof campaign with a custom two-stage config: compact plus a small Savitzky-Golay lab stage (
savgol_smooth,savgol_derivative, andsavgol_smooth -> finite_differencetemplates),max_features=1200, and CUDA PLS controls forced on.Output
/tmp/n4m_aom_staged_real_cohort_3_cuda_custom_stages.csv:2OK rows and1skipped row (n_features>1200),plan=custom, non-emptystages_json, ABI1.21.0.Route counters on OK rows: screen PLS moment CV fits
128, screen host fits0, screen CUDA device fits128; refit PLS moment CV fits32, refit host fits0, refit CUDA device fits32.Offline oracle comparison written to
/tmp/n4m_aom_staged_oracle_comparison_3_cuda_custom_stages.csvand/tmp/n4m_aom_staged_oracle_comparison_3_cuda_custom_stages.md; target wins were0vs AOM-PLS, AOM-Ridge and TabPFN on the two paired rows.Interpretation: this is a functionality proof for JSON-driven preprocessing family campaigns. It enables the incremental preprocessing experiments without dataset-name routing, but it is not evidence of a scoring improvement.
After the custom-stages JSON guard, the targeted benchmark-tool/catalog/CUDA-artifact/facade/wrapper/staged pytest set reports 94 passed.
Follow-up staged variant comparison helper:
Added
benchmarks/cross_binding/compare_aom_staged_variants.py, an offline CSV-only audit tool for comparing compact/wide/custom staged campaign outputs against each other before running the external AOM/TabPFN oracle comparator.The tool groups by campaign configuration columns only (
plan,stages_json, heads, budget, property filters and CUDA knobs). Dataset keys are used only for paired evaluation against a chosen baseline, never as routing or production-selection inputs.The CSV/markdown summary reports OK/skipped/error counts, median score/timing, total fit time, screen/refit PLS moment route totals, and paired win/loss/tie plus score ratios versus either
--baselineCSVs or a--baseline-labelfrom the supplied inputs.Added tests covering config grouping, skipped-row handling, route-counter totals, baseline-label pairing and the absence of dataset/source identity fields from the config key.
After this helper, the targeted benchmark-tool/catalog/CUDA-artifact/facade/wrapper/staged pytest set reports 97 passed.
Follow-up direct PLS reusable head:
Added
n4m.pls/n4m.python.pls, a direct reusable moment PLS surface backed byn4m_sweep_runwithheads=("pls",). It supports a fixedn_componentsor an explicit train-CVpls_componentsgrid and preserves the existing CUDA PLS controls (cuda_pls_parallel_folds,cuda_pls_min_device_features,cuda_pls_many_batched).Added
NativePLSRegressorton4m.sklearn, top-leveln4m, andn4m.moment. The wrapper predicts from replayable input-space coefficients plus intercept and reports the PLS moment route counters from the sweep result.Wired the existing
models.pls.pls_fit_simplecatalog entry ton4m.python.plsand documented the directn4musage indocs/methods/pls.md.Regenerated
direct_moment_heads_timing_cuda_smoke.csv; it now covers Ridge, PLS, PCR, CPPLS, continuum regression and ECR across three shapes with both function and sklearn replay rows onbuild/cuda-on/cpp/src/libn4m.so.Validation after this addition:
targeted benchmark-tool/catalog/CUDA-artifact/facade/wrapper/staged pytest: 98 passed;
catalog/scripts/validate.py --strict-abi: PASS;catalog/scripts/validate.py --check-references: PASS;catalog/scripts/split_legacy_methods.py --check: PASS.
Follow-up direct-head timing catalog alignment:
Wired the six reusable direct moment heads (
models.regularized.ridge,models.pls.pls_fit_simple,models.pls.pcr,models.pls.cppls,models.regularized.continuum_regression,models.specialized.ecr) to the shared timing registry entrybenchmarks/cross_binding/bench_direct_moment_heads_timing.py.Added a catalog guard proving those six per-method YAML files keep that registry entry and that the timing script exists.
Validation after this alignment:
targeted benchmark-tool/catalog/CUDA-artifact/facade/wrapper/staged pytest: 99 passed;
catalog/scripts/validate.py --strict-abi: PASS;catalog/scripts/validate.py --check-references: PASS;catalog/scripts/split_legacy_methods.py --check: PASS.
Follow-up direct PLS CUDA route proof:
Extended
bench_direct_moment_heads_timing.pywith the PLS CUDA controls used by the sweep/AOM smokes:--cuda-pls-min-device-features,--cuda-pls-parallel-foldsand--cuda-pls-many-batched.The direct-head timing CSV now records PLS route counters for both
native_functionandsklearn_fit_predictrows:n_pls_moment_cv_fits, host/device CV fits, CUDA fold batches/jobs and final route counters.Regenerated
direct_moment_heads_timing_cuda_smoke.csvon one GPU with--cuda-pls-min-device-features 1 --cuda-pls-parallel-folds. PLS rows now reportn_pls_moment_cv_fits=4, host CV fits0, CUDA device CV fits4and CUDA fold jobs4for each of the three shapes and both backends.Strengthened the CUDA artifact guard so direct PLS rows must prove device-CV routing, not only CUDA library loading and replay.
Validation after this route-proof update:
targeted benchmark-tool/catalog/CUDA-artifact/facade/wrapper/staged pytest: 99 passed;
catalog/scripts/validate.py --strict-abi: PASS;catalog/scripts/validate.py --check-references: PASS;catalog/scripts/split_legacy_methods.py --check: PASS.
Follow-up moment-stack PLS base reuse:
Switched the
"pls"base insideNativeMomentStackRegressorfrom the genericNativeMomentSweepRegressorto the new directNativePLSRegressor. It still usesn4m_sweep_rununderneath, but the stack now reuses the same individual PLS method exposed to end users.Strengthened wrapper tests so stack diagnostics must report
estimator="NativePLSRegressor"andmethod="pls"for OOF and final PLS base rows.Regenerated
moment_stack_timing_cuda_smoke.csvafter the swap. The PLS-only stack still reports CUDA route counters unchanged: OOF PLS moment CV fits16, host0, CUDA device16; final PLS CV fits4, host0, CUDA device4.Validation after this reuse alignment:
bindings/python/tests/test_moment_model_wrappers.py: 53 passed;bindings/python/tests/test_aom_moment_cuda_smoke_artifacts.py: 13 passed;targeted benchmark-tool/catalog/CUDA-artifact/facade/wrapper/staged pytest: 99 passed.
Follow-up AOM Ridge superblock strict-moment reference:
Added
n4m.aom_ridge_superblock, a Python-backed donor-style AOM Ridge superblock constrained to the strict-linear single-operator AOM bank. It builds concatenated operator views throughn4m.aom_preprocess, scores fixed or CV-selected Ridge alphas with fold-local centering/block scaling through the nativen4m.ridgebinding, and folds the final superblock coefficients back into original-inputinput_coefficientsplusintercept.Added
NativeAOMRidgeSuperblockRegressorand exported it through top-leveln4m,n4m.sklearn,n4m.aomandn4m.moment.Promoted the method to catalog entry
aom_pop.ridge_superblock, addeddocs/methods/aom_ridge_superblock.md, wiredbenchmarks/cross_binding/bench_aom_ridge_superblock_timing.py, and generatedaom_ridge_superblock_timing_cuda_smoke.csvonbuild/cuda-onwith function + sklearn rows andridge_backend=native.Kept donor
branch_global, MKL/kernel, row-reference-dependent preprocessing and nonlinear AOM Ridge modes out of scope for the moment contract.Validation after this slice:
py_compile on touched Python modules/tests: PASS;
focused Ridge-global tests plus
test_aom_moment_facade.py: 21 passed;targeted benchmark/catalog/CUDA-artifact/facade/wrapper/staged pytest: 118 passed;
catalog/scripts/validate.py --strict-abi: PASS, 203 methods and 701/701 exportedn4m_*symbols covered;catalog/scripts/validate.py --check-references: PASS, 203/203 production methods covered;catalog/scripts/split_legacy_methods.py --check: PASS, 203 per-method files up to date.
Validation after this slice:
py_compile on touched Python modules/tests: PASS;
focused Ridge-superblock tests plus
test_aom_moment_facade.py: 15 passed;targeted benchmark/catalog/CUDA-artifact/facade/wrapper/staged pytest: 113 passed;
catalog/scripts/validate.py --strict-abi: PASS, 202 methods and 701/701 exportedn4m_*symbols covered;catalog/scripts/validate.py --check-references: PASS, 202/202 production methods covered;catalog/scripts/split_legacy_methods.py --check: PASS, 202 per-method files up to date;git diff --check: exit 0, only known CRLF warnings on existing CSV artifacts.
Follow-up AOM Ridge global strict-moment selector:
Added
n4m.aom_ridge_global, a donor-style strict AOM Ridge global selector constrained to single strict-linear operators. It wraps the nativeaom_chain_sweep_runRidge-only path, so one operator and one Ridge alpha are selected by train CV and the final model reuses foldedinput_coefficientsplusintercept.Added
NativeAOMRidgeGlobalRegressorand exported it through top-leveln4m,n4m.sklearn,n4m.aomandn4m.moment.Promoted the method to catalog entry
aom_pop.ridge_global, addeddocs/methods/aom_ridge_global.md, wiredbenchmarks/cross_binding/bench_aom_ridge_global_timing.py, and generatedaom_ridge_global_timing_cuda_smoke.csvonbuild/cuda-onwith function + sklearn rows andridge_backend=native_aom_chain_sweep.Kept donor
branch_global, MKL/kernel, row-reference-dependent preprocessing and nonlinear AOM Ridge modes out of scope for the moment contract.
Follow-up AOM Ridge active-superblock strict-moment reference:
Added
n4m.aom_ridge_active_superblock, a Python-backed donor-style AOM Ridge active-superblock constrained to strict-linear single-operator AOM views.The active score is defined on native
n4m.aom_preprocessoutputs as||scale_b * Z_b.T @ y_c||_F^2by default. This avoids pretending to reproduce donor-privateop.apply_covsemantics while keeping the production model strictly linear and replayable.Alpha CV screens active operators inside each training fold only; the final model screens once on full calibration rows and fits Ridge through the native
n4m.ridgebinding. The final active superblock is folded back to original-inputinput_coefficientsplusintercept.Added
NativeAOMRidgeActiveSuperblockRegressorand exported it through top-leveln4m,n4m.sklearn,n4m.aomandn4m.moment.Promoted the method to catalog entry
aom_pop.ridge_active_superblock, addeddocs/methods/aom_ridge_active_superblock.md, wiredbenchmarks/cross_binding/bench_aom_ridge_active_superblock_timing.py, and generatedaom_ridge_active_superblock_timing_cuda_smoke.csvonbuild/cuda-onwith function + sklearn rows andridge_backend=native.Kept donor
branch_global, MKL/kernel, row-reference-dependent preprocessing and nonlinear AOM Ridge modes out of scope for the moment contract.Validation during implementation:
py_compile on touched Python modules: PASS.
Focused Ridge active/global/superblock wrapper tests:
6 passed.Targeted benchmark/catalog/CUDA-artifact guards for active/global/superblock:
11 passed.Combined benchmark/catalog/CUDA-artifact/facade/wrapper/staged pytest set:
123 passed.catalog/scripts/validate.py --strict-abi: PASS, 204 methods and 701/701 exportedn4m_*symbols covered.catalog/scripts/validate.py --check-references: PASS, 204/204 production methods covered.catalog/scripts/split_legacy_methods.py --check: PASS, 204 per-method files up to date.
Follow-up AOM-PLS superblock strict-moment reference:
Added
n4m.aom_pls_superblock, a Python-backed donor-style AOM-PLS superblock constrained to strict-linear single-operator AOM views.Added
NativeAOMPLSSuperblockRegressorand exported it through top-leveln4m,n4m.sklearn,n4m.aomandn4m.moment.The method concatenates strict operator outputs from
n4m.aom_preprocess, selects the PLS component count by train CV, fits through the nativen4m.plsbinding, and folds final superblock coefficients back to original-inputinput_coefficientsplusintercept.Promoted the method to catalog entry
aom_pop.aom_pls_superblock, addeddocs/methods/aom_pls_superblock.md, wiredbenchmarks/cross_binding/bench_aom_pls_superblock_timing.py, and generatedaom_pls_superblock_timing_cuda_smoke.csvonbuild/cuda-onwith functionsklearn rows,
pls_backend=nativeand CUDA device PLS route counters when--cuda-pls-min-device-features 1is used.
Kept donor
soft, active PLS pruning, MKL/kernel, row-reference-dependent preprocessing, nonlinear lifts and dataset/source routing out of scope for this slice.
Follow-up AOM Ridge MKL-light superblock strict-moment reference:
Added
n4m.aom_ridge_mkl_superblock, a Python-backed donor-style AOM Ridge MKL-light weighted-superblock constrained to strict-linear single-operator AOM views.The method learns non-negative KTA block weights on training rows only inside every alpha-CV fold, refits weights on the full calibration rows, fits native Ridge on the equivalent weighted superblock, and folds final coefficients back to original-input
input_coefficientsplusintercept.Added
NativeAOMRidgeMKLSuperblockRegressorand exported it through top-leveln4m,n4m.sklearn,n4m.aomandn4m.moment.Promoted the method to catalog entry
aom_pop.ridge_mkl_superblock, addeddocs/methods/aom_ridge_mkl_superblock.md, wiredbenchmarks/cross_binding/bench_aom_ridge_mkl_superblock_timing.py, and generatedaom_ridge_mkl_superblock_timing_cuda_smoke.csvonbuild/cuda-onwith function + sklearn rows,mkl_mode=alignment,ridge_backend=nativeand replay error at numerical-noise level.Kept donor branch/global reference-dependent preprocessing, local/SNV/MSC branches, row-reference-dependent preprocessing, nonlinear kernels, nonlinear lifts and dataset/source routing out of scope for this slice.
Validation after this slice:
py_compile on touched Python modules/tests/benchmark: PASS.
Focused AOM Ridge MKL-light superblock wrapper/function tests:
2 passed.Targeted benchmark/catalog/CUDA-artifact guards for AOM Ridge MKL-light superblock:
12 passed.Combined benchmark/catalog/CUDA-artifact/facade/wrapper/staged pytest set:
138 passed.catalog/scripts/validate.py --strict-abi: PASS, 207 methods and 701/701 exportedn4m_*symbols covered.catalog/scripts/validate.py --check-references: PASS, 207/207 production methods covered.catalog/scripts/split_legacy_methods.py --check: PASS, 207 per-method files up to date.
Validation after this slice:
py_compile on touched Python modules/tests/benchmark: PASS.
Focused AOM Ridge-PLS superblock wrapper/function tests:
2 passed.Targeted benchmark/catalog/CUDA-artifact guards for AOM Ridge-PLS superblock:
11 passed.Combined benchmark/catalog/CUDA-artifact/facade/wrapper/staged pytest set:
133 passed.catalog/scripts/validate.py --strict-abi: PASS, 206 methods and 701/701 exportedn4m_*symbols covered.catalog/scripts/validate.py --check-references: PASS, 206/206 production methods covered.catalog/scripts/split_legacy_methods.py --check: PASS, 206 per-method files up to date.
Validation during implementation:
py_compile on touched Python modules: PASS.
Focused AOM-PLS superblock wrapper/function tests:
2 passed.Targeted benchmark/catalog/CUDA-artifact guards for AOM-PLS superblock:
8 passed.Combined benchmark/catalog/CUDA-artifact/facade/wrapper/staged pytest set:
128 passed.catalog/scripts/validate.py --strict-abi: PASS, 205 methods and 701/701 exportedn4m_*symbols covered.catalog/scripts/validate.py --check-references: PASS, 205/205 production methods covered.catalog/scripts/split_legacy_methods.py --check: PASS, 205 per-method files up to date.
Follow-up AOM Ridge-PLS superblock strict-moment reference:
Added
n4m.aom_ridge_pls_superblock, a Python-backed donor-style AOM Ridge-PLS superblock constrained to strict-linear single-operator AOM views.The method concatenates strict operator outputs from
n4m.aom_preprocess, selects the PLS component count and Ridge-PLS lambda by train CV over the cartesian grid, fits through the nativen4m.ridge_plsbinding, and folds final superblock coefficients back to original-inputinput_coefficientsplusintercept.Added
NativeAOMRidgePLSSuperblockRegressorand exported it through top-leveln4m,n4m.sklearn,n4m.aomandn4m.moment.Promoted the method to catalog entry
aom_pop.aom_ridge_pls_superblock, addeddocs/methods/aom_ridge_pls_superblock.md, wiredbenchmarks/cross_binding/bench_aom_ridge_pls_superblock_timing.py, and generatedaom_ridge_pls_superblock_timing_cuda_smoke.csvonbuild/cuda-onwith function + sklearn rows andridge_pls_backend=native.Kept donor
soft, active PLS pruning, MKL/kernel, row-reference-dependent preprocessing, nonlinear lifts and dataset/source routing out of scope for this slice.
Follow-up rank-audit mismatch summary (2026-06-06):
Added a post-hoc mismatch-pattern section to
benchmarks/cross_binding/summarize_aom_rank_audit.py.The generated Markdown now summarizes cases where the train-CV winner is not the offline test oracle (
test_rank_delta > 0ororacle_gap_ratio > 0).Groupings:
selected_head -> oracle_headselected_chain -> oracle_chain
Reported aggregate stats: count, mean/median oracle-gap ratio, median selected test rank and median rank delta. The CSV output is unchanged.
Production invariant remains explicit: this is offline audit only, never test-set selection, and never routing by dataset name, source or id.
Regenerated rank-audit CSV/Markdown for compact-wide audit5, compact-wide audit10 and ECOSIS refit40.
The compact-wide audit10 mismatch summary exposes the main failure mode: ECOSIS selected
ridge:0.1 savgol_smooth(7,2)while the offline oracle waspls:1 detrend_poly(2), gap ratio0.5773.The ECOSIS refit40 stress test confirms this is not simply a retention-budget issue: selected
ridge:0.1 savgol_smooth(7,2)had test rank41, while the offline oracle wasridge:10 finite_difference(1), gap ratio2.9123.Validation:
py_compile on the summarizer and benchmark-tool tests: PASS.
test_aom_benchmark_tools.py:19 passedwithPYTHONPATH=bindings/python/srcandN4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so.git diff --check: PASS apart from known CRLF warnings on old CSV artifacts.
Follow-up read-only engine audit (2026-06-06):
Delegated a read-only audit of the remaining engine/performance gap after the method/facade/catalog surfaces were checked.
Conclusion: no obvious missing Python-facing method remains; the open gaps are still the documented C++/CUDA engine gaps.
Recommended smallest bounded engine slice:
share/extract the existing symmetric eigensolver currently local to
cpp/src/core/model.cpp;use it in
cpp/src/core/sweep.cppto score Ridge moment CV by one eigendecomposition per fold plus a lambda-path scan, instead of callingfit_ridge_from_moments()/solve_square_qr()independently for every lambda;raise any Ridge moment feature cap only after parity and timing prove the new path.
Review correction recorded: the current Ridge moment implementation uses a generic QR solve, not a Cholesky solve, so this should be framed as “QR-per-lambda to eigensolver-per-fold” rather than “Cholesky replacement”.
This was not implemented in the current pass because it is a real numerical helper extraction plus parity/timing task, not a trivial wiring patch.
Follow-up Ridge moment eigen-path implementation (2026-06-06):
Implemented the first bounded engine optimization for Ridge moment scoring. The shared C SVD helper now exposes internal
n4m_symmetric_eigh()for a read-only full symmetric eigendecomposition without adding public ABI.sweep.cppgained aRidgeMomentEigenPathroute that reuses one eigendecomposition per fold for multi-lambda Ridge moment scans. It mirrors the existing direct moment design/centering/scaling, preprojectsX'Yinto the eigenbasis, and reconstructs coefficients per lambda.The optimized route is guarded to
n_lambdas > 1and falls back to the existing QR-per-lambdafit_ridge_from_moments()path whenever the eigen-path cannot be prepared or used. Mono-lambda behavior remains the previous direct solve.Batch score-only AOM Ridge now groups internal work by
(chain, fold)so all lambdas share one local eigen-path; it does not persist all eigenvectors across chains. Public logical counters stay unchanged.Added NumPy parity coverage for multi-lambda Ridge moment candidate RMSEs plus direct-path versus eigen-path score equality for the same lambda, and reran the wrapper/benchmark-tool tests:
test_moment_model_wrappers.py:73 passed.test_aom_benchmark_tools.py:19 passed.dev
n4m_cbuild: PASS.ctest -R sweep: no C++ tests found in the current dev build.git diff --check: PASS except known old CSV CRLF warnings.
Synthetic timing smoke on
n=140, p=80, cv=5Ridge score-only:1/4/16/64lambdas ran in about0.0026 / 0.0064 / 0.0221 / 0.1417seconds.Read-only Claude review found no blocking issues; the suggested direct-versus-eigen same-lambda test was added.
Follow-up moment sweep timing smoke Ridge coverage (2026-06-06):
Extended
benchmarks/cross_binding/bench_moment_sweep_timing.pywith a tall96x48smoke cell. The previous default CUDA smoke shapes covered wide dual/materialized Ridge but did not exercise the exact Ridge moment route.Regenerated
benchmarks/cross_binding/moment_sweep_timing_cuda_smoke.csvagainstbuild/cuda-on/cpp/src/libn4m.so; the tall Ridge rows now reportn_ridge_moment_candidates=5andn_ridge_moment_cv_fits=25.Strengthened
bindings/python/tests/test_aom_moment_cuda_smoke_artifacts.pyso the committed artifact must include a Ridge moment row with zero dual/materialized Ridge CV counters, while preserving the existing exact PLS CUDA-device checks.Validation:
CUDA
n4m_cbuild: PASS.py_compile for the timing script and artifact test: PASS.
test_aom_moment_cuda_smoke_artifacts.py:21 passed.
Follow-up catalog/method-surface conformance (2026-06-06):
Re-ran the catalog and Python-binding conformance checks after the latest AOM/moment additions.
Results:
catalog/scripts/validate.py --strict-abi: PASS, 208 methods and 701/701 exportedn4m_*symbols covered.catalog/scripts/validate.py --check-references: PASS, 208/208 production methods covered.catalog/scripts/split_legacy_methods.py --check: PASS, 208 per-method files up to date.test_catalog_python_bindings.py:10 passed.
This proves the expanded reusable method surface remains catalog-consistent; the remaining open work is still engine/performance and broader oracle campaigns.
Follow-up oracle comparison table (2026-06-06):
Extended
benchmarks/cross_binding/compare_aom_staged_to_oracles.pyso Markdown summaries include a per-target-dataset table separating AOM-PLS oracle, AOM-Ridge oracle and TabPFN oracle scores and ratios.Regenerated the compact-wide audit10 oracle comparison Markdown/CSV and the aggregate
aom_staged_oracle_comparison.md/.csv.The compact-wide audit10 target remains the train-CV-selected production candidate. The offline comparison table is audit-only and reports:
AOM-PLS oracle: paired 7, median ratio
1.03079.AOM-Ridge oracle: paired 8, median ratio
1.08068.TabPFN oracle: paired 8, median ratio
0.978527.
Added benchmark-tool test coverage for the new table output and reran
test_aom_benchmark_tools.py:19 passed.
Follow-up methods index count (2026-06-06):
Updated
docs/methods/index.mdso the visible total catalogued native method count is208, matching the current per-method catalog files.Added a catalog Python-binding test that parses the displayed count and compares it to
catalog/methods/*.yaml, preventing stale end-user counts after future method additions.Validation:
test_catalog_python_bindings.pynow has11 passed.
Follow-up facade inventory conformance (2026-06-06):
Added a public-surface invariant for
n4m.aom.available_methods()andn4m.moment.available_methods(): every advertised row must declare typedcpuandcudacapability flags and at least one available execution capability.The broader facade suite already verifies object identity with
n4m.python/n4m.sklearn, top-level package re-export, catalog and docs resolution, docs-index links, Python binding-role coherence, relevant native AOM/moment catalog coverage, bounded preset wrappers and absence of dataset/source identity routing options.Validation:
PYTHONPATH=bindings/python/src N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so /home/delete/.venv/bin/python -m pytest bindings/python/tests/test_aom_moment_facade.py -q:20 passed.
Follow-up Ridge moment eigen-path in general sweep (2026-06-06):
Extended the Ridge moment eigen-path beyond the dedicated score-only Ridge scorer into the general
run_moment_sweep()Ridge moment loop.When a tall Ridge moment fold has more than one lambda, the general sweep now builds one fold-local eigen-path and scans lambdas through it. Mono-lambda and fallback cases still call the previous direct QR solve, preserving numerical fallback behavior and public logical counters.
Added
test_native_sweep_run_ridge_moment_score_only_matches_full_scores, covering tall multi-lambda Ridge moment scores, moment/dual counters, and score-only output suppression.Synthetic score-only timing on
n=140, p=80, cv=5:4 lambdas: multi-lambda
0.058237s, repeated mono-lambda0.143395s, max score diff8.3e-15.16 lambdas: multi-lambda
0.026655s, repeated mono-lambda0.306316s, max score diff8.3e-15.64 lambdas: multi-lambda
0.093162s, repeated mono-lambda1.277440s, max score diff1.1e-14.
Validation:
dev and CUDA
n4m_cbuilds: PASS.test_moment_model_wrappers.py:74 passed.CUDA-lib focused Ridge moment tests:
2 passed, 72 deselected.
Follow-up moment sweep CUDA smoke after general Ridge eigen-path (2026-06-06):
Regenerated
benchmarks/cross_binding/moment_sweep_timing_cuda_smoke.csvwithCUDA_VISIBLE_DEVICES=0,N4M_LIB_PATH=build/cuda-on/cpp/src/libn4m.so,--repeats 1 --native-only --cuda-pls-min-device-features 1 --cuda-pls-parallel-folds.Tightened the artifact test so the tall Ridge moment smoke cell (
96x48) proves fullrun_moment_sweep()andscore_onlyagree on selected lambda and CV RMSE while preserving final-fit behavior (1final Ridge moment fit for full,0for score-only).Regenerated tall Ridge rows report selected lambda
0.001, full RMSE0.08037705391174381, score-only RMSE0.08037705391174604, andn_ridge_moment_cv_fits=25on both rows.Validation:
py_compile for timing script and touched tests: PASS.
test_aom_moment_cuda_smoke_artifacts.py:21 passed.test_aom_moment_facade.py+test_moment_model_wrappers.py:94 passed.
Follow-up catalog/surface validation after timing smoke (2026-06-06):
Re-ran catalog validation after the general sweep eigen-path and smoke artifact updates.
Evidence:
catalog/scripts/validate.py --strict-abi: PASS, 208 methods and 701/701 exportedn4m_*symbols covered.catalog/scripts/validate.py --check-references: PASS, 208/208 production methods covered by references.catalog/scripts/split_legacy_methods.py --check: PASS, 208 per-method files up to date.test_catalog_python_bindings.py:11 passed./home/delete/.venv/bin/ctest --test-dir build/dev-release -R sweep --output-on-failure: no C++ tests found in this build.
Follow-up Ridge moment eigen-path telemetry (2026-06-06):
Added explicit route telemetry for Ridge moment lambda-path scoring:
n_ridge_moment_eigen_path_preparations,n_ridge_moment_eigen_path_cv_fitsandn_ridge_moment_direct_cv_fits.The logical counter
n_ridge_moment_cv_fitsremains unchanged; the new counters explain how those fits executed, which makes broad preprocessing screens auditable without inferring route behavior from timing.Propagated the telemetry through native sweep/AOM results, C API result serialization, Python scalar extraction, sklearn diagnostics, refit/staged campaign summaries and the committed moment-sweep CUDA smoke artifact.
The regenerated tall
96x48Ridge smoke rows now show:full sweep:
5eigen-path preparations,25eigen-path CV fits,0direct CV fits.score-only:
5eigen-path preparations,25eigen-path CV fits,0direct CV fits.
Validation:
dev and CUDA
n4m_cbuilds: PASS.focused Ridge telemetry tests:
3 passed, 71 deselected.test_moment_model_wrappers.py:74 passed.artifact/facade/wrapper suite:
115 passed.test_catalog_python_bindings.py:11 passed.catalog strict ABI/reference/split checks: PASS.
git diff --check: PASS apart from known CRLF warnings on old CSV artifacts.
Follow-up Ridge telemetry in benchmark runners (2026-06-06):
Propagated the Ridge moment eigen-path/direct telemetry to the benchmark runners that are used to audit preprocessing selection and timing:
run_aom_staged_real_cohort.py,bench_aom_screen_refit_scaling.py,bench_aom_staged_chain_campaign_timing.py,bench_aom_sweep_timing.pyandbench_aom_ridge_global_timing.py.The staged real-cohort CSV and diagnostics JSON now carry
n_ridge_moment_eigen_path_preparations,n_ridge_moment_eigen_path_cv_fitsandn_ridge_moment_direct_cv_fits, so campaign artifacts can prove whether Ridge moment CV work used the fast eigen-path or direct fallback.Regenerated the CUDA timing smokes affected by new columns:
aom_sweep_timing_cuda_smoke.csv,aom_ridge_global_timing_cuda_smoke.csvandaom_staged_chain_campaign_timing_cuda_smoke.csv.Added benchmark/staged tests for row/diagnostics/stage aggregation of the new counters. Staged report tests now cover the accounting invariant:
n_ridge_moment_eigen_path_cv_fits + n_ridge_moment_direct_cv_fits == n_ridge_moment_cv_fits.Validation:
py_compile on touched benchmark scripts/tests: PASS.
test_aom_benchmark_tools.py:19 passed.test_aom_staged_campaign.py:16 passed.test_aom_moment_cuda_smoke_artifacts.py:21 passed.combined benchmark/staged/artifact suite:
56 passed.facade/wrapper/catalog Python suite:
105 passed.catalog strict ABI/reference/split checks: PASS.
Follow-up real-cohort Ridge telemetry guard (2026-06-06):
Added
validate_ridge_moment_route_telemetry(report)inbenchmarks/cross_binding/run_aom_staged_real_cohort.py.The runner now validates Ridge moment route counters immediately after
n4m.aom_staged_chain_campaign(...)returns and before any CSV or diagnostics JSON is persisted.When the total/eigen/direct CV counters are all present, the guard requires:
n_ridge_moment_cv_fits,n_ridge_moment_eigen_path_cv_fitsandn_ridge_moment_direct_cv_fitsto be non-negative integer-like values.n_ridge_moment_eigen_path_cv_fits + n_ridge_moment_direct_cv_fits == n_ridge_moment_cv_fits.n_ridge_moment_eigen_path_preparationsto be non-negative/integer-like when present.
This is a campaign-integrity guard only: production selection remains exact train-CV-only and held-out/test ranking remains offline audit data.
Validation:
PYTHONPATH=bindings/python/src /home/delete/.venv/bin/python -m py_compile benchmarks/cross_binding/run_aom_staged_real_cohort.py bindings/python/tests/test_aom_benchmark_tools.pyPYTHONPATH=bindings/python/src N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so /home/delete/.venv/bin/python -m pytest bindings/python/tests/test_aom_benchmark_tools.py -q:20 passed.One-row CUDA real-cohort smoke with
CUDA_VISIBLE_DEVICES=0andN4M_LIB_PATH=build/cuda-on/cpp/src/libn4m.socompletedBEEFMARBLING/Beef_Marbling_RandomSplitsuccessfully. The output row and diagnostics JSON both reported Ridge counters12/6/6total/eigen/direct,selection_uses_test_set=False, and PLS screen device/host CV fits6/0.
Follow-up moment facade wrapper-target conformance (2026-06-06):
Added
aom_chain_screen_refit_campaignto then4m.momentfacade exports. The underlying native binding and top-leveln4mexport already existed, but three advertised moment screen/refit sklearn wrappers declaredwrapper_of="aom_chain_screen_refit_campaign"without the same target being reachable fromn4m.moment.Added a generic facade test ensuring every inventory
wrapper_oftarget is exported on the same facade, top-leveln4m, andn4m.python, and that all three are the identical native object.This is a public-surface conformance fix for reusable wrappers; it does not change fitting behavior, add hors-moment methods, or alter production selection policy.
Validation:
PYTHONPATH=bindings/python/src /home/delete/.venv/bin/python -m py_compile bindings/python/src/n4m/moment/__init__.py bindings/python/tests/test_aom_moment_facade.pyPYTHONPATH=bindings/python/src N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so /home/delete/.venv/bin/python -m pytest bindings/python/tests/test_aom_moment_facade.py -q:22 passed.PYTHONPATH=bindings/python/src N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so /home/delete/.venv/bin/python -m pytest bindings/python/tests/test_aom_moment_facade.py bindings/python/tests/test_moment_model_wrappers.py bindings/python/tests/test_catalog_python_bindings.py -q:107 passed.Catalog split/reference/strict-ABI checks: PASS.
Follow-up facade timing-benchmark coverage guard (2026-06-06):
Added a facade-level invariant that every catalogued method advertised by
n4m.aom.available_methods()orn4m.moment.available_methods()has a non-nullbench.registry_entry, that the entry lives underbenchmarks/cross_binding/, and that the script exists.A quick audit found 30 distinct catalog IDs on the public AOM/moment facades and no missing timing benchmark entries. The new test makes this a persistent end-user surface contract, complementing the broader catalog validator.
This is a conformance guard only; no method behavior, production selection, CUDA routing, or preprocessing family changed.
Validation:
PYTHONPATH=bindings/python/src /home/delete/.venv/bin/python -m py_compile bindings/python/tests/test_aom_moment_facade.pyPYTHONPATH=bindings/python/src N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so /home/delete/.venv/bin/python -m pytest bindings/python/tests/test_aom_moment_facade.py -q:24 passed.PYTHONPATH=bindings/python/src N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so /home/delete/.venv/bin/python -m pytest bindings/python/tests/test_aom_moment_facade.py bindings/python/tests/test_moment_model_wrappers.py bindings/python/tests/test_catalog_python_bindings.py -q:109 passed.Catalog split/reference/strict-ABI checks: PASS.
Follow-up AOM PLS superblock route-telemetry accounting (2026-06-06):
Fixed
n4m.aom_pls_superblockroute accounting. The method previously copied only the final selected PLS solve counters into the returned result, even though component selection had already executed one PLS solve per component/fold. This made timing and CUDA route diagnostics under-report the true work.The implementation now accumulates PLS route counters from every fold/component solve plus the final fit:
n_pls_moment_cv_fits,n_pls_moment_host_cv_fits,n_pls_moment_cuda_device_cv_fits,n_pls_moment_cuda_parallel_fold_batches,n_pls_moment_cuda_parallel_fold_jobs,n_pls_materialized_cv_fits,n_pls_moment_final_fits,n_pls_moment_host_final_fits,n_pls_moment_cuda_device_final_fitsandn_pls_materialized_final_fits.Extended
benchmarks/cross_binding/bench_aom_pls_superblock_timing.pyso the committed timing artifact also exposes CUDA parallel-fold jobs and final-fit host/device counters.Regenerated
benchmarks/cross_binding/aom_pls_superblock_timing_cuda_smoke.csvwithCUDA_VISIBLE_DEVICES=0andN4M_LIB_PATH=build/cuda-on/cpp/src/libn4m.so. The smoke profile now reports18/0/18PLS CV total/host/device jobs and9/0/9PLS final total/host/device fits for both function and sklearn rows, matching two component values over four folds plus one final fit.This is telemetry/accounting only. Selection, predictions, folded coefficients and the strict-linear moment contract are unchanged.
Validation:
PYTHONPATH=bindings/python/src /home/delete/.venv/bin/python -m py_compile bindings/python/src/n4m/python.py benchmarks/cross_binding/bench_aom_pls_superblock_timing.py bindings/python/tests/test_aom_moment_cuda_smoke_artifacts.py bindings/python/tests/test_moment_model_wrappers.pyPYTHONPATH=bindings/python/src N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so /home/delete/.venv/bin/python -m pytest bindings/python/tests/test_moment_model_wrappers.py -q -k 'aom_pls_superblock':2 passed, 72 deselected.PYTHONPATH=bindings/python/src N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so /home/delete/.venv/bin/python -m pytest bindings/python/tests/test_aom_moment_cuda_smoke_artifacts.py -q -k 'diversity_cuda_smoke_artifacts_cover_native_and_sklearn_replay':9 passed, 12 deselected.PYTHONPATH=bindings/python/src N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so /home/delete/.venv/bin/python -m pytest bindings/python/tests/test_moment_model_wrappers.py bindings/python/tests/test_aom_moment_cuda_smoke_artifacts.py -q:95 passed.Catalog split/reference/strict-ABI checks: PASS.
Follow-up AOM Ridge-PLS solve-count telemetry (2026-06-06):
Added deterministic solve-count telemetry to the Ridge-PLS AOM methods:
n_ridge_pls_fit_callsforn4m.aom_ridge_pls_superblockandn4m.aom_chain_ridge_pls.The count is
n_candidates * cv + 1, representing every native Ridge-PLS solve run during train-CV candidate scoring plus the final selected fit. This makes the CPU cost of these reusable Ridge-PLS methods explicit in the same timing artifacts that already cover replay and CUDA-build compatibility.Exposed the field through the sklearn diagnostics for
NativeAOMRidgePLSSuperblockRegressorandNativeAOMChainRidgePLSRegressor.Extended the timing rows in
bench_aom_ridge_pls_superblock_timing.pyandbench_aom_chain_ridge_pls_timing.py, then regenerated the CUDA-build smoke CSVs with one visible GPU. The smoke rows now report:Ridge-PLS superblock:
n_ridge_pls_fit_calls=17(4candidates x4folds + final fit).Chain Ridge-PLS:
n_ridge_pls_fit_calls=33(8candidates x4folds + final fit).
This is telemetry/accounting only; predictions, selected candidates, train-CV selection and folded coefficients are unchanged.
Validation:
PYTHONPATH=bindings/python/src /home/delete/.venv/bin/python -m py_compile bindings/python/src/n4m/python.py bindings/python/src/n4m/sklearn/native_sweeps.py benchmarks/cross_binding/bench_aom_ridge_pls_superblock_timing.py benchmarks/cross_binding/bench_aom_chain_ridge_pls_timing.py bindings/python/tests/test_moment_model_wrappers.py bindings/python/tests/test_aom_moment_cuda_smoke_artifacts.pyPYTHONPATH=bindings/python/src N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so /home/delete/.venv/bin/python -m pytest bindings/python/tests/test_moment_model_wrappers.py -q -k 'aom_ridge_pls_superblock or aom_chain_ridge_pls':4 passed, 70 deselected.PYTHONPATH=bindings/python/src N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so /home/delete/.venv/bin/python -m pytest bindings/python/tests/test_aom_moment_cuda_smoke_artifacts.py -q -k 'diversity_cuda_smoke_artifacts_cover_native_and_sklearn_replay':9 passed, 12 deselected.PYTHONPATH=bindings/python/src N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so /home/delete/.venv/bin/python -m pytest bindings/python/tests/test_moment_model_wrappers.py bindings/python/tests/test_aom_moment_cuda_smoke_artifacts.py -q:95 passed.Catalog split/reference/strict-ABI checks: PASS.
2026-06-06 — aom_operator_pls_stack and aom_ridge_blender cost-route telemetry¶
Audit findings:
Neither
aom_ridge_blendernoraom_operator_pls_stackhad any native cost counters in the C++ result structs (AomRidgeBlenderResult,AomOperatorPlsStackResult). No instrumentation existed at the C++ layer.Both methods’ costs are fully deterministic from existing scalar fields already in the result:
Ridge blender:
n_candidates * cvCV Ridge fits +n_candidatesfinal Ridge fits (one per candidate for full-data prediction).PLS stack:
(n_specs + 1) * cv * n_operatorsPLS projector fits during CV (n_specs scoring calls + 1 OOF refit of selected spec) +n_operatorsfinal PLS fits;(n_specs + 1) * cvRidge head fits during CV +1final Ridge head fit.
C++ is not touched: all counters are Python-side derived from the native scalars.
Changes:
bindings/python/src/n4m/python.py:aom_ridge_blender: addsn_ridge_blender_cv_fits,n_ridge_blender_final_fits, andn_ridge_blender_fit_callsto returned dict.aom_operator_pls_stack: addsn_operator_pls_stack_fit_calls,n_operator_pls_stack_pls_fit_calls,n_operator_pls_stack_ridge_fit_calls, plusn_pls_stack_cv_fits,n_pls_stack_final_fits,n_ridge_stack_cv_fits,n_ridge_stack_final_fitsto returned dict.
bindings/python/src/n4m/sklearn/native_sweeps.py:NativeAOMRidgeBlenderRegressor.get_diagnostics(): exposes the Ridge blender CV/final/total fit counters.NativeAOMOperatorPLSStackRegressor.get_diagnostics(): exposes the operator stack CV/final/total fit counters.
benchmarks/cross_binding/bench_aom_ridge_blender_timing.py: adds Ridge blender CV/final/total fit counters to CSV rows.benchmarks/cross_binding/bench_aom_operator_pls_stack_timing.py: adds operator stack CV/final/total PLS/Ridge fit counters to CSV rows.benchmarks/cross_binding/README.md,docs/methods/aom_ridge_blender.md, anddocs/methods/aom_operator_pls_stack.md: document replay plus fit-count telemetry and avoid stale fixed timing/ABI values.CSV smoke files updated:
aom_ridge_blender_timing*.csv,aom_operator_pls_stack_timing*.csv.bindings/python/tests/test_moment_model_wrappers.py: asserts exact counter values in compact-contract tests (ridge blender: cv_fits=96, final_fits=24; pls stack: pls_cv=240, pls_final=12, ridge_cv=20, ridge_final=1).bindings/python/tests/test_aom_moment_cuda_smoke_artifacts.py: adds per-CSV counter guards intest_diversity_cuda_smoke_artifacts_cover_native_and_sklearn_replay.Codex review follow-up added total fit-call counters and regenerated the four timing CSVs from the local benchmark scripts. Dev rows used
N4M_LIB_PATH=build/dev-release/cpp/src/libn4m.so; CUDA smoke rows usedCUDA_VISIBLE_DEVICES=0andN4M_LIB_PATH=build/cuda-on/cpp/src/libn4m.so. The smoke rows now reportn_ridge_blender_fit_calls=288andn_operator_pls_stack_fit_calls=21/n_operator_pls_stack_pls_fit_calls=252/n_operator_pls_stack_ridge_fit_calls=21.
Validation:
py_compileon all touched Python files: PASS.test_moment_model_wrappers.py -k 'aom_ridge_blender_compact_contract or aom_operator_pls_stack_compact_contract':2 passed, 72 deselected.test_aom_moment_cuda_smoke_artifacts.py -k 'diversity_cuda_smoke_artifacts_cover_native_and_sklearn_replay':9 passed, 12 deselected.Full suite
test_moment_model_wrappers.py + test_aom_moment_cuda_smoke_artifacts.py:95 passed.Catalog split/reference/strict-ABI checks: PASS.
git diff --check: PASS apart from known CRLF warnings on CSV artifacts.
2026-06-06 — Facade Duplicate Catalog-Role Guard¶
Added
test_duplicate_catalog_ids_are_explicit_wrappers_or_aliasestobindings/python/tests/test_aom_moment_facade.py.The guard allows duplicate
catalog_idrows only when they represent explicit wrappers, aliases, presets or campaign helpers over the same native catalog method. At most one row per facade may claim a primarycatalog_binding/direct_native_bindingrole for a catalog method, and every secondary duplicate must declarewrapper_of.This protects the AOM/moment public inventories from ambiguous duplicate primary methods while preserving the intentional reusable aliases and sklearn wrappers.
Validation:
py_compile on
test_aom_moment_facade.py: PASS.test_aom_moment_facade.py:26 passed.facade/wrapper/catalog suite:
test_aom_moment_facade.py,test_moment_model_wrappers.py,test_catalog_python_bindings.py:111 passed.catalog split/reference/strict-ABI checks: PASS.
2026-06-06 — CUDA Facade Smoke Diversity Alias Guard¶
Extended
benchmarks/cross_binding/aom_moment_cuda_facade_smoke.pyso the one-process CUDA child asserts the recently ported diversity methods are exported by bothn4m.aomand the top-leveln4mfacade:aom_ridge_blender,aom_operator_pls_stack,NativeAOMRidgeBlenderRegressor, andNativeAOMOperatorPLSStackRegressor.Regenerated
benchmarks/cross_binding/aom_moment_cuda_facade_smoke.jsonwithCUDA_VISIBLE_DEVICES=0andN4M_LIB_PATH=build/cuda-on/cpp/src/libn4m.so. The artifact now includesaom_diversity_aliases_top_level=trueandaom_diversity_estimators_alias_top_level=true.Added duplicate-key checking to the JSON artifact loader in
bindings/python/tests/test_aom_moment_cuda_smoke_artifacts.py, then asserted the two new facade fields. This protects the committed smoke report from silently shadowed keys and from regressions in the diversity facade exports.Validation:
py_compile on the CUDA smoke script and artifact tests: PASS.
focused CUDA facade artifact test:
1 passed, 20 deselected.full CUDA smoke artifact test file:
21 passed.
2026-06-06 — Claude Code Release-Readiness Audit¶
Delegated a focused Claude Code audit of the handoff and dirty diff with code permissions enabled but no long benchmark campaign. Claude did not produce a patch before its turn cap.
The only concrete possible gap it identified was Ridge moment
eigen_path/direct_cvtelemetry coverage. Codex checked the current tests and confirmed this is already guarded by both CUDA-artifact checks and benchmark tool tests, including exact assertions forn_ridge_moment_eigen_path_preparations,n_ridge_moment_eigen_path_cv_fits, andn_ridge_moment_direct_cv_fits.Codex also verified the staged preset estimator aliases
NativeAOMSavgolFocusRegressorandNativeAOMStrictFamilyLiteRegressorare present in top-leveln4m.__all__. No speculative facade or doc edit was made.Validation:
py_compile on the recently touched facade-smoke/test files: PASS.
targeted AOM/moment release-readiness suite:
test_aom_moment_facade.py,test_moment_model_wrappers.py,test_aom_moment_cuda_smoke_artifacts.py,test_catalog_python_bindings.py,test_aom_staged_campaign.py,test_aom_benchmark_tools.py:168 passed.catalog/scripts/validate.py: PASS, 208 methods.catalog/scripts/validate.py --strict-abi: PASS, 701/701 exportedn4m_*symbols covered.catalog/scripts/validate.py --check-references: PASS, 208/208 production methods covered.catalog/scripts/split_legacy_methods.py --check: PASS, 208 per-method files up to date.
2026-06-06 — CUDA PLS Many-Batched Route Telemetry¶
Added a dedicated committed smoke CSV,
benchmarks/cross_binding/moment_sweep_timing_cuda_many_batched_smoke.csv, generated on one GPU with--cuda-pls-min-device-features 1and--cuda-pls-many-batched.The PLS rows prove the alternate exact PLS moment CUDA route independently of parallel-fold scheduling: host CV fits stay at
0, CUDA-device CV fits equal total PLS CV fits, parallel-fold batches/jobs stay at0, and many-batched counters report one batch and one job per CV fold.Regenerated
benchmarks/cross_binding/aom_moment_cuda_facade_smoke.jsonso the staged/focus/strict facade sections record the new many-batched counters; those remain0there because that artifact exercises the parallel-fold facade route.Strengthened the wrapper fallback test so the new many-batched counters are asserted at zero when the CUDA knobs are requested but the device threshold intentionally forces the CPU path.
Validation:
py_compile on the touched smoke/test files: PASS.
exact wrapper fallback test:
1 passed.full CUDA artifact guard file:
22 passed.full moment wrapper test file:
74 passed.catalog strict ABI/reference/split checks: PASS.
git diff --check: PASS.
2026-06-06 — AOM/Moment Completion Audit¶
Re-audited the port against the requested end state and the coverage matrix. The reusable AOM/moment method surface, direct heads, staged/global configurable campaigns, CPU paths, CUDA build smokes, catalog files and docs are internally consistent.
Claude Code reran the targeted AOM/moment suite:
test_aom_benchmark_tools.py,test_catalog_python_bindings.py,test_aom_moment_cuda_smoke_artifacts.py,test_aom_moment_facade.py,test_moment_model_wrappers.py, andtest_aom_staged_campaign.py:169 passed.Catalog validation remained green: reference coverage for
208/208production methods, strict ABI coverage for701/701exportedn4m_*symbols, and per-method split files up to date.Verified referenced docs, benchmark scripts, CUDA smoke artifacts and coverage-matrix catalog ids exist on disk. No new small Python/facade/catalog gap was found.
Updated
DEFERRALS.mdso the GPU section no longer claims only single-fit CUDA exists: bounded exact PLS CV routes now ship, while the full fused cartesian/IKPLS-style 200k-chain grinder remains the explicit deferred engine/performance item.
2026-06-06 — Moment SSE BLAS Engine Slice¶
Added a small score-preserving throughput improvement to the hot
ridge_heldout_sse_from_moments()path used by Ridge and PLS moment CV scoring. In CPU BLAS builds, single-target (q=1) held-out moment scoring withp >= 64now computes the quadratic term throughlinalg::gemv(X'X beta) before the final dot product.The CUDA build keeps the existing scalar host SSE path to avoid accidental per-candidate GPU transfers; CUDA acceleration remains concentrated in the PLS moment component routes and their explicit counters.
This is not the fused cartesian IKPLS grinder, but it removes a CPU BLAS bottleneck from exact moment screen scoring without changing scores or selection.
Validation:
dev, BLAS and CUDA
n4m_cbuilds: PASS.targeted moment wrapper tests on dev and BLAS libs:
6 passed, 68 deselectedeach.test_aom_benchmark_tools.pyon dev and BLAS libs:20 passedeach.dev-vs-BLAS candidate-score comparison on
n=200, p=80: same selected lambda and max absolute score delta6.63e-15.Added
test_native_sweep_run_blas_sse_scores_match_scalar_buildso the dev-vs-BLAS candidate-score comparison is a permanent pytest guard.git diff --check: PASS.
2026-06-06 — Moment GPU Crossover Artifact Refresh¶
Added
--summary-outputtobenchmarks/cross_binding/bench_moment_gpu_crossover.py. The Markdown summary renders one row per(head, shape)with CPU time, CUDA default time, PLS-only CUDA many-batched time, best CUDA profile, speedups and recommended backend. The recommendation remains source-free: shape/head timing only, no dataset identity.Regenerated
benchmarks/cross_binding/moment_gpu_crossover.csvand addedbenchmarks/cross_binding/moment_gpu_crossover.mdwith one visible GPU,--repeats 3,--cuda-pls-min-device-features 1and--compare-cuda-pls-many-batched. The CSV now matches ABI1.21.0, includes CPU baseline, CUDA default and CUDA many-batched profiles, and had zero child-process errors.Current smoke interpretation: PLS stays on CPU at
260x256, CUDA default wins at512x512and256x1024, and many-batched remains slower than CUDA default on those larger PLS rows.Validation:
py_compile on the touched benchmark/test files: PASS.
targeted benchmark-tool pytest:
1 passed, 20 deselected; full file:21 passed.
2026-06-06 — PLS Exact Batch OpenMP Aggregation Slice¶
Parallelized the exact PLS moment score-only batch path’s per-chain held-out SSE/result aggregation with
N4M_PARALLEL_FOR_STATIC, after shared prefix fits have already been computed.The patch keeps public scores, candidate ordering, ABI and counters unchanged; per-chain failures are stored in thread-local vectors and reported through
Contextonly after the parallel region.Added
test_native_aom_pls_moment_batch_omp_scores_match_scalar_buildto compare scalar and OpenMP builds in subprocesses, including candidate-score parity and exact batch counter checks.Synthetic timing smoke (
n=192, p=32, 31 chains, PLS exact CV, components1/2/4, five repeats): dev3.00 ms, OpenMP one thread3.10 ms, OpenMP four threads2.45 ms, with unchanged selected score and score matrix shape.Validation:
dev, OpenMP and CUDA
n4m_cbuilds: PASS.full dev
test_moment_model_wrappers.py:76 passed.targeted dev and OpenMP wrapper checks:
2 passed, 74 deselectedeach.Claude Code Opus/max review found no blocking issue: no data race, no ABI/API/counter change, deterministic per-chain accumulation preserved. Residual non-blocking note: CI does not appear to build
omp-on, so the new parity guard is mostly a local guard until an OpenMP CI job exists.
2026-06-06 — CUDA PLS Scheduler Precedence Slice¶
Fixed
pls1_moment_components_many()route precedence so the explicitcuda_pls_many_batched=True/N4M_CUDA_PLS_MANY_BATCHED=1tiled scheduler is tried beforecuda_pls_parallel_folds=Truewhen both knobs are set.Made
N4M_CUDA_PLS_MANY_LEGACY=1a real override: it disables the tiled many-batched route even when the explicit Python flag or environment opt-in is set, then falls through to requested parallel-folds or the sequential legacy path.Added
test_cuda_pls_many_batched_precedes_parallel_and_legacy_overrides, a live CUDA subprocess guard that checks route counters for both cases and asserts candidate-score parity.Updated
docs/methods/sweep_run.mdanddocs/methods/aom_chain_sweep_run.mdwith the precedence and legacy override semantics.Validation:
CUDA
n4m_cbuild: PASS.targeted wrapper tests on
cuda-onanddev-release:2 passed, 75 deselectedeach.full dev
test_moment_model_wrappers.py:77 passed.CUDA artifact guard file:
22 passed.live CUDA precedence guard:
1 passed, 76 deselected.py_compile on
test_moment_model_wrappers.py: PASS.git diff --check: PASS.
2026-06-06 — PLS Exact Single-Chain Prefix Recovery Slice¶
Extended the exact PLS moment fallback strategy from score-only batches to single-chain
run_moment_sweepand the internalscore_pls1_moment_sweep. When the requested maximum component prefix fails, these paths now retry smaller requested prefixes in descending order and reuse recovered lower prefixes for fold-CV scoring.The slice preserves the moment-only semantics: no transformed
Xmaterialization is introduced for the recovered case, components above the recovered prefix becomeinf, and lower recovered components keep exact held-out moment SSE scoring.Final PLS moment refits now fit the selected component prefix instead of the largest component in the grid, so a selected lower component is not blocked by a rejected higher component.
Added direct internal coverage for
score_pls1_moment_sweepprefix recovery and updated the public wrapper rank-deficient fallback test son4m.sweep_runandn4m.pls_cross_validateexpect recovered moment fits rather than materialized PLS fallback on the covered case.Validation so far:
dev and CUDA
n4m_c/n4m_internal_testsbuilds: PASS.dev and CUDA internal tests: PASS.
targeted dev wrapper checks:
3 passed, 77 deselected.targeted one-GPU CUDA wrapper checks:
4 passed, 76 deselected.full dev wrapper:
80 passed.py_compile on
test_moment_model_wrappers.py: PASS.git diff --check: PASS.
2026-06-06 — PLS Exact Batch Lower-Prefix Downgrade Slice¶
Improved the score-only AOM PLS batch path when a maximum requested component prefix fails. Instead of immediately scalarizing every chain/fold job, the batch scorer now retries lower requested prefixes through the shared
fit_pls1_moment_prefixes_for_foldsroute.If a lower prefix succeeds globally, lower components are scored from that grouped batch and keep the existing CUDA route telemetry, including the many-batched counters. Components above the recovered grouped prefix still run through exact per-job fallback, so healthy chains can keep higher components finite while rank-deficient chains mark only their failed components
inf.This is score-preserving relative to the previous exact fallback while reducing the amount of scalar fallback work after late-component failures. It is a practical step toward the full cartesian PLS grinder because a single bad high component no longer destroys batching for all lower prefixes.
Validation:
dev
n4m_c/n4m_internal_testsbuild and internal tests: PASS.targeted dev wrapper checks:
8 passed, 72 deselected.CUDA
n4m_c/n4m_internal_testsbuild and internal tests: PASS.targeted one-GPU CUDA wrapper checks:
6 passed, 74 deselected.live CUDA many-batched guard now asserts the rank-deficient downgraded batch uses one many-batched prefix batch before host fallback handles the failing higher component.
full dev wrapper:
80 passed.py_compile on
test_moment_model_wrappers.py: PASS.git diff --check: PASS.
2026-06-06 — Staged PLS Batch Telemetry Propagation Slice¶
Propagated PLS moment score-batch counters through
aom_staged_chain_campaignreports. Per-stage summaries now exposen_pls_moment_score_batch_calls/jobs, top-level staged reports exposen_screen_pls_moment_score_batch_calls/jobsandn_refit_pls_moment_score_batch_calls/jobs, and thescale_x_valuesaggregation sums those counters across every evaluated model config.NativeAOMStagedChainCampaignRegressor.get_diagnostics()now exposes those staged screen/refit batch counters plus the staged PLS parallel-fold and many-batched CUDA counters. This closes an observability gap where a staged campaign could report PLS moment CV/device fits without proving whether the grouped batch route was actually used.Added staged campaign tests covering per-stage/top-level sums, split-head PLS exact-CV batch calls, model-config aggregation and sklearn diagnostics.
Validation:
staged campaign test file:
16 passed.targeted moment wrapper/inventory checks:
5 passed, 75 deselected.full dev wrapper:
80 passed.py_compile on touched Python modules/tests: PASS.
git diff --check: PASS.
2026-06-06 — Real-Cohort PLS Batch Telemetry Export Slice¶
Extended
run_aom_staged_real_cohort.pyso real benchmark rows and compact diagnostics JSON carry the staged PLS score-batch counters:n_screen_pls_moment_score_batch_calls/jobsandn_refit_pls_moment_score_batch_calls/jobs.Extended
compare_aom_staged_variants.pyroute-counter aggregation with the same fields. Variant summaries can now report total grouped PLS batch calls and jobs alongside CV/device/many-batched counters.This makes campaign artifacts sufficient to audit whether a real-cohort run stayed on the intended grouped PLS route, without reopening per-dataset JSON reports or inferring from CV fit counts.
Validation:
targeted staged-real-cohort/variant tests:
3 passed, 18 deselected.full benchmark tool tests:
21 passed.staged campaign tests:
16 passed.py_compile on touched benchmark scripts/tests: PASS.
git diff --check: PASS.
2026-06-06 — Staged Comparator PLS Score-Mode Slice¶
Added
pls_score_modetocompare_aom_staged_variants.pycampaign config keys and variant labels, so exact-CV (cv) and proxy (gcv_proxy) PLS staged campaign rows stay in separate summary variants.Added a comparator regression test that keeps two
cvrows on different datasets grouped together while separating thegcv_proxyrow, and asserts dataset identity/source/name tokens are not present in the config key.Updated the cross-binding benchmark README to document that score-mode knobs participate in variant grouping.
Validation:
focused staged comparator tests:
4 passed, 18 deselected.full benchmark tool tests:
22 passed.py_compile on touched Python files: PASS.
git diff --check: PASS.
2026-06-06 — Direct Moment Heads CPU/CUDA Artifact Schema Slice¶
Regenerated
direct_moment_heads_timing.csvanddirect_moment_heads_timing_cuda_smoke.csvwith the current direct-head timing schema. Both artifacts now cover all 9 reusable direct moment/linear heads across three shapes with native-function and sklearn replay rows.The CPU artifact now matches the CUDA artifact schema, including the PLS route controls/counters and the
n_pls_moment_cuda_many_batched_*fields. CPU PLS rows prove host exact-CV routing; CUDA PLS rows prove one-GPU device exact-CV routing with many-batched explicitly off.Strengthened artifact tests so both CPU and CUDA direct-head CSVs must expose the current route fields, current method set, replay status and route counters.
Validation:
regenerated CPU direct-head timing artifact on
build/dev-release.regenerated CUDA direct-head timing artifact on one visible GPU (
CUDA_VISIBLE_DEVICES=0,build/cuda-on).focused direct-head artifact tests:
2 passed, 21 deselected.full CUDA/artifact guard file:
23 passed.py_compile on touched benchmark/test files: PASS.
git diff --check: PASS.
2026-06-06 — AOM Score Campaign Facade Inventory Slice¶
Added the missing
chain_score_campaignrow ton4m.aom.available_methods(). The underlyingaom_chain_score_campaignfunction was already re-exported, but the facade inventory did not advertise the score-only global screen surface.The new row is declared as an ultra-configurable score-only strict-linear AOM/moment screen over Ridge/PLS heads, CPU/CUDA capable, linked to
aom_pop.aom_chain_sweepanddocs/methods/aom_chain_sweep_run.md, and marked as reusing candidate tables/audit reports.Added
_SCORE_CAMPAIGN_OPTIONSfrom the real Python signature so the public inventory exposes chain-grid, CV, scaling, moment-policy, PLS score-mode, split-head, checkpoint/resume and CPU/CUDA route knobs without dataset-name routing.Strengthened facade tests so the score-only campaign is required in the AOM reuse/global surface inventory and so
moment_stack/ its sklearn wrapper remain bounded to strict moment/linear heads:ridge,pls,pcr,continuum,ecr,cppls.Validation:
test_aom_moment_facade.py:28 passed.test_catalog_python_bindings.py:12 passed.py_compile on touched Python facade/test files: PASS.
git diff --check: PASS.
2026-06-06 — AOM Score Campaign CUDA Many-Batched Guard¶
Extended the live CUDA PLS route test to exercise
aom_chain_score_campaign, the chunked global score-only campaign surface used for large preprocessing screens.The new path runs four strict-linear chains over two chunks with PLS exact-CV scoring and
cuda_pls_many_batched=True. It proves campaign aggregation of2score-batch calls,16score-batch jobs,2CUDA many-batched batches and16CUDA many-batched jobs.The test reruns the same campaign with
N4M_CUDA_PLS_MANY_LEGACY=1, proving the candidate identity/signature and scores are unchanged while route counters switch to2CUDA parallel-fold batches /16jobs and zero many-batched jobs.Validation:
targeted live CUDA route test:
test_cuda_pls_many_batched_precedes_parallel_and_legacy_overrides:1 passed.full dev wrapper suite:
83 passed.py_compile on
test_moment_model_wrappers.py: PASS.git diff --check: PASS.
This closes a campaign-level evidence gap for the existing many-batched CUDA route. It does not claim the future fused IKPLS/cartesian CUDA grinder.
2026-06-06 — AOM Sweep CUDA Many-Batched Timing Artifact¶
Added
aom_sweep_timing_cuda_many_batched_smoke.csv, generated with the existingbench_aom_sweep_timing.pyscript onbuild/cuda-onusing--cuda-pls-min-device-features 1 --cuda-pls-many-batched.The new artifact keeps the full 49-row AOM sweep timing surface and proves the many-batched exact-CV PLS route at artifact level: PLS rows have zero parallel-fold jobs, positive many-batched batches, and many-batched jobs equal PLS moment CV fits.
Updated the artifact test helper to distinguish CUDA parallel-fold and CUDA many-batched AOM sweep profiles, then added
test_aom_sweep_cuda_many_batched_smoke_artifact_routes_exact_pls_cv.Validation:
full artifact guard file:
50 passed.focused AOM sweep artifact slice:
4 passed.py_compile on touched benchmark/test files: PASS.
git diff --check: PASS.
This strengthens release evidence for the existing AOM many-batched CUDA path, without claiming the deferred fused/batched IKPLS cartesian executor.
2026-06-06 — AOM Screen-Refit CUDA Many-Batched Timing Artifact¶
Added
aom_screen_refit_scaling_cuda_many_batched_smoke.csv, generated withbench_aom_screen_refit_scaling.pyon the one-GPUbuild/cuda-onlibrary using--cuda-pls-min-device-features 1,--cuda-pls-many-batched, and--backend-min-cuda-product 1.The artifact keeps the full 25-row PLS screen/refit scaling surface (
refit_top_k=1,2,4,8,16crossed with the five refit execution modes). Across the rows, exact-CV PLS refit reports730PLS moment CV fits,730CUDA device CV fits,101CUDA many-batched batches,730CUDA many-batched jobs, and zero CUDA parallel-fold jobs.The screen half of this benchmark uses the PLS
gcv_proxymode by design, so its exact-CV CUDA counters remain zero while proxy batch counters account for the screen. The artifact test now explicitly distinguishes CUDA parallel-fold refit, CUDA many-batched refit, and this proxy-screen behavior.Validation:
focused screen-refit artifact slice:
7 passed.full artifact guard file:
51 passed.
This closes release evidence for the existing public screen/refit many-batched CUDA route, without claiming the deferred fused IKPLS/cartesian CUDA executor.
2026-06-06 — Real-Cohort PLS Many-Batched CUDA Smoke Artifact¶
Added
aom_staged_real_cohort_diesel_pls_many_batched_cuda_smoke.csv, a one-row held-out real-cohort smoke generated withrun_aom_staged_real_cohort.pyonDIESEL/DIESEL_bp50_246_b-a, PLS-only,force_moments, exact-CV PLS scoring, and the one-GPU CUDA many-batched route (--cuda-pls-min-device-features 1 --cuda-pls-many-batched --backend-min-cuda-product 1).The artifact is ABI
1.22.0, recordsselection_uses_test_set=False, and proves the real benchmark runner now persists current PLS many-batched route telemetry: screen exact-CV PLS has12total CV fits, zero host fits,12CUDA device fits,2many-batched batches and12many-batched jobs; refit exact-CV PLS has6total/device fits, zero host fits, one many-batched batch and6many-batched jobs. Both stages report zero parallel-fold jobs.Added matching offline oracle join outputs
aom_staged_real_cohort_diesel_pls_many_batched_cuda_smoke_oracle_compare.csvand.md. The smoke uses only four PLS chains, so the joined oracle summary is benchmark-pipeline evidence, not a performance claim.Validation:
focused real-cohort smoke artifact test:
1 passed.full artifact guard file:
52 passed.
This closes a stale-evidence gap for real held-out benchmark artifacts after ABI
1.22.0, without claiming the deferred fused cartesian/IKPLS CUDA engine.
2026-06-06 — Coverage Matrix Many-Batched Release Audit¶
Refreshed
docs/architecture/aom_moment_coverage_matrix.mdafter the latest ABI1.22.0route-evidence commits. The integrated campaign helper now names CUDA many-batched batch/job counters, and the staged campaign row cites the real held-out DIESEL PLS many-batched CUDA smoke with train-only selection and screen/refit route counters.Added a dedicated many-batched artifact note covering the direct moment sweep, AOM sweep, screen/refit and real held-out benchmark-runner smokes. The note keeps the distinction explicit: these artifacts prove current route telemetry and exact-CV compatibility, not the deferred fused cartesian/IKPLS CUDA engine.
Validation:
facade/catalog inventory tests:
40 passed.git diff --check: PASS.
2026-06-06 — PLS Many-Batched cuBLAS Scalar Batching¶
Tightened the existing one-GPU exact PLS1 many-batched CUDA engine in
cpp/src/core/cuda_dispatch.cppwithout changing ABI or public method surfaces.The route still uses host C++ + cuBLAS only; CMake explicitly does not enable CUDA language or custom
.cukernels. A true fused IKPLS/cartesian executor remains separate engine work.Inside
pls1_moment_components_many_batched_tiled, added reusable per-tile device buffers for scalar batch vectors (dscale,dnorm_sq,dtt,dqdot).Replaced several component x job scalar operations with batched cuBLAS calls: vector norms and both
tt=w'Cw/qdot=w'sreductions now usecublasDgemmStridedBatched; column scaling fors -> w,Cw -> pandp*sqrt(tt) -> outernow usescublasDdgmm.The deterministic sign convention remains per job because the current host-C++ CUDA path has no custom kernel for branchy sign normalization.
Validation:
/home/delete/.venv/bin/cmake --build build/cuda-on --target n4m_c -j2: PASS.live one-GPU CUDA wrapper route guard:
test_cuda_pls_many_batched_precedes_parallel_and_legacy_overrides:1 passed./home/delete/.venv/bin/cmake --build build/cuda-on --target n4m_internal_tests -j2: PASS.CUDA_VISIBLE_DEVICES=0 ./build/cuda-on/cpp/tests/n4m_internal_tests: PASS.manual 32-chain / 128 exact-CV PLS fit smoke: many-batched and legacy selected the same best score to floating precision; many-batched used
1many-batched batch /128many-batched jobs and zero parallel-fold jobs, while legacy used1parallel-fold batch /128jobs.
2026-06-06 — PLS Many-Batched Sign Gather¶
Removed the remaining explicit host synchronization per job in the many-batched PLS sign convention path.
pls1_moment_components_many_batched_tilednow gathers the selected dominant-weight sign values into a reusable device vector with scalar device-devicecublasDcopy_v2calls, then performs one device-to-host copy for the whole tile/component before applying any negative sign flips.This keeps the deterministic CPU-compatible sign convention while reducing host synchronization pressure in large many-chain/fold batches. The per-job
cublasIdamax_v2calls remain because the current backend is still host C++ + cuBLAS and has no custom sign-normalization kernel.Validation:
/home/delete/.venv/bin/cmake --build build/cuda-on --target n4m_c -j2: PASS.live one-GPU CUDA wrapper route guard:
test_cuda_pls_many_batched_precedes_parallel_and_legacy_overrides:1 passed./home/delete/.venv/bin/cmake --build build/cuda-on --target n4m_internal_tests -j2andCUDA_VISIBLE_DEVICES=0 ./build/cuda-on/cpp/tests/n4m_internal_tests: PASS.manual 32-chain / 128 exact-CV PLS smoke: many-batched and legacy selected the same best CV score to floating precision and used the expected route counters. The small smoke did not show a speed gain, so this is recorded as a synchronization-structure cleanup rather than a benchmark claim.
2026-06-06 — PLS Many-Batched Score Deflation Batching¶
Batched the PLS score-vector deflation update in the same
pls1_moment_components_many_batched_tiledpath.The previous implementation still ran one
cublasDaxpy_v2per job fors -= tt * q_load * p_load. The updated path computes the per-job-tt*q_loadscale vector on host, usescublasDdgmmto form all scaledp_loadcolumns into the reusabledcwtile buffer, then applies one contiguouscublasDaxpy_v2over the tile, chunked only for cuBLAS int-range safety.This removes another component x job cuBLAS call from the current host-C++ + cuBLAS many-batched engine while preserving exact PLS moment scores.
Validation:
/home/delete/.venv/bin/cmake --build build/cuda-on --target n4m_c -j2: PASS.live one-GPU CUDA wrapper route guard:
test_cuda_pls_many_batched_precedes_parallel_and_legacy_overrides:1 passed./home/delete/.venv/bin/cmake --build build/cuda-on --target n4m_internal_tests -j2andCUDA_VISIBLE_DEVICES=0 ./build/cuda-on/cpp/tests/n4m_internal_tests: PASS.manual 32-chain / 128 exact-CV PLS smoke: many-batched and legacy selected the same best CV score to floating precision (
0.32402508454613366vs0.32402508454613355) with expected route counters. The observed smoke timings were0.249smany-batched vs0.313slegacy; this remains a small smoke, not a final benchmark claim.
2026-06-06 — PLS Many-Batched W/P Tile Storage¶
Removed the per-job strided device copies used only to store intermediate PLS
WandPoutputs in the many-batched route.W/Pare not consumed again on device during the component loop, so the CUDA workspace now stores them temporarily as component-major contiguous tiles. Each component copies the wholedwanddp_loadtile with one chunked contiguous cuBLAS copy forWand one forP.The final device-to-host copy is unchanged in size; the host repack step now converts component-major tile storage back to the row-major
p x n_componentslayout expected by the existing CPU prefix-fit code and public outputs.Validation:
/home/delete/.venv/bin/cmake --build build/cuda-on --target n4m_c -j2: PASS.live one-GPU CUDA wrapper route guard:
test_cuda_pls_many_batched_precedes_parallel_and_legacy_overrides:1 passed./home/delete/.venv/bin/cmake --build build/cuda-on --target n4m_internal_tests -j2andCUDA_VISIBLE_DEVICES=0 ./build/cuda-on/cpp/tests/n4m_internal_tests: PASS.manual 32-chain / 128 exact-CV PLS smoke: many-batched and legacy selected the same best CV score to floating precision (
0.32402508454613366vs0.32402508454613355) with expected route counters; observed smoke timing was0.252smany-batched vs0.307slegacy.
2026-06-06 — PLS Many-Batched Full-Output Guard¶
Extended the live one-GPU CUDA guard
test_cuda_pls_many_batched_precedes_parallel_and_legacy_overridesbeyond score-only route equivalence.The test now runs
n4m.sweep_run(... score_only=False)twice on the CUDA build: once on the default many-batched route and once withN4M_CUDA_PLS_MANY_LEGACY=1forcing the older parallel-fold path.It asserts that the many-batched CV route is used for the default run, the legacy parallel-fold route is used for the override, the final full-data PLS fit stays on the CUDA device, and both routes match on candidate scores, OOF predictions, final predictions, coefficients and intercept.
This closes the release guard gap left after the W/P tile-storage change: the host repack contract is now covered by a non-
score_onlyoutput check, not only by CV score comparisons.Validation:
CUDA_VISIBLE_DEVICES=0 PYTHONPATH=bindings/python/src N4M_LIB_PATH=build/cuda-on/cpp/src/libn4m.so /home/delete/.venv/bin/python -m pytest bindings/python/tests/test_moment_model_wrappers.py::test_cuda_pls_many_batched_precedes_parallel_and_legacy_overrides -q:1 passed.PYTHONPATH=bindings/python/src /home/delete/.venv/bin/python -m py_compile bindings/python/tests/test_moment_model_wrappers.py: PASS.git diff --check: PASS.
2026-06-06 — PLS Many-Batched Batched Sign Scaling¶
Tightened another piece of the CUDA PLS many-batched component loop in
cpp/src/core/cuda_dispatch.cpp.The deterministic sign convention still uses per-job
cublasIdamax_v2to find the dominant loading, because this host-C++ + cuBLAS backend still has no custom CUDA kernel.After gathering the sign values, the code no longer applies one
cublasDscal_v2per negative job. When any job in the tile needs a sign flip, it builds a+1/-1scale vector and applies all sign flips with one column-wisecublasDdgmminto the reusabledoutertile buffer.The signed weight tile is copied to the component-major
Woutput tile beforedouteris reused for the later C-deflation vector, preserving the host output layout introduced by the W/P tile-storage change.Validation:
/home/delete/.venv/bin/cmake --build build/cuda-on --target n4m_c -j2: PASS.CUDA_VISIBLE_DEVICES=0 PYTHONPATH=bindings/python/src N4M_LIB_PATH=build/cuda-on/cpp/src/libn4m.so /home/delete/.venv/bin/python -m pytest bindings/python/tests/test_moment_model_wrappers.py::test_cuda_pls_many_batched_precedes_parallel_and_legacy_overrides -q:1 passed./home/delete/.venv/bin/cmake --build build/cuda-on --target n4m_internal_tests -j2andCUDA_VISIBLE_DEVICES=0 ./build/cuda-on/cpp/tests/n4m_internal_tests: PASS.manual 32-chain / 128 exact-CV PLS smoke: many-batched and legacy selected matching best CV scores (
0.18291980848004097vs0.18291980848004116) with expected counters; observed smoke timing was0.219937smany-batched vs0.229734slegacy, which remains a route smoke rather than a final benchmark claim.
2026-06-06 — PLS Many-Batched Native Sign Kernel¶
Added the first small native CUDA kernel to the PLS many-batched engine:
cpp/src/core/cuda_kernels.cu.The CUDA backend now enables CUDA language only when
N4M_WITH_CUDA=ON. The existing host-C++cuda_dispatch.cppstill owns cuBLAS orchestration; the new.cuhelper is limited to sign normalization insidepls1_moment_components_many_batched_tiled.Replaced the previous per-job
cublasIdamax_v2+ scalar device-copy + host sign gather path with one block per job/column:each block finds the first maximum-absolute-weight index for that job;
it uses the selected weight sign to produce a signed weight column;
the signed tile is written into the existing
douterscratch buffer;downstream W-tile copy,
C*w,w*Cwandw*sconsume that signed tile beforedouteris reused for C deflation.
Updated CMake so CUDA builds can locate
nvccthroughfind_package(CUDAToolkit)even whennvccis not inPATH, and so project C/C++ warning flags are not applied to CUDA translation units.This removes the remaining per-job sign
Idamax/gather host orchestration from the tiled many-batched route. It is still a focused kernel inside the current executor, not yet a fused cartesian/IKPLS kernel suite.Validation:
/home/delete/.venv/bin/cmake --preset cuda-on: PASS./home/delete/.venv/bin/cmake --build build/cuda-on --target n4m_c -j2: PASS.CUDA_VISIBLE_DEVICES=0 PYTHONPATH=bindings/python/src N4M_LIB_PATH=build/cuda-on/cpp/src/libn4m.so /home/delete/.venv/bin/python -m pytest bindings/python/tests/test_moment_model_wrappers.py::test_cuda_pls_many_batched_precedes_parallel_and_legacy_overrides -q:1 passed./home/delete/.venv/bin/cmake --build build/cuda-on --target n4m_internal_tests -j2andCUDA_VISIBLE_DEVICES=0 ./build/cuda-on/cpp/tests/n4m_internal_tests: PASS./home/delete/.venv/bin/cmake --build build/dev-release --target n4m_c -j2: PASS.manual 32-chain / 128 exact-CV PLS smoke: many-batched and legacy selected matching best CV scores (
0.18291980848004097vs0.18291980848004116) with expected counters; observed smoke timing was0.174215smany-batched vs0.227833slegacy, still only a route smoke.
2026-06-06 — PLS Many-Batched Device Copy Cleanup¶
Removed the remaining cuBLAS dependency from two plain contiguous tile-copy operations in
pls1_moment_components_many_batched_tiled.The component-major
WandPtiles are now copied withcudaMemcpyAsyncdevice-to-device instead of chunkedcublasDcopy_v2calls. These copies are not BLAS operations; using the CUDA runtime avoids two extra cuBLAS launches per component while preserving stream-ordering with the current default-stream cuBLAS handle.The same scratch/lifetime contract is preserved:
signed weights are copied to
dWbeforedouteris reused for C deflation;dp_loadis copied todPafter loadings are finalized;the host repack from component-major tiles into row-major
p x n_componentsoutputs is unchanged.
Validation:
/home/delete/.venv/bin/cmake --build build/cuda-on --target n4m_c -j2: PASS.CUDA_VISIBLE_DEVICES=0 PYTHONPATH=bindings/python/src N4M_LIB_PATH=build/cuda-on/cpp/src/libn4m.so /home/delete/.venv/bin/python -m pytest bindings/python/tests/test_moment_model_wrappers.py::test_cuda_pls_many_batched_precedes_parallel_and_legacy_overrides -q:1 passed./home/delete/.venv/bin/cmake --build build/cuda-on --target n4m_internal_tests -j2andCUDA_VISIBLE_DEVICES=0 ./build/cuda-on/cpp/tests/n4m_internal_tests: PASS.manual 32-chain / 128 exact-CV PLS smoke: many-batched and legacy selected matching best CV scores (
0.18291980848004097vs0.18291980848004116) with expected counters; observed smoke timing was0.180833smany-batched vs0.239451slegacy, still only a route smoke.
2026-06-06 — PLS Many-Batched On-Device Scalar Preparation¶
Removed the per-component host<->device scalar ping-pong from
pls1_moment_components_many_batched_tiled. The legacy inner loop copieddnorm_sq,dttanddqdotto the host every component, computed the normalization scale, the Y loadingq,1/tt,sqrt(tt), the score deflation scale and the running Y residual on the host, and copied four scalar vectors back to the device — seven synchronouscudaMemcpyper component plus the host-sideQ[job][comp]write and residual accumulator.Three bounded native CUDA kernels in
cpp/src/core/cuda_kernels.cunow do this on the device (one thread per job):pls1_moment_prepare_scale_many: folds the Y-residual guard (previously the check at the top of the component loop) and the X-weight guard into the s-norm result, and writes the normalization scale1/(||s|| + eps*yy);pls1_moment_prepare_loadings_many: applies the X-score and Y-loading guards, storesqinto a device Q accumulator (component-majordQ[comp*batch + job]) and prepares the three deflation scales1/tt,sqrt(tt)and-tt*q;pls1_moment_update_yy_many: updates the device residualyy -= tt*q*qwith the same tiny-negative clamp.
The running Y residual (
dcur_yy) and the Y loadings (dQ) are now resident on the device for the whole tile; only the finalW/P/Qtiles, the residual (whenyy_outis requested) and a 3-int failure flag are read back, once per tile. The three loadingcublasDdgmmcalls consume the device-residentdinv_tt/dsqrt_tt/ddeflscales directly instead of a re-uploadeddscale.Failure semantics are preserved. Each guard records the first failure (earliest component via the sequential kernel order, recorded by
atomicCAS) into the shared flag as{code, job-local, comp}; the host rebuilds the exact legacy message ("CUDA PLS1 moment <reason> in batched job N") and returns status1. On the abort path the remaining components of the tile produce don’t-care, per-job-isolated outputs that the caller already discards on status1; the happy path pays zero in-loop synchronizations.No ABI, catalog or Python-surface change — the new functions are internal
n4m::cuda_dispatchhelpers.libn4m.sostays at1.22.0.This is still a focused executor over the existing cuBLAS + small native kernels, not the fused cartesian/IKPLS kernel suite.
Validation:
/home/delete/.venv/bin/cmake --build build/cuda-on --target n4m_c -j4: PASS (libn4m.so.1.22.0)./home/delete/.venv/bin/cmake --build build/cuda-on --target n4m_tests -j4andCUDA_VISIBLE_DEVICES=0 ./build/cuda-on/cpp/tests/n4m_tests:351 passed, 0 failed(incl.sweep/cuda_many_batched_opt_in_matches_default)./home/delete/.venv/bin/cmake --build build/cuda-on --target n4m_internal_tests -j4andCUDA_VISIBLE_DEVICES=0 ./build/cuda-on/cpp/tests/n4m_internal_tests: PASS.CUDA_VISIBLE_DEVICES=0 PYTHONPATH=bindings/python/src N4M_LIB_PATH=build/cuda-on/cpp/src/libn4m.so /home/delete/.venv/bin/python -m pytest bindings/python/tests/test_moment_model_wrappers.py::test_cuda_pls_many_batched_precedes_parallel_and_legacy_overrides -v:1 passed— this guard covers both the many-batched-vs-legacy 1e-10 score/counter equivalence and the rank-deficientrecoveredblock (component-1 finite, component-2inf, device/host CV fits4/2).Targeted CUDA pytest set (
test_aom_benchmark_tools,test_catalog_python_bindings,test_aom_moment_cuda_smoke_artifacts,test_aom_moment_facade,test_moment_model_wrappers,test_aom_staged_campaign):215 passed.Manual rank-deficient many-batched repro (n=24, p=64, rank-1 design, route forced): component-1 score
1.24e-07finite, component-2Infinity,n_pls_moment_cuda_many_batched_jobs=4,n_pls_materialized_cv_fits=0.Timing smoke (n=64, p=1024, cv=5, components 1..6): many-batched
0.145svs legacy0.147s, scores equal to 1e-9 — marginal, no regression (the glue was a small fraction of the gemm-dominated cost; the win grows with fold/component count).catalog/scripts/split_legacy_methods.py --check: PASS (208 files);validate.py --check-references: PASS (208/208);validate.py --strict-abi: PASS;reconcile_abi.py --check: 702/702; dev-releasen4m_cbuild: no work (CPU build unaffected);git diff --check: clean.