feat(cuda): re-enable C51 atom span EWMA anchored on clamp bounds

G.2 disabled atom span adaptation to break a positive feedback loop.
With clamp bounds now FROZEN, re-enabling span adaptation is safe.

The span anchors on the CLAMP bounds (WIN=1.0, LOSS=3.0) — the
structural ceiling on post-clamp rewards that Q actually sees. The
earlier version incorrectly used pre-clamp EMAs (pos_ema ≈ 50) which
would blow atoms to [-50, +50] with Δ_z=5.

V_MAX EWMAs from 1.0 toward WIN=1.0 (stays put).
V_MIN EWMAs from -1.0 toward -LOSS=-3.0 (slowly widens to cover the
loss tail). Final asymmetric span [-3, +1] with Δ_z=0.2 gives Q
full resolution across the clamped reward range.

α=0.001 (half-life ~700 steps), floor ±1.0.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-05-27 15:02:49 +02:00
parent 0a066a469d
commit 17b426ba5b

View File

@@ -224,51 +224,51 @@ extern "C" __global__ void rl_reward_clamp_controller(
isv[RL_REWARD_CLAMP_RATIO_INDEX] = ratio_clamped;
}
// Step 4 (G.2, 2026-05-24): WIN / LOSS bounds are STRUCTURAL — they
// stay at their trainer-seeded values (WIN=1.0, LOSS=3.0) matching
// the C51 atom span × 3:1 loss-aversion asymmetry. The previous
// adaptive widening was anti-correct: when clip_rate exceeded the
// 5% target, the controller WIDENED the clamp to accommodate the
// distribution, defeating the bound's structural purpose. The
// resulting positive feedback loop (large scaled rewards → wider
// clamp → larger V/Q targets → C51 atom support ratchets up → even
// larger scaled rewards permitted) was diagnosed in the F.5 200-step
// local smoke: WIN drifted 1.0 → 41.3 (41×) in 150 steps, scaled
// rewards of 14.71 flowed through, V regression spiked to l_v=104.
//
// The diagnostic-only state (pos_max_ema, neg_max_ema, RATIO,
// clip_rate_ema, MARGIN) is still maintained above so the diag can
// surface what the system *would* adapt to under an unbounded
// policy — useful for understanding reward-distribution drift. But
// the LOAD-BEARING WIN_INDEX / LOSS_INDEX slots are now write-once
// (trainer's isv_constants seed). Per
// pearl_audit_unboundedness_for_implicit_asymmetry: structural
// bounds must NOT adapt in response to the very signal they're
// meant to bound.
//
// Suppress unused-variable warnings on the diagnostic-only blocks.
// Step 4 (G.2): WIN / LOSS clamp bounds remain STRUCTURAL — the
// G.2 feedback loop required BOTH clamp widening AND span widening.
// With clamp frozen here, only the atom span adapts (Step 5 below).
// Suppress unused warnings on diagnostic-only state.
(void) margin;
(void) ema_new;
// ── C51 atom span — STRUCTURAL (G.2, 2026-05-24). ─────────────
// ── Step 5: C51 atom span adaptation from observed reward EMAs. ──
//
// The atom-span EWMA was the SECOND half of the positive feedback
// loop disabled in Step 4: with WIN/LOSS adapting to fit large
// scaled rewards, V_MAX/V_MIN ratcheted up to match (e.g., F.5
// smoke saw V_MAX=2.66 by step 149 from a seed of 1.0). Larger
// atom span then permitted V to predict larger values, amplifying
// advantage magnitude and PPO/V losses.
// G.2 disabled this because atom-span growth + clamp-bound growth
// created a positive feedback loop. With clamp bounds FROZEN (Step
// 4), the loop can't form — the clamp caps the reward magnitude
// regardless of atom span.
//
// Fix: the C51 atom span stays at its trainer-seeded value
// (V_MAX=1.0, V_MIN=-1.0). Q's distributional representation has
// FIXED structural resolution matching the seeded WIN/LOSS bounds
// — any reward signal outside that range is clipped at
// apply_reward_scale rather than absorbed by widening atoms.
// Per pearl_c51_atom_span_must_track_clamp_range: atom span tracks
// the SEED clamp range, not the runtime-adapted one.
// The span slowly tracks the observed reward tail EMAs so Q's
// distributional resolution covers the ACTUAL reward range. Without
// this, rewards exceeding the static span project to edge atoms and
// Q loses magnitude discrimination (wr plateaus at ~0.46).
//
// V_BOUND_FLOOR no longer used; keep the constant for any future
// re-introduction.
(void) V_BOUND_FLOOR;
(void) V_BOUND_EWMA_ALPHA;
// Floor at V_BOUND_FLOOR (1.0) preserves baseline resolution.
// Ceiling at clamp bounds (WIN=1.0, LOSS=3.0) prevents runaway.
// Slow EWMA (α=0.001, half-life ~700 steps) lets Q's atom mapping
// adapt gradually without whipsawing.
// Atom span anchors on the CLAMP bounds — the post-clamp reward
// range that Q's Bellman targets actually see. Pre-clamp EMAs
// (pos_ema ≈ 50) would blow atoms to [-50, +50] with Δ_z=5,
// destroying resolution. The clamp bounds (WIN, LOSS) are the
// structural ceiling on what rewards enter Q.
const float win_bound = isv[RL_REWARD_CLAMP_WIN_INDEX]; // 1.0
const float loss_bound = isv[RL_REWARD_CLAMP_LOSS_INDEX]; // 3.0
{
const float v_max_prev = isv[RL_C51_V_MAX_INDEX];
const float v_max_target = fmaxf(V_BOUND_FLOOR, win_bound);
const float v_max_new = (v_max_prev == 0.0f)
? v_max_target
: (1.0f - V_BOUND_EWMA_ALPHA) * v_max_prev
+ V_BOUND_EWMA_ALPHA * v_max_target;
isv[RL_C51_V_MAX_INDEX] = fmaxf(V_BOUND_FLOOR, v_max_new);
}
{
const float v_min_prev = isv[RL_C51_V_MIN_INDEX];
const float v_min_target = fminf(-V_BOUND_FLOOR, -loss_bound);
const float v_min_new = (v_min_prev == 0.0f)
? v_min_target
: (1.0f - V_BOUND_EWMA_ALPHA) * v_min_prev
+ V_BOUND_EWMA_ALPHA * v_min_target;
isv[RL_C51_V_MIN_INDEX] = fminf(-V_BOUND_FLOOR, v_min_new);
}
}