Native HPO — Phase 1 implementation progress log¶
Branch: feat/native-hpo-phase1 · Worktree: _worktrees/native-hpo-phase1 · Scope: nirs4all-methods only (stop before dag-ml / nirs4all-core / any other repo).
Plan: FINETUNING_ROADMAP.md §6 (two-phase + parallel tracks) · FINETUNING_F0_PR.md (F0 detail).
Rule: each big block is Codex-reviewed before moving on. Everything stays green (ctest dev-release) + ABI-gated.
Status board¶
Block |
State |
Codex review |
Notes |
|---|---|---|---|
Setup (worktree, baseline build) |
✅ done |
— |
isolated worktree; docs committed; toolchain OK |
F0 — ABI surface + |
🟢 done+reviewed |
✅ Codex |
365/365 tests; ABI 2.1; Codex-reviewed (14 findings all applied). Catalog |
F1 — samplers: sobol, lhs, ternary |
✅ done |
🟢 ternary+lhs |
|
F2/F5 — pruners: median, asha, hyperband, racing (+ fidelity engine) |
✅ done (pruners) |
🟢 median+asha+hyperband |
|
F3 — RNG consolidation → ga, pso |
✅ done (samplers) |
🟢 Codex |
|
F4 — cmaes, tpe, gp_ei |
✅ done |
🟢 cmaes+tpe |
|
Q — HpoSpec + comparators + parity CI |
✅ done |
— |
|
B — bindings python→R/MATLAB/WASM + cross-binding gate |
🟡 python done |
— |
Python binding ✅ (ask/tell wrapper + smoke/parity vs dev .so; cross-binding gate ✅ = Track Q golden traces). R/MATLAB/WASM: blocked on runtimes — not installable in this sandbox (no Rscript/Octave/EMSDK), so writing their C-shim marshallers blind can’t be validated; they follow the Python thin-marshaller + established R |
catalog YAML |
⬜ optional |
— |
HPO methods are enum-dispatched behind the shared |
Phase 1 — native HPO in libn4m |
✅ merged to main |
🟢 |
9 samplers + 5 pruners + finetune_estimator + Python binding + Track-Q parity CI; ABI 2.1 (733 symbols). Merged |
Phase 1.5 / E2 — active-aware conditional cascade |
✅ merged to main |
🟢 Codex |
Deactivate-only nested conditional activation (the substrate for sub-pipeline search). 387 tests. Merged |
Phase 2 / nirs4all integration — |
✅ done (nirs4all repo) |
🟢 Codex |
|
catalog YAML |
⬜ optional |
— |
HPO methods are enum-dispatched (no per-method symbols); docs + bib + Track-Q parity cover “comme pour le reste”. Deferred. |
Remaining (cross-language) |
⬜ |
— |
R/MATLAB/WASM finetuning bindings (thin marshallers over the native optimizer; acceptance = Track-Q golden traces) + the dag-ml cross-language pipeline compiler ( |
Legend: ⬜ todo · 🟡 in progress · ✅ done · 🟢 done+reviewed
Log¶
2026-07-11 — Phase 1.5 (E2) + Phase 2 (nirs4all native finetuning) shipped¶
Green light received (ecosystem released, no other agent) → merged Phase 1 to
nirs4all-methodsmain, then built the pipeline-finetuning path.E2 (native enabler, merged): rewrote
apply_conditionsto be deactivate-only — a param is active iff its own condition holds AND its conditional parent is active, resolved up the forest; the#cascade can no longer reactivate a dead branch regardless of declaration order. This is the correctness nested sub-pipeline search needs. Codex-reviewed (transcript 09: 1 Major + 2 Minor applied — order-independence, NOT_IN fail-closed, flat-space perf). 387 C++ tests; ABI unchanged.Phase 2 (nirs4all repo):
nirs4all/optimization/n4m_engine.py—N4MFinetuneManager, a drop-in peer ofOptunaManagerselected byfinetune_params={"engine":"n4m"}. Same DSL/approaches/metrics/FinetuneResult; 9 samplers + 4 pruners; wired intoBaseModelController.finetune.when/when_notconditional clause added (object__attribute conditional finetuning → uses E2). Exampleexamples/user/04_models/U08_native_finetuning.py, user-guide section, 22 unit tests. Codex-reviewed (9 Major + 3 Minor all applied — sorted_tuple, nested-static, approach routing, non-finite pruning, untold-trial, direction-blind aggregation, unknown-engine, seed=None, log-flag). Native TPE matches Optuna’s optimum (n_components=14, identical best_value). 218 finetune tests pass. Merged to nirs4allmain(bff6710c).Deferred: E1/E3 native enablers (not needed by the integration); R/MATLAB/WASM finetuning bindings (runtimes absent here); the dag-ml cross-language pipeline compiler (
PIPELINE_FINETUNING.mdP1–P6) for full operator/sub-pipeline materialisation.
2026-07-10 — Pipeline finetuning design (object__attribute over sub-pipelines)¶
User asked: can we finetune a pipeline directly with the operators in the search space (tune sub-pipelines / whole pipelines, Optuna
object__attributestyle), not just scalars? Verified the native substrate in code and wrotedocs/PIPELINE_FINETUNING.md.Verdict: the substrate is already there for a flat pipeline, one operator-choice per slot — categorical slot +
CONDITION_IN/_NOT_INgating +#name-cascade +is_activereadback + TPE tree-sampler (exactly thekernel/gammatest). What’s missing: (a) the pipeline⇄space compiler + materialiser (the bulk → dag-ml graph compile/materialise + a nirs4all Python controller for theobject__attributeUX → Phase 2), and (b) a few native enablers for deep/branchy spaces — E2 active-state-aware topological cascade (behavioural, no ABI), E1 multi-parent conditions (ABI minor), E3 first-class choice/branch node (ABI minor), E4 bounded variable-length (convention). None needs new numerical kernels. Full phased plan (E1–E4 native; P1–P6 dag-ml/nirs4all) + dependency DAG in the doc. Planned only — not implemented; handed back to the user.
2026-07-10 — Phase-1 (verifiable) consolidation — full green, ABI invariant held¶
Full project gate green:
ctest --preset dev-release= 2/2 binaries pass (n4m_tests 385 + n4m_internal_tests; labels abi/parity/smoke). version-sync OK (project 1.0.9, ABI 2.1.0). ABI symbol surface unchanged: 733 == 733, 0 added / 0 removed — proves the enum-value-only invariant held across all of F1–F5: the entire sampler+pruner library plus the two hyperband option fields (carved fromreserved[]) added zero public symbols beyond the single F0 ABI freeze.sizeof(n4m_optimizer_options_t)verified C == Python == 120.What’s done & verified (nirs4all-methods, this worktree): 9 sampler kinds (random, sobol[Tier-A], lhs, ternary, ga, pso, cmaes, tpe, gp_ei) · 5 pruner kinds (none, median, asha, hyperband, racing) ·
n4m_finetune_estimator· Python binding · Track-Q HPO parity CI (golden traces + Sobol-vs-scipy + pruner-vs-independent-reference, wired intoparity-gate.yml) · per-method docs + bibliography · 7 Codex reviews applied (F0/F1/F2/F3/F4/sobol-gp/hyperband).Honest remaining scope (NOT sandbox-verifiable or Phase-2-coupled): (a) R/MATLAB/WASM bindings — need Rscript/Octave/EMSDK, absent here; pattern-ready, acceptance test = the committed golden traces; (b)
n_componentsfidelity engine — the pruner ABI already consumes any host rung stream, so the PLS-learning-curve-driven pruned finetune loop is dag-ml orchestration (Phase 2, North Star); (c) catalog YAML — HPO methods are enum-dispatched (no per-method symbols), awkward fit, optional. Stopping here per the goal: nothing touched outside nirs4all-methods; awaiting green light to merge + start Phase 2.
2026-07-10 — Track Q: HPO cross-binding parity CI + hyperband Codex review applied¶
Track Q (
parity/hpo/) — the HPO parity harness, the analogue of the numeric fixtures.specs.py(HpoSpec+ a 12-cellREGISTRY: every sampler ≥1×, mixed/categorical, + median/asha/hyperband pruner cells),objectives.py(portable closed-form objectives so every binding computes the sametell()score),run_native.py(spec → StudyTrace via the Python binding),comparators.py,references.py(scipy Sobol + independent pure-Python reimplementations of the median/asha/hyperband rules),run.py(CLI gate) +golden/*.json(committed cross-binding contract) +tests/test_hpo_parity.py. Checks three things: golden-trace stability (same seed → same proposals, the cross-binding guarantee the R/MATLAB/WASM bindings must meet), Sobol Tier-A == scipy, and native pruner verdicts == the pure-Python reference (a second implementation guarding the native one). 16 pytest pass + CLI gate green; wired into.github/workflows/parity-gate.yml(the job that already builds libn4m). The pruner-decision cross-check passing (median 9, asha 11, hyperband 10 pruned, all matching an independent reimplementation) is strong evidence the native pruners — including brand-new hyperband — are correct.Hyperband Codex review (transcript 08: 0 Blocker, 3 Major, 1 Minor — all applied). Major: (1) removed the moving-high-water-mark
Rderivation —max_resourceis now required (>0) (make_pruner→N4M_ERR_INVALID_ARGUMENTfor 0) so the bracket count is stable for the study’s lifetime; (2)int64_tresource/R(nostep+1overflow) +step<0guard; (3)k > k_maxguard so rungs aboveRnever prune. Minor:test_hyperband_edges(rejectmax_resource=0; no-prune aboveR). Pure-Python reference mirrors the same rule;hyperband_prunegolden unchanged. 385 passed, 0 failed.
2026-07-10 — sobol+gp Codex review applied (transcript 07)¶
Codex review of the sobol+gp block: 0 Blocker, 3 Major, 1 Minor — all applied (
docs/reviews/finetuning-roadmap/codex-review-07-sobol-gp.md). Major: (1) Sobol OOB after 2^30 asks — added anexhausted_guard; once the Gray-code column would exceed 30 bits the sampler falls back to base random (no OOB read). (2) GP fake-distinct observations — the GP now stores the decoded (snapped) continuous-axis coords, so two asks that decode to the same integer share one coordinate (models real observations, not fakes); kept int axes in the GP —n_componentsis an int and continuous relaxation is standard (consistent with the approved CMA-ES) — which fixes Codex’s exact failure without crippling integer HPO. (3)M_PI→ localconstexpr kPi(C++17-portable). Minor: addedtest_gp_ei_maximize(EI-sign) +test_gp_ei_edge_cases(pure-categorical fallback + constant-objective/duplicate-coord Cholesky stability). 384 passed, 0 failed; Sobol Tier-A + Python smoke still green.
2026-07-10 — F5: hyperband pruner (completes the pruner set)¶
Added
N4M_PRUNER_HYPERBAND(cpp/src/core/optimization/pruners.cpp): Hyperband as an early-stopping pruner — multiple successive-halving brackets that hedge the early-stopping rate. Rungs at geometric resource levelseta^k(decision only on a rung boundary); each trial assigned a bracketsround-robin by stable ask order, with a grace period exempting it below rungs; at each rung, survive only if in the top 1/eta of same-bracket peers that reached it (ASHA-style, ties survive). Configured from two new forward-compat option fields carved fromreserved[]—max_resource(top rungR; 0 = derive from largest reported step) andreduction_factor(eta; 0 = default 3) — so no new ABI symbol andsizeof(n4m_optimizer_options_t)is unchanged (verified C == Python == 120 bytes; ABI symbol surface diff = 733↔733, zero adds/removes).asha’setais now also taken fromreduction_factor(default 3). Decision-level test proves bracket differentiation (worst-in-bracket-0 pruned at rung 0; bracket-1 trial with a far worse score survives via grace; non-rung step never prunes) + Python end-to-end. 382→384 passed. Docsdocs/methods/hyperband.md, updatedasha.md+docs/abi/changes_log.md. All 5 pruner kinds implemented.
2026-07-10 — F4: gp_ei sampler (completes F4 + all 8 samplers)¶
Added
N4M_SAMPLER_GP_EI(cpp/src/core/optimization/gp.cpp): Bayesian optimization with a Gaussian-process surrogate + Expected Improvement. Aftern_startup_trialsrandom trials, each ask fits an RBF GP on the completed+scored history over the continuous axes (unit space) and returns the max-EI candidate over a 64-point random acquisition batch. Dependency-free: RBF kernel with a median-distance lengthscale heuristic (no marginal-likelihood inner loop),K+1e-6·Isolved by a from-scratch dense Cholesky (forward/back substitution) — fine at NIRS trial counts. Direction-symmetric EI (MAXIMIZE negates the posterior mean),ξ=0.01. Non-continuous axes drawn by the shared decode (independent fallback);enqueueunsupported; pure-categorical spaces degrade to random. Stores its own per-trial proposals (id → cont-axis coords) so future fits read exact coordinates.Convergence test on a smooth 2-D objective in 60 evals (
best < 0.5); measuredbest < 0.03across seeds 1–10 — much more sample-efficient than random/CMA (300 evals). 381 passed, 0 failed. Reserved-sampler test now uses an out-of-range enum (all 8 kinds are implemented). ABI unchanged (enum-value-only). Docdocs/methods/gp_ei.md+rasmussen2006gpbib. F4 complete; the full sampler library — random, sobol, lhs, ternary, ga, pso, cmaes, tpe, gp_ei — is done.
2026-07-10 — F1: sobol sampler (Tier-A, completes F1)¶
Added
N4M_SAMPLER_SOBOL(cpp/src/core/optimization/sobol.cpp+sobol_direction.hpp): Sobol low-discrepancy sequence, one Sobol dimension per parameter, unscrambled Gray-code recursion over the embedded Joe–Kuonew-joe-kuo-6.21201direction numbers (52 dims × 30 bits, extracted fromscipy.stats.qmc.Sobol._sv). Numeric axes map the unit coord throughnumeric_from_unit(log/step/int aware); categoricals bucket it; params beyond dim 52 and conditional/sorted-tuple axes fall back to base random. Plugs into the base per-parameter hooks (override_numeric/override_categorical), so constraints/conditions/forced are handled by the base sampler.Tier-A bit-exact parity verified two ways. Nailed the Gray-code algorithm in Python first (
np.array_equalvs scipy, diff 0.0). C++ testtest_sobol_sequence_parityasserts the first 5 points of a 3-D space equal the known dyadic reference exactly (==, not approx). Pythontest_sobol_parity.pydrives the sampler through the binding and assertsnp.array_equalvsscipy.stats.qmc.Sobol(scramble=False)ford ∈ {1,3,6,10},Nup to 32 — all exact. 380 passed, 0 failed (C++); Python smoke + parity green. ABI unchanged (enum-value-only). Docdocs/methods/sobol.md. Scrambled (Owen) variant is a later Tier-B addition. F1 complete.Fixed the stale
reserved sampler NOT_IMPLEMENTEDtest to usegp_ei(sobol is now implemented; only the F4 GP surrogate stays reserved).
2026-07-10 — F6: Python binding¶
Added the Python binding for the native optimizer: ctypes decls for all 31
n4m_optimizer_*/n4m_search_space_*/n4m_trial_*/n4m_finetune_estimatorsymbols (_ffi_decls.py), anOptimizerOptionsctypes struct mirroringn4m_optimizer_options_t(native alignment;_types.py), and an idiomatic wrappern4m/model_selection/optimizer.py(SearchSpace,Trial,Optimizer+Sampler/Pruner/Direction/Metricenums). Exported fromn4m.model_selection.Smoke test (
bindings/python/tests/test_optimizer_smoke.py): random converges on a quadratic, TPE converges on a mixed continuous+categorical space and picks the right category, median pruner decisions, seed determinism — all pass against the dev.so(N4M_LIB_PATH=…/libn4m.so.2.1.0). Proves the ABI is usable from a consumer (struct layout, ask/tell, result round-trip). (Env note: two.soversions coexisted in the dev build — the stale pre-bump2.0.0and current2.1.0; point N4M_LIB_PATH at 2.1.0.)
2026-07-10 — F4 samplers Codex review applied¶
Codex review of cmaes+tpe: 3 Blocker, 3 Major, 1 Minor — all applied (
docs/reviews/finetuning-roadmap/codex-review-06-F4-samplers.md). Blockers: TPE now requires n≥2 + a non-degenerate split before activating (was an OOB read atn_startup_trials=1); CMA-ES updates from completed+scored trials only (weights renormalised over the scored count; skips the update when none scored) so pruned/failed members never corrupt the mean/covariance. Major: CMA-ES step-size path computed from the repaired stepyw/√C(not the rawz) — consistent after box-clipping; CMA-ES restricted to the continuous axes (cont_axes_map) with non-continuous axes sampled independently (Optuna-style fallback) instead of polluting the covariance; TPE categorical proposal sampled proportional to l/g (not argmax) so the constraint-retry loop can’t livelock. Documented (consistent with ga/pso): hard mutex/requires/exclude handled via fitness for population/CMA samplers; TPE stepped-axis snap at decode. CMA-ES and TPE still converge after the restructure. 379 passed, 0 failed.
2026-07-10 — F4: TPE sampler¶
Added
N4M_SAMPLER_TPE(cpp/src/core/optimization/tpe.cpp): univariate Tree-structured Parzen Estimator (Optuna default). Per param: split the completed history into good (top γ=0.25) / bad, build Parzenl(x)/g(x)(KDE in unit space for numeric via a newunit_from_numericinverse; Laplace-smoothed category frequencies for categorical), draw n_ei=24 candidates fromland keep argmaxl/g. Added a baseoverride_categorical()hook (parallel tooverride_numeric) so TPE plugs into the base sampler’s constraint/condition/forced machinery. Handles mixed/conditional spaces. Convergence test on a continuous+categorical objective (finds x≈3, category ‘a’). Also fixed a stale test (reserved samplernow usessobol, since TPE is implemented). 379 passed, 0 failed. ABI unchanged. Docdocs/methods/tpe.md.
2026-07-10 — F4: CMA-ES sampler¶
Added
N4M_SAMPLER_CMAES(cpp/src/core/optimization/cma.cpp): separable (diagonal) CMA-ES (Ros & Hansen 2008) over the unit hypercube — the canonical mean/covariance/step-size/evolution-path update with a diagonal covariance (no eigendecomposition). Reuses the async-population lifecycle + boundary guard + shared decode (non-continuous axes bucketed = Optuna’s independent fallback). Convergence test assertsbest < 0.1on a smooth 2D objective (a broken CMA-ES would not converge tightly). 378 passed, 0 failed. ABI unchanged. Docdocs/methods/cmaes.md.
2026-07-10 — F3 samplers Codex review applied¶
Codex review of ga+pso: 1 Blocker, 2 Major, 2 Minor — all applied (
docs/reviews/finetuning-roadmap/codex-review-05-F3-samplers.md). Blocker: population samplers refuse to cross a generation/iteration boundary until it is fully resolved (synchronous LIAR_NONE evolution) —ask_batchreturns a partial batch at the boundary instead of evolving on unscored members (addedresolved_in_range). Major:enqueue/warm-start rejected for population samplers (N4M_ERR_UNSUPPORTEDvia anallow_enqueue()hook) — a forced candidate can’t be inverse-encoded into the population; constraint handling documented as fitness-only. Minor: PSO velocity clamp (vmax=0.5). +1 regression test (batch boundary + enqueue reject). 377 passed, 0 failed.
2026-07-10 — F3: GA sampler¶
Added
N4M_SAMPLER_GA(cpp/src/core/optimization/ga.cpp): real-coded genetic algorithm over the unit hypercube[0,1)^P, decoded per parameter (numeric vianumeric_from_unit, categorical/ordinal bucketed) — handles mixed spaces uniformly. Generational: a population of 16 is asked out, then tournament selection + uniform crossover + Gaussian mutation + elitism produce the next generation once scores arrive (keyed on trial-id ranges; the async-population lifecycle will be reused bypso/cmaes). Convergence test on a 2D continuous quadratic. 375 passed, 0 failed. ABI unchanged. Docdocs/methods/ga_search.md. (This is the HPO-sampler GA over the typed space — distinct from the feature-selectionga_select; sharing the RNG-consolidated loops is a later refinement.)
2026-07-10 — F2: racing pruner¶
Added
N4M_PRUNER_RACING(Hoeffding racing) into the reviewed pruner architecture: each trial’s intermediate scores are repeated observations; a trial is pruned once its Hoeffding confidence interval (δ=0.05) no longer overlaps the best trial’s. This is the fold-safe early-stop (roadmap §2c) — correct for exchangeable CV folds where successive-halving’s rank-preservation assumption fails. Decision-level test (clearly-worse trial pruned once enough observations accumulate). 374 passed, 0 failed. ABI unchanged. Docdocs/methods/racing.md. Onlyhyperbandremains reserved (needs the bracket scheduler / total budget → F5).
2026-07-10 — F2 pruners Codex review applied¶
Codex review of the pruner block: 1 Blocker, 3 Major, 3 Minor — all applied (
docs/reviews/finetuning-roadmap/codex-review-04-F2-pruners.md). Blocker: terminal state is now terminal —tell_intermediate/tell_resultreject reports on a non-RUNNING trial (only an idempotent same-status re-report is accepted), so an auto-pruned trial can no longer be overwritten to COMPLETED and winbest(). Major: true 50th-percentile median (mean of the two middle values for even n) — direction-symmetric; finite-score validation on both tell paths (rejects NaN/Inf before it corruptsstd::sort); centralized pruner-kind validation inmake_optimizer(out-of-range/unimplemented pruner is rejected, not silently degraded to none). Minor: one value per(trial, step)(update-in-place); ASHAreduction_factordocumented as fixed-at-3 for F2 + ties-survive semantics. +2 regression tests (pruned-is-terminal, invalid-pruner + NaN). 373 passed, 0 failed.
2026-07-10 — F2: ASHA pruner¶
Added
N4M_PRUNER_ASHA(asynchronous successive halving) into the same factory: at each rung a trial survives only if in the top 1/reduction_factor (=3) of the peers at that rung, decided asynchronously (sound for a per-tellverdict). Decision-level test on a canned history. 371 passed, 0 failed. ABI unchanged. Docdocs/methods/asha.md. (Hyperband bracket-scheduler + racing + then_componentsfidelity engine remain in F2.)
2026-07-10 — F2 opener: pruner architecture + median pruner¶
Added the
Prunerabstraction (cpp/src/core/optimization/pruners.cpp):Optimizerholds aunique_ptr<Pruner>set by amake_pruner()factory fromopts.pruner;tell_intermediate()delegates the keep/prune verdict and marks pruned trialsN4M_TRIAL_PRUNED. This makes pruners orthogonal to samplers (composed via the options struct), matching the roadmap’s sampler ⟂ pruner split.make_optimizernow acceptsNONE+MEDIAN;asha/hyperband/racingslot into the same factory later.Implemented
N4M_PRUNER_MEDIAN(Vizier median stopping rule): prune when a trial’s intermediate score is worse than the median of peer scores at the same step; never beforemin_peers(=n_startup_trials) peers. Decision-level test on a canned history. 370 passed, 0 failed. ABI unchanged. Docdocs/methods/median_pruner.md.
2026-07-10 — F1 samplers Codex review applied¶
Codex review of ternary+lhs: 0 Blocker, 5 Major, 2 Minor — all applied (
docs/reviews/finetuning-roadmap/codex-review-03-F1-samplers.md). Ternary reworked into grid-index space: honoursstep(proposes only on-grid values), reserves RUNNING trials so batched asks don’t collide, skips inactive/off-domain history, and keeps arithmetic bounded (index space, guard against absurdly wide ranges). Baseenqueue()now validates numeric ranges + categorical indices (rejects out-of-range warm-starts). LHS: seed domain-separated from the base RNG;size_tindex comparison; clearer Fisher-Yates. +2 regression tests (stepped ternary + batch reservation distinctness, enqueue out-of-range rejection). 369 passed, 0 failed.
2026-07-10 — F1: lhs sampler¶
Added
N4M_SAMPLER_LHS(cpp/src/core/optimization/lhs.cpp): Latin Hypercube over the numeric axes for the firstn_startup_trialsasks (one independent permutation per dimension + per-cell jitter), random beyond the batch and for categoricals. Refactored the unit→value mapping intoOptimizer::numeric_from_unit()sorandom/lhs/(future)sobolshare it. Test asserts each decile is hit exactly once across the startup batch. 367 passed, 0 failed. ABI unchanged. Docdocs/methods/lhs.md.sobolis intentionally deferred: a useful Tier-A Sobol must bit-matchscipy.stats.qmc.Sobol(scramble=False), which requires the exact Joe–Kuonew-joe-kuo-6.21201direction-number table. Rather than ship an approximate/incorrect sequence, it will be done deliberately with the real table (fetch + embed a modest dimension subset) as a dedicated block.
2026-07-10 — F1: ternary sampler¶
Added
N4M_SAMPLER_TERNARY(cpp/src/core/optimization/ternary.cpp): unimodal-integer ternary search porting the nirs4allBinarySearchSampler(triplet anchor low/high/mid → bisect the larger gap toward the current best). Introduced a small base-class hookoverride_numeric()so adaptive samplers reuse the constraint/loop/conditions machinery without duplicatingsample(). Proposal is a pure function of the completed history (idempotent within an ask). Tunes the first integer axis; others stay random.Test: converges to k∈{6,7,8} on a unimodal objective in ≤25 trials. 366 passed, 0 failed. ABI snapshot unchanged (enum-value-only, no new symbol) — confirms the “later samplers add no ABI symbol” design. Doc
docs/methods/ternary.md.
2026-07-10 — F0 Codex review applied¶
Codex read-only review of F0: 14 findings (4 Blocker, 9 Major, 1 Minor), all applied (transcript
docs/reviews/finetuning-roadmap/codex-review-02-F0.md). Highlights: madeoptimization.hindependently includable (moved theN4M_STATIC_ASSERTmacro above the role-header includes and relocated the HPO enum asserts intooptimization.h) + added C/C++ compile-only include guards; hardened the ABI boundary (try/catch on every name-based trial accessor,std::string_viewlookups,struct_sizedefault-preserving copy with a< 8guard); made constraints authoritative (condition constraints reject unsupported shapes withN4M_ERR_UNSUPPORTED, enqueue validates param names, sampling skips RNG for forced dims and re-checks constraints,askreturns an error on constraint-exhaustion instead of a silent invalid trial); validated numeric ranges (reject NaN/Inf, log needs positive bounds); defaultdirection = AUTO(derive from metric);n4m_finetune_estimatornow rejects unsupported params, returns the full trial trace, and returnsNOT_FITTEDwhen nothing completes; implementedtimeout/durationviasteady_clock; documented MUTEX_GROUP (nirs4all_mutex_issubset) semantics.6 new regression tests lock the fixes (invalid ranges, struct_size guard, enqueue warm-start, conditional activation + conflicting-parent rejection, finetune unsupported-param rejection, AUTO+R2 maximization). 365 passed, 0 failed. ABI snapshot unchanged (internal fixes), version-sync green.
2026-07-10 — F0 green¶
Build clean, all tests pass: 359 passed, 0 failed (7 new optimization tests incl. a real
n4m_finetune_estimatorPLS-CV run;abi_version_compatible_with_headergreen after the bump).ABI snapshots regenerated (linux 734 + derived macos/windows 733) — drift is exactly the 31 new additive
n4m_*symbols, nothing removed. ABI minor bumped 2.0 → 2.1;docs/abi/changes_log.mdentry added; CHANGELOG updated.Docs:
docs/methods/{optimization,random}.md+_finetuning_bibliography.bib(14 refs).Deferred to Track Q (not blocking F0 landing): catalog
optimization.{random,none}.yaml+validate.py --strict-abireconcile (non-strict passes; new symbols are warnings), and theHpoSpec+ parity comparators + CI job. The C++ doctest is F0’s correctness gate with a single sampler.Next: commit F0, Codex review, then F1 (sobol/lhs/ternary).
2026-07-10 — F0 code + env fix¶
Wrote F0:
cpp/include/n4m/optimization.h(frozen ABI),cpp/src/core/optimization/optimizer.{hpp,cpp}(SearchSpace + Optimizer: random sampler, none pruner, constraint rejection, conditional activation, ask/tell),cpp/src/c_api/c_api_optimization.cpp(all wrappers +n4m_finetune_estimatorinternal PLS-CV driver),cpp/tests/test_optimization.cpp(7 cases). Wired n4m.h include + 4-byte enum asserts, CMakeLists (core .cpp), tests CMake + main.cpp registration.Toolchain/env fix (matters for every build here): the conda-linked
~/.local/bin/gfortranused by FITPACK pulls alibm.solinker script referencing the non-existent/lib64/libm.so.6, breaking thelibn4m.solink. FITPACK (spline smoothing) has a non-Fortran fallback and is irrelevant to HPO, so configure with-DCMAKE_Fortran_COMPILER=NOTFOUNDto disable it. C/C++ compiler is system/usr/bin/{cc,c++}; real libm is/usr/lib/x86_64-linux-gnu. Apply the same flag to the dev-release build used for ABI snapshots.
2026-07-10 — setup¶
Created isolated worktree
_worktrees/native-hpo-phase1on branchfeat/native-hpo-phase1frommain(f69a2ec7). Main checkout left clean/untouched.Moved + committed the 4 design docs (strategy, roadmap, F0 PR, Codex review) into the branch (
e39ac038).Toolchain: cmake 4.3.2, ninja, g++ (no clang++; g++ path).
Next: baseline build sanity, then F0.