chaddy81 commited on
Commit
0da65ef
·
verified ·
1 Parent(s): 09aa08f

Upload train_grpo_code.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. train_grpo_code.py +58 -56
train_grpo_code.py CHANGED
@@ -26,13 +26,12 @@ import os
26
  import re
27
  import subprocess
28
  import tempfile
29
- import traceback
30
  from typing import Any
31
 
32
  import torch
33
  import trackio
34
  from datasets import load_dataset
35
- from peft import PeftModel
36
  from transformers import AutoModelForCausalLM, AutoTokenizer
37
  from trl import GRPOTrainer, GRPOConfig
38
 
@@ -44,7 +43,7 @@ print("=" * 60)
44
  BASE_MODEL = "Qwen/Qwen3-0.6B"
45
  SFT_ADAPTER = "chaddy81/qwen3-0.6b-multicode-sft"
46
  OUTPUT_REPO = "chaddy81/qwen3-0.6b-multicode-grpo"
47
- MAX_EXAMPLES = 2000 # Limit for reasonable training time
48
 
49
  print(f"\n📦 Configuration:")
50
  print(f" Base model: {BASE_MODEL}")
@@ -68,7 +67,7 @@ def extract_python_code(text: str) -> str:
68
  for pattern in patterns:
69
  matches = re.findall(pattern, text, re.DOTALL)
70
  if matches:
71
- return matches[-1].strip() # Return last code block
72
 
73
  # If no markdown blocks, try to find code after common markers
74
  markers = ["Solution:", "Answer:", "Code:"]
@@ -82,18 +81,8 @@ def extract_python_code(text: str) -> str:
82
  return text.strip()
83
 
84
 
85
- def run_python_code(code: str, stdin_input: str, timeout: float = 5.0) -> tuple[bool, str]:
86
- """
87
- Execute Python code with given input and return (success, output).
88
-
89
- Args:
90
- code: Python source code to execute
91
- stdin_input: Input to pass via stdin
92
- timeout: Maximum execution time in seconds
93
-
94
- Returns:
95
- Tuple of (success: bool, output: str)
96
- """
97
  try:
98
  with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
99
  f.write(code)
@@ -120,7 +109,7 @@ def run_python_code(code: str, stdin_input: str, timeout: float = 5.0) -> tuple[
120
 
121
 
122
  def normalize_output(output: str) -> str:
123
- """Normalize output for comparison (strip whitespace, normalize newlines)."""
124
  return '\n'.join(line.strip() for line in output.strip().split('\n'))
125
 
126
 
@@ -134,29 +123,27 @@ def code_execution_reward(
134
  Reward function that executes generated code against test cases.
135
 
136
  Returns:
137
- - 1.0 if code passes all available tests
138
- - 0.5 if code passes some tests
139
- - 0.0 if code fails all tests or has errors
140
  """
141
  rewards = []
142
 
143
  for completion, tests, exs in zip(completions, official_tests, examples):
144
- # Extract code from completion
145
  code = extract_python_code(completion)
146
 
147
  if not code or len(code) < 10:
148
  rewards.append(0.0)
149
  continue
150
 
151
- # Combine official tests and examples
152
  all_tests = []
153
  if tests:
154
- all_tests.extend(tests[:3]) # Limit to first 3 official tests
155
  if exs:
156
- all_tests.extend(exs[:2]) # Add up to 2 examples
157
 
158
  if not all_tests:
159
- # No tests available, give neutral reward
160
  rewards.append(0.0)
161
  continue
162
 
@@ -168,10 +155,9 @@ def code_execution_reward(
168
  test_input = test.get('input', '')
169
  expected_output = test.get('output', '')
170
 
171
- success, actual_output = run_python_code(code, test_input, timeout=3.0)
172
 
173
  if success:
174
- # Compare outputs (normalized)
175
  if normalize_output(actual_output) == normalize_output(expected_output):
176
  passed += 1
177
 
@@ -200,18 +186,14 @@ dataset = load_dataset(
200
  )
201
  print(f" Total examples: {len(dataset)}")
202
 
203
- # Filter for Python problems only
204
- print(" Filtering for Python problems...")
205
- dataset = dataset.filter(lambda x: x.get('language') == 'python')
206
- print(f" Python problems: {len(dataset)}")
207
-
208
- # Filter for problems with test cases
209
- print(" Filtering for problems with tests...")
210
  dataset = dataset.filter(
211
- lambda x: (x.get('official_tests') and len(x['official_tests']) > 0) or
212
- (x.get('examples') and len(x['examples']) > 0)
 
213
  )
214
- print(f" Problems with tests: {len(dataset)}")
215
 
216
  # Limit dataset size
217
  if len(dataset) > MAX_EXAMPLES:
@@ -222,44 +204,63 @@ print(f"\n✅ Final dataset: {len(dataset)} examples")
222
 
223
 
224
  # ============================================================================
225
- # Model Loading
226
  # ============================================================================
227
 
228
- print("\n🔧 Loading model...")
229
 
230
- # Load base model
231
  print(" Loading base model...")
232
  base_model = AutoModelForCausalLM.from_pretrained(
233
  BASE_MODEL,
234
  torch_dtype=torch.bfloat16,
235
- device_map="auto",
236
  trust_remote_code=True,
237
  )
238
 
239
- # Load SFT adapter
240
  print(" Loading SFT adapter...")
241
  model = PeftModel.from_pretrained(base_model, SFT_ADAPTER)
242
 
243
- # Merge for GRPO (GRPO works better with merged models)
244
- print(" Merging adapter...")
245
  model = model.merge_and_unload()
246
 
 
 
 
 
 
247
  # Load tokenizer
248
  print(" Loading tokenizer...")
249
  tokenizer = AutoTokenizer.from_pretrained(SFT_ADAPTER, trust_remote_code=True)
250
  if tokenizer.pad_token is None:
251
  tokenizer.pad_token = tokenizer.eos_token
252
  tokenizer.padding_side = "left"
 
 
 
 
 
 
253
 
254
- print(" ✅ Model ready")
255
 
256
 
257
  # ============================================================================
258
- # GRPO Training
259
  # ============================================================================
260
 
261
  print("\n⚙️ Configuring GRPO trainer...")
262
 
 
 
 
 
 
 
 
 
 
 
263
  config = GRPOConfig(
264
  # Output & Hub
265
  output_dir="qwen3-grpo-code",
@@ -269,14 +270,14 @@ config = GRPOConfig(
269
  hub_private_repo=False,
270
 
271
  # GRPO parameters
272
- num_generations=4, # Generate 4 completions per prompt
273
- max_completion_length=512, # Max tokens for generated code
274
 
275
  # Training parameters
276
  num_train_epochs=1,
277
- per_device_train_batch_size=2,
278
- gradient_accumulation_steps=4,
279
- learning_rate=1e-6, # Very low LR for GRPO
280
 
281
  # Optimization
282
  warmup_ratio=0.1,
@@ -287,26 +288,27 @@ config = GRPOConfig(
287
  # Logging & checkpoints
288
  logging_steps=10,
289
  save_strategy="steps",
290
- save_steps=100,
291
  save_total_limit=2,
292
 
293
  # Monitoring
294
  report_to="trackio",
295
  project="qwen3-grpo-code",
296
- run_name="grpo-codeforces-v1",
297
  )
298
 
299
- print(" Initializing trainer...")
300
  trainer = GRPOTrainer(
301
- model=model,
302
  processing_class=tokenizer,
303
  reward_funcs=code_execution_reward,
304
  train_dataset=dataset,
305
  args=config,
 
306
  )
307
 
308
  print("\n🚀 Starting GRPO training...")
309
- print(" This will generate code, execute it, and learn from results.")
310
  print("=" * 60)
311
 
312
  trainer.train()
 
26
  import re
27
  import subprocess
28
  import tempfile
 
29
  from typing import Any
30
 
31
  import torch
32
  import trackio
33
  from datasets import load_dataset
34
+ from peft import LoraConfig, PeftModel
35
  from transformers import AutoModelForCausalLM, AutoTokenizer
36
  from trl import GRPOTrainer, GRPOConfig
37
 
 
43
  BASE_MODEL = "Qwen/Qwen3-0.6B"
44
  SFT_ADAPTER = "chaddy81/qwen3-0.6b-multicode-sft"
45
  OUTPUT_REPO = "chaddy81/qwen3-0.6b-multicode-grpo"
46
+ MAX_EXAMPLES = 1000 # Reduced for faster training
47
 
48
  print(f"\n📦 Configuration:")
49
  print(f" Base model: {BASE_MODEL}")
 
67
  for pattern in patterns:
68
  matches = re.findall(pattern, text, re.DOTALL)
69
  if matches:
70
+ return matches[-1].strip()
71
 
72
  # If no markdown blocks, try to find code after common markers
73
  markers = ["Solution:", "Answer:", "Code:"]
 
81
  return text.strip()
82
 
83
 
84
+ def run_python_code(code: str, stdin_input: str, timeout: float = 3.0) -> tuple[bool, str]:
85
+ """Execute Python code with given input and return (success, output)."""
 
 
 
 
 
 
 
 
 
 
86
  try:
87
  with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
88
  f.write(code)
 
109
 
110
 
111
  def normalize_output(output: str) -> str:
112
+ """Normalize output for comparison."""
113
  return '\n'.join(line.strip() for line in output.strip().split('\n'))
114
 
115
 
 
123
  Reward function that executes generated code against test cases.
124
 
125
  Returns:
126
+ - 1.0 if code passes all tests
127
+ - Partial credit for some tests
128
+ - 0.0 if fails all tests
129
  """
130
  rewards = []
131
 
132
  for completion, tests, exs in zip(completions, official_tests, examples):
 
133
  code = extract_python_code(completion)
134
 
135
  if not code or len(code) < 10:
136
  rewards.append(0.0)
137
  continue
138
 
139
+ # Combine tests (limit to avoid long execution)
140
  all_tests = []
141
  if tests:
142
+ all_tests.extend(tests[:2])
143
  if exs:
144
+ all_tests.extend(exs[:2])
145
 
146
  if not all_tests:
 
147
  rewards.append(0.0)
148
  continue
149
 
 
155
  test_input = test.get('input', '')
156
  expected_output = test.get('output', '')
157
 
158
+ success, actual_output = run_python_code(code, test_input, timeout=2.0)
159
 
160
  if success:
 
161
  if normalize_output(actual_output) == normalize_output(expected_output):
162
  passed += 1
163
 
 
186
  )
187
  print(f" Total examples: {len(dataset)}")
188
 
189
+ # Filter for Python problems with tests
190
+ print(" Filtering for Python problems with tests...")
 
 
 
 
 
191
  dataset = dataset.filter(
192
+ lambda x: x.get('language') == 'python' and
193
+ ((x.get('official_tests') and len(x['official_tests']) > 0) or
194
+ (x.get('examples') and len(x['examples']) > 0))
195
  )
196
+ print(f" Filtered: {len(dataset)}")
197
 
198
  # Limit dataset size
199
  if len(dataset) > MAX_EXAMPLES:
 
204
 
205
 
206
  # ============================================================================
207
+ # Model Loading - Merge SFT then save for GRPO
208
  # ============================================================================
209
 
210
+ print("\n🔧 Loading and preparing model...")
211
 
212
+ # Step 1: Load base model and SFT adapter
213
  print(" Loading base model...")
214
  base_model = AutoModelForCausalLM.from_pretrained(
215
  BASE_MODEL,
216
  torch_dtype=torch.bfloat16,
217
+ device_map="cpu",
218
  trust_remote_code=True,
219
  )
220
 
 
221
  print(" Loading SFT adapter...")
222
  model = PeftModel.from_pretrained(base_model, SFT_ADAPTER)
223
 
224
+ print(" Merging SFT adapter into base model...")
 
225
  model = model.merge_and_unload()
226
 
227
+ # Step 2: Save merged model temporarily
228
+ merged_path = "/tmp/merged_sft_model"
229
+ print(f" Saving merged model to {merged_path}...")
230
+ model.save_pretrained(merged_path, safe_serialization=True)
231
+
232
  # Load tokenizer
233
  print(" Loading tokenizer...")
234
  tokenizer = AutoTokenizer.from_pretrained(SFT_ADAPTER, trust_remote_code=True)
235
  if tokenizer.pad_token is None:
236
  tokenizer.pad_token = tokenizer.eos_token
237
  tokenizer.padding_side = "left"
238
+ tokenizer.save_pretrained(merged_path)
239
+
240
+ # Free memory
241
+ del model
242
+ del base_model
243
+ torch.cuda.empty_cache()
244
 
245
+ print(" ✅ Merged model saved")
246
 
247
 
248
  # ============================================================================
249
+ # GRPO Training with fresh LoRA
250
  # ============================================================================
251
 
252
  print("\n⚙️ Configuring GRPO trainer...")
253
 
254
+ # LoRA config for GRPO (smaller rank for efficiency)
255
+ peft_config = LoraConfig(
256
+ r=8,
257
+ lora_alpha=16,
258
+ lora_dropout=0.05,
259
+ target_modules=["q_proj", "k_proj", "v_proj", "o_proj"],
260
+ bias="none",
261
+ task_type="CAUSAL_LM",
262
+ )
263
+
264
  config = GRPOConfig(
265
  # Output & Hub
266
  output_dir="qwen3-grpo-code",
 
270
  hub_private_repo=False,
271
 
272
  # GRPO parameters
273
+ num_generations=4,
274
+ max_completion_length=256, # Shorter for faster training
275
 
276
  # Training parameters
277
  num_train_epochs=1,
278
+ per_device_train_batch_size=1, # Small batch for memory
279
+ gradient_accumulation_steps=8,
280
+ learning_rate=5e-7,
281
 
282
  # Optimization
283
  warmup_ratio=0.1,
 
288
  # Logging & checkpoints
289
  logging_steps=10,
290
  save_strategy="steps",
291
+ save_steps=50,
292
  save_total_limit=2,
293
 
294
  # Monitoring
295
  report_to="trackio",
296
  project="qwen3-grpo-code",
297
+ run_name="grpo-codeforces-v2",
298
  )
299
 
300
+ print(" Initializing trainer with merged SFT model + new LoRA...")
301
  trainer = GRPOTrainer(
302
+ model=merged_path, # Pass path - trainer loads with proper gradients
303
  processing_class=tokenizer,
304
  reward_funcs=code_execution_reward,
305
  train_dataset=dataset,
306
  args=config,
307
+ peft_config=peft_config, # New LoRA for GRPO
308
  )
309
 
310
  print("\n🚀 Starting GRPO training...")
311
+ print(" Training will generate code, execute it, and learn from results.")
312
  print("=" * 60)
313
 
314
  trainer.train()