Harsh-9209 commited on
Commit
210898f
·
verified ·
1 Parent(s): 80fd7a4

Remove hf_space_a100 directory - training dashboard moved to separate space

Browse files
hf_space_a100/.python-version DELETED
@@ -1 +0,0 @@
1
- 3.11
 
 
hf_space_a100/Dockerfile DELETED
@@ -1,56 +0,0 @@
1
- FROM python:3.11-slim
2
-
3
- # Install system dependencies
4
- RUN apt-get update && apt-get install -y \
5
- git \
6
- build-essential \
7
- && rm -rf /var/lib/apt/lists/*
8
-
9
- # Set up a new user named "user" with user ID 1000
10
- RUN useradd -m -u 1000 user
11
- USER user
12
- ENV HOME=/home/user \
13
- PATH=/home/user/.local/bin:$PATH
14
- WORKDIR $HOME/app
15
-
16
- # 1. Install the entire "Gold Standard" ML stack in one go.
17
- # This includes all vision/audio/quantization/acceleration tools to prevent ANY further 'ModuleNotFound' errors.
18
- RUN pip install --no-cache-dir \
19
- "torch==2.5.0" \
20
- "torchvision==0.20.0" \
21
- "torchaudio==2.5.0" \
22
- "triton==3.1.0" \
23
- "xformers==0.0.28.post2" \
24
- "accelerate" \
25
- "peft" \
26
- "bitsandbytes" \
27
- "sentencepiece" \
28
- "protobuf" \
29
- "transformers>=4.46.0" \
30
- "trl>=0.12.0" \
31
- "datasets" \
32
- "huggingface_hub" \
33
- "matplotlib" \
34
- "scipy" \
35
- --extra-index-url https://download.pytorch.org/whl/cu121
36
-
37
- # 2. Install Unsloth components (must be after torch stack)
38
- RUN pip install --no-cache-dir \
39
- "unsloth_zoo @ git+https://github.com/unslothai/unsloth-zoo.git" \
40
- "unsloth @ git+https://github.com/unslothai/unsloth.git"
41
-
42
- # 3. CRITICAL: Uninstall torchao completely to bypass the 'int1' bug
43
- RUN pip uninstall -y torchao
44
-
45
- # 4. Install Gradio and remaining UI needs
46
- COPY --chown=user requirements.txt $HOME/app/requirements.txt
47
- RUN pip install --no-cache-dir -r $HOME/app/requirements.txt
48
-
49
- # Copy application files
50
- COPY --chown=user . $HOME/app
51
-
52
- # Expose port
53
- EXPOSE 7860
54
-
55
- # Start the application
56
- CMD ["python", "app.py"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
hf_space_a100/README.md DELETED
@@ -1,13 +0,0 @@
1
- ---
2
- title: ConflictBench GRPO (A100)
3
- emoji: ⚔️
4
- colorFrom: indigo
5
- colorTo: purple
6
- sdk: docker
7
- pinned: false
8
- ---
9
-
10
- # ConflictBench GRPO Training Pipeline
11
-
12
- One-click automated GRPO training pipeline tailored for an **NVIDIA A100** GPU.
13
- This pipeline automatically clones the ConflictBench environment, generates scenarios based on a defined curriculum, runs GRPO fine-tuning using Unsloth, generates loss/reward plots, and pushes the final LoRA weights to the Hugging Face Hub.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
hf_space_a100/app.py DELETED
@@ -1,117 +0,0 @@
1
- import gradio as gr
2
- import threading
3
- import time
4
- import queue
5
- import os
6
-
7
- LOG_QUEUE = queue.Queue()
8
- TRAINING_ACTIVE = False
9
- PLOT_PATHS = []
10
-
11
- def log_callback(msg):
12
- LOG_QUEUE.put(f"[{time.strftime('%H:%M:%S')}] {msg}")
13
-
14
- def start_training():
15
- global TRAINING_ACTIVE, PLOT_PATHS
16
- if TRAINING_ACTIVE:
17
- yield "⚠️ Training already running...", *[None]*4
18
- return
19
-
20
- TRAINING_ACTIVE = True
21
- PLOT_PATHS.clear()
22
-
23
- def run():
24
- try:
25
- from train_script import run_training
26
- paths = run_training(progress_callback=log_callback)
27
- PLOT_PATHS.extend(paths or [])
28
- log_callback("🎉 Done! Check plots below.")
29
- except Exception as e:
30
- log_callback(f"❌ Training failed: {e}")
31
- import traceback
32
- log_callback(traceback.format_exc())
33
- finally:
34
- global TRAINING_ACTIVE
35
- TRAINING_ACTIVE = False
36
- LOG_QUEUE.put("<<FINISHED>>")
37
-
38
- thread = threading.Thread(target=run, daemon=True)
39
- thread.start()
40
-
41
- logs = []
42
- while thread.is_alive() or not LOG_QUEUE.empty():
43
- try:
44
- msg = LOG_QUEUE.get(timeout=1.0)
45
- if msg == "<<FINISHED>>":
46
- continue
47
- logs.append(msg)
48
- if len(logs) > 300:
49
- logs = logs[-300:]
50
-
51
- # Prepare UI outputs
52
- current_log_text = "\n".join(logs)
53
- imgs = [p if os.path.exists(p) else None for p in PLOT_PATHS[:4]]
54
- while len(imgs) < 4:
55
- imgs.append(None)
56
-
57
- yield current_log_text, *imgs
58
- except queue.Empty:
59
- # Yield current state even if no new messages
60
- current_log_text = "\n".join(logs)
61
- imgs = [p if os.path.exists(p) else None for p in PLOT_PATHS[:4]]
62
- while len(imgs) < 4:
63
- imgs.append(None)
64
- yield current_log_text, *imgs
65
-
66
- CSS = """
67
- .main-title { text-align: center; margin-bottom: 0.5em; }
68
- .log-box textarea { font-family: 'JetBrains Mono', 'Fira Code', monospace !important;
69
- font-size: 12px !important; background: #0d1117 !important;
70
- color: #c9d1d9 !important; }
71
- """
72
-
73
- with gr.Blocks(title="ConflictBench GRPO Trainer (A100)") as demo:
74
- gr.Markdown("# ⚔️ ConflictBench — GRPO Training Dashboard (A100 Target)", elem_classes="main-title")
75
- gr.Markdown("**One-click** production GRPO training script mapped to Run 2 parameters. "
76
- "Automatically streams logs and generates plots.")
77
-
78
- with gr.Row():
79
- with gr.Column(scale=1):
80
- gr.Markdown("### ⚙️ Run 2 Configuration")
81
- gr.Markdown(f"""
82
- | Parameter | Value |
83
- |-----------|-------|
84
- | Model | Qwen2.5-3B-Instruct |
85
- | Scenarios | 600 train / 30 eval |
86
- | Curriculum | 60% Diff-1 / 40% Diff-2 |
87
- | Global Batch | 8 (1 * 8 accum) |
88
- | Generations | 8 |
89
- | Max Output | 768 tokens |
90
- | LoRA rank | 16 |
91
- | Epochs | 3 |
92
- | β (KL) | 0.04 |
93
- | LR | 3e-6 |
94
- """)
95
- start_btn = gr.Button("🚀 Start Training", variant="primary", size="lg")
96
-
97
- with gr.Column(scale=2):
98
- gr.Markdown("### 📋 Live Training Logs")
99
- log_box = gr.Textbox(label="", lines=25, max_lines=25, interactive=False,
100
- elem_classes="log-box")
101
-
102
- gr.Markdown("---")
103
- gr.Markdown("### 📊 Training Plots")
104
- with gr.Row():
105
- plot1 = gr.Image(label="Reward Curve", type="filepath")
106
- plot2 = gr.Image(label="Loss Curve", type="filepath")
107
- with gr.Row():
108
- plot3 = gr.Image(label="KL Divergence", type="filepath")
109
- plot4 = gr.Image(label="Training Dashboard", type="filepath")
110
-
111
- # Start training and stream outputs to logs and plots
112
- start_btn.click(
113
- fn=start_training,
114
- outputs=[log_box, plot1, plot2, plot3, plot4]
115
- )
116
-
117
- demo.launch(server_name="0.0.0.0", server_port=7860, theme=gr.themes.Soft(), css=CSS, ssr_mode=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
hf_space_a100/requirements.txt DELETED
@@ -1,6 +0,0 @@
1
- gradio>=4.0.0
2
- transformers
3
- trl
4
- datasets
5
- huggingface_hub
6
- matplotlib
 
 
 
 
 
 
 
hf_space_a100/train_script.py DELETED
@@ -1,362 +0,0 @@
1
- """
2
- ConflictBench — GRPO Training Script for HF Space (A100 Target)
3
- Tailored for NVIDIA A100 (48GB VRAM)
4
- """
5
-
6
- import os
7
- import sys
8
- from unsloth import FastLanguageModel # Must be before transformers
9
- import json
10
- import random
11
- import logging
12
- import time
13
- import subprocess
14
- import shutil
15
- from pathlib import Path
16
- from typing import List, Dict, Any, Tuple
17
- from datetime import datetime
18
-
19
- import torch
20
- from datasets import Dataset
21
- from transformers import TrainerCallback
22
-
23
- # Setup Paths
24
- REPO_URL = "https://github.com/Harsh-4210/Conflict_Bench.git"
25
- REPO_DIR = Path("/tmp/conflictbench_repo")
26
- OUTPUT_DIR = Path("./grpo-out")
27
- PLOTS_DIR = OUTPUT_DIR / "plots"
28
-
29
- logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
30
- log = logging.getLogger("train")
31
-
32
- def clone_repo():
33
- """Clone the ConflictBench repo if not already present."""
34
- if REPO_DIR.exists():
35
- log.info("Repo already cloned, pulling latest...")
36
- try:
37
- subprocess.run(["git", "-C", str(REPO_DIR), "pull"], check=True)
38
- except:
39
- log.warning("Pull failed, deleting and re-cloning...")
40
- shutil.rmtree(REPO_DIR)
41
- subprocess.run(["git", "clone", REPO_URL, str(REPO_DIR)], check=True)
42
- else:
43
- log.info(f"Cloning {REPO_URL}...")
44
- subprocess.run(["git", "clone", REPO_URL, str(REPO_DIR)], check=True)
45
-
46
- if str(REPO_DIR) not in sys.path:
47
- sys.path.insert(0, str(REPO_DIR))
48
- log.info("✅ Repo ready")
49
-
50
- # ── Config (Run 2 Parameters) ────────────────────────────────────────────────
51
-
52
- TRAIN_SCENARIOS = 300 # ↓ was 600
53
- EVAL_SCENARIOS = 10 # ↓ was 30
54
- SEED = 42
55
-
56
- DIFFICULTY_TRAIN_WEIGHTS = {1: 0.60, 2: 0.40}
57
- DIFFICULTY_EVAL_WEIGHTS = {1: 0.40, 2: 0.40, 3: 0.20}
58
-
59
- NUM_EPOCHS = 2 # ↓ was 3
60
- BATCH_SIZE = 1
61
- GRADIENT_ACCUM = 4 # ↓ was 8
62
- NUM_GENERATIONS = 4 # ↓ was 8 ← BIGGEST SPEED WIN
63
- MAX_NEW_TOKENS = 512 # ↓ was 768
64
- MAX_PROMPT_CHARS = 2800
65
- LEARNING_RATE = 3e-6
66
- WARMUP_STEPS = 20
67
- BETA = 0.04
68
- TEMPERATURE = 0.85
69
- TOP_P = 0.92
70
-
71
- HF_REPO_ID = os.getenv("HF_REPO_ID", None)
72
- HF_TOKEN = os.getenv("HF_TOKEN", None)
73
-
74
- SYSTEM_PROMPT = """You are an expert business operations coordinator.
75
- Your task: given a set of business instructions from various stakeholders, identify ALL conflicts and produce a resolution plan.
76
-
77
- Authority hierarchy (lower number = higher authority — ALWAYS wins):
78
- 1. Legal & Compliance, Regulatory Affairs
79
- 2. CEO Office, CFO, CTO, COO
80
- 3. VP Engineering, VP Finance, VP Operations, VP Human Resources
81
- 4. Director of IT, Director of Finance
82
- 5. Engineering Manager, Finance Manager, HR Manager, IT Manager
83
- 6. Team Lead, Department Coordinator
84
-
85
- Rules:
86
- - When two instructions conflict, the HIGHER authority (lower tier number) always wins
87
- - List ALL conflicting pairs — do not miss any
88
- - Your execution_plan must contain ONLY the winning instructions + non-conflicting ones
89
- - overridden_instructions must contain ONLY the losing instructions
90
-
91
- Output ONLY valid JSON matching this exact schema. No preamble, no explanation outside the JSON:
92
- {"identified_conflicts":[{"instruction_a":"<ID>","instruction_b":"<ID>","conflict_type":"direct|resource|temporal|absolute","resolution":"<winning ID>","reasoning":"<one sentence>"}],"execution_plan":["<ID>",...],"overridden_instructions":["<ID>",...]}"""
93
-
94
- # Global list to collect training metrics for plotting
95
- _training_log = []
96
-
97
- # ── Dataset builder ───────────────────────────────────────────────────────────
98
-
99
- def build_dataset():
100
- from generator import ScenarioGenerator
101
- gen = ScenarioGenerator(seed=SEED)
102
-
103
- def make_records(n: int, diff_weights: Dict[int, float]) -> List[Dict[str, Any]]:
104
- records = []
105
- attempts = 0
106
- while len(records) < n and attempts < n * 5:
107
- attempts += 1
108
- diff = random.choices(list(diff_weights.keys()), weights=list(diff_weights.values()))[0]
109
- scenario = gen.generate(difficulty=diff)
110
-
111
- if len(scenario.prompt) > MAX_PROMPT_CHARS:
112
- continue
113
-
114
- messages = [
115
- {"role": "system", "content": SYSTEM_PROMPT},
116
- {"role": "user", "content": scenario.prompt},
117
- ]
118
-
119
- records.append({
120
- "prompt": messages,
121
- "scenario_json": json.dumps({
122
- "ground_truth_followed": scenario.ground_truth_followed,
123
- "ground_truth_overridden": scenario.ground_truth_overridden,
124
- "difficulty": scenario.difficulty,
125
- "conflicts": [
126
- {
127
- "instruction_a_id": c.instruction_a_id,
128
- "instruction_b_id": c.instruction_b_id,
129
- "conflict_type": c.conflict_type,
130
- "resolution_id": c.resolution_id,
131
- "explanation": c.explanation,
132
- } for c in scenario.conflicts
133
- ],
134
- "instructions": [
135
- {
136
- "id": ins.id,
137
- "action_key": ins.action_key,
138
- "action_value": ins.action_value,
139
- "source_priority": ins.source_priority,
140
- } for ins in scenario.instructions
141
- ],
142
- }),
143
- })
144
- return records
145
-
146
- train_records = make_records(TRAIN_SCENARIOS, DIFFICULTY_TRAIN_WEIGHTS)
147
- eval_records = make_records(EVAL_SCENARIOS, DIFFICULTY_EVAL_WEIGHTS)
148
- return Dataset.from_list(train_records), Dataset.from_list(eval_records)
149
-
150
- # ── Reward function ───────────────────────────────────────────────────────────
151
-
152
- def build_reward_fn():
153
- from generator import Scenario, Instruction, ConflictPair
154
- from verifier import score as _score
155
-
156
- RUBRIC_WEIGHTS_V2 = {
157
- "correct_final_state": 0.35,
158
- "no_contradictions": 0.25,
159
- "conflict_identification": 0.20,
160
- "efficiency": 0.10,
161
- "format_compliance": 0.10,
162
- }
163
-
164
- def reward_fn(prompts, completions, **kwargs):
165
- rewards = []
166
- scenario_jsons = kwargs.get("scenario_json", [None] * len(completions))
167
-
168
- for completion, scenario_json in zip(completions, scenario_jsons):
169
- if scenario_json is None:
170
- rewards.append(0.0)
171
- continue
172
- try:
173
- data = json.loads(scenario_json)
174
- instructions = [Instruction(id=ins["id"], text="", source="", source_priority=ins["source_priority"],
175
- instruction_type="absolute", action_key=ins["action_key"],
176
- action_value=ins["action_value"]) for ins in data["instructions"]]
177
- conflicts = [ConflictPair(instruction_a_id=c["instruction_a_id"], instruction_b_id=c["instruction_b_id"],
178
- conflict_type=c["conflict_type"], resolution_id=c["resolution_id"],
179
- explanation=c["explanation"]) for c in data["conflicts"]]
180
- scenario = Scenario(scenario_id="", domain="", difficulty=data.get("difficulty", 1),
181
- business_context="", instructions=instructions, conflicts=conflicts,
182
- ground_truth_followed=data["ground_truth_followed"],
183
- ground_truth_overridden=data["ground_truth_overridden"], prompt="")
184
-
185
- breakdown = _score(completion, scenario)
186
- composite_v2 = (
187
- RUBRIC_WEIGHTS_V2["correct_final_state"] * breakdown.correct_final_state +
188
- RUBRIC_WEIGHTS_V2["no_contradictions"] * breakdown.no_contradictions +
189
- RUBRIC_WEIGHTS_V2["conflict_identification"] * breakdown.conflict_identification +
190
- RUBRIC_WEIGHTS_V2["efficiency"] * breakdown.efficiency +
191
- RUBRIC_WEIGHTS_V2["format_compliance"] * breakdown.format_compliance
192
- )
193
-
194
- diff_bonus = 0.05 if data.get("difficulty", 1) == 2 else 0.0
195
- final_reward = min(1.0, composite_v2 + diff_bonus * composite_v2)
196
- rewards.append(round(final_reward, 4))
197
- except Exception as e:
198
- rewards.append(0.0)
199
- return rewards
200
-
201
- return reward_fn
202
-
203
- # ── Plotting ──────────────────────────────────────────────────────────────────
204
-
205
- def generate_plots():
206
- import matplotlib
207
- matplotlib.use("Agg")
208
- import matplotlib.pyplot as plt
209
-
210
- PLOTS_DIR.mkdir(parents=True, exist_ok=True)
211
- logs = _training_log
212
- if not logs: return []
213
-
214
- steps = [l["step"] for l in logs]
215
- plot_paths = []
216
-
217
- # Reward
218
- fig, ax = plt.subplots(figsize=(10, 5))
219
- train_r = [(l["step"], l["reward"]) for l in logs if "reward" in l]
220
- eval_r = [(l["step"], l["eval_reward"]) for l in logs if "eval_reward" in l]
221
- if train_r: ax.plot(*zip(*train_r), label="Train Reward")
222
- if eval_r: ax.plot(*zip(*eval_r), label="Eval Reward")
223
- ax.set_title("Reward Curve"); ax.legend(); ax.grid(True)
224
- p = PLOTS_DIR / "reward_curve.png"; fig.savefig(p); plt.close(fig); plot_paths.append(str(p))
225
-
226
- # Loss
227
- fig, ax = plt.subplots(figsize=(10, 5))
228
- loss = [(l["step"], l["loss"]) for l in logs if "loss" in l]
229
- if loss: ax.plot(*zip(*loss), color="red")
230
- ax.set_title("Training Loss"); ax.grid(True)
231
- p = PLOTS_DIR / "loss_curve.png"; fig.savefig(p); plt.close(fig); plot_paths.append(str(p))
232
-
233
- # KL
234
- fig, ax = plt.subplots(figsize=(10, 5))
235
- kl = [(l["step"], l["kl"]) for l in logs if "kl" in l]
236
- if kl: ax.plot(*zip(*kl), color="purple")
237
- ax.set_title("KL Divergence"); ax.grid(True)
238
- p = PLOTS_DIR / "kl_divergence.png"; fig.savefig(p); plt.close(fig); plot_paths.append(str(p))
239
-
240
- # Combined
241
- fig, axes = plt.subplots(2, 2, figsize=(12, 8))
242
- for ax, key in zip(axes.flat, ["reward", "loss", "kl", "eval_reward"]):
243
- pts = [(l["step"], l[key]) for l in logs if key in l]
244
- if pts: ax.plot(*zip(*pts))
245
- ax.set_title(key); ax.grid(True)
246
- fig.tight_layout()
247
- p = PLOTS_DIR / "training_dashboard.png"; fig.savefig(p); plt.close(fig); plot_paths.append(str(p))
248
-
249
- return plot_paths
250
-
251
- # ── Main Training Loop ────────────────────────────────────────────────────────
252
-
253
- def run_training(progress_callback=None):
254
- def emit(msg):
255
- if progress_callback: progress_callback(msg)
256
- log.info(msg)
257
-
258
- _training_log.clear()
259
- clone_repo()
260
-
261
- from trl import GRPOTrainer, GRPOConfig
262
-
263
- emit("🚀 Loading model (Unsloth 4-bit)...")
264
- model, tokenizer = FastLanguageModel.from_pretrained(
265
- model_name="unsloth/Qwen2.5-3B-Instruct-bnb-4bit",
266
- max_seq_length=4096,
267
- load_in_4bit=True,
268
- )
269
-
270
- model = FastLanguageModel.get_peft_model(
271
- model, r=16, lora_alpha=16, lora_dropout=0.0, bias="none",
272
- target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
273
- use_gradient_checkpointing="unsloth", random_state=SEED,
274
- )
275
-
276
- # Cast LoRA params to avoid Half/Float mismatch
277
- compute_dtype = torch.bfloat16 if torch.cuda.is_bf16_supported() else torch.float16
278
- for name, param in model.named_parameters():
279
- if "lora_" in name: param.data = param.data.to(compute_dtype)
280
- # Patch for compatibility with recent TRL GRPOTrainer
281
- if not hasattr(model, "warnings_issued"):
282
- model.warnings_issued = {}
283
-
284
- emit(f"✅ LoRA applied (dtype: {compute_dtype})")
285
-
286
- emit("📊 Building datasets...")
287
- train_dataset, eval_dataset = build_dataset()
288
- emit(f"✅ Datasets ready: {len(train_dataset)} train / {len(eval_dataset)} eval")
289
-
290
- class GradioLogCallback(TrainerCallback):
291
- def on_log(self, args, state, control, logs=None, **kwargs):
292
- if logs:
293
- entry = {"step": state.global_step, **{k: v for k, v in logs.items() if isinstance(v, (int, float))}}
294
- _training_log.append(entry)
295
-
296
- # Format learning rate with scientific notation, others with 4 decimals
297
- parts = []
298
- for k, v in entry.items():
299
- if k == "step": continue
300
- if "learning_rate" in k:
301
- parts.append(f"{k}={v:.2e}")
302
- else:
303
- parts.append(f"{k}={v:.4f}")
304
-
305
- msg = f"Step {state.global_step}: " + " | ".join(parts)
306
- emit(msg)
307
-
308
- grpo_config = GRPOConfig(
309
- output_dir=str(OUTPUT_DIR),
310
- per_device_train_batch_size=NUM_GENERATIONS, # Unsloth forces batch_size == num_generations
311
- per_device_eval_batch_size=NUM_GENERATIONS,
312
- gradient_accumulation_steps=1, # Keep effective batch size at 8 (8*1=8)
313
- num_train_epochs=NUM_EPOCHS,
314
- learning_rate=LEARNING_RATE,
315
- warmup_steps=WARMUP_STEPS,
316
- num_generations=NUM_GENERATIONS,
317
- max_completion_length=MAX_NEW_TOKENS,
318
- max_prompt_length=MAX_PROMPT_CHARS,
319
- temperature=TEMPERATURE,
320
- top_p=TOP_P,
321
- beta=BETA,
322
- logging_steps=10,
323
- save_steps=50,
324
- eval_strategy="steps",
325
- eval_steps=50,
326
- fp16=not torch.cuda.is_bf16_supported(),
327
- bf16=torch.cuda.is_bf16_supported(),
328
- report_to="none",
329
- seed=SEED,
330
- )
331
-
332
- trainer = GRPOTrainer(
333
- model=model, args=grpo_config, tokenizer=tokenizer,
334
- train_dataset=train_dataset, eval_dataset=eval_dataset,
335
- reward_funcs=[build_reward_fn()],
336
- )
337
- trainer.add_callback(GradioLogCallback())
338
-
339
- emit("🔥 Starting GRPO training...")
340
- trainer.train()
341
-
342
- emit("💾 Saving final model...")
343
- final_path = OUTPUT_DIR / "final"
344
- model.save_pretrained(str(final_path))
345
- tokenizer.save_pretrained(str(final_path))
346
-
347
- if HF_REPO_ID and HF_TOKEN:
348
- emit(f"☁️ Uploading to {HF_REPO_ID}...")
349
- try:
350
- from huggingface_hub import HfApi
351
- api = HfApi(token=HF_TOKEN)
352
- api.create_repo(HF_REPO_ID, repo_type="model", exist_ok=True)
353
- api.upload_folder(folder_path=str(final_path), repo_id=HF_REPO_ID, repo_type="model")
354
- emit(f"✅ Uploaded to https://huggingface.co/{HF_REPO_ID}")
355
- except Exception as e:
356
- emit(f"❌ Upload failed: {e}")
357
-
358
- emit("📈 Generating plots...")
359
- return generate_plots()
360
-
361
- if __name__ == "__main__":
362
- run_training()