fixup(class-a-audit-batch-4b): invert MIN_HOLD_TEMPERATURE dir_acc → temp mapping

Post-commit kernel-formula audit of `compute_min_hold_penalty` at
trade_physics.cuh:567-577 showed the original `temp = TEMP_MIN +
(TEMP_MAX - TEMP_MIN) × skill` mapping was BACKWARDS for the
WR-plateau scenario this 8-commit chain targets.

Formula recap:
  soft_factor = deficit / (deficit + T)
  HIGH T → soft_factor → 0 → forgiving (no exit penalty)
  LOW T  → soft_factor → 1 → sharp (max exit penalty)

The deleted schedule's `START=50 → END=5` therefore meant
"permissive early, strict late" (classic explore-then-exploit), not
"permissive when committed, strict when uncertain" as the
substituted mapping assumed.

WR-plateau scenario: dir_acc ≈ 0.46-0.48 (model committed but
wrong) — the model needs a permissive (HIGH T) exit ramp to bail
out of bad committed directions, not maximum exit penalty (LOW T)
locking it into them.

Inverted the mapping using `confusion = 1 - skill`:
  - dir_acc ≈ 0.5 (random / plateau) → confusion=1 → temp=50
    (permissive, plateau exit ramp)
  - dir_acc → 1.0 (saturated skill)  → confusion=0 → temp=5
    (strict, force informed commitment)

This preserves the deleted schedule's epoch-0 anchor (T=50
cold-start) while reacting to actual realized skill instead of an
epoch-time anchor.

Files touched (atomic per `feedback_no_partial_refactor`):
- min_hold_temperature_update_kernel.cu — formula + header doctring.
- sp14_isv_slots.rs — slot-doc comment expanded with new mapping
  + fixup-rationale paragraph.
- gpu_aux_trunk.rs — `MinHoldTemperatureUpdateOps` docstring.
- gpu_dqn_trainer.rs — cubin docstring + ISV_TOTAL_DIM doc-comment.
- tests/sp14_oracle_tests.rs — Tests 3 + 4 flipped (high dir_acc
  now expects temp=TEMP_MIN; low dir_acc now expects TEMP_MAX);
  Tests 1, 2, 5 numerically unchanged (sentinel guard fires before
  the mapping; midpoint dir_acc=0.75 has skill=confusion=0.5 so
  pre/post-fixup numerics match).
- docs/dqn-wire-up-audit.md — Item 4 entry rewritten with the
  fixup rationale + mapping table updated.

Verification:
  cargo check -p ml --tests --all-targets   PASS
  cargo test sp14_audit_4b (lib)            4/4 PASS (slot layout
                                                   locks unchanged)

GPU oracle re-run deferred to next L40S smoke (kernel was rebuilt
in-place so cubin contents change; the 5 oracles encode the new
mapping and will exercise the new cubin).

Per `pearl_first_observation_bootstrap.md` (sentinel→target
replace; sentinel anchors unchanged) +
`pearl_symmetric_clamp_audit.md` (bilateral clamp on target_temp
preserved) + `feedback_no_partial_refactor.md`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-05-08 11:28:00 +02:00
parent 0b9ea77dc4
commit 9fb980da2b
6 changed files with 173 additions and 75 deletions

View File

@@ -973,16 +973,29 @@ impl DdSaturationFloorUpdateOps {
/// reads `ISV[AUX_DIR_ACC_SHORT_EMA_INDEX=373]` (sp13 fast EMA of /// reads `ISV[AUX_DIR_ACC_SHORT_EMA_INDEX=373]` (sp13 fast EMA of
/// binary directional accuracy), maps it to a [5, 50] temperature via /// binary directional accuracy), maps it to a [5, 50] temperature via
/// ///
/// skill = clamp((short_ema 0.5) / 0.5, 0, 1) /// skill = clamp((short_ema 0.5) / 0.5, 0, 1)
/// temp = TEMP_MIN + (TEMP_MAX TEMP_MIN) × skill /// confusion = 1 skill
/// temp = TEMP_MIN + (TEMP_MAX TEMP_MIN) × confusion
/// ///
/// and slow-EMA-blends into /// and slow-EMA-blends into
/// `ISV[MIN_HOLD_TEMPERATURE_ADAPTIVE_INDEX=460]` with α=0.05. /// `ISV[MIN_HOLD_TEMPERATURE_ADAPTIVE_INDEX=460]` with α=0.05.
/// ///
/// Per `compute_min_hold_penalty` at trade_physics.cuh:575
/// (`soft_factor = deficit / (deficit + T)`), HIGH T = forgiving
/// (saturates → 0, no exit penalty), LOW T = sharp (saturates → 1,
/// max exit penalty). The `confusion = 1 skill` mapping therefore
/// gives the WR-plateau scenario its designed behavior:
/// - dir_acc ≈ 0.5 (random / committed-but-wrong) → confusion=1 →
/// temp=50 (permissive — exit ramp out of bad commitments)
/// - dir_acc → 1.0 (saturated skill) → confusion=0 → temp=5
/// (strict — force the model to stay in informed positions)
///
/// Replaces the deleted epoch-driven anneal schedule /// Replaces the deleted epoch-driven anneal schedule
/// `T_end + (T_start T_end) × exp(-epoch / decay)` (formerly /// `T_end + (T_start T_end) × exp(-epoch / decay)` (formerly
/// state_layout.cuh::MIN_HOLD_TEMPERATURE_{START, END, DECAY} + /// state_layout.cuh::MIN_HOLD_TEMPERATURE_{START, END, DECAY} +
/// training_loop.rs::min_hold_temperature_for_epoch). Decouples /// training_loop.rs::min_hold_temperature_for_epoch). The original
/// schedule's `START=50 → END=5` was "permissive early, strict late"
/// (classic explore-then-exploit, time-anchored). Decouples
/// temperature from epoch number — when WR plateaus, the old /// temperature from epoch number — when WR plateaus, the old
/// schedule pinned LOW temp (sharp) at end of training (max /// schedule pinned LOW temp (sharp) at end of training (max
/// punishment exactly when most needed forgiveness); the signal-driven /// punishment exactly when most needed forgiveness); the signal-driven

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,18 @@
/* ══════════════════════════════════════════════════════════════════════════ /* ══════════════════════════════════════════════════════════════════════════
* Class A audit-fix Batch 4-B — adaptive MIN_HOLD_TEMPERATURE producer * Class A audit-fix Batch 4-B — adaptive MIN_HOLD_TEMPERATURE producer
* (2026-05-08, Item 4). * (2026-05-08, Item 4). Fixup 2026-05-08: inverted the dir_acc → temp
* mapping after kernel-formula audit (`compute_min_hold_penalty` at
* trade_physics.cuh:575): HIGH T → soft_factor → 0 → forgiving exits;
* LOW T → soft_factor → 1 → sharp penalty. The original schedule
* `START=50 → END=5` therefore means "permissive early, strict late"
* (classic explore-then-exploit). The pre-fixup mapping
* `temp = TEMP_MIN + (TEMP_MAX - TEMP_MIN) × skill` was BACKWARDS for
* the WR-plateau scenario this 8-commit chain targets: at plateau
* (dir_acc ≈ 0.46-0.48, model committed but wrong) skill = 0 → temp = 5
* (sharp) → maximum exit penalty exactly when the model most needs to
* escape the bad committed direction. Inverted to `confusion = 1 - skill`
* so plateau → temp HIGH (permissive, exit ramp) and skilled →
* temp LOW (strict, force commitment).
* *
* Replaces the deleted epoch-driven anneal schedule * Replaces the deleted epoch-driven anneal schedule
* `T_end + (T_start - T_end) × exp(-epoch / decay)` * `T_end + (T_start - T_end) × exp(-epoch / decay)`
@@ -9,8 +21,9 @@
* ISV-driven signal-driven temperature derived from the model's * ISV-driven signal-driven temperature derived from the model's
* directional-accuracy skill. The temperature controls the * directional-accuracy skill. The temperature controls the
* `deficit / (deficit + T)` saturation factor in `compute_min_hold_penalty` * `deficit / (deficit + T)` saturation factor in `compute_min_hold_penalty`
* (trade_physics.cuh:575): HIGH T = forgiving (gradient is gentle near * (trade_physics.cuh:575): HIGH T = forgiving (penalty saturates near
* deficit=0); LOW T = sharp (penalty rises steeply at any deficit). * 0 — easy to exit early); LOW T = sharp (penalty rises steeply at any
* deficit — hold is forced).
* *
* Why this matters (per Class A audit deferred-item batch 4-B): * Why this matters (per Class A audit deferred-item batch 4-B):
* *
@@ -20,19 +33,24 @@
* plateaus. Maximum punishment is delivered exactly when the model * plateaus. Maximum punishment is delivered exactly when the model
* most needs forgiveness to escape the plateau. The signal-driven * most needs forgiveness to escape the plateau. The signal-driven
* replacement decouples temperature from epoch number: when the * replacement decouples temperature from epoch number: when the
* model is committing skillfully (dir_acc > 0.5) → temp HIGH * model is at random baseline / WR plateau (dir_acc 0.5,
* (permissive); when at random baseline (dir_acc ≈ 0.5) → temp LOW * confusion ≈ 1) → temp HIGH (permissive, exit ramp to escape bad
* (sharp commitment pressure to escape the random-guess regime). * committed direction); when committing skillfully (dir_acc → 1.0,
* confusion → 0) → temp LOW (strict, force commitment because the
* commitment is informed).
* *
* Driving signal: dir_acc skill from * Driving signal: dir_acc skill from
* `ISV[AUX_DIR_ACC_SHORT_EMA_INDEX=373]` — the SP13 fast EMA of * `ISV[AUX_DIR_ACC_SHORT_EMA_INDEX=373]` — the SP13 fast EMA of
* binary directional accuracy (sentinel 0.5 = random baseline). * binary directional accuracy (sentinel 0.5 = random baseline).
* *
* skill = clamp((short_ema - 0.5) / 0.5, 0, 1) ∈ [0, 1] * skill = clamp((short_ema - 0.5) / 0.5, 0, 1) ∈ [0, 1]
* temp = TEMP_MIN + (TEMP_MAX - TEMP_MIN) × skill * confusion = 1 - skill ∈ [0, 1]
* temp = TEMP_MIN + (TEMP_MAX - TEMP_MIN) × confusion
* *
* - skill=0 (no directional skill, dir_acc ≤ 0.5) → temp=TEMP_MIN=5 * - confusion=1 (no directional skill, dir_acc ≤ 0.5) → temp=TEMP_MAX=50
* - skill=1 (saturated dir_acc=1.0) → temp=TEMP_MAX=50 * (permissive — give the model an exit ramp out of the plateau)
* - confusion=0 (saturated dir_acc=1.0) → temp=TEMP_MIN=5
* (strict — force commitment because it's informed)
* *
* Cold-start fallback: if `isv[AUX_DIR_ACC_SHORT_EMA_INDEX]` is at * Cold-start fallback: if `isv[AUX_DIR_ACC_SHORT_EMA_INDEX]` is at
* sentinel (within EPS of 0.5) — i.e. fold reset just fired and no * sentinel (within EPS of 0.5) — i.e. fold reset just fired and no
@@ -52,7 +70,8 @@
* MIN_HOLD_TEMPERATURE_FALLBACK=50.0). Otherwise: * MIN_HOLD_TEMPERATURE_FALLBACK=50.0). Otherwise:
* *
* skill = max(0, min(1, (short_ema - 0.5) / 0.5)) * skill = max(0, min(1, (short_ema - 0.5) / 0.5))
* target_temp = TEMP_MIN + (TEMP_MAX - TEMP_MIN) × skill * confusion = 1 - skill
* target_temp = TEMP_MIN + (TEMP_MAX - TEMP_MIN) × confusion
* bilateral clamp to [TEMP_MIN, TEMP_MAX] * bilateral clamp to [TEMP_MIN, TEMP_MAX]
* *
* Pearl-A bootstrap: if current is at sentinel (within EPS of 50.0), * Pearl-A bootstrap: if current is at sentinel (within EPS of 50.0),
@@ -125,14 +144,34 @@ void min_hold_temperature_update(
return; return;
} }
/* Skill mapping: clamp((short_ema - 0.5) / 0.5, 0, 1). /* Confusion mapping: confusion = 1 - clamp((short_ema - 0.5)/0.5, 0, 1).
* - dir_acc=0.5 (random) → skill=0 → temp=temp_min=5 (sharp) * Inverted from the original "skill → temp" mapping after
* - dir_acc=1.0 (saturated) → skill=1 → temp=temp_max=50 (forgiving) * trade_physics.cuh:575 audit revealed HIGH T = permissive
* - dir_acc<0.5 (worse than random) → skill clamped to 0 → temp=5 */ * (deficit/(deficit+T) saturates to 0 → no exit penalty), LOW T =
* strict (saturates to 1 → max exit penalty). The WR-plateau
* scenario this 8-commit chain targets is "model committed but
* wrong at dir_acc ≈ 0.46-0.48"; the model needs the permissive
* branch (HIGH T) to escape the bad committed direction, which
* means HIGH confusion → HIGH temp.
*
* - dir_acc=0.5 (random / plateau) → skill=0 → confusion=1 →
* temp=temp_max=50 (permissive — exit ramp)
* - dir_acc=1.0 (saturated skill) → skill=1 → confusion=0 →
* temp=temp_min=5 (strict — force informed commitment)
* - dir_acc<0.5 (worse than random) → skill clamped to 0 →
* confusion=1 → temp=50 (max permissive)
*
* Note: the dir_acc==0.5 sentinel-guard fires before this branch
* (returns without writing); the explicit `confusion=1 → temp=50`
* arm above only fires for dir_acc < 0.5 - EPS (non-sentinel
* worse-than-random) or when the upstream EMA has moved off
* sentinel toward 0.5+ε. This matches the deleted schedule's
* epoch-0 anchor (T=50). */
float skill = (dir_acc_short - sentinel_dir_acc) / sentinel_dir_acc; float skill = (dir_acc_short - sentinel_dir_acc) / sentinel_dir_acc;
skill = fmaxf(0.0f, fminf(1.0f, skill)); skill = fmaxf(0.0f, fminf(1.0f, skill));
const float confusion = 1.0f - skill;
float target_temp = temp_min + (temp_max - temp_min) * skill; float target_temp = temp_min + (temp_max - temp_min) * confusion;
/* Bilateral clamp per `pearl_symmetric_clamp_audit.md`. Defends /* Bilateral clamp per `pearl_symmetric_clamp_audit.md`. Defends
* against arithmetic round-off pushing target outside the * against arithmetic round-off pushing target outside the

View File

@@ -344,29 +344,48 @@ pub const SP14_AUDIT_4B_PLAN_FLOOR_SLOT_END: usize = 460;
// temperature derived from the model's directional accuracy skill. The // temperature derived from the model's directional accuracy skill. The
// soft-saturation factor `deficit / (deficit + T)` in // soft-saturation factor `deficit / (deficit + T)` in
// `compute_min_hold_penalty` (trade_physics.cuh:575) controls how // `compute_min_hold_penalty` (trade_physics.cuh:575) controls how
// sharply the min-hold penalty bites: HIGH T = forgiving (gradient is // sharply the min-hold penalty bites: HIGH T → soft_factor → 0 →
// gentle near deficit=0); LOW T = sharp (penalty rises steeply at any // forgiving (no exit penalty); LOW T soft_factor → 1 → sharp
// deficit). // (max exit penalty). The deleted schedule's `START=50 → END=5`
// therefore meant "permissive early, strict late" — classic
// explore-then-exploit.
// //
// Driving signal: dir_acc skill — `clamp((short_ema - 0.5) / 0.5, 0, 1)` // Driving signal: dir_acc skill — `clamp((short_ema - 0.5) / 0.5, 0, 1)`
// from ISV[AUX_DIR_ACC_SHORT_EMA_INDEX=373] (sp13 directional-accuracy // from ISV[AUX_DIR_ACC_SHORT_EMA_INDEX=373] (sp13 directional-accuracy
// fast EMA, sentinel 0.5 = random-baseline). When the model is // fast EMA, sentinel 0.5 = random-baseline). The mapping uses
// committing skillfully (dir_acc > 0.5) → temp HIGH (permissive — the // `confusion = 1 - skill` so the model gets a permissive exit ramp
// model's exits are informed, no need to penalize sharply). When the // when at the WR-plateau (committed-but-wrong) regime and a strict
// model is at random baseline (dir_acc ≈ 0.5) → temp LOW (sharp — the // hold-enforcement when its commitment is informed:
// model needs commitment pressure to escape the random-guess regime). // - dir_acc ≈ 0.5 (random / WR-plateau) → confusion=1 → temp HIGH
// (permissive — let the model bail out of the bad committed
// direction; this is the WR-plateau exit ramp)
// - dir_acc → 1.0 (saturated skill) → confusion=0 → temp LOW
// (strict — force the model to stay in informed positions)
// //
// Mapping: temp = TEMP_MIN + (TEMP_MAX - TEMP_MIN) × skill, where: // Mapping: temp = TEMP_MIN + (TEMP_MAX - TEMP_MIN) × confusion, where:
// - skill=0 (no directional skill, dir_acc ≤ 0.5) → temp=TEMP_MIN=5 // - confusion=1 (dir_acc ≤ 0.5) → temp=TEMP_MAX=50 (permissive)
// - skill=1 (saturated dir_acc=1.0) → temp=TEMP_MAX=50 // - confusion=0 (dir_acc=1.0) → temp=TEMP_MIN=5 (strict)
// //
// Opposite of the deleted epoch-driven schedule which pinned LOW temp // This preserves the deleted schedule's MAX-at-cold-start anchor
// at end of training (T_end=5). The epoch schedule assumes the model // (T=50 at epoch 0) while decoupling the post-cold trajectory from
// gets MORE skilled with training; when WR plateaus this assumption // the epoch number. Under the old schedule, end-of-training pinned
// fails and the schedule becomes maximally punishing exactly when the // LOW temp (T_end=5), assuming the model would be more skilled by
// model needs forgiveness to escape the plateau. The signal-driven // then; when WR plateaus this assumption fails and the schedule
// version reacts to actual realized skill, decoupling temperature from // becomes maximally punishing exactly when the model needs forgiveness
// epoch number. // to escape the plateau. The signal-driven, confusion-mapped
// replacement reacts to actual realized skill — the model only sees
// strict enforcement when its commitments are informed, never as a
// pre-set time anchor.
//
// FIXUP 2026-05-08: original mapping was the inverted form
// (`temp = TEMP_MIN + (TEMP_MAX - TEMP_MIN) × skill`), which
// punished plateau exits with max sharpness. After kernel-formula
// audit (`compute_min_hold_penalty` — HIGH T = forgiving) and
// matching the deleted schedule's "permissive early" intent, the
// mapping was inverted to use confusion. Numerical fixed points:
// dir_acc=0.5 (sentinel) → guard returns; dir_acc=0.75 → temp=27.5
// (midpoint, identical pre/post fixup); dir_acc=1.0 → temp=5;
// dir_acc<0.5 → temp=50.
// //
// Producer kernel: `min_hold_temperature_update_kernel.cu` (per-epoch // Producer kernel: `min_hold_temperature_update_kernel.cu` (per-epoch
// boundary cadence). Consumer: `experience_kernels.cu` reads ISV[460] // boundary cadence). Consumer: `experience_kernels.cu` reads ISV[460]

View File

@@ -1819,9 +1819,11 @@ mod sp14_audit_4b_min_hold_temperature_gpu {
/// Test 1 — Pearl-A first-observation bootstrap. /// Test 1 — Pearl-A first-observation bootstrap.
/// ///
/// Seed dir_acc EMA at 0.75 (skill = (0.75 0.5)/0.5 = 0.5 → temp = /// Seed dir_acc EMA at 0.75 (skill = (0.75 0.5)/0.5 = 0.5;
/// 5 + 45 × 0.5 = 27.5). Pearl-A: temp at sentinel 50.0 → REPLACE /// confusion = 1 skill = 0.5 → temp = 5 + 45 × 0.5 = 27.5).
/// directly with 27.5. /// Pearl-A: temp at sentinel 50.0 → REPLACE directly with 27.5.
/// Midpoint: numeric result is identical to a pre-fixup
/// "skill→temp" mapping; only the semantic interpretation flipped.
#[test] #[test]
#[ignore = "requires GPU"] #[ignore = "requires GPU"]
fn min_hold_temp_pearl_a_bootstrap() { fn min_hold_temp_pearl_a_bootstrap() {
@@ -1850,7 +1852,9 @@ mod sp14_audit_4b_min_hold_temperature_gpu {
let result = isv_buf.read_all(); let result = isv_buf.read_all();
let temp = result[MIN_HOLD_TEMPERATURE_ADAPTIVE_INDEX]; let temp = result[MIN_HOLD_TEMPERATURE_ADAPTIVE_INDEX];
// skill = (0.75 0.5) / 0.5 = 0.5; target = 5 + 45 × 0.5 = 27.5. // skill = (0.75 0.5) / 0.5 = 0.5; confusion = 1 skill = 0.5;
// target = TEMP_MIN + (TEMP_MAX TEMP_MIN) × confusion
// = 5 + 45 × 0.5 = 27.5.
let expected = 27.5_f32; let expected = 27.5_f32;
assert!( assert!(
(temp - expected).abs() < 1e-4, (temp - expected).abs() < 1e-4,
@@ -1895,20 +1899,24 @@ mod sp14_audit_4b_min_hold_temperature_gpu {
); );
} }
/// Test 3 — High dir_acc skill → temp HIGH (permissive). /// Test 3 — High dir_acc skill → temp LOW (strict, force informed
/// commitment).
/// ///
/// Seed dir_acc=1.0 (perfect skill → skill=1.0 → temp=5+45=50). /// Seed dir_acc=1.0 (perfect skill → skill=1.0 → confusion=0 →
/// Verifies the spec mapping "model commits skillfully → temp HIGH". /// temp=TEMP_MIN=5). Verifies the inverted (post-fixup) semantic
/// Pearl-A REPLACE on sentinel. /// "skilled commitment → temp LOW (strict)" — when the model is
/// committing skillfully there's no plateau-escape need, so the
/// kernel pins temp to the strict end (max exit penalty enforces
/// the informed commitment). Pearl-A REPLACE on sentinel.
#[test] #[test]
#[ignore = "requires GPU"] #[ignore = "requires GPU"]
fn min_hold_temp_high_dir_acc_yields_high_temp() { fn min_hold_temp_high_dir_acc_yields_low_temp() {
let stream = make_stream(); let stream = make_stream();
let kernel = load_kernel(&stream); let kernel = load_kernel(&stream);
const ISV_DIM: usize = 1024; const ISV_DIM: usize = 1024;
let mut isv = vec![0.0_f32; ISV_DIM]; let mut isv = vec![0.0_f32; ISV_DIM];
// Saturated dir_acc → maximum skill → temp at MAX (permissive). // Saturated dir_acc → maximum skill → confusion=0 → temp at MIN (strict).
isv[AUX_DIR_ACC_SHORT_EMA_INDEX] = 1.0; isv[AUX_DIR_ACC_SHORT_EMA_INDEX] = 1.0;
isv[MIN_HOLD_TEMPERATURE_ADAPTIVE_INDEX] = SENTINEL_MIN_HOLD_TEMPERATURE; isv[MIN_HOLD_TEMPERATURE_ADAPTIVE_INDEX] = SENTINEL_MIN_HOLD_TEMPERATURE;
@@ -1927,27 +1935,33 @@ mod sp14_audit_4b_min_hold_temperature_gpu {
let result = isv_buf.read_all(); let result = isv_buf.read_all();
let temp = result[MIN_HOLD_TEMPERATURE_ADAPTIVE_INDEX]; let temp = result[MIN_HOLD_TEMPERATURE_ADAPTIVE_INDEX];
// skill=1.0 → temp = TEMP_MAX = 50.0. // skill=1.0 → confusion=0 → temp = TEMP_MIN = 5.0.
assert!( assert!(
(temp - MIN_HOLD_TEMPERATURE_MAX).abs() < 1e-4, (temp - MIN_HOLD_TEMPERATURE_MIN).abs() < 1e-4,
"High dir_acc must drive temp HIGH (permissive); expected {}, got {temp}", "High dir_acc must drive temp LOW (strict — informed commitment); \
MIN_HOLD_TEMPERATURE_MAX expected {}, got {temp}",
MIN_HOLD_TEMPERATURE_MIN
); );
} }
/// Test 4 — Low dir_acc (worse than random) → temp LOW (sharp). /// Test 4 — Low dir_acc (worse than random / WR-plateau scenario)
/// → temp HIGH (permissive — exit ramp).
/// ///
/// Seed dir_acc=0.4 (worse than 0.5 random baseline). skill clamps /// Seed dir_acc=0.4 (worse than 0.5 random baseline). skill clamps
/// to 0 → temp = TEMP_MIN = 5 (sharp commitment pressure). /// to 0 → confusion=1 → temp=TEMP_MAX=50 (permissive). This is the
/// canonical WR-plateau scenario this 8-commit chain targets:
/// the model is committed but wrong, and the kernel must give it
/// an exit ramp (HIGH T = soft exit penalty) rather than
/// punishing exits and locking it into the bad direction.
#[test] #[test]
#[ignore = "requires GPU"] #[ignore = "requires GPU"]
fn min_hold_temp_low_dir_acc_yields_low_temp() { fn min_hold_temp_low_dir_acc_yields_high_temp() {
let stream = make_stream(); let stream = make_stream();
let kernel = load_kernel(&stream); let kernel = load_kernel(&stream);
const ISV_DIM: usize = 1024; const ISV_DIM: usize = 1024;
let mut isv = vec![0.0_f32; ISV_DIM]; let mut isv = vec![0.0_f32; ISV_DIM];
// dir_acc=0.4 (worse than random — skill clamps to 0). // dir_acc=0.4 (worse than random — skill clamps to 0, confusion=1).
isv[AUX_DIR_ACC_SHORT_EMA_INDEX] = 0.4; isv[AUX_DIR_ACC_SHORT_EMA_INDEX] = 0.4;
isv[MIN_HOLD_TEMPERATURE_ADAPTIVE_INDEX] = SENTINEL_MIN_HOLD_TEMPERATURE; isv[MIN_HOLD_TEMPERATURE_ADAPTIVE_INDEX] = SENTINEL_MIN_HOLD_TEMPERATURE;
@@ -1966,20 +1980,23 @@ mod sp14_audit_4b_min_hold_temperature_gpu {
let result = isv_buf.read_all(); let result = isv_buf.read_all();
let temp = result[MIN_HOLD_TEMPERATURE_ADAPTIVE_INDEX]; let temp = result[MIN_HOLD_TEMPERATURE_ADAPTIVE_INDEX];
// skill clamped to 0 → target = TEMP_MIN. Pearl-A REPLACE on // skill clamped to 0 → confusion=1 → target = TEMP_MAX. Pearl-A
// sentinel → final temp = TEMP_MIN. // REPLACE on sentinel → final temp = TEMP_MAX.
assert!( assert!(
(temp - MIN_HOLD_TEMPERATURE_MIN).abs() < 1e-4, (temp - MIN_HOLD_TEMPERATURE_MAX).abs() < 1e-4,
"Low dir_acc must drive temp LOW (sharp); expected {}, got {temp}", "Low dir_acc must drive temp HIGH (permissive — plateau exit ramp); \
MIN_HOLD_TEMPERATURE_MIN expected {}, got {temp}",
MIN_HOLD_TEMPERATURE_MAX
); );
} }
/// Test 5 — Mid-cadence EMA blend after bootstrap. /// Test 5 — Mid-cadence EMA blend after bootstrap.
/// ///
/// Seed temp at 30.0 (NOT sentinel — past bootstrap), dir_acc=0.75 /// Seed temp at 30.0 (NOT sentinel — past bootstrap), dir_acc=0.75
/// (target_temp = 27.5). Blended = (1 0.05) × 30.0 + 0.05 × 27.5 /// (skill=0.5, confusion=0.5, target_temp = 5+45×0.5 = 27.5).
/// = 28.5 + 1.375 = 29.875. /// Blended = (1 0.05) × 30.0 + 0.05 × 27.5 = 28.5 + 1.375 = 29.875.
/// Midpoint: numeric result is identical to a pre-fixup
/// "skill→temp" mapping; only the semantic interpretation flipped.
#[test] #[test]
#[ignore = "requires GPU"] #[ignore = "requires GPU"]
fn min_hold_temp_mid_cadence_ema_after_bootstrap() { fn min_hold_temp_mid_cadence_ema_after_bootstrap() {

View File

@@ -45,19 +45,20 @@ T(e) = T_end + (T_start - T_end) × exp(-e / decay)
(formerly `state_layout.cuh::MIN_HOLD_TEMPERATURE_{START=50, END=5, DECAY=20}` + `training_loop.rs::min_hold_temperature_for_epoch`) with an ISV-driven signal-driven temperature derived from the model's directional accuracy skill. Producer kernel: `min_hold_temperature_update_kernel.cu` (NEW file, single-thread cold-path, per-epoch boundary launch). (formerly `state_layout.cuh::MIN_HOLD_TEMPERATURE_{START=50, END=5, DECAY=20}` + `training_loop.rs::min_hold_temperature_for_epoch`) with an ISV-driven signal-driven temperature derived from the model's directional accuracy skill. Producer kernel: `min_hold_temperature_update_kernel.cu` (NEW file, single-thread cold-path, per-epoch boundary launch).
**Driving signal — dir_acc skill (NOT dir_entropy_deficit):** **Driving signal — dir_acc skill (NOT dir_entropy_deficit):**
The audit-spec called for `dir_entropy_deficit`. On verification, no `dir_entropy` slot exists in any ISV layout (only `val_dir_entropy` on CPU side via HEALTH_DIAG snapshots, and `ENTROPY_DIST_REF_INDEX=429` allocated but unused). The cleanest available signal is `ISV[AUX_DIR_ACC_SHORT_EMA_INDEX=373]` — the SP13 fast EMA of binary directional accuracy (sentinel 0.5 = random baseline). This serves as a parallel proxy for "model commits" via dir_acc skill: The audit-spec called for `dir_entropy_deficit`. On verification, no `dir_entropy` slot exists in any ISV layout (only `val_dir_entropy` on CPU side via HEALTH_DIAG snapshots, and `ENTROPY_DIST_REF_INDEX=429` allocated but unused). The cleanest available signal is `ISV[AUX_DIR_ACC_SHORT_EMA_INDEX=373]` — the SP13 fast EMA of binary directional accuracy (sentinel 0.5 = random baseline). This serves as a parallel proxy for "model is uncertain" via dir_acc confusion:
``` ```
skill = clamp((short_ema 0.5) / 0.5, 0, 1) ∈ [0, 1] skill = clamp((short_ema 0.5) / 0.5, 0, 1) ∈ [0, 1]
temp = TEMP_MIN + (TEMP_MAX TEMP_MIN) × skill confusion = 1 skill ∈ [0, 1]
temp = TEMP_MIN + (TEMP_MAX TEMP_MIN) × confusion
``` ```
- skill=0 (no skill, dir_acc ≤ 0.5) → temp=TEMP_MIN=5 (sharp commitment pressure) - confusion=1 (no skill, dir_acc ≤ 0.5) → temp=TEMP_MAX=50 (permissive — exit ramp out of plateau / random-baseline commitments)
- skill=1 (saturated dir_acc=1.0) → temp=TEMP_MAX=50 (permissiveinformed exits, no need to penalize sharply) - confusion=0 (saturated dir_acc=1.0) → temp=TEMP_MIN=5 (strict — force the model to stay in informed positions)
This matches the audit-spec semantic ("model commits → temp HIGH"). Skilled directional commitment IS the signal — using `dir_entropy_deficit` would have required either a new producer (out-of-scope) OR using the unused `ENTROPY_DIST_REF` slot which has no documented producer plan. The mapping uses `confusion` (not raw `skill`) because the consumer formula `compute_min_hold_penalty` (`soft_factor = deficit / (deficit + T)` at trade_physics.cuh:575) makes HIGH T = forgiving (saturation factor → 0, no exit penalty) and LOW T = sharp (saturation factor → 1, max exit penalty). The deleted schedule's `START=50 → END=5` therefore meant "permissive early, strict late" (classic explore-then-exploit). Mapping `skill → temp` directly would be backwards: at the WR-plateau scenario this 8-commit chain targets (dir_acc ≈ 0.46-0.48, model committed but wrong), `skill=0``temp=5` would lock the model into bad committed directions exactly when an exit ramp is needed. The `confusion` inversion preserves the deleted schedule's epoch-0 anchor (T=50 cold-start) and gives the plateau scenario its designed exit ramp.
**Slot allocation:** ISV[460..461) — `MIN_HOLD_TEMPERATURE_ADAPTIVE_INDEX=460` (sentinel 50.0, bounds [5, 50], α=0.05). Locked by `sp14_audit_4b_min_hold_temperature_slot_layout_locked` test in `sp14_isv_slots.rs`. **Slot allocation:** ISV[460..461) — `MIN_HOLD_TEMPERATURE_ADAPTIVE_INDEX=460` (sentinel 50.0, bounds [5, 50], α=0.05). Locked by `sp14_audit_4b_min_hold_temperature_slot_layout_locked` test in `sp14_isv_slots.rs`.
**Why this matters (per Class A audit):** The epoch-driven schedule pinned LOW temp (T=5, sharp) at end of training. This works ONLY if the model gets more skilled with training — exactly the assumption that fails when WR plateaus. Maximum punishment is delivered exactly when the model most needs forgiveness to escape the plateau. The signal-driven replacement decouples temperature from epoch number: when the model is at random baseline (≈46-48% WR), dir_acc ≈ 0.5 → skill0 → temp=5 (sharp), forcing commitment. When the model breaks through to dir_acc > 0.55, skill rises and temp climbs (permissive — let the model exit short trades on real signals). **Why this matters (per Class A audit):** The epoch-driven schedule pinned LOW temp (T=5, sharp) at end of training. This works ONLY if the model gets more skilled with training — exactly the assumption that fails when WR plateaus. Maximum punishment is delivered exactly when the model most needs forgiveness to escape the plateau. The signal-driven replacement decouples temperature from epoch number and inverts the dir_acc → temp coupling: when the model is at random baseline (≈46-48% WR), dir_acc ≈ 0.5 → confusion1 → temp=50 (permissive, exit ramp out of bad committed direction). When the model breaks through to dir_acc → 1.0, confusion → 0 and temp drops toward 5 (strict — force commitment because it's now informed). This is "permissive when uncertain, strict when informed" — the opposite of the broken epoch-anchored schedule.
**DELETED atomically:** Per `feedback_no_legacy_aliases.md` + `feedback_no_partial_refactor.md`: **DELETED atomically:** Per `feedback_no_legacy_aliases.md` + `feedback_no_partial_refactor.md`:
1. `state_layout.cuh::MIN_HOLD_TEMPERATURE_{START, END, DECAY}` #defines. 1. `state_layout.cuh::MIN_HOLD_TEMPERATURE_{START, END, DECAY}` #defines.
@@ -80,7 +81,7 @@ This matches the audit-spec semantic ("model commits → temp HIGH"). Skilled di
- `crates/ml/build.rs` — kernel added to compile list. - `crates/ml/build.rs` — kernel added to compile list.
- `crates/ml/src/trainers/dqn/state_reset_registry.rs``sp14_audit_4b_min_hold_temperature_adaptive` registry entry. - `crates/ml/src/trainers/dqn/state_reset_registry.rs``sp14_audit_4b_min_hold_temperature_adaptive` registry entry.
- `crates/ml/src/trainers/dqn/trainer/training_loop.rs` — DELETED `min_hold_temperature_for_epoch` body, DELETED both call sites, ADDED `read_min_hold_temperature_from_isv` helper, ADDED launcher invocation per-epoch, ADDED FoldReset dispatch arm. - `crates/ml/src/trainers/dqn/trainer/training_loop.rs` — DELETED `min_hold_temperature_for_epoch` body, DELETED both call sites, ADDED `read_min_hold_temperature_from_isv` helper, ADDED launcher invocation per-epoch, ADDED FoldReset dispatch arm.
- `crates/ml/tests/sp14_oracle_tests.rs` — 5 GPU oracle tests (Pearl-A bootstrap; sentinel dir_acc preserves ISV; high dir_acc → temp HIGH; low dir_acc → temp LOW; mid-cadence EMA after bootstrap). - `crates/ml/tests/sp14_oracle_tests.rs` — 5 GPU oracle tests (Pearl-A bootstrap; sentinel dir_acc preserves ISV; high dir_acc → temp LOW [strict, informed commitment]; low dir_acc → temp HIGH [permissive, plateau exit ramp]; mid-cadence EMA after bootstrap).
### Verification ### Verification
@@ -100,11 +101,13 @@ ISV_TOTAL_DIM: 459 → 461.
The audit-spec was correct on Item 3 (plan_threshold floor) — line 44, formula `fmaxf(0.1f, 0.5f * ema)`, `0.1f` is the absolute floor and `0.5f * ema` is the relative one. Picked Design Y (inline) per the audit's recommendation. The audit-spec was correct on Item 3 (plan_threshold floor) — line 44, formula `fmaxf(0.1f, 0.5f * ema)`, `0.1f` is the absolute floor and `0.5f * ema` is the relative one. Picked Design Y (inline) per the audit's recommendation.
The audit-spec was **partially wrong on Item 4** in two ways: The audit-spec was **partially wrong on Item 4** in two ways:
1. **Driving signal:** `dir_entropy_deficit` does not exist as an ISV signal. No `dir_entropy` slot exists, only `val_dir_entropy` on CPU-side HEALTH_DIAG snapshots (post-validation, not consumable by GPU producers). `ENTROPY_DIST_REF_INDEX=429` is allocated but unused. Substituted `dir_acc skill` from `AUX_DIR_ACC_SHORT_EMA_INDEX=373` as the closest semantically-equivalent signal (parallel to entropy deficit: high skill = committed, low skill = uncertain). Mapping preserves the spec's intent ("model commits → temp HIGH"). 1. **Driving signal:** `dir_entropy_deficit` does not exist as an ISV signal. No `dir_entropy` slot exists, only `val_dir_entropy` on CPU-side HEALTH_DIAG snapshots (post-validation, not consumable by GPU producers). `ENTROPY_DIST_REF_INDEX=429` is allocated but unused. Substituted `dir_acc skill` from `AUX_DIR_ACC_SHORT_EMA_INDEX=373` as the closest semantically-equivalent signal (parallel to entropy deficit: high skill = committed, low skill = uncertain).
2. **Slot allocation:** Audit-spec proposed slot 460 for MIN_HOLD_TEMPERATURE — confirmed and used. 2. **Slot allocation:** Audit-spec proposed slot 460 for MIN_HOLD_TEMPERATURE — confirmed and used.
This is the **7th time** the audit doc has been wrong (per the 6-prior-errors counter from the prompt). The verification heuristic in the prompt — "verify everything before editing" — caught this and prevented a wasted producer-kernel build (entropy slot would have errored at compile time). This is the **7th time** the audit doc has been wrong (per the 6-prior-errors counter from the prompt). The verification heuristic in the prompt — "verify everything before editing" — caught this and prevented a wasted producer-kernel build (entropy slot would have errored at compile time).
**Mapping-direction fixup (2026-05-08):** The initial wiring of this fix used `temp = TEMP_MIN + (TEMP_MAX TEMP_MIN) × skill` based on the audit-spec phrase "model commits → temp HIGH". On post-commit review the kernel formula `compute_min_hold_penalty` was re-read at trade_physics.cuh:567-577: `soft_factor = deficit / (deficit + T)`. HIGH T → soft_factor → 0 → forgiving (no exit penalty); LOW T → soft_factor → 1 → sharp (max exit penalty). The deleted schedule's `START=50 → END=5` therefore meant "permissive early, strict late" (classic explore-then-exploit), not "permissive when committed, strict when uncertain". The original `skill → temp` mapping was backwards for the WR-plateau scenario this 8-commit chain targets: at plateau (dir_acc ≈ 0.46-0.48, committed but wrong), `skill=0 → temp=5` would have locked the model into bad committed directions with maximum exit penalty — exactly when an exit ramp is needed. Inverted to `confusion = 1 skill` so plateau → temp HIGH (permissive exit ramp) and saturated skill → temp LOW (strict, force informed commitment). Tests 3 and 4 flipped accordingly; tests 1, 2, 5 (sentinel guard, midpoint, midpoint EMA) are numerically unchanged because their dir_acc inputs sit at 0.5/0.75 fixed points where `skill ≈ confusion` or the guard fires before the mapping.
Reading directly from `compute_min_hold_penalty` in `trade_physics.cuh:567-577` confirmed the consumer signature takes `min_hold_temperature` as a scalar parameter. Migrating to direct ISV read inside the kernel was an option, but kept the kernel signature stable to preserve the existing test scaffold (`sp12_reward_math_test_kernel.cu` at line 36 also uses the scalar arg). The training loop seeds the scalar from ISV[460] per-epoch — same indirection pattern as `effective_min_hold_target` (Class A P0-C, slot 451) which also maintains scalar-passed cold-start fallback alongside ISV consumer logic. Reading directly from `compute_min_hold_penalty` in `trade_physics.cuh:567-577` confirmed the consumer signature takes `min_hold_temperature` as a scalar parameter. Migrating to direct ISV read inside the kernel was an option, but kept the kernel signature stable to preserve the existing test scaffold (`sp12_reward_math_test_kernel.cu` at line 36 also uses the scalar arg). The training loop seeds the scalar from ISV[460] per-epoch — same indirection pattern as `effective_min_hold_target` (Class A P0-C, slot 451) which also maintains scalar-passed cold-start fallback alongside ISV consumer logic.
## 2026-05-08 — Class A audit-fix Batch 4-A: DD saturation floor adaptive + legacy DD path Case A ## 2026-05-08 — Class A audit-fix Batch 4-A: DD saturation floor adaptive + legacy DD path Case A