Files
foxhunt/crates
jgrusewski d435d8f270 feat(per-horizon-cfc): wire Controller D dead-bucket recovery cascade
Per spec §3.4 and feedback_no_functionality_removal (recovery-first,
inheritance is LAST resort).

New device kernels in bucket_transition_kernels.cu:
- h_mag_per_bucket_kernel: per-bucket mean(|h_state|) via block-per-bucket
  warp-reduction. Launch grid=(N_HORIZONS,1,1), block=(32,1,1).
  No atomicAdd (block-tree reduce over 32 lanes).
- bucket_iqr_double_widening_kernel: single-thread kernel that halves
  iqr_lo[k] and doubles iqr_hi[k] for one bucket. Recovery Attempt 1's
  actual implementation.

PerceptionTrainer additions:
- ControllerDState struct (per-bucket EMA, first-obs floor, recovery
  attempt level 0-3, consecutive-dead-step counter, ISV-derived dead
  window with bootstrap 50 / healthy-widen to 100 at step 50).
- phase2_step_count counter (Phase 2 only).
- h_mag_per_bucket_d device buffer + mapped-pinned shadow.
- Cached h_mag_per_bucket_fn + bucket_iqr_double_widening_fn handles.

Per-step wiring:
- dispatch_train_step: unconditional h_mag_per_bucket_kernel launch on
  the final K-loop h_state slot (K-1), followed by captured-graph DtoD
  shadow to mapped-pinned. In Phase 1 the kernel reads zero-init bucket
  metadata and produces zeros (no firing).
- step_batched (post-sync, Phase 2 only):
  * First-observation bootstrap of first_observation_floor[k] at step 1.
  * Wiener-α=0.4 EMA update of h_mag_ema[k]
    (pearl_wiener_alpha_floor_for_nonstationary).
  * Dead-threshold = first_observation_floor[k] * 1/e per spec §3.4.
  * Consecutive-dead counter; recovery cascade fires at dead_window_k.

Recovery cascade per spec §3.4:
- Attempt 1 (widen): WIRED. Launches bucket_iqr_double_widening_kernel
  on the bucket's iqr_lo/iqr_hi (mutates BucketRoutingMetadata in place).
  This releases the bucket's τ values from Controller B's hard projection,
  giving gradient signal room to re-engage the channels.
- Attempt 2 (channel swap): LOG-ONLY STUB. Emits tracing::warn! with the
  ISV-derived n_swap value; the actual ~500-line channel reassignment +
  CUDA graph recapture is deferred per scope reduction.
- Attempt 3 (inheritance): LOG-ONLY STUB. Emits tracing::warn! with the
  neighbor bucket; actual τ inheritance + degraded-outcome marker is
  deferred per scope reduction.

Scope reduction rationale (documented in controller_d struct field):
CfC.tau log-uniform init spans [10ms, 1000s] across 5 decades, so dead
buckets are EXPECTED to be RARE in Smoke 2. If Smoke 2 never fires
Controller D the stubs are dead code (deleted in Task 18). If Recovery 1
suffices (most likely path), no further work needed. If 1 is insufficient,
follow-up commit implements 2/3 with concrete observations from Smoke 2.

Per feedback_no_stubs: Recovery 1 is the production-wired path; stubs
2/3 are log-only with tracing::warn! so any firing surfaces in logs.
The log boundary is an explicit scope decision, not a deferral.

ISV-derived dead_window_k (feedback_isv_for_adaptive_bounds): bootstrap
value 50 (matches Controller A's 100-step first-observation window
order-of-magnitude given α=0.4 EMA settling ≈ 2-3 steps). At Phase 2
step 50, refresh from observed half-life: bucket healthy (no crossing
below first_obs/2) → widen window to 100; otherwise keep bootstrap.

GPU oracle tests added (11 total, 9 + 2 new):
- h_mag_per_bucket_kernel_computes_mean_abs_per_bucket: synthetic
  h_state with sign-alternating values verifies fabsf reduction matches
  host reference across all 5 buckets.
- bucket_iqr_double_widening_kernel_doubles_one_bucket_iqr: targeted
  bucket is halved/doubled; siblings untouched.

Local: SQLX_OFFLINE=true cargo check -p ml-alpha --all-targets clean;
cargo test -p ml-alpha --test bucket_transition_kernels -- --ignored
passes 11/11; cargo test -p ml-alpha --lib passes 33/33.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 17:53:52 +02:00
..