feat(sp21): T3.1+T3.2 ISV defrost — wr_ema + hold_pct_ema (atomic)

SP21 Tier-3 foundation: defrost two production-frozen ISV slots that
pinned at 0 across every observed training epoch (d7bj7, xmd6b). Both
bugs in the same aggregator kernel, same structural shape: per-step
binary majority-vote indicators feeding fractional EMAs.

T3.1 — WR_EMA defrost (sp20_aggregate_inputs_kernel.cu):
  - was: is_win_out = (2 * wins_count >= closed_count) ? 1 : 0
         binary "majority won this step" indicator
  - now: win_fraction_out = (float)wins_count / (float)closed_count
         fractional in [0, 1]
  - With actual val WR≈0.46, the binary signal was 0 most of the time,
    pinning WR_EMA at 0 across 30+ epochs in xmd6b. EMA now converges
    to population win rate.

T3.2 — HOLD_PCT_EMA defrost (same kernel, same pattern):
  - was: action_is_hold_out = (hold_count * 2 > n_envs) ? 1 : 0
         strict-majority indicator
  - now: hold_fraction_out = (float)hold_count / (float)n_envs
  - Cascade: HOLD_COST_SCALE controller compared hold_pct_ema=0 to
    tgt±0.05, always saw < lower, ramped × 0.95 → clamped at floor
    0.01 (the hold_cost_scale=0.0100 observation in d7bj7 logs).
    T3.5 expected to cascade-fix in next training run.
  - HOLD_REWARD_EMA gate preserves strict-majority semantic via
    hold_fraction > 0.5f test in the consumer kernel.

Atomic across struct fields, kernel logic, Rust mirror, byte
serialization, doc tables, and 4 test files (per
feedback_no_partial_refactor):
  - sp20_aggregate_inputs_kernel.cu (struct + 2 computations)
  - sp20_emas_compute_kernel.cu (struct + reader + gate)
  - sp20_aggregate_inputs.rs (doc table)
  - sp20_emas_compute.rs (Rust struct + serialize + 3 tests)
  - sp20_aggregate_inputs_test.rs (reader sig + 4 test assertions)
  - sp20_emas_compute_test.rs (5 struct literal updates)
  - sp20_phase1_4_wireup_test.rs (HOLD_PCT_EMA expected 0.625)

Verification:
  - cargo check -p ml --tests: passes (warnings only)
  - cargo test -p ml --lib sp20_emas: 6/6 unit tests pass

Pearl candidate: binary-majority aggregator over a fractional
underlying signal cannot serve as input to a fractional-target EMA.
The previous fix (commit 64bbbe418) addressed the per-bar vs segment
predicate at the producer site, but didn't notice the aggregator's
binarization step still collapsed the fraction to {0, 1}. Two bugs
in series, both now resolved.

Plan reference: docs/plans/2026-05-10-sp21-train-eval-coherence-isv-defrost.md
Tiers 3.3-3.6 remaining; T3.5 expected to cascade-fix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-05-10 20:13:13 +02:00
parent 6df44e4c6d
commit 1790a31b66
8 changed files with 266 additions and 111 deletions

View File

@@ -2,6 +2,70 @@
**Status:** Populated during Plan 1 Task 6 (A.5 orphan audit). Updated on every commit per Invariant 7.
## 2026-05-10 — SP21 Tier-3 foundation: T3.1+T3.2 ISV defrost (wr_ema, hold_pct_ema)
**Branch:** `sp20-aux-h-fixed` (off `6df44e4c6`).
**Commits:** 1 atomic (this commit).
Defrosts two production-frozen ISV slots that pinned-at-zero across all
observed training epochs in d7bj7/xmd6b logs. Both bugs were in the same
aggregator kernel (`sp20_aggregate_inputs_kernel.cu`), same structural
shape: per-step binary majority-vote indicators feeding fractional EMAs.
**T3.1: WR_EMA — `is_win``win_fraction` (struct field rename + type change).**
The aggregator computed `is_win_out = (2 * wins_count >= closed_count) ? 1 : 0`
— a binary "majority of closed envs won" indicator. With actual val WR
≈ 0.46 (xmd6b 30 epochs), the binary signal was 0 most of the time, and
WR_EMA → 0 by Wiener-α floor blend. The aggregator now emits
`win_fraction = wins_count / closed_count ∈ [0, 1]` and the EMA blends
over the float directly. WR_EMA will converge to the population win rate.
**T3.2: HOLD_PCT_EMA — `action_is_hold``hold_fraction` (same fix shape).**
The aggregator computed `action_is_hold_out = (hold_count * 2 > n_envs) ? 1 : 0`
— strict-majority indicator. With actual hold pct in [0.10, 0.56],
majority was rarely met, HOLD_PCT_EMA → 0. **Cascade**: the
`HOLD_COST_SCALE` controller compares `hold_pct_ema` to `tgt ± 0.05` and
ramps; with hold_pct_ema=0 it always saw `< lower` and ramped × 0.95
every step → clamped at floor 0.01 (the `hold_cost_scale = 0.0100`
observation in d7bj7 sp20_isv logs). Fixing T3.2 should cascade-fix
T3.5 — verify in next training run. HOLD_REWARD_EMA gate preserves
strict-majority semantic via `hold_fraction > 0.5f` test in the
consumer kernel.
**Affected files (atomic per `feedback_no_partial_refactor`):**
- `crates/ml/src/cuda_pipeline/sp20_aggregate_inputs_kernel.cu`
struct fields, output computations.
- `crates/ml/src/cuda_pipeline/sp20_emas_compute_kernel.cu`
struct fields, EMA reader, HOLD_REWARD_EMA gate.
- `crates/ml/src/cuda_pipeline/sp20_aggregate_inputs.rs` — doc table.
- `crates/ml/src/cuda_pipeline/sp20_emas_compute.rs` — Rust struct mirror,
byte serialization, doc table, default-sentinel test, round-trip test.
- `crates/ml/tests/sp20_aggregate_inputs_test.rs` — reader signature,
4 fractional-comparison test assertions, 2 renamed test fns.
- `crates/ml/tests/sp20_emas_compute_test.rs` — 5 struct-literal field
renames (is_win → win_fraction, action_is_hold → hold_fraction).
- `crates/ml/tests/sp20_phase1_4_wireup_test.rs` — HOLD_PCT_EMA expected
value updated 1.0 → 0.625 (5/8 envs Hold).
**Verification:**
- `cargo check -p ml --tests` — passes (warnings only).
- `cargo test -p ml --lib sp20_emas` — 6/6 unit tests pass, including
`pack_inputs_round_trips` with `win_fraction: 0.46` + `hold_fraction: 0.0`.
- GPU oracle tests gated `#[ignore = "requires GPU"]` — compile cleanly.
**Pearls applied / candidates:**
- `pearl_first_observation_bootstrap` — unchanged, both EMAs still bootstrap
from sentinel.
- `pearl_wiener_alpha_floor_for_nonstationary` — unchanged, α=0.4 floor.
- New pearl candidate: **binary-majority aggregator over a fractional
underlying signal cannot serve as input to a fractional-target EMA**.
When the EMA is supposed to converge to a population fraction, the
observation must itself be the fraction or a 0/1 sample, not a
thresholded reduction.
## 2026-05-10 — REVERT: aux_horizon_update_kernel sp20-aux-h-fixed experiment closed
Reverts the forced-H=30 diagnostic that tested the bootstrap-failure hypothesis. **Hypothesis verdict: dead.** Across two epochs of `train-d7bj7`: