File size: 6,026 Bytes
60b21d3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
# Planting GRPO on OpenTSLM — runbook

Goal: (A) train OpenTSLM on **our** faithful rationales and compare to their baseline
rationales (downstream SFT experiment), then (B) add a **GRPO** stage with the dual
answer+faithfulness reward on top of the SFT model.

Everything here is GPU-blocked at *run* time (all 8 GPUs busy with ECG agent2 + other
users). The code/setup below is done; launch when a GPU frees.

---

## 0. Environment (no GPU needed to install)

Their stack: Python 3.12, torch ≥2.9.1, transformers ≥4.57, peft, open-flamingo, wfdb.
Keep it separate from our agent pipeline venv (which is 3.13).

```bash
cd ~/Adinath/TimeAgent/OpenTSLM
python3.12 -m venv .venv_otslm
source .venv_otslm/bin/activate
pip install --upgrade pip
pip install -r requirements.txt        # installs torch 2.9.x + open-flamingo + -e .
# HF access for the base LLM:
huggingface-cli login                  # needs a token with Llama-3.2-1B access,
                                        # OR use the open google/gemma-3-270m instead
```

Decision — base LLM: `meta-llama/Llama-3.2-1B` (default, gated) vs `google/gemma-3-270m`
(open, smaller, faster to iterate). Recommend gemma-3-270m for the first end-to-end
smoke run, then Llama-3.2-1B for the paper numbers.

---

## A. Train on our faithful data (SFT: ours vs baseline)

OpenTSLM's HAR loader reads `src/data/har_cot/har_cot_{train,val,test}_cot.csv`
with columns `x_axis,y_axis,z_axis,label,rationale`. `compute_loss` teacher-forces on
`rationale`, so swapping that column is the whole intervention.

1. Let their loader download the originals once (so we have the time series + labels):
   ```bash
   python -c "from opentslm.time_series_datasets.har_cot.har_cot_loader import ensure_har_cot_dataset; ensure_har_cot_dataset()"
   ```
2. Inject our rationales (our faithful HAR CoT lives on anviksha:
   `faithful_har_cot_{train,val,test}.csv`):
   ```bash
   for sp in train val test; do
     python grpo/inject_faithful_data.py \
       --orig src/data/har_cot/har_cot_${sp}_cot.csv \
       --ours /path/to/faithful_har_cot_${sp}.csv \
       --ours-rationale-col our_rationale \
       --out  src/data/har_cot/har_cot_${sp}_cot.csv --backup
   done
   ```
   (`--backup` keeps `*.orig.csv` so you can train the baseline arm too.)
3. Train each arm and compare test macro-F1:
   ```bash
   # ours
   python curriculum_learning.py --model OpenTSLMSP --stages stage3_cot --llm_id google/gemma-3-270m
   # baseline: restore *.orig.csv into place, retrain into a different results dir
   ```
   Results land under `results/<llm_id_safe>/OpenTSLMSP/stage3_cot/`.

The same pattern applies to Sleep (`stage4_sleep_cot`) and ECG (`stage5_ecg_cot`) once
those faithful CSVs are final; ECG uses `--key sample_id` in the injector.

---

## B. Add the GRPO stage (`stage6_grpo`)

The pieces are in `grpo/`:
- `reward.py` — `dual_reward()` = 0.7·answer + 0.3·faithfulness (HAR scorer ready;
  add ECG/Sleep/WESAD scorers from `codebase/agent3_*.py` patterns).
- `grpo_trainer.py``GRPOTrainer.grpo_loss(batch)`: samples N rollouts via
  `model.generate`, scores them, group-normalizes advantages, returns a PG loss
  (+ optional KL to a frozen SFT reference).

### Three wiring edits in `curriculum_learning.py`

1. **Carry reward fields through the dataset.** The reward needs the gold label and the
   signal facts per sample. In the GRPO dataset, attach to each item:
   - `item["gold_label"]` = the activity/stage/answer label (already available pre-rationale),
   - `item["facts"]` = the Stage-1 facts dict for that sample (load `signal_facts_*.json`
     keyed by sample_id; add a `sample_id` column to the injected CSVs to join).
   Build a `reward_fn(completion, item)` closure:
   ```python
   from grpo.reward import dual_reward, HAR_SCORER
   reward_fn = lambda c, it: dual_reward(c, it["gold_label"], it["facts"], HAR_SCORER)
   ```

2. **Swap the train step.** In `_train_stage`'s inner loop (currently L1114–1121):
   ```python
   # SFT:
   #   optimizer.zero_grad(); loss = model.compute_loss(batch)
   #   loss.backward(); optimizer.step()
   # GRPO:
   optimizer.zero_grad()
   loss, stats = grpo.grpo_loss(batch)        # grpo = GRPOTrainer(model, reward_fn, cfg, ref_model)
   loss.backward()
   clip_grad_norm_(model.parameters(), 1.0)
   optimizer.step()
   ```
   Gate this on `stage_name == "stage6_grpo"` so SFT stages are untouched. Use a smaller
   LR (e.g. 1e-6–5e-6) and a smaller batch (rollouts multiply cost by N).

3. **Register the stage.** Add `"stage6_grpo"` to `CURRICULUM_STAGES` and a `stage6_grpo`
   method mirroring `stage3_cot` (same HAR dataset, but it must start from the completed
   `stage3_cot` checkpoint — GRPO needs a competent SFT policy first). Initialize
   `ref_model` as a frozen clone of that checkpoint for the KL term (start with
   `kl_coef=0.0`, raise if the policy degenerates).

### GRPO config starting point
```python
from grpo.grpo_trainer import GRPOConfig
GRPOConfig(num_rollouts=8, max_new_tokens=400, temperature=1.0,
           kl_coef=0.0, w_answer=0.7, w_faith=0.3)
```

---

## Order of operations (when a GPU frees)
1. Set up `.venv_otslm`, HF login, smoke-test with `demo/huggingface/03_test_hf_har_cot.py`.
2. SFT arm A (ours) + arm B (baseline) on `stage3_cot`; record test macro-F1 → paper §Downstream.
3. From arm A's checkpoint, run `stage6_grpo`; track `reward_mean / answer_reward /
   faith_reward` from `grpo_loss` stats; evaluate faithfulness + accuracy vs the SFT model.
4. Repeat for Sleep/ECG once their faithful data is final.

## Open risks to watch
- `model.generate` must accept `num_return_sequences`/`do_sample` (they pass `**kwargs`
  to `self.llm.generate`, so it should — verify on the smoke run).
- Rollout cost = N × generate per sample; keep batch small and consider fewer rollouts
  (4–6) for the large splits.
- Reward needs per-sample facts; the cleanest join is a `sample_id` column threaded from
  Stage-1 facts → injected CSV → dataset item.
```