feat(alpha): phase_e_kill_criteria producer kernel (slots 539-542)

Phase E.0 Task 9. Single-block, single-thread CUDA producer that writes 4
raw scalar observations into a contiguous scratch_out[0..4] block:

  scratch_out[0] → ISV[539] Q_SPREAD_EMA          (kill: ≥ 0.05)
  scratch_out[1] → ISV[540] ACTION_ENTROPY_EMA    (kill: ≥ 0.5·ln(9) ≈ 1.10)
  scratch_out[2] → ISV[541] RETURN_VS_RANDOM_EMA  (kill: ≥ 0, i.e. ≥ random)
  scratch_out[3] → ISV[542] EARLY_Q_MOVEMENT_EMA  (kill: ≥ 0.01, learned)

Downstream `apply_pearls_ad_kernel` (n_slots=4) chained on the same stream
applies Pearl A first-observation bootstrap + Pearl D Wiener-α smoothing —
composes with the canonical val_sharpe_delta_compute_kernel pattern rather
than reimplementing Wiener math (per feedback_no_cpu_compute_strict — one
Wiener implementation in the codebase, the GPU one).

Inputs:
  q_values[batch, n_actions]   most-recent Q forward output (device)
  action_counts[n_actions]     empirical action histogram (device)
  scalar_inputs[3]             [rollout_R_mean, q_init_norm, q_early_norm]
                               via mapped-pinned (host writes between rollouts)
  isv                          reads slots 547 + 548 (random baseline
                               mean/std — populated once by Task 7c,
                               TrainingPersist)

No atomicAdd (per feedback_no_atomicadd), no host branches
(per pearl_no_host_branches_in_captured_graph), no CPU compute
(per feedback_cpu_is_read_only). __threadfence_system() before exit for
the chained Pearls applicator's visibility guarantee.

Cubin produced: target/release/build/ml-*/out/phase_e_kill_criteria.cubin
(16.8 KB).

Launcher integration is Task 11 (separate commit so this task can stand
alone for cubin validation). Audit doc docs/isv-slots.md updated per
Invariant 7.
This commit is contained in:
jgrusewski
2026-05-15 14:07:41 +02:00
parent 4f71ab32ae
commit d493b729bf
3 changed files with 184 additions and 0 deletions

View File

@@ -547,3 +547,22 @@ Phase E Task 2 lands the registry entries + 7 dispatch arms (4 SoftReset diagnos
- `crates/ml/src/trainers/dqn/trainer/training_loop.rs` — 7 dispatch arms in `reset_named_state` (the 3 TrainingPersist names are not dispatched)
All 10 registry tests + the global registry coverage test pass: `cargo test -p ml --lib state_reset_registry`.
## Alpha block — Phase E Task 9 producer kernel (2026-05-15)
Phase E.1 Task 9 lands the producer for slots 539-542 (`phase_e_kill_criteria.cu`). Single-block, single-thread kernel; writes 4 raw scalar observations into `scratch_out[0..4]`, then the existing `apply_pearls_ad_kernel` (chained on the same stream, `n_slots=4`) applies Pearl A bootstrap + Pearl D Wiener-α smoothing into the four ISV slots:
- `scratch_out[0]` → ISV[539] `Q_SPREAD_EMA_INDEX` — mean over batch of `std(Q, axis=action) / |mean(Q)|`
- `scratch_out[1]` → ISV[540] `ACTION_ENTROPY_EMA_INDEX``H(empirical action distribution)` in nats
- `scratch_out[2]` → ISV[541] `RETURN_VS_RANDOM_EMA_INDEX``(rollout_R ISV[547]) / max(ISV[548], 1e-6)`
- `scratch_out[3]` → ISV[542] `EARLY_Q_MOVEMENT_EMA_INDEX``|q_early_norm q_init_norm| / max(|q_init_norm|, 1e-6)`
Kernel reads ISV[547]/ISV[548] (random baseline mean/std, populated once by Task 7c, TrainingPersist) via slot indices passed as kernel args. Composability per `val_sharpe_delta_compute_kernel` — no inline Wiener math here, the canonical `apply_pearls_ad_kernel` handles it.
**Files touched:**
- `crates/ml/src/cuda_pipeline/phase_e_kill_criteria.cu` (new) — producer kernel
- `crates/ml/build.rs` — added kernel to `kernels_with_common` list
**Cubin:** `target/release/build/ml-*/out/phase_e_kill_criteria.cubin` (~16.8 KB).
**Launcher wiring:** lands in Phase E.1 Task 11 (the DQN training loop reads `phase_e_kill_criteria_compute_kernel` from the compiled cubin and chains `apply_pearls_ad_kernel` on the same stream after it).