23f2002275 commited on
Commit
9e58a11
·
1 Parent(s): 916512d

feat(train): flip Path 3 -> Path 1 — production 1.5B GRPO on a100-large

Browse files

Previous job (69ece94a, a10g-largex2) loaded
unsloth/Qwen2.5-Coder-0.5B-Instruct-bnb-4bit because job_train.sh
hardcoded `model=qwen_0_5b_smoke` and never got switched to the
production 1.5B target documented in HACKATHON_FINAL_PLAN.md and
PROJECT.md ("A 1.5B model trained on our env solves 200K-token QA
tasks via recursion"). Submission was about to ship as 0.5B.

This commit switches both the SFT and GRPO blocks to:
model=qwen_1_5b -> unsloth/Qwen2.5-Coder-1.5B-Instruct-bnb-4bit
(LoRA r16, max_seq=16384)
train.max_steps=200 -> enough room for the format-gate-crossing
transition + visible reward climb (50 was a
smoke; previous 50-step run with broken
sys_msg had reward=0 every step).

Memory budget retuned for the bigger model on the bigger GPU:
num_generations 4 -> 8 (back to TRL/STACK default)
max_prompt_length 2048 -> 4096 (full prompt budget)
max_completion_length 512 -> 1024 (long-form answers fit)
vllm_gpu_mem_util 0.35 -> 0.50 (A100-80GB has the headroom)

Hardware: scripts/hf_jobs_helper.py default flavor moves
a10g-largex2 -> a100-large
The CUDA_VISIBLE_DEVICES=0 guardrail stays as a defensive no-op for
single-GPU instances (still trips the multi-rank entropy bug if we
ever fall back to a 2x flavor).

Estimated run: SFT ~12 min + GRPO 200 steps ~75 min = ~90 min total,
~$5-7 on HF Jobs a100-large.

Made-with: Cursor

Files changed (2) hide show
  1. scripts/hf_jobs_helper.py +6 -1
  2. scripts/job_train.sh +23 -19
scripts/hf_jobs_helper.py CHANGED
@@ -75,10 +75,15 @@ def submit() -> None:
75
  'git clone -b main https://oauth2:$HF_TOKEN@huggingface.co/Pratham-math/fathom-code /w && '
76
  'bash /w/scripts/job_train.sh',
77
  ]
 
 
 
 
 
78
  job = api.run_job(
79
  image="pytorch/pytorch:2.6.0-cuda12.4-cudnn9-devel",
80
  command=cmd,
81
- flavor="a10g-largex2",
82
  namespace=USER,
83
  secrets={
84
  "HF_TOKEN": os.environ["HF_TOKEN"],
 
75
  'git clone -b main https://oauth2:$HF_TOKEN@huggingface.co/Pratham-math/fathom-code /w && '
76
  'bash /w/scripts/job_train.sh',
77
  ]
78
+ # a100-large = 1x A100-80GB. Required for the 1.5B production run:
79
+ # 1.5B 4-bit weights + LoRA + grad/optim state + vLLM colocate KV cache
80
+ # for num_generations=8 @ max_completion=1024 needs ~30-40 GB.
81
+ # The previous a10g-largex2 (24 GB usable on 1 GPU) only held 0.5B safely.
82
+ flavor = os.environ.get("HF_JOB_FLAVOR", "a100-large")
83
  job = api.run_job(
84
  image="pytorch/pytorch:2.6.0-cuda12.4-cudnn9-devel",
85
  command=cmd,
86
+ flavor=flavor,
87
  namespace=USER,
88
  secrets={
89
  "HF_TOKEN": os.environ["HF_TOKEN"],
scripts/job_train.sh CHANGED
@@ -5,11 +5,12 @@ set -e
5
  cd /w
6
  export PYTHONPATH="/w:${PYTHONPATH}"
7
 
8
- # Force single-GPU training. The 0.5B (and 1.5B) 4-bit + LoRA model fits on
9
- # one A10G easily, and TRL's SFTTrainer entropy logging path on multi-GPU
10
- # (a10g-largex2) crashes with `RuntimeError: tensor a (2) must match tensor
11
- # b (4) at non-singleton dimension 0` because the per-rank logits get mixed
12
- # with the full-batch attention mask. Hide the second GPU completely.
 
13
  export CUDA_VISIBLE_DEVICES=0
14
 
15
  # Install huggingface_hub first so we can download data files (parity with job_smoke.sh)
@@ -62,7 +63,8 @@ python -c "import urllib.request, sys; sys.exit(0 if urllib.request.urlopen('htt
62
  python -m train.smoke_test --env-url http://localhost:8001
63
  grep -q "VERDICT: GO" outputs/smoke/SMOKE_RESULT.md || (cat outputs/smoke/SMOKE_RESULT.md; exit 1)
64
 
65
- # SFT (Path 30.5B sanity check; switch to qwen_1_5b for Path 1)
 
66
  # NOTE: use initialize_config_dir (absolute path) — heredocs have no caller file,
67
  # so the relative `../configs` resolves against CWD and breaks. Absolute is robust.
68
  python <<'PY'
@@ -70,17 +72,19 @@ from hydra import initialize_config_dir, compose
70
  from train.model_load import load_model_and_tokenizer
71
  from train.sft import run_sft
72
  with initialize_config_dir(config_dir="/w/configs", version_base="1.3"):
73
- cfg = compose(config_name="config", overrides=["model=qwen_0_5b_smoke","train=sft"])
74
  m, t = load_model_and_tokenizer(cfg)
75
  print("SFT adapter:", run_sft(cfg, m, t))
76
  PY
77
 
78
- # GRPO 50 steps (Path 30.5B sanity check; switch to qwen_1_5b + max_steps=400 for Path 1)
79
- # Memory overrides for single-A10G + vLLM colocate at 0.5B:
80
- # - num_generations=4 (was 8) → halves rollout VRAM
81
- # - max_prompt_length=2048 (was 4096) → halves KV cache per prompt
82
- # - max_completion_length=512 (was 2048) quarters generation memory
83
- # - vllm_gpu_memory_utilization=0.35 leaves ~16GB headroom for trainer
 
 
84
  python <<'PY'
85
  from hydra import initialize_config_dir, compose
86
  from train.model_load import load_model_and_tokenizer
@@ -88,13 +92,13 @@ from train.grpo import run_grpo
88
  from rewards.compose import make_reward_fn
89
  with initialize_config_dir(config_dir="/w/configs", version_base="1.3"):
90
  cfg = compose(config_name="config", overrides=[
91
- "model=qwen_0_5b_smoke",
92
  "train=grpo",
93
- "train.max_steps=50",
94
- "train.num_generations=4",
95
- "train.max_prompt_length=2048",
96
- "train.max_completion_length=512",
97
- "train.vllm_gpu_memory_utilization=0.35",
98
  ])
99
  m, t = load_model_and_tokenizer(cfg)
100
  print("GRPO merged:", run_grpo(cfg, m, t, make_reward_fn(cfg.reward), "http://localhost:8001"))
 
5
  cd /w
6
  export PYTHONPATH="/w:${PYTHONPATH}"
7
 
8
+ # Force single-GPU training. On a100-large (1x A100-80GB) this is a no-op,
9
+ # but kept as a defensive guardrail in case we ever fall back to a 2x flavor:
10
+ # TRL's SFTTrainer entropy logging path on multi-GPU crashes with
11
+ # `RuntimeError: tensor a (2) must match tensor b (4) at non-singleton
12
+ # dimension 0` because per-rank logits get mixed with the full-batch
13
+ # attention mask. Hiding extra GPUs completely sidesteps this.
14
  export CUDA_VISIBLE_DEVICES=0
15
 
16
  # Install huggingface_hub first so we can download data files (parity with job_smoke.sh)
 
63
  python -m train.smoke_test --env-url http://localhost:8001
64
  grep -q "VERDICT: GO" outputs/smoke/SMOKE_RESULT.md || (cat outputs/smoke/SMOKE_RESULT.md; exit 1)
65
 
66
+ # SFT (Path 1production 1.5B). qwen_1_5b config points at
67
+ # unsloth/Qwen2.5-Coder-1.5B-Instruct-bnb-4bit per STACK §3.1.
68
  # NOTE: use initialize_config_dir (absolute path) — heredocs have no caller file,
69
  # so the relative `../configs` resolves against CWD and breaks. Absolute is robust.
70
  python <<'PY'
 
72
  from train.model_load import load_model_and_tokenizer
73
  from train.sft import run_sft
74
  with initialize_config_dir(config_dir="/w/configs", version_base="1.3"):
75
+ cfg = compose(config_name="config", overrides=["model=qwen_1_5b","train=sft"])
76
  m, t = load_model_and_tokenizer(cfg)
77
  print("SFT adapter:", run_sft(cfg, m, t))
78
  PY
79
 
80
+ # GRPO 200 steps (Path 1production 1.5B on A100-80GB).
81
+ # Memory budget on a100-large (~80GB):
82
+ # - 1.5B 4-bit + LoRA-r16 weights + optim state ≈ 6-8 GB
83
+ # - vLLM colocate KV cache @ num_generations=8, max_prompt=4096, max_completion=1024 ≈ 25-35 GB
84
+ # - vllm_gpu_memory_utilization=0.50 leaves ~30 GB for trainer + activations
85
+ # Ramping max_steps from 50 200 to give the reward curve room to climb
86
+ # (50 steps was a smoke; 200 is enough to show the gate-crossing transition
87
+ # from format_gate=0 → format_gate=1 → composite reward rising).
88
  python <<'PY'
89
  from hydra import initialize_config_dir, compose
90
  from train.model_load import load_model_and_tokenizer
 
92
  from rewards.compose import make_reward_fn
93
  with initialize_config_dir(config_dir="/w/configs", version_base="1.3"):
94
  cfg = compose(config_name="config", overrides=[
95
+ "model=qwen_1_5b",
96
  "train=grpo",
97
+ "train.max_steps=200",
98
+ "train.num_generations=8",
99
+ "train.max_prompt_length=4096",
100
+ "train.max_completion_length=1024",
101
+ "train.vllm_gpu_memory_utilization=0.50",
102
  ])
103
  m, t = load_model_and_tokenizer(cfg)
104
  print("GRPO merged:", run_grpo(cfg, m, t, make_reward_fn(cfg.reward), "http://localhost:8001"))