Spaces:
Sleeping
Sleeping
File size: 6,537 Bytes
5aa0f26 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 | """
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()
|