Spaces:
Sleeping
Sleeping
| """ | |
| Toy REINFORCE training for CompilerOptimizationEnv (CPU, no PyTorch). | |
| This is meant for the Hugging Face Space: fast smoke training that uses the | |
| same `runtime_core` mock engine and passes as the Gradio demo. | |
| For full GRPO + Unsloth + LLM, use your Colab / GPU notebooks | |
| (`compiler_optimization_grpo.ipynb`, `role2_deliverable3_*`). | |
| CLI: python train.py --episodes 50 --max-steps 8 --seed 0 | |
| Or import: from train import run_toy_training; print(run_toy_training(20, 8, 0)) | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import math | |
| import random | |
| import textwrap | |
| from typing import List, Sequence, Tuple | |
| from runtime_core import ( | |
| CompilerOptimizationEnv, | |
| MOCK_PASSES, | |
| MockEngine, | |
| SAMPLE_PROGRAM, | |
| ) | |
| # Additional tiny programs so the policy is not overfit to a single IR. | |
| TRAINING_PROGRAMS: List[List[dict]] = [ | |
| SAMPLE_PROGRAM, | |
| [ | |
| {"op": "const", "dest": "a", "args": ["1"], "type": "int"}, | |
| {"op": "add", "dest": "b", "args": ["a", "a"], "type": "int"}, | |
| {"op": "ret", "args": ["b"]}, | |
| ], | |
| [ | |
| {"op": "const", "dest": "x", "args": ["2"], "type": "int"}, | |
| {"op": "const", "dest": "y", "args": ["4"], "type": "int"}, | |
| {"op": "mul", "dest": "z", "args": ["x", "y"], "type": "int"}, | |
| {"op": "const", "dest": "k", "args": ["1"], "type": "int"}, | |
| {"op": "add", "dest": "w", "args": ["z", "k"], "type": "int"}, | |
| {"op": "ret", "args": ["w"]}, | |
| ], | |
| ] | |
| ACTIONS: Tuple[str, ...] = tuple(sorted(MOCK_PASSES.keys())) | |
| def _softmax(logits: Sequence[float]) -> List[float]: | |
| m = max(logits) if logits else 0.0 | |
| ex = [math.exp(x - m) for x in logits] | |
| s = sum(ex) or 1.0 | |
| return [e / s for e in ex] | |
| def _sample_action(rng: random.Random, logits: List[float]) -> Tuple[int, List[float], float]: | |
| """Return (action index, prob vector, log prob of chosen action).""" | |
| p = _softmax(logits) | |
| u = rng.random() | |
| acc = 0.0 | |
| idx = len(p) - 1 | |
| for i, pi in enumerate(p): | |
| acc += pi | |
| if u <= acc: | |
| idx = i | |
| break | |
| log_p = math.log(p[idx] + 1e-12) | |
| return idx, p, log_p | |
| def _rollout( | |
| engine: MockEngine, | |
| program: List[dict], | |
| max_steps: int, | |
| logits: List[float], | |
| rng: random.Random, | |
| ) -> Tuple[float, List[Tuple[int, List[float], float]]]: | |
| """ | |
| One episode. Returns total reward and per-step (action index, prob vector, step reward) | |
| for REINFORCE with returns G_t = sum of rewards from t onward. | |
| """ | |
| env = CompilerOptimizationEnv(engine, MOCK_PASSES, max_steps=max_steps) | |
| env.reset(program) | |
| total = 0.0 | |
| trace: List[Tuple[int, List[float], float]] = [] | |
| for _ in range(max_steps): | |
| a_idx, pvec, _ = _sample_action(rng, logits) | |
| action = ACTIONS[a_idx] | |
| step = env.step(action) | |
| r = float(step.reward) | |
| total += r | |
| trace.append((a_idx, pvec, r)) | |
| if step.done: | |
| break | |
| return total, trace | |
| def _reinforce_update( | |
| logits: List[float], trace: List[Tuple[int, List[float], float]], lr: float | |
| ) -> None: | |
| """REINFORCE with Monte Carlo return G_t from each step.""" | |
| G = 0.0 | |
| for t in range(len(trace) - 1, -1, -1): | |
| a_idx, pvec, r = trace[t] | |
| G += r | |
| for i in range(len(logits)): | |
| delta = 1.0 if i == a_idx else 0.0 | |
| logits[i] += lr * G * (delta - pvec[i]) | |
| def run_toy_training( | |
| episodes: int, | |
| max_steps: int, | |
| seed: int = 0, | |
| lr: float = 0.15, | |
| ) -> str: | |
| """ | |
| Train a stateless categorical policy over the three mock passes; print-friendly report. | |
| """ | |
| if episodes < 1: | |
| return "episodes must be >= 1" | |
| if max_steps < 1: | |
| return "max_steps must be >= 1" | |
| episodes = int(episodes) | |
| max_steps = int(max_steps) | |
| seed = int(seed) | |
| lr = float(lr) | |
| rng = random.Random(seed) | |
| engine = MockEngine() | |
| logits = [0.0 for _ in ACTIONS] | |
| history: List[Tuple[int, float]] = [] | |
| for ep in range(1, episodes + 1): | |
| program = TRAINING_PROGRAMS[(ep - 1) % len(TRAINING_PROGRAMS)] | |
| G, trace = _rollout(engine, program, max_steps, logits, rng) | |
| if trace: | |
| _reinforce_update(logits, trace, lr) | |
| history.append((ep, G)) | |
| final_p = _softmax(logits) | |
| lines = [ | |
| "Toy REINFORCE (stateless policy over pass names, CPU, stdlib only)", | |
| f" episodes={episodes} max_steps={max_steps} seed={seed} lr={lr}", | |
| f" actions order: {list(ACTIONS)}", | |
| "", | |
| f" final logits: {[round(x, 4) for x in logits]}", | |
| f" final policy: {', '.join(f'{a}={p:.3f}' for a, p in zip(ACTIONS, final_p))}", | |
| "", | |
| " return per episode (last 10): " + ", ".join(f"{G:+.1f}" for _, G in history[-10:]), | |
| "", | |
| ] | |
| if history: | |
| mean_r = sum(G for _, G in history) / len(history) | |
| lines.append(f" mean return over all episodes: {mean_r:+.3f}") | |
| lines.append("") | |
| lines.append( | |
| textwrap.dedent( | |
| """ | |
| This does not train an LLM. For GRPO + Qwen + Unsloth, run the project notebooks | |
| on a GPU machine (e.g. Colab), not the CPU Space. | |
| """ | |
| ).strip() | |
| ) | |
| return "\n".join(lines) | |
| def _parse_args() -> argparse.Namespace: | |
| p = argparse.ArgumentParser(description="Toy REINFORCE on CompilerOptimizationEnv (CPU)") | |
| p.add_argument("--episodes", type=int, default=50, help="Number of training episodes") | |
| p.add_argument("--max-steps", type=int, default=8, help="max_steps per env episode") | |
| p.add_argument("--seed", type=int, default=0, help="RNG seed") | |
| p.add_argument("--lr", type=float, default=0.15, help="REINFORCE learning rate") | |
| p.add_argument( | |
| "--out-json", | |
| type=str, | |
| default="", | |
| help="If set, write a small run summary to this path (e.g. training_log.json).", | |
| ) | |
| return p.parse_args() | |
| def main() -> None: | |
| args = _parse_args() | |
| report = run_toy_training( | |
| episodes=args.episodes, | |
| max_steps=args.max_steps, | |
| seed=args.seed, | |
| lr=args.lr, | |
| ) | |
| print(report) | |
| if args.out_json: | |
| payload = { | |
| "episodes": args.episodes, | |
| "max_steps": args.max_steps, | |
| "seed": args.seed, | |
| "lr": args.lr, | |
| "summary_text": report, | |
| } | |
| with open(args.out_json, "w", encoding="utf-8") as f: | |
| json.dump(payload, f, indent=2) | |
| print(f"\nWrote {args.out_json}") | |
| if __name__ == "__main__": | |
| main() | |