Compilertetris / train.py
github-actions
Auto deploy from GitHub
5aa0f26
Raw
History Blame Contribute Delete
6.54 kB
"""
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()