Commit Graph

16 Commits

Author SHA1 Message Date
jgrusewski
206ebd3558 fix(sp4): Task A1 review fix-ups — fingerprint seed + branch count + doc refresh
3 important + 1 optional findings from code quality review:

1. layout_fingerprint_seed() now lists all 40 SP4 slots and bumps
   `ISV_TOTAL_DIM=131` -> `ISV_TOTAL_DIM=171`. Without this, the fail-fast
   checkpoint-load guard would not detect the SP4 layout extension —
   a binary built against new code would falsely compare-equal to old
   checkpoints' fingerprints.

2. Added `SP4_BRANCH_COUNT=4` constant and `debug_assert!(branch <
   SP4_BRANCH_COUNT)` in `atom_pos_bound`. Test extended to verify
   atom_pos_bound max does not alias into WEIGHT_BOUND family.

3. Refreshed stale content in docs/isv-slots.md: ISV_TOTAL_DIM 96->171,
   fingerprint location [37..39)/[47..49) -> [115..117), table entries
   [94]/[95] -> [115]/[116].

4. Added `debug_assert!(group < SP4_PARAM_GROUP_COUNT)` to weight_bound,
   adam_m_bound, adam_v_bound, wd_rate accessors (symmetric with the
   atom_pos_bound change).

cargo check clean, cargo test slot_layout_is_contiguous_and_total_40 passes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 22:02:41 +02:00
jgrusewski
c724fa99be feat(sp4): Task A1 — define 40 ISV slot indices for signal-driven bounds
Layer A additive: ISV_TOTAL_DIM 131 → 171. New module sp4_isv_slots.rs
defines TARGET_Q_BOUND, ATOM_POS_BOUND[4], WEIGHT_BOUND[8], ADAM_M_BOUND[8],
ADAM_V_BOUND[8], WD_RATE[8], GRAD_CLIP_BOUND, H_S2_BOUND, L1_LAMBDA_TRUNK.
40 slots total. ParamGroup enum for type-safe per-group launches.

No consumers wired yet — slots are reserved but unused. Behavior unchanged.
docs/isv-slots.md updated with the SP4 reservation table per Invariant 7.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 21:54:11 +02:00
jgrusewski
c247d9cab0 refactor(dqn-v2): delete legacy CPU-DtoH per_branch_vsn_mean / per_branch_target_drift
Per pearl_cold_path_no_exception_to_gpu_drives.md and explicit user
direction ("remove the legacy paths!"), the two Plan-1-era host-side
DtoH+CPU-loop helpers are GONE — not just routed around. Replaced
with GPU kernel reductions writing to ISV slots.

Deleted (160 lines):
- GpuDqnTrainer::per_branch_vsn_mean()    — DtoH params slice + host abs+mean loop
- GpuDqnTrainer::per_branch_target_drift() — DtoH (target+online) slices + host RMS loop
- FusedTrainingCtx::per_branch_vsn_mean() and per_branch_target_drift() wrappers

Added (GPU-only producers, ~150 lines):
- target_drift_kernel.cu: 2-block reduction RMS(target − online) for mag/dir
  branches. 256-thread smem tree-reduce per block; thread 0 EMA-updates
  ISV slot via pinned device-mapped (no DtoH).
- ISV slots [92] TARGET_DRIFT_MAG_EMA_INDEX, [93] TARGET_DRIFT_DIR_EMA_INDEX
- Fingerprint shifted [90,91] → [94,95]; ISV_TOTAL_DIM 92 → 96
- Trainer accessors: branch_param_slice_indices(), target_params_buf_device_ptr()
- Collector launcher launch_target_drift_ema_inplace()
- Constructor cold-start writes for the 2 new slots

HEALTH_DIAG site refactor:
- vsn_mag/vsn_dir read from ISV[VSN_MAG_EMA_INDEX=87], ISV[88] (Task 5
  GPU-driven kernel produced these, replacing the legacy VSN scalars)
- drift_mag/drift_dir read from ISV[TARGET_DRIFT_MAG_EMA_INDEX=92], ISV[93]
- All 4 reads via FusedTrainingCtx::read_isv_signal_at — pinned device-mapped
  so host reads are coherent with GPU kernel writes without explicit DtoH

isv-slots.md: rows for [87..89] updated to reflect GPU-only producers
(replaces stale text claiming host-DtoH); 2 new rows for [92, 93];
fingerprint shifted to [94, 95]; ISV_TOTAL_DIM bumped 92 → 96.

Smoke fold-2 best Sharpe 100.45 at ep5; per-fold best_val_metric
3.75/10.09/20.21 (avg 11.35) — within Plan 3 T5 baseline (95-117 range).

Pearl validation: this commit demonstrates the cold-path-no-exception
rule applied retroactively. The legacy methods existed for an entire
plan generation; "cold path is fine" was the rationale. New rule:
if a value is computed (reduction/EMA/RMS), the compute is in a kernel,
period — regardless of frequency.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 11:02:23 +02:00
jgrusewski
cfc4ccb72f feat(dqn-v2): E.5 Mode A attention-focus ISV — fully GPU-driven
Plan 4 Task 5 Mode A. Light, ISV-only, no model parameters added,
no checkpoint break.

ISV tail-append:
- [87] VSN_MAG_EMA_INDEX
- [88] VSN_DIR_EMA_INDEX
- [89] MAMBA2_RETENTION_EMA_INDEX
- Fingerprint shifted [85,86] → [90,91]; ISV_TOTAL_DIM 87 → 92

Producer (attention_focus_ema_kernel.cu):
- Single kernel, 3-block multi-reduction, fully GPU-driven
- Block 0 reduces magnitude-branch VSN-weight slice of params buffer
- Block 1 reduces direction-branch VSN-weight slice
- Block 2 reduces mamba2_h_enriched buffer
- Each block: 256-thread smem tree-reduce → mean |x| → EMA-update
  ISV slot via pinned device-mapped (no explicit DtoH)
- Adaptive α matches Plan 3 Task 3 convention

GPU-only correction (per user direction "no dtoh, pinned memory"):
First-pass agent implementation used host-DtoH + CPU loop for the
Mamba2 retention scalar (mirroring the pre-existing
per_branch_vsn_mean() pattern). User flagged this as a violation
of spec §4.C.6 GPU-drives-CPU-reads even on cold path. Rewrote
the kernel to do all 3 reductions on-device via smem block-reduce,
reading params/mamba2 buffers via raw_ptr() and writing to pinned
ISV slots directly. Removed mamba2_retention_mean() from
GpuDqnTrainer + FusedTrainingCtx (was the host-DtoH culprit).

Read-only AttentionMonitor mirrors PlanThresholdMonitor pattern.
StateResetRegistry: all 3 slots FoldReset.

Smoke fold-2 best Sharpe 95.09, avg best_val_metric 10.98 — within
Plan 3 baseline range. ISV slots populate non-zero from epoch 2.

Pearl: cold-path is not an exception to GPU-drives-CPU-reads. If
the producer is computing a value (not just reading a CPU-side
constant), the compute belongs on GPU regardless of frequency.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 10:17:29 +02:00
jgrusewski
3cb083f182 feat(dqn-v2): B.3 + C.5 GPU-only replay seed warm-start + CQL α ramp
Plan 3 Tasks 8 + 9. Single commit because Task 9 directly consumes Task 8's
seed-fraction signal; no useful intermediate state.

ISV tail-append:
- [82] SEED_STEPS_TARGET_INDEX — config replay_seed_steps (CPU constructor write)
- [83] SEED_STEPS_DONE_INDEX — GPU-incremented per collect_experiences_gpu
- [84] SEED_FRAC_EMA_INDEX — adaptive EMA of (1 - done/target)
- Fingerprint shifted [80,81] → [85,86]; ISV_TOTAL_DIM 82 → 87

GPU-only design (per user direction "fully gpu driven no cpu involvement"):
- 4 scripted policies as ONE CUDA kernel (scripted_policy_kernel.cu)
- Per-sample policy mix (40% uniform LCG / 20% momentum / 20% mean-rev /
  20% vwap-deviation) deterministic by `i % 5`
- Action source switched at launch boundary (CPU per-epoch read of ISV slot
  decides which kernel to dispatch; the action computation itself is 100% GPU)
- No CPU physics mirror — existing GPU `experience_env_step` runs unchanged

seed_step_counter_update_kernel.cu:
- Single-thread cold-path; increments DONE, computes FRAC = max(0, 1-done/target)
- Adaptive α matches Task 3/4 convention (α_base × (1 + 0.5×|clamp(sharpe,±2)|))

cql_alpha_seed_update_kernel.cu (Task 9):
- target = config.cql_alpha × max(0, 1 - seed_frac)
- During seed phase (frac=1) → target=0 → CQL α decays to 0 (no pessimism on
  exploration data); as frac → 0 → CQL α ramps to config value
- Updates ISV[CQL_ALPHA_INDEX=48]; CQL gradient kernel reads slot 48 via
  pinned device-mapped ISV (Plan 1 Task 12 consumer pattern unchanged)

Registry: SEED_STEPS_DONE + SEED_FRAC_EMA both FoldReset; CQL_ALPHA flipped
SchemaContract → FoldReset; SEED_STEPS_TARGET stays SchemaContract.

Read-only monitors (mirror PlanThresholdMonitor / StateKlMonitor pattern):
- monitors/seed_monitor.rs — surfaces ISV[82..85) for HEALTH_DIAG +
  controller_activity smoke fire-rate
- monitors/cql_alpha_monitor.rs — surfaces ISV[48] + ISV[84] dependency

Smoke (RTX 3050 Ti, 3 folds × 5 epochs, dqn-smoketest profile with
replay_seed_steps=1000 override so seed phase completes mid-fold):
- All 3 folds saved best-checkpoint
- Fold 2 best Sharpe = 92.4938 at epoch 1 (target range 80-120) ✓
- Per-fold val_metric: f0=3.80 / f1=9.73 / f2=20.24 (loss-based)
- HEALTH_DIAG[3..4] cql_alpha=0.0500 with health=0.49 → base ≈ 0.10 from ISV[48],
  consistent with kernel ramping toward final×(1-frac); regime gate
  (1-regime)×health applies on top
- 11 cargo check warnings (matches pre-task baseline; no new warnings)
- 6/6 monitor unit tests pass (read/diagnose/observe×fire_rate)

Smoke override rationale: smoke runs ~200 samples per collect (4 episodes ×
50 timesteps) × 5 epochs × 3 folds ≈ 3000 total. Default 100k target would
keep entire smoke in seed phase. Override to 1000 lets the seed→network
transition complete mid-fold so the CQL α ramp is observable.

Per pearl_one_unbounded_signal_per_reward.md: cql_alpha is bounded (clamped
to config_final × (1 - seed_frac) ∈ [0, config_final]), composes safely with
downstream CQL loss.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 09:13:39 +02:00
jgrusewski
673eb66124 feat(dqn-v2): C.3 state-distribution KL divergence + adaptive amplification
Plan 3 Task 7.

ISV tail-append:
- [78] STATE_KL_TRAIN_VAL_EMA_INDEX — moment-match KL on OFI dims, EMA
- [79] STATE_KL_AMPLIFICATION_INDEX — Flat-trap escape multiplier ∈ [1,2]
- Fingerprint shifted [76,77] → [80,81]; ISV_TOTAL_DIM 78 → 82

Producer (state_kl_divergence_kernel.cu):
- Per-dim Gaussian moment-match KL summed over OFI block (32 dims)
- Adaptive α matches Task 3 convention: α_base × (1 + 0.5 × |sharpe|), α_base=0.05
- Amp formula: ratio = new_ema / prev_ema; target = 1 + clamp(ratio−1, 0, 1)
- Kernel-internal trailing-EMA-of-self pattern — no separate threshold ISV slot
- Single block, one thread per OFI dim, smem reduction, no atomicAdd, no DtoH

Val-side launch: GpuBacktestEvaluator exposes val_state_sample() returning
(ptr, n) into the chunked_states_buf the val pass populates. Train side uses
the experience-collector's existing states buffer. Both buffers in same
CUDA context; launch enqueues on collector's stream after compute_validation_loss.

Consumer (experience_kernels.cu):
- B.1 opp_cost ×= kl_amp (penalty intensifies on train/val drift)
- B.2 bonus ×= kl_amp (novelty bonus intensifies on drift)
- fmaxf(1.0, ISV[STATE_KL_AMP]) guard at consumers handles cold-start 0 → no-op

Per pearl_one_unbounded_signal_per_reward.md: amp ∈ [1,2] is bounded; stacks
safely with B.1's q_abs_ref unbounded multiplicand. No formula blow-out risk.

Constructor cold-start: KL=0.0, amp=1.0 (consumer no-op until kernel fires).
Registry: both slots FoldReset with cold-start values restored on fold boundary.
Read-only StateKLMonitor surfaces both slots in diagnose snapshot.

Smoke results: All 3 folds pass cleanly with diverse epoch convergence
patterns (best at ep5/ep2/ep3 — addresses prior concern about always-ep1
in fold 2). Per-fold best Sharpe: 105.13, 83.28, 103.49. Average
best_val_metric 10.64.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 02:26:09 +02:00
jgrusewski
8f59c1e3b8 feat(dqn-v2): B.4 adaptive plan threshold — upgrade PLAN_THRESHOLD_INDEX static→GPU-driven
Plan 3 Task 4.

ISV tail-append:
- [75] READINESS_EMA_INDEX — batch-mean readiness EMA (GPU-written)
- [49] PLAN_THRESHOLD_INDEX — producer upgraded from static constructor
  write to GPU kernel (same consumer path unchanged)
- Fingerprint shifted [73,74] → [76,77]; ISV_TOTAL_DIM 75 → 78

Producer (plan_threshold_update_kernel.cu):
- Single-block reduction of readiness_per_sample [N*L]
- Adaptive α = α_base × (1 + 0.5 × |clamp(sharpe, -2, 2)|); α_base=0.05
- Derived: threshold = max(0.1, 0.5 × readiness_ema) → ISV[49]

Consumer sites unchanged (experience_kernels.cu 4 sites + backtest_plan_kernel.cu).
The upgrade is producer-only; consumers keep reading ISV[49] as before but now
receive an adaptive value tracking the policy's actual readiness distribution
rather than a hardcoded 0.5 midpoint.

PlanThresholdMonitor (read-only observer) surfaces plan_threshold.eff +
plan_threshold.readiness_ema for HEALTH_DIAG / controller_activity smoke.

StateResetRegistry: PLAN_THRESHOLD flipped SchemaContract→FoldReset;
READINESS_EMA registered as FoldReset. Both fold-reset arms restore the
cold-start values (0.5 / 1.0) before the first kernel fire on the new fold.

No breaking changes to consumer API.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 01:35:07 +02:00
jgrusewski
b5d19c1004 feat(dqn-v2): B.2 ISV-driven trade-attempt bonus — novelty at Flat→Positioned
Plan 3 Task 3.

ISV tail-append:
- [71] TRADE_ATTEMPT_RATE_EMA — Flat→Positioned transition rate EMA
       (GPU-written by trade_attempt_rate_ema_update, adaptive α)
- [72] TRADE_TARGET_RATE — reference rate, CPU-frozen at epoch 5
- Fingerprint shifted [69,70] → [73,74]; ISV_TOTAL_DIM 71 → 75

Producer kernel (trade_rate_ema_kernel.cu):
- Single-block reduction of flat_to_pos_per_sample [N*L] (no atomicAdd)
- Adaptive EMA: α = α_base × (1 + 0.5 × |clamp(sharpe, -2, 2)|)
- α_base = 0.05 (matches reward_component_ema convention)
- Launched from training_loop alongside reward_component_ema

Consumer (experience_env_step):
- Flat→Positioned site: novelty = max(0, 1 - attempt/target)
- bonus = conviction_core × vol_proxy × novelty
- reward += shaping_scale × bonus; rc[5] captures bonus for ISV[68]
- Explicit freeze gate: target_raw > 1e-6f, so bonus is structurally
  inert pre-freeze (prevents spurious novelty=1.0 on epoch-1 when
  attempt_rate is still 0)

Epoch-5 freeze (training_loop):
- measured = ISV[TRADE_ATTEMPT_RATE_EMA]; floor at 0.001
- Prevents novelty from sticking at 1.0 post-freeze

StateResetRegistry: both slots registered as FoldReset with per-slot
reset dispatch arms in training_loop's fold-boundary path.

Smoke: multi_fold_convergence passes (fold 2 best Sharpe 87.55 —
 slightly above Task 2 baseline 85.6, within noise; bonus inert
 in 5-epoch smoke so training matches pre-B.2 baseline as designed).
cargo check clean at 11 warnings baseline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 23:11:20 +02:00
jgrusewski
44539d8f4b feat(dqn-v2): C.2 reward-component attribution — 6 ISV EMAs + HEALTH_DIAG split
New GPU kernel reward_component_ema reads per-sample per-component
rewards [popart, cf, trail, micro, opp_cost, bonus] and updates 6 ISV
EMA slots via adaptive-rate EMA (α=0.05). CPU-side RewardComponentMonitor
is a read-only observer per spec §4.C.6; HEALTH_DIAG emits reward_split
line each epoch.

experience_kernels.cu extended with reward_components_per_sample [N*L,6]
output param; rc[0]=popart, rc[1]=cf, rc[3]=micro populated; rc[2,4,5]
are structural placeholders for Plan 3 B.1/B.2/C.4/D.4.

ISV slots 63-68 added (REWARD_{POPART,CF,TRAIL,MICRO,OPP_COST,BONUS}_EMA);
fingerprint shifted 61-62 → 69-70; ISV_TOTAL_DIM 63 → 71.
Layout fingerprint seed updated; auto-recomputes on next run.

StateResetRegistry: isv_reward_component_emas → FoldReset.
training_loop.rs: reset_named_state arm zeroes slots 63..69 at fold boundary.
docs/isv-slots.md + docs/dqn-wire-up-audit.md updated (+2 Wired rows, 107 total).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 22:08:40 +02:00
jgrusewski
3c18ebd637 feat(dqn-v2): D.8 Plan 2 Task 6C — TLOB cuBLAS port, train+val parity
Implement TLOB attention (OFI[32]→TLOB[16]) using existing DQN cuBLAS
infrastructure.  Random Xavier init, trained end-to-end via DQN reward.
Both training (experience_env_step) and val (backtest_plan_kernel) write
TLOB features to SL_TLOB_START=74, preserving state-dist parity.

State layout changes:
  - SL_TLOB_DIM=16 inserted at SL_OFI_START+SL_OFI_DIM (=74)
  - SL_MTF_START 74→90, SL_PORTFOLIO_START 90→106, SL_PLAN_ISV_START 98→114
  - SL_PADDING_START 105→121, SL_STATE_DIM 112→128
  - state_layout.rs mirrored: TLOB_START=74, STATE_DIM=128

New files:
  - cuda_pipeline/gpu_tlob.rs: GpuTlob with 9 cublasLt GEMM descriptors,
    forward/backward/adam_step, copy_params_from for val weight sync
  - cuda_pipeline/tlob_kernel.cu: tlob_sdp_forward, tlob_sdp_backward,
    tlob_write_states, tlob_read_grad_from_states kernels

Wiring:
  - fused_training.rs: TLOB forward before trunk; TLOB backward+Adam Phase 6
  - training_loop.rs: per-epoch ISV[60]=TLOB_REGIME_FOCUS_EMA update
  - gpu_backtest_evaluator.rs: val TLOB forward after each launch_gather,
    set_tlob_from_training syncs weights from training TLOB each epoch
  - metrics.rs: creates eval GpuTlob on evaluator stream, syncs params
  - gpu_dqn_trainer.rs: ISV_TOTAL_DIM 60→63, fingerprint indices 58/59→61/62

ISV slot audit: isv-slots.md updated (slot 60=TLOB_REGIME_FOCUS_EMA,
fingerprint shifted to 61/62, ISV_TOTAL_DIM=63).
Wire-up audit: dqn-wire-up-audit.md updated (gpu_tlob.rs + tlob_kernel.cu).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-24 21:38:42 +02:00
jgrusewski
d071501e44 feat(dqn-v2): D.2 per-branch gamma — GPU kernel + monitor (spec §4.D.2 + §4.C.6)
Replaces scalar GAMMA_EFF_INDEX=43 with 4 per-branch slots at 43-46
(DIR, MAG, ORD, URG). New per_branch_gamma_update_kernel.cu reads
per-branch v-range + health and writes 4 slots. Deletes
gamma_update_kernel.cu per feedback_no_partial_refactor.md.

Per-branch base/max: direction [0.92, 0.99] (trend horizon),
magnitude [0.88, 0.95], order [0.85, 0.93], urgency [0.80, 0.90]
(execution horizon). Kernel diverges each branch's γ from uniform
bootstrap based on its branch's Q-stats.

ISV slots downstream of 43 shift +3: KELLY (44→47), CQL_ALPHA (45→48),
PLAN_THRESHOLD (46→49), Q_P05_* (47→50..54), Q_P95_* (51→54..58),
fingerprint (55-56 → 58-59). ISV_TOTAL_DIM grows 57 → 60.
Layout fingerprint auto-recomputes from updated seed bytes.

c51_loss_kernel reads per-branch γ from ISV[43+d] inside the branch loop;
iql_compute_per_sample_support reads ISV[43+d] per-branch for half_w.
kelly_cap_update_kernel write index updated 44→47.
q_quantile_kernel slot bases updated 47/51 → 50/54.
state_layout.cuh ISV_PLAN_THRESHOLD_IDX updated 46→49.

CPU-side PerBranchGammaMonitor (gamma_monitor.rs) is read-only observer
of all 4 slots; read() returns mean, diagnose() exposes individual γ values
+ spread + health. No CPU-side γ computation (spec §4.C.6 GPU-drives).

Plan 2 Task 3. Spec §4.D.2 + §4.C.6.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 20:01:38 +02:00
jgrusewski
2cf9927647 feat(dqn-v2): C.1 quantile-based atom support (Plan 2 Task 1)
Replaces half = max(10*q_gap, 3*std_ema, floor) tuned constants with
quantile-based: half = max(|q_p95 - v_center|, |v_center - q_p5|)
clamped to [min_half_floor, abs_half]. Covers observed Q distribution
directly.

New GPU kernel q_quantile_reduce reads per-sample Q-values from q_out_buf,
sorts per branch (bitonic for power-of-2 N, quickselect otherwise), writes
ISV[Q_P05_*=47..51) and ISV[Q_P95_*=51..55). Cold-path per-epoch (4 blocks,
1 thread each). No atomicAdd.

ISV slots 47-54 added at tail (8 total). Fingerprint shifted to 55-56.
ISV_TOTAL_DIM grows 49 -> 57. Fingerprint seed updated; recomputes hash
at compile time (new value: 0xbf6c400c026d77e3).

Launch order: reduce_current_q_stats (populates q_out_buf) ->
q_quantile_reduce (writes ISV P5/P95) -> reduce_current_q_stats_per_branch
-> update_eval_v_range (reads ISV P5/P95 for half-width).

Bootstrap: q_p05 = v_min, q_p95 = v_max (matches current atom range at
cold start). FoldReset: isv_q_quantiles dispatch arm resets to bootstrap
values at fold boundary.

StateResetRegistry: isv_q_quantiles as FoldReset; dispatch arm added in
training_loop.rs::reset_named_state.

Docs: isv-slots.md rows [47..57) updated, fingerprint tail reference
corrected to [55..57). dqn-wire-up-audit.md: q_quantile_kernel.cu added.

Plan 2 Task 1. Spec §4.C.1.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 19:29:26 +02:00
jgrusewski
ac9bcab94a feat(dqn-v2): C.6 static ISV writes (Tasks 12, 15, 16) + slot pre-allocation
Lands the static-configuration branch of Plan 1 C.6: cql_alpha,
conviction_floor (no-op if IQL_BRANCH_SCALE_FLOOR already serves),
plan_threshold written to dedicated ISV slots at constructor. Consumer
kernels (CQL, backtest_plan, experience) read from ISV instead of
config fields / hardcoded literals.

Also pre-allocates ISV slots for the 6 upcoming GPU-kernel tasks
(atoms, gamma, kelly_cap, tau, epsilon outputs + CPU-born epoch inputs).
Those slots start at 0; GPU kernels in follow-up commits fill them.

New ISV slots:
- EPOCH_IDX_INDEX=39, TOTAL_EPOCHS_INDEX=40 (CPU-born inputs)
- EPSILON_EFF_INDEX=41, TAU_EFF_INDEX=42, GAMMA_EFF_INDEX=43,
  KELLY_CAP_EFF_INDEX=44 (GPU-written in follow-up tasks)
- CQL_ALPHA_INDEX=45, PLAN_THRESHOLD_INDEX=46 (static config; this commit)
- Task 15 confirmed no-op: IQL_BRANCH_SCALE_FLOOR_INDEX=36 already
  serves conviction-floor role (constructor + ISV read in iql kernel).

Layout fingerprint auto-updated via seed-byte edits; fingerprint
re-tail at [47..49). ISV_TOTAL_DIM 39 -> 49.

GpuDqnTrainConfig gains total_epochs field; fused_training.rs passes
hyperparams.epochs at construction; default 0 for smoke tests.

write_isv_signal_at bound extended from ISV_DIM(23) to ISV_TOTAL_DIM(49)
so tail slots are writable by CPU.

cql_alpha consumer: compute_cql_logit_gradients reads base from
ISV[CQL_ALPHA_INDEX] instead of config.cql_alpha; adaptive formula
(base x health x (1-regime_stability)) unchanged.

plan_threshold consumers: experience_kernels.cu (experience_state_gather,
experience_action_select, experience_env_step) and backtest_plan_kernel.cu
(backtest_plan_state_isv) read plan_thr from ISV[ISV_PLAN_THRESHOLD_IDX=46]
via isv_signals pointer already present in both kernels; null-guard defaults
to 0.5f for smoke-scale runs without ISV warmup.

ISV_PLAN_THRESHOLD_IDX=46 defined in state_layout.cuh (included by both
plan kernel files); value must match PLAN_THRESHOLD_INDEX in gpu_dqn_trainer.rs.

StateResetRegistry entries added for all new slots: SchemaContract for
TOTAL_EPOCHS/CQL_ALPHA/PLAN_THRESHOLD; FoldReset for EPOCH_IDX and
GPU-written per-fold slots.

No behavioural change: all threshold/base values remain at their prior
defaults; consumers now read adaptive values from ISV instead of baked-in
config or hardcoded literals.

Plan 1 Tasks 12, 15, 16 + pre-allocation for 9, 10, 11, 13, 14.
Spec §4.C.6 (2026-04-24 GPU-drives-CPU-reads revision).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 16:45:46 +02:00
jgrusewski
fbb8694a0b feat(dqn-v2): A.2 ISV layout fingerprint at ISV[37..39) (tail placement)
Implements spec §4.A.2 structural layout fingerprint with tail placement
rather than head placement (spec alternative: §4.A.2 Step 5.3 alt).

Head placement (ISV[0..2)) was rejected because isv_signals[0] and [1]
are actively written by the isv_signal_update kernel (Q-drift EMA and
gradient-norm EMA). Shifting those would require updating every literal
reference in experience_kernels.cu — a larger change than warranted for
pure contract enforcement. Tail placement leaves all existing indices
intact, touches zero kernel .cu files, and fulfils the same design contract.

Key changes:
- ISV_LAYOUT_FINGERPRINT_LO_INDEX = 37, HI_INDEX = 38 (u64 across 2×f32).
- LAYOUT_FINGERPRINT_CURRENT: u64 = FNV-1a of slot-list seed bytes.
  Value: 0x85d4d76b578a7c17. Any slot change updates seed bytes,
  which updates the hash automatically.
- Constructor writes fingerprint after zero-init; calls
  check_layout_fingerprint() to self-verify before returning.
- check_layout_fingerprint(): reads pinned slots [37..39), recomposes u64,
  fails-fast on mismatch with "retrain required" message.
- Error message does NOT mention migration as an option.
- Pre-commit hook rejects `fn migrate_isv|upgrade_isv` names — makes the
  no-migration rule structurally enforced (check_no_isv_migrations).
- ISV_TOTAL_DIM: 37 → 39.
- Zero existing index shifts (no kernel literal sites affected).
- StateResetRegistry entry renamed ISV_SCHEMA_VERSION → ISV_LAYOUT_FINGERPRINT.
- ResetCategory::SchemaContract docstring updated to remove "migration" framing.
- docs/isv-slots.md: updated table + design note for tail placement.

Tests: state_reset_registry 3 unit tests pass with renamed entry.
       cargo check -p ml clean (pre-existing warnings only).

Plan 1 Task 5. Spec §4.A.2 (tail-placement alternative).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 12:40:59 +02:00
jgrusewski
1353e71ae6 spec+plan(dqn-v2): layout fingerprint replaces schema version (§4.A.2)
User raised a backward-compat concern: integer "schema version"
semantically implies a family of coexisting versions with upgrade
paths between them, inviting the forbidden pattern
(feedback_no_legacy_aliases.md, feedback_no_partial_refactor.md).

Replace with a compile-time structural fingerprint:
- ISV[0..2) stores a u64 FNV-1a hash of the slot layout (split across
  two f32 lanes preserving raw bits).
- The hash is computed by a const fn over the slot list; any slot
  change automatically updates the fingerprint. No human decides
  "what version is this now".
- Checkpoint load is fail-fast only. Error message does NOT mention
  migration as an option.
- Pre-commit hook rejects any `fn migrate_isv|upgrade_isv` to make
  the no-migration rule structurally enforced (landed as part of
  Plan 1 Task 5 hook extension).
- StateResetRegistry entry renamed ISV_SCHEMA_VERSION →
  ISV_LAYOUT_FINGERPRINT (Task 2's landed code touched in Task 5's
  commit per no-partial-refactor).

Updated:
- spec §4.A.2 (fingerprint design + rationale)
- spec §5 landing-order note (fingerprint auto-updates on layout change)
- Plan 1 architecture line
- Plan 1 Task 2 StateResetRegistry test + entry naming
- Plan 1 Task 5 — full rewrite of implementation steps
- Plan 1 exit criteria #6
- Plan 2 pre-plan verification (grep fingerprint constants, not version==0)
- Plan 2 Task 6D.1 (update fingerprint seed, not bump version)
- Plan 2 Task 6D exit criteria #7
- Plan 3 pre-plan verification
- docs/isv-slots.md ISV[0..2) row

No code changed. Plan 1 Task 5 is not yet implemented — this commit
realigns the spec + plans so the implementer subagent works from the
corrected design.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 11:47:07 +02:00
jgrusewski
06989cfdf9 infra(dqn-v2): audit doc scaffolding + pre-commit enforcement
Plan 1 Task 1. Creates the five audit docs plus config/metric-bands.toml
that track Invariants 2, 7, 8 per the DQN v2 spec, and extends the
pre-commit hook with two checks:

  - component-adding commits must touch an audit doc (Invariant 7)
  - added code may not contain TODO/FIXME/XXX/HACK/TBD/unimplemented!/
    todo! markers (Invariant 9)

Tests: manually verified by staging a TODO-marked file; commit
rejected with the correct error message.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 10:25:27 +02:00