Files
foxhunt/docs
jgrusewski 7d10ea8b3e feat(sp13): B1.1a — K=1→2 + softmax CE kernel rewrites + struct flips
Flips the aux next-bar head from K=1 MSE regression to K=2 softmax
cross-entropy classification. Kernel ABIs, struct fields, partial-buf
shapes, and per-step ISV producers all migrate atomically; the producer
that fills `aux_sign_labels` with real -1/0/1 from the price trajectory
lands separately in B1.1b.

Why split B1.1a from B1.1b: the original B1 brief was decomposed (B1.0
+ B1.1) after six full-B1 dispatches confirmed agent-session capacity
is the bottleneck, not cascade understanding. B1.1 itself is now further
split into B1.1a (kernel/struct/test cascade — this commit) and B1.1b
(producer kernel + replay direct path + experience collector hoist +
remaining tests + Smoke A). B1.1a is contract-consistent atomic
kernel-side; B1.1b lands the producer + smoke.

Why labels stay zero-init: B1.1a is producer-less by design. The
`aux_nb_label_buf: CudaSlice<i32>` is `alloc_zeros<i32>` so every
sample receives label 0 ("down"). The model converges on "predict
class 0 (down) everywhere" until B1.1b lands the producer kernel that
fills real -1/0/1 from the 30-bar price trajectory. This degraded
training behavior is intentional and known — the cascade is internally
consistent (every consumer of K-flip / softmax tile / CE / i32 label
migrates atomically per `feedback_no_partial_refactor`); the labels are
placeholder. Local unit tests (CE correctness, dir_acc correctness,
isv_tanh correctness, fingerprint bump) validate B1.1a in isolation;
no L40S smoke runs between B1.1a and B1.1b.

Four contracts (atomic in this commit):
  1. K_NB flip 1 → 2: AUX_NEXT_BAR_K constant, compute_param_sizes
     ([121]/[122] grow), fingerprint seed rename
     (PARAM_AUX_NB_W2/B2 → PARAM_AUX_NB_W2_K2/B2_K2 — bumps the hash),
     forward + backward kernels, partial-buf allocs (nb_w2 [B,H]→[B,K,H],
     nb_b2 [B]→[B,K]), saxpy spec table, max_aux_tensor_len,
     aux_nb_pred_buf renamed to aux_nb_logits_buf per
     feedback_no_legacy_aliases.
  2. Softmax tile: aux_next_bar_forward writes [B, K] softmax via
     in-kernel stable softmax (max-shift form, K=2 single-thread fanout
     mirrors regime kernel); 4 consumers read the tile (loss, backward,
     dir-acc, isv-tanh). NEW field aux_nb_softmax_buf [B, K].
  3. MSE → CE: aux_next_bar_loss_reduce reads softmax + i32 labels,
     masks -1, divides by B_valid (mean-over-valid-rows), writes loss +
     B_valid scalar. aux_next_bar_backward reads B_valid so loss + grad
     share the same divisor — derivatives of the same scalar function.
     NEW field aux_nb_valid_count_buf [1]. All-skip batch produces
     loss = 0 (no NaN; fmaxf(valid, 1) divisor) and zero gradients.
     Numerical floor 1e-30 prevents -log(0) = +inf in extreme-logit path.
  4. i32 label dtype: aux_nb_label_buf flipped f32 → i32; -1 mask
     sentinel handled across loss + backward + dir-acc + isv-tanh.
     The strided_gather of next_states[:, 0] retired entirely.

Cascade (atomic per feedback_no_partial_refactor):
- aux_heads_kernel.cu: aux_next_bar_forward gains K + softmax tile
  output (via in-kernel stable softmax); aux_next_bar_loss_reduce
  ABI flipped (softmax + i32 labels, mean-over-valid CE,
  valid_count_out); aux_next_bar_backward ABI flipped (softmax +
  i32 labels + valid_count, K-fanout d_logits, masked rows zero
  across the K-vector)
- aux_dir_acc_reduce_kernel.cu: read softmax + i32 labels, argmax
  over K, output grew 3 → 6 floats (added n_down/n_up/n_skip);
  shmem 4 → 6 int arrays
- aux_pred_to_isv_tanh_kernel.cu: read softmax tile, compute
  mean(softmax[:, 1] - softmax[:, 0]); tanh transcend retired
  (structural [-1, +1] bound via softmax components per
  pearl_bounded_modifier_outputs_require_structural_activation)
- gpu_aux_heads.rs: AUX_NEXT_BAR_K 1 → 2; forward_next_bar gains K +
  logits_out + softmax_out args; next_bar_loss_reduce gains K +
  valid_count_out; backward_next_bar gains softmax_in + labels_i32_in
  + valid_count_in + K
- gpu_dqn_trainer.rs: compute_param_sizes ([121]/[122]); fingerprint
  seed rename (W2/B2 → W2_K2/B2_K2); struct fields (logits, softmax,
  i32 label, valid_count); aux_dir_acc_buf 3 → 6 floats; partial-buf
  allocs grow; max_aux_tensor_len extended; saxpy spec table updated;
  orchestrator launchers (launch_aux_dir_acc_reduce,
  launch_aux_pred_to_isv_tanh, launch_sp13_aux_dir_metrics) gain K
  arg; strided_gather block deleted entirely
- training_loop.rs: aux_b1_diag HEALTH_DIAG line reads
  aux_dir_acc_buf [3..6] for n_down / n_up / n_skip + mask_frac;
  doc comment update for the per-step aux dir-metrics block
- tests/sp13_phase0_oracle_tests.rs: 6 dir_acc + 3 isv_tanh tests
  rewritten in-place to new ABI (no shadow tests, per
  feedback_no_legacy_aliases)
- tests/sp13_layer_b_oracle_tests.rs (NEW): 11 B1.1a tests — 5 CE
  loss/backward (single-row, batch-mixed, all-skip-NaN, backward
  single-row, backward batch-mixed); 2 dir_acc (handcrafted argmax,
  all-skip NaN-safe); 2 isv_tanh (bounded fuzz, mean handcrafted);
  2 layout regression (fingerprint bump, HEALTH_DIAG snap stable)
- docs/dqn-wire-up-audit.md: B1.1a section

Hard rules upheld:
- feedback_no_partial_refactor: every consumer of K-flip / softmax tile
  / CE / i32 labels migrates atomically — kernels + orchestrators +
  struct + diag + existing oracle tests
- feedback_no_atomicadd: block tree-reduce only; CE loss reduce uses
  2 parallel partial-reduction strips; CE backward uses existing
  per-sample partial → final aux_param_grad_reduce pattern
- feedback_cpu_is_read_only: aux_nb_label_buf is GPU-resident
  CudaSlice<i32>; HEALTH_DIAG aux_b1_diag reads via mapped-pinned
  aux_dir_acc_buf (no DtoH)
- feedback_no_stubs: every new buffer + kernel arg wired through to
  a real consumer; CE forward / loss / backward chain executes
  end-to-end against placeholder labels (degraded behavior, not stub)
- feedback_no_legacy_aliases: aux_nb_pred_buf renamed in-place
  (no shim); PARAM_AUX_NB_W2/B2 renamed to _W2_K2/_B2_K2 in seed
  (no _DEPRECATED alias); 9 existing oracle tests rewritten in-place
- feedback_no_cpu_test_fallbacks: 9 GPU tests gated #[ignore]; 2
  layout-regression tests are CPU-only (pub const + size_of)
- feedback_no_htod_htoh_only_mapped_pinned: every CPU↔GPU buffer
  in tests + production is MappedF32Buffer / MappedI32Buffer
- feedback_isv_for_adaptive_bounds: no hardcoded thresholds added
  (1e-30 numerical floor on log is stability epsilon, not tunable)
- feedback_trust_code_not_docs: 8/8 Phase 0 anchors verified at
  HEAD 75e94858c before editing
- pearl_bounded_modifier_outputs_require_structural_activation:
  softmax IS the structural activation; runtime tanh squash retired

Build: cargo check --workspace --tests clean.
Tests:
  - snapshot_size_is_stable passes at 149*4=596 bytes (B1.1a
    doesn't touch HEALTH_DIAG snap-words)
  - fingerprint_bumped_from_pre_b1_1a passes (proves W2/B2 rename
    flipped seed hash)
  - health_diag_snap_size_stable_at_149_floats passes
  - 9 GPU oracle tests in sp13_layer_b_oracle_tests pass on local
    RTX 3050 Ti (B=1/4)
  - 14 tests in sp13_phase0_oracle_tests pass (6 dir_acc + 3
    isv_tanh rewrites + 5 unchanged hold/EMA tests)

Pre-existing test failures (14) on `cargo test -p ml --lib` are
environmental (OFI test data missing, etc.) and reproduce identically
at HEAD 75e94858c (B1.0) — verified via git stash round-trip.

Net delta: 9 files (8 source + 1 audit doc), +1039 / −539 LOC.

Next: B1.1b — producer kernel + replay direct-path 8th gather +
experience collector hoist + remaining 7 tests (6 producer + 1
end-to-end round-trip) + Smoke A on L40S.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 14:09:48 +02:00
..