spec(sp10): unconditional Thompson selector + ISV-driven temperature

Brainstorm spec for SP10. Resolves the val-Flat-collapse pathology that
persisted through Fix 33-37: the eval-time argmax in experience_action_
select picks Hold deterministically every val bar (dir_entropy=0,
trade_count=1 in 214k bars) regardless of controller state.

Architecture:
  - Delete `if (eval_mode)` argmax branch in direction-selector kernel
  - Use temperature-blended Thompson: q_eff = E[Q] + τ × (Thompson - E[Q])
  - τ = clamp(intent_eval_divergence / divergence_target, 0.5, 2.0)
  - τ self-corrects: collapse → high τ; healthy → low τ; permanent 0.5 floor
  - Reuses SP9's intent_eval_divergence_compute_kernel (extended, not new)

Per pearl_controller_anchors_isv_driven: τ is ISV-driven, no
hardcoded constants beyond Invariant 1 numerical anchors (clamp range).
Per pearl_blend_formulas_must_have_permanent_floor: MIN_TEMP=0.5 ensures
eval ALWAYS has stochasticity.

The pearl_thompson_for_distributional_action_selection was about Bellman
TARGET argmax (selector/target symmetry). It does NOT prohibit Thompson
at the rollout selector. SP10 amends the pearl to clarify.

Scope: 1 ISV slot, kernel modification (no new kernel — extend SP9's
producer), 1 consumer kernel rewrite, test update, pearl amendment,
audit doc Fix 38. ~300-500 LOC, single atomic commit per
feedback_no_partial_refactor.
This commit is contained in:
jgrusewski
2026-05-03 22:24:14 +02:00
parent 8a25b330fc
commit 6ae9bc0055

View File

@@ -0,0 +1,234 @@
# SP10 — Unconditional Thompson Selector + ISV-driven Temperature
**Date:** 2026-05-03
**Status:** Draft (user review pending)
**Authors:** session-1777830566225
**Related pearls:**
- `pearl_thompson_for_distributional_action_selection.md` (existing — selector/target asymmetry)
- `pearl_controller_anchors_isv_driven.md` (anchors must be ISV-driven)
- `pearl_blend_formulas_must_have_permanent_floor.md` (permanent floor pattern)
- `pearl_cold_start_exit_signal_or.md` (OR'd cold-start signals)
## 1. Overview
Eliminate argmax-at-eval bias in the direction-branch action selector by
making the selector unconditional Thompson sampling with an ISV-driven
temperature parameter. The temperature is signal-driven from SP9's
existing `intent_eval_divergence` canary: high divergence (eval collapsed)
→ raise τ (more exploration); low divergence (healthy) → lower τ (closer
to argmax-like exploitation).
This is the next link in the Fix 33→37 chain. Fix 33-37 addressed
training-side controller dynamics; SP10 addresses the eval-side selector
that was producing val-Flat-collapse regardless of controller state.
## 2. Pathology
T10 train-multi-seed-khr7c ep2 (commit `8a25b330f`, all SP9 fixes
applied):
```
val: trade_count=1 in 214,654 bars
active_frac=0.0 dir_entropy=0.0 sharpe=0.0
sp9_kelly_warmup [floor=0.25 (engaged)
divergence=500000+ (eval collapsed)
conf [stat=1.0 bhv=0.0 tmp=0.2]]
```
Despite Kelly warmup floor=0.25 (allowing Half-mag exposure), Kelly
controllers all healthy, IQN warnings absent — the model picks ONE
direction (Hold) at every val bar. dir_entropy=0 means deterministic
single-action over the whole 214k window.
Root cause: `experience_action_select` kernel uses `argmax(E[Q])` at
eval (line 1208). With Hold's E[Q] ≈ 0 (no position cost) and
Short/Long's E[Q] = ε (small edge minus tx costs), argmax wins Hold
deterministically every bar. Thompson sampling would let positive-Q
directions win proportional to their posterior overlap with Hold.
The `pearl_thompson_for_distributional_action_selection` was about
selector/target asymmetry in the **Bellman target** (DDQN target Q
computation). It does NOT prohibit Thompson at the rollout selector;
only at the target. SP10 honors that distinction.
## 3. Architecture
### 3.1 Kernel change
Replace the `if (eval_mode)` branch in `experience_action_select` with
unconditional Thompson sampling, modulated by an ISV-driven temperature:
```cuda
// Read ISV-driven temperature
float temp = isv_signals[EVAL_THOMPSON_TEMP_INDEX];
// Per-direction Thompson sample with temperature blend
float best_q_eff = -1e30f;
int best_d = 0;
for (int d = 0; d < b0_size; d++) {
softmax_c51_inline(logits_i + d * n_atoms, n_atoms, probs_d);
compute_atom_values_inline(per_sample_support, atom_positions,
i, d, b0_size, n_atoms, atom_vals_d);
float u = philox_uniform(i, timestep, rng_ctr++);
float q_sample = sample_c51_inverse_cdf(probs_d, atom_vals_d, n_atoms, u);
// Temperature blend: τ=0 → argmax of E[Q]; τ=1 → pure Thompson;
// τ>1 → exaggerated exploration (corrective during collapse).
float q_eff = e_dir[d] + temp * (q_sample - e_dir[d]);
if (q_eff > best_q_eff) { best_q_eff = q_eff; best_d = d; }
}
dir_idx = best_d;
```
The `if (eval_mode)` branch and its argmax body are deleted entirely.
No conditional path remains — one selector for both training and eval.
### 3.2 Temperature formula (ISV producer)
Extended from SP9's existing `intent_eval_divergence_compute_kernel`. The
existing kernel already computes `divergence`. Add a second scratch write:
```cuda
// Existing SP9 output:
scratch_buf[scratch_div_idx] = divergence;
// SP10 addition: temperature output, using same divergence + target
float div_target = isv_signals[KELLY_DIVERGENCE_TARGET_INDEX]; // = 2.0 (Invariant-1)
float temp = clamp(divergence / max(div_target, EPS_DIV),
THOMPSON_TEMP_MIN, THOMPSON_TEMP_MAX);
scratch_buf[scratch_temp_idx] = temp;
```
Where:
- `THOMPSON_TEMP_MIN = 0.5f` (Invariant 1: permanent stochasticity floor per
`pearl_blend_formulas_must_have_permanent_floor`; never fully deterministic)
- `THOMPSON_TEMP_MAX = 2.0f` (Invariant 1: cap on exploration boost; prevents
pathological all-uniform sampling)
- `EPS_DIV = 1e-6f` (Invariant 1: divide-by-zero protection)
Behavior:
- Healthy (`divergence ≈ target = 2.0`): `temp ≈ 1.0` → pure Thompson
- Collapsed (`divergence ≫ target`, e.g., 1e6 sentinel): `temp = 2.0` → exploration boost
- Healthy and converging (`divergence < target`): `temp = 0.5` → closer to argmax behavior, but never fully deterministic
The MIN_TEMP=0.5 floor is the permanent-stochasticity guarantee per
`pearl_blend_formulas_must_have_permanent_floor`. Eval action selection
ALWAYS has at least 50% Thompson contribution.
### 3.3 ISV slot allocation
After SP9's last slot at 338:
| Slot | Purpose | Reset semantics |
|---|---|---|
| `EVAL_THOMPSON_TEMP_INDEX = 339` | Action selector temperature | FoldReset (sentinel 0, Pearl A bootstrap to MIN_TEMP) |
Bump `SP5_SLOT_END = 340` and `ISV_TOTAL_DIM = 340`.
Add 1 new scratch slot for the temperature output.
### 3.4 Producer kernel — extend or new?
**Decision: extend existing `intent_eval_divergence_compute_kernel`** rather
than create a separate producer. Reasons:
1. The temperature is a deterministic function of `divergence` (no new
inputs needed)
2. Both outputs come from the same compute (cheap to add one extra write)
3. Atomic commit per `feedback_no_partial_refactor` — slot + kernel
extension + consumer migration all together
4. Matches `pearl_engagement_rate_self_correction` pattern (one signal,
multiple consumers)
The kernel signature gains 2 new parameters (scratch_temp_idx and
divergence_target_isv_index) but otherwise unchanged.
## 4. Pearl amendments
### 4.1 `pearl_thompson_for_distributional_action_selection.md`
Update wording to clarify the selector/target distinction:
- BEFORE: "Thompson at training, argmax at eval"
- AFTER: "Thompson at all times for the rollout selector; argmax ONLY for the
Bellman target Q computation (DDQN target). Temperature on the Thompson
sample is ISV-driven from intent_eval_divergence per
pearl_controller_anchors_isv_driven."
### 4.2 No new pearl
The principle here is captured by combining existing pearls:
- `pearl_thompson_for_distributional_action_selection` (amended) — selector vs target
- `pearl_controller_anchors_isv_driven` — the temperature is ISV-driven
- `pearl_blend_formulas_must_have_permanent_floor` — MIN_TEMP=0.5 floor
- `pearl_engagement_rate_self_correction` — temperature self-corrects via divergence
## 5. Test changes
`crates/ml/src/cuda_pipeline/mod.rs:1242``test_eval_action_select_eval_argmax_picks_best`:
- **Rename** to `test_eval_action_select_thompson_picks_proportionally`
- **Update** assertion: instead of "best direction always wins", assert
"best direction wins with probability ≥ posterior weight of best
direction" (allowing the temperature blend's stochasticity)
- **Histogram check**: with τ=1.0 and a clearly-best direction (large
Q gap), best wins ≥ 80% of samples; with smaller gap, ≥ 50%
Existing comment at `mod.rs:1370` ("Thompson-eval histogram: short=...
long=... (expected: P(Long)=1.0)") suggests prior preparation for this
case — test framework already aware of Thompson-eval semantics.
## 6. Implementation phases (atomic per feedback_no_partial_refactor)
Single commit:
1. Allocate 1 new ISV slot (EVAL_THOMPSON_TEMP_INDEX=339), bump
SP5_SLOT_END and ISV_TOTAL_DIM
2. Extend `intent_eval_divergence_compute_kernel.cu` with temperature
output (~10 LOC addition + signature change)
3. Update launcher in `gpu_dqn_trainer.rs` for new kernel signature
4. Modify `experience_action_select` kernel: delete `if (eval_mode)` argmax
branch, replace Thompson branch with temperature-blended version, add
`EVAL_THOMPSON_TEMP_INDEX` ISV read at top of direction-selection block
5. Update FoldReset registry: 1 new slot entry, 1 new dispatch arm
6. Update test `test_eval_action_select_eval_argmax_picks_best`
`test_eval_action_select_thompson_picks_proportionally`
7. Update pearl `pearl_thompson_for_distributional_action_selection.md`
in MEMORY (selector/target wording)
8. Audit doc Fix 38 entry
Total estimated: ~300-500 LOC across ~6 files. ~45-60 min focused work.
## 7. Validation criteria
After SP10 lands:
- Smoke (5-epoch L40S `multi_fold_convergence`):
- val_active_frac > 0.10 with τ ≥ 0.5 (always some action variety)
- dir_entropy at eval > 0.3 (multi-direction selection)
- HEALTH_DIAG sp10 line shows τ evolving with divergence
- T10 50-epoch:
- val_trade_count > 1000 in 214k-bar window (was 1)
- val_active_frac > 0.20 across folds
- sharpe positive net of fold transitions
## 8. Risks and open questions
- **Risk 1**: Pure Thompson eval introduces PnL variance — short windows
show more sharpe noise. Mitigation: τ floor at 0.5, not full Thompson;
diagnostic is multi-fold mean sharpe, not single-fold spot value.
- **Risk 2**: Production trading reproducibility — Philox RNG + state +
timestep makes Thompson deterministic given seed. Same model + same
seed = same actions. Reproducibility is preserved at the seed level.
- **Risk 3**: The pearl amendment may conflict with intent at the time it
was written. Per `feedback_trust_code_not_docs`, the smoke evidence
trumps the original wording; pearl gets updated to reflect reality.
## 9. Followups (NOT in this commit)
- Adaptive temperature beyond the divergence canary (e.g., per-state
uncertainty from Q variance, regime-dependent τ)
- Apply same Thompson selector logic to mag/ord/urg branches if their
argmax-at-eval pathways are found to bias similarly (currently mag
uses Boltzmann softmax, ord/urg also stochastic — likely fine)
- Bellman target Q computation review: confirm DDQN target argmax stays
argmax (no SP10 change there)