diff --git a/docs/superpowers/specs/2026-05-05-sp14-aux-q-wire-earned-gradient-flow.md b/docs/superpowers/specs/2026-05-05-sp14-aux-q-wire-earned-gradient-flow.md index e5f7e2346..631e95f8d 100644 --- a/docs/superpowers/specs/2026-05-05-sp14-aux-q-wire-earned-gradient-flow.md +++ b/docs/superpowers/specs/2026-05-05-sp14-aux-q-wire-earned-gradient-flow.md @@ -1,27 +1,35 @@ # SP14 — Aux→Q Wire + Earned Gradient Flow Pearl -**Status:** Design v2 (post-critical-review; awaiting final user approval) +**Status:** Design v3 (post-second-critical-review; awaiting final user approval) **Date:** 2026-05-05 **Author:** Brainstormed in session post-Smoke A -**Parent commit:** `eaf4adcb9` (v1 spec) → v2 with corrections +**Parent commit:** `d243a6f08` (v2 spec) → v3 with two further corrections **Depends on:** SP13 chain (B0 → B0.1 → B1.0 → B1.1a → B1.1b) all landed at `6657e5626` **Branch target:** `sp11-reward-as-controlled-subsystem` -## v1 → v2 changelog +## Changelog + +### v2 → v3 (this revision; addresses two further user-flagged issues) + +1. **Direction Q-head is K=4, not K=3.** v2 trusted the stale comment at `gpu_dqn_trainer.rs:2438` ("default: 3 — Short/Flat/Long") but `state_layout.cuh:123-126` is the authoritative source: `DIR_SHORT=0, DIR_HOLD=1, DIR_LONG=2, DIR_FLAT=3`. Production callsites (`gpu_iqn_head.rs:123`, `gpu_backtest_evaluator.rs:144`, all smoke configs) all set `branch_0_size: 4`. SP13 added `DIR_HOLD` as a separate fourth action when Hold-pricing landed; the config default comment was never updated. v3 corrects: + - B.1 action-space context: 4 actions with Hold and Flat both non-committal + - B.2.3 q_disagreement mapping: K=4↔K=2 with both Hold and Flat masked + - This is exactly the `feedback_trust_code_not_docs` failure mode — a single stale comment would have silently corrupted the disagreement signal +2. **Adaptive β rate limiter (was structural).** v2 hardcoded `β=0.9`. Per `feedback_isv_for_adaptive_bounds`, β is now signal-driven from variance of `α_grad_raw`, mirroring the k_aux/k_q variance-driven steepness pattern. Adds 2 ISV slots: `ALPHA_GRAD_RAW_VARIANCE_EMA_INDEX` and `BETA_RATE_LIMITER_ADAPTIVE_INDEX`. Net 11→13 slots. + +### v1 → v2 Critical review against current code state at `eaf4adcb9` surfaced 8 corrections: -1. **Action-class asymmetry corrected.** Direction Q-head emits K=3 (Short/Flat/Long), not K=4. Aux head emits K=2 (down/up). Gate 2 q_disagreement signal now uses K=3↔K=2 mapping with Flat masking (B.2.3). The 4-action `ExposureLevel` enum from SP13 is the *combined exposure decision*, not the direction Q-head output. +1. **Action-class asymmetry corrected** (further corrected in v3 to K=4). 2. **Forward launch order constraint added** (B.1). Wire creates new serial dep: aux forward must complete before direction Q-head forward. Cited `pearl_canary_input_freshness_launch_order`. 3. **Adaptive sigmoid k formula fixed** (B.2.5). v1 had unreachable `k_max = 50` because formula caps `k ≤ k_base`. v2 uses `max(...)` instead of `clip(..., k_min, k_max)`, with `k_max = k_base` implicit. `k_base_aux = 20`, `k_base_q = 15`. -4. **α_grad rate limiter promoted from nice-to-have to v1** (new B.2.8). Schmitt state-flip introduces sigmoid discontinuity (~0.15-0.4 jump). Rate limiter (β=0.9 EMA smoothing) damps the transition. Added `ALPHA_GRAD_SMOOTHED_INDEX` slot. -5. **q_disagreement_baseline drop** (B.2.3). v1 had adaptive baseline as long-EMA — risk of feedback loop. v2 uses structural 0.5 (analytic K=3-with-Flat-masked random alignment). Drop `Q_DISAGREEMENT_BASELINE_INDEX`. -6. **Backward gradient scaling clarified** (B.4). α_grad scales `dL/dx` (input gradient flowing back to aux), NOT `dL/dW` (Q-head's weight gradient). Q-head learns to use the wire freely; gate only controls upstream gradient flow. +4. **α_grad rate limiter promoted from nice-to-have to v1** (new B.2.8). Schmitt state-flip introduces sigmoid discontinuity (~0.15-0.4 jump). Rate limiter EMA smoothing damps the transition. v3 makes β ISV-adaptive. +5. **q_disagreement_baseline drop** (B.2.3). v1 had adaptive baseline as long-EMA — risk of feedback loop. v2/v3 uses structural 0.5 (analytic K-with-Hold-Flat-masked random alignment). +6. **Backward gradient scaling clarified** (B.4). α_grad scales `dL/dx` (input gradient flowing back to aux), NOT `dL/dW` (Q-head's weight gradient). 7. **4 hard rules added** (B.6): `feedback_no_hiding`, `feedback_no_htod_htoh_only_mapped_pinned`, `feedback_kill_runs_on_anomaly_quickly`, `pearl_canary_input_freshness_launch_order`. 8. **Smoke A2 explicit kill criteria table added** (B.7). -Net slot count unchanged (11), composition shifted: dropped baseline, added smoothed. - --- ## Goal @@ -91,9 +99,15 @@ This is the architectural piece. Two coupled changes that must land atomically p **Slot 375 fate:** kept as-is (batch-mean scalar) for HEALTH_DIAG observability. The existing `aux_pred_to_isv_tanh_kernel` continues to write `mean(softmax[1] - softmax[0])` for diagnostic purposes. The wire bypasses slot 375 and reads `aux_nb_softmax_buf` directly per-bar — slot 375 becomes pure diagnostic, not a critical data path. -**Q-head SGEMM weight matrix:** the direction Q-head's first FC layer (`w_b0fc`-equivalent — implementer to identify exact tensor index in `compute_param_sizes()` against current code) grows its INPUT dimension from `SH2` to `SH2 + 1`. Output dim (advantage hidden / direction logits) unchanged. Production `branch_0_size = 3` (Short/Flat/Long per `gpu_dqn_trainer.rs:2438`) — output dim is unaffected by the wire. The new row of weights is initialized to **zero** at trainer construction. Rationale: model starts ignoring the new input (no perturbation to existing learned behavior), then learns to use it through gradient descent. Xavier-init would inject noise on day 0 that the trunk would have to learn to denoise. +**Q-head SGEMM weight matrix:** the direction Q-head's first FC layer (`w_b0fc`-equivalent — implementer to identify exact tensor index in `compute_param_sizes()` against current code) grows its INPUT dimension from `SH2` to `SH2 + 1`. Output dim (advantage hidden / direction logits) unchanged. Production `branch_0_size = 4` (Short/Hold/Long/Flat per `state_layout.cuh:123-126` — **the comment at `gpu_dqn_trainer.rs:2438` saying "default: 3" is STALE pre-SP13**; verified against production callsites in `gpu_iqn_head.rs:123`, `gpu_backtest_evaluator.rs:144`, smoke configs). The new row of weights is initialized to **zero** at trainer construction. Rationale: model starts ignoring the new input (no perturbation to existing learned behavior), then learns to use it through gradient descent. Xavier-init would inject noise on day 0 that the trunk would have to learn to denoise. -**Action-space context for the wire's downstream meaning:** the direction Q-head emits 3 actions: Short (0), Flat (1), Long (2). The aux head emits 2 classes: down (0), up (1) [plus -1 skip mask]. The wire's signal `softmax[1] - softmax[0]` is positive when aux thinks "up" (favoring Long), negative when aux thinks "down" (favoring Short). Flat is the abstention action — model picks it when neither directional commitment seems profitable. The wire makes aux's directional confidence available as input; the Q-head learns whether to trust it. +**Action-space context for the wire's downstream meaning:** the direction Q-head emits **4 actions**, indexed per `state_layout.cuh`: +- `DIR_SHORT = 0` — open/maintain short position +- `DIR_HOLD = 1` — keep current position (no-op; SP13 Hold-pricing applies here) +- `DIR_LONG = 2` — open/maintain long position +- `DIR_FLAT = 3` — close all positions to zero + +The aux head emits 2 classes: down (0), up (1) [plus -1 skip mask]. The wire's signal `softmax[1] - softmax[0]` is positive when aux thinks "up" (favoring Long), negative when aux thinks "down" (favoring Short). Hold and Flat are non-committal actions — Hold keeps existing position (no new directional commitment), Flat exits to zero (also not a new commitment). The wire makes aux's directional confidence available as input; the Q-head learns whether to trust it for the Short/Long commitment decision. **Forward launch order constraint** (per `pearl_canary_input_freshness_launch_order`): the wire creates a new serial dependency in the per-step graph capture. The aux head forward (`aux_next_bar_forward` writing `aux_nb_softmax_buf [B, K=2]`) must complete BEFORE the direction Q-head's first FC reads the softmax diff. Pre-SP14, aux and direction Q-head ran in parallel from `h_s2`; post-SP14, they're serial: trunk → aux forward → direction Q-head forward. Implementer must order the two launches in graph capture; failure to do so produces a stale-input bug (direction Q-head reads previous step's softmax, exactly the failure mode the canary pearl warns about). @@ -137,31 +151,37 @@ where: **Driver signals:** `q_disagreement_short_ema` (NEW ISV slot, fast EMA α=0.3) and `q_disagreement_long_ema` (NEW ISV slot, slow EMA α=0.05 for HEALTH_DIAG drift visibility). Baseline is **structural 0.5** — not an ISV slot. Long-EMA-as-baseline was considered and rejected because adaptive baseline drift creates a feedback loop (disagreement drops → baseline drops → gate sees lower values as "above baseline" → opens spuriously). -**K=3 ↔ K=2 mapping** (the action-class asymmetry): aux head emits K=2 (down=0, up=1). Direction Q-head emits K=3 (Short=0, Flat=1, Long=2). Naive `argmax(Q) != argmax(aux)` doesn't apply across different cardinality. The disagreement signal masks Flat picks (model declined to commit; no signal): +**K=4 ↔ K=2 mapping** (the action-class asymmetry): aux head emits K=2 (down=0, up=1). Direction Q-head emits K=4 (`DIR_SHORT=0`, `DIR_HOLD=1`, `DIR_LONG=2`, `DIR_FLAT=3` per `state_layout.cuh:123-126`). Naive `argmax(Q) != argmax(aux)` doesn't apply across different cardinality. The disagreement signal masks BOTH Hold and Flat picks — neither represents a NEW directional commitment (Hold = keep current position; Flat = exit to zero): ``` For each row b in batch: - aux_class = argmax(aux_softmax[b]) # 0 or 1 - q_dir_class = argmax(Q_dir_logits[b]) # 0, 1, or 2 + aux_class = argmax(aux_softmax[b]) # 0 (down) or 1 (up) + q_dir_class = argmax(Q_dir_logits[b]) # 0, 1, 2, or 3 - if q_dir_class == 1 (Flat): # neutral; model declined - q_disagreement[b] = (skip from EMA — mask out) + if q_dir_class == DIR_HOLD or q_dir_class == DIR_FLAT: # 1 or 3 + # neutral; no new directional commitment; mask out + q_disagreement[b] = 0.0 contribution_count = 0 else: + # Q picked DIR_SHORT (0) or DIR_LONG (2) # Map Q to aux space: Short=0 ↔ down=0, Long=2 ↔ up=1 - q_dir_mapped = 0 if q_dir_class == 0 else 1 + q_dir_mapped = 0 if q_dir_class == DIR_SHORT else 1 q_disagreement[b] = (q_dir_mapped != aux_class) ? 1.0 : 0.0 contribution_count = 1 q_disagreement_batch_mean = sum(q_disagreement) / max(1, sum(contribution_count)) ``` -Then EMA over steps via standard Pearl-A bootstrap pattern. Skip-rows from Flat picks don't pollute the EMA — they're treated as "no information about disagreement", not "no disagreement." +Then EMA over steps via standard Pearl-A bootstrap pattern. Skip-rows from Hold/Flat picks don't pollute the EMA — they're treated as "no information about directional disagreement", not "no disagreement." + +**Note on Hold semantics:** Hold could be argued as a *soft* directional commitment ("I think the current position is right"), but resolving that requires the current position state which the disagreement kernel doesn't have access to. v1 keeps the simple "no new commitment → mask" rule. If post-30-epoch validation shows the disagreement signal is too noisy because of frequent Hold picks during training, v2 could thread the current-position bit into the disagreement kernel. **Behavior:** opens when persistent disagreement is meaningfully above the structural 0.5 baseline. Specifically: -- `q_disagreement_short_ema > 0.5 + ε` → Q-head is fighting aux signal (with Flat masked out, this means the model is actively picking Short when aux says up, or Long when aux says down) → co-training would help align them +- `q_disagreement_short_ema > 0.5 + ε` → Q-head is fighting aux signal (with Hold/Flat masked out, this means the model is actively picking Short when aux says up, or Long when aux says down) → co-training would help align them - `q_disagreement_short_ema ≤ 0.5` → Q-head agreement is at-or-better-than chance → no benefit from gradient flow +**Edge case — all rows masked:** if the entire batch picks Hold/Flat (early training abstention), `contribution_count = 0` everywhere. The `max(1, ...)` denominator prevents division by zero; q_disagreement stays at sentinel 0.5 via Pearl-A bootstrap (first observation). Gate 2 stays closed because raw signal hasn't moved away from baseline. This is correct behavior — "no committed directional decisions to evaluate" → "no co-training signal to feed." + **Argmax source:** use the deterministic `argmax(Q_dir_logits)` at both training and eval time — i.e., the Q-head's preferred direction *before* Thompson sampling. This tracks the Q-head's underlying directional belief; the disagreement signal should not be polluted by exploration noise. Thompson-sampled action selection diverges from argmax stochastically (especially early in training when Q values are flat), so using the post-Thompson selection would conflate two signals: directional belief and exploration. We want the belief. **Why this gate exists:** open Gate 1 alone would unlock co-training even when Q-head is already aligned with aux — wasted gradient flow. Gate 2 ensures co-training happens only when the system shows it's productive. @@ -236,42 +256,61 @@ The pathological dynamic to defend against: Q-head learns that disagreeing with This is the "smart toddler with circuit breaker" pattern: the model can't game the wire if doing so triggers a forced reset. -#### B.2.8 — α_grad rate limiter (smoothing across state transitions) +#### B.2.8 — α_grad rate limiter (ISV-driven adaptive smoothing) -The Schmitt-trigger hysteresis (B.2.4) introduces a sigmoid-argument discontinuity at the moment `gate1_open_state` flips between {0, 1}. The argument shifts by 0.06 (the hysteresis band). With `k_aux ≈ 10`, the sigmoid output jumps by ~0.15; with high `k_aux ≈ 25` (low-variance regime), the jump is ~0.4. That's a meaningful gradient discontinuity at a single step boundary. +The Schmitt-trigger hysteresis (B.2.4) introduces a sigmoid-argument discontinuity at the moment `gate1_open_state` flips between {0, 1}. The argument shifts by 0.06 (the hysteresis band). With `k_aux ≈ 10`, the sigmoid output jumps by ~0.15; with high `k_aux ≈ 20` (low-variance regime), the jump is ~0.4. That's a meaningful gradient discontinuity at a single step boundary. -**Mitigation:** exponentially-smoothed α_grad output: +**Mitigation:** exponentially-smoothed α_grad output with **ISV-driven adaptive β**: ``` α_grad_smoothed = β · α_grad_smoothed_prev + (1 − β) · α_grad_raw -where β = 0.9 (structural; ~10-step smoothing window) ``` -The kernels reading α_grad for backward gradient scaling (B.4 launch site) consume `α_grad_smoothed`, NOT `α_grad_raw`. `α_grad_raw` remains in HEALTH_DIAG so we can see both the raw decision and the smoothed actuation. +Per `feedback_isv_for_adaptive_bounds`, β is signal-driven, not a hardcoded constant. The driver is the variance of `α_grad_raw` itself — the same Welford pattern used for k_aux/k_q in B.2.5: -**Why β = 0.9:** ten-step smoothing window means a state flip is absorbed over ~10 backward passes, with α_grad transitioning roughly linearly. Long enough to dampen the discontinuity; short enough that the gate's directional intent (open vs closed) still expresses promptly. β is structural — not adaptive — because rate-limiter behavior should be consistent regardless of signal regime. +``` +β = clip(β_base + variance_alpha_raw_ema / variance_ref_alpha, [β_base, β_max]) +``` -**Per-fold reset:** `α_grad_smoothed` resets to 0 at fold boundary via state-reset-registry. Fresh fold starts with closed gate; the smoothing kicks in only when gate transitions occur during training. +- **Welford EMA variance** of `α_grad_raw` updates per-step (NEW ISV slot `ALPHA_GRAD_RAW_VARIANCE_EMA_INDEX`) +- Stable α_grad_raw (low variance) → β = β_base (light smoothing; preserves gate's directional intent) +- Volatile α_grad_raw (high variance from frequent state flips or noisy sigmoids) → β → β_max (heavy smoothing dampens jitter) +- At variance = 0 (sentinel/cold start), β = β_base +- At variance = variance_ref_alpha, β midway through range +- At variance >> variance_ref_alpha, β saturates at β_max + +The kernels reading α_grad for backward gradient scaling (B.4 launch site) consume `α_grad_smoothed`, NOT `α_grad_raw`. Both emit to HEALTH_DIAG (and adaptive β too) so we can see the raw decision, the actuation, and the rate-limiter's response. + +**Bounds (structural numerical-stability anchors):** +- `β_base = 0.5` (light smoothing baseline; ~2-step half-life when α_grad_raw is stable) +- `β_max = 0.95` (heavy smoothing; ~20-step half-life under volatile signal) +- `variance_ref_alpha = 0.01` (1% variance threshold for the adaptive transition) + +**Adaptive β slot:** `BETA_RATE_LIMITER_ADAPTIVE_INDEX` (NEW) — current β value, written by `alpha_grad_compute_kernel`, read by HEALTH_DIAG only (the smoothing math uses β inline). Keeps the pearl pattern consistent (k_aux and k_q have their own ISV slots; β joins them). + +**Per-fold reset:** `α_grad_smoothed` resets to 0 at fold boundary via state-reset-registry. Variance EMA resets to 0 (sentinel) → initial β = β_base. Fresh fold starts with closed gate and light smoothing; both adapt as signal emerges. ### B.3 — ISV slot allocations -11 new ISV slots required: +13 new ISV slots required: | Slot name | Purpose | Reset at fold? | |---|---|---| -| `Q_DISAGREEMENT_SHORT_EMA_INDEX` | Fast EMA (α=0.3) of per-step disagreement (Flat-masked) | Yes (sentinel 0.5) | +| `Q_DISAGREEMENT_SHORT_EMA_INDEX` | Fast EMA (α=0.3) of per-step disagreement (Hold/Flat-masked) | Yes (sentinel 0.5) | | `Q_DISAGREEMENT_LONG_EMA_INDEX` | Slow EMA (α=0.05) — HEALTH_DIAG drift visibility only, NOT used as adaptive baseline | Yes (sentinel 0.5) | | `K_AUX_ADAPTIVE_INDEX` | Current `k_aux` value | Yes (sentinel `k_base_aux`) | | `K_Q_ADAPTIVE_INDEX` | Current `k_q` value | Yes (sentinel `k_base_q`) | +| `BETA_RATE_LIMITER_ADAPTIVE_INDEX` | Current β value for α_grad rate limiter | Yes (sentinel `β_base`) | | `AUX_DIR_ACC_VARIANCE_EMA_INDEX` | Welford variance of aux_dir_acc | Yes (sentinel 0.0 → initial k = k_base) | | `Q_DISAGREEMENT_VARIANCE_EMA_INDEX` | Welford variance of disagreement | Yes (sentinel 0.0 → initial k = k_base) | +| `ALPHA_GRAD_RAW_VARIANCE_EMA_INDEX` | Welford variance of α_grad_raw (drives adaptive β) | Yes (sentinel 0.0 → initial β = β_base) | | `GATE1_OPEN_STATE_INDEX` | Schmitt-trigger persistent state | Yes (0 — closed) | | `ALPHA_GRAD_RAW_INDEX` | Raw computed α_grad value (HEALTH_DIAG visibility) | Yes (0) | | `ALPHA_GRAD_SMOOTHED_INDEX` | Rate-limited α_grad consumed by backward path (B.2.8) | Yes (0) | | `AUX_DIR_ACC_POST_OPEN_MIN_INDEX` | Anti-gradient-hacking detection | Yes (1.0 — no min observed) | | `GRADIENT_HACK_LOCKOUT_REMAINING_INDEX` | Circuit-breaker epoch counter | Yes (0 — no lockout) | -**Baseline for Gate 2 is structural 0.5** — not an ISV slot. The K=2-aux ↔ K=3-Q-with-Flat-masked random alignment at random init is exactly 0.5; this is the analytic baseline. Long-EMA-as-baseline was rejected (feedback loop risk; see B.2.3). +**Baseline for Gate 2 is structural 0.5** — not an ISV slot. The K=2-aux ↔ K=4-Q-with-Hold/Flat-masked random alignment at random init is 0.5; this is the analytic baseline (when Q picks Short or Long uniformly, half the time it agrees with aux's binary prediction). Long-EMA-as-baseline was rejected (feedback loop risk; see B.2.3). All slots register in `state_reset_registry.rs` with appropriate sentinels following the existing pattern (lines 913-922 for the two pre-existing aux_dir_acc EMAs are the canonical template). Slot indices to be allocated in `sp14_isv_slots.rs` (NEW file; mirrors `sp13_isv_slots.rs` pattern). @@ -280,7 +319,7 @@ All slots register in `state_reset_registry.rs` with appropriate sentinels follo Three new GPU kernels: 1. **`q_disagreement_update_kernel`** — per-step compute K=3↔K=2 mapped argmax mismatch (B.2.3 logic, with Flat masked) from `Q_dir_logits` and `aux_nb_softmax_buf`, EMA update for short (α=0.3) and long (α=0.05), Welford variance update. Block tree-reduce per `feedback_no_atomicadd`; tracks both numerator (sum of disagreements) and denominator (count of non-Flat picks) separately, then divides at the end. ~100 LOC. -2. **`alpha_grad_compute_kernel`** — read all driver signals, evaluate Schmitt-trigger logic for Gate 1 (B.2.4), compute adaptive `k_aux` and `k_q` from variance EMAs (B.2.5), evaluate both sigmoids, multiply with warmup gate (B.2.6), write `ALPHA_GRAD_RAW_INDEX`. Then apply rate-limiter (B.2.8): `α_grad_smoothed = β · α_grad_smoothed_prev + (1−β) · α_grad_raw`, write `ALPHA_GRAD_SMOOTHED_INDEX`. ~80 LOC. +2. **`alpha_grad_compute_kernel`** — read all driver signals, evaluate Schmitt-trigger logic for Gate 1 (B.2.4), compute adaptive `k_aux` and `k_q` from variance EMAs (B.2.5), evaluate both sigmoids, multiply with warmup gate (B.2.6), write `ALPHA_GRAD_RAW_INDEX`. Then update Welford variance of α_grad_raw → compute adaptive `β` (B.2.8) → apply rate-limiter `α_grad_smoothed = β · α_grad_smoothed_prev + (1−β) · α_grad_raw`, write `ALPHA_GRAD_SMOOTHED_INDEX` and `BETA_RATE_LIMITER_ADAPTIVE_INDEX`. Three Welford variance EMAs total (aux_dir_acc, q_disagreement, α_grad_raw); each is associative + parallelizable (block tree-reduce). ~100 LOC. 3. **`gradient_hack_detect_kernel`** — read aux_dir_acc, gate1_open_state, q_disagreement, decide lockout state, write to `GRADIENT_HACK_LOCKOUT_REMAINING_INDEX` and force-clear `GATE1_OPEN_STATE_INDEX` if triggered. ~40 LOC. Plus modifications to: @@ -293,13 +332,14 @@ Plus modifications to: New per-epoch `pearl_egf_diag` line: ``` -pearl_egf_diag α_smoothed=0.42 α_raw=0.45 gate1=0.92 gate2=0.49 k_aux=15.3 k_q=8.7 +pearl_egf_diag α_smoothed=0.42 α_raw=0.45 β=0.62 gate1=0.92 gate2=0.49 + k_aux=15.3 k_q=8.7 var_aux=0.008 var_q=0.024 var_alpha=0.018 q_dis_short=0.523 q_dis_long=0.498 gate1_state=open aux_post_open_min=0.55 lockout_remaining=0 warmup=1.0 ``` -Both `α_raw` and `α_smoothed` emit so the rate limiter's effect is visible. `q_dis_long` is included even though it's not used as baseline — it's there to track whether disagreement is drifting (training-side directional alignment improving over folds). Without these emit fields, diagnosing α_grad behavior in the smoke logs is forensic-only. +All three adaptive scalars emit (`α_smoothed`, `α_raw`, `β`, `k_aux`, `k_q`) plus their driving variances (`var_aux`, `var_q`, `var_alpha`) so the chain from variance → steepness/smoothing → output is fully observable. `q_dis_long` is included even though it's not used as baseline — it's there to track whether disagreement is drifting (training-side directional alignment improving over folds). Without these emit fields, diagnosing α_grad behavior in the smoke logs is forensic-only. ### B.6 — Hard rules upheld @@ -357,9 +397,9 @@ Both `α_raw` and `α_smoothed` emit so the rate limiter's effect is visible. `q | Component | LOC | |---|---| -| 11 new ISV slots + constants in `sp14_isv_slots.rs` | ~80 | -| `q_disagreement_update_kernel` (NEW; K=3↔K=2 mapping with Flat masking) | ~100 | -| `alpha_grad_compute_kernel` (NEW; includes Schmitt + adaptive k + rate limiter) | ~80 | +| 13 new ISV slots + constants in `sp14_isv_slots.rs` | ~95 | +| `q_disagreement_update_kernel` (NEW; K=4↔K=2 mapping with Hold/Flat masking) | ~100 | +| `alpha_grad_compute_kernel` (NEW; Schmitt + adaptive k + adaptive β rate limiter + 3 Welford variances) | ~100 | | `gradient_hack_detect_kernel` (NEW) | ~40 | | Direction Q-head forward modification (in_dim+1, concat reading aux_nb_softmax_buf) | ~100 | | Direction Q-head backward gradient scaling (dL/dx scaling, NOT dL/dW) | ~30 |