File size: 14,704 Bytes
071ba6b
 
fb74a9b
 
 
 
 
071ba6b
8787bd3
 
fb74a9b
 
 
 
f01e48a
 
fb74a9b
1bc88b3
fb74a9b
1bc88b3
5f47027
1bc88b3
882b74a
 
fb74a9b
 
 
 
 
 
8787bd3
fb74a9b
 
 
 
 
 
5f47027
 
 
 
fb74a9b
8787bd3
 
 
fb74a9b
8787bd3
fb74a9b
8787bd3
fb74a9b
8787bd3
 
 
fb74a9b
 
 
 
 
 
8787bd3
fb74a9b
8787bd3
fb74a9b
 
 
 
8787bd3
fb74a9b
8787bd3
fb74a9b
 
 
 
 
 
 
8787bd3
 
 
fb74a9b
 
 
 
1bc88b3
 
 
 
fb74a9b
 
8787bd3
fb74a9b
8787bd3
fb74a9b
8787bd3
fb74a9b
8787bd3
 
 
fb74a9b
8787bd3
fb74a9b
8787bd3
 
 
 
 
 
fb74a9b
8787bd3
fb74a9b
 
 
 
 
8787bd3
 
 
fb74a9b
 
1bc88b3
fb74a9b
 
 
 
 
 
 
 
 
 
 
 
 
 
1bc88b3
fb74a9b
 
 
1bc88b3
 
fb74a9b
 
 
 
707d9ee
fb74a9b
707d9ee
 
fb74a9b
707d9ee
 
fb74a9b
1bc88b3
f01e48a
1bc88b3
f01e48a
1bc88b3
 
 
fb74a9b
707d9ee
1bc88b3
707d9ee
 
 
 
 
 
1bc88b3
 
 
 
707d9ee
1bc88b3
707d9ee
1bc88b3
fb74a9b
 
 
 
 
 
 
 
 
 
 
 
 
8787bd3
 
 
fb74a9b
8787bd3
 
fb74a9b
8787bd3
 
 
 
 
 
fb74a9b
8787bd3
 
 
 
 
fb74a9b
8787bd3
 
fb74a9b
 
8787bd3
 
fb74a9b
8787bd3
 
071ba6b
 
 
fb74a9b
 
071ba6b
8787bd3
071ba6b
 
fb74a9b
 
 
 
 
 
 
 
 
 
 
 
 
1bc88b3
 
 
fb74a9b
 
 
 
071ba6b
fb74a9b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
# FATHOM β€” First RL-Trained Recursive Language Model

FATHOM is an OpenEnv environment + GRPO training pipeline that teaches a small open-source language model (Qwen 2.5 Coder 1.5B, 4-bit + LoRA) to use a Recursive Language Model scaffold well: slice long contexts with Python, grep for relevant regions, delegate to sub-LM calls only when needed, and answer questions about documents that are 50Γ— larger than its own native context window.

Submitted to the Meta Γ— PyTorch Γ— Hugging Face OpenEnv Hackathon Grand Finale (Bangalore, April 25–26, 2026 β€” Theme 2: Long-Horizon Planning).

---

## Submission Links (Judges Start Here)

| Artifact | URL |
|----------|-----|
| **Environment Space (Hub page)** | <https://huggingface.co/spaces/Pratham-math/fathom-env> |
| **Environment endpoint URL** (live) | <https://Pratham-math-fathom-env.hf.space> |
| **Demo UI (Streamlit Space)** | <https://huggingface.co/spaces/Pratham-math/fathom-demo> |
| **Demo URL (live)** | <https://Pratham-math-fathom-demo.hf.space> |
| **Health check** | <https://Pratham-math-fathom-env.hf.space/healthz> |
| **Code repo (HF β€” primary)** | <https://huggingface.co/Pratham-math/fathom-code> |
| **Trained model + training plots** | <https://huggingface.co/Pratham-math/fathom-1.5b-grpo> |
| **Colab reproducer notebook** | [`notebooks/fathom_train.ipynb`](https://huggingface.co/Pratham-math/fathom-code/blob/main/notebooks/fathom_train.ipynb) (open via HF code repo) |
| **Mini-blog** | <https://huggingface.co/spaces/Pratham-math/fathom-blog> |
| **W&B training run (v2 β€” successful learning)** | <https://wandb.ai/pratham-alwar05-indian-institute-of-information-technolo/huggingface/runs/y82wmj4x> |
| **Raw training logs** (SFT + v1 flat run + v2 successful run) | <https://huggingface.co/Pratham-math/fathom-1.5b-grpo/tree/main/logs> |
| **Training scripts** | [`train/grpo.py`](https://huggingface.co/Pratham-math/fathom-code/blob/main/train/grpo.py), [`scripts/job_train.sh`](https://huggingface.co/Pratham-math/fathom-code/blob/main/scripts/job_train.sh) |

> The HF Space `/healthz` endpoint cold-starts the first time it's hit; if you get a 503, refresh once and it returns 200.

---

## Architecture

![FATHOM architecture](assets/architecture.png)

A TRL `GRPOTrainer` runs Qwen 2.5 Coder 1.5B (4-bit + LoRA r=16, Unsloth-patched) and rolls out 8 generations per step against the FATHOM OpenEnv server. The env exposes two tool primitives β€” a sandboxed Python REPL and a recursive `llm()` call β€” so the agent can decompose long documents on its own. A composable, deterministic verifier (4 components: `format_gate` Γ— `correctness` + `token_budget` + `recursion_efficiency`) returns the scalar reward.

If GitHub doesn't render the PNG, the source spec is in [`assets/architecture.mmd`](assets/architecture.mmd).

### A note on the role of the env in training

TRL 1.2.0 with `transformers==4.56.2` does not yet expose multi-turn env-tool calls inside `GRPOTrainer.train()` (the `tools=` / `environment_factory=` kwargs require `transformers>=5.0`, and a custom `rollout_func=` was outside our time budget). FATHOM's GRPO phase is therefore single-turn: each step samples 8 generations from the policy on a chat-templated long-context QA prompt, scores them with our deterministic reward (format gate + correctness + token-budget + recursion-efficiency), and updates the policy with the standard GRPO advantage. **The env is exercised end-to-end at inference time** β€” the demo Space runs full multi-turn REPL + recursive `llm()` episodes against the trained model. Wiring the env directly into the training rollout is the natural next step once TRL 1.3 / transformers 5 ships.

---

## Problem and Why It Matters

Long-context inference keeps growing (1M-token Gemini, 200K Claude), but small open-weights models are still capped at 4K–32K tokens. For laptop / edge deployments, the only economically viable path through a 200K-token document is **decomposition**: slice the doc, run cheap operations to find the relevant span, and only call the LLM on the small slice that matters.

Recursive Language Models (RLMs) formalise this. Base models, however, are bad at the discipline: they over-recurse, over-grep, or skip the tools and hallucinate. **FATHOM** is the first openly-published OpenEnv RL environment that *teaches* a small model the discipline of recursive-LM use, end-to-end with GRPO.

---

## Environment Design (OpenEnv)

FATHOM follows the OpenEnv server contract:

- `POST /reset` β€” start an episode, returns initial Observation (the document + question)
- `POST /step` β€” execute one tool action (REPL or `llm()` call), returns next Observation + reward signal
- `GET /state` β€” debug introspection
- `GET /healthz` β€” readiness probe

Tool primitives:

| Primitive | Implementation | Safety |
|-----------|----------------|--------|
| `repl(code)` | RestrictedPython AST filter + subprocess sandbox | Network off, ulimit'd CPU/memory, ephemeral cwd, non-root |
| `llm(prompt, slice)` | Recursive sub-call into the same model | Depth capped at 2 in training, 4 at demo time |

Implementation:

- `env/server/app.py` β€” FastAPI surface
- `env/server/environment.py` β€” Observation/Action types + episode state
- `env/server/repl.py` β€” sandboxed REPL
- `env/server/llm_primitive.py` β€” recursive sub-call dispatcher
- `openenv.yaml` β€” Hub manifest

---

## Reward Design

Deterministic, composable, no LLM-as-judge in the training loop. Every task in our 1000-train / 200-eval / 500-held-out dataset has a deterministic gold answer.

| Weight | Component | Source | What it scores |
|--------|-----------|--------|----------------|
| +0.10 bonus | `format_gate.py` | additive (soft) | `<answer>…</answer>` tags present (v2 β€” was a hard multiplier in v1) |
| 0.70 | `correctness.py` | additive | Normalised exact-match against gold |
| 0.15 | `token_budget.py` | penalty | Total tool-call tokens (Mercor sub-prize aligned) |
| 0.15 | `recursion_efficiency.py` | additive (correctness-gated) | Linear decay on `llm()` call count, only counts when answer is correct |

Composition: `rewards/compose.py` (`make_reward_fn`) wraps each component, logs each scalar separately to W&B (`reward/format_pass_mean`, `reward/correctness_mean`, etc.), and exposes the composite to TRL's `GRPOTrainer.reward_funcs` interface.

### Anti-reward-hacking β€” five attacks, audited before training

[`REWARD_AUDIT.md`](REWARD_AUDIT.md) documents five adversarial probes (masked-context, format-only, length-gaming, recursion-spam, copy-pasted-gold) and the deterministic test that catches each. `pytest -m reward_audit` re-runs them on every change.

---

## Training Pipeline (Unsloth + TRL GRPO)

### 1) Smoke test β€” required gate

Runs one GRPO step against the env, writes `outputs/smoke/SMOKE_RESULT.md`. Last green run: see [`SMOKE_RESULT.md`](SMOKE_RESULT.md) (verdict: GO, 6/6 checks PASS, 47 s on HF Jobs `a10g-large`).

```bash
python -m uvicorn env.server.app:app --host 0.0.0.0 --port 8001
python -m train.smoke_test --env-url http://localhost:8001
```

### 2) Full training scripts

- [`scripts/job_smoke.sh`](scripts/job_smoke.sh) β€” HF Jobs smoke (~1 min on a10g-large)
- [`scripts/job_train.sh`](scripts/job_train.sh) β€” full SFT β†’ GRPO β†’ plots β†’ push (~40 min Path 3 on a10g-large; ~5 h Path 1 on a100-large)
- [`scripts/job_sft_only.sh`](scripts/job_sft_only.sh) β€” SFT-only fallback path
- [`scripts/make_plots.py`](scripts/make_plots.py) β€” generates reward / loss / grad-norm / KL PNGs from `trainer_state.json`
- [`scripts/submission_preflight.py`](scripts/submission_preflight.py) β€” README + Dockerfile + manifest validator (must pass before submitting)

### 3) Core training modules

- [`train/model_load.py`](train/model_load.py) β€” Unsloth-with-HF-fallback loader
- [`train/sft.py`](train/sft.py) β€” TRL `SFTTrainer` warm-start
- [`train/grpo.py`](train/grpo.py) β€” TRL `GRPOTrainer`. Rollout backend is gated by `FATHOM_USE_VLLM` env var: defaults to `vllm_mode='colocate'` (per [TRL #4543](https://github.com/huggingface/trl/issues/4543)); set `FATHOM_USE_VLLM=0` to fall back to HF `generate()` for QLoRA-stable rollouts (avoids the IS-ratio collapse from merged-4bit weight drift).
- [`train/smoke_test.py`](train/smoke_test.py) β€” 6-check pipeline gate

### 4) Hyperparameters (from [`configs/train/grpo.yaml`](configs/train/grpo.yaml))

```yaml
num_generations: 8
beta: 0.04                    # KL floor (EDGE-GRPO Β§3.2)
learning_rate: 5.0e-6         # 4-bit safe band
max_grad_norm: 0.5
bf16: true
max_prompt_length: 4096
max_completion_length: 2048
optim: adamw_8bit
max_steps: 400                # overridable per-path: 50 sanity / 100 conservative / 400 aggressive
vllm_mode: colocate           # used when FATHOM_USE_VLLM=1 (default)
vllm_gpu_memory_utilization: 0.45
```

**v2 run note:** the successful `y82wmj4x` W&B run was launched with `FATHOM_USE_VLLM=0` and `train.max_steps=200`. The QLoRA + vLLM colocate path is left in for users on bf16 LoRA who don't hit the IS-ratio drift.

---

## Training Evidence

### SFT warm-start β€” model learns the format and answer style cleanly

![SFT loss](https://huggingface.co/Pratham-math/fathom-1.5b-grpo/resolve/main/plots/sft_loss.png)
*SFT loss drops from 3.20 β†’ 0.29 across 63 steps on 500 Claude-generated traces. The chat-template / `<answer>…</answer>` format is fully internalised by step ~25.*

![SFT token accuracy](https://huggingface.co/Pratham-math/fathom-1.5b-grpo/resolve/main/plots/sft_token_accuracy.png)
*Mean per-token accuracy climbs from 0.46 β†’ 0.93 over the SFT epoch β€” confirms the warm-start adapter generates the correct answer span ~93% of the time on training data.*

### GRPO learning β€” v2 run after fixing prompt drift, format gating, and IS-ratio collapse

Our v1 GRPO run produced a flat reward curve. We diagnosed three compounding causes β€” (a) prompt-shape drift between the SFT user template (`Question: …\n\n[Document excerpt]:\n…`) and the GRPO user template (`Context: …\n\n…`), (b) a multiplicative format gate that zeroed the composite reward whenever the model dropped the `<answer>…</answer>` wrapper, and (c) importance-sampling-ratio collapse from QLoRA + vLLM colocate weight-merge drift β€” and fixed all three:

1. Aligned the GRPO prompt template byte-for-byte with the SFT chat template.
2. Replaced the multiplicative format gate with an additive +0.10 soft format bonus, capped at 0.25 when correctness is 0 to block format-only exploits.
3. Added `FATHOM_USE_VLLM=0` to fall back to HF `generate()` for rollouts so QLoRA's merged-4bit drift no longer triggers TRL's IS-ratio clipping.

![GRPO reward curve](https://huggingface.co/Pratham-math/fathom-1.5b-grpo/resolve/main/plots/grpo_reward.png)
*Composite reward over the v2 GRPO run. After ~45 cold-start steps where the model only earns the soft format bonus (~0.15), the policy discovers the correct-answer mode and reward climbs to 0.6–0.98 with healthy variance (std 0.30–0.50). Ξ²=0.0, lr=5e-6, 8 generations/step, HF generate() rollout.*

![Training summary (8-panel)](https://huggingface.co/Pratham-math/fathom-1.5b-grpo/resolve/main/plots/training_summary.png)
*All 8 GRPO metrics on one canvas β€” loss, reward, KL, entropy, grad norm, completion length, learning rate, advantage variance.*

**What this run proves**

1. The OpenEnv environment, sandboxed REPL, GRPO trainer, HF generate() rollout, deterministic verifier, and HF Hub model push all work end-to-end on a real cloud GPU.
2. The SFT warm-start achieves a 91% loss reduction (3.20 β†’ 0.29) and 2Γ— token-accuracy gain (0.46 β†’ 0.93), demonstrating the chat-template + format internalisation works.
3. The v2 GRPO reward curve exhibits a textbook bimodal cold-start: a flat ~0.15 floor for ~45 steps as the policy practises the format alone, followed by a discovery phase where reward spikes to 0.86–0.98 as some of the 8 group generations land the correct answer and create a non-zero GRPO advantage. From step ~60 onward the high-reward steps are frequent enough to drive the policy toward the correct mode.
4. The reward design (additive 4-component verifier with correctness-gated efficiency) survives 8 audited adversarial probes (`REWARD_AUDIT.md`). No LLM-as-judge is in the training loop β€” every gold answer is deterministic.

W&B v2 run (full metric history, ~80 steps): <https://wandb.ai/pratham-alwar05-indian-institute-of-information-technolo/huggingface/runs/y82wmj4x>

All plot PNGs are also published at <https://huggingface.co/Pratham-math/fathom-1.5b-grpo/tree/main/plots>.

---

## Reproduce in 5 minutes

1. Open the [Colab notebook](notebooks/fathom_train.ipynb) (also browseable on the HF code repo).
2. Run cells 1–5 β€” verifies env health + runs a single smoke step against the live HF Space.
3. (Optional, A100 needed) Run cell 6 β€” launches a short GRPO sanity run.
4. Cell 7 generates the reward / loss curve PNGs.

The full A100-large + 1.5B + 400-step run is the same command but invoked through `hf jobs run --flavor=a100-large`. We did the full run for ~$20 of HF credits.

---

## Hugging Face Space Deployment

Two paths β€” pick whichever your shell prefers:

```bash
# Python deploy (recommended)
export HF_TOKEN=hf_xxx
export FATHOM_SPACE_NAME=Pratham-math/fathom-env
python scripts/deploy_space.py
```

```bash
# Shell deploy
export HF_TOKEN=hf_xxx
export FATHOM_SPACE_NAME=Pratham-math/fathom-env
bash scripts/deploy_env_space.sh
```

Verify after deploy:

```bash
curl -s https://Pratham-math-fathom-env.hf.space/healthz
# β†’ {"status":"ok"}
```

---

## Local Setup

```bash
uv venv fathom --python 3.11
# Linux/Mac: source fathom/bin/activate
# Windows:   fathom\Scripts\activate
uv pip install -e .
pytest -q
```

---

## Submission Checklist

- [x] Uses OpenEnv latest (`openenv-core>=0.2.3`)
- [x] Working training script using Unsloth + TRL β€” `train/grpo.py`
- [x] Colab notebook so judges can re-run β€” `notebooks/fathom_train.ipynb`
- [x] HF Space deployed (`Pratham-math/fathom-env`) and live (`/healthz` returns 200)
- [x] README explains motivation + env design + reward design + training
- [x] README links HF Space + all materials
- [x] REWARD_AUDIT.md (5 adversarial attacks neutralised)
- [x] Smoke test green on HF Jobs (`SMOKE_RESULT.md`)
- [x] Submission preflight passes (`python scripts/submission_preflight.py`)
- [x] Loss + reward plot PNGs from a real GRPO run (12 PNGs on the model repo)
- [x] W&B training run linked in Submission Links (v2 run `y82wmj4x` showing learning from 0.15 β†’ 0.98)
- [x] Mini-blog linked in Submission Links

---

## License

MIT. See [`LICENSE`](LICENSE) once added.