kernel-env / train /small_run.py
Shreyansh327's picture
Publish OpenEnv kernel environment and bounded GRPO proof artifacts
e313085 verified
Raw History Blame Contribute Delete
5.42 kB
"""Bounded OpenEnv RL pilot: frozen baseline -> GRPO LoRA -> identical evaluation.
TRAIN_GPU=H100:2 modal run modal_app.py::job --max-turns 2 --reward roofline \
--cmd 'timeout 5400 python small_run.py --out /runs/proof-20261019'
"""
import argparse
import json
import os
import subprocess
import sys
import time
from pathlib import Path
from baseline_eval import split_task_ids, summarize
TRAIN = ["level1/19_ReLU", "level1/20_LeakyReLU", "level1/21_Sigmoid", "level1/22_Tanh"]
TRANSFER = ["level1/25_Swish", "level1/28_HardSigmoid", "level1/32_HardTanh"]
HELDOUT = ["level1/23_Softmax", "level1/24_LogSoftmax", "level1/88_MinGPTNewGelu"]
def compare(before, after, groups):
"""Compare the same tasks; never infer a training gain from within-episode refinement."""
if sorted(e["task_id"] for e in before) != sorted(e["task_id"] for e in after):
raise ValueError("before/after task sets differ")
if any(e.get("error") for e in before + after):
raise ValueError("evaluation contains infrastructure errors")
result = {}
for name, ids in groups.items():
result[name] = {}
for label, episodes in [("before", before), ("after", after)]:
chosen = [e for e in episodes if e["task_id"] in ids]
if sorted(e["task_id"] for e in chosen) != sorted(ids):
raise ValueError(f"missing or repeated task in {name}/{label}")
summary = summarize(chosen)
# Including failures prevents a policy solving fewer easy tasks from appearing faster.
summary["mean_best_speedup_including_failures"] = sum(
max((t["speedup"] for t in e["turns"] if t["correct"]), default=0)
for e in chosen
) / len(chosen)
result[name][label] = summary
return result
def main():
p = argparse.ArgumentParser()
p.add_argument("--out", required=True)
p.add_argument("--model", default="Qwen/Qwen2.5-Coder-7B-Instruct")
p.add_argument("--steps", type=int, default=16)
a = p.parse_args()
out = Path(a.out)
out.mkdir(parents=True, exist_ok=False)
groups = {"training_tasks": TRAIN, "reserved_transfer": TRANSFER, "official_heldout": HELDOUT}
assert set(TRAIN + TRANSFER) <= set(split_task_ids("train", [1]))
assert set(HELDOUT) <= set(split_task_ids("eval", [1]))
assert not set(TRAIN) & set(TRANSFER + HELDOUT)
manifest = {"model": a.model, "steps": a.steps, "groups": groups,
"eval_temperature": 0.0, "max_new_tokens": 2048, "seed": 42,
"created_at_unix": time.time(), "torch_baseline": "eager"}
(out / "manifest.json").write_text(json.dumps(manifest, indent=2))
task_file = out / "train_tasks.json"
task_file.write_text(json.dumps(TRAIN))
def run(args, name):
print(f"PHASE {name}: {args}", flush=True)
start = time.monotonic()
with (out / f"{name}.log").open("w") as log:
proc = subprocess.Popen([sys.executable, "-u", *args], stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, text=True, bufsize=1)
for line in proc.stdout:
print(line, end="", flush=True)
log.write(line)
log.flush()
if proc.wait():
raise RuntimeError(f"{name} failed; see {log.name}")
manifest.setdefault("phase_seconds", {})[name] = time.monotonic() - start
(out / "manifest.json").write_text(json.dumps(manifest, indent=2))
def evaluate(model, label):
run(["baseline_eval.py", "--env-url", os.environ["KERNEL_ENV_URL"], "--model", model,
"--backend", "vllm", "--temperature", "0", "--max-new-tokens", "2048",
"--max-model-len", "8192", "--task-ids", *TRAIN, *TRANSFER, *HELDOUT,
"--out", str(out / f"{label}.jsonl")], label)
evaluate(a.model, "before")
run(["grpo_kernel.py", "--model_name_or_path", a.model, "--dtype", "bfloat16", "--bf16", "true",
"--env_url", os.environ["KERNEL_ENV_URL"], "--task_file", str(task_file),
"--use_peft", "--lora_r", "16", "--lora_alpha", "32", "--lora_target_modules", "all-linear",
"--use_vllm", "--vllm_mode", "colocate", "--vllm_gpu_memory_utilization", "0.25",
"--vllm_max_model_length", "8192", "--num_generations", "4",
"--per_device_train_batch_size", "4", "--gradient_accumulation_steps", "1",
"--max_completion_length", "2048", "--max_steps", str(a.steps), "--learning_rate", "5e-5",
"--gradient_checkpointing", "true", "--max_grad_norm", "0.05", "--loss_type", "dr_grpo",
"--return_mode", "discounted", "--gamma", "0.4", "--temperature", "0.8",
"--seed", "42", "--logging_steps", "1", "--report_to", "none", "--save_steps", "8",
"--save_total_limit", "2", "--output_dir", str(out / "adapter")], "train")
run(["merge_lora.py", "--base", a.model, "--adapter", str(out / "adapter"),
"--out", str(out / "merged")], "merge")
evaluate(str(out / "merged"), "after")
episodes = [[json.loads(l) for l in (out / f"{label}.jsonl").read_text().splitlines()]
for label in ("before", "after")]
result = compare(*episodes, groups)
(out / "comparison.json").write_text(json.dumps(result, indent=2))
print("COMPARISON", json.dumps(result, indent=2), flush=True)
if __name__ == "__main__":
main()