chane335 commited on
Commit
2faa62c
·
verified ·
1 Parent(s): 8867e44

Run 8: disable unlikeliness (β_rank=0.0) + keep β=0.04 + env-verified traces — root-cause fix for Run 7 R1 over-prediction

Browse files
tests/test_rewards.py CHANGED
@@ -259,41 +259,44 @@ def test_wrappers_survive_trl_keyword_calling_convention():
259
  # ─────────────────────────────────────────────────────────────────────────────
260
 
261
 
262
- def test_unlikeliness_reward_penalizes_top_ranked_positive_samples():
263
- """The highest-reward sample in a group should be shaped down by β_rank=0.25
264
- relative to the lowest-reward positive sample. This breaks the rank bias
265
- that produced Run 5's degenerate R2-only policy."""
 
 
 
 
 
 
 
 
 
 
 
 
 
266
  pack = build_reward_pack(total_episodes=300)
267
- pack.episode_counter[0] = 200 # Env weight = 1.5 at this point
268
 
269
  def raw_returning_spread(completions, **_):
270
- # 4 rollouts with distinct positive rewards — classic GRPO group
271
  return [1.0, 0.8, 0.6, 0.4]
272
 
273
  wrapped = weighted_environmental_reward(raw_returning_spread, pack)
274
  scores = wrapped(completions=["a", "b", "c", "d"])
275
 
276
- # G=4. rank_norm = (G-1 - rank_of[i]) / G, so for rank 0 (top), that's
277
- # (3 - 0) / 4 = 0.75. Multiplier = (1 - 0.25 * 0.75) = 0.8125.
278
- # For rank 3 (bottom), (3 - 3) / 4 = 0. Multiplier = 1.0.
279
- # Schedule weight = 1.5.
280
- # Top : 1.5 * 1.0 * 0.8125 = 1.21875
281
- # Rank1 : 1.5 * 0.8 * (1 - 0.25*0.5) = 1.5 * 0.8 * 0.875 = 1.05
282
- # Rank2 : 1.5 * 0.6 * (1 - 0.25*0.25) = 1.5 * 0.6 * 0.9375 = 0.84375
283
- # Bottom: 1.5 * 0.4 * 1.0 = 0.6
284
- assert abs(scores[0] - 1.5 * 1.0 * 0.8125) < 1e-6, f"top score wrong: {scores[0]}"
285
- assert abs(scores[3] - 1.5 * 0.4 * 1.0) < 1e-6, f"bottom score wrong: {scores[3]}"
286
- unshaped_ratio = 1.0 / 0.4
287
- shaped_ratio = scores[0] / scores[3]
288
- assert shaped_ratio < unshaped_ratio, (
289
- f"unlikeliness shaping failed: shaped ratio {shaped_ratio} >= unshaped {unshaped_ratio}"
290
- )
291
 
292
 
293
- def test_unlikeliness_reward_skips_negative_samples():
294
- """Unlikeliness shaping only applies multiplicative penalty to positive
295
- rewards. Negative rewards pass through unchanged so we don't accidentally
296
- up-weight losses."""
297
  pack = build_reward_pack(total_episodes=300)
298
  pack.episode_counter[0] = 200
299
 
@@ -303,11 +306,11 @@ def test_unlikeliness_reward_skips_negative_samples():
303
  wrapped = weighted_environmental_reward(raw, pack)
304
  scores = wrapped(completions=["a", "b", "c", "d"])
305
 
306
- # Top (0.8) is penalized; the three negatives are not multiplied
307
- assert scores[0] < 1.5 * 0.8 # penalized
308
- # Negatives kept raw then weighted
309
  for s in scores[1:]:
310
- assert abs(s - (1.5 * -0.1)) < 1e-6, f"negative reward shaped: {s}"
311
 
312
 
313
  def test_r_level_bonus_applied_for_correct_high_r_predictions():
 
259
  # ─────────────────────────────────────────────────────────────────────────────
260
 
261
 
262
+ def test_unlikeliness_reward_disabled_in_run_8():
263
+ """Run 8: BETA_RANK is now 0.0 (disabled) because unlikeliness shaping
264
+ INVERTED the gradient signal for our classification-style task. Our
265
+ continuous partial-credit reward (level_accuracy × calibration) meant
266
+ top-reward-ranked samples = correct predictions, so the He et al.
267
+ penalty on top-ranked samples paid more for WRONG predictions.
268
+
269
+ With BETA_RANK=0.0, shaped rewards equal raw rewards (times the
270
+ schedule weight), so the gradient is clean.
271
+ """
272
+ from training.rewards import BETA_RANK
273
+ assert BETA_RANK == 0.0, (
274
+ f"Expected BETA_RANK=0.0 in Run 8; got {BETA_RANK}. "
275
+ "If you re-enabled unlikeliness shaping, also re-validate that it "
276
+ "doesn't invert the gradient for classification-style rewards."
277
+ )
278
+
279
  pack = build_reward_pack(total_episodes=300)
280
+ pack.episode_counter[0] = 200 # env weight = 1.5
281
 
282
  def raw_returning_spread(completions, **_):
 
283
  return [1.0, 0.8, 0.6, 0.4]
284
 
285
  wrapped = weighted_environmental_reward(raw_returning_spread, pack)
286
  scores = wrapped(completions=["a", "b", "c", "d"])
287
 
288
+ # With BETA_RANK=0.0 and no R-level bonus firing (no training_log exposed
289
+ # by the raw_fn), the wrapper is just: schedule_weight × raw_reward.
290
+ # Env weight = 1.5.
291
+ assert abs(scores[0] - 1.5 * 1.0) < 1e-6, f"top score wrong: {scores[0]}"
292
+ assert abs(scores[3] - 1.5 * 0.4) < 1e-6, f"bottom score wrong: {scores[3]}"
293
+ # Ratio of top:bottom preserved (no longer inverted by shaping)
294
+ assert abs(scores[0] / scores[3] - 1.0 / 0.4) < 1e-6
 
 
 
 
 
 
 
 
295
 
296
 
297
+ def test_unlikeliness_reward_passes_negatives_unchanged():
298
+ """With BETA_RANK=0.0, negative rewards flow through unchanged too
299
+ (previously shaping only affected positives; now nothing is shaped)."""
 
300
  pack = build_reward_pack(total_episodes=300)
301
  pack.episode_counter[0] = 200
302
 
 
306
  wrapped = weighted_environmental_reward(raw, pack)
307
  scores = wrapped(completions=["a", "b", "c", "d"])
308
 
309
+ # No penalty on top (BETA_RANK=0.0)
310
+ assert abs(scores[0] - 1.5 * 0.8) < 1e-6, f"top shouldn't be penalized now: {scores[0]}"
311
+ # Negatives still flow through
312
  for s in scores[1:]:
313
+ assert abs(s - 1.5 * -0.1) < 1e-6, f"negative reward shaped unexpectedly: {s}"
314
 
315
 
316
  def test_r_level_bonus_applied_for_correct_high_r_predictions():
tools/fetch_run8.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """One-shot script to pull Run 8 artifacts from HF Hub.
2
+
3
+ Run 8 root-cause fix:
4
+ BETA_RANK = 0.25 → 0.0 (disabled unlikeliness shaping).
5
+
6
+ Why: He et al. 2506.02355 designed unlikeliness reward for BINARY-reward
7
+ theorem proving. Our classification-style RLVR with partial-credit rewards
8
+ (level_accuracy × calibration ∈ [0,1]) INVERTED the gradient — GRPO paid
9
+ more for wrong R1 predictions than correct R2 predictions on db_snapshot
10
+ (Run 7 training log shows 0.773 vs 0.751).
11
+
12
+ Cross-reference: "Rewards as Labels: Revisiting RLVR from a Classification
13
+ Perspective" (arxiv 2602.05630) confirms GRPO's Gradient Misassignment in
14
+ Positives for classification tasks.
15
+
16
+ Everything else from Run 7 is preserved:
17
+ * β=0.04 KL (stabilized late drift)
18
+ * μ=2 PPO epochs
19
+ * 78 env-verified warmup traces
20
+ * 4 forced variants
21
+ * R-level balance bonus
22
+
23
+ Theory predictions:
24
+ * Eval accuracy: 46% (Run 7) → 85-92% (high confidence)
25
+ * R5 recall: preserved at ≥95%
26
+ * No R1 over-prediction in confusion matrix
27
+ * task_force_push_release recovered to R2
28
+
29
+ GUARDRAIL: if eval R5 recall < 95%, revert to Run 6.1 adapter.
30
+ """
31
+ from __future__ import annotations
32
+
33
+ import os
34
+ import shutil
35
+ import subprocess
36
+ from huggingface_hub import snapshot_download
37
+
38
+
39
+ TARGET_DIR = "training_runs/run_8_disable_unlikeliness"
40
+
41
+
42
+ def main() -> None:
43
+ if os.path.exists(TARGET_DIR):
44
+ shutil.rmtree(TARGET_DIR)
45
+ token = subprocess.check_output(["hf", "auth", "token"], text=True).strip()
46
+ path = snapshot_download(
47
+ repo_id="chane335/permanence-artifacts",
48
+ repo_type="dataset",
49
+ local_dir=TARGET_DIR,
50
+ token=token,
51
+ )
52
+ total = 0
53
+ for root, _dirs, files in os.walk(path):
54
+ for f in files:
55
+ rel = os.path.relpath(os.path.join(root, f), path)
56
+ if ".cache" in rel:
57
+ continue
58
+ size = os.path.getsize(os.path.join(root, f))
59
+ total += size
60
+ print(f" {size:>12,} bytes {rel}")
61
+ print(f"TOTAL: {total/1e6:.1f} MB")
62
+ print(f"\nCheck eval first: python -c \"import json; "
63
+ f"print(json.load(open('{TARGET_DIR}/eval/results.json')))\"")
64
+
65
+
66
+ if __name__ == "__main__":
67
+ main()
training/config.yaml CHANGED
@@ -1,52 +1,51 @@
1
- # PERMANENCE Training Config — Run 7 (R4/R5 calibration + git context)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  #
3
  # Architecture: 4-stage pipeline (training/pipeline.py)
4
  #
5
- # Stage 1 — SFT on 78 tech+forced warmup traces × 10 epochs
6
- # artifacts/sft/adapter/ + status.json
7
- # → gate: final_training_loss < 1.0
8
- # Run 7 trace distribution: R1=22, R2=23, R3=3, R4=7, R5=23.
9
- # Added 6 calibration traces: git_push_force → R2 (when nothing
10
- # overwritten), git_push → R2, git_commit → R2, git_filter_branch
11
- # → R4 (reflog preserves), fs_rm_rf → R4 (backup), db_truncate
12
- # → R4 (snapshot). Fixes Run 6 failure: model conflated all git
13
- # context with R4/R5 (task_force_push_release eval 6/6 predicted
14
- # R4 when actual was R2) and all destructive DB ops with R5
15
- # (37/41 actual R4 predicted as R5).
16
  #
17
  # Stage 2 — Format-coverage gate on 20 held-out prompts
18
- # → artifacts/gate/status.json
19
  # → gate: ≥80% of completions contain both tags
20
  #
21
- # Stage 3 — GRPO with 2 reward functions (dynamically weighted):
22
- # reward_format (weight: 1.0 0.0 over 150 eps)
23
- # reward_environmental (weight: 0.5 1.5 over 150 eps)
24
- #
25
- # Run 6 innovations preserved (He et al. arXiv:2506.02355):
26
- # * Unlikeliness shaping β_rank=0.25breaks GRPO rank bias
27
- # * R-level balance bonus: +0.1 per R-level above 3 for
28
- # correct rare predictions. R4 → +0.1, R5 → +0.2.
29
- # * Curriculum introduces forced-outcome variants:
30
- # eps 0– 50: standard tasks only (baseline)
31
- # eps 51–150: 50% forced (break local optimum)
32
- # eps 151–end: 70% forced (full spectrum required)
33
- # * μ=2 inner PPO-style updates per generation batch
34
- #
35
- # Length auto-abort if mean completion > 1000 chars for 3 windows
36
- # → artifacts/grpo/adapter/ + training_log.json
37
  #
38
  # Stage 4 — Held-out eval comparing scripted / SFT-only / GRPO-trained
39
- # → artifacts/eval/results.json + comparison.csv
40
  #
41
- # Run 7 theory predictions (to verify at end of training):
42
- # * Eval accuracy: 75% (Run 6.1) → 82-88%
43
- # * task_force_push_release recovered from -0.17 regression predicts R2
44
- # * R4 row accuracy in training log: 4.9% (Run 6) → 30-50%
45
- # * R5 recall held at ≥95% (if drops below, revert to Run 6.1)
46
- # * Mean reward 0.65-0.80 (between Run 5 and Run 6)
 
 
47
  #
48
- # GUARDRAIL: if eval shows R5 recall < 95% after training, revert to the
49
- # Run 6.1 adapter and ship that instead.
50
  #
51
  # Constraints:
52
  # - T4 GPU (16 GB). Llama-3.2-3B in 4-bit Unsloth + LoRA fits under 12 GB.
@@ -57,12 +56,10 @@ model_name: unsloth/Llama-3.2-3B-Instruct-bnb-4bit
57
  total_episodes: 300
58
  group_size: 4
59
  learning_rate: 4.0e-5
60
- # Run 7: β doubled 0.02 → 0.04 (TRL default). Run 6 showed prediction drift
61
- # starting at step ~135 when forced variants entered the batch mix classic
62
- # plasticity-stability dilemma (arxiv 2602.06453). Higher β constrains policy
63
- # from drifting too far from SFT reference, preventing the late-training
64
- # "git_commit R2 → R4" collapse that Run 6 exhibited (100% correct through
65
- # step 135, then degrading to 0-30% correct by step 300).
66
  kl_coefficient: 0.04
67
  gradient_clip: 1.0
68
  lora_r: 16
@@ -76,8 +73,7 @@ format_reward_cutoff: 300
76
  eval_episodes: 36
77
  eval_seed_offset: 50000
78
 
79
- # Run 6 — μ=2 inner PPO-style updates per generation batch. He et al.
80
- # 2506.02355 recommendation. TRL default is 1. Range 1..4 safe; 2 sweet spot.
81
  ppo_epochs: 2
82
 
83
  # Domain filter: devtools | meridian | (empty for mixed)
 
1
+ # PERMANENCE Training Config — Run 8 (disable unlikeliness shaping)
2
+ #
3
+ # Run 8 Root-cause fix: BETA_RANK = 0.0 (disable unlikeliness reward).
4
+ #
5
+ # Diagnosis from Run 7 training log:
6
+ # Our reward function gives partial credit for wrong-but-close predictions
7
+ # (level_accuracy × calibration ∈ [0,1]). The He et al. unlikeliness reward
8
+ # was designed for BINARY-reward theorem proving. When applied to our
9
+ # continuous-reward classification task, it PENALIZES correct high-reward
10
+ # predictions relative to wrong-but-still-positive ones. Empirical proof:
11
+ # Run 7: db_snapshot (actual R2) predicted R1 → avg reward 0.773
12
+ # db_snapshot (actual R2) predicted R2 → avg reward 0.751
13
+ # GRPO learned to predict R1 because the unlikeliness shaping paid more
14
+ # for the WRONG answer. Eval accuracy collapsed 75% → 46%.
15
+ #
16
+ # Cross-reference: "Rewards as Labels: Revisiting RLVR from a Classification
17
+ # Perspective" (arxiv 2602.05630) — GRPO already has "Gradient Misassignment
18
+ # in Positives" for classification tasks; unlikeliness shaping amplifies it.
19
  #
20
  # Architecture: 4-stage pipeline (training/pipeline.py)
21
  #
22
+ # Stage 1 — SFT on 78 env-verified tech+forced warmup traces × 10 epochs
23
+ # R1=22, R2=23, R3=3, R4=7, R5=23
24
+ # → gate coverage 100% (Run 7 proved this works)
 
 
 
 
 
 
 
 
25
  #
26
  # Stage 2 — Format-coverage gate on 20 held-out prompts
 
27
  # → gate: ≥80% of completions contain both tags
28
  #
29
+ # Stage 3 — GRPO with preserved Run 6/7 innovations:
30
+ # * β = 0.04 KL (kept from Run 7 — stabilized late drift)
31
+ # * μ = 2 inner PPO epochs (kept)
32
+ # * Forced variants curriculum (kept — broke R2-only)
33
+ # * R-level balance bonus (+0.1 per R-level for correct R4/R5)
34
+ # * Unlikeliness β_rank = 0.0 (DISABLED Run 8 root-cause fix)
35
+ # * Length auto-abort if mean > 1000 chars for 3 windows
 
 
 
 
 
 
 
 
 
36
  #
37
  # Stage 4 — Held-out eval comparing scripted / SFT-only / GRPO-trained
 
38
  #
39
+ # Run 8 theory predictions:
40
+ # * Eval accuracy: 46% (Run 7) → 85-92% (high confidence, mechanism clear)
41
+ # * R5 recall: preserved at ≥95% (forced variants still drive R5 signal)
42
+ # * Mean reward: 0.64 (Run 7) → 0.65-0.75 (small improvement, same training)
43
+ # * No R1 over-prediction (smoking-gun reward inversion is gone)
44
+ # * No R4/R5 conflation regression (β=0.04 still holds)
45
+ # * task_force_push_release: correct R2 on all 6 scenarios
46
+ # * task_schema_migration: correct R2 on all 6 scenarios
47
  #
48
+ # GUARDRAIL: if eval R5 recall < 95%, revert to Run 6.1 adapter.
 
49
  #
50
  # Constraints:
51
  # - T4 GPU (16 GB). Llama-3.2-3B in 4-bit Unsloth + LoRA fits under 12 GB.
 
56
  total_episodes: 300
57
  group_size: 4
58
  learning_rate: 4.0e-5
59
+
60
+ # Run 7: β doubled 0.02 0.04 (TRL default) stabilized late-training drift.
61
+ # Run 8: kept at 0.04 this change is proven to help, independent of the
62
+ # unlikeliness bug.
 
 
63
  kl_coefficient: 0.04
64
  gradient_clip: 1.0
65
  lora_r: 16
 
73
  eval_episodes: 36
74
  eval_seed_offset: 50000
75
 
76
+ # μ=2 inner PPO-style updates per generation batch. Kept from Run 7.
 
77
  ppo_epochs: 2
78
 
79
  # Domain filter: devtools | meridian | (empty for mixed)
training/rewards.py CHANGED
@@ -58,7 +58,33 @@ CONFIDENCE_RE = re.compile(r"confidence=[\"']([0-9.]+)[\"']", re.IGNORECASE)
58
  THINKING_RE = re.compile(r"<thinking>.*?</thinking>", re.IGNORECASE | re.DOTALL)
59
 
60
  # Run 6 hyperparameters — from He et al. 2506.02355
61
- BETA_RANK = 0.25 # Unlikeliness reward strength (paper uses same value)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  R_LEVEL_BONUS_PER_LEVEL = 0.1 # Additive bonus per R-level of correct rare prediction
63
 
64
 
 
58
  THINKING_RE = re.compile(r"<thinking>.*?</thinking>", re.IGNORECASE | re.DOTALL)
59
 
60
  # Run 6 hyperparameters — from He et al. 2506.02355
61
+ #
62
+ # Run 8 update: BETA_RANK set to 0.0 (disabled).
63
+ #
64
+ # Research basis: The unlikeliness-reward technique in He et al. was designed
65
+ # for FORMAL THEOREM PROVING with BINARY rewards (proof works / doesn't).
66
+ # Our task is a classification-style RLVR with CONTINUOUS partial-credit
67
+ # rewards (level_accuracy × calibration in [0, 1]). Applying unlikeliness
68
+ # to our continuous-reward setting has the opposite of the intended effect:
69
+ # it penalizes correct, confident predictions (high reward) relative to
70
+ # wrong-but-close predictions (lower but still positive reward).
71
+ #
72
+ # Evidence from Run 7 training log:
73
+ # db_snapshot (actual R2) with predicted R1 → avg reward 0.773
74
+ # db_snapshot (actual R2) with predicted R2 → avg reward 0.751
75
+ # The unlikeliness shaping inverted the gradient: R1 paid MORE than R2.
76
+ # GRPO learned to predict R1 and eval accuracy dropped to 46%.
77
+ #
78
+ # Cross-reference: "Rewards as Labels: Revisiting RLVR from a Classification
79
+ # Perspective" (arxiv 2602.05630) identifies GRPO's "Gradient Misassignment
80
+ # in Positives" for classification tasks. Unlikeliness shaping amplifies
81
+ # this pathology rather than fixing it in our setting.
82
+ #
83
+ # Setting BETA_RANK=0.0 disables the shaping entirely. The forced variants
84
+ # + R-level balance bonus still address the Run 5 R2-only collapse. Our
85
+ # classification-style reward already has a clear gradient signal without
86
+ # needing unlikeliness to surface rare samples.
87
+ BETA_RANK = 0.0 # Run 8: disabled (was 0.25 in Runs 6 and 7)
88
  R_LEVEL_BONUS_PER_LEVEL = 0.1 # Additive bonus per R-level of correct rare prediction
89
 
90