SaylorTwift HF Staff commited on
Commit
e7168a9
·
verified ·
1 Parent(s): 75a9ca0

Resilient worker (transient CaptureContractError -> unscorable, systematic still fatal), resume-from-repo, eval worker base-cells download fix

Browse files
Files changed (3) hide show
  1. eval_watch.py +4 -0
  2. run_job.sh +28 -0
  3. train_lora.py +47 -3
eval_watch.py CHANGED
@@ -75,6 +75,10 @@ def main():
75
  print("[watch] evaluating base model first", flush=True)
76
  run_eval(BASE_MODEL, "/tmp/base_cells.json")
77
  upload("/tmp/base_cells.json", "eval/base_cells.json")
 
 
 
 
78
 
79
  summary = {}
80
  if "eval/summary.json" in fs:
 
75
  print("[watch] evaluating base model first", flush=True)
76
  run_eval(BASE_MODEL, "/tmp/base_cells.json")
77
  upload("/tmp/base_cells.json", "eval/base_cells.json")
78
+ elif not Path("/tmp/base_cells.json").exists():
79
+ # A previous worker already computed base cells: reuse them locally.
80
+ local = api.hf_hub_download(REPO, "eval/base_cells.json")
81
+ Path("/tmp/base_cells.json").write_text(Path(local).read_text())
82
 
83
  summary = {}
84
  if "eval/summary.json" in fs:
run_job.sh CHANGED
@@ -126,11 +126,39 @@ for name, url in [("vllm", "http://127.0.0.1:8001/v1/models"),
126
  raise SystemExit(f"{name} server never became ready")
127
  EOF
128
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
129
  echo "=== [train] GPU 0, MAX_STEPS=${MAX_STEPS:-1000} ==="
130
  CUDA_VISIBLE_DEVICES=0 \
131
  VLLM_URL=http://127.0.0.1:8001 \
132
  ENV_URL=http://127.0.0.1:8000 \
133
  TRAIN_SPLIT=/app/prepared/datasets/train \
 
134
  python3 train_lora.py
135
 
136
  if [ "${SKIP_EVAL:-0}" != "1" ] && [ -n "${ADAPTER_REPO:-}" ]; then
 
126
  raise SystemExit(f"{name} server never became ready")
127
  EOF
128
 
129
+ echo "=== [resume] latest pushed checkpoint, if any ==="
130
+ # Optimizer shards are not uploaded, so resume restores the policy, scheduler,
131
+ # task position (rollout_state.json) and RNG of the sampler — with a fresh
132
+ # optimizer state. Close enough for RL.
133
+ RESUME_FROM=""
134
+ if [ -n "${ADAPTER_REPO:-}" ]; then
135
+ RESUME_FROM=$(python3 - <<'EOF'
136
+ import os, re
137
+ from huggingface_hub import HfApi, snapshot_download
138
+
139
+ repo = os.environ.get("ADAPTER_REPO", "")
140
+ try:
141
+ files = HfApi().list_repo_files(repo)
142
+ except Exception:
143
+ files = []
144
+ ckpts = sorted(
145
+ {f.split("/")[0] for f in files if f.startswith("checkpoint-") and f.endswith("adapter_config.json")},
146
+ key=lambda s: int(s.rsplit("-", 1)[1]),
147
+ )
148
+ if ckpts:
149
+ path = snapshot_download(repo, allow_patterns=[f"{ckpts[-1]}/*"])
150
+ print(os.path.join(path, ckpts[-1]))
151
+ EOF
152
+ )
153
+ fi
154
+ echo "RESUME_FROM=${RESUME_FROM:-<fresh start>}"
155
+
156
  echo "=== [train] GPU 0, MAX_STEPS=${MAX_STEPS:-1000} ==="
157
  CUDA_VISIBLE_DEVICES=0 \
158
  VLLM_URL=http://127.0.0.1:8001 \
159
  ENV_URL=http://127.0.0.1:8000 \
160
  TRAIN_SPLIT=/app/prepared/datasets/train \
161
+ RESUME_FROM="$RESUME_FROM" \
162
  python3 train_lora.py
163
 
164
  if [ "${SKIP_EVAL:-0}" != "1" ] && [ -n "${ADAPTER_REPO:-}" ]; then
train_lora.py CHANGED
@@ -27,7 +27,11 @@ from peft import LoraConfig
27
  from transformers import AutoTokenizer, TrainerCallback
28
  from trl.experimental.async_grpo.async_grpo_config import AsyncGRPOConfig
29
  from trl.experimental.async_grpo.async_grpo_trainer import AsyncGRPOTrainer
30
- from trl.experimental.async_grpo.openenv_harness import HarnessRolloutWorker
 
 
 
 
31
 
32
  from harbor_env.harness import HarborSessionFactory # via .pth to OpenEnv/envs
33
 
@@ -84,6 +88,43 @@ class UploadCheckpointsCallback(TrainerCallback):
84
  return control
85
 
86
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  class HeartbeatCallback(TrainerCallback):
88
  """Touch a file at every optimizer step; run_job.sh's watchdog kills the
89
  job if it goes stale. The first full run stalled silently for 13h when the
@@ -124,7 +165,7 @@ def make_factory():
124
 
125
 
126
  def make_rollout_worker(dataset, tokenizer):
127
- return HarnessRolloutWorker(
128
  harness_session_factory=make_factory(),
129
  harness_adapter=None, # let the harness own the loop (black-box capture)
130
  rollout_reward_fn=correctness_plus_efficiency,
@@ -205,7 +246,10 @@ def main():
205
  peft_config=peft_config,
206
  callbacks=[UploadCheckpointsCallback(ADAPTER_REPO), HeartbeatCallback()],
207
  )
208
- trainer.train()
 
 
 
209
  trainer.save_model("./checkpoints/final") # adapter_model.safetensors + adapter_config.json
210
  if ADAPTER_REPO:
211
  trainer.push_to_hub()
 
27
  from transformers import AutoTokenizer, TrainerCallback
28
  from trl.experimental.async_grpo.async_grpo_config import AsyncGRPOConfig
29
  from trl.experimental.async_grpo.async_grpo_trainer import AsyncGRPOTrainer
30
+ from trl.experimental.async_grpo.openenv_harness import (
31
+ CaptureContractError,
32
+ HarnessRolloutWorker,
33
+ _HarnessRolloutLoop,
34
+ )
35
 
36
  from harbor_env.harness import HarborSessionFactory # via .pth to OpenEnv/envs
37
 
 
88
  return control
89
 
90
 
91
+ class ResilientHarnessRolloutLoop(_HarnessRolloutLoop):
92
+ """Score a contract-violating capture as unscorable instead of killing the worker.
93
+
94
+ TRL makes CaptureContractError fatal: never train on a corrupted capture.
95
+ Right in principle — but in practice the corruption source is transient infra
96
+ (a dropped websocket between worker and env server), and one bad capture used
97
+ to kill a 25h run at step 551. Dropping the rollout keeps bad data out of
98
+ training just the same. To keep the safety property for SYSTEMATIC corruption
99
+ (e.g. every capture broken, as in the 401 incident), re-raise after a full
100
+ two groups' worth of consecutive contract errors.
101
+ """
102
+
103
+ MAX_CONSECUTIVE_CONTRACT_ERRORS = 16
104
+
105
+ def __init__(self, *args, **kwargs):
106
+ super().__init__(*args, **kwargs)
107
+ self._contract_errors = 0
108
+
109
+ def _run_session(self, prompt, group_id=0):
110
+ import logging
111
+
112
+ try:
113
+ result = super()._run_session(prompt, group_id)
114
+ except CaptureContractError as exc:
115
+ self._contract_errors += 1
116
+ if self._contract_errors >= self.MAX_CONSECUTIVE_CONTRACT_ERRORS:
117
+ raise
118
+ logging.warning("capture contract violation; scoring unscorable (%s)", exc)
119
+ return self._EMPTY_ROLLOUT, None
120
+ self._contract_errors = 0
121
+ return result
122
+
123
+
124
+ class ResilientHarnessRolloutWorker(HarnessRolloutWorker):
125
+ _loop_cls = ResilientHarnessRolloutLoop
126
+
127
+
128
  class HeartbeatCallback(TrainerCallback):
129
  """Touch a file at every optimizer step; run_job.sh's watchdog kills the
130
  job if it goes stale. The first full run stalled silently for 13h when the
 
165
 
166
 
167
  def make_rollout_worker(dataset, tokenizer):
168
+ return ResilientHarnessRolloutWorker(
169
  harness_session_factory=make_factory(),
170
  harness_adapter=None, # let the harness own the loop (black-box capture)
171
  rollout_reward_fn=correctness_plus_efficiency,
 
246
  peft_config=peft_config,
247
  callbacks=[UploadCheckpointsCallback(ADAPTER_REPO), HeartbeatCallback()],
248
  )
249
+ resume = os.environ.get("RESUME_FROM", "") or None
250
+ if resume:
251
+ print(f"resuming from {resume}")
252
+ trainer.train(resume_from_checkpoint=resume)
253
  trainer.save_model("./checkpoints/final") # adapter_model.safetensors + adapter_config.json
254
  if ADAPTER_REPO:
255
  trainer.push_to_hub()