From 6715ab4ea1b719f4b9e86cc9af2e94f6ae0473a1 Mon Sep 17 00:00:00 2001 From: jgrusewski Date: Tue, 5 May 2026 19:58:18 +0200 Subject: [PATCH] =?UTF-8?q?feat(sp14):=20B.8=20=E2=80=94=20direction=20Q-h?= =?UTF-8?q?ead=20input=20dim=20SH2=20=E2=86=92=20SH2+1=20+=20fingerprint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Forward wire (weight-side only — dispatch consumer lands in B.9): direction Q-head's first FC weight tensor `w_b0fc` (param-table index 17) input dim grows by 1 to accept aux_softmax_diff. The (rows, cols) shape table at the trainer-init Xavier site is updated in lock-step with `compute_param_sizes()`. Output dim (adv_h) and the bias `b_b0fc` are unchanged — bias is per-output, not per-input. `layout_fingerprint_seed()` entry renamed `PARAM_W_B0FC` → `PARAM_W_B0FC_AUX1`, forcing the FNV-1a hash to bump per Invariant 8 (old checkpoints fail-fast at load via `check_layout_fingerprint` instead of silently aliasing onto the new architecture). Per `feedback_no_legacy_aliases`, no `_DEPRECATED` shim — straight in-place rename. New weight column zero-initialised in trainer construction (mirrors the OFI column zeroing for w_b2fc/w_b3fc): the model starts ignoring the new input and learns to use it through gradient descent. Xavier- init on this column would inject day-0 noise the trunk would have to denoise — let the EGF gate decide when the aux signal is trustworthy. Atomic-migration consumers (`feedback_no_partial_refactor`) updated: - `gpu_dqn_trainer.rs` — Xavier (rows, cols) table at index 17 grows to (adv_h, SH2+1); spectral-norm descriptor entry [4] for W_a1 grows in_dim from sh2 to sh2+1; spec_v_a1 power-iteration vector grows from sh2 to sh2+1 floats. - `dqn/smoke_tests/gradient_budget.rs` — both `alloc_dueling` fixtures' slot 8 grow `cfg.adv_h * cfg.shared_h2` → `cfg.adv_h * (cfg.shared_h2 + 1)`. - `docs/dqn-wire-up-audit.md` — new SP14 Layer B B.8 entry per Invariant 7. Note: forward GEMM dispatch still uses `K = shared_h2` until B.9 lands the concat-then-SGEMM consumer (per plan §2358). Until then the new column reads as ignored padding; this is safe because (a) it's zero- initialised, (b) GPU-only smoke tests are skipped on this CPU CI, (c) the fingerprint bump invalidates any pre-SP14 checkpoint that would attempt to load. Test: `layout_fingerprint_bumps_after_sp14_wire` (CPU-only, in `sp14_oracle_tests.rs`) hashes the pre-B.8 seed verbatim with the single difference `PARAM_W_B0FC` (vs post-B.8 `PARAM_W_B0FC_AUX1`) and asserts `LAYOUT_FINGERPRINT_CURRENT` differs — any silent revert of the rename trips this test. Mirrors the `fingerprint_bumped_from_ pre_b1_1a` pattern from `sp13_layer_b_oracle_tests.rs`. Verified: - `SQLX_OFFLINE=true cargo check -p ml` clean, 18 warnings (baseline) - `cargo test -p ml --test sp14_oracle_tests` 2 passed, 6 ignored (GPU) - `cargo test -p ml --test sp13_layer_b_oracle_tests fingerprint_bumped_from_pre_b1_1a` still passes (sister test) Co-Authored-By: Claude Opus 4.7 (1M context) --- .../ml/src/cuda_pipeline/gpu_dqn_trainer.rs | 37 +++- .../dqn/smoke_tests/gradient_budget.rs | 6 +- crates/ml/tests/sp14_oracle_tests.rs | 197 ++++++++++++++++++ docs/dqn-wire-up-audit.md | 39 ++++ 4 files changed, 270 insertions(+), 9 deletions(-) diff --git a/crates/ml/src/cuda_pipeline/gpu_dqn_trainer.rs b/crates/ml/src/cuda_pipeline/gpu_dqn_trainer.rs index e575d8023..cf3d84604 100644 --- a/crates/ml/src/cuda_pipeline/gpu_dqn_trainer.rs +++ b/crates/ml/src/cuda_pipeline/gpu_dqn_trainer.rs @@ -2134,7 +2134,7 @@ const fn layout_fingerprint_seed() -> &'static [u8] { PARAM_W_A_H_S2=7;PARAM_B_A_H_S2=8;PARAM_W_B_H_S2=9;PARAM_B_B_H_S2=10;\ PARAM_GAMMA_H_S2=11;PARAM_BETA_H_S2=12;\ PARAM_W_V1=13;PARAM_B_V1=14;PARAM_W_V2=15;PARAM_B_V2=16;\ - PARAM_W_B0FC=17;PARAM_B_B0FC=18;PARAM_W_B0OUT=19;PARAM_B_B0OUT=20;\ + PARAM_W_B0FC_AUX1=17;PARAM_B_B0FC=18;PARAM_W_B0OUT=19;PARAM_B_B0OUT=20;\ PARAM_W_B1FC=21;PARAM_B_B1FC=22;PARAM_W_B1OUT=23;PARAM_B_B1OUT=24;\ PARAM_W_B2FC=25;PARAM_B_B2FC=26;PARAM_W_B2OUT=27;PARAM_B_B2OUT=28;\ PARAM_W_B3FC=29;PARAM_B_B3FC=30;PARAM_W_B3OUT=31;PARAM_B_B3OUT=32;\ @@ -2804,7 +2804,7 @@ pub(crate) fn compute_param_sizes(cfg: &GpuDqnTrainConfig) -> [usize; NUM_WEIGHT cfg.value_h, // [14] b_v1 cfg.num_atoms * cfg.value_h, // [15] w_v2 cfg.num_atoms, // [16] b_v2 - cfg.adv_h * cfg.shared_h2, // [17] w_b0fc + cfg.adv_h * (cfg.shared_h2 + 1), // [17] w_b0fc (SP14 aux→Q wire: SH2+1 input — last col = aux_softmax_diff) cfg.adv_h, // [18] b_b0fc cfg.branch_0_size * cfg.num_atoms * cfg.adv_h, // [19] w_b0out cfg.branch_0_size * cfg.num_atoms, // [20] b_b0out @@ -17949,8 +17949,8 @@ impl GpuDqnTrainer { alloc_spec_pair!(spec_u_v1, spec_v_v1, vh, sh2, "spec_u_v1", "spec_v_v1"); // W_v2 [num_atoms, value_h] alloc_spec_pair!(spec_u_v2, spec_v_v2, na, vh, "spec_u_v2", "spec_v_v2"); - // W_a1 [adv_h, shared_h2] - alloc_spec_pair!(spec_u_a1, spec_v_a1, ah, sh2, "spec_u_a1", "spec_v_a1"); + // W_a1 [adv_h, shared_h2 + 1] — SP14 aux→Q wire (input dim grew by 1) + alloc_spec_pair!(spec_u_a1, spec_v_a1, ah, sh2 + 1, "spec_u_a1", "spec_v_a1"); // W_a2 [b0*num_atoms, adv_h] alloc_spec_pair!(spec_u_a2, spec_v_a2, b0 * na, ah, "spec_u_a2", "spec_v_a2"); // W_bo1 [adv_h, shared_h2] @@ -18010,8 +18010,8 @@ impl GpuDqnTrainer { w_ptrs[13], spec_u_v1.dev_ptr, spec_v_v1.dev_ptr, vh_u64, sh2_u64, 0, // [3] W_v2 [num_atoms, value_h] — GOFF 15 (was 6) w_ptrs[15], spec_u_v2.dev_ptr, spec_v_v2.dev_ptr, na_u64, vh_u64, 0, - // [4] W_a1 [adv_h, shared_h2] — GOFF 17 (was 8) - w_ptrs[17], spec_u_a1.dev_ptr, spec_v_a1.dev_ptr, ah_u64, sh2_u64, 0, + // [4] W_a1 [adv_h, shared_h2 + 1] — GOFF 17 (was 8); SP14 aux→Q wire (input dim SH2+1) + w_ptrs[17], spec_u_a1.dev_ptr, spec_v_a1.dev_ptr, ah_u64, sh2_u64 + 1, 0, // [5] W_a2 [b0*num_atoms, adv_h] — GOFF 19 (was 10) w_ptrs[19], spec_u_a2.dev_ptr, spec_v_a2.dev_ptr, b0_u64 * na_u64, ah_u64, 0, // [6] W_bo1 [adv_h, shared_h2] — GOFF 21 (was 12) @@ -27758,7 +27758,7 @@ impl GpuDqnTrainer { (0, 0), // [14] b_v1 (cfg.num_atoms, cfg.value_h), // [15] w_v2 (0, 0), // [16] b_v2 - (cfg.adv_h, cfg.shared_h2), // [17] w_b0fc + (cfg.adv_h, cfg.shared_h2 + 1), // [17] w_b0fc (SP14 aux→Q wire: SH2+1 input) (0, 0), // [18] b_b0fc (cfg.branch_0_size * cfg.num_atoms, cfg.adv_h), // [19] w_b0out (0, 0), // [20] b_b0out @@ -27959,6 +27959,29 @@ impl GpuDqnTrainer { } } + // ── SP14 forward wire: zero the last (aux_softmax_diff) column of w_b0fc [17] ── + // xavier_init fills the full tensor (adv_h × (shared_h2 + 1)). The new + // column at index SH2 carries `softmax_up - softmax_down` from the aux + // head; starting it at zero means the model initially ignores the new + // input and learns to use it through gradient descent. Xavier-init on + // this column would inject day-0 noise that the trunk would have to + // denoise — we let the EGF gate decide when the signal is trustworthy. + // (Mirrors the OFI column zeroing for w_b2fc/w_b3fc above.) + { + let fc_idx = 17_usize; + let base: usize = sizes[..fc_idx].iter().map(|&s| align4(s)).sum(); + let sh2 = cfg.shared_h2; + let sh2p1 = sh2 + 1; + let ah = cfg.adv_h; + // w_b0fc layout: [AH, SH2+1] row-major. Zero column [SH2] for all AH rows. + for row in 0..ah { + let idx = base + row * sh2p1 + sh2; + if idx < host_buf.len() { + host_buf[idx] = 0.0_f32; + } + } + } + // ── KAN spline coefficient init: uniform 0.125 (flat 0.5 gate ≈ sigmoid(0)) ── // Indices 51, 53, 55, 57 are kan_coeff_d [AH, 8]. Fill with 1/8 so the // spline sums to 0.5 regardless of input (mimics sigmoid(0) = 0.5). diff --git a/crates/ml/src/trainers/dqn/smoke_tests/gradient_budget.rs b/crates/ml/src/trainers/dqn/smoke_tests/gradient_budget.rs index a07c6d026..e74124c68 100644 --- a/crates/ml/src/trainers/dqn/smoke_tests/gradient_budget.rs +++ b/crates/ml/src/trainers/dqn/smoke_tests/gradient_budget.rs @@ -53,7 +53,8 @@ fn alloc_dueling(stream: &Arc, cfg: &GpuDqnTrainConf alloc(cfg.shared_h2 * cfg.shared_h1), alloc(cfg.shared_h2), alloc(cfg.value_h * cfg.shared_h2), alloc(cfg.value_h), alloc(na * cfg.value_h), alloc(na), - alloc(cfg.adv_h * cfg.shared_h2), alloc(cfg.adv_h), + // w_b0fc input dim grew to SH2+1 in SP14 (aux→Q wire); bias unchanged. + alloc(cfg.adv_h * (cfg.shared_h2 + 1)), alloc(cfg.adv_h), alloc(cfg.branch_0_size * na * cfg.adv_h), alloc(cfg.branch_0_size * na), ], }; @@ -157,7 +158,8 @@ fn test_spectral_norm_constrains_operator_norm() -> anyhow::Result<()> { alloc_large(cfg.shared_h2 * cfg.shared_h1), alloc_large(cfg.shared_h2), alloc_large(cfg.value_h * cfg.shared_h2), alloc_large(cfg.value_h), alloc_large(na * cfg.value_h), alloc_large(na), - alloc_large(cfg.adv_h * cfg.shared_h2), alloc_large(cfg.adv_h), + // w_b0fc input dim grew to SH2+1 in SP14 (aux→Q wire); bias unchanged. + alloc_large(cfg.adv_h * (cfg.shared_h2 + 1)), alloc_large(cfg.adv_h), alloc_large(cfg.branch_0_size * na * cfg.adv_h), alloc_large(cfg.branch_0_size * na), ], }; diff --git a/crates/ml/tests/sp14_oracle_tests.rs b/crates/ml/tests/sp14_oracle_tests.rs index ba4c3b5ff..c6f3e8665 100644 --- a/crates/ml/tests/sp14_oracle_tests.rs +++ b/crates/ml/tests/sp14_oracle_tests.rs @@ -768,3 +768,200 @@ mod gpu { ); } } + +// ═══════════════════════════════════════════════════════════════════════════ +// Test B.8: Layout-fingerprint regression for direction Q-head input bump. +// +// CPU-only test (no GPU required). Ensures the fingerprint hash changed when +// `w_b0fc`'s input dim grew from `SH2` to `SH2+1`, per Invariant 8 of the +// SP14 plan: any structural reshuffle MUST bump +// `LAYOUT_FINGERPRINT_CURRENT` so old checkpoints fail-fast on load instead +// of silently mapping into the new architecture. The bump is achieved by +// renaming the seed entry `PARAM_W_B0FC` → `PARAM_W_B0FC_AUX1`. Per +// `feedback_no_legacy_aliases`, no `_DEPRECATED` shim is added. +// ═══════════════════════════════════════════════════════════════════════════ + +/// Inline FNV-1a 64-bit hash for the regression test. Mirrors the +/// `const fn fnv1a_64` in `gpu_dqn_trainer.rs` so the test computes +/// the same hash the production fingerprint uses. (Mirrors the +/// equivalent helper in `sp13_layer_b_oracle_tests.rs`.) +const fn fnv1a_64(bytes: &[u8]) -> u64 { + const OFFSET: u64 = 0xcbf29ce484222325; + const PRIME: u64 = 0x00000100000001b3; + let mut h: u64 = OFFSET; + let mut i = 0; + while i < bytes.len() { + h ^= bytes[i] as u64; + h = h.wrapping_mul(PRIME); + i += 1; + } + h +} + +/// Pre-B.8 seed — the post-B.7 seed string with `PARAM_W_B0FC` (instead of +/// the post-B.8 `PARAM_W_B0FC_AUX1`). Verbatim except for that one rename. +/// Hashing this constant gives the pre-B.8 fingerprint that the bump must +/// differ from. A silent revert of the rename would make this test fail. +const PRE_B8_SEED: &[u8] = b"SLOT_0_Q_DRIFT=0;\ + SLOT_1_GRAD_NORM_EMA=1;\ + SLOT_2_TD_ERR_EMA=2;\ + SLOT_3_ENS_VAR_EMA=3;\ + SLOT_4_ENS_VAR_VEL=4;\ + SLOT_5_REWARD_EMA=5;\ + SLOT_6_ATOM_UTIL_EMA=6;\ + SLOT_7_LOSS_EMA=7;\ + SLOT_8_ADX_EMA=8;\ + SLOT_9_REGIME_DISAGREE=9;\ + SLOT_10_REGIME_VEL_EMA=10;\ + SLOT_11_REGIME_STABILITY=11;\ + LEARNING_HEALTH=12;\ + Q_MAG_MEAN_QUARTER=13;Q_MAG_MEAN_HALF=14;Q_MAG_MEAN_FULL=15;Q_ABS_REF=16;\ + Q_DIR_MEAN_SHORT=17;Q_DIR_MEAN_HOLD=18;Q_DIR_MEAN_LONG=19;Q_DIR_MEAN_FLAT=20;Q_DIR_ABS_REF=21;\ + SHARPE_EMA=22;\ + V_CENTER_DIR=23;V_HALF_DIR=24;V_CENTER_MAG=25;V_HALF_MAG=26;\ + V_CENTER_ORD=27;V_HALF_ORD=28;V_CENTER_URG=29;V_HALF_URG=30;\ + GRAD_NORM_TARGET_DIR=31;GRAD_NORM_TARGET_MAG=32;GRAD_NORM_TARGET_ORD=33;GRAD_NORM_TARGET_URG=34;\ + GRAD_SCALE_LIMIT=35;IQL_BRANCH_SCALE_FLOOR=36;\ + EPOCH_IDX=39;TOTAL_EPOCHS=40;\ + EPSILON_EFF=41;TAU_EFF=42;\ + GAMMA_DIR_EFF=43;GAMMA_MAG_EFF=44;GAMMA_ORD_EFF=45;GAMMA_URG_EFF=46;\ + KELLY_CAP_EFF=47;CQL_ALPHA=48;PLAN_THRESHOLD=49;\ + Q_P05_DIR=50;Q_P05_MAG=51;Q_P05_ORD=52;Q_P05_URG=53;\ + Q_P95_DIR=54;Q_P95_MAG=55;Q_P95_ORD=56;Q_P95_URG=57;\ + TLOB_REGIME_FOCUS_EMA=60;\ + REWARD_POPART_EMA=63;REWARD_CF_EMA=64;REWARD_TRAIL_EMA=65;\ + REWARD_MICRO_EMA=66;REWARD_OPP_COST_EMA=67;REWARD_BONUS_EMA=68;\ + TRADE_ATTEMPT_RATE_EMA=71;TRADE_TARGET_RATE=72;\ + READINESS_EMA=75;\ + STATE_KL_EMA=78;STATE_KL_AMP=79;\ + SEED_STEPS_TARGET=82;SEED_STEPS_DONE=83;SEED_FRAC_EMA=84;\ + VSN_MAG_EMA=87;VSN_DIR_EMA=88;MAMBA2_RETENTION_EMA=89;\ + TARGET_DRIFT_MAG_EMA=92;TARGET_DRIFT_DIR_EMA=93;\ + H_S2_RMS_EMA=96;\ + IQN_Q_P05_EMA=99;IQN_Q_P25_EMA=100;IQN_Q_P75_EMA=101;IQN_Q_P95_EMA=102;\ + VSN_MASK_GROUP_0_EMA=105;VSN_MASK_GROUP_1_EMA=106;VSN_MASK_GROUP_2_EMA=107;\ + VSN_MASK_GROUP_3_EMA=108;VSN_MASK_GROUP_4_EMA=109;VSN_MASK_GROUP_5_EMA=110;\ + AUX_NEXT_BAR_MSE_EMA=113;AUX_REGIME_CE_EMA=114;\ + ISV_LAYOUT_FINGERPRINT_LO=115;ISV_LAYOUT_FINGERPRINT_HI=116;\ + MOE_EXPERT_UTIL_EMA_BASE=118;MOE_EXPERT_UTIL_EMA_COUNT=8;\ + MOE_GATE_ENTROPY_EMA=126;\ + MOE_LAMBDA_EFF=128;\ + Q_DRIFT_RATE=129;\ + FOLD_WARMUP_FACTOR=130;\ + TARGET_Q_BOUND=131;\ + ATOM_POS_BOUND_BRANCH_0=132;ATOM_POS_BOUND_BRANCH_1=133;ATOM_POS_BOUND_BRANCH_2=134;ATOM_POS_BOUND_BRANCH_3=135;\ + WEIGHT_BOUND_GROUP_0=136;WEIGHT_BOUND_GROUP_1=137;WEIGHT_BOUND_GROUP_2=138;WEIGHT_BOUND_GROUP_3=139;\ + WEIGHT_BOUND_GROUP_4=140;WEIGHT_BOUND_GROUP_5=141;WEIGHT_BOUND_GROUP_6=142;WEIGHT_BOUND_GROUP_7=143;\ + ADAM_M_BOUND_GROUP_0=144;ADAM_M_BOUND_GROUP_1=145;ADAM_M_BOUND_GROUP_2=146;ADAM_M_BOUND_GROUP_3=147;\ + ADAM_M_BOUND_GROUP_4=148;ADAM_M_BOUND_GROUP_5=149;ADAM_M_BOUND_GROUP_6=150;ADAM_M_BOUND_GROUP_7=151;\ + ADAM_V_BOUND_GROUP_0=152;ADAM_V_BOUND_GROUP_1=153;ADAM_V_BOUND_GROUP_2=154;ADAM_V_BOUND_GROUP_3=155;\ + ADAM_V_BOUND_GROUP_4=156;ADAM_V_BOUND_GROUP_5=157;ADAM_V_BOUND_GROUP_6=158;ADAM_V_BOUND_GROUP_7=159;\ + WD_RATE_GROUP_0=160;WD_RATE_GROUP_1=161;WD_RATE_GROUP_2=162;WD_RATE_GROUP_3=163;\ + WD_RATE_GROUP_4=164;WD_RATE_GROUP_5=165;WD_RATE_GROUP_6=166;WD_RATE_GROUP_7=167;\ + GRAD_CLIP_BOUND=168;H_S2_BOUND=169;L1_LAMBDA_TRUNK=170;\ + BW_D_H_S2_BOUND=171;Q_DIR_GRAD_BOUND=172;\ + SP5_BASE=174;ATOM_V_CENTER_BASE=174;ATOM_V_HALF_BASE=178;ATOM_HEADROOM_BASE=182;\ + ATOM_CLIP_RATE_BASE=186;BUDGET_C51_BASE=190;BUDGET_IQN_BASE=194;BUDGET_CQL_BASE=198;\ + BUDGET_ENS_BASE=202;FLATNESS_BASE=206;NOISY_SIGMA_BASE=210;SIGMA_FRACTION_BASE=214;\ + BRANCH_ENTROPY_BASE=218;Q_VAR_PER_BRANCH_BASE=222;ADAM_BETA1_BASE=226;ADAM_BETA2_BASE=234;\ + ADAM_EPS_BASE=242;IQN_TAU_BASE=250;TRAIL_DIST_PER_DIR_BASE=270;ATOM_NUM_ATOMS_BASE=274;\ + KELLY_F_SMOOTH=280;CONVICTION_SMOOTH=281;TRADE_VAR_SMOOTH=282;\ + KELLY_SAMPLE_COUNT=283;WIN_RATE_SMOOTH=284;LOSS_RATE_SMOOTH=285;\ + PNL_TOTAL=286;PNL_MEAN=287;PNL_VAR=288;PNL_MAX_DD=289;\ + HEALTH_SCORE=290;Q_GAP_NORM=291;Q_VAR_NORM=292;GRAD_NORM_NORM=293;\ + TRAINING_SHARPE_EMA=294;MAX_DD_EMA=295;LOW_DD_RATIO=296;\ + LB_DIFF_VAR_CQL_BASE=297;LB_SAMPLE_VAR_CQL_BASE=301;\ + LB_DIFF_VAR_C51_BASE=305;LB_SAMPLE_VAR_C51_BASE=309;\ + LB_CQL_ACTIVE_BASE=313;LB_C51_ACTIVE_BASE=317;\ + TRAIN_ACTIVE_FRAC_INDEX=321;\ + LB_MAX_BUDGET_CQL_BASE=322;LB_MAX_BUDGET_C51_BASE=326;\ + KELLY_WARMUP_FLOOR=330;Q_VAR_MAG_EMA=331;\ + INTENT_EVAL_DIVERGENCE=332;KELLY_SAMPLE_COUNT_TARGET=333;\ + KELLY_DIVERGENCE_TARGET=334;KELLY_TEMPORAL_TARGET=335;\ + EVAL_DIST_Q=336;EVAL_DIST_H=337;EVAL_DIST_F=338;\ + EVAL_THOMPSON_TEMP=339;\ + REWARD_POPART_WEIGHT=340;REWARD_CF_WEIGHT=341;REWARD_TRAIL_WEIGHT=342;\ + REWARD_MICRO_WEIGHT=343;REWARD_OPP_COST_WEIGHT=344;REWARD_BONUS_WEIGHT=345;\ + CURIOSITY_PRESSURE=346;SABOTEUR_INTENSITY_MULT=347;\ + REWARD_WEIGHT_FLOOR=348;CURIOSITY_BOUND=349;\ + VAL_SHARPE_DELTA_EMA=350;VAL_SHARPE_VAR_EMA=351;\ + REWARD_COMPONENT_MAG_RATIO_BASE=352;\ + SABOTEUR_ENGAGEMENT_RATE=358;PNL_REWARD_MAGNITUDE_EMA=359;\ + POPART_COMPONENT_MAG_EMA=360;\ + REWARD_COMPONENT_VAR_EMA_BASE=361;\ + TARGET_DIR_ACC=372;AUX_DIR_ACC_SHORT_EMA=373;AUX_DIR_ACC_LONG_EMA=374;\ + AUX_DIR_PREDICTION=375;DIR_SKILL_BONUS_ALPHA=376;DIR_SKILL_BONUS_BETA=377;\ + LUCK_WIN_DISCOUNT=378;SKILL_BONUS_CAP_RATIO=379;\ + HOLD_COST=380;HOLD_RATE_TARGET=381;HOLD_RATE_OBSERVED_EMA=382;\ + ISV_TOTAL_DIM=383;\ + PARAM_W_A_H_S1=0;PARAM_B_A_H_S1=1;PARAM_W_B_H_S1=2;PARAM_B_B_H_S1=3;\ + PARAM_W_RESIDUAL_H_S1=4;PARAM_GAMMA_H_S1=5;PARAM_BETA_H_S1=6;\ + PARAM_W_A_H_S2=7;PARAM_B_A_H_S2=8;PARAM_W_B_H_S2=9;PARAM_B_B_H_S2=10;\ + PARAM_GAMMA_H_S2=11;PARAM_BETA_H_S2=12;\ + PARAM_W_V1=13;PARAM_B_V1=14;PARAM_W_V2=15;PARAM_B_V2=16;\ + PARAM_W_B0FC=17;PARAM_B_B0FC=18;PARAM_W_B0OUT=19;PARAM_B_B0OUT=20;\ + PARAM_W_B1FC=21;PARAM_B_B1FC=22;PARAM_W_B1OUT=23;PARAM_B_B1OUT=24;\ + PARAM_W_B2FC=25;PARAM_B_B2FC=26;PARAM_W_B2OUT=27;PARAM_B_B2OUT=28;\ + PARAM_W_B3FC=29;PARAM_B_B3FC=30;PARAM_W_B3OUT=31;PARAM_B_B3OUT=32;\ + PARAM_W_BN=33;PARAM_B_BN=34;\ + PARAM_W_VSN1_0=35;PARAM_W_VSN2_0=36;\ + PARAM_W_VSN1_1=37;PARAM_W_VSN2_1=38;\ + PARAM_W_VSN1_2=39;PARAM_W_VSN2_2=40;\ + PARAM_W_VSN1_3=41;PARAM_W_VSN2_3=42;\ + PARAM_W_GATE_0=43;PARAM_B_GATE_0=44;\ + PARAM_W_GATE_1=45;PARAM_B_GATE_1=46;\ + PARAM_W_GATE_2=47;PARAM_B_GATE_2=48;\ + PARAM_W_GATE_3=49;PARAM_B_GATE_3=50;\ + PARAM_KAN_COEFF_0=51;PARAM_KAN_RESID_0=52;\ + PARAM_KAN_COEFF_1=53;PARAM_KAN_RESID_1=54;\ + PARAM_KAN_COEFF_2=55;PARAM_KAN_RESID_2=56;\ + PARAM_KAN_COEFF_3=57;PARAM_KAN_RESID_3=58;\ + PARAM_W_REGIME=59;PARAM_B_REGIME=60;\ + PARAM_SPACING_RAW_0=61;PARAM_SPACING_RAW_1=62;\ + PARAM_SPACING_RAW_2=63;PARAM_SPACING_RAW_3=64;\ + PARAM_W_V1_5BAR=65;PARAM_B_V1_5BAR=66;\ + PARAM_W_V2_5BAR=67;PARAM_B_V2_5BAR=68;\ + PARAM_W_V1_20BAR=69;PARAM_B_V1_20BAR=70;\ + PARAM_W_V2_20BAR=71;PARAM_B_V2_20BAR=72;\ + PARAM_W_RISK_FC=73;PARAM_B_RISK_FC=74;\ + PARAM_W_RISK_OUT=75;PARAM_B_RISK_OUT=76;\ + PARAM_W_ISV_FC1=77;PARAM_B_ISV_FC1=78;\ + PARAM_W_ISV_FC2=79;PARAM_B_ISV_FC2=80;\ + PARAM_W_ISV_GATE=81;PARAM_B_ISV_GATE=82;\ + PARAM_W_ISV_GAMMA=83;PARAM_B_ISV_GAMMA=84;\ + PARAM_W_CONF_FC=85;PARAM_B_CONF_FC=86;\ + PARAM_W_FEATURE_GATE=87;PARAM_B_FEATURE_GATE=88;\ + PARAM_W_TEMPORAL_ROUTE=89;PARAM_B_TEMPORAL_ROUTE=90;\ + PARAM_W_PLAN_FC=91;PARAM_B_PLAN_FC=92;\ + PARAM_W_PLAN_OUT=93;PARAM_B_PLAN_OUT=94;\ + PARAM_VSN_W1_G0=95;PARAM_VSN_B1_G0=96;PARAM_VSN_W2_G0=97;PARAM_VSN_B2_G0=98;\ + PARAM_VSN_W1_G1=99;PARAM_VSN_B1_G1=100;PARAM_VSN_W2_G1=101;PARAM_VSN_B2_G1=102;\ + PARAM_VSN_W1_G2=103;PARAM_VSN_B1_G2=104;PARAM_VSN_W2_G2=105;PARAM_VSN_B2_G2=106;\ + PARAM_VSN_W1_G3=107;PARAM_VSN_B1_G3=108;PARAM_VSN_W2_G3=109;PARAM_VSN_B2_G3=110;\ + PARAM_VSN_W1_G4=111;PARAM_VSN_B1_G4=112;PARAM_VSN_W2_G4=113;PARAM_VSN_B2_G4=114;\ + PARAM_VSN_W1_G5=115;PARAM_VSN_B1_G5=116;PARAM_VSN_W2_G5=117;PARAM_VSN_B2_G5=118;\ + PARAM_AUX_NB_W1=119;PARAM_AUX_NB_B1=120;PARAM_AUX_NB_W2_K2=121;PARAM_AUX_NB_B2_K2=122;\ + PARAM_AUX_RG_W1=123;PARAM_AUX_RG_B1=124;PARAM_AUX_RG_W2=125;PARAM_AUX_RG_B2=126;\ + PARAM_MOE_GATE_W1=127;PARAM_MOE_GATE_B1=128;PARAM_MOE_GATE_W2=129;PARAM_MOE_GATE_B2=130;\ + PARAM_MOE_EXPERT_0_W1=131;PARAM_MOE_EXPERT_0_B1=132;PARAM_MOE_EXPERT_0_W2=133;PARAM_MOE_EXPERT_0_B2=134;\ + PARAM_MOE_EXPERT_1_W1=135;PARAM_MOE_EXPERT_1_B1=136;PARAM_MOE_EXPERT_1_W2=137;PARAM_MOE_EXPERT_1_B2=138;\ + PARAM_MOE_EXPERT_2_W1=139;PARAM_MOE_EXPERT_2_B1=140;PARAM_MOE_EXPERT_2_W2=141;PARAM_MOE_EXPERT_2_B2=142;\ + PARAM_MOE_EXPERT_3_W1=143;PARAM_MOE_EXPERT_3_B1=144;PARAM_MOE_EXPERT_3_W2=145;PARAM_MOE_EXPERT_3_B2=146;\ + PARAM_MOE_EXPERT_4_W1=147;PARAM_MOE_EXPERT_4_B1=148;PARAM_MOE_EXPERT_4_W2=149;PARAM_MOE_EXPERT_4_B2=150;\ + PARAM_MOE_EXPERT_5_W1=151;PARAM_MOE_EXPERT_5_B1=152;PARAM_MOE_EXPERT_5_W2=153;PARAM_MOE_EXPERT_5_B2=154;\ + PARAM_MOE_EXPERT_6_W1=155;PARAM_MOE_EXPERT_6_B1=156;PARAM_MOE_EXPERT_6_W2=157;PARAM_MOE_EXPERT_6_B2=158;\ + PARAM_MOE_EXPERT_7_W1=159;PARAM_MOE_EXPERT_7_B1=160;PARAM_MOE_EXPERT_7_W2=161;PARAM_MOE_EXPERT_7_B2=162;\ + PARAM_TOTAL_TENSORS=163"; + +#[test] +fn layout_fingerprint_bumps_after_sp14_wire() { + use ml::cuda_pipeline::gpu_dqn_trainer::LAYOUT_FINGERPRINT_CURRENT; + let pre_b8_fp = fnv1a_64(PRE_B8_SEED); + assert_ne!( + LAYOUT_FINGERPRINT_CURRENT, pre_b8_fp, + "B.8 fingerprint must differ from pre-B.8 value (the W_B0FC \ + rename to _AUX1 must change the seed hash). \ + current = {:#018x}, pre-B.8 = {:#018x}", + LAYOUT_FINGERPRINT_CURRENT, pre_b8_fp + ); +} diff --git a/docs/dqn-wire-up-audit.md b/docs/dqn-wire-up-audit.md index 5c3828678..a7b01d4ba 100644 --- a/docs/dqn-wire-up-audit.md +++ b/docs/dqn-wire-up-audit.md @@ -6516,3 +6516,42 @@ This kernel is the fourth and final known-orphan in the B.3..B.6 producer chain. - **Launcher**: NOT YET WIRED. Launchers and graph-capture integration land in B.8/B.9/B.11. - **Consumers**: `sp14_dir_qaux_concat_scratch` will be consumed by the direction Q-head's first FC SGEMM once B.8 bumps the input-dim constant and B.9/B.11 replace the `h_s2` pointer with the wider scratch pointer. - **Reverse dependencies**: no new ISV reads or writes in this commit — purely buffer/handle infrastructure. + +## SP14 Layer B — Commit B.8: direction Q-head input dim SH2 → SH2+1 + fingerprint bump (2026-05-05) + +**Why this commit.** B.8 is the architectural-shape change that opens the slot for the aux→Q forward wire. The direction Q-head's first FC weight tensor `w_b0fc` (param-table index 17) input dim grows from `cfg.shared_h2` to `cfg.shared_h2 + 1` to accept `aux_softmax_diff` (the EGF gate's gradient channel). Every consumer of the layout contract migrates atomically per `feedback_no_partial_refactor`; the layout fingerprint bumps so old checkpoints fail-fast at load. Forward-dispatch wiring (the concat-then-SGEMM at the call site in `forward_branch_q_head`) lands in B.9 — until then the new column is inert (zero-initialised, read as ignored padding by the existing K=SH2 SGEMM, will be activated when the dispatch grows to K=SH2+1). + +### Changes + +| Component | Role | +|-----------|------| +| `compute_param_sizes()` index 17 | `cfg.adv_h * cfg.shared_h2` → `cfg.adv_h * (cfg.shared_h2 + 1)` — drives the flat `params_buf` allocation, every prefix-sum offset table, and Adam m/v sizing | +| Xavier (rows, cols) table at index 17 | `(cfg.adv_h, cfg.shared_h2)` → `(cfg.adv_h, cfg.shared_h2 + 1)` — keeps weight initialisation aligned with the new storage shape | +| New zero-init block for column SH2 | After Xavier-init, zero `host_buf[base + row * (SH2+1) + SH2]` for `row in 0..adv_h` so the new aux_softmax_diff column starts at 0 (model ignores the wire on day 0; gradient descent learns to use it) | +| `layout_fingerprint_seed()` rename | `PARAM_W_B0FC=17` → `PARAM_W_B0FC_AUX1=17` — forces FNV-1a hash difference per Invariant 8; no `_DEPRECATED` shim per `feedback_no_legacy_aliases` | +| Spectral-norm descriptor entry [4] | `in_dim` `sh2_u64` → `sh2_u64 + 1` for `W_a1`; the spec_v_a1 power-iteration vector also grows from `sh2` to `sh2 + 1` floats | +| Smoke-test fixtures (`gradient_budget.rs`) | Both `DuelingWeightBacking` slot 8 allocations (`alloc(...)` and `alloc_large(...)`) grow `cfg.adv_h * cfg.shared_h2` → `cfg.adv_h * (cfg.shared_h2 + 1)` | +| New CPU oracle test | `layout_fingerprint_bumps_after_sp14_wire` — hashes a verbatim pre-B.8 seed (with `PARAM_W_B0FC` instead of `_AUX1`) and asserts `LAYOUT_FINGERPRINT_CURRENT` differs | + +**Why zero-init the new column** (vs Xavier across all SH2+1 columns): mirrors the OFI column zeroing for `w_b2fc` / `w_b3fc` (gpu_dqn_trainer.rs:27941). Xavier-init on the new column would inject day-0 noise that the trunk would need to denoise — the EGF gate is designed to decide *when* the aux signal is trustworthy, not to fight off random initialisation. + +### File-change summary + +| Change | File | +|--------|------| +| `compute_param_sizes` index 17, Xavier (rows, cols) table, zero-init block, fingerprint-seed rename, spec-norm descriptor + spec_v_a1 size | `crates/ml/src/cuda_pipeline/gpu_dqn_trainer.rs` | +| Both `DuelingWeightBacking` slot 8 fixtures resized | `crates/ml/src/trainers/dqn/smoke_tests/gradient_budget.rs` | +| New CPU `layout_fingerprint_bumps_after_sp14_wire` test (mirrors SP13's `fingerprint_bumped_from_pre_b1_1a` pattern with verbatim pre-B.8 seed constant) | `crates/ml/tests/sp14_oracle_tests.rs` | + +### Verification + +- `SQLX_OFFLINE=true cargo check -p ml` — clean, 18 warnings (pre-existing baseline) +- `SQLX_OFFLINE=true cargo test -p ml --test sp14_oracle_tests` — 2 passed (CPU), 6 ignored (GPU — Layer B oracle tests skip without `--features cuda --ignored`) +- `SQLX_OFFLINE=true cargo test -p ml --test sp13_layer_b_oracle_tests fingerprint_bumped_from_pre_b1_1a` — sister regression passes (the SP14 fingerprint bump does not invalidate SP13's pre-B1.1a invariant) + +### Wire status + +- **Weight shape**: yes — flat buffer, Xavier (rows, cols), zero-init, spectral-norm descriptor, and smoke fixtures all agree on `[adv_h, SH2 + 1]`. Bias `b_b0fc` (index 18) unchanged — biases are per-output, not per-input. +- **Fingerprint**: yes — `LAYOUT_FINGERPRINT_CURRENT` bumps via the `_AUX1` rename; `check_layout_fingerprint` (gpu_dqn_trainer.rs:21259) will refuse any pre-SP14 checkpoint at load. +- **Forward dispatch**: NOT WIRED. Branch-0's `forward_branch_q_head` still computes `(fc_input, fc_k) = (h_s2_ptr, self.shared_h2)` (batched_forward.rs:1991). Until B.9 lands the concat → SGEMM consumer, the new column reads as ignored padding (safe because zero-init + GPU-only smoke tests are skipped on CPU CI + fingerprint bump invalidates pre-SP14 checkpoints). +- **Reverse dependencies**: `gpu_weights::extract_dueling_weights` reads sizes from the `GpuVarStore` itself — old python checkpoints would expose a SH2-wide `advantage_fc.weight`, which the bumped fingerprint blocks before the extraction reaches that path.