From 089248ea10cc44ec3c5e98f7c14f925acac845d1 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 25 Oct 2026 21:37:21 +0000 Subject: [PATCH] Attention explorer: dislikes no longer grow the change size, learned size capped at 0.35 Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01RnrTBt9b8CdJwub5roffF3 --- modules/sampling/attention_bandit/__init__.py | 10 +++++++--- .../attention_bandit/tests/test_attention_bandit.py | 12 +++++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/modules/sampling/attention_bandit/__init__.py b/modules/sampling/attention_bandit/__init__.py index 9cdc6154..797533ae 100644 --- a/modules/sampling/attention_bandit/__init__.py +++ b/modules/sampling/attention_bandit/__init__.py @@ -71,6 +71,7 @@ STAY_HEAD_START = 2.0 # extra likes "as is" starts with, so a new key is s MAX_ROWS = 10 ** 9 # no cap: a rated run is under 1 KB START_SPREAD = 0.2 SPREAD_MIN, SPREAD_MAX = 0.05, 0.5 +LEARN_MAX = 0.35 # what ratings can grow the size to; 0.5x/1.5x on many blocks wrecked a picture on H3 LR = 0.5 EXPLORE = 0.05 @@ -121,8 +122,11 @@ def learned_spread(rows, signature): continue if all(int(a) == STAY for a in p["arms"].tolist()): continue # nothing changed, so nothing to blame on the size - m += LR * _sign(r["reward"]) * (float(p["spread"]) - m) - m = min(max(m, SPREAD_MIN), SPREAD_MAX) + used = float(p["spread"]) + # A like pulls toward the size it used. A dislike can only say "too much": it pushes the + # size down from a bigger one, never up from a smaller one (that would ratchet to the cap). + m += LR * (used - m) if r["reward"] > 0 else LR * min(0.0, m - used) + m = min(max(m, SPREAD_MIN), LEARN_MAX) return m @@ -197,7 +201,7 @@ def install(patcher, values, key): rows = taste.rows(KIND) picks = draw(counts(rows, signature, len(groups))) spread = manual_spread if manual else min(max( - learned_spread(rows, signature) + random.gauss(0.0, EXPLORE), SPREAD_MIN), SPREAD_MAX) + learned_spread(rows, signature) + random.gauss(0.0, EXPLORE), SPREAD_MIN), LEARN_MAX) live["scaled"] = live["calls"] = 0 live["temps"] = {b: 1.0 + spread * ARMS[a] for g, a in zip(groups, picks) for b in g} captured.update({"arms": torch.tensor(picks), "sig": torch.tensor(signature), diff --git a/modules/sampling/attention_bandit/tests/test_attention_bandit.py b/modules/sampling/attention_bandit/tests/test_attention_bandit.py index 8e5c9ce9..016bdf02 100644 --- a/modules/sampling/attention_bandit/tests/test_attention_bandit.py +++ b/modules/sampling/attention_bandit/tests/test_attention_bandit.py @@ -54,15 +54,21 @@ def _srow(reward, arms, spread, sig=(2, 0, 9)): def test_the_size_moves_toward_liked_and_away_from_disliked_and_ignores_unchanged_runs(): - from modules.sampling.attention_bandit import SPREAD_MAX, SPREAD_MIN, START_SPREAD, learned_spread + from modules.sampling.attention_bandit import LEARN_MAX, SPREAD_MIN, START_SPREAD, learned_spread sig = [2, 0, 9] assert learned_spread([], sig) == START_SPREAD assert learned_spread([_srow(1.0, [0, 1], 0.4)], sig) > START_SPREAD assert learned_spread([_srow(-1.0, [0, 1], 0.4)], sig) < START_SPREAD assert learned_spread([_srow(1.0, [1, 1], 0.4)], sig) == START_SPREAD # nothing changed assert learned_spread([_srow(1.0, [0, 1], 0.4, sig=(3, 0, 9))], sig) == START_SPREAD # other layout - assert learned_spread([_srow(1.0, [0, 0], 0.5)] * 30, sig) == pytest.approx(0.5, abs=1e-3) - assert SPREAD_MIN <= learned_spread([_srow(-1.0, [0, 0], 0.5)] * 30, sig) <= SPREAD_MAX + assert learned_spread([_srow(1.0, [0, 0], 0.5)] * 30, sig) == pytest.approx(LEARN_MAX, abs=1e-3) + assert SPREAD_MIN <= learned_spread([_srow(-1.0, [0, 0], 0.5)] * 30, sig) <= LEARN_MAX + + +def test_dislikes_of_small_changes_never_grow_the_size(): + from modules.sampling.attention_bandit import START_SPREAD, learned_spread + assert learned_spread([_srow(-1.0, [0, 1], 0.1)] * 20, [2, 0, 9]) == START_SPREAD + assert learned_spread([_srow(-1.0, [0, 1], 0.4)] * 20, [2, 0, 9]) < START_SPREAD def test_the_log_names_exact_blocks_and_skips_the_unchanged():