AnonMLuser commited on
Commit
ae0d0fd
·
verified ·
1 Parent(s): 5f9861b

Refresh artefacts and code for the second review release

Browse files
Files changed (34) hide show
  1. .gitattributes +0 -234
  2. .gitignore +1 -0
  3. Craftax_Baselines/Dockerfile +2 -2
  4. Craftax_Baselines/run_docker.sh +1 -1
  5. Dockerfile +10 -5
  6. LICENSE +2 -1
  7. README.md +194 -146
  8. checkpoints/offline/Craftax-Classic-Symbolic-v1-Offline-Diffusion-BC-100M/99942400/commit_success.txt +1 -1
  9. checkpoints/offline/Craftax-Classic-Symbolic-v1-Offline-Diffusion-BC-100M/99942400/default/commit_success.txt +1 -1
  10. checkpoints/offline/Craftax-Classic-Symbolic-v1-Offline-Diffusion-BC-100M/resume_metadata.json +1 -7
  11. checkpoints/online/Craftax-Classic-Symbolic-v1-Online-Diffusion-DAgger-100M/40370176/commit_success.txt +1 -1
  12. checkpoints/online/Craftax-Classic-Symbolic-v1-Online-Diffusion-DAgger-100M/40370176/default/commit_success.txt +1 -1
  13. checkpoints/online/Craftax-Classic-Symbolic-v1-Online-Diffusion-DAgger-100M/resume_metadata.json +0 -6
  14. checkpoints/ppo_agents/Craftax-Classic-Symbolic-v1-PPO_RNN-1000M/wandb-summary.json +31 -1
  15. checkpoints/ppo_agents/Craftax-Symbolic-v1-PPO_RNN-1000M/config.yaml +0 -6
  16. checkpoints/ppo_agents/Craftax-Symbolic-v1-PPO_RNN-1000M/wandb-summary.json +76 -1
  17. configs/defaults.yaml +10 -0
  18. demo_craftax.ipynb +275 -31
  19. experiments/README.md +144 -37
  20. experiments/rl_finetuning/ablations/losses.py +29 -0
  21. experiments/rl_finetuning/ablations/registry.py +10 -0
  22. experiments/rl_finetuning/ablations/training.py +10 -7
  23. experiments/rl_finetuning/analysis/gdelta.py +548 -0
  24. experiments/rl_finetuning/analysis/plots.py +4 -2
  25. experiments/rl_finetuning/analysis/tables.py +345 -9
  26. experiments/rl_finetuning/configs/ablations_final_craftax_classic_gpu_24gb.yaml +2 -2
  27. experiments/rl_finetuning/configs/ablations_final_craftax_classic_gpu_h200.yaml +1 -1
  28. experiments/rl_finetuning/configs/ablations_final_craftax_gpu_24gb.yaml +2 -2
  29. experiments/rl_finetuning/configs/ablations_final_craftax_gpu_h200.yaml +1 -1
  30. experiments/rl_finetuning/outputs/craftax_classic_ablations/gdelta/gdelta_aggregate.json +103 -0
  31. experiments/rl_finetuning/outputs/craftax_classic_ablations/gdelta/gdelta_seed0.json +74 -0
  32. experiments/rl_finetuning/outputs/craftax_classic_ablations/gdelta/gdelta_seed1.json +74 -0
  33. experiments/rl_finetuning/outputs/craftax_classic_ablations/gdelta/gdelta_seed2.json +74 -0
  34. experiments/rl_finetuning/outputs/craftax_classic_ablations/results.json +6 -11
.gitattributes CHANGED
@@ -33,237 +33,3 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
- checkpoints/offline/Craftax-Classic-Symbolic-v1-Offline-Diffusion-BC-100M/99942400/default/ocdbt.process_0/d/503349be2d11cb1bf04a17e7b3e2d1e0 filter=lfs diff=lfs merge=lfs -text
37
- checkpoints/offline/Craftax-Classic-Symbolic-v1-Offline-Diffusion-BC-100M/99942400/default/ocdbt.process_0/d/781b85e3ec447139f7d23160643a9c21 filter=lfs diff=lfs merge=lfs -text
38
- checkpoints/offline/Craftax-Classic-Symbolic-v1-Offline-Diffusion-BC-100M/99942400/default/ocdbt.process_0/d/92adc334c11103c7741baed0d71e66cb filter=lfs diff=lfs merge=lfs -text
39
- checkpoints/online/Craftax-Classic-Symbolic-v1-Online-Diffusion-DAgger-100M/100000000/default/ocdbt.process_0/d/35a15297533c9b116fa22b2bb1975990 filter=lfs diff=lfs merge=lfs -text
40
- checkpoints/online/Craftax-Classic-Symbolic-v1-Online-Diffusion-DAgger-100M/100000000/default/ocdbt.process_0/d/9778194f691de46821092616cc3a1ff2 filter=lfs diff=lfs merge=lfs -text
41
- checkpoints/ppo_agents/Craftax-Classic-Symbolic-v1-PPO_RNN-1000M/1000000000/default/ocdbt.process_0/d/369457b7c6608f1adf28eb88024d6b91 filter=lfs diff=lfs merge=lfs -text
42
- checkpoints/ppo_agents/Craftax-Classic-Symbolic-v1-PPO_RNN-1000M/1000000000/default/ocdbt.process_0/d/6bb110a840637eede93e25f5356236f9 filter=lfs diff=lfs merge=lfs -text
43
- checkpoints/ppo_agents/Craftax-Symbolic-v1-PPO_RNN-1000M/1000000000/default/ocdbt.process_0/d/e5a4020f50167115120fe5dac41c20fb filter=lfs diff=lfs merge=lfs -text
44
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/achievement_breakdown.png filter=lfs diff=lfs merge=lfs -text
45
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/achievement_collapse_action_diversity.png filter=lfs diff=lfs merge=lfs -text
46
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/achievement_collapse_advantage_clip.png filter=lfs diff=lfs merge=lfs -text
47
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/achievement_collapse_attention_only.png filter=lfs diff=lfs merge=lfs -text
48
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/achievement_collapse_baseline_rl.png filter=lfs diff=lfs merge=lfs -text
49
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/achievement_collapse_bc_wins.png filter=lfs diff=lfs merge=lfs -text
50
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/achievement_collapse_entropy_bonus.png filter=lfs diff=lfs merge=lfs -text
51
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/achievement_collapse_ewc.png filter=lfs diff=lfs merge=lfs -text
52
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/achievement_collapse_ffn_only.png filter=lfs diff=lfs merge=lfs -text
53
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/achievement_collapse_frozen_backbone.png filter=lfs diff=lfs merge=lfs -text
54
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/achievement_collapse_gradient_surgery.png filter=lfs diff=lfs merge=lfs -text
55
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/achievement_collapse_head_only.png filter=lfs diff=lfs merge=lfs -text
56
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/achievement_collapse_kl_penalty.png filter=lfs diff=lfs merge=lfs -text
57
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/achievement_collapse_layer_ablation_top1.png filter=lfs diff=lfs merge=lfs -text
58
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/achievement_collapse_layer_ablation_top2.png filter=lfs diff=lfs merge=lfs -text
59
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/achievement_collapse_layer_ablation_top3.png filter=lfs diff=lfs merge=lfs -text
60
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/achievement_collapse_llrd.png filter=lfs diff=lfs merge=lfs -text
61
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/achievement_collapse_lora.png filter=lfs diff=lfs merge=lfs -text
62
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/achievement_collapse_low_t.png filter=lfs diff=lfs merge=lfs -text
63
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/achievement_collapse_mixed_replay.png filter=lfs diff=lfs merge=lfs -text
64
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/achievement_collapse_normalized_adv.png filter=lfs diff=lfs merge=lfs -text
65
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/achievement_collapse_reward_filtering.png filter=lfs diff=lfs merge=lfs -text
66
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/achievement_collapse_reward_model.png filter=lfs diff=lfs merge=lfs -text
67
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/achievement_collapse_running_stats.png filter=lfs diff=lfs merge=lfs -text
68
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/achievement_collapse_t_curriculum.png filter=lfs diff=lfs merge=lfs -text
69
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/achievement_collapse_trust_region_kl.png filter=lfs diff=lfs merge=lfs -text
70
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/action_dist/js_divergence_comparison.png filter=lfs diff=lfs merge=lfs -text
71
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/cka_similarity.png filter=lfs diff=lfs merge=lfs -text
72
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/curves_action_diversity.png filter=lfs diff=lfs merge=lfs -text
73
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/curves_advantage_clip.png filter=lfs diff=lfs merge=lfs -text
74
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/curves_attention_only.png filter=lfs diff=lfs merge=lfs -text
75
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/curves_baseline_rl.png filter=lfs diff=lfs merge=lfs -text
76
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/curves_bc_wins.png filter=lfs diff=lfs merge=lfs -text
77
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/curves_entropy_bonus.png filter=lfs diff=lfs merge=lfs -text
78
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/curves_ewc.png filter=lfs diff=lfs merge=lfs -text
79
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/curves_ffn_only.png filter=lfs diff=lfs merge=lfs -text
80
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/curves_frozen_backbone.png filter=lfs diff=lfs merge=lfs -text
81
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/curves_gradient_surgery.png filter=lfs diff=lfs merge=lfs -text
82
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/curves_head_only.png filter=lfs diff=lfs merge=lfs -text
83
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/curves_kl_penalty.png filter=lfs diff=lfs merge=lfs -text
84
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/curves_layer_ablation_top1.png filter=lfs diff=lfs merge=lfs -text
85
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/curves_layer_ablation_top2.png filter=lfs diff=lfs merge=lfs -text
86
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/curves_layer_ablation_top3.png filter=lfs diff=lfs merge=lfs -text
87
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/curves_llrd.png filter=lfs diff=lfs merge=lfs -text
88
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/curves_lora.png filter=lfs diff=lfs merge=lfs -text
89
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/curves_low_t.png filter=lfs diff=lfs merge=lfs -text
90
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/curves_mixed_replay.png filter=lfs diff=lfs merge=lfs -text
91
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/curves_normalized_adv.png filter=lfs diff=lfs merge=lfs -text
92
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/curves_reward_filtering.png filter=lfs diff=lfs merge=lfs -text
93
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/curves_reward_model.png filter=lfs diff=lfs merge=lfs -text
94
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/curves_running_stats.png filter=lfs diff=lfs merge=lfs -text
95
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/curves_t_curriculum.png filter=lfs diff=lfs merge=lfs -text
96
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/curves_trust_region_kl.png filter=lfs diff=lfs merge=lfs -text
97
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/eval_scores_over_training.png filter=lfs diff=lfs merge=lfs -text
98
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/final_score_comparison.png filter=lfs diff=lfs merge=lfs -text
99
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/gradient_alignment.png filter=lfs diff=lfs merge=lfs -text
100
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/gradient_conflict_map.png filter=lfs diff=lfs merge=lfs -text
101
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/per_layer_grad_heatmap_action_diversity.png filter=lfs diff=lfs merge=lfs -text
102
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/per_layer_grad_heatmap_advantage_clip.png filter=lfs diff=lfs merge=lfs -text
103
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/per_layer_grad_heatmap_attention_only.png filter=lfs diff=lfs merge=lfs -text
104
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/per_layer_grad_heatmap_baseline_rl.png filter=lfs diff=lfs merge=lfs -text
105
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/per_layer_grad_heatmap_bc_wins.png filter=lfs diff=lfs merge=lfs -text
106
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/per_layer_grad_heatmap_entropy_bonus.png filter=lfs diff=lfs merge=lfs -text
107
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/per_layer_grad_heatmap_ewc.png filter=lfs diff=lfs merge=lfs -text
108
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/per_layer_grad_heatmap_ffn_only.png filter=lfs diff=lfs merge=lfs -text
109
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/per_layer_grad_heatmap_frozen_backbone.png filter=lfs diff=lfs merge=lfs -text
110
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/per_layer_grad_heatmap_gradient_surgery.png filter=lfs diff=lfs merge=lfs -text
111
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/per_layer_grad_heatmap_head_only.png filter=lfs diff=lfs merge=lfs -text
112
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/per_layer_grad_heatmap_kl_penalty.png filter=lfs diff=lfs merge=lfs -text
113
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/per_layer_grad_heatmap_layer_ablation_top1.png filter=lfs diff=lfs merge=lfs -text
114
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/per_layer_grad_heatmap_layer_ablation_top2.png filter=lfs diff=lfs merge=lfs -text
115
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/per_layer_grad_heatmap_layer_ablation_top3.png filter=lfs diff=lfs merge=lfs -text
116
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/per_layer_grad_heatmap_llrd.png filter=lfs diff=lfs merge=lfs -text
117
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/per_layer_grad_heatmap_lora.png filter=lfs diff=lfs merge=lfs -text
118
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/per_layer_grad_heatmap_low_t.png filter=lfs diff=lfs merge=lfs -text
119
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/per_layer_grad_heatmap_mixed_replay.png filter=lfs diff=lfs merge=lfs -text
120
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/per_layer_grad_heatmap_normalized_adv.png filter=lfs diff=lfs merge=lfs -text
121
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/per_layer_grad_heatmap_reward_filtering.png filter=lfs diff=lfs merge=lfs -text
122
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/per_layer_grad_heatmap_reward_model.png filter=lfs diff=lfs merge=lfs -text
123
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/per_layer_grad_heatmap_running_stats.png filter=lfs diff=lfs merge=lfs -text
124
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/per_layer_grad_heatmap_t_curriculum.png filter=lfs diff=lfs merge=lfs -text
125
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/per_layer_grad_heatmap_trust_region_kl.png filter=lfs diff=lfs merge=lfs -text
126
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/representation_drift.png filter=lfs diff=lfs merge=lfs -text
127
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/score_delta_over_baseline_rl.png filter=lfs diff=lfs merge=lfs -text
128
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/t_bin_grad_norms_action_diversity.png filter=lfs diff=lfs merge=lfs -text
129
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/t_bin_grad_norms_advantage_clip.png filter=lfs diff=lfs merge=lfs -text
130
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/t_bin_grad_norms_attention_only.png filter=lfs diff=lfs merge=lfs -text
131
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/t_bin_grad_norms_baseline_rl.png filter=lfs diff=lfs merge=lfs -text
132
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/t_bin_grad_norms_bc_wins.png filter=lfs diff=lfs merge=lfs -text
133
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/t_bin_grad_norms_entropy_bonus.png filter=lfs diff=lfs merge=lfs -text
134
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/t_bin_grad_norms_ewc.png filter=lfs diff=lfs merge=lfs -text
135
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/t_bin_grad_norms_ffn_only.png filter=lfs diff=lfs merge=lfs -text
136
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/t_bin_grad_norms_frozen_backbone.png filter=lfs diff=lfs merge=lfs -text
137
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/t_bin_grad_norms_gradient_surgery.png filter=lfs diff=lfs merge=lfs -text
138
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/t_bin_grad_norms_head_only.png filter=lfs diff=lfs merge=lfs -text
139
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/t_bin_grad_norms_kl_penalty.png filter=lfs diff=lfs merge=lfs -text
140
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/t_bin_grad_norms_layer_ablation_top1.png filter=lfs diff=lfs merge=lfs -text
141
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/t_bin_grad_norms_layer_ablation_top2.png filter=lfs diff=lfs merge=lfs -text
142
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/t_bin_grad_norms_layer_ablation_top3.png filter=lfs diff=lfs merge=lfs -text
143
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/t_bin_grad_norms_llrd.png filter=lfs diff=lfs merge=lfs -text
144
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/t_bin_grad_norms_lora.png filter=lfs diff=lfs merge=lfs -text
145
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/t_bin_grad_norms_low_t.png filter=lfs diff=lfs merge=lfs -text
146
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/t_bin_grad_norms_mixed_replay.png filter=lfs diff=lfs merge=lfs -text
147
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/t_bin_grad_norms_normalized_adv.png filter=lfs diff=lfs merge=lfs -text
148
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/t_bin_grad_norms_reward_filtering.png filter=lfs diff=lfs merge=lfs -text
149
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/t_bin_grad_norms_reward_model.png filter=lfs diff=lfs merge=lfs -text
150
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/t_bin_grad_norms_running_stats.png filter=lfs diff=lfs merge=lfs -text
151
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/t_bin_grad_norms_t_curriculum.png filter=lfs diff=lfs merge=lfs -text
152
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/t_bin_grad_norms_trust_region_kl.png filter=lfs diff=lfs merge=lfs -text
153
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/t_bin_norms_heatmap.png filter=lfs diff=lfs merge=lfs -text
154
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/t_distribution_analysis.png filter=lfs diff=lfs merge=lfs -text
155
- experiments/rl_finetuning/outputs/craftax_classic_final_results/analysis/figures/win_rate_and_effective_batch_size.png filter=lfs diff=lfs merge=lfs -text
156
- checkpoints/offline/Craftax-Classic-Symbolic-v1-Offline-Diffusion-BC-100M/99942400/default/ocdbt.process_0/d/1b7ef41abe2bf7d133b20680543c1738 filter=lfs diff=lfs merge=lfs -text
157
- checkpoints/offline/Craftax-Classic-Symbolic-v1-Offline-Diffusion-BC-100M/99942400/default/ocdbt.process_0/d/8f77d89a5c0896490dcc8e0a7a3e001f filter=lfs diff=lfs merge=lfs -text
158
- checkpoints/online/Craftax-Classic-Symbolic-v1-Online-Diffusion-DAgger-100M/40370176/default/ocdbt.process_0/d/8bf3ebb1b364b9527375993e4a94621d filter=lfs diff=lfs merge=lfs -text
159
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/achievement_breakdown.png filter=lfs diff=lfs merge=lfs -text
160
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/achievement_collapse_action_diversity.png filter=lfs diff=lfs merge=lfs -text
161
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/achievement_collapse_advantage_clip.png filter=lfs diff=lfs merge=lfs -text
162
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/achievement_collapse_attention_only.png filter=lfs diff=lfs merge=lfs -text
163
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/achievement_collapse_baseline_rl.png filter=lfs diff=lfs merge=lfs -text
164
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/achievement_collapse_bc_wins.png filter=lfs diff=lfs merge=lfs -text
165
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/achievement_collapse_entropy_bonus.png filter=lfs diff=lfs merge=lfs -text
166
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/achievement_collapse_ewc.png filter=lfs diff=lfs merge=lfs -text
167
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/achievement_collapse_ffn_only.png filter=lfs diff=lfs merge=lfs -text
168
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/achievement_collapse_frozen_backbone.png filter=lfs diff=lfs merge=lfs -text
169
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/achievement_collapse_gradient_surgery.png filter=lfs diff=lfs merge=lfs -text
170
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/achievement_collapse_head_only.png filter=lfs diff=lfs merge=lfs -text
171
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/achievement_collapse_kl_penalty.png filter=lfs diff=lfs merge=lfs -text
172
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/achievement_collapse_layer_ablation_top1.png filter=lfs diff=lfs merge=lfs -text
173
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/achievement_collapse_layer_ablation_top2.png filter=lfs diff=lfs merge=lfs -text
174
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/achievement_collapse_layer_ablation_top3.png filter=lfs diff=lfs merge=lfs -text
175
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/achievement_collapse_llrd.png filter=lfs diff=lfs merge=lfs -text
176
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/achievement_collapse_lora.png filter=lfs diff=lfs merge=lfs -text
177
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/achievement_collapse_low_t.png filter=lfs diff=lfs merge=lfs -text
178
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/achievement_collapse_mixed_replay.png filter=lfs diff=lfs merge=lfs -text
179
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/achievement_collapse_normalized_adv.png filter=lfs diff=lfs merge=lfs -text
180
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/achievement_collapse_reward_filtering.png filter=lfs diff=lfs merge=lfs -text
181
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/achievement_collapse_reward_model.png filter=lfs diff=lfs merge=lfs -text
182
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/achievement_collapse_running_stats.png filter=lfs diff=lfs merge=lfs -text
183
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/achievement_collapse_t_curriculum.png filter=lfs diff=lfs merge=lfs -text
184
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/achievement_collapse_trust_region_kl.png filter=lfs diff=lfs merge=lfs -text
185
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/cka_similarity.png filter=lfs diff=lfs merge=lfs -text
186
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/curves_action_diversity.png filter=lfs diff=lfs merge=lfs -text
187
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/curves_advantage_clip.png filter=lfs diff=lfs merge=lfs -text
188
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/curves_attention_only.png filter=lfs diff=lfs merge=lfs -text
189
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/curves_baseline_rl.png filter=lfs diff=lfs merge=lfs -text
190
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/curves_bc_wins.png filter=lfs diff=lfs merge=lfs -text
191
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/curves_entropy_bonus.png filter=lfs diff=lfs merge=lfs -text
192
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/curves_ewc.png filter=lfs diff=lfs merge=lfs -text
193
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/curves_ffn_only.png filter=lfs diff=lfs merge=lfs -text
194
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/curves_frozen_backbone.png filter=lfs diff=lfs merge=lfs -text
195
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/curves_gradient_surgery.png filter=lfs diff=lfs merge=lfs -text
196
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/curves_head_only.png filter=lfs diff=lfs merge=lfs -text
197
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/curves_kl_penalty.png filter=lfs diff=lfs merge=lfs -text
198
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/curves_layer_ablation_top1.png filter=lfs diff=lfs merge=lfs -text
199
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/curves_layer_ablation_top2.png filter=lfs diff=lfs merge=lfs -text
200
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/curves_layer_ablation_top3.png filter=lfs diff=lfs merge=lfs -text
201
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/curves_llrd.png filter=lfs diff=lfs merge=lfs -text
202
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/curves_lora.png filter=lfs diff=lfs merge=lfs -text
203
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/curves_low_t.png filter=lfs diff=lfs merge=lfs -text
204
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/curves_mixed_replay.png filter=lfs diff=lfs merge=lfs -text
205
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/curves_normalized_adv.png filter=lfs diff=lfs merge=lfs -text
206
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/curves_reward_filtering.png filter=lfs diff=lfs merge=lfs -text
207
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/curves_reward_model.png filter=lfs diff=lfs merge=lfs -text
208
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/curves_running_stats.png filter=lfs diff=lfs merge=lfs -text
209
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/curves_t_curriculum.png filter=lfs diff=lfs merge=lfs -text
210
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/curves_trust_region_kl.png filter=lfs diff=lfs merge=lfs -text
211
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/eval_scores_over_training.png filter=lfs diff=lfs merge=lfs -text
212
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/final_score_comparison.png filter=lfs diff=lfs merge=lfs -text
213
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/gradient_alignment.png filter=lfs diff=lfs merge=lfs -text
214
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/gradient_conflict_map.png filter=lfs diff=lfs merge=lfs -text
215
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/per_layer_grad_heatmap_action_diversity.png filter=lfs diff=lfs merge=lfs -text
216
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/per_layer_grad_heatmap_advantage_clip.png filter=lfs diff=lfs merge=lfs -text
217
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/per_layer_grad_heatmap_attention_only.png filter=lfs diff=lfs merge=lfs -text
218
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/per_layer_grad_heatmap_baseline_rl.png filter=lfs diff=lfs merge=lfs -text
219
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/per_layer_grad_heatmap_bc_wins.png filter=lfs diff=lfs merge=lfs -text
220
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/per_layer_grad_heatmap_entropy_bonus.png filter=lfs diff=lfs merge=lfs -text
221
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/per_layer_grad_heatmap_ewc.png filter=lfs diff=lfs merge=lfs -text
222
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/per_layer_grad_heatmap_ffn_only.png filter=lfs diff=lfs merge=lfs -text
223
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/per_layer_grad_heatmap_frozen_backbone.png filter=lfs diff=lfs merge=lfs -text
224
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/per_layer_grad_heatmap_gradient_surgery.png filter=lfs diff=lfs merge=lfs -text
225
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/per_layer_grad_heatmap_head_only.png filter=lfs diff=lfs merge=lfs -text
226
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/per_layer_grad_heatmap_kl_penalty.png filter=lfs diff=lfs merge=lfs -text
227
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/per_layer_grad_heatmap_layer_ablation_top1.png filter=lfs diff=lfs merge=lfs -text
228
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/per_layer_grad_heatmap_layer_ablation_top2.png filter=lfs diff=lfs merge=lfs -text
229
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/per_layer_grad_heatmap_layer_ablation_top3.png filter=lfs diff=lfs merge=lfs -text
230
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/per_layer_grad_heatmap_llrd.png filter=lfs diff=lfs merge=lfs -text
231
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/per_layer_grad_heatmap_lora.png filter=lfs diff=lfs merge=lfs -text
232
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/per_layer_grad_heatmap_low_t.png filter=lfs diff=lfs merge=lfs -text
233
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/per_layer_grad_heatmap_mixed_replay.png filter=lfs diff=lfs merge=lfs -text
234
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/per_layer_grad_heatmap_normalized_adv.png filter=lfs diff=lfs merge=lfs -text
235
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/per_layer_grad_heatmap_reward_filtering.png filter=lfs diff=lfs merge=lfs -text
236
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/per_layer_grad_heatmap_reward_model.png filter=lfs diff=lfs merge=lfs -text
237
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/per_layer_grad_heatmap_running_stats.png filter=lfs diff=lfs merge=lfs -text
238
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/per_layer_grad_heatmap_t_curriculum.png filter=lfs diff=lfs merge=lfs -text
239
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/per_layer_grad_heatmap_trust_region_kl.png filter=lfs diff=lfs merge=lfs -text
240
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/representation_drift.png filter=lfs diff=lfs merge=lfs -text
241
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/score_delta_over_baseline_rl.png filter=lfs diff=lfs merge=lfs -text
242
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/t_bin_grad_norms_action_diversity.png filter=lfs diff=lfs merge=lfs -text
243
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/t_bin_grad_norms_advantage_clip.png filter=lfs diff=lfs merge=lfs -text
244
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/t_bin_grad_norms_attention_only.png filter=lfs diff=lfs merge=lfs -text
245
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/t_bin_grad_norms_baseline_rl.png filter=lfs diff=lfs merge=lfs -text
246
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/t_bin_grad_norms_bc_wins.png filter=lfs diff=lfs merge=lfs -text
247
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/t_bin_grad_norms_entropy_bonus.png filter=lfs diff=lfs merge=lfs -text
248
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/t_bin_grad_norms_ewc.png filter=lfs diff=lfs merge=lfs -text
249
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/t_bin_grad_norms_ffn_only.png filter=lfs diff=lfs merge=lfs -text
250
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/t_bin_grad_norms_frozen_backbone.png filter=lfs diff=lfs merge=lfs -text
251
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/t_bin_grad_norms_gradient_surgery.png filter=lfs diff=lfs merge=lfs -text
252
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/t_bin_grad_norms_head_only.png filter=lfs diff=lfs merge=lfs -text
253
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/t_bin_grad_norms_kl_penalty.png filter=lfs diff=lfs merge=lfs -text
254
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/t_bin_grad_norms_layer_ablation_top1.png filter=lfs diff=lfs merge=lfs -text
255
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/t_bin_grad_norms_layer_ablation_top2.png filter=lfs diff=lfs merge=lfs -text
256
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/t_bin_grad_norms_layer_ablation_top3.png filter=lfs diff=lfs merge=lfs -text
257
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/t_bin_grad_norms_llrd.png filter=lfs diff=lfs merge=lfs -text
258
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/t_bin_grad_norms_lora.png filter=lfs diff=lfs merge=lfs -text
259
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/t_bin_grad_norms_low_t.png filter=lfs diff=lfs merge=lfs -text
260
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/t_bin_grad_norms_mixed_replay.png filter=lfs diff=lfs merge=lfs -text
261
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/t_bin_grad_norms_normalized_adv.png filter=lfs diff=lfs merge=lfs -text
262
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/t_bin_grad_norms_reward_filtering.png filter=lfs diff=lfs merge=lfs -text
263
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/t_bin_grad_norms_reward_model.png filter=lfs diff=lfs merge=lfs -text
264
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/t_bin_grad_norms_running_stats.png filter=lfs diff=lfs merge=lfs -text
265
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/t_bin_grad_norms_t_curriculum.png filter=lfs diff=lfs merge=lfs -text
266
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/t_bin_grad_norms_trust_region_kl.png filter=lfs diff=lfs merge=lfs -text
267
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/t_bin_norms_heatmap.png filter=lfs diff=lfs merge=lfs -text
268
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/t_distribution_analysis.png filter=lfs diff=lfs merge=lfs -text
269
- experiments/rl_finetuning/outputs/craftax_classic_ablations/figures/win_rate_and_effective_batch_size.png filter=lfs diff=lfs merge=lfs -text
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.gitignore CHANGED
@@ -246,3 +246,4 @@ CLI_CHANGES.md
246
 
247
  # Claude Code: per-machine settings stay local; the rest of .claude/ is shared
248
  .claude/settings.local.json
 
 
246
 
247
  # Claude Code: per-machine settings stay local; the rest of .claude/ is shared
248
  .claude/settings.local.json
249
+ results/
Craftax_Baselines/Dockerfile CHANGED
@@ -33,9 +33,9 @@ USER duser
33
  WORKDIR /home/duser
34
 
35
  # Install Python packages
36
- ENV PATH="/home/duser/.local/bin:$PATH"
37
  RUN python3 -m pip install --upgrade pip
38
  ARG REQS
39
  RUN pip install $REQS -f https://storage.googleapis.com/jax-releases/jax_cuda_releases.html
40
 
41
- WORKDIR /home/duser/Craftax
 
33
  WORKDIR /home/duser
34
 
35
  # Install Python packages
36
+ ENV PATH="<path omitted for anonymous review>"
37
  RUN python3 -m pip install --upgrade pip
38
  ARG REQS
39
  RUN pip install $REQS -f https://storage.googleapis.com/jax-releases/jax_cuda_releases.html
40
 
41
+ WORKDIR <path omitted for anonymous review>
Craftax_Baselines/run_docker.sh CHANGED
@@ -14,7 +14,7 @@ for gpu in $gpus; do
14
  docker run \
15
  --gpus device=$gpu \
16
  -e WANDB_API_KEY=$WANDB_API_KEY \
17
- -v $(pwd):/home/duser/Craftax \
18
  --name craftax_$gpu \
19
  --user $(id -u) \
20
  --rm \
 
14
  docker run \
15
  --gpus device=$gpu \
16
  -e WANDB_API_KEY=$WANDB_API_KEY \
17
+ -v $(pwd):<path omitted for anonymous review> \
18
  --name craftax_$gpu \
19
  --user $(id -u) \
20
  --rm \
Dockerfile CHANGED
@@ -1,4 +1,4 @@
1
- # craftax: vast.ai image.
2
  #
3
  # Build and push (amd64 is mandatory: vast GPU hosts are x86, an arm64 image
4
  # from an Apple Silicon Mac will not run):
@@ -59,7 +59,12 @@ RUN cp uv.lock /opt/venv/baked-uv.lock
59
  WORKDIR /workspace
60
 
61
  # --- optional: bake the released checkpoints ------------------------------
62
- # Left out on purpose. 470 MB unpacks into a large small-file layer, which is
63
- # the slowest kind to pull and a likely cause of stalled image pulls. Mount or
64
- # copy a local checkpoints/ directory into /opt/checkpoints instead. Released
65
- # weights are withheld for anonymous review; see README.md.
 
 
 
 
 
 
1
+ # remdm-planner-craftax: vast.ai image.
2
  #
3
  # Build and push (amd64 is mandatory: vast GPU hosts are x86, an arm64 image
4
  # from an Apple Silicon Mac will not run):
 
59
  WORKDIR /workspace
60
 
61
  # --- optional: bake the released checkpoints ------------------------------
62
+ # Left out on purpose. 470 MB on the Hub unpacks into a large small-file layer,
63
+ # which is the slowest kind to pull and a likely cause of stalled image pulls.
64
+ # Fetching them at boot takes under a minute. If you do want them baked, use an
65
+ # isolated env so the pinned huggingface_hub in /opt/venv is not disturbed:
66
+ #
67
+ # RUN HF_HOME=/tmp/hf uvx --from "huggingface_hub[cli]" \
68
+ # hf download AnonMLuser/remdm-planner-craftax \
69
+ # --include "checkpoints/**" --local-dir /opt/checkpoints \
70
+ # && rm -rf /tmp/hf
LICENSE CHANGED
@@ -1,6 +1,7 @@
1
  MIT License
2
 
3
- Copyright (c) 2026 The authors of "Return-Weighted ELBO Fine-Tuning of Discrete Diffusion Planners Reduces to Self-Imitation"
 
4
 
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
  of this software and associated documentation files (the "Software"), to deal
 
1
  MIT License
2
 
3
+ Copyright (c) 2026 The authors of "Return-Weighted ELBO Fine-Tuning Degrades
4
+ Masked Diffusion Planners"
5
 
6
  Permission is hereby granted, free of charge, to any person obtaining a copy
7
  of this software and associated documentation files (the "Software"), to deal
README.md CHANGED
@@ -38,13 +38,24 @@ checkpoints/
38
  ablation fine-tunes)
39
  ppo_agents/ PPO-RNN experts
40
 
41
- experiments/rl_finetuning/outputs/craftax_classic_ablations/
42
  figures/ 113 pre-computed PNGs
43
  tables/ main_results.csv, group_summary.csv, hypothesis_verdict.csv,
44
- achievement_summary.csv, gradient_analysis.csv, significance_test.txt, ...
45
- results.json
46
-
47
- results/inference/ Single-seed evaluations through the inpainting sampler (see below)
 
 
 
 
 
 
 
 
 
 
 
48
  ```
49
 
50
  ## Headline numbers (Craftax Classic)
@@ -59,6 +70,39 @@ results/inference/ Single-seed evaluations through the inpainting sampler (see
59
  No condition of the 25 recovers the checkpoint it started from. Group means are
60
  9.74 (A), 7.07 (B), 10.53 (C) and 7.97 (D).
61
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  ## Two evaluation paths — read this before comparing numbers
63
 
64
  Every number in the paper comes from the **ablation harness**
@@ -66,12 +110,14 @@ Every number in the paper comes from the **ablation harness**
66
  `sample_plan` with no locked prefix, 50 denoising steps, 8 actions executed per
67
  plan, scored as `returned_episode_returns`.
68
 
69
- `main.py --mode inference` takes a **different** path: `sample_plan_inpainting`,
70
- which freezes every executed action as an inpainting prefix and replans every
71
- step. It is a different planner at evaluation time and scores far lower on the same
72
- weights (3.26 against 11.81 for the DAgger checkpoint). The single-seed JSONs under
73
- `results/inference/` were produced by that path and are kept as an ablation on the
74
- planning-as-inpainting design choice — they are not paper numbers.
 
 
75
 
76
  `demo_craftax.ipynb` evaluates through the harness path (Cell 5) and runs the
77
  inpainting path only as a clearly labelled ablation (Cell 5b).
@@ -103,13 +149,13 @@ Then open `demo_craftax.ipynb`, or see the project documentation below.
103
 
104
  JAX implementation of **ReMDM** (Remasking Discrete Diffusion Model) for action-sequence planning in [Craftax](https://github.com/MichaelTMatthews/Craftax), a JAX-accelerated, procedurally generated open-world survival game. A bidirectional transformer generates `plan_horizon`-length action plans by iteratively denoising masked token sequences, conditioned on the current symbolic observation. Trained under a pre-trained PPO expert, either offline (behavioural cloning on live rollouts) or online (DAgger).
105
 
106
- The sibling repository [`minihack/`](../minihack) implements the same method in PyTorch on MiniHack. Both repos share the same CLI, config layout and README structure; commands transfer between them by swapping the repo name and benchmark-specific values.
107
 
108
  ## Method
109
 
110
- The planner starts from a fully-masked action sequence and iteratively unmasks tokens over `T` denoising steps; ReMDM extends MDLM with remasking strategies that let committed tokens be re-predicted, improving plan coherence. Two independent training pipelines are compared head-to-head in the accompanying paper (under submission; citation to follow), both supervised by a pre-trained PPO expert:
111
 
112
- One PPO expert checkpoint feeds both pipelines: `--mode offline` behaviour-clones from live expert rollouts, `--mode online` runs DAgger from scratch against expert labels. Either output is scored with `--mode inference`.
113
 
114
  ## Setup
115
 
@@ -117,7 +163,9 @@ Prerequisites: Python 3.12+, [uv](https://docs.astral.sh/uv/). Linux GPU use nee
117
 
118
  ```bash
119
  git clone https://github.com/ANONYMOUS/remdm-planners.git
120
- cd remdm-planners/craftax
 
 
121
 
122
  # Default: CPU-only JAX (macOS, or Linux without a GPU).
123
  # Installs the dev group (pytest) too.
@@ -135,16 +183,17 @@ Extras: `cuda13` and `cuda12` are mutually exclusive and Linux-only. JAX ships G
135
  ## Repo layout
136
 
137
  ```
138
- craftax/
139
- ├── Craftax_Baselines/ Vendored PPO expert training and env wrappers (upstream-derived)
140
  ├── configs/ Experiment configs (defaults.yaml + presets, see Configuration)
141
  ├── src/ Model, diffusion, planner pipelines
142
  ├── experiments/
143
  │ └── rl_finetuning/ RL fine-tuning ablation suite (run_ablations.py)
144
- ├── scripts/ Param counter, PPO evaluator, HF upload utilities
145
  ├── tests/ Smoke suite — uv run pytest
146
  ├── checkpoints/ Gitignored — offline/, online/, ppo_agents/ (see Checkpoints)
147
- ├── results/inference/ Eval JSONs from --mode inference (published, see Checkpoints)
 
148
  ├── demo_craftax.ipynb Demo notebook
149
  ├── main.py CLI entry point
150
  └── pyproject.toml uv project — deps, cuda12/cuda13 extras, dev group
@@ -162,7 +211,7 @@ python main.py --mode smoke
162
 
163
  Two independent training methods; neither depends on the other. An offline BC checkpoint can warm-start DAgger via `--checkpoint`, but this was not used for the paper results. All training modes need a PPO expert checkpoint.
164
 
165
- ### Stage 1 — Train the PPO expert (`Craftax_Baselines/`)
166
 
167
  ```bash
168
  cd Craftax_Baselines
@@ -179,8 +228,6 @@ Rolls out the PPO agent live at each update.
179
 
180
  ```bash
181
  python main.py --mode offline --ppo-checkpoint /path/to/ppo_checkpoint
182
- python main.py --mode offline --ppo-checkpoint /path/to/ppo_checkpoint \
183
- --override offline_total_timesteps=100000000
184
  ```
185
 
186
  ### Online DAgger
@@ -189,8 +236,6 @@ Trained from scratch. Per iteration a mixed expert/learner policy rolls out, the
189
 
190
  ```bash
191
  python main.py --mode online --ppo-checkpoint /path/to/ppo_checkpoint
192
- python main.py --mode online --ppo-checkpoint /path/to/ppo_checkpoint \
193
- --override online_total_timesteps=100000000
194
 
195
  # Optional: warm-start from a pre-trained offline checkpoint
196
  python main.py --mode online --ppo-checkpoint /path/to/ppo_checkpoint \
@@ -212,36 +257,43 @@ python main.py --mode collect --ppo-checkpoint /path/to/ppo_checkpoint \
212
  ### Resuming a training run
213
 
214
  ```bash
215
- # Offline. --resume also accepts a wandb: artifact reference.
216
  python main.py --mode offline --ppo-checkpoint /path/to/ppo_checkpoint \
217
  --resume /path/to/completed_offline_checkpoint \
218
  --override offline_total_timesteps=200000000
219
-
220
- # Online. --resume-step / --resume-wandb-run-id override the metadata sidecar.
221
- python main.py --mode online --ppo-checkpoint /path/to/ppo_checkpoint \
222
- --resume /path/to/completed_online_checkpoint \
223
- --override online_total_timesteps=200000000
224
  ```
225
 
226
- The DAgger replay buffer is not persisted; it refills within a few iterations. The cosine LR schedule spans the full `num_updates`, with the step counter offset so the LR resumes exactly where it stopped. With a metadata sidecar, `resume_step` and `resume_wandb_run_id` are auto-detected; without one, pass `--resume-step`.
227
 
228
- `--resume` restores the optimiser state, so it needs a checkpoint written by the current AdamW chain; an older one fails loudly on the optimiser-state structure, and there is no compatibility path. Use `--checkpoint` instead — parameters only, warm-starting a fresh run.
229
 
230
  ## Evaluation from a checkpoint
231
 
232
  ```bash
233
  python main.py --mode inference --checkpoint /path/to/checkpoint --output results/inference/eval.json
 
 
 
 
 
 
 
 
234
 
235
- # Released checkpoints need their matching config (see Checkpoints)
 
 
 
 
 
 
 
 
236
  python main.py --mode inference \
237
  --config configs/final_craftax_classic_gpu_24gb.yaml \
238
  --checkpoint checkpoints/online/Craftax-Classic-Symbolic-v1-Online-Diffusion-DAgger-100M
239
  ```
240
 
241
- Prints mean episode return, completed episodes, steps per second, and per-achievement unlock counts; `--output` also writes them as JSON. Uses historical inpainting: the first `hist_len` plan positions are locked to observed history. Evaluation length is set by the `eval_steps` / `eval_num_envs` config keys.
242
-
243
- Write eval JSONs into `results/inference/` (created for you): `scripts/hf_upload.py` publishes every JSON it finds there.
244
-
245
  Any checkpoint flag (`--checkpoint`, `--ppo-checkpoint`, `--resume`) accepts a W&B artifact reference prefixed `wandb:`; the artifact downloads automatically (location: `wandb_download_dir`, default `./artifacts/`).
246
 
247
  ```bash
@@ -253,7 +305,7 @@ python main.py --mode inference \
253
 
254
  ### RL baselines
255
 
256
- PPO baselines (the expert family: `ppo`, `ppo_rnn`, `ppo_rnd`) train in the vendored `Craftax_Baselines` tree, see [Training](#stage-1--train-the-ppo-expert-craftax_baselines). Evaluate an expert with `scripts/eval_ppo_expert.py`:
257
 
258
  ```bash
259
  uv run python scripts/eval_ppo_expert.py \
@@ -275,7 +327,7 @@ python main.py --mode online --ppo-checkpoint <ppo> --config configs/classic_exp
275
 
276
  ### RL fine-tuning ablation suite
277
 
278
- 25 registered ablations (same names as in the minihack repo). See `experiments/README.md`.
279
 
280
  ```bash
281
  python experiments/rl_finetuning/run_ablations.py --list
@@ -286,26 +338,33 @@ python experiments/rl_finetuning/run_ablations.py \
286
  --ablations baseline_rl kl_penalty --fast
287
  ```
288
 
 
 
 
 
 
 
 
 
289
  ## Configuration
290
 
291
  One YAML config holds the experiment; the CLI holds the run.
292
 
293
- - **Config files** (`configs/*.yaml`): hyperparameters, model and method settings, ablation definitions. Any file passed via `--config` is merged onto `configs/defaults.yaml`. Exactly two layers: a preset never inherits from another preset.
294
- - **CLI flags**: per-invocation values — `--seed`, `--checkpoint`, `--ppo-checkpoint`, `--data`, `--output`, `--resume*`, `--jit/--no-jit` (disable JIT for debugging).
295
- - **`--override KEY=VALUE`** (repeatable): ad hoc config overrides. Keys are validated against `defaults.yaml` and values are cast to the key's type; a typo is an error, not a silent no-op.
296
 
297
- Precedence, lowest to highest: `configs/defaults.yaml` < `--config` file < `--override` and run flags.
 
 
298
 
299
  **`defaults.yaml` is the final Craftax Classic recipe, not a neutral baseline.** Run `main.py` with no `--config` and you get the paper's Classic DAgger run: a 384-dim, 6-layer model over 100M env frames.
300
 
301
- **Presets hold only deltas, never restate a value they would inherit.** A key belongs in a preset only if its value differs from `defaults.yaml`. Restating one is not harmless duplication: it silently pins the preset when the recipe later moves. `tests/test_config.py` enforces this.
302
 
303
  > **Schedule keys are denominated in env frames, not update steps.** Six settings — `lr_warmup_frames`, `offline_total_timesteps`, `online_total_timesteps`, `dagger_beta_final`, `dagger_buffer_cycles`, `val_interval_frames` — declare the *hardware-invariant* quantity; `resolve_num_updates()` and `resolve_scaled_hyperparams()` derive the update-step forms the runners consume (`num_updates`, `LR_WARMUP_STEPS`, `DAGGER_BETA_DECAY`, `DAGGER_BUFFER_MAX`, `VAL_INTERVAL`) from them at load. Set the frame-denominated key; the derived ones are outputs, not inputs.
304
 
305
  ```bash
306
- python main.py --mode offline --ppo-checkpoint <ppo> \
307
- --override lr=1e-4 --override plan_horizon=64 --override num_minibatches=16
308
- python main.py --mode offline --ppo-checkpoint <ppo> --no-jit --override num_envs=4
309
  ```
310
 
311
  | Preset | Purpose |
@@ -318,29 +377,19 @@ python main.py --mode offline --ppo-checkpoint <ppo> --no-jit --override num_env
318
  | `configs/classic_exp_d_{100K,250K,850K,3M}_model.yaml` | Craftax Classic model-size scaling sweep |
319
  | `configs/craftax_exp_d_{500K,1M,3M,7M}_model.yaml` | Full Craftax model-size scaling sweep |
320
  | `configs/final_craftax_classic_{gpu_h200,gpu_24gb}.yaml` | Final Classic DAgger — `num_envs` and `seed` only; the recipe is `defaults.yaml` |
321
- | `configs/final_craftax_{gpu_h200,gpu_24gb}.yaml` | Final Full Craftax DAgger — the 11 keys where Full Craftax departs from the Classic recipe, plus `num_envs` and `seed` |
322
 
323
- Within each family the two cluster configs differ only in `num_envs` and `seed`. Nothing in the loader enforces that: the guard is `test_cluster_siblings_differ_only_in_num_envs_and_seed`. **A Full Craftax hyperparameter change must be made in both `final_craftax_*` files**, since with no inheritance those 11 keys are duplicated verbatim in each; a Classic one belongs in `defaults.yaml`.
324
 
325
- Fairness-critical values are env-frame denominated (the six keys above) and rescaled by `resolve_scaled_hyperparams()` at load, so one recipe runs on any hardware tier. Key hyperparameters are documented inline in `configs/defaults.yaml`; the [appendix](#key-hyperparameters) tabulates them. Ablation-suite hyperparameters live in `experiments/rl_finetuning/configs/`, loaded by `run_ablations.py`, not `main.py`.
326
 
327
  ## Checkpoints
328
 
329
- With `save_policy: true` (the default), training saves Orbax checkpoints to `policies` (final) and `policies_best` (highest validation return). With W&B on these sit under `wandb.run.dir` and are uploaded as W&B artifacts named `{env_name}-policy` and `{env_name}-policy-best`; with W&B off they go to `{checkpoint_dir}/{mode}/{run_name}/` instead, so a run never discards its weights. Diffusion checkpoints carry a `resume_metadata.json` sidecar the authoritative record of the producing run's config, and what `--resume` reads to auto-detect `resume_step` and `resume_wandb_run_id`. PPO checkpoints carry `config.yaml` and `wandb-summary.json`.
330
-
331
- **Pass the checkpoint directory, not the step subdirectory** — `CheckpointManager` resolves the latest step itself.
332
 
333
- Offline checkpoints save at the resolved env-frame budget, which is 99,942,400 for the Classic recipe at 512 envs (1525 updates × 512 × 128).
334
 
335
- `checkpoints/` is gitignored. The layout below is the published layout.
336
-
337
- Released weights and the pre-computed ablation outputs are mirrored on the
338
- Hugging Face Hub at [`AnonMLuser/remdm-planner-craftax`](https://huggingface.co/AnonMLuser/remdm-planner-craftax),
339
- an anonymous account holding the layout below.
340
-
341
- ```bash
342
- uv run hf download AnonMLuser/remdm-planner-craftax --include "checkpoints/**" --local-dir .
343
- ```
344
 
345
  | Checkpoint directory | Environment | Role | Trained for |
346
  |---|---|---|---|
@@ -349,41 +398,62 @@ uv run hf download AnonMLuser/remdm-planner-craftax --include "checkpoints/**" -
349
  | `checkpoints/ppo_agents/Craftax-Classic-Symbolic-v1-PPO_RNN-1000M` | Craftax Classic | PPO-RNN expert | 1e9 env frames |
350
  | `checkpoints/ppo_agents/Craftax-Symbolic-v1-PPO_RNN-1000M` | Full Craftax | PPO-RNN expert | 1e9 env frames |
351
 
352
- Full-Craftax diffusion planner checkpoints are not released: no full-Craftax training run has completed.
353
 
354
- ### Experiment outputs
 
 
 
 
 
 
 
 
 
 
 
355
 
356
- Ablation figures, tables and `results.json` are **not in the repository** and never
357
- should be: they are regenerated output, and 244 MB of them was rewritten out of the
358
- history. `experiments/rl_finetuning/outputs/` and `results/inference/` are gitignored.
359
 
360
- Regenerate them:
 
 
361
 
362
  ```bash
363
- # Regenerate from a checkpoint; writes to outputs/{run_id}/
 
 
 
 
364
  python experiments/rl_finetuning/run_ablations.py --checkpoint $PRETRAINED_CKPT --all
365
  ```
366
 
367
- `scripts/hf_upload_demo.py` reads `outputs/craftax_classic_final_results/{figures,tables}`
368
- from the working copy, so fetch or regenerate before running it. `demo_craftax.ipynb`
369
- needs no local copy — it reads them from its own `snapshot_download`.
370
 
371
- **Match the config to the checkpoint.** The model is built from the config, not the checkpoint, and a mismatch raises at restore. All released diffusion checkpoints are `d_model` 384, `n_heads` 8, `n_layers` 6, `d_ff` 768 — the architecture `defaults.yaml` also carries — so use the matching `final_*` config, which additionally sets the right `env_name` and recipe values:
372
 
373
- ```bash
374
- python main.py --mode inference \
375
- --config configs/final_craftax_classic_gpu_24gb.yaml \
376
- --checkpoint checkpoints/online/Craftax-Classic-Symbolic-v1-Online-Diffusion-DAgger-100M
377
 
378
- # Train a new planner against the released Full Craftax PPO expert
379
- python main.py --mode online \
380
- --config configs/final_craftax_gpu_24gb.yaml \
381
- --ppo-checkpoint checkpoints/ppo_agents/Craftax-Symbolic-v1-PPO_RNN-1000M
382
  ```
383
 
 
 
 
 
 
 
384
  ### Publishing to the Hub
385
 
386
- `scripts/hf_upload.py` rediscovers and uploads three things, each keeping its repo-relative path: `checkpoints/`, every `experiments/rl_finetuning/outputs/<run>/` holding a `results.json` (with `diagnosis.md`, `tables/`, `figures/`), and the eval JSONs in `results/inference/`. It drops W&B and hub config keys, shortens absolute paths and regenerates the model card.
387
 
388
  ```bash
389
  HF_TOKEN=hf_xxx uv run python scripts/hf_upload.py --repo-id <ANON_HF_REPO_ID> --dry-run
@@ -391,11 +461,11 @@ HF_TOKEN=hf_xxx uv run python scripts/hf_upload.py --repo-id <ANON_HF_REPO_ID> -
391
 
392
  `--dry-run` prints the staged tree and card without uploading; drop it to upload. Also `--inference-results <FILE|DIR> ...` (eval JSONs kept elsewhere), `--private`, `--yes`.
393
 
394
- **Checkpoint discovery expects the released layout**, `checkpoints/<role>/<name>/<step>/` — the layout the Hub repo mirrors. A training run writes elsewhere, so copy its `wandb.run.dir/policies` directory to `checkpoints/{offline,online}/<name>` first, or nothing is staged. `checkpoints/hf/` is skipped: that is where a Hub *download* lands, and publishing from it would push already-published artefacts back up into a nested `checkpoints/hf/checkpoints/...` tree.
395
 
396
  ## Results, citation, licence
397
 
398
- Results tables and the full method description are in the accompanying paper (under submission); `demo_craftax.ipynb` reproduces the headline evaluation. Citation to be added on publication. Licence: MIT, see `LICENSE`.
399
 
400
  ---
401
 
@@ -412,7 +482,7 @@ Set via the `env_name` config key.
412
 
413
  ## Remasking strategies
414
 
415
- Controlled by the `remask_strategy` key. All strategies operate on top of the three-phase loop controlled by `use_loop`, `t_on`, and `t_off`.
416
 
417
  | Strategy | Formula | Description |
418
  |---|---|---|
@@ -422,21 +492,18 @@ Controlled by the `remask_strategy` key. All strategies operate on top of the th
422
 
423
  ## Key hyperparameters
424
 
425
- **Environment**
 
426
 
427
- | Parameter | Default | Description |
428
- |---|---|---|
429
- | `env_name` | `Craftax-Classic-Symbolic-v1` | Craftax environment ID. Use `Craftax-Symbolic-v1` for Full Craftax. |
430
- | `use_optimistic_resets` | `false` | Use `OptimisticResetVecEnvWrapper` instead of `AutoResetEnvWrapper` |
431
- | `optimistic_reset_ratio` | 16 | Fraction of envs reset per step when optimistic resets are enabled |
432
 
433
  **Diffusion model**
434
 
435
  | Parameter | Default | Description |
436
  |---|---|---|
437
  | `plan_horizon` | 32 | Action plan length H |
438
- | `diffusion_steps` | 15 | Denoising steps T during training |
439
- | `diffusion_steps_eval` | 10 | Denoising steps T at inference |
440
  | `diffusion_schedule` | `cosine` | Noise schedule: `cosine` or `linear` |
441
  | `remask_strategy` | `rescale` | Remasking strategy: `rescale`, `cap`, or `conf` |
442
  | `train_sigma` | 0.0 | Per-token remasking correction during training (0 = standard MDLM) |
@@ -447,38 +514,24 @@ Controlled by the `remask_strategy` key. All strategies operate on top of the th
447
  | `temperature` | 0.5 | Softmax temperature for token sampling |
448
  | `top_p` | 0.95 | Nucleus sampling threshold |
449
 
450
- **Transformer architecture**
451
-
452
- | Parameter | Default | Description |
453
- |---|---|---|
454
- | `d_model` | 384 | Hidden dimension |
455
- | `n_heads` | 8 | Attention heads |
456
- | `n_layers` | 6 | Transformer blocks |
457
- | `d_ff` | 768 | FFN inner dimension |
458
- | `obs_encoder_layers` | 2 | MLP layers in the observation encoder |
459
- | `obs_encoder_width` | 768 | Observation encoder hidden width |
460
- | `dropout_rate` | 0.1 | Dropout rate (disabled at inference) |
461
 
462
  **Offline training**
463
 
464
  | Parameter | Default | Description |
465
  |---|---|---|
466
  | `offline_total_timesteps` | 1e8 | Env-frame budget. Derives `num_updates` as `offline_total_timesteps // (num_envs * num_steps)`. |
467
- | `num_envs` | 1024 | Parallel environments |
468
- | `num_steps` | 128 | Environment steps collected per update |
469
- | `num_minibatches` | 8 | Gradient minibatches per epoch |
470
- | `update_epochs` | 8 | SGD epochs per update step |
471
  | `num_repeats` | 1 | Independent training seeds (vmapped) |
472
  | `lr` | 3e-4 | AdamW learning rate (cosine-decayed to 10% over all gradient steps) |
473
  | `weight_decay` | 0.0 | Decoupled AdamW decay for core training; 0.0 is Adam exactly (the ablation suite keeps 1e-4) |
474
  | `lr_warmup_frames` | 1.6384e6 | Env-frame linear warm-up budget (0 = disabled). Derives `LR_WARMUP_STEPS` in gradient steps. |
475
  | `max_grad_norm` | 1.0 | Global gradient clipping norm |
476
  | `return_weight_cap` | 5.0 | Clip ceiling for per-window return weights (lower clip fixed at 0.1) |
477
- | `collect_temperature` | 1.0 | Softmax temperature on PPO logits during live data collection |
478
  | `val_interval_frames` | 1e6 | Env-frames between validation rollouts. Derives `VAL_INTERVAL` in update steps. |
479
- | `val_diffusion_steps` | 50 | Denoising steps during validation rollouts |
480
- | `val_replan_every` | 4 | Env steps executed per diffusion plan during validation |
481
- | `val_steps` | 256 | Total env steps per validation rollout |
482
 
483
  **Online DAgger training**
484
 
@@ -495,28 +548,24 @@ Controlled by the `remask_strategy` key. All strategies operate on top of the th
495
 
496
  | Parameter | Default | Description |
497
  |---|---|---|
498
- | `collect_num_steps` | 10000000 | Total environment steps to collect |
499
- | `collect_num_envs` | 128 | Parallel environments during collection |
500
  | `ppo_model_type` | `ppo_rnn` | PPO architecture: `ppo`, `ppo_rnn`, or `ppo_rnd` |
501
- | `layer_size` | 512 | PPO actor-critic hidden layer width |
502
- | `eval_steps` | 10000 | Environment steps for evaluation |
503
- | `eval_num_envs` | 32 | Parallel agents during evaluation (independent of `num_envs`) |
504
 
505
- **Checkpointing / resume / logging**
506
 
507
  | Parameter | Default | Description |
508
  |---|---|---|
509
  | `save_policy` | `true` | Save final checkpoint and upload as W&B artifact |
510
- | `resume_checkpoint_path` | `null` | Per-run: `--resume` (accepts `wandb:` refs) |
511
- | `resume_wandb_run_id` | `null` | Per-run: `--resume-wandb-run-id` (auto-read from metadata) |
512
- | `resume_step` | `null` | Per-run: `--resume-step` (auto-read from metadata) |
513
  | `seed` | `null` | RNG seed (random if null; per-run: `--seed`) |
514
- | `use_wandb` | `true` | Enable Weights & Biases logging |
515
- | `wandb_project` | `remdm-planner-craftax` | W&B project name |
516
- | `wandb_entity` | `null` | W&B entity (null = your default entity) |
517
- | `wandb_download_dir` | `null` | Download dir for W&B artifacts; null = `./artifacts/` |
518
  | `jax_compilation_cache_dir` | `null` | Persistent XLA compilation cache; null = off. See below |
519
 
 
 
 
520
  ### Persistent compilation cache
521
 
522
  The whole training run is one `jax.jit`, so every process pays one large
@@ -533,7 +582,7 @@ python main.py --mode online --ppo-checkpoint <ppo> \
533
 
534
  ## Environment wrappers
535
 
536
- From `Craftax_Baselines/wrappers.py`:
537
 
538
  | Wrapper | Purpose |
539
  |---|---|
@@ -550,30 +599,29 @@ Stack (identical for training and inference): `env -> LogWrapper -> AutoResetEnv
550
  uv run pytest
551
  ```
552
 
553
- A CPU-only suite, 13 modules. Tiny synthetic data and a shrunken model throughout — no real checkpoints, datasets or network calls, and nothing written outside `tmp_path`. `conftest.py` forces `JAX_PLATFORMS=cpu` and disables W&B; there are no custom markers.
554
 
555
  | File | Covers |
556
  |---|---|
557
- | `test_smoke_src.py`, `test_smoke_experiments.py` | that things **run**: imports, model from the real config, a gradient step, checkpoint round-trip, samplers, resolvers, every CLI entry point, and all 25 ablations' losses and optimizers |
558
- | `test_spec_*.py`, `test_method_spec*.py` | that things are **correct**: each canonical statement of the method/training specification pinned against the implementation |
559
  | `test_config.py`, `test_recipe_values.py` | the preset, delta-only, cluster-sibling and poolability rules, and the shipped recipe values |
 
560
  | `test_gpu_agreement.py` | CPU/GPU agreement, skipped without a device |
561
 
562
  ## Implementation notes
563
 
564
- | Topic | Note |
565
- |---|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
566
- | JAX purity | `make_train_offline_diffusion` / `make_train_online_dagger` are fully JIT-compatible; env construction and checkpoint I/O sit outside `jax.jit`. |
567
- | Offline data | `--mode offline` rolls out PPO live. `--mode collect` saves an `.npz` for inspection only — re-feeding it to `--mode offline` is unsupported; pass `--ppo-checkpoint`. |
568
- | Episode-boundary masking | A window at `(e, t)` is valid only if `dones[e, t+1:t+H-1]` are all `False`. |
569
- | Return weighting | Valid windows are weighted by cumulative reward, normalised by the batch mean, clipped to `[0.1, return_weight_cap]`, and applied as per-sample multipliers before loss reduction. |
570
- | LR schedule | Cosine decay `lr -> lr * 0.1` over all gradient steps. `lr_warmup_frames` prepends linear warm-up, converted to gradient steps as `(frames // fpu) * update_epochs * num_minibatches (* dagger_train_passes online)`. |
571
- | Env-frame invariance | The six frame-denominated keys are converted to update-step form by `resolve_scaled_hyperparams()` using `fpu = num_envs * num_steps`, so one config runs on any hardware tier. |
572
- | DAgger sizing | `dagger_sizing()` in `src/planners/common.py` is the single source of truth for `samples_per_update`, buffer capacity and `n_train_passes`. |
573
- | Loss weight clipping | The MDLM SUBS weight `-alpha'(t) / (1 - alpha_t)` is clipped to 1000 for stability as `alpha_t -> 1`. |
574
- | Validation rollouts | Every `val_interval` updates, using inference sampling parameters with `val_diffusion_steps`, `val_replan_every` and `val_steps`. |
575
- | W&B namespaces | Centralised in `src/planners/logging.py`: `diffusion/`, `train/`, `env/`, `val/`, `dagger/`. `train/sps` only in modes with live env interaction. |
576
- | DAgger aggregation | Ross et al. (2011). A circular buffer accumulates `(obs, expert_plan)` across iterations; windows use a sliding stride so every visited state contributes a label. The expert receives correct `done` flags so its RNN state resets at episode boundaries. |
577
- | Best-checkpoint tracking | Highest-validation-return parameters are kept alongside the live ones and uploaded as `{env_name}-policy-best`. |
578
- | Denoising indexing | Reverse scan runs `step_idx = 0 -> T-1`, mapping to `t = (T - step_idx) / T` (high to low noise). |
579
- | PPO experts | Training lives entirely in `Craftax_Baselines/`; planner modes only consume checkpoints. Released PPO checkpoints were saved on GPU and fail to restore on a CPU-only machine. |
 
38
  ablation fine-tunes)
39
  ppo_agents/ PPO-RNN experts
40
 
41
+ experiments/rl_finetuning/outputs/craftax_classic_ablations/ The published 25-condition suite
42
  figures/ 113 pre-computed PNGs
43
  tables/ main_results.csv, group_summary.csv, hypothesis_verdict.csv,
44
+ achievement_summary.csv, gradient_analysis.csv, significance_test.txt,
45
+ gdelta.csv, results.tex, ...
46
+ gdelta/ gdelta_{seed0,seed1,seed2,aggregate}.json <- the return-term measurement
47
+ results.json, diagnosis.md
48
+
49
+ experiments/rl_finetuning/outputs/review_*/ Five review arms, outside the 25-condition suite
50
+ review_anchor_baseline_rl/ same-stack anchor for the four below
51
+ review_run1_bc_all/ unweighted control (every weight fixed at one)
52
+ review_run2_advclip_lr_matched/ advantage_clip at a matched effective step
53
+ review_run4_baseline_lr1e-4/ learning-rate sweep
54
+ review_run4_baseline_lr1e-5/
55
+
56
+ results/inference/ Single-seed evaluations through the inpainting sampler (see below),
57
+ plus PPO-RNN expert evaluations
58
+ results/paper_figures/ The 11 manuscript figures as vector PDF
59
  ```
60
 
61
  ## Headline numbers (Craftax Classic)
 
70
  No condition of the 25 recovers the checkpoint it started from. Group means are
71
  9.74 (A), 7.07 (B), 10.53 (C) and 7.97 (D).
72
 
73
+ ### The review arms
74
+
75
+ These are separate runs on a different host and are quoted **against each other**, not
76
+ against the table above: the suite does not reproduce across hosts, and the same
77
+ checkpoint and configuration score the pretrained planner at 11.9754 here against
78
+ 11.8084 above, a consistent offset of 1.41%. `baseline_rl` was therefore re-run as a
79
+ same-stack anchor.
80
+
81
+ | Arm | lr | Score |
82
+ |---|---|---|
83
+ | `baseline_rl` — same-stack anchor | 3e-4 | 8.3395 ± 0.1468 |
84
+ | `bc_all` — uniform weights, all rollout windows | 3e-4 | **4.7348 ± 0.1869** |
85
+ | `advantage_clip` — matched effective step | 3.223e-4 | **4.9246 ± 0.2573** |
86
+ | `baseline_rl` | 1e-4 | **10.1486 ± 0.1811** |
87
+ | `baseline_rl` | 1e-5 | **10.8104 ± 0.1024** |
88
+
89
+ Removing the return weighting while keeping the rollouts costs *more* than the
90
+ weighting itself (−3.60 against the anchor). Lowering the learning rate by a factor of
91
+ thirty recovers 68% of the degradation without removing it — 10.8104 still sits 1.1650
92
+ below the same-stack checkpoint, more than ten times the seed sd at that rate.
93
+ Matching the effective step does not close the advantage-clipping gap.
94
+
95
+ The MiniHack counterparts of all three arms were run and are reported in the paper, but
96
+ **their run directories are not part of either release** — only the Craftax Classic ones
97
+ are published here. The MiniHack numbers are quoted in that repo's notebook.
98
+
99
+ ### The return term
100
+
101
+ `experiments/rl_finetuning/outputs/craftax_classic_ablations/gdelta/` holds the direct
102
+ measurement of the return term `g_δ` at the pretrained checkpoint, over 3 rollout seeds
103
+ × 8 noise draws on 1,024-window batches. Reproduce it on a CPU with
104
+ `run_ablations.py --measure-gdelta`. The Eq.-4 correctness residual is 4.8 × 10⁻⁷.
105
+
106
  ## Two evaluation paths — read this before comparing numbers
107
 
108
  Every number in the paper comes from the **ablation harness**
 
110
  `sample_plan` with no locked prefix, 50 denoising steps, 8 actions executed per
111
  plan, scored as `returned_episode_returns`.
112
 
113
+ `main.py --mode inference` can also take a **different** path,
114
+ `sample_plan_inpainting`, which freezes every executed action as an inpainting prefix
115
+ and replans every step. It is a different planner at evaluation time and scores far
116
+ lower on the same weights (3.26 against 11.81 for the DAgger checkpoint). The
117
+ single-seed JSONs under `results/inference/` were produced by that path and are kept as
118
+ an ablation on the planning-as-inpainting design choice — they are not paper numbers.
119
+ `configs/defaults.yaml` now sets `inference_sampler: sample_plan`, so the two paths
120
+ agree by default.
121
 
122
  `demo_craftax.ipynb` evaluates through the harness path (Cell 5) and runs the
123
  inpainting path only as a clearly labelled ablation (Cell 5b).
 
149
 
150
  JAX implementation of **ReMDM** (Remasking Discrete Diffusion Model) for action-sequence planning in [Craftax](https://github.com/MichaelTMatthews/Craftax), a JAX-accelerated, procedurally generated open-world survival game. A bidirectional transformer generates `plan_horizon`-length action plans by iteratively denoising masked token sequences, conditioned on the current symbolic observation. Trained under a pre-trained PPO expert, either offline (behavioural cloning on live rollouts) or online (DAgger).
151
 
152
+ The sibling repository [`remdm-planner-minihack`](../remdm-planner-minihack) implements the same method in PyTorch on MiniHack. Both repos share the same CLI, config layout and README structure; commands transfer between them by swapping the repo name and benchmark-specific values.
153
 
154
  ## Method
155
 
156
+ The planner starts from a fully-masked action sequence and iteratively unmasks tokens over `T` denoising steps; ReMDM extends MDLM with remasking strategies that let committed tokens be re-predicted, improving plan coherence.
157
 
158
+ Two independent training pipelines are compared head-to-head, both fed by one pre-trained PPO expert checkpoint: `--mode offline` behaviour-clones from live expert rollouts, `--mode online` runs DAgger from scratch against expert labels. Either output is scored with `--mode inference`.
159
 
160
  ## Setup
161
 
 
163
 
164
  ```bash
165
  git clone https://github.com/ANONYMOUS/remdm-planners.git
166
+ cd remdm-planner-craftax
167
+ # Or, if already cloned without submodules:
168
+ git submodule update --init --recursive
169
 
170
  # Default: CPU-only JAX (macOS, or Linux without a GPU).
171
  # Installs the dev group (pytest) too.
 
183
  ## Repo layout
184
 
185
  ```
186
+ remdm-planner-craftax/
187
+ ├── Craftax_Baselines/ Git submodule — PPO expert training and env wrappers
188
  ├── configs/ Experiment configs (defaults.yaml + presets, see Configuration)
189
  ├── src/ Model, diffusion, planner pipelines
190
  ├── experiments/
191
  │ └── rl_finetuning/ RL fine-tuning ablation suite (run_ablations.py)
192
+ ├── scripts/ Param counter, PPO evaluator, paper figures, HF upload, provisioning
193
  ├── tests/ Smoke suite — uv run pytest
194
  ├── checkpoints/ Gitignored — offline/, online/, ppo_agents/ (see Checkpoints)
195
+ ├── results/ Gitignored, created on demand inference/ eval JSONs and
196
+ │ paper_figures/ manuscript PDFs, both published (see Checkpoints)
197
  ├── demo_craftax.ipynb Demo notebook
198
  ├── main.py CLI entry point
199
  └── pyproject.toml uv project — deps, cuda12/cuda13 extras, dev group
 
211
 
212
  Two independent training methods; neither depends on the other. An offline BC checkpoint can warm-start DAgger via `--checkpoint`, but this was not used for the paper results. All training modes need a PPO expert checkpoint.
213
 
214
+ ### Stage 1 — Train the PPO expert (submodule)
215
 
216
  ```bash
217
  cd Craftax_Baselines
 
228
 
229
  ```bash
230
  python main.py --mode offline --ppo-checkpoint /path/to/ppo_checkpoint
 
 
231
  ```
232
 
233
  ### Online DAgger
 
236
 
237
  ```bash
238
  python main.py --mode online --ppo-checkpoint /path/to/ppo_checkpoint
 
 
239
 
240
  # Optional: warm-start from a pre-trained offline checkpoint
241
  python main.py --mode online --ppo-checkpoint /path/to/ppo_checkpoint \
 
257
  ### Resuming a training run
258
 
259
  ```bash
260
+ # Same shape for --mode online. --resume also accepts a wandb: artifact reference.
261
  python main.py --mode offline --ppo-checkpoint /path/to/ppo_checkpoint \
262
  --resume /path/to/completed_offline_checkpoint \
263
  --override offline_total_timesteps=200000000
 
 
 
 
 
264
  ```
265
 
266
+ The DAgger replay buffer is not persisted; it refills within a few iterations. The cosine LR schedule spans the full `num_updates`, offset so the LR resumes where it stopped. `resume_step` and `resume_wandb_run_id` come from the metadata sidecar; without one, pass `--resume-step` (or `--resume-wandb-run-id`) explicitly.
267
 
268
+ `--resume` restores the optimiser state, so it needs a checkpoint written by the current AdamW chain; an older one fails loudly and there is no compatibility path. Use `--checkpoint` instead — parameters only, warm-starting a fresh run.
269
 
270
  ## Evaluation from a checkpoint
271
 
272
  ```bash
273
  python main.py --mode inference --checkpoint /path/to/checkpoint --output results/inference/eval.json
274
+ ```
275
+
276
+ Prints steps per second, per-achievement unlock counts, and two returns that must not be quoted against one another; `--output` also writes both as JSON:
277
+
278
+ | Reported | JSON key | Meaning |
279
+ |---|---|---|
280
+ | Mean return, completed episodes | `mean_return_completed_episodes` (with `n_completed_episodes`) | Mean over every episode that terminated inside the rollout — the `returned_episode_returns` statistic the ablation tables and the paper report |
281
+ | Mean return, first life only | `mean_return_first_life`, and `mean_score` for backwards compatibility | Strict single-life return: the first episode of each env only. A harsher statistic |
282
 
283
+ By default this replans from scratch every `eval_replan` (8) steps, conditioned only on the current observation — the same sampler and cadence as `build_eval_fn` in the ablation harness, so it is the protocol behind the published numbers. Length and width come from `eval_steps` / `eval_num_envs`.
284
+
285
+ `--override inference_sampler=inpainting` switches to the historical-inpainting sampler, which replans every step with the executed actions locked as a fixed prefix, leaving fewer free positions the further into a window it gets. It is kept as an ablation on the planning-as-inpainting design choice and **scores far lower on the same weights**; no published number comes from it.
286
+
287
+ Write eval JSONs into `results/inference/` (created for you): `scripts/hf_upload.py` publishes every JSON it finds there.
288
+
289
+ **Match the config to the checkpoint.** The model is built from the config, not the checkpoint, and a mismatch raises at restore. Every released diffusion checkpoint carries the `defaults.yaml` architecture, so evaluate with the matching `final_*` config, which also sets the right `env_name` and recipe values:
290
+
291
+ ```bash
292
  python main.py --mode inference \
293
  --config configs/final_craftax_classic_gpu_24gb.yaml \
294
  --checkpoint checkpoints/online/Craftax-Classic-Symbolic-v1-Online-Diffusion-DAgger-100M
295
  ```
296
 
 
 
 
 
297
  Any checkpoint flag (`--checkpoint`, `--ppo-checkpoint`, `--resume`) accepts a W&B artifact reference prefixed `wandb:`; the artifact downloads automatically (location: `wandb_download_dir`, default `./artifacts/`).
298
 
299
  ```bash
 
305
 
306
  ### RL baselines
307
 
308
+ PPO baselines (the expert family: `ppo`, `ppo_rnn`, `ppo_rnd`) train in the `Craftax_Baselines` submodule, see [Training](#stage-1--train-the-ppo-expert-submodule). Evaluate an expert with `scripts/eval_ppo_expert.py`:
309
 
310
  ```bash
311
  uv run python scripts/eval_ppo_expert.py \
 
327
 
328
  ### RL fine-tuning ablation suite
329
 
330
+ 26 registered ablations (same names as in the minihack repo). See `experiments/README.md`.
331
 
332
  ```bash
333
  python experiments/rl_finetuning/run_ablations.py --list
 
338
  --ablations baseline_rl kl_penalty --fast
339
  ```
340
 
341
+ The same entry point measures the return term of the gradient decomposition at the
342
+ pretrained checkpoint, with no training and no accelerator:
343
+
344
+ ```bash
345
+ python experiments/rl_finetuning/run_ablations.py --measure-gdelta --gdelta-seeds 0 1 2 \
346
+ --checkpoint $PRETRAINED_CKPT --results-path $RUN/results.json --output-dir $RUN
347
+ ```
348
+
349
  ## Configuration
350
 
351
  One YAML config holds the experiment; the CLI holds the run.
352
 
353
+ Precedence, lowest to highest: `configs/defaults.yaml` < `--config` preset < `--override` and run flags. Exactly two config layers a preset never inherits from another preset.
 
 
354
 
355
+ - **Config files** (`configs/*.yaml`): hyperparameters, model and method settings, ablation definitions.
356
+ - **Run flags**: `--seed`, `--checkpoint`, `--ppo-checkpoint`, `--data`, `--output`, `--resume*`, `--jit/--no-jit` (disable JIT for debugging).
357
+ - **`--override KEY=VALUE`** (repeatable): keys are validated against `defaults.yaml` and cast to the key's type, so a typo is an error, not a silent no-op.
358
 
359
  **`defaults.yaml` is the final Craftax Classic recipe, not a neutral baseline.** Run `main.py` with no `--config` and you get the paper's Classic DAgger run: a 384-dim, 6-layer model over 100M env frames.
360
 
361
+ **Presets hold only deltas.** A key belongs in a preset only if its value differs from `defaults.yaml`; restating one silently pins the preset when the recipe later moves. `tests/test_config.py` enforces this.
362
 
363
  > **Schedule keys are denominated in env frames, not update steps.** Six settings — `lr_warmup_frames`, `offline_total_timesteps`, `online_total_timesteps`, `dagger_beta_final`, `dagger_buffer_cycles`, `val_interval_frames` — declare the *hardware-invariant* quantity; `resolve_num_updates()` and `resolve_scaled_hyperparams()` derive the update-step forms the runners consume (`num_updates`, `LR_WARMUP_STEPS`, `DAGGER_BETA_DECAY`, `DAGGER_BUFFER_MAX`, `VAL_INTERVAL`) from them at load. Set the frame-denominated key; the derived ones are outputs, not inputs.
364
 
365
  ```bash
366
+ python main.py --mode offline --ppo-checkpoint <ppo> --no-jit \
367
+ --override lr=1e-4 --override plan_horizon=64 --override num_envs=4
 
368
  ```
369
 
370
  | Preset | Purpose |
 
377
  | `configs/classic_exp_d_{100K,250K,850K,3M}_model.yaml` | Craftax Classic model-size scaling sweep |
378
  | `configs/craftax_exp_d_{500K,1M,3M,7M}_model.yaml` | Full Craftax model-size scaling sweep |
379
  | `configs/final_craftax_classic_{gpu_h200,gpu_24gb}.yaml` | Final Classic DAgger — `num_envs` and `seed` only; the recipe is `defaults.yaml` |
380
+ | `configs/final_craftax_{gpu_h200,gpu_24gb}.yaml` | Final Full Craftax DAgger — the 8 keys where Full Craftax departs from the Classic recipe, plus `num_envs` and `seed` |
381
 
382
+ Within each family the two machine configs differ only in `num_envs` and `seed`, guarded by `test_cluster_siblings_differ_only_in_num_envs_and_seed` rather than by the loader. **A Full Craftax hyperparameter change must be made in both `final_craftax_*` files** with no inheritance those 8 keys are duplicated verbatim in each; a Classic one belongs in `defaults.yaml`.
383
 
384
+ Key hyperparameters are documented inline in `configs/defaults.yaml`; the [appendix](#key-hyperparameters) tabulates the load-bearing ones. Ablation-suite hyperparameters live in `experiments/rl_finetuning/configs/`, loaded by `run_ablations.py`, not `main.py`.
385
 
386
  ## Checkpoints
387
 
388
+ With `save_policy: true` (the default), training saves Orbax checkpoints to `policies` (final) and `policies_best` (highest validation return) under `wandb.run.dir` with W&B on, uploaded as `{env_name}-policy` and `{env_name}-policy-best`, and under `{checkpoint_dir}/{mode}/{run_name}/` with W&B off, so a run never discards its weights. Diffusion checkpoints carry a `resume_metadata.json` sidecar recording the producing run's config, which is what `--resume` reads; PPO checkpoints carry `config.yaml` and `wandb-summary.json`.
 
 
389
 
390
+ **Pass the checkpoint directory, not the step subdirectory** — `CheckpointManager` resolves the latest step itself. Offline checkpoints save at the resolved env-frame budget: 99,942,400 for the Classic recipe at 512 envs (1525 updates × 512 × 128).
391
 
392
+ `checkpoints/` is gitignored; released weights live on the Hub at [`AnonMLuser/remdm-planner-craftax`](https://huggingface.co/AnonMLuser/remdm-planner-craftax), mirroring the layout below.
 
 
 
 
 
 
 
 
393
 
394
  | Checkpoint directory | Environment | Role | Trained for |
395
  |---|---|---|---|
 
398
  | `checkpoints/ppo_agents/Craftax-Classic-Symbolic-v1-PPO_RNN-1000M` | Craftax Classic | PPO-RNN expert | 1e9 env frames |
399
  | `checkpoints/ppo_agents/Craftax-Symbolic-v1-PPO_RNN-1000M` | Full Craftax | PPO-RNN expert | 1e9 env frames |
400
 
401
+ Full-Craftax diffusion planner checkpoints are not released: no full-Craftax training run has completed; the released Full Craftax expert can still supervise a new run:
402
 
403
+ ```bash
404
+ python main.py --mode online \
405
+ --config configs/final_craftax_gpu_24gb.yaml \
406
+ --ppo-checkpoint checkpoints/ppo_agents/Craftax-Symbolic-v1-PPO_RNN-1000M
407
+ ```
408
+
409
+ ```bash
410
+ # All four (~470 MB); narrow the --include glob for a single checkpoint.
411
+ uv run hf download AnonMLuser/remdm-planner-craftax --include "checkpoints/**" --local-dir .
412
+ ```
413
+
414
+ **Keep the `--include`.** The Hub repo carries its own `README.md` (the generated model card), `LICENSE` and `.gitattributes`; dropping the glob and pulling into `--local-dir .` overwrites this repository's copies of all three. To fetch everything, add `--exclude "README.md" "LICENSE" ".gitattributes"`, or use a separate `--local-dir`. Publishing is safe either way — `hf_upload.py` stages `LICENSE` and the demo `README.md` from git, not the working tree.
415
 
416
+ ### Experiment outputs
 
 
417
 
418
+ Ablation figures, tables and `results.json` are regenerated output, so
419
+ `experiments/rl_finetuning/outputs/` and `results/inference/` are gitignored. Obtain
420
+ them either way:
421
 
422
  ```bash
423
+ # Fetch the published run (figures, tables, results.json, diagnosis.md)
424
+ uv run hf download AnonMLuser/remdm-planner-craftax \
425
+ --include "experiments/rl_finetuning/outputs/**" --local-dir .
426
+
427
+ # Or regenerate from a checkpoint; writes to outputs/{run_id}/
428
  python experiments/rl_finetuning/run_ablations.py --checkpoint $PRETRAINED_CKPT --all
429
  ```
430
 
431
+ `scripts/hf_upload_demo.py` reads `outputs/craftax_classic_ablations/{figures,tables}`
432
+ from the working copy, so fetch or regenerate first. `demo_craftax.ipynb` needs no local
433
+ copy — it reads them through its own `snapshot_download`.
434
 
435
+ ### Paper figures
436
 
437
+ Each manuscript figure puts Craftax Classic and MiniHack side by side, so they are built
438
+ by `scripts/paper_figures.py` rather than by the single-environment
439
+ `experiments/rl_finetuning/analysis/plots.py`. It reads *both* repositories'
440
+ `results.json` and emits vector PDF at NeurIPS column width:
441
 
442
+ ```bash
443
+ uv run python scripts/paper_figures.py \
444
+ --minihack-results ../remdm-planner-minihack/experiments/rl_finetuning/outputs/minihack_ablations/results.json \
445
+ --outdir results/paper_figures
446
  ```
447
 
448
+ The MiniHack path defaults to that sibling checkout. Pass `--emit-tex-macros` to
449
+ `run_ablations.py` to also write `tables/results.tex`, one `\newcommand` per headline
450
+ quantity, so the manuscript cites generated numbers instead of retyping them. Macros from
451
+ this repository are prefixed `rw` and the sibling suite's `mh`, so both files can be
452
+ `\input` together.
453
+
454
  ### Publishing to the Hub
455
 
456
+ `scripts/hf_upload.py` rediscovers and uploads four things, each keeping its repo-relative path: `checkpoints/`, every `experiments/rl_finetuning/outputs/<run>/` holding a `results.json` (with `diagnosis.md`, `tables/`, `figures/`, `gdelta/`), the eval JSONs in `results/inference/`, and the manuscript figure PDFs in `results/paper_figures/`. It drops W&B and hub config keys, shortens absolute paths and regenerates the model card.
457
 
458
  ```bash
459
  HF_TOKEN=hf_xxx uv run python scripts/hf_upload.py --repo-id <ANON_HF_REPO_ID> --dry-run
 
461
 
462
  `--dry-run` prints the staged tree and card without uploading; drop it to upload. Also `--inference-results <FILE|DIR> ...` (eval JSONs kept elsewhere), `--private`, `--yes`.
463
 
464
+ **Checkpoint discovery expects the released layout**, `checkpoints/<role>/<name>/<step>/`. A training run writes elsewhere, so copy its `wandb.run.dir/policies` directory to `checkpoints/{offline,online}/<name>` first, or nothing is staged. `checkpoints/hf/` is skipped that is where a Hub *download* lands, and publishing from it would nest already-published artefacts under `checkpoints/hf/checkpoints/...`.
465
 
466
  ## Results, citation, licence
467
 
468
+ Results tables and the full method description are in *Return-Weighted ELBO Fine-Tuning Degrades Masked Diffusion Planners* (under submission); `demo_craftax.ipynb` reproduces the headline evaluation. Citation to be added on publication. Licence: MIT, see `LICENSE`.
469
 
470
  ---
471
 
 
482
 
483
  ## Remasking strategies
484
 
485
+ Selected by `remask_strategy`, on top of the three-phase loop controlled by `use_loop`, `t_on` and `t_off`.
486
 
487
  | Strategy | Formula | Description |
488
  |---|---|---|
 
492
 
493
  ## Key hyperparameters
494
 
495
+ `configs/defaults.yaml` is authoritative and commented inline. Tabulated here are the
496
+ keys that change a result, carry a hazard, or are named elsewhere in this README.
497
 
498
+ **Environment.** `env_name` selects the benchmark: `Craftax-Classic-Symbolic-v1`
499
+ (default) or `Craftax-Symbolic-v1` for Full Craftax.
 
 
 
500
 
501
  **Diffusion model**
502
 
503
  | Parameter | Default | Description |
504
  |---|---|---|
505
  | `plan_horizon` | 32 | Action plan length H |
506
+ | `diffusion_steps` / `diffusion_steps_eval` | 15 / 10 | Denoising steps T at training and at inference |
 
507
  | `diffusion_schedule` | `cosine` | Noise schedule: `cosine` or `linear` |
508
  | `remask_strategy` | `rescale` | Remasking strategy: `rescale`, `cap`, or `conf` |
509
  | `train_sigma` | 0.0 | Per-token remasking correction during training (0 = standard MDLM) |
 
514
  | `temperature` | 0.5 | Softmax temperature for token sampling |
515
  | `top_p` | 0.95 | Nucleus sampling threshold |
516
 
517
+ **Transformer architecture.** `d_model` 384, `n_heads` 8, `n_layers` 6, `d_ff` 768,
518
+ `obs_encoder_layers` 2, `obs_encoder_width` 768, `dropout_rate` 0.1 — the shape every
519
+ released checkpoint carries. A checkpoint restores only against a matching config.
 
 
 
 
 
 
 
 
520
 
521
  **Offline training**
522
 
523
  | Parameter | Default | Description |
524
  |---|---|---|
525
  | `offline_total_timesteps` | 1e8 | Env-frame budget. Derives `num_updates` as `offline_total_timesteps // (num_envs * num_steps)`. |
526
+ | `num_envs` / `num_steps` | 1024 / 128 | Parallel environments, and env steps per update; their product is `fpu` |
527
+ | `num_minibatches` / `update_epochs` | 8 / 8 | Gradient minibatches per epoch, and epochs per update |
 
 
528
  | `num_repeats` | 1 | Independent training seeds (vmapped) |
529
  | `lr` | 3e-4 | AdamW learning rate (cosine-decayed to 10% over all gradient steps) |
530
  | `weight_decay` | 0.0 | Decoupled AdamW decay for core training; 0.0 is Adam exactly (the ablation suite keeps 1e-4) |
531
  | `lr_warmup_frames` | 1.6384e6 | Env-frame linear warm-up budget (0 = disabled). Derives `LR_WARMUP_STEPS` in gradient steps. |
532
  | `max_grad_norm` | 1.0 | Global gradient clipping norm |
533
  | `return_weight_cap` | 5.0 | Clip ceiling for per-window return weights (lower clip fixed at 0.1) |
 
534
  | `val_interval_frames` | 1e6 | Env-frames between validation rollouts. Derives `VAL_INTERVAL` in update steps. |
 
 
 
535
 
536
  **Online DAgger training**
537
 
 
548
 
549
  | Parameter | Default | Description |
550
  |---|---|---|
551
+ | `collect_num_steps` / `collect_num_envs` | 1e7 / 128 | Steps to collect, and envs collecting them |
 
552
  | `ppo_model_type` | `ppo_rnn` | PPO architecture: `ppo`, `ppo_rnn`, or `ppo_rnd` |
553
+ | `eval_steps` / `eval_num_envs` | 10000 / 32 | Evaluation length and width (independent of `num_envs`) |
554
+ | `inference_sampler` | `sample_plan` | `sample_plan` (the published protocol) or `inpainting` |
555
+ | `eval_replan` | 8 | Env steps executed per plan under `sample_plan` |
556
 
557
+ **Checkpointing / logging**
558
 
559
  | Parameter | Default | Description |
560
  |---|---|---|
561
  | `save_policy` | `true` | Save final checkpoint and upload as W&B artifact |
562
+ | `checkpoint_dir` | `checkpoints` | Where checkpoints land when W&B is off |
 
 
563
  | `seed` | `null` | RNG seed (random if null; per-run: `--seed`) |
 
 
 
 
564
  | `jax_compilation_cache_dir` | `null` | Persistent XLA compilation cache; null = off. See below |
565
 
566
+ The `resume_*`, `use_wandb` and `wandb_*` keys mirror the run flags documented under
567
+ [Configuration](#configuration).
568
+
569
  ### Persistent compilation cache
570
 
571
  The whole training run is one `jax.jit`, so every process pays one large
 
582
 
583
  ## Environment wrappers
584
 
585
+ From `Craftax_Baselines/wrappers.py` (submodule):
586
 
587
  | Wrapper | Purpose |
588
  |---|---|
 
599
  uv run pytest
600
  ```
601
 
602
+ A CPU-only suite, 14 modules. Tiny synthetic data and a shrunken model throughout — no real checkpoints, datasets or network calls, and nothing written outside `tmp_path`. `conftest.py` forces `JAX_PLATFORMS=cpu` and disables W&B; there are no custom markers.
603
 
604
  | File | Covers |
605
  |---|---|
606
+ | `test_smoke_src.py`, `test_smoke_experiments.py` | that things **run**: imports, model from the real config, a gradient step, checkpoint round-trip, samplers, resolvers, every CLI entry point, and all 26 ablations' losses and optimizers |
607
+ | `test_spec_*.py`, `test_method_spec*.py` | that things are **correct**: each canonical statement of the parent workspace's `the spec *.md` pinned against the implementation |
608
  | `test_config.py`, `test_recipe_values.py` | the preset, delta-only, cluster-sibling and poolability rules, and the shipped recipe values |
609
+ | `test_gdelta.py`, `test_tex_macros.py` | the `--measure-gdelta` decomposition, and the `--emit-tex-macros` output: definitions only, uniquely named, letters only |
610
  | `test_gpu_agreement.py` | CPU/GPU agreement, skipped without a device |
611
 
612
  ## Implementation notes
613
 
614
+ | Topic | Note |
615
+ |---|---|
616
+ | JAX purity | `make_train_offline_diffusion` / `make_train_online_dagger` are fully JIT-compatible; env construction and checkpoint I/O sit outside `jax.jit`. |
617
+ | Offline data | `--mode offline` rolls out PPO live. `--mode collect` saves an `.npz` for inspection only — re-feeding it to `--mode offline` is unsupported. |
618
+ | Episode-boundary masking | A window at `(e, t)` is valid only if `dones[e, t+1:t+H-1]` are all `False`. |
619
+ | Return weighting | Valid windows are weighted by cumulative reward, normalised by the batch mean, clipped to `[0.1, return_weight_cap]`, and applied as per-sample multipliers before loss reduction. |
620
+ | LR schedule | Cosine decay `lr -> lr * 0.1` over all gradient steps. `lr_warmup_frames` prepends linear warm-up, converted as `(frames // fpu) * update_epochs * num_minibatches (* dagger_train_passes online)`. |
621
+ | DAgger sizing | `dagger_sizing()` in `src/planners/common.py` is the single source of truth for `samples_per_update`, buffer capacity and `n_train_passes`. |
622
+ | DAgger aggregation | Ross et al. (2011). A circular buffer accumulates `(obs, expert_plan)` across iterations, with a sliding stride so every visited state contributes a label; the expert receives correct `done` flags so its RNN state resets at episode boundaries. |
623
+ | Loss weight clipping | The MDLM SUBS weight `-alpha'(t) / (1 - alpha_t)` is clipped to 1000 for stability as `alpha_t -> 1`. |
624
+ | Denoising indexing | Reverse scan runs `step_idx = 0 -> T-1`, mapping to `t = (T - step_idx) / T` (high to low noise). |
625
+ | Validation and best checkpoint | Every `val_interval` updates, at `val_diffusion_steps` / `val_replan_every` / `val_steps`. The highest-return parameters are kept alongside the live ones and uploaded as `{env_name}-policy-best`. |
626
+ | W&B namespaces | Centralised in `src/planners/logging.py`: `diffusion/`, `train/`, `env/`, `val/`, `dagger/`. `train/sps` only in modes with live env interaction. |
627
+ | PPO experts | Training lives entirely in `Craftax_Baselines/`; planner modes only consume checkpoints. Released PPO checkpoints were saved on GPU and fail to restore on a CPU-only machine. |
 
 
checkpoints/offline/Craftax-Classic-Symbolic-v1-Offline-Diffusion-BC-100M/99942400/commit_success.txt CHANGED
@@ -1 +1 @@
1
- Checkpoint commit was successful to <path omitted for anonymous review>
 
1
+ Checkpoint commit was successful to policies/99942400
checkpoints/offline/Craftax-Classic-Symbolic-v1-Offline-Diffusion-BC-100M/99942400/default/commit_success.txt CHANGED
@@ -1 +1 @@
1
- Checkpoint commit was successful to <path omitted for anonymous review>
 
1
+ Checkpoint commit was successful to 99942400.orbax-checkpoint-tmp/default
checkpoints/offline/Craftax-Classic-Symbolic-v1-Offline-Diffusion-BC-100M/resume_metadata.json CHANGED
@@ -2,7 +2,6 @@
2
  "mode": "offline",
3
  "update_step": 1525,
4
  "total_gradient_steps_completed": 97600,
5
- "wandb_run_id": null,
6
  "config_snapshot": {
7
  "ENV_NAME": "Craftax-Classic-Symbolic-v1",
8
  "USE_OPTIMISTIC_RESETS": false,
@@ -58,14 +57,9 @@
58
  "SAVE_POLICY": true,
59
  "CHECKPOINT_DIR": "checkpoints",
60
  "RESUME_CHECKPOINT_PATH": null,
61
- "RESUME_WANDB_RUN_ID": null,
62
  "RESUME_STEP": null,
63
- "JAX_COMPILATION_CACHE_DIR": "anonymous/jax-cache",
64
  "SEED": 42,
65
- "USE_WANDB": true,
66
- "WANDB_PROJECT": "remdm-planner-craftax",
67
- "WANDB_ENTITY": "null",
68
- "WANDB_DOWNLOAD_DIR": null,
69
  "MODE": "offline",
70
  "JIT": true,
71
  "PPO_CHECKPOINT_PATH": "checkpoints/ppo_agents/Craftax-Classic-Symbolic-v1-PPO_RNN-1000M",
 
2
  "mode": "offline",
3
  "update_step": 1525,
4
  "total_gradient_steps_completed": 97600,
 
5
  "config_snapshot": {
6
  "ENV_NAME": "Craftax-Classic-Symbolic-v1",
7
  "USE_OPTIMISTIC_RESETS": false,
 
57
  "SAVE_POLICY": true,
58
  "CHECKPOINT_DIR": "checkpoints",
59
  "RESUME_CHECKPOINT_PATH": null,
 
60
  "RESUME_STEP": null,
61
+ "JAX_COMPILATION_CACHE_DIR": null,
62
  "SEED": 42,
 
 
 
 
63
  "MODE": "offline",
64
  "JIT": true,
65
  "PPO_CHECKPOINT_PATH": "checkpoints/ppo_agents/Craftax-Classic-Symbolic-v1-PPO_RNN-1000M",
checkpoints/online/Craftax-Classic-Symbolic-v1-Online-Diffusion-DAgger-100M/40370176/commit_success.txt CHANGED
@@ -1 +1 @@
1
- Checkpoint commit was successful to <path omitted for anonymous review>
 
1
+ Checkpoint commit was successful to policies_best/40370176
checkpoints/online/Craftax-Classic-Symbolic-v1-Online-Diffusion-DAgger-100M/40370176/default/commit_success.txt CHANGED
@@ -1 +1 @@
1
- Checkpoint commit was successful to <path omitted for anonymous review>
 
1
+ Checkpoint commit was successful to 40370176.orbax-checkpoint-tmp/default
checkpoints/online/Craftax-Classic-Symbolic-v1-Online-Diffusion-DAgger-100M/resume_metadata.json CHANGED
@@ -2,7 +2,6 @@
2
  "mode": "online",
3
  "update_step": 1525,
4
  "total_gradient_steps_completed": 97600,
5
- "wandb_run_id": null,
6
  "config_snapshot": {
7
  "ENV_NAME": "Craftax-Classic-Symbolic-v1",
8
  "USE_OPTIMISTIC_RESETS": false,
@@ -58,14 +57,9 @@
58
  "SAVE_POLICY": true,
59
  "CHECKPOINT_DIR": "checkpoints",
60
  "RESUME_CHECKPOINT_PATH": null,
61
- "RESUME_WANDB_RUN_ID": null,
62
  "RESUME_STEP": null,
63
  "JAX_COMPILATION_CACHE_DIR": null,
64
  "SEED": 42,
65
- "USE_WANDB": true,
66
- "WANDB_PROJECT": "remdm-planner-craftax",
67
- "WANDB_ENTITY": "null",
68
- "WANDB_DOWNLOAD_DIR": null,
69
  "MODE": "online",
70
  "JIT": true,
71
  "PPO_CHECKPOINT_PATH": "checkpoints/ppo_agents/Craftax-Classic-Symbolic-v1-PPO_RNN-1000M",
 
2
  "mode": "online",
3
  "update_step": 1525,
4
  "total_gradient_steps_completed": 97600,
 
5
  "config_snapshot": {
6
  "ENV_NAME": "Craftax-Classic-Symbolic-v1",
7
  "USE_OPTIMISTIC_RESETS": false,
 
57
  "SAVE_POLICY": true,
58
  "CHECKPOINT_DIR": "checkpoints",
59
  "RESUME_CHECKPOINT_PATH": null,
 
60
  "RESUME_STEP": null,
61
  "JAX_COMPILATION_CACHE_DIR": null,
62
  "SEED": 42,
 
 
 
 
63
  "MODE": "online",
64
  "JIT": true,
65
  "PPO_CHECKPOINT_PATH": "checkpoints/ppo_agents/Craftax-Classic-Symbolic-v1-PPO_RNN-1000M",
checkpoints/ppo_agents/Craftax-Classic-Symbolic-v1-PPO_RNN-1000M/wandb-summary.json CHANGED
@@ -1 +1,31 @@
1
- {"_runtime":5855,"Achievements/collect_stone":99.93519592285156,"achievements":19.780298233032227,"Achievements/collect_diamond":18.0168514251709,"Achievements/defeat_zombie":99.22229766845703,"Achievements/collect_wood":100.00000762939453,"Achievements/collect_coal":99.1574935913086,"Achievements/defeat_skeleton":93.77835845947266,"Achievements/make_wood_sword":99.74076843261719,"sps":173326.93437804468,"Achievements/place_table":100.00000762939453,"Achievements/place_stone":99.8703842163086,"Achievements/place_plant":99.74076843261719,"_wandb":{"runtime":5855},"_timestamp":1.7731733404075222e+09,"Achievements/make_iron_sword":97.40765380859375,"_step":1906,"episode_return":19.358198165893555,"Achievements/eat_cow":99.54634094238281,"Achievements/collect_sapling":99.74076843261719,"Achievements/make_wood_pickaxe":100.00000762939453,"Achievements/place_furnace":99.8703842163086,"Achievements/collect_drink":98.57421112060547,"episode_length":341.3253479003906,"Achievements/make_stone_sword":99.54634094238281,"Achievements/make_iron_pickaxe":92.22294616699219,"Achievements/wake_up":83.79779815673828,"Achievements/collect_iron":98.18535614013672,"Achievements/eat_plant":0.3240440785884857,"Achievements/make_stone_pickaxe":99.35191345214844}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_runtime": 5855,
3
+ "Achievements/collect_stone": 99.93519592285156,
4
+ "achievements": 19.780298233032227,
5
+ "Achievements/collect_diamond": 18.0168514251709,
6
+ "Achievements/defeat_zombie": 99.22229766845703,
7
+ "Achievements/collect_wood": 100.00000762939453,
8
+ "Achievements/collect_coal": 99.1574935913086,
9
+ "Achievements/defeat_skeleton": 93.77835845947266,
10
+ "Achievements/make_wood_sword": 99.74076843261719,
11
+ "sps": 173326.93437804468,
12
+ "Achievements/place_table": 100.00000762939453,
13
+ "Achievements/place_stone": 99.8703842163086,
14
+ "Achievements/place_plant": 99.74076843261719,
15
+ "_timestamp": 1773173340.4075222,
16
+ "Achievements/make_iron_sword": 97.40765380859375,
17
+ "_step": 1906,
18
+ "episode_return": 19.358198165893555,
19
+ "Achievements/eat_cow": 99.54634094238281,
20
+ "Achievements/collect_sapling": 99.74076843261719,
21
+ "Achievements/make_wood_pickaxe": 100.00000762939453,
22
+ "Achievements/place_furnace": 99.8703842163086,
23
+ "Achievements/collect_drink": 98.57421112060547,
24
+ "episode_length": 341.3253479003906,
25
+ "Achievements/make_stone_sword": 99.54634094238281,
26
+ "Achievements/make_iron_pickaxe": 92.22294616699219,
27
+ "Achievements/wake_up": 83.79779815673828,
28
+ "Achievements/collect_iron": 98.18535614013672,
29
+ "Achievements/eat_plant": 0.3240440785884857,
30
+ "Achievements/make_stone_pickaxe": 99.35191345214844
31
+ }
checkpoints/ppo_agents/Craftax-Symbolic-v1-PPO_RNN-1000M/config.yaml CHANGED
@@ -42,11 +42,5 @@ UPDATE_EPOCHS:
42
  value: 4
43
  USE_OPTIMISTIC_RESETS:
44
  value: true
45
- USE_WANDB:
46
- value: true
47
  VF_COEF:
48
  value: 0.5
49
- WANDB_ENTITY:
50
- value: null
51
- WANDB_PROJECT:
52
- value: null
 
42
  value: 4
43
  USE_OPTIMISTIC_RESETS:
44
  value: true
 
 
45
  VF_COEF:
46
  value: 0.5
 
 
 
 
checkpoints/ppo_agents/Craftax-Symbolic-v1-PPO_RNN-1000M/wandb-summary.json CHANGED
@@ -1 +1,76 @@
1
- {"Achievements/place_plant":97.02970123291016,"Achievements/collect_drink":88.11881256103516,"Achievements/place_torch":92.07920837402344,"Achievements/defeat_pigman":0,"Achievements/enter_vault":0,"Achievements/defeat_skeleton":34.158416748046875,"Achievements/collect_ruby":14.356435775756836,"Achievements/make_wood_sword":95.04950714111328,"_timestamp":1.77421704354534e+09,"Achievements/defeat_troll":0,"Achievements/defeat_kobold":0,"Achievements/make_stone_sword":89.1089096069336,"Achievements/defeat_necromancer":0,"Achievements/fire_bow":68.81188201904297,"Achievements/collect_stone":98.51484680175781,"Achievements/defeat_gnome_archer":0,"Achievements/cast_iceball":0,"Achievements/enchant_armour":0,"Achievements/make_diamond_sword":1.9801980257034302,"Achievements/make_iron_pickaxe":6.93069314956665,"_wandb":{"runtime":10138},"Achievements/eat_plant":0.9900990128517151,"Achievements/enter_gnomish_mines":0,"Achievements/damage_necromancer":0,"Achievements/find_bow":82.67326354980469,"Achievements/defeat_gnome_warrior":0,"_runtime":10138,"Achievements/enter_graveyard":0,"Achievements/eat_cow":70.79207611083984,"Achievements/drink_potion":58.41584014892578,"Achievements/enter_dungeon":87.62376403808594,"Achievements/eat_bat":0,"Achievements/defeat_deep_thing":0,"Achievements/make_iron_sword":3.9603960514068604,"achievements":23.94554901123047,"Achievements/make_wood_pickaxe":99.00990295410156,"Achievements/eat_snail":75.7425765991211,"Achievements/wake_up":53.96039581298828,"Achievements/cast_fireball":0,"Achievements/defeat_zombie":44.05940628051758,"Achievements/defeat_lizard":0,"Achievements/make_arrow":84.15841674804688,"Achievements/defeat_frost_troll":0,"Achievements/make_diamond_armour":0,"Achievements/enter_fire_realm":0,"Achievements/make_torch":92.07920837402344,"Achievements/enchant_sword":0,"episode_length":331.6633605957031,"Achievements/place_table":99.50495147705078,"Achievements/enter_ice_realm":0,"Achievements/learn_fireball":0,"Achievements/collect_wood":99.50495147705078,"Achievements/collect_sapphire":12.87128734588623,"Achievements/open_chest":82.67326354980469,"Achievements/collect_iron":65.3465347290039,"Achievements/defeat_archer":0,"Achievements/defeat_knight":0,"Achievements/make_iron_armour":0,"Achievements/place_furnace":98.51484680175781,"Achievements/defeat_orc_solider":60.39603805541992,"Achievements/collect_sapling":97.02970123291016,"_step":15257,"Achievements/make_diamond_pickaxe":3.9603960514068604,"Achievements/defeat_orc_mage":50.99009704589844,"Achievements/place_stone":98.51484680175781,"episode_return":35.05544662475586,"Achievements/make_stone_pickaxe":83.1683120727539,"Achievements/enter_troll_mines":0,"Achievements/defeat_fire_elemental":0,"Achievements/learn_iceball":0,"Achievements/collect_diamond":15.841584205627441,"Achievements/collect_coal":86.63365936279297,"Achievements/defeat_ice_elemental":0,"Achievements/enter_sewers":0,"sps":100008.66166595295}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "Achievements/place_plant": 97.02970123291016,
3
+ "Achievements/collect_drink": 88.11881256103516,
4
+ "Achievements/place_torch": 92.07920837402344,
5
+ "Achievements/defeat_pigman": 0,
6
+ "Achievements/enter_vault": 0,
7
+ "Achievements/defeat_skeleton": 34.158416748046875,
8
+ "Achievements/collect_ruby": 14.356435775756836,
9
+ "Achievements/make_wood_sword": 95.04950714111328,
10
+ "_timestamp": 1774217043.54534,
11
+ "Achievements/defeat_troll": 0,
12
+ "Achievements/defeat_kobold": 0,
13
+ "Achievements/make_stone_sword": 89.1089096069336,
14
+ "Achievements/defeat_necromancer": 0,
15
+ "Achievements/fire_bow": 68.81188201904297,
16
+ "Achievements/collect_stone": 98.51484680175781,
17
+ "Achievements/defeat_gnome_archer": 0,
18
+ "Achievements/cast_iceball": 0,
19
+ "Achievements/enchant_armour": 0,
20
+ "Achievements/make_diamond_sword": 1.9801980257034302,
21
+ "Achievements/make_iron_pickaxe": 6.93069314956665,
22
+ "Achievements/eat_plant": 0.9900990128517151,
23
+ "Achievements/enter_gnomish_mines": 0,
24
+ "Achievements/damage_necromancer": 0,
25
+ "Achievements/find_bow": 82.67326354980469,
26
+ "Achievements/defeat_gnome_warrior": 0,
27
+ "_runtime": 10138,
28
+ "Achievements/enter_graveyard": 0,
29
+ "Achievements/eat_cow": 70.79207611083984,
30
+ "Achievements/drink_potion": 58.41584014892578,
31
+ "Achievements/enter_dungeon": 87.62376403808594,
32
+ "Achievements/eat_bat": 0,
33
+ "Achievements/defeat_deep_thing": 0,
34
+ "Achievements/make_iron_sword": 3.9603960514068604,
35
+ "achievements": 23.94554901123047,
36
+ "Achievements/make_wood_pickaxe": 99.00990295410156,
37
+ "Achievements/eat_snail": 75.7425765991211,
38
+ "Achievements/wake_up": 53.96039581298828,
39
+ "Achievements/cast_fireball": 0,
40
+ "Achievements/defeat_zombie": 44.05940628051758,
41
+ "Achievements/defeat_lizard": 0,
42
+ "Achievements/make_arrow": 84.15841674804688,
43
+ "Achievements/defeat_frost_troll": 0,
44
+ "Achievements/make_diamond_armour": 0,
45
+ "Achievements/enter_fire_realm": 0,
46
+ "Achievements/make_torch": 92.07920837402344,
47
+ "Achievements/enchant_sword": 0,
48
+ "episode_length": 331.6633605957031,
49
+ "Achievements/place_table": 99.50495147705078,
50
+ "Achievements/enter_ice_realm": 0,
51
+ "Achievements/learn_fireball": 0,
52
+ "Achievements/collect_wood": 99.50495147705078,
53
+ "Achievements/collect_sapphire": 12.87128734588623,
54
+ "Achievements/open_chest": 82.67326354980469,
55
+ "Achievements/collect_iron": 65.3465347290039,
56
+ "Achievements/defeat_archer": 0,
57
+ "Achievements/defeat_knight": 0,
58
+ "Achievements/make_iron_armour": 0,
59
+ "Achievements/place_furnace": 98.51484680175781,
60
+ "Achievements/defeat_orc_solider": 60.39603805541992,
61
+ "Achievements/collect_sapling": 97.02970123291016,
62
+ "_step": 15257,
63
+ "Achievements/make_diamond_pickaxe": 3.9603960514068604,
64
+ "Achievements/defeat_orc_mage": 50.99009704589844,
65
+ "Achievements/place_stone": 98.51484680175781,
66
+ "episode_return": 35.05544662475586,
67
+ "Achievements/make_stone_pickaxe": 83.1683120727539,
68
+ "Achievements/enter_troll_mines": 0,
69
+ "Achievements/defeat_fire_elemental": 0,
70
+ "Achievements/learn_iceball": 0,
71
+ "Achievements/collect_diamond": 15.841584205627441,
72
+ "Achievements/collect_coal": 86.63365936279297,
73
+ "Achievements/defeat_ice_elemental": 0,
74
+ "Achievements/enter_sewers": 0,
75
+ "sps": 100008.66166595295
76
+ }
configs/defaults.yaml CHANGED
@@ -136,6 +136,16 @@ layer_size: 512
136
  # ── Inference (--mode inference) ─────────────────────────────────────────────
137
  eval_steps: 10000
138
  eval_num_envs: 32
 
 
 
 
 
 
 
 
 
 
139
 
140
  # ── Checkpointing ────────────────────────────────────────────────────────────
141
  save_policy: true
 
136
  # ── Inference (--mode inference) ─────────────────────────────────────────────
137
  eval_steps: 10000
138
  eval_num_envs: 32
139
+ # Sampler used by --mode inference:
140
+ # sample_plan replan from scratch every eval_replan steps, conditioned only
141
+ # on the current observation. What build_eval_fn does, and the
142
+ # protocol behind every published number.
143
+ # inpainting replan every step with each executed action locked as an
144
+ # inpainting prefix. An ablation on the planning-as-inpainting
145
+ # design choice, not the source of any published number; it
146
+ # scores far lower on the same weights.
147
+ inference_sampler: sample_plan
148
+ eval_replan: 8 # env steps executed per plan (sample_plan)
149
 
150
  # ── Checkpointing ────────────────────────────────────────────────────────────
151
  save_policy: true
demo_craftax.ipynb CHANGED
@@ -61,10 +61,9 @@
61
  "# =============================================================================\n",
62
  "\n",
63
  "# Public HuggingFace repo holding the released artefacts: source, checkpoints\n",
64
- "# and the pre-computed ablation outputs. This is an anonymous mirror published\n",
65
- "# for double-blind review, so it carries no account name or URL that would\n",
66
- "# identify the authors. Point it at your own Hub repo holding the same layout,\n",
67
- "# or train from source (see README.md).\n",
68
  "HF_REPO_ID = \"AnonMLuser/remdm-planner-craftax\"\n",
69
  "LOCAL_DIR = \"remdm-planner-craftax\"\n",
70
  "\n",
@@ -264,7 +263,7 @@
264
  " \"craftax>=1.5.0\",\n",
265
  " \"flax>=0.12.6\",\n",
266
  " \"optax>=0.2.8\",\n",
267
- " \"orbax-checkpoint>=0.5\",\n",
268
  " \"distrax>=0.1.7\",\n",
269
  " \"chex>=0.1.91\",\n",
270
  " \"polars>=1.39.3\",\n",
@@ -293,9 +292,9 @@
293
  "\n",
294
  "if HF_REPO_ID == \"UNSET_HF_REPO_ID\":\n",
295
  " raise RuntimeError(\n",
296
- " \"HF_REPO_ID is unset: checkpoints are withheld for anonymous review. \"\n",
297
- " \"Edit the constant in Cell 1 to point at a Hub repo holding the \"\n",
298
- " \"published layout, or train from source (see README.md).\"\n",
299
  " )\n",
300
  "\n",
301
  "snapshot_path = snapshot_download(repo_id=HF_REPO_ID, local_dir=LOCAL_DIR)\n",
@@ -537,6 +536,7 @@
537
  " \"EVAL_STEPS\": EVAL_STEPS,\n",
538
  " \"EVAL_NUM_ENVS\": EVAL_NUM_ENVS,\n",
539
  " \"DIFFUSION_STEPS_EVAL\": INPAINTING_DIFFUSION_STEPS,\n",
 
540
  " \"USE_WANDB\": False,\n",
541
  " \"CHECKPOINT_PATH\": DIFFUSION_ONLINE_CKPT,\n",
542
  " }\n",
@@ -981,12 +981,16 @@
981
  " **1.16** on Craftax Classic (effective sample size 437 of a 1024-window batch),\n",
982
  " so the returns *do* rank windows within a batch and the familiar explanation\n",
983
  " that sparse returns fail to discriminate is unavailable. Measured directly at\n",
984
- " the pretrained parameters, `‖g_δ‖/‖∇L_BC‖ = 0.49 ± 0.01` at cosine\n",
985
- " **0.02 ± 0.05**. A cosine needs a reference: in `D = 9.33 × 10⁶` dimensions two\n",
986
- " independent directions have `cos 3.3 × 10⁻⁴`, so 0.02 is about sixty standard\n",
987
- " deviations from random`g_δ` carries structure. But two independent noise\n",
988
- " draws of `∇L_BC` itself agree at **0.893 ± 0.010**, so `g_δ` points somewhere\n",
989
- " imitation does not.\n",
 
 
 
 
990
  "3. **Shrinking the return term makes things worse — the central negative control.**\n",
991
  " If the return term drove the degradation, suppressing it should help. Advantage\n",
992
  " clipping confines the weights to [0.8, 1.2] and does exactly what it should:\n",
@@ -994,10 +998,19 @@
994
  " **0.097 ± 0.003**, a fivefold reduction. Its score is **5.06** — 3.16 below\n",
995
  " baseline RL and second worst in the suite. The binary win mask sits between the\n",
996
  " two on both axes (`CV_A` 0.77, ratio 0.417 ± 0.025) and scores 7.28, also below\n",
997
- " baseline. Across these three, the ordering of the return term's magnitude is the\n",
998
- " **reverse** of the ordering of final score. A condition whose return term is\n",
 
999
  " nearly absent degrades further than one whose return term is large, so the\n",
1000
  " degradation cannot be attributed to the return weighting.\n",
 
 
 
 
 
 
 
 
1001
  "\n",
1002
  "### What is left is the data\n",
1003
  "\n",
@@ -1147,12 +1160,235 @@
1147
  " print(fh.read())"
1148
  ]
1149
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1150
  {
1151
  "cell_type": "markdown",
1152
  "id": "d2dbe7e3e2ab",
1153
  "metadata": {},
1154
  "source": [
1155
- "## 3. Conclusions\n",
1156
  "\n",
1157
  "1. **Return-weighted ELBO fine-tuning degrades the checkpoint.** From **11.81**,\n",
1158
  " baseline RL falls to **8.22 ± 0.14** and none of the 25 conditions ends above\n",
@@ -1167,21 +1403,27 @@
1167
  " 3.73** over the same iterations in which eval score falls from **12.06 to\n",
1168
  " 8.49**. The objective is failing on its own terms too.\n",
1169
  "3. **The reward is not what does the damage.** The return term is real and large:\n",
1170
- " `‖g_δ‖/‖∇L_BC‖ = 0.49 ± 0.01` at cosine `0.02 ± 0.05`, against `0.893 ± 0.010`\n",
1171
- " for `∇L_BC` against itself around sixty standard deviations from a random\n",
1172
- " direction, so it is structure and not noise. But the central control does not\n",
1173
- " go our way. Advantage clipping cuts that ratio fivefold to `0.097 ± 0.003` and\n",
1174
- " scores **3.16 below** baseline RL, second worst in the suite. Across the three\n",
1175
- " transforms where the decomposition is defined, the ordering of the return\n",
1176
- " term's magnitude is the reverse of the ordering of final score.\n",
 
 
1177
  "4. **What is left is the data.** The suite is ordered by plasticity — conditions\n",
1178
  " that restrict the update keep more of the checkpoint (Spearman **−0.79**\n",
1179
- " between final KL and final score) — and the condition closest to unweighted\n",
1180
- " training on self-generated rollouts sits near the bottom. Together these point\n",
1181
- " at fine-tuning on the model's own rollouts as the damaging ingredient, with the\n",
1182
- " return weighting a large but incidental passenger. The paper does not dress\n",
1183
- " this up as more than it is: without the unweighted arm it is an inference from\n",
1184
- " a near-substitute.\n",
 
 
 
 
1185
  "\n",
1186
  "**Why the sign constraint is not the whole story.** Eq. 3 is a bound only for\n",
1187
  "`A_i ≥ 0`, so the objective can re-rank sampled behaviour but never push mass away\n",
@@ -1221,7 +1463,9 @@
1221
  "objective: `CV_A` comes free from an effective-sample-size counter and says whether\n",
1222
  "the returns rank anything at all. The ratio `‖g_δ‖/‖∇L_BC‖` costs two backward\n",
1223
  "passes and says how much of the update the reward is responsible for. Ours said\n",
1224
- "0.49, which looked like a mechanism until the clipping control said otherwise.\n",
 
 
1225
  "\n",
1226
  "**Open problem.** Per-step formulations over the denoising chain (d1, DiffPO) admit\n",
1227
  "**signed** advantages, and so change the object being optimised rather than the\n",
 
61
  "# =============================================================================\n",
62
  "\n",
63
  "# Public HuggingFace repo holding the released artefacts: source, checkpoints\n",
64
+ "# and the pre-computed ablation outputs. No authentication required. Point it\n",
65
+ "# at your own Hub repo holding the same layout, or train from source\n",
66
+ "# (see README.md).\n",
 
67
  "HF_REPO_ID = \"AnonMLuser/remdm-planner-craftax\"\n",
68
  "LOCAL_DIR = \"remdm-planner-craftax\"\n",
69
  "\n",
 
263
  " \"craftax>=1.5.0\",\n",
264
  " \"flax>=0.12.6\",\n",
265
  " \"optax>=0.2.8\",\n",
266
+ " \"orbax-checkpoint>=0.12\",\n",
267
  " \"distrax>=0.1.7\",\n",
268
  " \"chex>=0.1.91\",\n",
269
  " \"polars>=1.39.3\",\n",
 
292
  "\n",
293
  "if HF_REPO_ID == \"UNSET_HF_REPO_ID\":\n",
294
  " raise RuntimeError(\n",
295
+ " \"HF_REPO_ID is unset. Edit the constant in Cell 1 to point at a Hub \"\n",
296
+ " \"repo holding the published layout, or train from source \"\n",
297
+ " \"(see README.md).\"\n",
298
  " )\n",
299
  "\n",
300
  "snapshot_path = snapshot_download(repo_id=HF_REPO_ID, local_dir=LOCAL_DIR)\n",
 
536
  " \"EVAL_STEPS\": EVAL_STEPS,\n",
537
  " \"EVAL_NUM_ENVS\": EVAL_NUM_ENVS,\n",
538
  " \"DIFFUSION_STEPS_EVAL\": INPAINTING_DIFFUSION_STEPS,\n",
539
+ " \"INFERENCE_SAMPLER\": \"inpainting\",\n",
540
  " \"USE_WANDB\": False,\n",
541
  " \"CHECKPOINT_PATH\": DIFFUSION_ONLINE_CKPT,\n",
542
  " }\n",
 
981
  " **1.16** on Craftax Classic (effective sample size 437 of a 1024-window batch),\n",
982
  " so the returns *do* rank windows within a batch and the familiar explanation\n",
983
  " that sparse returns fail to discriminate is unavailable. Measured directly at\n",
984
+ " the pretrained parameters, `‖g_δ‖/‖∇L_BC‖ = 0.48 ± 0.01` at cosine\n",
985
+ " **0.02 ± 0.05**. A cosine needs a reference. In `D = 9.33 × 10⁶` dimensions two\n",
986
+ " independent directions have `cos ~ N(0, 1/D)`, a standard deviation of\n",
987
+ " `3.3 × 10⁻⁴`but the seed-level dispersion of the measured cosine is two\n",
988
+ " orders of magnitude larger than that, so **we do not read this column as a test\n",
989
+ " against isotropy**: 0.02 ± 0.05 is consistent with zero at the precision of the\n",
990
+ " seed mean. The informative reference is how well imitation agrees with itself:\n",
991
+ " two independent noise draws of `∇L_BC` give **0.893 ± 0.010**. Against that,\n",
992
+ " `g_δ` is half the imitation gradient in norm and points somewhere imitation\n",
993
+ " does not.\n",
994
  "3. **Shrinking the return term makes things worse — the central negative control.**\n",
995
  " If the return term drove the degradation, suppressing it should help. Advantage\n",
996
  " clipping confines the weights to [0.8, 1.2] and does exactly what it should:\n",
 
998
  " **0.097 ± 0.003**, a fivefold reduction. Its score is **5.06** — 3.16 below\n",
999
  " baseline RL and second worst in the suite. The binary win mask sits between the\n",
1000
  " two on both axes (`CV_A` 0.77, ratio 0.417 ± 0.025) and scores 7.28, also below\n",
1001
+ " baseline. Across these three the return term's magnitude and final score are\n",
1002
+ " ordered **the same way**, largest term scoring highest which is the reverse of\n",
1003
+ " what a return-driven degradation predicts. A condition whose return term is\n",
1004
  " nearly absent degrades further than one whose return term is large, so the\n",
1005
  " degradation cannot be attributed to the return weighting.\n",
1006
+ "4. **Most of the ratio is weight dispersion, not the return signal.** A\n",
1007
+ " shuffled-`δ` null permutes `δ` across the batch, preserving the weight multiset\n",
1008
+ " and `CV_A` while destroying every association between a window's weight and its\n",
1009
+ " own gradient. On Craftax Classic it retains roughly three quarters of the\n",
1010
+ " measured ratio at every transform, at a near-constant **0.36 of `CV_A`**. So on\n",
1011
+ " this benchmark the ratio is largely a function of dispersion and the ordering\n",
1012
+ " above tracks dispersion rather than the return signal specifically. MiniHack\n",
1013
+ " separates the two much further — see its notebook.\n",
1014
  "\n",
1015
  "### What is left is the data\n",
1016
  "\n",
 
1160
  " print(fh.read())"
1161
  ]
1162
  },
1163
+ {
1164
+ "cell_type": "markdown",
1165
+ "metadata": {},
1166
+ "source": [
1167
+ "## 3. The return term, measured directly\n",
1168
+ "\n",
1169
+ "Section 2's conditions vary *how* the return enters the loss. This section measures\n",
1170
+ "*how much* it contributes, at a single parameter point, with no training involved.\n",
1171
+ "\n",
1172
+ "The return-weighted ELBO gradient factors exactly, with no approximation:\n",
1173
+ "\n",
1174
+ "```\n",
1175
+ "∇L_RW = Ā · [ ∇L_BC + (1/B) Σ_i δ_i ∇ℓ_i ] δ_i = A_i/Ā − 1\n",
1176
+ " └────── g_δ ──────┘\n",
1177
+ "```\n",
1178
+ "\n",
1179
+ "`Ā` rescales the step size and leaves the direction unchanged, so everything the\n",
1180
+ "return does enters through `g_δ`. Both terms are computable at one parameter point,\n",
1181
+ "so we compute them: load the checkpoint every ablation starts from, collect one\n",
1182
+ "on-policy batch under the suite's own rollout function, and take `∇L_BC`, `g_δ` and\n",
1183
+ "`∇L_RW` on that batch under a **shared** `(z_t, t)` draw, so the only difference is\n",
1184
+ "the weight vector. 3 rollout seeds × 8 noise draws, 1,024-window batches. It runs in\n",
1185
+ "minutes on a laptop CPU — `run_ablations.py --measure-gdelta`.\n",
1186
+ "\n",
1187
+ "**Correctness check.** Reconstructing `∇L_RW` from the two right-hand terms and\n",
1188
+ "comparing against the directly computed one gives a relative residual below\n",
1189
+ "**5 × 10⁻⁷** on every draw — float32 accumulation over 9.33M parameters, not a\n",
1190
+ "modelling gap.\n",
1191
+ "\n",
1192
+ "The `Shuffled δ` column permutes `δ` across the batch. That preserves the weight\n",
1193
+ "multiset and `CV_A` exactly while destroying every association between a window's\n",
1194
+ "weight and its own gradient, so it bounds how much of the ratio needs that\n",
1195
+ "association at all.\n"
1196
+ ]
1197
+ },
1198
+ {
1199
+ "cell_type": "code",
1200
+ "execution_count": null,
1201
+ "metadata": {},
1202
+ "outputs": [],
1203
+ "source": [
1204
+ "# =============================================================================\n",
1205
+ "# CELL 12 — The measured return term (paper Table 2, appendix \"Measuring the\n",
1206
+ "# Return Term\"). Read from the shipped gdelta artefacts, not retyped.\n",
1207
+ "# =============================================================================\n",
1208
+ "\n",
1209
+ "import json\n",
1210
+ "import os\n",
1211
+ "\n",
1212
+ "import polars as pl\n",
1213
+ "\n",
1214
+ "RUN_DIR = \"experiments/rl_finetuning/outputs/craftax_classic_ablations\"\n",
1215
+ "GDELTA_DIR = os.path.join(RUN_DIR, \"gdelta\")\n",
1216
+ "\n",
1217
+ "gdelta_df = pl.read_csv(os.path.join(RUN_DIR, \"tables\", \"gdelta.csv\"))\n",
1218
+ "print(\"Return term at the pretrained checkpoint, 3 seeds x 8 draws:\")\n",
1219
+ "print(\n",
1220
+ " gdelta_df.select(\n",
1221
+ " [\"Transform\", \"CV_A\", \"Ratio\", \"Ratio_Sd\", \"Ratio_Shuffled\", \"Cos\", \"Cos_Sd\",\n",
1222
+ " \"A1_Violated\"]\n",
1223
+ " )\n",
1224
+ ")\n",
1225
+ "\n",
1226
+ "with open(os.path.join(GDELTA_DIR, \"gdelta_aggregate.json\")) as fh:\n",
1227
+ " agg = json.load(fh)\n",
1228
+ "\n",
1229
+ "print()\n",
1230
+ "_draws = agg[\"n_draws_per_seed\"]\n",
1231
+ "_draws = _draws[0] if isinstance(_draws, list) and len(set(_draws)) == 1 else _draws\n",
1232
+ "print(f\"seeds x draws : {agg['n_seeds']} x {_draws}\")\n",
1233
+ "print(f\"batch (windows) : {agg['batch']}\")\n",
1234
+ "print(f\"parameters : {agg['n_params']:,}\")\n",
1235
+ "print(f\"Eq.-4 max rel. residual : {agg['eq4_residual_max']:.3e}\")\n",
1236
+ "print(f\"cos(random directions) sd: {agg['random_cos_sd']:.3e}\")\n",
1237
+ "print(\n",
1238
+ " f\"cos(dL_BC, dL_BC') mean : {agg['bc_self_cos_mean']:.3f} \"\n",
1239
+ " f\"+/- {agg['bc_self_cos_std_seeds']:.3f} <- what alignment looks like when a \"\n",
1240
+ " f\"direction IS imitation\"\n",
1241
+ ")\n",
1242
+ "\n",
1243
+ "print()\n",
1244
+ "print(\n",
1245
+ " \"Reading it: the ratio and the final score are ordered the SAME way across the\\n\"\n",
1246
+ " \"three transforms where the decomposition is defined (baseline 0.485/8.22,\\n\"\n",
1247
+ " \"bc_wins 0.417/7.28, advantage_clip 0.097/5.06). A return-driven degradation\\n\"\n",
1248
+ " \"predicts the opposite. normalized_adv is excluded: it mean-centres the weights,\\n\"\n",
1249
+ " \"so Abar ~ 0, delta is undefined and (A1) fails -- the objective is no longer a\\n\"\n",
1250
+ " \"bound there, which is the point, not a measurement artefact.\\n\"\n",
1251
+ " \"\\n\"\n",
1252
+ " \"Note the Shuffled column: on Craftax Classic it retains roughly three quarters\\n\"\n",
1253
+ " \"of each measured ratio, at a near-constant 0.36 of CV_A. Most of the ratio is\\n\"\n",
1254
+ " \"weight dispersion rather than the return signal specifically. MiniHack does not\\n\"\n",
1255
+ " \"reproduce that -- there the null costs about five sixths -- so the redundancy is\\n\"\n",
1256
+ " \"a Craftax-side property, not a law.\"\n",
1257
+ ")\n"
1258
+ ]
1259
+ },
1260
+ {
1261
+ "cell_type": "markdown",
1262
+ "metadata": {},
1263
+ "source": [
1264
+ "## 4. Review arms: the unweighted control and the learning-rate sweep\n",
1265
+ "\n",
1266
+ "The 25-condition suite leaves two accounts of the degradation open, and each has a\n",
1267
+ "run that closes it. These sit **outside** the suite's tables — they are separate\n",
1268
+ "runs on a different host, at three seeds each, shipped under\n",
1269
+ "`experiments/rl_finetuning/outputs/review_*/`.\n",
1270
+ "\n",
1271
+ "**Read them against each other, not against Section 2.** The suite does not\n",
1272
+ "reproduce across hosts: the same checkpoint and configuration score the pretrained\n",
1273
+ "planner at **11.9754** here against **11.8084** in Section 2, and baseline RL at\n",
1274
+ "**8.3395** against **8.2233** — a consistent offset of 1.41%. So `baseline_rl` was\n",
1275
+ "re-run alongside the new arms as a same-stack anchor, and every comparison below is\n",
1276
+ "within the review stack.\n",
1277
+ "\n",
1278
+ "| Arm | What it asks |\n",
1279
+ "|---|---|\n",
1280
+ "| `review_anchor_baseline_rl` | The same-stack anchor every other row is quoted against |\n",
1281
+ "| `review_run1_bc_all` | **Is the weighting the problem, or the data?** Every weight fixed at one, over the same rollout windows — the unweighted control |\n",
1282
+ "| `review_run2_advclip_lr_matched` | **Was the clipping control just a smaller step?** `advantage_clip` re-run with its learning rate scaled by the measured `Ā_base/Ā_clip = 1.0744`, giving 3.223e-4 |\n",
1283
+ "| `review_run4_baseline_lr1e-4` / `_lr1e-5` | **Is the degradation just too large a step?** The suite fine-tunes at the pretraining learning rate of 3e-4 |\n",
1284
+ "\n",
1285
+ "The MiniHack counterparts of all three questions were run too, and the paper reports\n",
1286
+ "them, but **their run directories are not part of this release** — only the Craftax\n",
1287
+ "Classic ones are shipped here. The MiniHack numbers are quoted in its notebook.\n"
1288
+ ]
1289
+ },
1290
+ {
1291
+ "cell_type": "code",
1292
+ "execution_count": null,
1293
+ "metadata": {},
1294
+ "outputs": [],
1295
+ "source": [
1296
+ "# =============================================================================\n",
1297
+ "# CELL 13 — Review arms, read from the shipped run directories\n",
1298
+ "# =============================================================================\n",
1299
+ "\n",
1300
+ "import os\n",
1301
+ "\n",
1302
+ "import polars as pl\n",
1303
+ "\n",
1304
+ "OUT = \"experiments/rl_finetuning/outputs\"\n",
1305
+ "REVIEW_RUNS = [\n",
1306
+ " (\"review_anchor_baseline_rl\", \"anchor: baseline_rl @3e-4 (same stack)\"),\n",
1307
+ " (\"review_run1_bc_all\", \"bc_all: uniform weights, all windows\"),\n",
1308
+ " (\"review_run2_advclip_lr_matched\", \"advantage_clip @3.223e-4 (matched step)\"),\n",
1309
+ " (\"review_run4_baseline_lr1e-4\", \"baseline_rl @1e-4\"),\n",
1310
+ " (\"review_run4_baseline_lr1e-5\", \"baseline_rl @1e-5\"),\n",
1311
+ "]\n",
1312
+ "\n",
1313
+ "rows = []\n",
1314
+ "for run, label in REVIEW_RUNS:\n",
1315
+ " csv = os.path.join(OUT, run, \"tables\", \"main_results.csv\")\n",
1316
+ " if not os.path.exists(csv):\n",
1317
+ " print(f\"missing: {csv}\")\n",
1318
+ " continue\n",
1319
+ " df = pl.read_csv(csv)\n",
1320
+ " r = df.row(0, named=True)\n",
1321
+ " rows.append(\n",
1322
+ " {\n",
1323
+ " \"Arm\": label,\n",
1324
+ " \"Condition\": r[\"Method\"],\n",
1325
+ " \"Score\": r[\"Final_Score\"],\n",
1326
+ " \"Sd\": r[\"Score_Std\"],\n",
1327
+ " \"vs_Pretrained\": r[\"Delta_vs_Pretrained\"],\n",
1328
+ " }\n",
1329
+ " )\n",
1330
+ "\n",
1331
+ "review_df = pl.DataFrame(rows)\n",
1332
+ "print(\"Review arms (same-stack pretrained checkpoint = 11.9754):\")\n",
1333
+ "print(review_df)\n",
1334
+ "\n",
1335
+ "anchor = next(r[\"Score\"] for r in rows if r[\"Condition\"] == \"baseline_rl\" and \"anchor\" in r[\"Arm\"])\n",
1336
+ "bc_all = next((r[\"Score\"] for r in rows if r[\"Condition\"] == \"bc_all\"), None)\n",
1337
+ "lr5 = next((r[\"Score\"] for r in rows if \"1e-5\" in r[\"Arm\"]), None)\n",
1338
+ "PRETRAINED_SAME_STACK = 11.9754\n",
1339
+ "\n",
1340
+ "print()\n",
1341
+ "if bc_all is not None:\n",
1342
+ " print(\n",
1343
+ " f\"Unweighted control : bc_all {bc_all:.4f} vs anchor {anchor:.4f} \"\n",
1344
+ " f\"= {bc_all - anchor:+.4f}\"\n",
1345
+ " )\n",
1346
+ " print(\n",
1347
+ " \" Removing the weighting while keeping the rollouts\\n\"\n",
1348
+ " \" degrades the model FURTHER than the weighted objective\\n\"\n",
1349
+ " \" does. The seeds do not overlap. That is the protective\\n\"\n",
1350
+ " \" reading -- the inverse of a return-driven degradation.\\n\"\n",
1351
+ " \" The host offset works against this result, not for it:\\n\"\n",
1352
+ " \" on the published stack bc_all would sit lower still.\\n\"\n",
1353
+ " \" Exact two-sided permutation test: p = 0.10, which is the\\n\"\n",
1354
+ " \" design floor at 3 seeds, not a significant result.\"\n",
1355
+ " )\n",
1356
+ "if lr5 is not None:\n",
1357
+ " shortfall = PRETRAINED_SAME_STACK - lr5\n",
1358
+ " recovered = (lr5 - anchor) / (PRETRAINED_SAME_STACK - anchor)\n",
1359
+ " print()\n",
1360
+ " print(\n",
1361
+ " f\"Learning-rate sweep: 3e-4 {anchor:.4f} -> 1e-4 -> 1e-5 {lr5:.4f}; still \"\n",
1362
+ " f\"{shortfall:.4f} below the same-stack checkpoint,\"\n",
1363
+ " )\n",
1364
+ " print(\n",
1365
+ " f\" having recovered {recovered:.0%} of the default's \"\n",
1366
+ " f\"shortfall. That residual is more than ten times the\"\n",
1367
+ " )\n",
1368
+ " print(\n",
1369
+ " \" seed sd at that rate, and the sd does not fall as the \"\n",
1370
+ " \"step shrinks. Lowering the step by a\"\n",
1371
+ " )\n",
1372
+ " print(\n",
1373
+ " \" factor of thirty buys back two thirds of the \"\n",
1374
+ " \"degradation WITHOUT removing it. 3e-5 was not run.\"\n",
1375
+ " )\n",
1376
+ "print()\n",
1377
+ "print(\n",
1378
+ " \"Matched effective step: advantage_clip at 3.223e-4 reaches 4.9246, leaving it\\n\"\n",
1379
+ " \" 3.4149 below the same-stack anchor, where the unmatched arm\\n\"\n",
1380
+ " \" sat 3.1601 below the published baseline. Equalising the\\n\"\n",
1381
+ " \" effective step does NOT close the clipping gap, so Section 3's\\n\"\n",
1382
+ " \" central negative control was not measuring step size.\"\n",
1383
+ ")\n"
1384
+ ]
1385
+ },
1386
  {
1387
  "cell_type": "markdown",
1388
  "id": "d2dbe7e3e2ab",
1389
  "metadata": {},
1390
  "source": [
1391
+ "## 5. Conclusions\n",
1392
  "\n",
1393
  "1. **Return-weighted ELBO fine-tuning degrades the checkpoint.** From **11.81**,\n",
1394
  " baseline RL falls to **8.22 ± 0.14** and none of the 25 conditions ends above\n",
 
1403
  " 3.73** over the same iterations in which eval score falls from **12.06 to\n",
1404
  " 8.49**. The objective is failing on its own terms too.\n",
1405
  "3. **The reward is not what does the damage.** The return term is real and large:\n",
1406
+ " `‖g_δ‖/‖∇L_BC‖ = 0.48 ± 0.01`, half the imitation gradient in norm, against\n",
1407
+ " `0.893 ± 0.010` for `∇L_BC` against itself. Its cosine with imitation is\n",
1408
+ " `0.02 ± 0.05`, which we read as consistent with zero rather than as a departure\n",
1409
+ " from isotropy. But the central control does not go our way. Advantage clipping\n",
1410
+ " cuts that ratio fivefold to `0.097 ± 0.003` and scores **3.16 below** baseline\n",
1411
+ " RL, second worst in the suite. Across the three transforms where the\n",
1412
+ " decomposition is defined, the return term's magnitude and final score are\n",
1413
+ " ordered the same way — the reverse of what a return-driven degradation\n",
1414
+ " predicts.\n",
1415
  "4. **What is left is the data.** The suite is ordered by plasticity — conditions\n",
1416
  " that restrict the update keep more of the checkpoint (Spearman **−0.79**\n",
1417
+ " between final KL and final score) — and the unweighted control now exists.\n",
1418
+ " `bc_all` fixes every weight at one over the same rollout windows the weighted\n",
1419
+ " baseline trains on, and it reaches **4.73 ± 0.19** against a same-stack anchor\n",
1420
+ " of **8.34 ± 0.15** (Section 4 below). Removing the weighting while keeping the\n",
1421
+ " rollouts costs *more* than the weighting itself, on both benchmarks. That fits\n",
1422
+ " a **protective** reading of the return weighting rather than a damaging one —\n",
1423
+ " the inverse of the reading this study set out to test. It does not show whether\n",
1424
+ " the rollouts themselves contribute, which would need an arm that does not train\n",
1425
+ " on them, and the pairwise permutation test reaches only `p = 0.10`, the design\n",
1426
+ " floor at three seeds.\n",
1427
  "\n",
1428
  "**Why the sign constraint is not the whole story.** Eq. 3 is a bound only for\n",
1429
  "`A_i ≥ 0`, so the objective can re-rank sampled behaviour but never push mass away\n",
 
1463
  "objective: `CV_A` comes free from an effective-sample-size counter and says whether\n",
1464
  "the returns rank anything at all. The ratio `‖g_δ‖/‖∇L_BC‖` costs two backward\n",
1465
  "passes and says how much of the update the reward is responsible for. Ours said\n",
1466
+ "0.48, which looked like a mechanism until the clipping control said otherwise.\n",
1467
+ "Take the shuffled-`δ` null alongside it — on Craftax Classic three quarters of\n",
1468
+ "that 0.48 survives destroying the weight/gradient association entirely.\n",
1469
  "\n",
1470
  "**Open problem.** Per-step formulations over the denoising chain (d1, DiffPO) admit\n",
1471
  "**signed** advantages, and so change the object being optimised rather than the\n",
experiments/README.md CHANGED
@@ -8,7 +8,7 @@ These scripts are **standalone research code** — they import from `src/` but d
8
  ## `rl_finetuning/` — RL Fine-Tuning Ablation Suite
9
 
10
  Diagnoses why RL fine-tuning of the diffusion model collapses and which interventions fix it.
11
- Implements **25 ablations**: a baseline plus four groups (A: Regularisation, B: Training Signal, C: Architecture, D: Data Quality), with a comprehensive diagnostic and analysis pipeline.
12
 
13
  **Training data is on-policy.** Each iteration rolls the *current* model out under its EMA weights (`diffusion_steps_collect` denoising steps per plan, `num_steps // plan_horizon` plan cycles) and trains on those windows, weighted by each window's own H-step reward sum. The suite therefore needs no expert: `--ppo-checkpoint` is gone, and only the pretrained diffusion `--checkpoint` is required.
14
 
@@ -20,27 +20,30 @@ rl_finetuning/
20
  ├── ablations/
21
  │ ├── losses.py # All loss/objective variants as factory functions
22
  │ ├── optimizers.py # LLRD, LoRA, gradient surgery, param masking
23
- │ ├── registry.py # AblationSpec dataclass + REGISTRY (25 ablations)
24
  │ └── training.py # make_run_ablation() factory + AblationHistory dataclass
25
  ├── diagnostics/
26
  │ ├── gradient.py # Grad alignment, per-layer norms, surgery metrics
27
  │ ├── representation.py # KL drift, CKA similarity, activation norms
28
  │ └── timestep.py # t-bin gradient norms, per-t loss decomposition
29
  ├── analysis/
30
- │ ├── action_distribution.py # Pre- vs post-finetuning action dist divergences + plots
31
- │ ├── plots.py # All matplotlib figure generators
32
  │ ├── tables.py # Summary tables as polars DataFrames + LaTeX export
33
- ── report.py # diagnosis.md + decision tree figure
 
34
  └── configs/
35
- ├── ablations_default.yaml # Base hyperparameters for every ablation run
36
- ├── ablations_fast.yaml # Smoke-test overlay (50 iterations, 16 envs)
37
- ├── ablations_final_craftax_classic_gpu_24gb.yaml # Matches configs/final_craftax_classic_gpu_24gb.yaml (RTX 3090 Ti, seed 42)
38
- ├── ablations_final_craftax_classic_gpu_h200.yaml # Matches configs/final_craftax_classic_gpu_h200.yaml (H200, seed 43)
39
- ├── ablations_final_craftax_gpu_24gb.yaml # Matches configs/final_craftax_gpu_24gb.yaml (24 GB reference machine (GPU model unrecorded), seed 42)
40
- └── ablations_final_craftax_gpu_h200.yaml # Matches configs/final_craftax_gpu_h200.yaml (H200, seed 43)
41
  ```
42
 
43
- `ablations_default.yaml` carries the transformer architecture of the released DAgger checkpoints — 384-dim, 8 heads, 6 layers, `d_ff` 768, `plan_horizon` 32, the same for Classic and Full — so the `ablations_final_*` presets need not restate it. A run against a differently-shaped checkpoint must override those keys, or the model build fails on a shape mismatch.
 
 
44
 
45
  ### Config layering
46
 
@@ -69,9 +72,12 @@ jax_compilation_cache_dir: /var/tmp/your-user/jax-cache
69
 
70
  ### Usage
71
 
72
- The pretrained diffusion checkpoint can come from either offline training (`--mode offline`) or DAgger online training (`--mode online`). Use `--checkpoint` to point to it. For DAgger runs, either the final (`{env}-policy`) or best-validation (`{env}-policy-best`) artifact produced by `--mode online` can be consumed directly.
73
 
74
- Checkpoint paths accept `wandb:` prefixed artifact references (e.g., `wandb:team/project/artifact:latest`), which are downloaded automatically before training begins.
 
 
 
75
 
76
  **Smoke test (2 ablations, fast config):**
77
  ```bash
@@ -81,7 +87,7 @@ python experiments/rl_finetuning/run_ablations.py \
81
  --checkpoint $PRETRAINED_CKPT
82
  ```
83
 
84
- **Full suite (all 25 ablations):**
85
  ```bash
86
  python experiments/rl_finetuning/run_ablations.py \
87
  --config configs/defaults.yaml \
@@ -94,7 +100,7 @@ python experiments/rl_finetuning/run_ablations.py \
94
 
95
  **Full suite against a pinned `final_*` checkpoint:**
96
  ```bash
97
- # Craftax Classic, 24 GB hardware (seed 42 checkpoint)
98
  python experiments/rl_finetuning/run_ablations.py \
99
  --ablations-config experiments/rl_finetuning/configs/ablations_final_craftax_classic_gpu_24gb.yaml \
100
  --all --num-seeds 3 \
@@ -109,13 +115,6 @@ python experiments/rl_finetuning/run_ablations.py \
109
  --checkpoint $PRETRAINED_CKPT
110
  ```
111
 
112
- **From a W&B artifact:**
113
- ```bash
114
- python experiments/rl_finetuning/run_ablations.py \
115
- --ablations baseline_rl \
116
- --checkpoint wandb:my-team/remdm-planner-craftax/Craftax-Classic-Symbolic-v1-policy:latest
117
- ```
118
-
119
  **Re-plot from saved results (no training):**
120
  ```bash
121
  python experiments/rl_finetuning/run_ablations.py \
@@ -143,7 +142,8 @@ poolable** with it:
143
  | `eval_steps` | 1024 | 512 | 1024 | 512 | noisier score |
144
  | `mixed_replay_buffer_size` | 20000 | 10000 | 10000 | 10000 | replay horizon |
145
 
146
- Differences in diagnostic cadence (`eval_every`, `cka_every`,
 
147
  `cka_batch_size`, `per_layer_every`, `repr_drift_every`, `grad_align_every`,
148
  `t_analysis_every`) are wall-clock only and do not affect poolability.
149
 
@@ -155,11 +155,6 @@ the poolable set. The key set itself is declared once, in
155
  `run_ablations._RESULT_AFFECTING`, so the classification the tests check and
156
  the refusal `--merge` performs are the same policy.
157
 
158
- **List all ablations:**
159
- ```bash
160
- python experiments/rl_finetuning/run_ablations.py --list
161
- ```
162
-
163
  ### Ablations
164
 
165
  | Group | Name | Tests |
@@ -177,6 +172,7 @@ python experiments/rl_finetuning/run_ablations.py --list
177
  | | `advantage_clip` | PPO-style advantage clipping [1-ε, 1+ε] |
178
  | | `normalized_adv` | Std-normalised advantages |
179
  | | `bc_wins` | Uniform ELBO on win windows (no advantage weighting) |
 
180
  | | `low_t` | ELBO restricted to low-t (fine-detail) regime |
181
  | **C: Architecture** | `frozen_backbone` | Train the action head + token embeddings (backbone frozen) |
182
  | | `head_only` | Train only the final action projection |
@@ -209,36 +205,62 @@ experiments/rl_finetuning/outputs/{run_id}/
209
  │ ├── cka_similarity.png
210
  │ ├── t_distribution_analysis.png
211
  │ ├── t_bin_grad_norms_{name}.png
 
 
212
  │ ├── win_rate_and_effective_batch_size.png
213
  │ ├── achievement_breakdown.png # Start vs end achievement rates (stacked bars)
214
  │ ├── achievement_collapse_{name}.png # Per-ablation achievement heatmap over time
215
  │ ├── diagnosis_decision_tree.png
216
- │ └── action_dist/
217
  │ ├── action_freq_{name}.png # Side-by-side pre/post action frequency bars
218
  │ ├── transition_matrix_{name}.png # 3-panel heatmap (pre, post, difference)
219
  │ ├── action_metrics_{name}.png # 2x2 dashboard (entropy in nats, effective, Gini, divergences)
220
  │ └── js_divergence_comparison.png # Cross-ablation JS divergence bar chart
 
 
 
221
  └── tables/
222
  ├── main_results.{csv,tex}
223
  ├── significance_test.txt # Max-statistic permutation test + p floor + bootstrap CI
 
224
  ├── gradient_analysis.{csv,tex}
225
  ├── t_distribution.{csv,tex}
 
 
226
  ├── forgetting_analysis.{csv,tex}
227
  ├── hypothesis_verdict.{csv,tex}
228
- ── achievement_summary.{csv,tex} # Per-achievement final unlock rates
 
 
229
  ```
230
 
 
 
 
 
 
 
 
 
231
  **`results.json` schema:**
232
  ```json
233
  {
234
  "pretrained_score": 0.1234,
235
  "pretrained_ach_rates": {"achievement_collect_wood": 0.42, ...},
236
- "config": {"MAX_ITER": 1000, ...},
 
237
  "ablations": {
238
  "kl_penalty": {
239
  "score": 0.1456, // mean across seeds
240
  "score_std": 0.008, // std across seeds (0.0 if num_seeds=1)
241
  "all_scores": [0.1456], // per-seed scores
 
 
 
 
 
 
 
242
  "history": { ... } // AblationHistory serialised
243
  }
244
  }
@@ -246,14 +268,99 @@ experiments/rl_finetuning/outputs/{run_id}/
246
  ```
247
 
248
  `results.json` is written incrementally after each ablation completes — a partial file with
249
- N of 25 ablations is fully valid and loadable by `--analyze-only --results-path`.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
250
 
251
  ### W&B logging
252
 
253
- All metrics are logged under `ablations/{method_name}/{metric}`, e.g.:
254
- - `ablations/kl_penalty/eval_score`
255
- - `ablations/gradient_surgery/grad_align`
256
- - `ablations/ewc/repr_drift_kl`
 
 
 
 
257
 
258
  ### Diagnostic metrics collected
259
 
 
8
  ## `rl_finetuning/` — RL Fine-Tuning Ablation Suite
9
 
10
  Diagnoses why RL fine-tuning of the diffusion model collapses and which interventions fix it.
11
+ Implements **26 ablations**: a baseline plus four groups (A: Regularisation, B: Training Signal, C: Architecture, D: Data Quality), with a comprehensive diagnostic and analysis pipeline.
12
 
13
  **Training data is on-policy.** Each iteration rolls the *current* model out under its EMA weights (`diffusion_steps_collect` denoising steps per plan, `num_steps // plan_horizon` plan cycles) and trains on those windows, weighted by each window's own H-step reward sum. The suite therefore needs no expert: `--ppo-checkpoint` is gone, and only the pretrained diffusion `--checkpoint` is required.
14
 
 
20
  ├── ablations/
21
  │ ├── losses.py # All loss/objective variants as factory functions
22
  │ ├── optimizers.py # LLRD, LoRA, gradient surgery, param masking
23
+ │ ├── registry.py # AblationSpec dataclass + REGISTRY (26 ablations)
24
  │ └── training.py # make_run_ablation() factory + AblationHistory dataclass
25
  ├── diagnostics/
26
  │ ├── gradient.py # Grad alignment, per-layer norms, surgery metrics
27
  │ ├── representation.py # KL drift, CKA similarity, activation norms
28
  │ └── timestep.py # t-bin gradient norms, per-t loss decomposition
29
  ├── analysis/
30
+ │ ├── gdelta.py # Return term g_delta of the decomposition (no training)
31
+ │ ├── plots.py # 16 matplotlib figure generators
32
  │ ├── tables.py # Summary tables as polars DataFrames + LaTeX export
33
+ ── report.py # diagnosis.md + decision tree figure
34
+ │ └── action_distribution.py # Pre/post-RL action distribution analysis
35
  └── configs/
36
+ ├── ablations_default.yaml # Base: all ablation hyperparameters
37
+ ├── ablations_fast.yaml # Smoke-test overlay (50 iterations, 16 envs)
38
+ ├── ablations_final_craftax_classic_gpu_24gb.yaml # RTX 3090 Ti, seed 42 (reference)
39
+ ├── ablations_final_craftax_classic_gpu_h200.yaml # H200, seed 43
40
+ ├── ablations_final_craftax_gpu_24gb.yaml # Full Craftax, 24GB, seed 42
41
+ └── ablations_final_craftax_gpu_h200.yaml # Full Craftax, H200, seed 43
42
  ```
43
 
44
+ Each `ablations_final_*` matches the `configs/final_*` of the same name.
45
+
46
+ `ablations_default.yaml` carries the released checkpoints' architecture — 384-dim, 8 heads, 6 layers, `d_ff` 768, `plan_horizon` 32, the same for Classic and Full — so the `ablations_final_*` presets need not restate it. A run against a differently-shaped checkpoint must override those keys or the model build fails on a shape mismatch.
47
 
48
  ### Config layering
49
 
 
72
 
73
  ### Usage
74
 
75
+ `--checkpoint` takes the pretrained diffusion checkpoint from either `--mode offline` or `--mode online`; for DAgger, the final (`{env}-policy`) or best-validation (`{env}-policy-best`) artifact is consumed directly. It also accepts `wandb:team/project/artifact:latest` references, downloaded automatically before training begins.
76
 
77
+ **List all ablations:**
78
+ ```bash
79
+ python experiments/rl_finetuning/run_ablations.py --list
80
+ ```
81
 
82
  **Smoke test (2 ablations, fast config):**
83
  ```bash
 
87
  --checkpoint $PRETRAINED_CKPT
88
  ```
89
 
90
+ **Full suite (all 26 ablations):**
91
  ```bash
92
  python experiments/rl_finetuning/run_ablations.py \
93
  --config configs/defaults.yaml \
 
100
 
101
  **Full suite against a pinned `final_*` checkpoint:**
102
  ```bash
103
+ # Craftax Classic, GPU-24GB hardware (seed 42 checkpoint)
104
  python experiments/rl_finetuning/run_ablations.py \
105
  --ablations-config experiments/rl_finetuning/configs/ablations_final_craftax_classic_gpu_24gb.yaml \
106
  --all --num-seeds 3 \
 
115
  --checkpoint $PRETRAINED_CKPT
116
  ```
117
 
 
 
 
 
 
 
 
118
  **Re-plot from saved results (no training):**
119
  ```bash
120
  python experiments/rl_finetuning/run_ablations.py \
 
142
  | `eval_steps` | 1024 | 512 | 1024 | 512 | noisier score |
143
  | `mixed_replay_buffer_size` | 20000 | 10000 | 10000 | 10000 | replay horizon |
144
 
145
+ Values above are post-layering: a config's own value, or what it inherits from
146
+ `ablations_default.yaml`. Differences in diagnostic cadence (`eval_every`, `cka_every`,
147
  `cka_batch_size`, `per_layer_every`, `repr_drift_every`, `grad_align_every`,
148
  `t_analysis_every`) are wall-clock only and do not affect poolability.
149
 
 
155
  `run_ablations._RESULT_AFFECTING`, so the classification the tests check and
156
  the refusal `--merge` performs are the same policy.
157
 
 
 
 
 
 
158
  ### Ablations
159
 
160
  | Group | Name | Tests |
 
172
  | | `advantage_clip` | PPO-style advantage clipping [1-ε, 1+ε] |
173
  | | `normalized_adv` | Std-normalised advantages |
174
  | | `bc_wins` | Uniform ELBO on win windows (no advantage weighting) |
175
+ | | `bc_all` | Uniform ELBO on all rollout windows (no advantage weighting) |
176
  | | `low_t` | ELBO restricted to low-t (fine-detail) regime |
177
  | **C: Architecture** | `frozen_backbone` | Train the action head + token embeddings (backbone frozen) |
178
  | | `head_only` | Train only the final action projection |
 
205
  │ ├── cka_similarity.png
206
  │ ├── t_distribution_analysis.png
207
  │ ├── t_bin_grad_norms_{name}.png
208
+ │ ├── t_bin_norms_heatmap.png # Per-t-bin gradient norms, final iteration
209
+ │ ├── group_comparison.png # Boxplot of scores by ablation group
210
  │ ├── win_rate_and_effective_batch_size.png
211
  │ ├── achievement_breakdown.png # Start vs end achievement rates (stacked bars)
212
  │ ├── achievement_collapse_{name}.png # Per-ablation achievement heatmap over time
213
  │ ├── diagnosis_decision_tree.png
214
+ │ └── action_dist/ # On by default; --no-action-dist skips it
215
  │ ├── action_freq_{name}.png # Side-by-side pre/post action frequency bars
216
  │ ├── transition_matrix_{name}.png # 3-panel heatmap (pre, post, difference)
217
  │ ├── action_metrics_{name}.png # 2x2 dashboard (entropy in nats, effective, Gini, divergences)
218
  │ └── js_divergence_comparison.png # Cross-ablation JS divergence bar chart
219
+ ├── gdelta/ # --measure-gdelta only
220
+ │ ├── gdelta_seed{n}.json # Per rollout seed; +/- within is across that seed's draws
221
+ │ └── gdelta_aggregate.json # Across seeds; the dispersion the paper's table prints
222
  └── tables/
223
  ├── main_results.{csv,tex}
224
  ├── significance_test.txt # Max-statistic permutation test + p floor + bootstrap CI
225
+ ├── group_summary.{csv,tex} # Group-level summary table
226
  ├── gradient_analysis.{csv,tex}
227
  ├── t_distribution.{csv,tex}
228
+ ├── repr_drift.{csv,tex} # KL drift values at the final iteration
229
+ ├── per_env.{csv,tex} # Per-achievement rates; needs pretrained_ach_rates
230
  ├── forgetting_analysis.{csv,tex}
231
  ├── hypothesis_verdict.{csv,tex}
232
+ ── achievement_summary.{csv,tex} # Per-achievement final unlock rates
233
+ ├── gdelta.{csv,tex} # --measure-gdelta only: the decomposition per weight transform
234
+ └── results.tex # --emit-tex-macros only: \newcommand per headline number
235
  ```
236
 
237
+ **Action distribution analysis** runs by default and is disabled with
238
+ `--no-action-dist`. The rollout is one vectorised scan over `num_envs`, sized
239
+ from the config rather than by an episode count, so it costs a fraction of a
240
+ training run — which is why the default differs from the minihack twin, where
241
+ the same flag defaults off because MiniHack rollouts are not vectorised. It
242
+ reads each ablation's `final_params`, so it only covers ablations that
243
+ completed.
244
+
245
  **`results.json` schema:**
246
  ```json
247
  {
248
  "pretrained_score": 0.1234,
249
  "pretrained_ach_rates": {"achievement_collect_wood": 0.42, ...},
250
+ "config": {"MAX_ITER": 1000, ...}, // the merged config, keys uppercase
251
+ "merge_provenance": { ... }, // --merge only: inputs + which supplied config
252
  "ablations": {
253
  "kl_penalty": {
254
  "score": 0.1456, // mean across seeds
255
  "score_std": 0.008, // std across seeds (0.0 if num_seeds=1)
256
  "all_scores": [0.1456], // per-seed scores
257
+ "base_seed": 42, "seeds": [42], // seeding actually used
258
+ "wall_clock_s": 812.4,
259
+ "per_seed_finals": [{...}], // per-seed end-of-run metrics
260
+ "final_ach_rates": {"achievement_collect_wood": 0.40, ...},
261
+ // achievement detail of the same post-loop
262
+ // evaluations that produced all_scores, seed-averaged
263
+ "all_final_ach_rates": [{...}], // per-seed, before averaging
264
  "history": { ... } // AblationHistory serialised
265
  }
266
  }
 
268
  ```
269
 
270
  `results.json` is written incrementally after each ablation completes — a partial file with
271
+ N of 26 ablations is fully valid and loadable by `--analyze-only --results-path`.
272
+
273
+ ### Measuring the return term (`--measure-gdelta`)
274
+
275
+ Splits the return-weighted ELBO gradient into an imitation term and a return term at a
276
+ single parameter point:
277
+
278
+ ```
279
+ grad L_RW = Abar * ( grad L_BC + g_delta ),
280
+ g_delta = (1/B) sum_i delta_i grad l_i, delta_i = A_i/Abar - 1.
281
+ ```
282
+
283
+ Loads the pretrained checkpoint, collects one on-policy batch from it, and evaluates
284
+ `grad L_BC`, `g_delta` and `grad L_RW` on that batch at those parameters under a shared
285
+ `(z_t, t)` draw, so the only difference between the three is the weight vector. It repeats
286
+ for the four weighting ablations (`baseline_rl`, `advantage_clip`, `normalized_adv`,
287
+ `bc_wins`) and reports `CV_A`, `Abar`, `ESS/B`, the norm ratio and the cosine, plus a
288
+ shuffled-`delta` null that keeps the weight multiset and destroys its association with each
289
+ window's return. No training and no optimiser step occur; it runs on a laptop CPU.
290
+
291
+ Results land in `gdelta/` under the run's own output directory, beside `results.json`, and
292
+ the aggregate additionally produces `tables/gdelta.{csv,tex}`. With `--emit-tex-macros`,
293
+ the analysis pass picks the aggregate up and emits the measured quantities as `\rwGdelta*`
294
+ macros. Those are kept separate from the `\rwCvA*` macros, which recover `CV_A` from the
295
+ ESS logged during training: the two are measured on different batches and do not agree.
296
+
297
+ Config comes from `--results-path`, so the weight transforms measured are the ones that run
298
+ trained under; without it the standard layering applies.
299
+
300
+ **Reproduction (three rollout seeds, aggregated in one pass):**
301
+ ```bash
302
+ python experiments/rl_finetuning/run_ablations.py --measure-gdelta --gdelta-seeds 0 1 2 \
303
+ --checkpoint checkpoints/online/Craftax-Classic-Symbolic-v1-Online-Diffusion-DAgger-100M \
304
+ --results-path experiments/rl_finetuning/outputs/craftax_classic_ablations/results.json \
305
+ --output-dir experiments/rl_finetuning/outputs/craftax_classic_ablations
306
+ ```
307
+
308
+ Seeds run on separate machines are aggregated afterwards with `--gdelta-inputs`, the
309
+ counterpart to `--merge`:
310
+ ```bash
311
+ python experiments/rl_finetuning/run_ablations.py --run-id gdelta \
312
+ --gdelta-inputs experiments/rl_finetuning/outputs/gdelta/gdelta_seed{0,1,2}.json
313
+ ```
314
+
315
+ A single seed's `ratio_std_draws` / `cos_std_draws` are dispersions over that seed's eight
316
+ `(z_t, t)` draws. The aggregate averages the per-seed means and reports the standard
317
+ deviation **across seeds**, which is what the paper's table prints.
318
+
319
+ ### CLI reference
320
+
321
+ | Flag | Description |
322
+ |---|---|
323
+ | `--checkpoint PATH` | Pretrained diffusion checkpoint, offline or DAgger (path or `wandb:` artifact) |
324
+ | `--config PATH` | Main pipeline config (default: `configs/defaults.yaml`) |
325
+ | `--ablations-config PATH` | Ablations config, layered on `ablations_default.yaml` (default: `ablations_default.yaml`) |
326
+ | `--all` | Run all 26 ablations |
327
+ | `--ablations NAME [NAME ...]` | Run specific ablations by name |
328
+ | `--list` | Print registered ablations and exit |
329
+ | `--fast` | Smoke-test overlay: `ablations_fast.yaml` applied last (`max_iter` 50, `num_envs` 16) |
330
+ | `--num-seeds N` | Seeds per ablation (overrides `num_seeds`, default 3) |
331
+ | `--seed N` | Base random seed |
332
+ | `--output-dir DIR` | Root output directory (default: `outputs/{run_id}/`) |
333
+ | `--run-id ID` | Run identifier (default: `run_{timestamp}`) |
334
+ | `--analyze-only` | Skip training, regenerate analysis from existing results |
335
+ | `--results-path PATH` | Explicit path to `results.json` (with `--analyze-only` or `--measure-gdelta`) |
336
+ | `--merge PATH [PATH ...]` | Merge multiple `results.json` files and regenerate analysis |
337
+ | `--measure-gdelta` | Measure the return term at the pretrained checkpoint; no training |
338
+ | `--gdelta-seeds N [N ...]` | Rollout seeds to measure (default `0`); the reported +/- is across these |
339
+ | `--gdelta-draws N` | Independent `(z_t, t)` draws per seed (default 8) |
340
+ | `--gdelta-inputs PATH [PATH ...]` | Aggregate per-seed gdelta JSONs from separate machines |
341
+ | `--emit-tex-macros` | Also write `tables/results.tex`, one `\newcommand` per headline number |
342
+ | `--action-dist` / `--no-action-dist` | Pre/post action-distribution analysis (default **on** here; **off** in the minihack twin) |
343
+ | `--use-wandb` / `--no-use-wandb` | Enable/disable W&B logging (overrides `use_wandb`, default `false`) |
344
+ | `--wandb-project NAME` | W&B project (default `remdm-planner-craftax-ablations`) |
345
+ | `--wandb-entity NAME` | W&B entity |
346
+ | `--max-iter N` | Override max training iterations |
347
+ | `--num-envs N` | Override rollout environments per iteration |
348
+ | `--batch-size N` | Override batch size |
349
+ | `--eval-every N` | Override evaluation frequency |
350
+ | `--lr FLOAT` | Override learning rate |
351
+
352
+ There is no `--override`: keys that are not flags are set in the config files.
353
 
354
  ### W&B logging
355
 
356
+ Three metrics per ablation, logged under `ablations/{name}/` against `iteration`:
357
+ `train_loss`, `env_score` (both every logged iteration) and `eval_score` (every
358
+ `eval_every`). They are written after the arm finishes, not during it, and there is
359
+ no `wandb.summary` write.
360
+
361
+ Every other quantity in the table below — gradient alignment, per-layer norms, KL
362
+ drift, CKA, the t-bin norms — is collected into `AblationHistory` and reaches
363
+ `results.json` only. Read those from the run directory, not from W&B.
364
 
365
  ### Diagnostic metrics collected
366
 
experiments/rl_finetuning/ablations/losses.py CHANGED
@@ -380,6 +380,35 @@ def make_loss_bc_wins(ctx: LossContext) -> LossFn:
380
  return loss_fn
381
 
382
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
383
  def make_loss_low_t(ctx: LossContext) -> LossFn:
384
  """Return-weighted ELBO restricted to t ∈ [ε, t_max_low].
385
 
 
380
  return loss_fn
381
 
382
 
383
+ def make_loss_bc_all(ctx: LossContext) -> LossFn:
384
+ """Uniform ELBO over every rollout window (BC on self-generated data).
385
+
386
+ The return weighting is dropped outright: ``advantages`` is ignored and
387
+ ``compute_loss`` takes its plain batch mean, which is exactly a weight of
388
+ 1.0 on every window. The batch is the one ``baseline_rl`` trains on --
389
+ same collection, same windows -- so the pair separates the on-policy data
390
+ distribution from the weighting applied to it, which no other arm does.
391
+
392
+ ``bc_wins`` is the nearest neighbour and does not answer this: it keeps
393
+ only the winning windows, so it varies the data as well as the weights.
394
+
395
+ Hypothesis: if this degrades like ``baseline_rl``, fine-tuning on
396
+ self-generated rollouts is the cause and the weighting is incidental.
397
+
398
+ Args:
399
+ ctx: Shared loss context.
400
+
401
+ Returns:
402
+ ``LossFn`` averaging uniformly over the whole batch.
403
+ """
404
+
405
+ def loss_fn(params, acts, obs, valid, rng, advantages):
406
+ del advantages # uniform weights: the point of the arm
407
+ return _core_loss(ctx, params, rng, acts, obs, valid, None)
408
+
409
+ return loss_fn
410
+
411
+
412
  def make_loss_low_t(ctx: LossContext) -> LossFn:
413
  """Return-weighted ELBO restricted to t ∈ [ε, t_max_low].
414
 
experiments/rl_finetuning/ablations/registry.py CHANGED
@@ -16,6 +16,7 @@ from experiments.rl_finetuning.ablations.losses import (
16
  LossFn,
17
  make_loss_advantage_clip,
18
  make_loss_baseline,
 
19
  make_loss_bc_wins,
20
  make_loss_entropy_bonus,
21
  make_loss_ewc,
@@ -303,6 +304,15 @@ REGISTRY: dict[str, AblationSpec] = {
303
  optimizer_factory=_std_opt,
304
  wins_only=True,
305
  ),
 
 
 
 
 
 
 
 
 
306
  "low_t": AblationSpec(
307
  name="low_t",
308
  group="B",
 
16
  LossFn,
17
  make_loss_advantage_clip,
18
  make_loss_baseline,
19
+ make_loss_bc_all,
20
  make_loss_bc_wins,
21
  make_loss_entropy_bonus,
22
  make_loss_ewc,
 
304
  optimizer_factory=_std_opt,
305
  wins_only=True,
306
  ),
307
+ "bc_all": AblationSpec(
308
+ name="bc_all",
309
+ group="B",
310
+ description="Uniform ELBO on all rollout windows (no advantage weighting)",
311
+ hypothesis="If it degrades like baseline_rl: the self-generated rollouts, "
312
+ "not the return weighting, are the cause",
313
+ loss_factory=make_loss_bc_all,
314
+ optimizer_factory=_std_opt,
315
+ ),
316
  "low_t": AblationSpec(
317
  name="low_t",
318
  group="B",
experiments/rl_finetuning/ablations/training.py CHANGED
@@ -1549,7 +1549,7 @@ def run_ablation(
1549
  rng: jax.Array,
1550
  wandb_run: Any = None,
1551
  output_dir: Any = None,
1552
- ) -> tuple[AblationHistory, float, Any]:
1553
  """Run one complete ablation (Python-level wrapper around make_run_ablation).
1554
 
1555
  Handles EWC Fisher estimation (which requires rollouts before JIT),
@@ -1572,7 +1572,9 @@ def run_ablation(
1572
  output_dir: Optional Path for per-iteration checkpoint saving.
1573
 
1574
  Returns:
1575
- Tuple of ``(history, final_score, final_params)``.
 
 
1576
  """
1577
  logger.info("=" * 60)
1578
  logger.info("ABLATION: %s [Group %s]", spec.name, spec.group)
@@ -1684,13 +1686,14 @@ def run_ablation(
1684
  final_params = jax.device_get(final_carry.ema_params)
1685
  final_score = float(final_info.get("returned_episode_returns", jnp.array(0.0)))
1686
 
1687
- # Extract per-achievement unlock rates from final eval
 
 
 
 
1688
  final_ach = {
1689
  k: float(v) / 100.0 for k, v in final_info.items() if "achievement" in k.lower()
1690
  }
1691
- # Overwrite the empty dicts in history with final eval achievements
1692
- if history.per_achievement_rates:
1693
- history.per_achievement_rates[-1] = final_ach
1694
 
1695
  logger.info(" [%s] FINAL score: %.4f", spec.name, final_score)
1696
 
@@ -1713,4 +1716,4 @@ def run_ablation(
1713
  }
1714
  )
1715
 
1716
- return history, final_score, final_params
 
1549
  rng: jax.Array,
1550
  wandb_run: Any = None,
1551
  output_dir: Any = None,
1552
+ ) -> tuple[AblationHistory, float, Any, dict[str, float]]:
1553
  """Run one complete ablation (Python-level wrapper around make_run_ablation).
1554
 
1555
  Handles EWC Fisher estimation (which requires rollouts before JIT),
 
1572
  output_dir: Optional Path for per-iteration checkpoint saving.
1573
 
1574
  Returns:
1575
+ Tuple of ``(history, final_score, final_params, final_ach)``, where
1576
+ ``final_ach`` is the per-achievement unlock rate of the same post-loop
1577
+ evaluation that produced ``final_score``.
1578
  """
1579
  logger.info("=" * 60)
1580
  logger.info("ABLATION: %s [Group %s]", spec.name, spec.group)
 
1686
  final_params = jax.device_get(final_carry.ema_params)
1687
  final_score = float(final_info.get("returned_episode_returns", jnp.array(0.0)))
1688
 
1689
+ # Per-achievement detail of the *same* evaluation that produced
1690
+ # `final_score`. It is returned separately rather than written over the
1691
+ # last entry of `history.per_achievement_rates`: that entry belongs to the
1692
+ # last in-loop evaluation, a different draw, and overwriting it made the
1693
+ # achievement tables and the headline score disagree by construction.
1694
  final_ach = {
1695
  k: float(v) / 100.0 for k, v in final_info.items() if "achievement" in k.lower()
1696
  }
 
 
 
1697
 
1698
  logger.info(" [%s] FINAL score: %.4f", spec.name, final_score)
1699
 
 
1716
  }
1717
  )
1718
 
1719
+ return history, final_score, final_params, final_ach
experiments/rl_finetuning/analysis/gdelta.py ADDED
@@ -0,0 +1,548 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Measure the return term g_delta of the return-weighted ELBO decomposition.
2
+
3
+ Implements the measurement behind Eq. 8 and Table 6 of the paper. Writing the
4
+ per-window weight as A_i and the batch mean as Abar, the training gradient
5
+ decomposes exactly as
6
+
7
+ grad L_RW = Abar * ( grad L_BC + g_delta ),
8
+ g_delta = (1/B) sum_i delta_i grad l_i, delta_i = A_i/Abar - 1,
9
+
10
+ so g_delta carries the entire directional contribution of the return and Abar
11
+ is the scalar step-size rescaling. :func:`measure` loads a pretrained
12
+ checkpoint, collects one on-policy batch from it, and evaluates grad L_BC,
13
+ g_delta and grad L_RW on that batch at those parameters under a shared
14
+ (z_t, t) draw, so the only difference between the three is the weight vector.
15
+ It repeats for every weight transform the ablation suite uses, and reports
16
+ three references the cosine column needs:
17
+
18
+ * the random-direction null, cos ~ N(0, 1/sqrt(D)) for D parameters;
19
+ * cos(grad L_BC, grad L_BC) across two independent noise draws, which is the
20
+ value a direction attains when it *is* the imitation direction;
21
+ * the shuffled-delta null, which permutes delta across the batch. This keeps
22
+ the multiset of weights and hence CV_A, and destroys the association
23
+ between a window's weight and that window's gradient. Any part of the
24
+ measured ratio and cosine that survives the shuffle is batch
25
+ heterogeneity, not return signal.
26
+
27
+ No training and no optimiser step occur. Runs on CPU in a few minutes.
28
+
29
+ The per-draw standard deviations a single seed reports are *within* one rollout
30
+ seed. The figure Table 6 prints is the standard deviation across rollout seeds,
31
+ which :func:`aggregate` computes over the per-seed records.
32
+
33
+ Driven by ``run_ablations.py --measure-gdelta``; see :func:`run_gdelta`.
34
+ """
35
+
36
+ from __future__ import annotations
37
+
38
+ import logging
39
+ from pathlib import Path
40
+
41
+ import numpy as np
42
+ import orjson
43
+
44
+ logger = logging.getLogger(__name__)
45
+
46
+ # The variant -> ablation mapping this module assumes. Each entry names the
47
+ # registry key, the loss factory that key must still use, and its wins_only
48
+ # flag. verify_registry() fails loudly if the suite drifts away from it, so a
49
+ # registry edit cannot silently desynchronise the measurement.
50
+ REGISTRY_RULES = {
51
+ "baseline_clipped_ratio": ("baseline_rl", "make_loss_baseline", False),
52
+ "advantage_clip": ("advantage_clip", "make_loss_advantage_clip", False),
53
+ "normalized_adv": ("normalized_adv", "make_loss_normalized_adv", False),
54
+ "bc_wins": ("bc_wins", "make_loss_bc_wins", True),
55
+ }
56
+
57
+ BASELINE = "baseline_clipped_ratio"
58
+
59
+ GDELTA_DIRNAME = "gdelta"
60
+ AGGREGATE_FILENAME = "gdelta_aggregate.json"
61
+
62
+
63
+ class RegistryDriftError(RuntimeError):
64
+ """The ablation registry no longer matches the variants measured here."""
65
+
66
+
67
+ def verify_registry() -> None:
68
+ """Fail if the ablation registry no longer matches the assumed variants."""
69
+ from experiments.rl_finetuning.ablations.registry import REGISTRY
70
+
71
+ for variant, (name, factory, wins_only) in REGISTRY_RULES.items():
72
+ spec = REGISTRY.get(name)
73
+ if spec is None:
74
+ raise RegistryDriftError(
75
+ f"registry has no ablation {name!r}; variant {variant!r} is stale"
76
+ )
77
+ if spec.loss_factory.__name__ != factory:
78
+ raise RegistryDriftError(
79
+ f"ablation {name!r} now uses {spec.loss_factory.__name__}, "
80
+ f"not {factory}; variant {variant!r} measures a weighting the "
81
+ "trainer no longer applies"
82
+ )
83
+ if bool(spec.wins_only) != wins_only:
84
+ raise RegistryDriftError(
85
+ f"ablation {name!r} has wins_only={spec.wins_only}, expected "
86
+ f"{wins_only}; variant {variant!r} is stale"
87
+ )
88
+
89
+
90
+ def build_variants(adv, returns, cfg, batch):
91
+ """The weight vectors the suite's four weighting ablations apply.
92
+
93
+ ``bc_wins`` is the binary win mask rescaled by ``B / n_wins``, which is the
94
+ vector ``make_loss_bc_wins`` builds internally from the mask that
95
+ ``_compute_advantages(wins_only=True)`` hands it. The rescaling is what
96
+ turns ``compute_loss``'s batch mean into a uniform mean over the winners,
97
+ so it is part of the weight the trainer applies, not a normalisation added
98
+ here.
99
+ """
100
+ import jax.numpy as jnp
101
+
102
+ eps = cfg["ADV_CLIP_EPS"]
103
+ win = (returns > cfg["WIN_THRESHOLD"]).astype(jnp.float32)
104
+ n_win = win.sum()
105
+ return {
106
+ BASELINE: adv,
107
+ "advantage_clip": jnp.clip(adv, 1.0 - eps, 1.0 + eps),
108
+ "normalized_adv": (adv - adv.mean()) / (adv.std() + 1e-8),
109
+ "bc_wins": win * jnp.where(n_win > 0, batch / jnp.maximum(n_win, 1.0), 0.0),
110
+ }
111
+
112
+
113
+ def centred_delta(weights):
114
+ """Return ``(delta, Abar, a1_holds)`` for a weight vector.
115
+
116
+ (A1) of the paper asks for non-negative weights with a strictly positive
117
+ mean. Where it holds, ``delta_i = A_i/Abar - 1`` and ``mean(delta) == 0``.
118
+ Where it does not -- ``normalized_adv`` mean-centres, so its weights are
119
+ signed and its mean vanishes -- the ratio is meaningless, and the caller is
120
+ told rather than handed a number that divides by a value near zero.
121
+ """
122
+ import jax.numpy as jnp
123
+
124
+ wbar = float(jnp.mean(weights))
125
+ scale = float(jnp.max(jnp.abs(weights))) + 1e-12
126
+ a1_holds = bool(jnp.all(weights >= 0.0)) and wbar > 1e-6 * scale
127
+ if not a1_holds:
128
+ return weights - wbar, wbar, False
129
+ return weights / wbar - 1.0, wbar, True
130
+
131
+
132
+ def against_bc(g, g_bc, norm_bc):
133
+ """``(||g|| / ||g_BC||, cos(g, g_BC))`` for one gradient against imitation."""
134
+ import jax.numpy as jnp
135
+
136
+ norm = float(jnp.linalg.norm(g))
137
+ return norm / norm_bc, float(jnp.dot(g, g_bc) / (norm * norm_bc + 1e-12))
138
+
139
+
140
+ def effective_sample_size(weights) -> float:
141
+ """ESS as a fraction of the batch: (sum A)^2 / (B sum A^2)."""
142
+ import jax.numpy as jnp
143
+
144
+ total = float(jnp.sum(weights))
145
+ sq = float(jnp.sum(weights ** 2))
146
+ if sq <= 0.0:
147
+ return float("nan")
148
+ return total ** 2 / (sq * weights.shape[0])
149
+
150
+
151
+ def restore_params(net, rng, obs_dim, plan_horizon, ckpt: str):
152
+ """Restore Orbax parameters with an explicit single-device sharding.
153
+
154
+ ``model.load_checkpoint`` builds its restore target with ``jax.eval_shape``,
155
+ whose leaves carry no sharding. Orbax >= 0.12 rejects that when the
156
+ checkpoint was written under a different device topology, which is the
157
+ common case for a checkpoint trained on GPU and inspected on CPU. Attaching
158
+ a concrete sharding here is the only difference from the library path.
159
+ """
160
+ import jax
161
+ import orbax.checkpoint as ocp
162
+
163
+ from src.planners.model import _validate_restored_tree, abstract_params
164
+
165
+ sharding = jax.sharding.SingleDeviceSharding(jax.devices()[0])
166
+ abstract = abstract_params(net, rng, obs_dim, plan_horizon)
167
+ restore_args = jax.tree.map(
168
+ lambda x: ocp.ArrayRestoreArgs(sharding=sharding, dtype=x.dtype), abstract
169
+ )
170
+ with ocp.CheckpointManager(str(Path(ckpt).resolve())) as mgr:
171
+ step = mgr.latest_step()
172
+ restored = mgr.restore(
173
+ step,
174
+ args=ocp.args.PyTreeRestore(
175
+ item={"params": abstract},
176
+ restore_args={"params": restore_args},
177
+ partial_restore=True,
178
+ ),
179
+ )
180
+ params = restored["params"]
181
+ _validate_restored_tree(params, abstract, ckpt)
182
+ return params, step
183
+
184
+
185
+ def aggregate(blobs: list[dict], inputs: list[str] | None = None) -> dict:
186
+ """Combine per-seed records, reporting the standard deviation across seeds.
187
+
188
+ Each input contributes one number per variant per column -- its own mean
189
+ over the ``(z_t, t)`` draws. The dispersion reported here is over those
190
+ per-seed means, which is the quantity the paper's table claims.
191
+
192
+ Args:
193
+ blobs: Per-seed records, as returned by :func:`measure`.
194
+ inputs: Optional provenance paths recorded in the output.
195
+
196
+ Returns:
197
+ The aggregate record.
198
+ """
199
+ labels = inputs if inputs is not None else [
200
+ f"seed{b.get('seed', i)}" for i, b in enumerate(blobs)
201
+ ]
202
+ for blob, label in zip(blobs, labels, strict=True):
203
+ if blob.get("aggregate"):
204
+ raise ValueError(f"{label} is already an aggregate")
205
+
206
+ names = list(blobs[0]["variants"])
207
+ for blob, label in zip(blobs, labels, strict=True):
208
+ if list(blob["variants"]) != names:
209
+ raise ValueError(f"{label} has a different variant set")
210
+
211
+ def across(values):
212
+ arr = np.array(values, dtype=float)
213
+ return float(arr.mean()), float(arr.std())
214
+
215
+ out = {
216
+ "aggregate": True,
217
+ "inputs": [str(p) for p in labels],
218
+ "seeds": [int(b["seed"]) for b in blobs],
219
+ "n_seeds": len(blobs),
220
+ "n_draws_per_seed": [int(b["n_draws"]) for b in blobs],
221
+ "n_params": int(blobs[0]["n_params"]),
222
+ "random_cos_sd": float(blobs[0]["random_cos_sd"]),
223
+ "batch": int(blobs[0]["batch"]),
224
+ "eq4_residual_max": float(max(b["eq4_residual_max"] for b in blobs)),
225
+ "variants": {},
226
+ }
227
+ out["bc_self_cos_mean"], out["bc_self_cos_std_seeds"] = across(
228
+ [b["bc_self_cos_mean"] for b in blobs]
229
+ )
230
+
231
+ columns = [
232
+ "cv_a", "abar", "abar_ratio_to_baseline", "ess_fraction",
233
+ "ratio_mean", "cos_mean", "ratio_shuffled_mean", "cos_shuffled_mean",
234
+ ]
235
+ for name in names:
236
+ rec = {"a1_violated": any(b["variants"][name]["a1_violated"] for b in blobs)}
237
+ for col in columns:
238
+ mean, std = across([b["variants"][name][col] for b in blobs])
239
+ stem = col[:-5] if col.endswith("_mean") else col
240
+ rec[f"{stem}_mean"] = mean
241
+ rec[f"{stem}_std_seeds"] = std
242
+ out["variants"][name] = rec
243
+ return out
244
+
245
+
246
+ def print_aggregate(agg: dict) -> None:
247
+ print(f"\naggregate over {agg['n_seeds']} rollout seeds {agg['seeds']}, "
248
+ f"{agg['n_draws_per_seed']} draws each; +/- is ACROSS SEEDS")
249
+ print(f"cos(grad L_BC, grad L_BC) = {agg['bc_self_cos_mean']:.3f} "
250
+ f"+/- {agg['bc_self_cos_std_seeds']:.3f}")
251
+ print(f"Eq. 4 identity, max relative residual = {agg['eq4_residual_max']:.2e}\n")
252
+ header = (f"{'weight transform':26s} {'CV_A':>7s} {'Abar':>8s} {'Abar/base':>10s} "
253
+ f"{'ESS/B':>7s} {'ratio':>16s} {'cos':>16s} "
254
+ f"{'ratio(shuf)':>16s} {'cos(shuf)':>16s}")
255
+ print(header)
256
+ for name, rec in agg["variants"].items():
257
+ flag = " [(A1) violated]" if rec["a1_violated"] else ""
258
+ print(f"{name:26s} {rec['cv_a_mean']:7.3f} {rec['abar_mean']:8.3f} "
259
+ f"{rec['abar_ratio_to_baseline_mean']:10.3f} "
260
+ f"{rec['ess_fraction_mean']:7.3f} "
261
+ f"{rec['ratio_mean']:9.3f} +/-{rec['ratio_std_seeds']:.3f} "
262
+ f"{rec['cos_mean']:+9.3f} +/-{rec['cos_std_seeds']:.3f} "
263
+ f"{rec['ratio_shuffled_mean']:9.3f} +/-{rec['ratio_shuffled_std_seeds']:.3f} "
264
+ f"{rec['cos_shuffled_mean']:+9.3f} +/-{rec['cos_shuffled_std_seeds']:.3f}"
265
+ f"{flag}")
266
+
267
+
268
+ def measure(
269
+ config: dict,
270
+ ckpt: str,
271
+ *,
272
+ seed: int = 0,
273
+ n_draws: int = 8,
274
+ num_envs: int | None = None,
275
+ batch_size: int | None = None,
276
+ ) -> dict:
277
+ """Measure the decomposition at one rollout seed.
278
+
279
+ Args:
280
+ config: UPPERCASE config dict; the run's own, so the weight
281
+ transforms match the ones it trained under.
282
+ ckpt: Orbax checkpoint directory of the pretrained planner.
283
+ seed: Rollout seed. Also fixes the ``(z_t, t)`` draws.
284
+ n_draws: Independent ``(z_t, t)`` draws to average over.
285
+ num_envs: Override ``NUM_ENVS``; ``None`` keeps the config value.
286
+ batch_size: Override ``BATCH_SIZE``; ``None`` keeps the config value.
287
+
288
+ Returns:
289
+ The per-seed record.
290
+ """
291
+ import jax
292
+ import jax.numpy as jnp
293
+
294
+ from experiments.rl_finetuning.ablations.training import (
295
+ _compute_advantages,
296
+ build_rollout_fn,
297
+ )
298
+ from src.diffusion.loss import compute_loss
299
+ from src.diffusion.schedules import SCHEDULE_MAP
300
+ from src.planners.env import make_env
301
+ from src.planners.model import build_model, make_apply_fns
302
+
303
+ verify_registry()
304
+
305
+ cfg = dict(config)
306
+ if num_envs is not None:
307
+ cfg["NUM_ENVS"] = num_envs
308
+ if batch_size is not None:
309
+ cfg["BATCH_SIZE"] = batch_size
310
+
311
+ env, env_params = make_env(cfg, cfg["NUM_ENVS"])
312
+ num_actions = env.action_space(env_params).n
313
+ obs_dim = env.observation_space(env_params).shape[0]
314
+ cfg["NUM_ACTIONS"] = num_actions
315
+ schedule_fn, schedule_deriv_fn = SCHEDULE_MAP[cfg.get("DIFFUSION_SCHEDULE", "cosine")]
316
+
317
+ net = build_model(cfg, num_actions)
318
+ apply_eval, apply_train = make_apply_fns(net)
319
+
320
+ rng = jax.random.PRNGKey(seed)
321
+ # A separate stream for the shuffled-delta null, so adding the control
322
+ # leaves every draw of the real measurement bit-for-bit unchanged.
323
+ perm_rng = jax.random.PRNGKey(seed + 10_000)
324
+ rng, key = jax.random.split(rng)
325
+ params, step = restore_params(net, key, obs_dim, cfg["PLAN_HORIZON"], ckpt)
326
+ n_params = sum(int(np.prod(x.shape)) for x in jax.tree.leaves(params))
327
+ random_cos_sd = 1.0 / np.sqrt(n_params)
328
+ print(f"checkpoint step {step}, D = {n_params/1e6:.2f}M, "
329
+ f"random-cosine null sd = {random_cos_sd:.2e}", flush=True)
330
+
331
+ # ---- one on-policy batch from the pretrained policy ----
332
+ collect = build_rollout_fn(env, env_params, apply_eval, cfg, obs_dim)
333
+ rng, key = jax.random.split(rng)
334
+ obs0, state0 = env.reset(key, env_params)
335
+ done0 = jnp.zeros(cfg["NUM_ENVS"], dtype=bool)
336
+ rng, key = jax.random.split(rng)
337
+ _, _, _, _, f_obs, f_acts, f_valid, f_ret, _ = collect(
338
+ params, state0, obs0, done0, key
339
+ )
340
+ adv, _, _ = _compute_advantages(
341
+ f_ret,
342
+ cfg["RETURN_WEIGHT_FLOOR"],
343
+ cfg["RETURN_WEIGHT_CAP"],
344
+ wins_only=False,
345
+ win_thresh=cfg["WIN_THRESHOLD"],
346
+ use_running_stats=False,
347
+ ema_decay=0.99,
348
+ running_mean=jnp.array(0.0),
349
+ running_std=jnp.array(1.0),
350
+ )
351
+
352
+ batch = min(cfg["BATCH_SIZE"], f_obs.shape[0])
353
+ rng, key = jax.random.split(rng)
354
+ idx = jax.random.permutation(key, f_obs.shape[0])[:batch]
355
+ obs_b, act_b, val_b = f_obs[idx], f_acts[idx], f_valid[idx]
356
+ adv_b, ret_b = adv[idx], f_ret[idx]
357
+ print(f"batch {batch} windows, win rate {float((ret_b > cfg['WIN_THRESHOLD']).mean()):.3f}",
358
+ flush=True)
359
+
360
+ variants = build_variants(adv_b, ret_b, cfg, batch)
361
+
362
+ def flatten(tree):
363
+ return jnp.concatenate([x.ravel() for x in jax.tree.leaves(tree)])
364
+
365
+ def gradient(weights, key):
366
+ def loss(p):
367
+ value, _ = compute_loss(
368
+ apply_train, p, key, act_b, obs_b, val_b, num_actions,
369
+ schedule_fn, schedule_deriv_fn,
370
+ sigma_t=cfg.get("TRAIN_SIGMA", 0.0),
371
+ label_smoothing=cfg.get("LABEL_SMOOTHING", 0.0),
372
+ advantages=weights,
373
+ )
374
+ return value
375
+ return flatten(jax.grad(loss)(params))
376
+
377
+ deltas = {}
378
+ stats = {}
379
+ for name, weights in variants.items():
380
+ delta, wbar, a1_holds = centred_delta(weights)
381
+ deltas[name] = delta
382
+ stats[name] = {
383
+ "cv_a": float(jnp.sqrt(jnp.mean(delta ** 2))),
384
+ "abar": wbar,
385
+ "ess_fraction": effective_sample_size(weights),
386
+ "a1_violated": not a1_holds,
387
+ }
388
+ base_abar = stats[BASELINE]["abar"]
389
+ for rec in stats.values():
390
+ rec["abar_ratio_to_baseline"] = rec["abar"] / base_abar
391
+
392
+ acc = {name: {"ratio": [], "cos": [], "ratio_shuf": [], "cos_shuf": []}
393
+ for name in variants}
394
+ bc_self, residuals = [], []
395
+ for draw in range(n_draws):
396
+ rng, key = jax.random.split(rng)
397
+ g_bc = gradient(None, key)
398
+ norm_bc = float(jnp.linalg.norm(g_bc))
399
+
400
+ rng, key2 = jax.random.split(rng)
401
+ g_bc2 = gradient(None, key2) # same objective, independent noise draw
402
+ bc_self.append(
403
+ float(jnp.dot(g_bc, g_bc2) / (norm_bc * jnp.linalg.norm(g_bc2) + 1e-12))
404
+ )
405
+
406
+ for name, weights in variants.items():
407
+ delta = deltas[name]
408
+ g_delta = gradient(delta, key)
409
+ ratio, cos = against_bc(g_delta, g_bc, norm_bc)
410
+ acc[name]["ratio"].append(ratio)
411
+ acc[name]["cos"].append(cos)
412
+
413
+ # Shuffled-delta null: same multiset of weights, same CV_A, the
414
+ # association with each window's own return destroyed. The (z_t, t)
415
+ # key is the one the real measurement used, so the two differ only
416
+ # in which window carries which weight.
417
+ perm_rng, perm_key = jax.random.split(perm_rng)
418
+ shuffled = jax.random.permutation(perm_key, delta)
419
+ ratio_s, cos_s = against_bc(gradient(shuffled, key), g_bc, norm_bc)
420
+ acc[name]["ratio_shuf"].append(ratio_s)
421
+ acc[name]["cos_shuf"].append(cos_s)
422
+
423
+ if name == BASELINE:
424
+ # Eq. 4 identity check: grad L_RW == Abar * (grad L_BC + g_delta)
425
+ g_rw = gradient(weights, key)
426
+ residuals.append(float(
427
+ jnp.linalg.norm(g_rw - stats[name]["abar"] * (g_bc + g_delta))
428
+ / (jnp.linalg.norm(g_rw) + 1e-12)
429
+ ))
430
+ print(f" draw {draw + 1}/{n_draws}", flush=True)
431
+
432
+ out = {
433
+ "aggregate": False,
434
+ "checkpoint_step": int(step),
435
+ "n_params": int(n_params),
436
+ "random_cos_sd": float(random_cos_sd),
437
+ "batch": int(batch),
438
+ "seed": seed,
439
+ "n_draws": n_draws,
440
+ "bc_self_cos_mean": float(np.mean(bc_self)),
441
+ "bc_self_cos_std": float(np.std(bc_self)),
442
+ "eq4_residual_max": float(np.max(residuals)),
443
+ "variants": {},
444
+ }
445
+ print(f"\ncos(grad L_BC, grad L_BC) across draws = "
446
+ f"{np.mean(bc_self):.3f} +/- {np.std(bc_self):.3f} [same-objective reference]")
447
+ print(f"random-direction null: cos ~ N(0, {random_cos_sd:.2e})")
448
+ print(f"Eq. 4 identity, max relative residual = {np.max(residuals):.2e}")
449
+ print(f"+/- below is across the {n_draws} (z_t, t) draws of this one seed, "
450
+ "not across seeds\n")
451
+ print(f"{'weight transform':26s} {'CV_A':>7s} {'Abar':>8s} {'Abar/base':>10s} "
452
+ f"{'ESS/B':>7s} {'ratio':>16s} {'cos':>16s} "
453
+ f"{'ratio(shuf)':>16s} {'cos(shuf)':>16s}")
454
+ for name, rec in acc.items():
455
+ ratio, cos = np.array(rec["ratio"]), np.array(rec["cos"])
456
+ ratio_s, cos_s = np.array(rec["ratio_shuf"]), np.array(rec["cos_shuf"])
457
+ out["variants"][name] = {
458
+ **stats[name],
459
+ "ratio_mean": float(ratio.mean()), "ratio_std_draws": float(ratio.std()),
460
+ "cos_mean": float(cos.mean()), "cos_std_draws": float(cos.std()),
461
+ "ratio_shuffled_mean": float(ratio_s.mean()),
462
+ "ratio_shuffled_std": float(ratio_s.std()),
463
+ "cos_shuffled_mean": float(cos_s.mean()),
464
+ "cos_shuffled_std": float(cos_s.std()),
465
+ }
466
+ flag = " [(A1) violated]" if stats[name]["a1_violated"] else ""
467
+ print(f"{name:26s} {stats[name]['cv_a']:7.3f} {stats[name]['abar']:8.3f} "
468
+ f"{stats[name]['abar_ratio_to_baseline']:10.3f} "
469
+ f"{stats[name]['ess_fraction']:7.3f} "
470
+ f"{ratio.mean():9.3f} +/-{ratio.std():.3f} "
471
+ f"{cos.mean():+9.3f} +/-{cos.std():.3f} "
472
+ f"{ratio_s.mean():9.3f} +/-{ratio_s.std():.3f} "
473
+ f"{cos_s.mean():+9.3f} +/-{cos_s.std():.3f}{flag}")
474
+ return out
475
+
476
+
477
+ def _write(path: Path, blob: dict) -> None:
478
+ path.parent.mkdir(parents=True, exist_ok=True)
479
+ path.write_bytes(orjson.dumps(blob, option=orjson.OPT_INDENT_2))
480
+ logger.info("Wrote %s", path)
481
+
482
+
483
+ def load_gdelta_aggregate(output_dir: Path) -> dict | None:
484
+ """The aggregate record for a run, or ``None`` if it was never measured."""
485
+ path = Path(output_dir) / GDELTA_DIRNAME / AGGREGATE_FILENAME
486
+ if not path.is_file():
487
+ return None
488
+ return orjson.loads(path.read_bytes())
489
+
490
+
491
+ def run_gdelta(
492
+ config: dict,
493
+ ckpt: str,
494
+ output_dir: Path,
495
+ *,
496
+ seeds: list[int],
497
+ n_draws: int = 8,
498
+ num_envs: int | None = None,
499
+ batch_size: int | None = None,
500
+ ) -> dict:
501
+ """Measure every seed and write the per-seed and aggregate records.
502
+
503
+ Artifacts land in ``output_dir/gdelta/``, beside the run's ``results.json``,
504
+ so one run directory holds the suite's scores and the gradient measurement
505
+ taken at the checkpoint they started from.
506
+
507
+ Args:
508
+ config: UPPERCASE config dict.
509
+ ckpt: Orbax checkpoint directory of the pretrained planner.
510
+ output_dir: The run's root output directory.
511
+ seeds: Rollout seeds to measure.
512
+ n_draws: Independent ``(z_t, t)`` draws per seed.
513
+ num_envs: Override ``NUM_ENVS``; ``None`` keeps the config value.
514
+ batch_size: Override ``BATCH_SIZE``; ``None`` keeps the config value.
515
+
516
+ Returns:
517
+ The aggregate record.
518
+ """
519
+ gdelta_dir = Path(output_dir) / GDELTA_DIRNAME
520
+ blobs, labels = [], []
521
+ for seed in seeds:
522
+ logger.info("Measuring g_delta at rollout seed %d", seed)
523
+ blob = measure(
524
+ config,
525
+ ckpt,
526
+ seed=seed,
527
+ n_draws=n_draws,
528
+ num_envs=num_envs,
529
+ batch_size=batch_size,
530
+ )
531
+ path = gdelta_dir / f"gdelta_seed{seed}.json"
532
+ _write(path, blob)
533
+ blobs.append(blob)
534
+ labels.append(str(path))
535
+
536
+ agg = aggregate(blobs, labels)
537
+ _write(gdelta_dir / AGGREGATE_FILENAME, agg)
538
+ print_aggregate(agg)
539
+ return agg
540
+
541
+
542
+ def aggregate_files(paths: list[str], output_dir: Path) -> dict:
543
+ """Aggregate per-seed records written elsewhere, e.g. by another machine."""
544
+ blobs = [orjson.loads(Path(p).read_bytes()) for p in paths]
545
+ agg = aggregate(blobs, [str(p) for p in paths])
546
+ _write(Path(output_dir) / GDELTA_DIRNAME / AGGREGATE_FILENAME, agg)
547
+ print_aggregate(agg)
548
+ return agg
experiments/rl_finetuning/analysis/plots.py CHANGED
@@ -791,10 +791,12 @@ def plot_achievement_breakdown(
791
  names, values in [0, 1]).
792
  output_dir: Output directory for the figure.
793
  """
794
- # Collect ablations that have at least two eval checkpoints.
 
 
795
  valid: list[tuple[str, dict[str, float], dict[str, float]]] = []
796
  for name, res in results.items():
797
- rates = res["history"].per_achievement_rates
798
  if len(rates) >= 2:
799
  valid.append((name, rates[0], rates[-1]))
800
 
 
791
  names, values in [0, 1]).
792
  output_dir: Output directory for the figure.
793
  """
794
+ # Collect ablations with at least two *populated* eval checkpoints. Empty
795
+ # entries are checkpoints where the scan could not return variable-key
796
+ # dicts, not evaluations that unlocked nothing.
797
  valid: list[tuple[str, dict[str, float], dict[str, float]]] = []
798
  for name, res in results.items():
799
+ rates = [r for r in res["history"].per_achievement_rates if r]
800
  if len(rates) >= 2:
801
  valid.append((name, rates[0], rates[-1]))
802
 
experiments/rl_finetuning/analysis/tables.py CHANGED
@@ -15,6 +15,10 @@ import polars as pl
15
 
16
  from experiments.rl_finetuning.ablations.registry import REGISTRY
17
  from experiments.rl_finetuning.ablations.training import AblationHistory
 
 
 
 
18
 
19
  logger = logging.getLogger(__name__)
20
 
@@ -100,6 +104,26 @@ def baseline_rl_score_of(results: dict[str, dict], pretrained_score: float) -> f
100
  return results.get("baseline_rl", {}).get("score", pretrained_score)
101
 
102
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  def _latex_escape(text: str) -> str:
104
  """Escape LaTeX special characters in a string.
105
 
@@ -267,13 +291,13 @@ def write_significance_test(results: dict[str, dict], out_dir: Path) -> None:
267
  f"bootstrap 95% CI of the difference (10000 resamples, seed 0): "
268
  f"[{lo:.4f}, {hi:.4f}]\n"
269
  + (
270
- f"arms excluded from the max (seed count != {n_b}): "
271
- f"{', '.join(dropped)}\n"
272
  if dropped
273
  else ""
274
  )
275
  )
276
 
 
277
  def make_main_results_table(
278
  results: dict[str, dict],
279
  pretrained_score: float,
@@ -369,6 +393,47 @@ def make_gradient_analysis_table(
369
  return pl.DataFrame(rows)
370
 
371
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
372
  def make_t_distribution_table(
373
  results: dict[str, dict],
374
  ) -> pl.DataFrame:
@@ -592,8 +657,7 @@ def make_per_env_table(
592
  """
593
  ablation_finals: dict[str, dict[str, float]] = {}
594
  for name, res in results.items():
595
- rates = res["history"].per_achievement_rates
596
- ablation_finals[name] = rates[-1] if rates else {}
597
 
598
  all_keys: list[str] = sorted(
599
  set(pretrained_ach_rates) | {k for d in ablation_finals.values() for k in d}
@@ -645,9 +709,7 @@ def make_hypothesis_verdict_table(
645
  result = verdict(score, baseline_rl_score, pretrained_score)
646
  conclusion = {
647
  "IMPROVEMENT": "Hypothesis SUPPORTED — this intervention helps",
648
- "COLLAPSE": (
649
- "Hypothesis REFUTED — intervention did not prevent collapse"
650
- ),
651
  "NEUTRAL": "Inconclusive — no significant change",
652
  }[result]
653
 
@@ -685,8 +747,7 @@ def make_achievement_table(
685
  # Collect final achievement rates for every ablation.
686
  ablation_finals: dict[str, dict[str, float]] = {}
687
  for name, res in results.items():
688
- rates = res["history"].per_achievement_rates
689
- ablation_finals[name] = rates[-1] if rates else {}
690
 
691
  # Union of all achievement keys.
692
  all_keys: list[str] = sorted(
@@ -709,11 +770,262 @@ def make_achievement_table(
709
  return pl.DataFrame(rows)
710
 
711
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
712
  def generate_summary_tables(
713
  results: dict[str, dict],
714
  pretrained_score: float,
715
  output_dir: Path,
716
  pretrained_ach_rates: dict[str, float] | None = None,
 
 
717
  ) -> dict[str, pl.DataFrame]:
718
  """Generate all summary tables and save to output_dir/tables/.
719
 
@@ -727,6 +1039,15 @@ def generate_summary_tables(
727
  pretrained_ach_rates: Optional per-achievement unlock rates for the pretrained
728
  baseline (keys = achievement name, values in [0, 1]).
729
  When provided, a per-achievement summary table is generated.
 
 
 
 
 
 
 
 
 
730
 
731
  Returns:
732
  Dict mapping table name -> polars DataFrame.
@@ -818,5 +1139,20 @@ def generate_summary_tables(
818
  label="tab:per_env",
819
  )
820
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
821
  logger.info("All tables saved to %s", tables_dir)
822
  return tables
 
15
 
16
  from experiments.rl_finetuning.ablations.registry import REGISTRY
17
  from experiments.rl_finetuning.ablations.training import AblationHistory
18
+ from experiments.rl_finetuning.analysis.gdelta import (
19
+ REGISTRY_RULES,
20
+ load_gdelta_aggregate,
21
+ )
22
 
23
  logger = logging.getLogger(__name__)
24
 
 
104
  return results.get("baseline_rl", {}).get("score", pretrained_score)
105
 
106
 
107
+ def final_ach_rates(res: dict) -> dict[str, float]:
108
+ """Per-achievement rates of the evaluation that produced ``score``.
109
+
110
+ ``final_ach_rates`` is the detail of the post-loop evaluation the headline
111
+ score comes from, so tables built on it reconcile with the score by
112
+ construction. Suite runs from before that field existed wrote the same
113
+ evaluation over the last in-loop history entry; those are read from there.
114
+
115
+ Args:
116
+ res: One entry of a results dict.
117
+
118
+ Returns:
119
+ ``{achievement: rate}``, empty when neither source has any.
120
+ """
121
+ if res.get("final_ach_rates"):
122
+ return res["final_ach_rates"]
123
+ rates = res["history"].per_achievement_rates
124
+ return rates[-1] if rates else {}
125
+
126
+
127
  def _latex_escape(text: str) -> str:
128
  """Escape LaTeX special characters in a string.
129
 
 
291
  f"bootstrap 95% CI of the difference (10000 resamples, seed 0): "
292
  f"[{lo:.4f}, {hi:.4f}]\n"
293
  + (
294
+ f"arms excluded from the max (seed count != {n_b}): {', '.join(dropped)}\n"
 
295
  if dropped
296
  else ""
297
  )
298
  )
299
 
300
+
301
  def make_main_results_table(
302
  results: dict[str, dict],
303
  pretrained_score: float,
 
393
  return pl.DataFrame(rows)
394
 
395
 
396
+ def make_gdelta_table(agg: dict) -> pl.DataFrame:
397
+ """The gradient-decomposition table: one row per weight transform.
398
+
399
+ Columns follow the paper's ``tab:gdelta``. Every ``+/-`` is the standard
400
+ deviation ACROSS ROLLOUT SEEDS, which is what :func:`gdelta.aggregate`
401
+ computes; a single seed's dispersion over its own ``(z_t, t)`` draws is a
402
+ different and smaller quantity and is not reported here.
403
+
404
+ ``normalized_adv`` mean-centres its weights, so (A1) fails and its ratio
405
+ divides by a mean near zero. The row is kept with an explicit flag rather
406
+ than dropped, because its absence would read as an omission.
407
+
408
+ Args:
409
+ agg: The aggregate record from ``gdelta/gdelta_aggregate.json``.
410
+
411
+ Returns:
412
+ Polars DataFrame.
413
+ """
414
+ rows = []
415
+ for name, rec in agg["variants"].items():
416
+ rows.append(
417
+ {
418
+ "Transform": name,
419
+ "CV_A": round(rec["cv_a_mean"], 3),
420
+ "Abar": round(rec["abar_mean"], 3),
421
+ "Abar_Ratio_Baseline": round(rec["abar_ratio_to_baseline_mean"], 3),
422
+ "ESS_Fraction": round(rec["ess_fraction_mean"], 3),
423
+ "Ratio": round(rec["ratio_mean"], 3),
424
+ "Ratio_Sd": round(rec["ratio_std_seeds"], 3),
425
+ "Cos": round(rec["cos_mean"], 3),
426
+ "Cos_Sd": round(rec["cos_std_seeds"], 3),
427
+ "Ratio_Shuffled": round(rec["ratio_shuffled_mean"], 3),
428
+ "Ratio_Shuffled_Sd": round(rec["ratio_shuffled_std_seeds"], 3),
429
+ "Cos_Shuffled": round(rec["cos_shuffled_mean"], 3),
430
+ "Cos_Shuffled_Sd": round(rec["cos_shuffled_std_seeds"], 3),
431
+ "A1_Violated": bool(rec["a1_violated"]),
432
+ }
433
+ )
434
+ return pl.DataFrame(rows)
435
+
436
+
437
  def make_t_distribution_table(
438
  results: dict[str, dict],
439
  ) -> pl.DataFrame:
 
657
  """
658
  ablation_finals: dict[str, dict[str, float]] = {}
659
  for name, res in results.items():
660
+ ablation_finals[name] = final_ach_rates(res)
 
661
 
662
  all_keys: list[str] = sorted(
663
  set(pretrained_ach_rates) | {k for d in ablation_finals.values() for k in d}
 
709
  result = verdict(score, baseline_rl_score, pretrained_score)
710
  conclusion = {
711
  "IMPROVEMENT": "Hypothesis SUPPORTED — this intervention helps",
712
+ "COLLAPSE": ("Hypothesis REFUTED — intervention did not prevent collapse"),
 
 
713
  "NEUTRAL": "Inconclusive — no significant change",
714
  }[result]
715
 
 
747
  # Collect final achievement rates for every ablation.
748
  ablation_finals: dict[str, dict[str, float]] = {}
749
  for name, res in results.items():
750
+ ablation_finals[name] = final_ach_rates(res)
 
751
 
752
  # Union of all achievement keys.
753
  all_keys: list[str] = sorted(
 
770
  return pl.DataFrame(rows)
771
 
772
 
773
+ _DIGIT_WORDS = (
774
+ "Zero",
775
+ "One",
776
+ "Two",
777
+ "Three",
778
+ "Four",
779
+ "Five",
780
+ "Six",
781
+ "Seven",
782
+ "Eight",
783
+ "Nine",
784
+ )
785
+
786
+
787
+ def _macro_name(*parts: str) -> str:
788
+ """A TeX-legal control sequence name built from arbitrary identifiers.
789
+
790
+ ``\\newcommand`` names may contain letters only, so underscores and
791
+ hyphens become word boundaries and digits are spelled out:
792
+ ``layer_ablation_top1`` -> ``LayerAblationTopOne``.
793
+ """
794
+ out = []
795
+ for part in parts:
796
+ for word in str(part).replace("-", "_").split("_"):
797
+ if not word:
798
+ continue
799
+ chars = [_DIGIT_WORDS[int(c)] if c.isdigit() else c for c in word]
800
+ joined = "".join(chars)
801
+ out.append(joined[0].upper() + joined[1:])
802
+ return "".join(out)
803
+
804
+
805
+ def _cv_a(ess: float, batch: int) -> float:
806
+ """Weight dispersion recovered from an effective sample size."""
807
+ if not batch or ess <= 0:
808
+ return float("nan")
809
+ return float(np.sqrt(max(batch / ess - 1.0, 0.0)))
810
+
811
+
812
+ def write_gdelta_table(agg: dict, output_dir: Path) -> pl.DataFrame:
813
+ """Write ``tables/gdelta.{csv,tex}`` for one aggregate record."""
814
+ df = make_gdelta_table(agg)
815
+ _save_table(
816
+ df,
817
+ Path(output_dir) / "tables" / "gdelta",
818
+ caption="Return-term decomposition per weight transform.",
819
+ label="tab:gdelta",
820
+ )
821
+ return df
822
+
823
+
824
+ def _define_gdelta_macros(agg: dict, define) -> None:
825
+ """Emit the measured decomposition quantities as ``\\newcommand``s.
826
+
827
+ Each variant is tagged by its ABLATION name, not its gdelta variant name,
828
+ so ``baseline_clipped_ratio`` becomes ``BaselineRl`` and sits beside the
829
+ ``Score`` and ``Ess`` macros for the same condition. The ``Gdelta`` infix
830
+ keeps these clear of the ESS-derived ``CvA`` macros, which measure a
831
+ different thing on different batches.
832
+ """
833
+ define("GdeltaNSeeds", value=str(int(agg["n_seeds"])))
834
+ define("GdeltaNParams", value=f"{agg['n_params'] / 1e6:.2f}")
835
+ define("GdeltaRandomCosSd", value=f"{agg['random_cos_sd']:.1e}")
836
+ define("GdeltaBatch", value=str(int(agg["batch"])))
837
+ define("GdeltaBcSelfCos", value=f"{agg['bc_self_cos_mean']:.3f}")
838
+ define("GdeltaBcSelfCosSd", value=f"{agg['bc_self_cos_std_seeds']:.3f}")
839
+ define("GdeltaEqFourResidual", value=f"{agg['eq4_residual_max']:.1e}")
840
+
841
+ for variant, rec in agg["variants"].items():
842
+ rule = REGISTRY_RULES.get(variant)
843
+ tag = _macro_name(rule[0] if rule else variant)
844
+ define("GdeltaCvA", tag=tag, value=f"{rec['cv_a_mean']:.2f}")
845
+ define("GdeltaAbar", tag=tag, value=f"{rec['abar_mean']:.3f}")
846
+ define(
847
+ "GdeltaAbarRatio", tag=tag,
848
+ value=f"{rec['abar_ratio_to_baseline_mean']:.3f}",
849
+ )
850
+ define("GdeltaEss", tag=tag, value=f"{rec['ess_fraction_mean']:.2f}")
851
+ define("GdeltaRatio", tag=tag, value=f"{rec['ratio_mean']:.3f}")
852
+ define("GdeltaRatioSd", tag=tag, value=f"{rec['ratio_std_seeds']:.3f}")
853
+ define("GdeltaCos", tag=tag, value=f"{rec['cos_mean']:.3f}")
854
+ define("GdeltaCosSd", tag=tag, value=f"{rec['cos_std_seeds']:.3f}")
855
+ define(
856
+ "GdeltaRatioShuf", tag=tag,
857
+ value=f"{rec['ratio_shuffled_mean']:.3f}",
858
+ )
859
+ define(
860
+ "GdeltaRatioShufSd", tag=tag,
861
+ value=f"{rec['ratio_shuffled_std_seeds']:.3f}",
862
+ )
863
+ define("GdeltaCosShuf", tag=tag, value=f"{rec['cos_shuffled_mean']:.3f}")
864
+ define(
865
+ "GdeltaCosShufSd", tag=tag,
866
+ value=f"{rec['cos_shuffled_std_seeds']:.3f}",
867
+ )
868
+
869
+
870
+ def write_tex_macros(
871
+ results: dict[str, dict],
872
+ pretrained_score: float,
873
+ out_path: Path,
874
+ config: dict | None = None,
875
+ prefix: str = "rw",
876
+ scale: float = 1.0,
877
+ gdelta: dict | None = None,
878
+ ) -> Path:
879
+ """Write ``\\newcommand`` definitions for every headline quantity.
880
+
881
+ The paper workspace expects generated numbers to reach the manuscript
882
+ through macros so each is traceable to the run that produced it, rather
883
+ than being retyped as a literal.
884
+
885
+ Each quantity is emitted at the number of decimals the manuscript prints
886
+ it with, not at a uniform significant-figure count. A macro is a literal
887
+ substitution: ``%.4g`` would render ``\\rwGroupMeanA`` as ``9.744`` where
888
+ the paper prints ``9.74``, silently changing the displayed precision at
889
+ every site. Scores, standard deviations and group statistics take two
890
+ decimals; effective sample size takes none, which is how Section 6.4
891
+ quotes it.
892
+
893
+ Args:
894
+ results: Dict mapping ablation_name -> result entry.
895
+ pretrained_score: Pretrained model eval score.
896
+ out_path: File to write, conventionally ``results.tex``.
897
+ config: The run's config, read for ``BATCH_SIZE`` so
898
+ :math:`\\mathrm{CV}_A` and ESS can be emitted.
899
+ prefix: Macro-name prefix, keeping these out of the way of
900
+ LaTeX's own names. Two suites sharing one
901
+ ``results.tex`` need different prefixes.
902
+ gdelta: Optional aggregate record from
903
+ ``gdelta/gdelta_aggregate.json``. When present, the
904
+ measured decomposition quantities are emitted under a
905
+ ``Gdelta`` infix. They are deliberately NOT merged
906
+ into the ``CvA`` macros above: those recover
907
+ :math:`\\mathrm{CV}_A` from the ESS logged during
908
+ training, whereas these are measured on the
909
+ measurement batches at the pretrained checkpoint. The
910
+ two differ, and the manuscript quotes both.
911
+ scale: Multiplier on score-like quantities before
912
+ formatting, for a suite whose metric is a fraction
913
+ the paper reports as a percentage. Never applied to
914
+ ESS or :math:`\\mathrm{CV}_A`, which are unitless.
915
+
916
+ Returns:
917
+ The path written.
918
+ """
919
+ cfg = config or {}
920
+ batch = int(cfg.get("BATCH_SIZE") or cfg.get("batch_size") or 0)
921
+
922
+ def score(value: float, decimals: int = 2) -> str:
923
+ return f"{float(value) * scale:.{decimals}f}"
924
+
925
+ lines = [
926
+ "% Generated by experiments/rl_finetuning/analysis/tables.py.",
927
+ "% Do not hand-edit: regenerate with --emit-tex-macros.",
928
+ "",
929
+ ]
930
+ defined: dict[str, str] = {}
931
+
932
+ def define(*parts: str, value: str, tag: str = "") -> None:
933
+ """Emit one ``\\newcommand``, refusing to redefine a name.
934
+
935
+ The mangling in :func:`_macro_name` is lossy -- ``top1`` and
936
+ ``top_one`` both become ``TopOne`` -- so two conditions can collide.
937
+ Silently overwriting would put a number under a name that reads as
938
+ another condition's, which is exactly the failure the macro
939
+ convention exists to prevent.
940
+ """
941
+ macro = f"{prefix}{''.join(parts)}{tag}"
942
+ if macro in defined:
943
+ raise ValueError(
944
+ f"macro name collision: \\{macro} would be defined twice "
945
+ f"(existing value {defined[macro]!r}, new value {value!r}). "
946
+ f"_macro_name() mangles distinct identifiers onto one TeX "
947
+ f"control sequence; rename the condition or extend the rule."
948
+ )
949
+ defined[macro] = value
950
+ lines.append(f"\\newcommand{{\\{macro}}}{{{value}}}")
951
+
952
+ define("PretrainedScore", value=score(pretrained_score))
953
+ if batch:
954
+ define("BatchSize", value=str(batch))
955
+
956
+ within_seed_var: list[float] = []
957
+ baseline = (
958
+ float(results["baseline_rl"]["score"]) if "baseline_rl" in results else None
959
+ )
960
+ for name in sorted(results):
961
+ res = results[name]
962
+ tag = _macro_name(name)
963
+ define("Score", tag=tag, value=score(res["score"]))
964
+ # Magnitude only: the manuscript carries the sign as $-$ / $+$.
965
+ define(
966
+ "DeltaPretrained",
967
+ tag=tag,
968
+ value=score(abs(float(res["score"]) - pretrained_score)),
969
+ )
970
+ if baseline is not None:
971
+ define(
972
+ "DeltaBaseline",
973
+ tag=tag,
974
+ value=score(abs(float(res["score"]) - baseline)),
975
+ )
976
+ define("ScoreSd", tag=tag, value=score(res.get("score_std", 0.0)))
977
+ scores = [float(s) for s in res.get("all_scores", [])]
978
+ if len(scores) >= 2:
979
+ within_seed_var.append(float(np.var(scores, ddof=1)))
980
+
981
+ ess_series = res["history"].effective_batch_size
982
+ if ess_series:
983
+ ess = float(np.mean(ess_series))
984
+ define("Ess", tag=tag, value=f"{ess:.0f}")
985
+ if batch:
986
+ cv = float(np.mean([_cv_a(e, batch) for e in ess_series if e > 0]))
987
+ define("CvA", tag=tag, value=f"{cv:.2f}")
988
+
989
+ # Pooled within-condition sd across seeds: the seed noise a single
990
+ # condition's score carries, not the spread between conditions.
991
+ if within_seed_var:
992
+ pooled = float(np.sqrt(np.mean(within_seed_var)))
993
+ define("PooledSeedSd", value=score(pooled))
994
+
995
+ group_df = make_group_summary_table(results)
996
+ for row in group_df.iter_rows(named=True):
997
+ tag = _macro_name(row["Group"])
998
+ for col, suffix in (
999
+ ("Mean", "GroupMean"),
1000
+ ("Best", "GroupBest"),
1001
+ ("Worst", "GroupWorst"),
1002
+ ("StdDev", "GroupSd"),
1003
+ ):
1004
+ define(suffix, tag=tag, value=score(row[col]))
1005
+ define("GroupN", tag=tag, value=str(int(row["N"])))
1006
+ if baseline is not None:
1007
+ define(
1008
+ "GroupDelta",
1009
+ tag=tag,
1010
+ value=score(abs(float(row["Mean"]) - baseline)),
1011
+ )
1012
+
1013
+ if gdelta:
1014
+ _define_gdelta_macros(gdelta, define)
1015
+
1016
+ out_path.parent.mkdir(parents=True, exist_ok=True)
1017
+ out_path.write_text("\n".join(lines) + "\n")
1018
+ logger.info("Saved %s (%d macros)", out_path, len(defined))
1019
+ return out_path
1020
+
1021
+
1022
  def generate_summary_tables(
1023
  results: dict[str, dict],
1024
  pretrained_score: float,
1025
  output_dir: Path,
1026
  pretrained_ach_rates: dict[str, float] | None = None,
1027
+ emit_tex_macros: bool = False,
1028
+ config: dict | None = None,
1029
  ) -> dict[str, pl.DataFrame]:
1030
  """Generate all summary tables and save to output_dir/tables/.
1031
 
 
1039
  pretrained_ach_rates: Optional per-achievement unlock rates for the pretrained
1040
  baseline (keys = achievement name, values in [0, 1]).
1041
  When provided, a per-achievement summary table is generated.
1042
+ emit_tex_macros: Also write ``tables/results.tex``, one
1043
+ ``\\newcommand`` per headline quantity, for the
1044
+ manuscript to cite instead of a literal.
1045
+ config: The run's config, needed by the macro file for
1046
+ ``BATCH_SIZE``.
1047
+
1048
+ A ``gdelta/gdelta_aggregate.json`` in ``output_dir``, written by
1049
+ ``run_ablations.py --measure-gdelta``, additionally produces
1050
+ ``tables/gdelta.{csv,tex}`` and the measured decomposition macros.
1051
 
1052
  Returns:
1053
  Dict mapping table name -> polars DataFrame.
 
1139
  label="tab:per_env",
1140
  )
1141
 
1142
+ # Present only once the gradient measurement has been run for this run
1143
+ # directory; its absence is the normal state and not an error.
1144
+ gdelta_agg = load_gdelta_aggregate(output_dir)
1145
+ if gdelta_agg is not None:
1146
+ tables["gdelta"] = write_gdelta_table(gdelta_agg, output_dir)
1147
+
1148
+ if emit_tex_macros:
1149
+ write_tex_macros(
1150
+ results,
1151
+ pretrained_score,
1152
+ tables_dir / "results.tex",
1153
+ config=config,
1154
+ gdelta=gdelta_agg,
1155
+ )
1156
+
1157
  logger.info("All tables saved to %s", tables_dir)
1158
  return tables
experiments/rl_finetuning/configs/ablations_final_craftax_classic_gpu_24gb.yaml CHANGED
@@ -1,8 +1,8 @@
1
  # =============================================================================
2
- # RL Fine-Tuning Ablation Suite — Craftax Classic, 24 GB hardware
3
  # Matches checkpoint produced by: configs/final_craftax_classic_gpu_24gb.yaml
4
  # (d_model=384, n_heads=8, n_layers=6, d_ff=768, plan_horizon=32, seed 42)
5
- # Target hardware: RTX 3090 Ti (24 GB VRAM)
6
  # That shape is the ablations_default.yaml architecture, so it is inherited
7
  # rather than restated here. This file holds only its own deltas; every
8
  # other key comes from the base, which is layered on automatically.
 
1
  # =============================================================================
2
+ # RL Fine-Tuning Ablation Suite — Craftax Classic, GPU-24GB hardware
3
  # Matches checkpoint produced by: configs/final_craftax_classic_gpu_24gb.yaml
4
  # (d_model=384, n_heads=8, n_layers=6, d_ff=768, plan_horizon=32, seed 42)
5
+ # Target hardware: GPU-24GB RTX 3090 Ti (24 GB VRAM)
6
  # That shape is the ablations_default.yaml architecture, so it is inherited
7
  # rather than restated here. This file holds only its own deltas; every
8
  # other key comes from the base, which is layered on automatically.
experiments/rl_finetuning/configs/ablations_final_craftax_classic_gpu_h200.yaml CHANGED
@@ -1,5 +1,5 @@
1
  # =============================================================================
2
- # RL Fine-Tuning Ablation Suite — Craftax Classic, H200 hardware
3
  # Matches checkpoint produced by: configs/final_craftax_classic_gpu_h200.yaml
4
  # (d_model=384, n_heads=8, n_layers=6, d_ff=768, plan_horizon=32, seed 43)
5
  # Target hardware: H200 8 GB partition
 
1
  # =============================================================================
2
+ # RL Fine-Tuning Ablation Suite — Craftax Classic, GPU-H200 hardware
3
  # Matches checkpoint produced by: configs/final_craftax_classic_gpu_h200.yaml
4
  # (d_model=384, n_heads=8, n_layers=6, d_ff=768, plan_horizon=32, seed 43)
5
  # Target hardware: H200 8 GB partition
experiments/rl_finetuning/configs/ablations_final_craftax_gpu_24gb.yaml CHANGED
@@ -1,8 +1,8 @@
1
  # =============================================================================
2
- # RL Fine-Tuning Ablation Suite — Full Craftax, 24 GB hardware
3
  # Matches checkpoints produced by: configs/final_craftax_gpu_24gb.yaml
4
  # (d_model=384, n_heads=8, n_layers=6, d_ff=768, plan_horizon=32, seed 42)
5
- # Target hardware: 24 GB reference machine (GPU model unrecorded) (24 GB VRAM)
6
  # That shape is the ablations_default.yaml architecture, so it is inherited
7
  # rather than restated here. This file holds only its own deltas; every
8
  # other key comes from the base, which is layered on automatically.
 
1
  # =============================================================================
2
+ # RL Fine-Tuning Ablation Suite — Full Craftax, GPU-24GB hardware
3
  # Matches checkpoints produced by: configs/final_craftax_gpu_24gb.yaml
4
  # (d_model=384, n_heads=8, n_layers=6, d_ff=768, plan_horizon=32, seed 42)
5
+ # Target hardware: GPU-24GB reference machine (GPU model unrecorded) (24 GB VRAM)
6
  # That shape is the ablations_default.yaml architecture, so it is inherited
7
  # rather than restated here. This file holds only its own deltas; every
8
  # other key comes from the base, which is layered on automatically.
experiments/rl_finetuning/configs/ablations_final_craftax_gpu_h200.yaml CHANGED
@@ -1,5 +1,5 @@
1
  # =============================================================================
2
- # RL Fine-Tuning Ablation Suite — Full Craftax, H200 hardware
3
  # Matches checkpoints produced by: configs/final_craftax_gpu_h200.yaml
4
  # (d_model=384, n_heads=8, n_layers=6, d_ff=768, plan_horizon=32, seed 43)
5
  # Target hardware: H200 8 GB partition
 
1
  # =============================================================================
2
+ # RL Fine-Tuning Ablation Suite — Full Craftax, GPU-H200 hardware
3
  # Matches checkpoints produced by: configs/final_craftax_gpu_h200.yaml
4
  # (d_model=384, n_heads=8, n_layers=6, d_ff=768, plan_horizon=32, seed 43)
5
  # Target hardware: H200 8 GB partition
experiments/rl_finetuning/outputs/craftax_classic_ablations/gdelta/gdelta_aggregate.json ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "aggregate": true,
3
+ "inputs": [
4
+ "experiments/rl_finetuning/outputs/craftax_classic_ablations/gdelta/gdelta_seed0.json",
5
+ "experiments/rl_finetuning/outputs/craftax_classic_ablations/gdelta/gdelta_seed1.json",
6
+ "experiments/rl_finetuning/outputs/craftax_classic_ablations/gdelta/gdelta_seed2.json"
7
+ ],
8
+ "seeds": [
9
+ 0,
10
+ 1,
11
+ 2
12
+ ],
13
+ "n_seeds": 3,
14
+ "n_draws_per_seed": [
15
+ 8,
16
+ 8,
17
+ 8
18
+ ],
19
+ "n_params": 9334289,
20
+ "random_cos_sd": 0.000327310078675855,
21
+ "batch": 1024,
22
+ "eq4_residual_max": 4.80532605706685e-7,
23
+ "variants": {
24
+ "baseline_clipped_ratio": {
25
+ "a1_violated": false,
26
+ "cv_a_mean": 0.9854568839073181,
27
+ "cv_a_std_seeds": 0.018878114308338592,
28
+ "abar_mean": 1.0235402981440227,
29
+ "abar_std_seeds": 0.010061440877455132,
30
+ "abar_ratio_to_baseline_mean": 1.0,
31
+ "abar_ratio_to_baseline_std_seeds": 0.0,
32
+ "ess_fraction_mean": 0.507413487651479,
33
+ "ess_fraction_std_seeds": 0.009639155208801934,
34
+ "ratio_mean": 0.48452565418889765,
35
+ "ratio_std_seeds": 0.006983698107912942,
36
+ "cos_mean": 0.023888170331095655,
37
+ "cos_std_seeds": 0.055179809801403155,
38
+ "ratio_shuffled_mean": 0.3560985313002038,
39
+ "ratio_shuffled_std_seeds": 0.01911878846489955,
40
+ "cos_shuffled_mean": -0.05353597580688074,
41
+ "cos_shuffled_std_seeds": 0.025540160726829325
42
+ },
43
+ "advantage_clip": {
44
+ "a1_violated": false,
45
+ "cv_a_mean": 0.2014569491147995,
46
+ "cv_a_std_seeds": 0.0004557291260598919,
47
+ "abar_mean": 0.9526590506235758,
48
+ "abar_std_seeds": 0.009015712817429673,
49
+ "abar_ratio_to_baseline_mean": 0.9307592739286493,
50
+ "abar_ratio_to_baseline_std_seeds": 0.003565955741888492,
51
+ "ess_fraction_mean": 0.9609977946615301,
52
+ "ess_fraction_std_seeds": 0.00016941930355432837,
53
+ "ratio_mean": 0.09726429819029546,
54
+ "ratio_std_seeds": 0.0032068849286760316,
55
+ "cos_mean": -0.002687329212979724,
56
+ "cos_std_seeds": 0.06825983346564395,
57
+ "ratio_shuffled_mean": 0.07327280714136783,
58
+ "ratio_shuffled_std_seeds": 0.002498156230349314,
59
+ "cos_shuffled_mean": -0.03329448491179695,
60
+ "cos_shuffled_std_seeds": 0.052956390835973914
61
+ },
62
+ "normalized_adv": {
63
+ "a1_violated": true,
64
+ "cv_a_mean": 0.9999999205271403,
65
+ "cv_a_std_seeds": 7.434005313662571e-8,
66
+ "abar_mean": -1.0322158535321554e-8,
67
+ "abar_std_seeds": 3.960053117034971e-8,
68
+ "abar_ratio_to_baseline_mean": -1.0236111820250936e-8,
69
+ "abar_ratio_to_baseline_std_seeds": 3.8875732176752924e-8,
70
+ "ess_fraction_mean": 1.6747491186726309e-15,
71
+ "ess_fraction_std_seeds": 9.26030088934509e-16,
72
+ "ratio_mean": 0.4919759005782294,
73
+ "ratio_std_seeds": 0.016081065492987393,
74
+ "cos_mean": 0.023888198743710138,
75
+ "cos_std_seeds": 0.05517983637811973,
76
+ "ratio_shuffled_mean": 0.357488476116716,
77
+ "ratio_shuffled_std_seeds": 0.020395976400501593,
78
+ "cos_shuffled_mean": 0.05980315369864305,
79
+ "cos_shuffled_std_seeds": 0.04091382822120188
80
+ },
81
+ "bc_wins": {
82
+ "a1_violated": false,
83
+ "cv_a_mean": 0.7747447292009989,
84
+ "cv_a_std_seeds": 0.021514876051124374,
85
+ "abar_mean": 1.0,
86
+ "abar_std_seeds": 1.7547132305955582e-7,
87
+ "abar_ratio_to_baseline_mean": 0.9770956797109048,
88
+ "abar_ratio_to_baseline_std_seeds": 0.009621438085986775,
89
+ "ess_fraction_mean": 0.624999973036458,
90
+ "ess_fraction_std_seeds": 0.013053179236684295,
91
+ "ratio_mean": 0.4170324476063991,
92
+ "ratio_std_seeds": 0.024545794473638916,
93
+ "cos_mean": 0.1420223995228298,
94
+ "cos_std_seeds": 0.09300934862546119,
95
+ "ratio_shuffled_mean": 0.2839805669591658,
96
+ "ratio_shuffled_std_seeds": 0.007360032818748407,
97
+ "cos_shuffled_mean": 0.014593212322021524,
98
+ "cos_shuffled_std_seeds": 0.056382858532646296
99
+ }
100
+ },
101
+ "bc_self_cos_mean": 0.892872134844462,
102
+ "bc_self_cos_std_seeds": 0.009867953837605352
103
+ }
experiments/rl_finetuning/outputs/craftax_classic_ablations/gdelta/gdelta_seed0.json ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "aggregate": false,
3
+ "checkpoint_step": 40370176,
4
+ "n_params": 9334289,
5
+ "random_cos_sd": 0.000327310078675855,
6
+ "batch": 1024,
7
+ "seed": 0,
8
+ "n_draws": 8,
9
+ "bc_self_cos_mean": 0.8886517286300659,
10
+ "bc_self_cos_std": 0.013864377043282333,
11
+ "eq4_residual_max": 4.80532605706685e-7,
12
+ "variants": {
13
+ "baseline_clipped_ratio": {
14
+ "cv_a": 0.9587592482566833,
15
+ "abar": 1.0352622270584106,
16
+ "ess_fraction": 0.5210452873754741,
17
+ "a1_violated": false,
18
+ "abar_ratio_to_baseline": 1.0,
19
+ "ratio_mean": 0.4929142524131889,
20
+ "ratio_std_draws": 0.062149263585443426,
21
+ "cos_mean": -0.05232078512199223,
22
+ "cos_std_draws": 0.12823055031892652,
23
+ "ratio_shuffled_mean": 0.3702785550029438,
24
+ "ratio_shuffled_std": 0.04985673247875491,
25
+ "cos_shuffled_mean": -0.07264717968791956,
26
+ "cos_shuffled_std": 0.1375778261666584
27
+ },
28
+ "advantage_clip": {
29
+ "cv_a": 0.2019623965024948,
30
+ "abar": 0.9651404619216919,
31
+ "ess_fraction": 0.9608097961720935,
32
+ "a1_violated": false,
33
+ "abar_ratio_to_baseline": 0.9322666631661406,
34
+ "ratio_mean": 0.10178494489910378,
35
+ "ratio_std_draws": 0.010863613185205834,
36
+ "cos_mean": -0.08856321198982187,
37
+ "cos_std_draws": 0.09348871621494285,
38
+ "ratio_shuffled_mean": 0.07580454134313921,
39
+ "ratio_shuffled_std": 0.006146976066602656,
40
+ "cos_shuffled_mean": -0.08668288914486766,
41
+ "cos_shuffled_std": 0.10151970823488679
42
+ },
43
+ "normalized_adv": {
44
+ "cv_a": 0.9999999403953552,
45
+ "abar": -2.1653249859809875e-8,
46
+ "ess_fraction": 4.688632853842156e-16,
47
+ "a1_violated": true,
48
+ "abar_ratio_to_baseline": -2.0915715162655282e-8,
49
+ "ratio_mean": 0.5141167874551692,
50
+ "ratio_std_draws": 0.06482259643725917,
51
+ "cos_mean": -0.05232076484389836,
52
+ "cos_std_draws": 0.12823054295881925,
53
+ "ratio_shuffled_mean": 0.38073488366371927,
54
+ "ratio_shuffled_std": 0.024812852770472734,
55
+ "cos_shuffled_mean": 0.06606726971222088,
56
+ "cos_shuffled_std": 0.16606576384474356
57
+ },
58
+ "bc_wins": {
59
+ "cv_a": 0.7458090782165527,
60
+ "abar": 0.9999998211860657,
61
+ "ess_fraction": 0.6425777397495268,
62
+ "a1_violated": false,
63
+ "abar_ratio_to_baseline": 0.9659386723955539,
64
+ "ratio_mean": 0.3823743698913481,
65
+ "ratio_std_draws": 0.04196826406011548,
66
+ "cos_mean": 0.02126681269146502,
67
+ "cos_std_draws": 0.12931455515444024,
68
+ "ratio_shuffled_mean": 0.2905509104827731,
69
+ "ratio_shuffled_std": 0.0079822831305291,
70
+ "cos_shuffled_mean": 0.0010593719780445099,
71
+ "cos_shuffled_std": 0.22469821243454166
72
+ }
73
+ }
74
+ }
experiments/rl_finetuning/outputs/craftax_classic_ablations/gdelta/gdelta_seed1.json ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "aggregate": false,
3
+ "checkpoint_step": 40370176,
4
+ "n_params": 9334289,
5
+ "random_cos_sd": 0.000327310078675855,
6
+ "batch": 1024,
7
+ "seed": 1,
8
+ "n_draws": 8,
9
+ "bc_self_cos_mean": 0.8834625333547592,
10
+ "bc_self_cos_std": 0.028890878138939563,
11
+ "eq4_residual_max": 4.458028683984594e-7,
12
+ "variants": {
13
+ "baseline_clipped_ratio": {
14
+ "cv_a": 0.9988502860069275,
15
+ "abar": 1.0246647596359253,
16
+ "ess_fraction": 0.5005752960258663,
17
+ "a1_violated": false,
18
+ "abar_ratio_to_baseline": 1.0,
19
+ "ratio_mean": 0.48484595625152527,
20
+ "ratio_std_draws": 0.03739208589573637,
21
+ "cos_mean": 0.047454304061830044,
22
+ "cos_std_draws": 0.06307783640351931,
23
+ "ratio_shuffled_mean": 0.36894560309691693,
24
+ "ratio_shuffled_std": 0.022733560664731905,
25
+ "cos_shuffled_mean": -0.017437556954973843,
26
+ "cos_shuffled_std": 0.13464438960797637
27
+ },
28
+ "advantage_clip": {
29
+ "cv_a": 0.20085792243480682,
30
+ "abar": 0.94867342710495,
31
+ "ess_fraction": 0.9612204281808505,
32
+ "a1_violated": false,
33
+ "abar_ratio_to_baseline": 0.9258378588544649,
34
+ "ratio_mean": 0.0953185879622219,
35
+ "ratio_std_draws": 0.011684367167521479,
36
+ "cos_mean": 0.002065446664346382,
37
+ "cos_std_draws": 0.07558667393060121,
38
+ "ratio_shuffled_mean": 0.0741409312015546,
39
+ "ratio_shuffled_std": 0.0062286893856330055,
40
+ "cos_shuffled_mean": 0.03888408502098173,
41
+ "cos_shuffled_std": 0.14181727110818865
42
+ },
43
+ "normalized_adv": {
44
+ "cv_a": 0.9999998211860657,
45
+ "abar": 4.284083843231201e-8,
46
+ "ess_fraction": 1.835337984556805e-15,
47
+ "a1_violated": true,
48
+ "abar_ratio_to_baseline": 4.18096143440454e-8,
49
+ "ratio_mean": 0.4854039954925514,
50
+ "ratio_std_draws": 0.037435128863097536,
51
+ "cos_mean": 0.04745421721599996,
52
+ "cos_std_draws": 0.06307784333867371,
53
+ "ratio_shuffled_mean": 0.3606536861227143,
54
+ "ratio_shuffled_std": 0.0351022186659039,
55
+ "cos_shuffled_mean": 0.10648557799868286,
56
+ "cos_shuffled_std": 0.06018093926588145
57
+ },
58
+ "bc_wins": {
59
+ "cv_a": 0.7973600029945374,
60
+ "abar": 0.9999999403953552,
61
+ "ess_fraction": 0.6113279738392736,
62
+ "a1_violated": false,
63
+ "abar_ratio_to_baseline": 0.9759288889281859,
64
+ "ratio_mean": 0.4326711384712623,
65
+ "ratio_std_draws": 0.03118598407923515,
66
+ "cos_mean": 0.1572374363313429,
67
+ "cos_std_draws": 0.14007506866888625,
68
+ "ratio_shuffled_mean": 0.287686680066821,
69
+ "ratio_shuffled_std": 0.014448778378290157,
70
+ "cos_shuffled_mean": -0.04669254180043936,
71
+ "cos_shuffled_std": 0.2434607700636855
72
+ }
73
+ }
74
+ }
experiments/rl_finetuning/outputs/craftax_classic_ablations/gdelta/gdelta_seed2.json ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "aggregate": false,
3
+ "checkpoint_step": 40370176,
4
+ "n_params": 9334289,
5
+ "random_cos_sd": 0.000327310078675855,
6
+ "batch": 1024,
7
+ "seed": 2,
8
+ "n_draws": 8,
9
+ "bc_self_cos_mean": 0.9065021425485611,
10
+ "bc_self_cos_std": 0.0077027198757505366,
11
+ "eq4_residual_max": 4.4017934897055966e-7,
12
+ "variants": {
13
+ "baseline_clipped_ratio": {
14
+ "cv_a": 0.9987611174583435,
15
+ "abar": 1.010693907737732,
16
+ "ess_fraction": 0.5006198795530965,
17
+ "a1_violated": false,
18
+ "abar_ratio_to_baseline": 1.0,
19
+ "ratio_mean": 0.47581675390197886,
20
+ "ratio_std_draws": 0.050013706533038056,
21
+ "cos_mean": 0.07653099205344915,
22
+ "cos_std_draws": 0.1371696455986107,
23
+ "ratio_shuffled_mean": 0.3290714358007507,
24
+ "ratio_shuffled_std": 0.017993567005007666,
25
+ "cos_shuffled_mean": -0.07052319077774882,
26
+ "cos_shuffled_std": 0.14557688155890267
27
+ },
28
+ "advantage_clip": {
29
+ "cv_a": 0.20155052840709686,
30
+ "abar": 0.9441632628440857,
31
+ "ess_fraction": 0.9609631596316461,
32
+ "a1_violated": false,
33
+ "abar_ratio_to_baseline": 0.9341732997653425,
34
+ "ratio_mean": 0.09468936170956067,
35
+ "ratio_std_draws": 0.0089181304684684,
36
+ "cos_mean": 0.07843577768653631,
37
+ "cos_std_draws": 0.15751443556107037,
38
+ "ratio_shuffled_mean": 0.06987294887940967,
39
+ "ratio_shuffled_std": 0.005245661765416722,
40
+ "cos_shuffled_mean": -0.05208465061150491,
41
+ "cos_shuffled_std": 0.1682371637969602
42
+ },
43
+ "normalized_adv": {
44
+ "cv_a": 1.0,
45
+ "abar": -5.21540641784668e-8,
46
+ "ess_fraction": 2.720046086076872e-15,
47
+ "a1_violated": true,
48
+ "abar_ratio_to_baseline": -5.1602234642142926e-8,
49
+ "ratio_mean": 0.4764069187869675,
50
+ "ratio_std_draws": 0.05007575024715252,
51
+ "cos_mean": 0.07653114385902882,
52
+ "cos_std_draws": 0.13716963854788167,
53
+ "ratio_shuffled_mean": 0.3310768585637146,
54
+ "ratio_shuffled_std": 0.02154253356764448,
55
+ "cos_shuffled_mean": 0.006856613385025412,
56
+ "cos_shuffled_std": 0.19543885350694304
57
+ },
58
+ "bc_wins": {
59
+ "cv_a": 0.7810651063919067,
60
+ "abar": 1.000000238418579,
61
+ "ess_fraction": 0.6210942055205736,
62
+ "a1_violated": false,
63
+ "abar_ratio_to_baseline": 0.989419477808975,
64
+ "ratio_mean": 0.4360518344565869,
65
+ "ratio_std_draws": 0.025181532407221242,
66
+ "cos_mean": 0.24756294954568148,
67
+ "cos_std_draws": 0.08224887485750225,
68
+ "ratio_shuffled_mean": 0.2737041103279033,
69
+ "ratio_shuffled_std": 0.02088922719350182,
70
+ "cos_shuffled_mean": 0.08941280678845942,
71
+ "cos_shuffled_std": 0.19656693288770055
72
+ }
73
+ }
74
+ }
experiments/rl_finetuning/outputs/craftax_classic_ablations/results.json CHANGED
@@ -26,11 +26,11 @@
26
  },
27
  "merge_provenance": {
28
  "inputs": [
29
- "<path omitted for anonymous review>",
30
- "<path omitted for anonymous review>",
31
- "<path omitted for anonymous review>"
32
  ],
33
- "config_from": "<path omitted for anonymous review>"
34
  },
35
  "config": {
36
  "ENV_NAME": "Craftax-Classic-Symbolic-v1",
@@ -87,14 +87,9 @@
87
  "SAVE_POLICY": true,
88
  "CHECKPOINT_DIR": "checkpoints",
89
  "RESUME_CHECKPOINT_PATH": null,
90
- "RESUME_WANDB_RUN_ID": null,
91
  "RESUME_STEP": null,
92
- "JAX_COMPILATION_CACHE_DIR": "<path omitted for anonymous review>",
93
  "SEED": null,
94
- "USE_WANDB": true,
95
- "WANDB_PROJECT": "remdm-planner-craftax-ablations",
96
- "WANDB_ENTITY": "null",
97
- "WANDB_DOWNLOAD_DIR": null,
98
  "MAX_ITER": 500,
99
  "BATCH_SIZE": 1024,
100
  "DIFFUSION_STEPS_COLLECT": 5,
@@ -132,7 +127,7 @@
132
  "REWARD_MODEL_LR": 0.001,
133
  "REWARD_MODEL_TRAIN_STEPS": 50,
134
  "NUM_SEEDS": 3,
135
- "CHECKPOINT_PATH": "<path omitted for anonymous review>",
136
  "NUM_ACTIONS": 17
137
  },
138
  "ablations": {
 
26
  },
27
  "merge_provenance": {
28
  "inputs": [
29
+ "gpu_24gb_0/results.json",
30
+ "gpu_24gb_1/results.json",
31
+ "gpu_24gb_2/results.json"
32
  ],
33
+ "config_from": "gpu_24gb_0/results.json"
34
  },
35
  "config": {
36
  "ENV_NAME": "Craftax-Classic-Symbolic-v1",
 
87
  "SAVE_POLICY": true,
88
  "CHECKPOINT_DIR": "checkpoints",
89
  "RESUME_CHECKPOINT_PATH": null,
 
90
  "RESUME_STEP": null,
91
+ "JAX_COMPILATION_CACHE_DIR": null,
92
  "SEED": null,
 
 
 
 
93
  "MAX_ITER": 500,
94
  "BATCH_SIZE": 1024,
95
  "DIFFUSION_STEPS_COLLECT": 5,
 
127
  "REWARD_MODEL_LR": 0.001,
128
  "REWARD_MODEL_TRAIN_STEPS": 50,
129
  "NUM_SEEDS": 3,
130
+ "CHECKPOINT_PATH": "ablation_src/policy-best-v2",
131
  "NUM_ACTIONS": 17
132
  },
133
  "ablations": {