Spaces:
Sleeping
Sleeping
github-actions commited on
Commit ·
8b58cfa
1
Parent(s): bc450e0
Auto deploy from GitHub
Browse files- space/Dockerfile +5 -2
- space/space/space/Dockerfile +19 -6
- space/space/space/space/space/space/program_generator.py +116 -0
- space/space/space/space/space/space/space/reverse_pass/README.md +57 -0
- space/space/space/space/space/space/space/reverse_pass/compiler_optimization_grpo.ipynb +850 -0
- space/space/space/space/space/space/space/reverse_pass/toyir_rl_support.py +473 -0
- space/space/space/space/space/space/space/space/reverse_pass/reversepass_new_eval_baseline.ipynb +0 -0
- space/space/space/space/space/space/space/space/space/app.py +26 -2
- space/space/space/space/space/space/space/space/space/space/space/runtime_core.py +316 -0
- space/space/space/space/space/space/space/space/space/space/space/space/Dockerfile +19 -0
- space/space/space/space/space/space/space/space/space/space/space/space/README.md +50 -5
- space/space/space/space/space/space/space/space/space/space/space/space/app.py +162 -177
- space/space/space/space/space/space/space/space/space/space/space/space/requirements.txt +2 -24
- space/space/space/space/space/space/space/space/space/space/space/space/space/compiler_optimization_grpo.ipynb +959 -0
- space/space/space/space/space/space/space/space/space/space/space/space/space/space/role2_deliverable3_training_loop (2) (1) (1).ipynb +0 -0
- space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/DockerFile +34 -0
- space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/README.md +9 -1
- space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/requirements.txt +24 -0
- space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/app.py +182 -0
- space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/compiler_tetris (1).ipynb +651 -0
- space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/metahack1 (1).ipynb +0 -0
- space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.DS_Store +0 -0
- space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/metahack1 (1).ipynb +1379 -0
- space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/metahack1.ipynb +484 -0
- space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.github/workflows/deploy.yml +30 -0
- space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/README.md +1 -11
- space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.gitattributes +35 -0
- space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/README.md +11 -0
- space/space/space/space/space/space/space/space/space/space/train.py +208 -0
- space/space/space/space/space/space/space/space/untitled folder.zip +3 -0
- space/space/space/space/space/space/write_colab_synth_notebook.py +345 -0
- space/space/space/space/write_colab_synth_notebook.py +28 -22
- write_colab_synth_notebook.py +143 -12
space/Dockerfile
CHANGED
|
@@ -16,7 +16,7 @@ ENV DEBIAN_FRONTEND=noninteractive \
|
|
| 16 |
NVIDIA_VISIBLE_DEVICES=all \
|
| 17 |
NVIDIA_DRIVER_CAPABILITIES=compute,utility
|
| 18 |
|
| 19 |
-
# Python + basic
|
| 20 |
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 21 |
python3.11 python3.11-venv python3-pip \
|
| 22 |
git ca-certificates \
|
|
@@ -25,8 +25,11 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
| 25 |
COPY requirements.txt .
|
| 26 |
RUN python3.11 -m pip install --upgrade pip && python3.11 -m pip install -r requirements.txt
|
| 27 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
COPY . .
|
| 29 |
|
| 30 |
EXPOSE 7860
|
| 31 |
-
|
| 32 |
CMD ["python3.11", "app.py"]
|
|
|
|
| 16 |
NVIDIA_VISIBLE_DEVICES=all \
|
| 17 |
NVIDIA_DRIVER_CAPABILITIES=compute,utility
|
| 18 |
|
| 19 |
+
# Python + basic deps
|
| 20 |
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 21 |
python3.11 python3.11-venv python3-pip \
|
| 22 |
git ca-certificates \
|
|
|
|
| 25 |
COPY requirements.txt .
|
| 26 |
RUN python3.11 -m pip install --upgrade pip && python3.11 -m pip install -r requirements.txt
|
| 27 |
|
| 28 |
+
# CUDA-enabled PyTorch (so `torch.cuda.is_available()` is True on GPU Spaces)
|
| 29 |
+
RUN python3.11 -m pip install --no-cache-dir \
|
| 30 |
+
torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
|
| 31 |
+
|
| 32 |
COPY . .
|
| 33 |
|
| 34 |
EXPOSE 7860
|
|
|
|
| 35 |
CMD ["python3.11", "app.py"]
|
space/space/space/Dockerfile
CHANGED
|
@@ -1,19 +1,32 @@
|
|
| 1 |
# Optional: use Hugging Face Space SDK = docker (set `sdk: docker` in README.md).
|
| 2 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
|
| 4 |
-
FROM
|
| 5 |
|
| 6 |
WORKDIR /app
|
| 7 |
|
| 8 |
-
ENV
|
|
|
|
| 9 |
PIP_NO_CACHE_DIR=1 \
|
| 10 |
-
GRADIO_SERVER_NAME=0.0.0.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
COPY requirements.txt .
|
| 13 |
-
RUN pip install --upgrade pip && pip install -r requirements.txt
|
| 14 |
|
| 15 |
COPY . .
|
| 16 |
|
| 17 |
EXPOSE 7860
|
| 18 |
|
| 19 |
-
CMD ["
|
|
|
|
| 1 |
# Optional: use Hugging Face Space SDK = docker (set `sdk: docker` in README.md).
|
| 2 |
+
#
|
| 3 |
+
# IMPORTANT:
|
| 4 |
+
# - If your Space is on CPU hardware, `torch.cuda.is_available()` will be False no matter what.
|
| 5 |
+
# - If your Space is on GPU hardware, you must use a CUDA-enabled base image (below),
|
| 6 |
+
# otherwise CUDA won't be visible inside the container.
|
| 7 |
|
| 8 |
+
FROM nvidia/cuda:12.1.1-cudnn8-runtime-ubuntu22.04
|
| 9 |
|
| 10 |
WORKDIR /app
|
| 11 |
|
| 12 |
+
ENV DEBIAN_FRONTEND=noninteractive \
|
| 13 |
+
PYTHONUNBUFFERED=1 \
|
| 14 |
PIP_NO_CACHE_DIR=1 \
|
| 15 |
+
GRADIO_SERVER_NAME=0.0.0.0 \
|
| 16 |
+
NVIDIA_VISIBLE_DEVICES=all \
|
| 17 |
+
NVIDIA_DRIVER_CAPABILITIES=compute,utility
|
| 18 |
+
|
| 19 |
+
# Python + basic build deps (kept minimal)
|
| 20 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 21 |
+
python3.11 python3.11-venv python3-pip \
|
| 22 |
+
git ca-certificates \
|
| 23 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 24 |
|
| 25 |
COPY requirements.txt .
|
| 26 |
+
RUN python3.11 -m pip install --upgrade pip && python3.11 -m pip install -r requirements.txt
|
| 27 |
|
| 28 |
COPY . .
|
| 29 |
|
| 30 |
EXPOSE 7860
|
| 31 |
|
| 32 |
+
CMD ["python3.11", "app.py"]
|
space/space/space/space/space/space/program_generator.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Random Toy-IR list-of-dicts for GRPO / Deliverable2 / runtime_core.
|
| 3 |
+
|
| 4 |
+
Schema matches `runtime_core.SAMPLE_PROGRAM` (op, args, dest, type) — the same
|
| 5 |
+
shape `Deliverable2_Formatter` and `CompilerOptimizationEnv` expect.
|
| 6 |
+
|
| 7 |
+
Note: `metahack1 (1).ipynb` uses a different TAC shape (CONST/src1/STORE).
|
| 8 |
+
Use that notebook's generators only if you add a separate converter; this
|
| 9 |
+
module is self-contained for training stack compatibility.
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
from __future__ import annotations
|
| 13 |
+
|
| 14 |
+
import copy
|
| 15 |
+
import json
|
| 16 |
+
import random
|
| 17 |
+
from typing import List
|
| 18 |
+
|
| 19 |
+
# Hand-written seeds (same as Colab / train.py short list) — kept in sync for regression.
|
| 20 |
+
_BUILTIN_EXTRA: List[List[dict]] = [
|
| 21 |
+
[
|
| 22 |
+
{"op": "const", "dest": "a", "args": ["1"], "type": "int"},
|
| 23 |
+
{"op": "add", "dest": "b", "args": ["a", "a"], "type": "int"},
|
| 24 |
+
{"op": "ret", "args": ["b"]},
|
| 25 |
+
],
|
| 26 |
+
[
|
| 27 |
+
{"op": "const", "dest": "x", "args": ["2"], "type": "int"},
|
| 28 |
+
{"op": "const", "dest": "y", "args": ["4"], "type": "int"},
|
| 29 |
+
{"op": "mul", "dest": "z", "args": ["x", "y"], "type": "int"},
|
| 30 |
+
{"op": "const", "dest": "k", "args": ["1"], "type": "int"},
|
| 31 |
+
{"op": "add", "dest": "w", "args": ["z", "k"], "type": "int"},
|
| 32 |
+
{"op": "ret", "args": ["w"]},
|
| 33 |
+
],
|
| 34 |
+
]
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def random_toy_ir_program(rng: random.Random) -> List[dict]:
|
| 38 |
+
"""
|
| 39 |
+
One valid program: consts v0.., then a chain of add/mul on existing names, then ret.
|
| 40 |
+
All ops use the mock-engine-friendly list schema.
|
| 41 |
+
"""
|
| 42 |
+
n_const = rng.randint(2, 5)
|
| 43 |
+
n_arith = rng.randint(1, 5)
|
| 44 |
+
progs: List[dict] = []
|
| 45 |
+
for i in range(n_const):
|
| 46 |
+
progs.append(
|
| 47 |
+
{
|
| 48 |
+
"op": "const",
|
| 49 |
+
"dest": f"v{i}",
|
| 50 |
+
"args": [str(rng.randint(0, 20))],
|
| 51 |
+
"type": "int",
|
| 52 |
+
}
|
| 53 |
+
)
|
| 54 |
+
available = [f"v{i}" for i in range(n_const)]
|
| 55 |
+
nxt = n_const
|
| 56 |
+
for _j in range(n_arith):
|
| 57 |
+
a = rng.choice(available)
|
| 58 |
+
b = rng.choice(available)
|
| 59 |
+
opn = rng.choice(["add", "mul"])
|
| 60 |
+
d = f"v{nxt}"
|
| 61 |
+
nxt += 1
|
| 62 |
+
progs.append({"op": opn, "dest": d, "args": [a, b], "type": "int"})
|
| 63 |
+
available.append(d)
|
| 64 |
+
progs.append({"op": "ret", "args": [available[-1]]})
|
| 65 |
+
return progs
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def build_training_program_corpus(
|
| 69 |
+
n_total: int = 120,
|
| 70 |
+
seed: int = 42,
|
| 71 |
+
*,
|
| 72 |
+
include_builtins: bool = True,
|
| 73 |
+
) -> List[List[dict]]:
|
| 74 |
+
"""
|
| 75 |
+
Return `n_total` programs for GRPO. Optionally prepend SAMPLE_PROGRAM + 2 hand-written IRs
|
| 76 |
+
(when include_builtins), then fill with random_toy_ir_program, deduplicating by JSON key.
|
| 77 |
+
|
| 78 |
+
Typical range: set `n_total` between 50 and 200 in the notebook.
|
| 79 |
+
"""
|
| 80 |
+
if n_total < 1:
|
| 81 |
+
raise ValueError("n_total must be >= 1")
|
| 82 |
+
|
| 83 |
+
rng = random.Random(seed)
|
| 84 |
+
out: List[List[dict]] = []
|
| 85 |
+
seen: set[str] = set()
|
| 86 |
+
|
| 87 |
+
def _add(p: List[dict]) -> None:
|
| 88 |
+
k = json.dumps(p, sort_keys=True)
|
| 89 |
+
if k in seen:
|
| 90 |
+
return
|
| 91 |
+
seen.add(k)
|
| 92 |
+
out.append(copy.deepcopy(p))
|
| 93 |
+
|
| 94 |
+
if include_builtins:
|
| 95 |
+
from runtime_core import SAMPLE_PROGRAM
|
| 96 |
+
|
| 97 |
+
for p in (SAMPLE_PROGRAM, *_BUILTIN_EXTRA):
|
| 98 |
+
if len(out) >= n_total:
|
| 99 |
+
break
|
| 100 |
+
_add(p)
|
| 101 |
+
|
| 102 |
+
# Fill with random programs (dedupe by full JSON; allow dup if generator keeps colliding)
|
| 103 |
+
guard = 0
|
| 104 |
+
while len(out) < n_total:
|
| 105 |
+
guard += 1
|
| 106 |
+
if guard > 200_000:
|
| 107 |
+
out.append(random_toy_ir_program(rng))
|
| 108 |
+
continue
|
| 109 |
+
cand = random_toy_ir_program(rng)
|
| 110 |
+
k = json.dumps(cand, sort_keys=True)
|
| 111 |
+
if k in seen:
|
| 112 |
+
continue
|
| 113 |
+
seen.add(k)
|
| 114 |
+
out.append(cand)
|
| 115 |
+
|
| 116 |
+
return out
|
space/space/space/space/space/space/space/reverse_pass/README.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Compiler Phase-Ordering RLVR (Toy-IR)
|
| 2 |
+
|
| 3 |
+
Toy-IR compiler phase-ordering environment for RL with verifiable rewards (RLVR), including reverse passes to escape local optima.
|
| 4 |
+
|
| 5 |
+
## What Is Implemented
|
| 6 |
+
|
| 7 |
+
- Reverse passes are available in the pass library:
|
| 8 |
+
- `expand_constant`
|
| 9 |
+
- `duplicate_computation`
|
| 10 |
+
- Action routing and validation include reverse passes.
|
| 11 |
+
- Prompt guidance includes reverse-pass descriptions and usage intent.
|
| 12 |
+
- Reward shaping is terminal-weighted with RLVR hard gate:
|
| 13 |
+
- non-equivalent -> `-1000.0`
|
| 14 |
+
- terminal -> `((original_cycles - current_cycles) / original_cycles) * 100.0`
|
| 15 |
+
- non-terminal -> `-0.1`
|
| 16 |
+
- Episode termination:
|
| 17 |
+
- `STOP`/`done`, or
|
| 18 |
+
- hard cap at 5 steps.
|
| 19 |
+
- Reverse-pass instrumentation logs every 50 episodes/completions:
|
| 20 |
+
- `reverse_pass_episodes`
|
| 21 |
+
- `expand_constant_count`
|
| 22 |
+
- `duplicate_computation_count`
|
| 23 |
+
|
| 24 |
+
## Verified So Far
|
| 25 |
+
|
| 26 |
+
- Smoke test runs end-to-end without runtime errors.
|
| 27 |
+
- Rewards are scalar floats (not NaN) in tested rollouts.
|
| 28 |
+
- Forced reverse-pass episodes show expected behavior:
|
| 29 |
+
- small negative intermediate rewards (`-0.1`)
|
| 30 |
+
- terminal reward depends on final outcome (positive if chain beats baseline, negative if not)
|
| 31 |
+
|
| 32 |
+
## Pending (GPU Required)
|
| 33 |
+
|
| 34 |
+
Comparative training runs are still pending and require a GPU environment with:
|
| 35 |
+
|
| 36 |
+
- `torch`
|
| 37 |
+
- `trl`
|
| 38 |
+
- `unsloth`
|
| 39 |
+
- `wandb`
|
| 40 |
+
|
| 41 |
+
Required fair comparison:
|
| 42 |
+
|
| 43 |
+
1. Baseline run: reverse passes disabled (`baseline_no_reverse`)
|
| 44 |
+
2. Reverse run: reverse passes enabled (`with_reverse_passes`)
|
| 45 |
+
3. Same episode budget for both runs
|
| 46 |
+
4. Compare reward/cycle curves and reverse-pass usage metrics in WandB
|
| 47 |
+
|
| 48 |
+
## Team Handoff Status
|
| 49 |
+
|
| 50 |
+
- Reverse-pass feature: shipped
|
| 51 |
+
- Training wiring + reward shaping + instrumentation: shipped
|
| 52 |
+
- Comparative training: pending in GPU environment
|
| 53 |
+
- README: this document
|
| 54 |
+
|
| 55 |
+
## Suggested Final Pre-Training Sanity Check
|
| 56 |
+
|
| 57 |
+
Run one smoke/equivalence check on a real Role 3 generated curriculum program (not just mock IR) to confirm verifier behavior in-pipeline before long training.
|
space/space/space/space/space/space/space/reverse_pass/compiler_optimization_grpo.ipynb
ADDED
|
@@ -0,0 +1,850 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "markdown",
|
| 5 |
+
"id": "title-cell",
|
| 6 |
+
"metadata": {},
|
| 7 |
+
"source": [
|
| 8 |
+
"# 🔧 Compiler Optimization RL Environment\n",
|
| 9 |
+
"### OpenEnv Hackathon 2026 — Theme #2: Long-Horizon Planning\n",
|
| 10 |
+
"\n",
|
| 11 |
+
"**What this notebook does:**\n",
|
| 12 |
+
"1. Defines a fully OpenEnv-compliant `CompilerOptimizationEnv`\n",
|
| 13 |
+
"2. Loads `Qwen2.5-3B-Instruct` via Unsloth (4-bit QLoRA)\n",
|
| 14 |
+
"3. Trains with GRPO (TRL) — LLM learns to pick compiler passes that reduce CPU cycles\n",
|
| 15 |
+
"4. Runs a smoke-test with a mock engine so you can verify reward logic without real hardware\n",
|
| 16 |
+
"5. Plots reward curves\n",
|
| 17 |
+
"\n",
|
| 18 |
+
"---\n",
|
| 19 |
+
"**Stack:** `unsloth` · `trl` · `openenv` · `wandb` · `matplotlib`\n",
|
| 20 |
+
"\n",
|
| 21 |
+
"> **Runtime:** Google Colab T4 GPU recommended. For the smoke-test only, CPU is fine."
|
| 22 |
+
]
|
| 23 |
+
},
|
| 24 |
+
{
|
| 25 |
+
"cell_type": "markdown",
|
| 26 |
+
"id": "install-header",
|
| 27 |
+
"metadata": {},
|
| 28 |
+
"source": [
|
| 29 |
+
"## 📦 Cell 1 — Install Dependencies"
|
| 30 |
+
]
|
| 31 |
+
},
|
| 32 |
+
{
|
| 33 |
+
"cell_type": "code",
|
| 34 |
+
"execution_count": null,
|
| 35 |
+
"id": "install-cell",
|
| 36 |
+
"metadata": {},
|
| 37 |
+
"outputs": [],
|
| 38 |
+
"source": [
|
| 39 |
+
"# Install all required packages\n",
|
| 40 |
+
"# Unsloth must be installed before trl to get the right CUDA kernels\n",
|
| 41 |
+
"!pip install \"unsloth[colab-new] @ git+https://github.com/unslothai/unsloth.git\" --quiet\n",
|
| 42 |
+
"!pip install trl datasets transformers accelerate peft bitsandbytes --quiet\n",
|
| 43 |
+
"!pip install wandb matplotlib --quiet\n",
|
| 44 |
+
"\n",
|
| 45 |
+
"# Optional: install openenv for production use\n",
|
| 46 |
+
"# !pip install openenv --quiet\n",
|
| 47 |
+
"\n",
|
| 48 |
+
"print(\"✅ All packages installed\")"
|
| 49 |
+
]
|
| 50 |
+
},
|
| 51 |
+
{
|
| 52 |
+
"cell_type": "markdown",
|
| 53 |
+
"id": "env-header",
|
| 54 |
+
"metadata": {},
|
| 55 |
+
"source": [
|
| 56 |
+
"## 🌍 Cell 2 — CompilerOptimizationEnv (OpenEnv-Compliant)"
|
| 57 |
+
]
|
| 58 |
+
},
|
| 59 |
+
{
|
| 60 |
+
"cell_type": "code",
|
| 61 |
+
"execution_count": null,
|
| 62 |
+
"id": "env-cell",
|
| 63 |
+
"metadata": {},
|
| 64 |
+
"outputs": [],
|
| 65 |
+
"source": [
|
| 66 |
+
"# CompilerOptimizationEnv, PASSES, reverse passes, terminal-weighted reward (toyir_rl_support)\n",
|
| 67 |
+
"import os\n",
|
| 68 |
+
"import sys\n",
|
| 69 |
+
"\n",
|
| 70 |
+
"if os.getcwd() not in sys.path:\n",
|
| 71 |
+
" sys.path.insert(0, os.getcwd())\n",
|
| 72 |
+
"\n",
|
| 73 |
+
"from toyir_rl_support import (\n",
|
| 74 |
+
" MCPEnvironment,\n",
|
| 75 |
+
" StepResult,\n",
|
| 76 |
+
" EpisodeStats,\n",
|
| 77 |
+
" CompilerOptimizationEnv,\n",
|
| 78 |
+
" PASSES,\n",
|
| 79 |
+
" MOCK_PASSES,\n",
|
| 80 |
+
" compute_shaped_reward,\n",
|
| 81 |
+
" rollout_shaped_return,\n",
|
| 82 |
+
" log_reverse_pass_stats_for_completion,\n",
|
| 83 |
+
" ensure_import_path,\n",
|
| 84 |
+
" MockEngine,\n",
|
| 85 |
+
" MOCK_ENGINE,\n",
|
| 86 |
+
" SAMPLE_PROGRAM,\n",
|
| 87 |
+
")\n",
|
| 88 |
+
"\n",
|
| 89 |
+
"ensure_import_path()\n",
|
| 90 |
+
"print(\"✅ Loaded CompilerOptimizationEnv, PASSES, and reward helpers from toyir_rl_support\")\n"
|
| 91 |
+
]
|
| 92 |
+
},
|
| 93 |
+
{
|
| 94 |
+
"cell_type": "markdown",
|
| 95 |
+
"id": "smoke-header",
|
| 96 |
+
"metadata": {},
|
| 97 |
+
"source": [
|
| 98 |
+
"## 🧪 Cell 3 — Smoke Test (No GPU / Real Engine Needed)\n",
|
| 99 |
+
"Validates the entire reward pipeline with a mock engine. Run this before spending compute."
|
| 100 |
+
]
|
| 101 |
+
},
|
| 102 |
+
{
|
| 103 |
+
"cell_type": "code",
|
| 104 |
+
"execution_count": null,
|
| 105 |
+
"id": "smoke-test-cell",
|
| 106 |
+
"metadata": {},
|
| 107 |
+
"outputs": [],
|
| 108 |
+
"source": [
|
| 109 |
+
"# Smoke test (MockEngine; same PASSES as training)\n",
|
| 110 |
+
"engine = MockEngine()\n",
|
| 111 |
+
"env = CompilerOptimizationEnv(\n",
|
| 112 |
+
" engine, MOCK_PASSES, max_steps=CompilerOptimizationEnv.MAX_EPISODE_STEPS\n",
|
| 113 |
+
")\n",
|
| 114 |
+
"obs = env.reset(SAMPLE_PROGRAM)\n",
|
| 115 |
+
"\n",
|
| 116 |
+
"print(\"═\" * 55)\n",
|
| 117 |
+
"print(\"SMOKE TEST\")\n",
|
| 118 |
+
"print(\"═\" * 55)\n",
|
| 119 |
+
"print(f\"Initial state:\\n{obs}\")\n",
|
| 120 |
+
"print(f\"\\nBaseline cycles : {env.previous_cycles}\")\n",
|
| 121 |
+
"print(f\"Available actions: {env.available_actions()}\")\n",
|
| 122 |
+
"print()\n",
|
| 123 |
+
"\n",
|
| 124 |
+
"actions_to_try = [\n",
|
| 125 |
+
" \"constant_folding\",\n",
|
| 126 |
+
" \"dead_code_elimination\",\n",
|
| 127 |
+
" \"peephole_optimization\", # no-op\n",
|
| 128 |
+
" \"hallucinated_pass\", # invalid — but won't kill episode yet\n",
|
| 129 |
+
" \"constant_folding\",\n",
|
| 130 |
+
" \"dead_code_elimination\",\n",
|
| 131 |
+
" \"expand_constant\",\n",
|
| 132 |
+
" \"STOP\",\n",
|
| 133 |
+
"]\n",
|
| 134 |
+
"\n",
|
| 135 |
+
"for action in actions_to_try:\n",
|
| 136 |
+
" result = env.step(action)\n",
|
| 137 |
+
" tag = \"✗\" if result.reward < 0 else \"✓\"\n",
|
| 138 |
+
" print(f\"{tag} '{action}'\")\n",
|
| 139 |
+
" print(f\" reward={result.reward:+.2f} done={result.done} is_terminal={result.info.get('is_terminal')}\")\n",
|
| 140 |
+
" relevant = {k: v for k, v in result.info.items()\n",
|
| 141 |
+
" if k in (\"delta_pct\", \"error\", \"no_op\", \"reason\",\n",
|
| 142 |
+
" \"terminal_bonus\", \"episode_stats\", \"is_terminal\")}\n",
|
| 143 |
+
" if relevant:\n",
|
| 144 |
+
" print(f\" info: {relevant}\")\n",
|
| 145 |
+
" print()\n",
|
| 146 |
+
" if result.done:\n",
|
| 147 |
+
" break\n",
|
| 148 |
+
"\n",
|
| 149 |
+
"print(\"✅ Smoke test passed\")\n"
|
| 150 |
+
]
|
| 151 |
+
},
|
| 152 |
+
{
|
| 153 |
+
"cell_type": "markdown",
|
| 154 |
+
"id": "reward-plot-header",
|
| 155 |
+
"metadata": {},
|
| 156 |
+
"source": [
|
| 157 |
+
"## 📊 Cell 4 — Visualise Reward Across a Mock Episode"
|
| 158 |
+
]
|
| 159 |
+
},
|
| 160 |
+
{
|
| 161 |
+
"cell_type": "code",
|
| 162 |
+
"execution_count": null,
|
| 163 |
+
"id": "reward-plot-cell",
|
| 164 |
+
"metadata": {},
|
| 165 |
+
"outputs": [],
|
| 166 |
+
"source": [
|
| 167 |
+
"import matplotlib.pyplot as plt\n",
|
| 168 |
+
"import matplotlib.ticker as ticker\n",
|
| 169 |
+
"\n",
|
| 170 |
+
"# Run a full episode and collect data\n",
|
| 171 |
+
"env = CompilerOptimizationEnv(MockEngine(), MOCK_PASSES, max_steps=CompilerOptimizationEnv.MAX_EPISODE_STEPS)\n",
|
| 172 |
+
"env.reset(SAMPLE_PROGRAM)\n",
|
| 173 |
+
"\n",
|
| 174 |
+
"sequence = [\n",
|
| 175 |
+
" \"constant_folding\", \"dead_code_elimination\", \"peephole_optimization\",\n",
|
| 176 |
+
" \"expand_constant\", \"peephole_optimization\",\n",
|
| 177 |
+
"]\n",
|
| 178 |
+
"\n",
|
| 179 |
+
"rewards, cycle_counts, actions_log = [], [], []\n",
|
| 180 |
+
"for act in sequence:\n",
|
| 181 |
+
" r = env.step(act)\n",
|
| 182 |
+
" rewards.append(r.reward)\n",
|
| 183 |
+
" cycle_counts.append(env.previous_cycles)\n",
|
| 184 |
+
" actions_log.append(act)\n",
|
| 185 |
+
" if r.done:\n",
|
| 186 |
+
" break\n",
|
| 187 |
+
"\n",
|
| 188 |
+
"steps = list(range(1, len(rewards) + 1))\n",
|
| 189 |
+
"\n",
|
| 190 |
+
"fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 6), sharex=True)\n",
|
| 191 |
+
"fig.suptitle(\"Compiler Optimization Episode — Mock Engine\", fontsize=14, fontweight=\"bold\")\n",
|
| 192 |
+
"\n",
|
| 193 |
+
"# Reward per step\n",
|
| 194 |
+
"colors = [\"#2ecc71\" if r >= 0 else \"#e74c3c\" for r in rewards]\n",
|
| 195 |
+
"ax1.bar(steps, rewards, color=colors, edgecolor=\"white\", linewidth=0.5)\n",
|
| 196 |
+
"ax1.axhline(0, color=\"grey\", linewidth=0.8, linestyle=\"--\")\n",
|
| 197 |
+
"ax1.set_ylabel(\"Reward\")\n",
|
| 198 |
+
"ax1.set_title(\"Reward per Step (green = positive, red = negative)\")\n",
|
| 199 |
+
"ax1.yaxis.set_major_formatter(ticker.FormatStrFormatter(\"%.1f\"))\n",
|
| 200 |
+
"\n",
|
| 201 |
+
"# Cycle count over time\n",
|
| 202 |
+
"ax2.plot(steps, cycle_counts, marker=\"o\", color=\"#3498db\", linewidth=2, markersize=6)\n",
|
| 203 |
+
"ax2.set_xlabel(\"Step\")\n",
|
| 204 |
+
"ax2.set_ylabel(\"CPU Cycles\")\n",
|
| 205 |
+
"ax2.set_title(\"CPU Cycle Count Over Episode (lower = better)\")\n",
|
| 206 |
+
"ax2.set_xticks(steps)\n",
|
| 207 |
+
"ax2.set_xticklabels(\n",
|
| 208 |
+
" [a.replace(\"_\", \"\\n\") for a in actions_log],\n",
|
| 209 |
+
" fontsize=7,\n",
|
| 210 |
+
")\n",
|
| 211 |
+
"\n",
|
| 212 |
+
"plt.tight_layout()\n",
|
| 213 |
+
"plt.savefig(\"episode_reward_curve.png\", dpi=150, bbox_inches=\"tight\")\n",
|
| 214 |
+
"plt.show()\n",
|
| 215 |
+
"print(\"📈 Plot saved as episode_reward_curve.png\")\n"
|
| 216 |
+
]
|
| 217 |
+
},
|
| 218 |
+
{
|
| 219 |
+
"cell_type": "markdown",
|
| 220 |
+
"id": "model-header",
|
| 221 |
+
"metadata": {},
|
| 222 |
+
"source": [
|
| 223 |
+
"## 🤖 Cell 5 — Load Model with Unsloth (QLoRA 4-bit)\n",
|
| 224 |
+
"> **Requires T4 GPU.** Skip to Cell 9 if you only want to test the environment."
|
| 225 |
+
]
|
| 226 |
+
},
|
| 227 |
+
{
|
| 228 |
+
"cell_type": "code",
|
| 229 |
+
"execution_count": null,
|
| 230 |
+
"id": "model-cell",
|
| 231 |
+
"metadata": {},
|
| 232 |
+
"outputs": [],
|
| 233 |
+
"source": [
|
| 234 |
+
"import torch\n",
|
| 235 |
+
"from unsloth import FastLanguageModel\n",
|
| 236 |
+
"\n",
|
| 237 |
+
"MODEL_NAME = \"unsloth/Qwen2.5-3B-Instruct\" # swap to 7B if VRAM allows\n",
|
| 238 |
+
"MAX_SEQ_LEN = 1024\n",
|
| 239 |
+
"LORA_RANK = 16\n",
|
| 240 |
+
"\n",
|
| 241 |
+
"model, tokenizer = FastLanguageModel.from_pretrained(\n",
|
| 242 |
+
" model_name = MODEL_NAME,\n",
|
| 243 |
+
" max_seq_length = MAX_SEQ_LEN,\n",
|
| 244 |
+
" dtype = None, # auto bf16/fp16\n",
|
| 245 |
+
" load_in_4bit = True,\n",
|
| 246 |
+
")\n",
|
| 247 |
+
"\n",
|
| 248 |
+
"model = FastLanguageModel.get_peft_model(\n",
|
| 249 |
+
" model,\n",
|
| 250 |
+
" r = LORA_RANK,\n",
|
| 251 |
+
" target_modules = [\"q_proj\", \"k_proj\", \"v_proj\", \"o_proj\",\n",
|
| 252 |
+
" \"gate_proj\", \"up_proj\", \"down_proj\"],\n",
|
| 253 |
+
" lora_alpha = LORA_RANK * 2,\n",
|
| 254 |
+
" lora_dropout = 0.0,\n",
|
| 255 |
+
" bias = \"none\",\n",
|
| 256 |
+
" use_gradient_checkpointing = \"unsloth\",\n",
|
| 257 |
+
" random_state = 42,\n",
|
| 258 |
+
")\n",
|
| 259 |
+
"\n",
|
| 260 |
+
"print(f\"✅ Loaded {MODEL_NAME} with QLoRA rank={LORA_RANK}\")\n",
|
| 261 |
+
"print(f\" GPU memory allocated: {torch.cuda.memory_allocated() / 1e9:.2f} GB\")"
|
| 262 |
+
]
|
| 263 |
+
},
|
| 264 |
+
{
|
| 265 |
+
"cell_type": "markdown",
|
| 266 |
+
"id": "prompt-header",
|
| 267 |
+
"metadata": {},
|
| 268 |
+
"source": [
|
| 269 |
+
"## 💬 Cell 6 — System Prompt & Dataset Builder"
|
| 270 |
+
]
|
| 271 |
+
},
|
| 272 |
+
{
|
| 273 |
+
"cell_type": "code",
|
| 274 |
+
"execution_count": null,
|
| 275 |
+
"id": "prompt-cell",
|
| 276 |
+
"metadata": {},
|
| 277 |
+
"outputs": [],
|
| 278 |
+
"source": [
|
| 279 |
+
"import json\n",
|
| 280 |
+
"import re\n",
|
| 281 |
+
"from datasets import Dataset\n",
|
| 282 |
+
"\n",
|
| 283 |
+
"\n",
|
| 284 |
+
"class Deliverable2_Formatter:\n",
|
| 285 |
+
" 'State translation + robust action-array extraction for Role 2.'\n",
|
| 286 |
+
"\n",
|
| 287 |
+
" @staticmethod\n",
|
| 288 |
+
" def translate_state(raw_json: list) -> str:\n",
|
| 289 |
+
" pseudo_assembly = []\n",
|
| 290 |
+
" for i, instruction in enumerate(raw_json):\n",
|
| 291 |
+
" if not isinstance(instruction, dict):\n",
|
| 292 |
+
" pseudo_assembly.append(f\"{i}. NOP\")\n",
|
| 293 |
+
" continue\n",
|
| 294 |
+
" op = str(instruction.get(\"op\", \"UNKNOWN\")).upper()\n",
|
| 295 |
+
" args = \", \".join(str(arg) for arg in instruction.get(\"args\", []))\n",
|
| 296 |
+
" dest = instruction.get(\"dest\", \"\")\n",
|
| 297 |
+
" if dest:\n",
|
| 298 |
+
" pseudo_assembly.append(f\"{i}. {dest} = {op} {args}\".rstrip())\n",
|
| 299 |
+
" else:\n",
|
| 300 |
+
" pseudo_assembly.append(f\"{i}. {op} {args}\".rstrip())\n",
|
| 301 |
+
" return \"\\n\".join(pseudo_assembly) if pseudo_assembly else \"; (empty program)\"\n",
|
| 302 |
+
"\n",
|
| 303 |
+
" @staticmethod\n",
|
| 304 |
+
" def extract_action_array(llm_output: str) -> list:\n",
|
| 305 |
+
" text = (llm_output or \"\").strip()\n",
|
| 306 |
+
" if not text:\n",
|
| 307 |
+
" raise ValueError(\"Invalid JSON format\")\n",
|
| 308 |
+
"\n",
|
| 309 |
+
" try:\n",
|
| 310 |
+
" parsed = json.loads(text)\n",
|
| 311 |
+
" if isinstance(parsed, list):\n",
|
| 312 |
+
" return parsed\n",
|
| 313 |
+
" except json.JSONDecodeError:\n",
|
| 314 |
+
" pass\n",
|
| 315 |
+
"\n",
|
| 316 |
+
" match = re.search(r\"\\[.*?\\]\", text, re.DOTALL)\n",
|
| 317 |
+
" if match:\n",
|
| 318 |
+
" try:\n",
|
| 319 |
+
" parsed = json.loads(match.group(0))\n",
|
| 320 |
+
" if isinstance(parsed, list):\n",
|
| 321 |
+
" return parsed\n",
|
| 322 |
+
" except json.JSONDecodeError:\n",
|
| 323 |
+
" pass\n",
|
| 324 |
+
"\n",
|
| 325 |
+
" raise ValueError(\"Invalid JSON format\")\n",
|
| 326 |
+
"\n",
|
| 327 |
+
"\n",
|
| 328 |
+
"def build_system_prompt(passes: dict) -> str:\n",
|
| 329 |
+
" lines: list = []\n",
|
| 330 |
+
" for name in sorted(passes.keys()):\n",
|
| 331 |
+
" if name == \"expand_constant\":\n",
|
| 332 |
+
" lines.append(\n",
|
| 333 |
+
" \" - expand_constant: Splits a constant assignment into runtime arithmetic (e.g., CONST 8 becomes CONST 3 + ADD 5). May temporarily increase cycles but can enable forward passes to find better optimization chains. Use sparingly when standard passes seem stuck.\"\n",
|
| 334 |
+
" )\n",
|
| 335 |
+
" elif name == \"duplicate_computation\":\n",
|
| 336 |
+
" lines.append(\n",
|
| 337 |
+
" \" - duplicate_computation: Creates a redundant copy of a binary operation with a fresh variable. May temporarily increase cycles but can enable alternative dead code elimination paths. Use when redundancy might unlock further simplification.\"\n",
|
| 338 |
+
" )\n",
|
| 339 |
+
" else:\n",
|
| 340 |
+
" lines.append(f\" - {name}\")\n",
|
| 341 |
+
" action_block = \"\\n\".join(lines)\n",
|
| 342 |
+
" return (\n",
|
| 343 |
+
" \"You are a compiler optimization agent. Your goal is to reduce \"\n",
|
| 344 |
+
" \"CPU cycle count by applying optimization passes to the program below.\\n\\n\"\n",
|
| 345 |
+
" f\"Available actions:\\n{action_block}\\n\"\n",
|
| 346 |
+
" \" - STOP (emit this in the array to stop; optional alias: \\\"done\\\". Stop early if no further improvement is possible.)\\n\\n\"\n",
|
| 347 |
+
" \"Rules:\\n\"\n",
|
| 348 |
+
" \" • Output only a JSON array of pass names (example: [\\\"constant_folding\\\"]).\\n\"\n",
|
| 349 |
+
" \" • No explanation, no markdown, no extra text.\\n\"\n",
|
| 350 |
+
" \" • Do not invent actions not listed above.\\n\"\n",
|
| 351 |
+
" \" • Maximum 5 passes per response (hard episode cap: 5 optimization steps or STOP).\\n\"\n",
|
| 352 |
+
" )\n",
|
| 353 |
+
"\n",
|
| 354 |
+
"\n",
|
| 355 |
+
"def build_dataset(\n",
|
| 356 |
+
" programs: list,\n",
|
| 357 |
+
" engine,\n",
|
| 358 |
+
" passes: dict,\n",
|
| 359 |
+
") -> Dataset:\n",
|
| 360 |
+
" # Each row = one episode; GRPO samples K completions per row\n",
|
| 361 |
+
" env = CompilerOptimizationEnv(\n",
|
| 362 |
+
" engine, passes, max_steps=CompilerOptimizationEnv.MAX_EPISODE_STEPS\n",
|
| 363 |
+
" )\n",
|
| 364 |
+
" system_prompt = build_system_prompt(passes)\n",
|
| 365 |
+
"\n",
|
| 366 |
+
" rows = []\n",
|
| 367 |
+
" for prog in programs:\n",
|
| 368 |
+
" translated_state = Deliverable2_Formatter.translate_state(prog)\n",
|
| 369 |
+
" prompt = [\n",
|
| 370 |
+
" {\"role\": \"system\", \"content\": system_prompt},\n",
|
| 371 |
+
" {\"role\": \"user\", \"content\": f\"Current program:\\n{translated_state}\\n\\nChoose optimization passes:\"},\n",
|
| 372 |
+
" ]\n",
|
| 373 |
+
" rows.append({\"prompt\": prompt, \"program_json\": json.dumps(prog)})\n",
|
| 374 |
+
"\n",
|
| 375 |
+
" return Dataset.from_list(rows)\n",
|
| 376 |
+
"\n",
|
| 377 |
+
"\n",
|
| 378 |
+
"# --- Demo: build dataset from mock programs -----------------------------------\n",
|
| 379 |
+
"def make_mock_programs(n: int = 20) -> list:\n",
|
| 380 |
+
" # Generate N random mock IR programs\n",
|
| 381 |
+
" import random\n",
|
| 382 |
+
" ops = [\"add\", \"mul\", \"sub\", \"const\", \"load\"]\n",
|
| 383 |
+
" progs = []\n",
|
| 384 |
+
" for _ in range(n):\n",
|
| 385 |
+
" length = random.randint(4, 12)\n",
|
| 386 |
+
" prog = [\n",
|
| 387 |
+
" {\n",
|
| 388 |
+
" \"op\": random.choice(ops),\n",
|
| 389 |
+
" \"dest\": f\"v{i}\",\n",
|
| 390 |
+
" \"args\": [f\"v{max(0, i-1)}\"],\n",
|
| 391 |
+
" \"type\": \"int\",\n",
|
| 392 |
+
" }\n",
|
| 393 |
+
" for i in range(length)\n",
|
| 394 |
+
" ]\n",
|
| 395 |
+
" prog.append({\"op\": \"ret\", \"args\": [f\"v{length-1}\"]})\n",
|
| 396 |
+
" progs.append(prog)\n",
|
| 397 |
+
" return progs\n",
|
| 398 |
+
"\n",
|
| 399 |
+
"\n",
|
| 400 |
+
"mock_programs = make_mock_programs(n=30)\n",
|
| 401 |
+
"train_dataset = build_dataset(mock_programs, MockEngine(), MOCK_PASSES)\n",
|
| 402 |
+
"\n",
|
| 403 |
+
"print(f\"✅ Dataset built: {len(train_dataset)} episodes\")\n",
|
| 404 |
+
"print(f\" Sample prompt keys: {list(train_dataset[0].keys())}\")\n"
|
| 405 |
+
]
|
| 406 |
+
},
|
| 407 |
+
{
|
| 408 |
+
"cell_type": "markdown",
|
| 409 |
+
"id": "reward-fn-header",
|
| 410 |
+
"metadata": {},
|
| 411 |
+
"source": [
|
| 412 |
+
"## 🎯 Cell 7 — Reward Function Factory (for GRPO)"
|
| 413 |
+
]
|
| 414 |
+
},
|
| 415 |
+
{
|
| 416 |
+
"cell_type": "code",
|
| 417 |
+
"execution_count": null,
|
| 418 |
+
"id": "reward-fn-cell",
|
| 419 |
+
"metadata": {},
|
| 420 |
+
"outputs": [],
|
| 421 |
+
"source": [
|
| 422 |
+
"import json\n",
|
| 423 |
+
"from typing import Any\n",
|
| 424 |
+
"\n",
|
| 425 |
+
"# Rollout + terminal weighting; reverse-pass W&B window logging\n",
|
| 426 |
+
"from toyir_rl_support import (\n",
|
| 427 |
+
" CompilerOptimizationEnv,\n",
|
| 428 |
+
" MOCK_PASSES,\n",
|
| 429 |
+
" rollout_shaped_return,\n",
|
| 430 |
+
" log_reverse_pass_stats_for_completion,\n",
|
| 431 |
+
" SAMPLE_PROGRAM,\n",
|
| 432 |
+
")\n",
|
| 433 |
+
"\n",
|
| 434 |
+
"EP_CAP = CompilerOptimizationEnv.MAX_EPISODE_STEPS\n",
|
| 435 |
+
"\n",
|
| 436 |
+
"\n",
|
| 437 |
+
"def _normalize_action_seq(actions: list, cap: int, passes: dict) -> list:\n",
|
| 438 |
+
" out: list = []\n",
|
| 439 |
+
" for x in actions[:cap]:\n",
|
| 440 |
+
" raw = str(x).strip()\n",
|
| 441 |
+
" s_low = raw.lower()\n",
|
| 442 |
+
" if s_low in (\"stop\", \"done\"):\n",
|
| 443 |
+
" out.append(\"done\" if s_low == \"done\" else \"STOP\")\n",
|
| 444 |
+
" continue\n",
|
| 445 |
+
" key = None\n",
|
| 446 |
+
" for k in passes:\n",
|
| 447 |
+
" if k.lower() == s_low:\n",
|
| 448 |
+
" key = k\n",
|
| 449 |
+
" break\n",
|
| 450 |
+
" if key is None:\n",
|
| 451 |
+
" out.append(raw)\n",
|
| 452 |
+
" else:\n",
|
| 453 |
+
" out.append(key)\n",
|
| 454 |
+
" return out\n",
|
| 455 |
+
"\n",
|
| 456 |
+
"\n",
|
| 457 |
+
"def make_reward_fn(engine, passes, max_steps: int = EP_CAP):\n",
|
| 458 |
+
" # TRL GRPO reward: terminal-weighted `rollout_shaped_return` + reverse-pass logging\n",
|
| 459 |
+
" _cap = min(int(max_steps), EP_CAP)\n",
|
| 460 |
+
"\n",
|
| 461 |
+
" def reward_fn(prompts, completions, **kwargs):\n",
|
| 462 |
+
" programs = kwargs.get(\"program_json\", [None] * len(completions))\n",
|
| 463 |
+
" rewards: list = []\n",
|
| 464 |
+
"\n",
|
| 465 |
+
" for completion, prog_json in zip(completions, programs):\n",
|
| 466 |
+
" raw = completion if isinstance(completion, str) else completion[0][\"content\"]\n",
|
| 467 |
+
" if prog_json is None:\n",
|
| 468 |
+
" rewards.append(CompilerOptimizationEnv.INVALID_ACTION_PENALTY)\n",
|
| 469 |
+
" continue\n",
|
| 470 |
+
" program = (\n",
|
| 471 |
+
" json.loads(prog_json) if isinstance(prog_json, str) else list(prog_json)\n",
|
| 472 |
+
" )\n",
|
| 473 |
+
" if not program:\n",
|
| 474 |
+
" rewards.append(CompilerOptimizationEnv.INVALID_ACTION_PENALTY)\n",
|
| 475 |
+
" continue\n",
|
| 476 |
+
" try:\n",
|
| 477 |
+
" actions = Deliverable2_Formatter.extract_action_array(raw)\n",
|
| 478 |
+
" except ValueError:\n",
|
| 479 |
+
" rewards.append(CompilerOptimizationEnv.INVALID_ACTION_PENALTY)\n",
|
| 480 |
+
" continue\n",
|
| 481 |
+
" if not actions:\n",
|
| 482 |
+
" rewards.append(CompilerOptimizationEnv.INVALID_ACTION_PENALTY)\n",
|
| 483 |
+
" continue\n",
|
| 484 |
+
" norm = _normalize_action_seq(actions, _cap, passes)\n",
|
| 485 |
+
" tr, n_e, n_d, _hs, _hc, ok = rollout_shaped_return(\n",
|
| 486 |
+
" program, norm, engine, passes\n",
|
| 487 |
+
" )\n",
|
| 488 |
+
" if not ok:\n",
|
| 489 |
+
" rewards.append(-1000.0)\n",
|
| 490 |
+
" else:\n",
|
| 491 |
+
" log_reverse_pass_stats_for_completion(n_e, n_d)\n",
|
| 492 |
+
" rewards.append(tr)\n",
|
| 493 |
+
" return rewards\n",
|
| 494 |
+
"\n",
|
| 495 |
+
" return reward_fn\n",
|
| 496 |
+
"\n",
|
| 497 |
+
"\n",
|
| 498 |
+
"reward_fn = make_reward_fn(MockEngine(), MOCK_PASSES, max_steps=EP_CAP)\n",
|
| 499 |
+
"print(\"✅ Reward function factory ready\")\n",
|
| 500 |
+
"\n",
|
| 501 |
+
"# Quick sanity check\n",
|
| 502 |
+
"test_completions = [\n",
|
| 503 |
+
" '[\"constant_folding\"]',\n",
|
| 504 |
+
" 'Here is my plan: [\"dead_code_elimination\", \"peephole_optimization\", \"done\"]',\n",
|
| 505 |
+
" \"hallucinated_pass\",\n",
|
| 506 |
+
"]\n",
|
| 507 |
+
"test_programs = [json.dumps(SAMPLE_PROGRAM)] * 3\n",
|
| 508 |
+
"test_rewards = reward_fn(\n",
|
| 509 |
+
" prompts=[\"\"] * 3,\n",
|
| 510 |
+
" completions=test_completions,\n",
|
| 511 |
+
" program_json=test_programs,\n",
|
| 512 |
+
")\n",
|
| 513 |
+
"print(\"\\nReward sanity check:\")\n",
|
| 514 |
+
"for act, rew in zip(test_completions, test_rewards):\n",
|
| 515 |
+
" print(f\" {act!r} → {rew:+.2f}\")\n"
|
| 516 |
+
]
|
| 517 |
+
},
|
| 518 |
+
{
|
| 519 |
+
"cell_type": "markdown",
|
| 520 |
+
"id": "trainer-header",
|
| 521 |
+
"metadata": {},
|
| 522 |
+
"source": [
|
| 523 |
+
"## 🚀 Cell 8 — GRPO Trainer Config & Training"
|
| 524 |
+
]
|
| 525 |
+
},
|
| 526 |
+
{
|
| 527 |
+
"cell_type": "code",
|
| 528 |
+
"execution_count": null,
|
| 529 |
+
"id": "trainer-cell",
|
| 530 |
+
"metadata": {},
|
| 531 |
+
"outputs": [],
|
| 532 |
+
"source": [
|
| 533 |
+
"from trl import GRPOConfig, GRPOTrainer\n",
|
| 534 |
+
"\n",
|
| 535 |
+
"# Optional W&B — comment out if not using\n",
|
| 536 |
+
"try:\n",
|
| 537 |
+
" import wandb\n",
|
| 538 |
+
" wandb.init(project=\"openenv-compiler-opt\", name=\"grpo-qwen2.5-3b\")\n",
|
| 539 |
+
" REPORT_TO = \"wandb\"\n",
|
| 540 |
+
"except Exception:\n",
|
| 541 |
+
" REPORT_TO = \"none\"\n",
|
| 542 |
+
"\n",
|
| 543 |
+
"\n",
|
| 544 |
+
"grpo_config = GRPOConfig(\n",
|
| 545 |
+
" # ── Generation ────────────────────────────────────────────────────────\n",
|
| 546 |
+
" num_generations = 4, # K rollouts per prompt for group-relative advantage\n",
|
| 547 |
+
" max_new_tokens = 16, # Actions are 1 word; don't waste context\n",
|
| 548 |
+
" temperature = 0.9,\n",
|
| 549 |
+
" top_p = 0.95,\n",
|
| 550 |
+
"\n",
|
| 551 |
+
" # ── Optimisation ──────────────────────────────────────────────────────\n",
|
| 552 |
+
" learning_rate = 5e-6,\n",
|
| 553 |
+
" per_device_train_batch_size = 2,\n",
|
| 554 |
+
" gradient_accumulation_steps = 4, # effective batch = 8\n",
|
| 555 |
+
" num_train_epochs = 3,\n",
|
| 556 |
+
" max_grad_norm = 0.5,\n",
|
| 557 |
+
"\n",
|
| 558 |
+
" # ── GRPO-specific ─────────────────────────────────────────────────────\n",
|
| 559 |
+
" beta = 0.04, # KL penalty; keeps policy near reference\n",
|
| 560 |
+
"\n",
|
| 561 |
+
" # ── Logging / checkpointing ───────────────────────────────────────────\n",
|
| 562 |
+
" output_dir = \"./grpo_compiler_checkpoints\",\n",
|
| 563 |
+
" logging_steps = 10,\n",
|
| 564 |
+
" save_steps = 100,\n",
|
| 565 |
+
" report_to = REPORT_TO,\n",
|
| 566 |
+
"\n",
|
| 567 |
+
" # ── Reproducibility ───────────────────────────────────────────────────\n",
|
| 568 |
+
" seed = 42,\n",
|
| 569 |
+
")\n",
|
| 570 |
+
"\n",
|
| 571 |
+
"trainer = GRPOTrainer(\n",
|
| 572 |
+
" model = model,\n",
|
| 573 |
+
" tokenizer = tokenizer,\n",
|
| 574 |
+
" config = grpo_config,\n",
|
| 575 |
+
" train_dataset = train_dataset,\n",
|
| 576 |
+
" reward_funcs = reward_fn,\n",
|
| 577 |
+
")\n",
|
| 578 |
+
"\n",
|
| 579 |
+
"print(\"✅ Trainer configured\")\n",
|
| 580 |
+
"print(f\" num_generations (K) = {grpo_config.num_generations}\")\n",
|
| 581 |
+
"print(f\" effective batch size = \"\n",
|
| 582 |
+
" f\"{grpo_config.per_device_train_batch_size * grpo_config.gradient_accumulation_steps}\")\n",
|
| 583 |
+
"print(f\" KL beta = {grpo_config.beta}\")\n",
|
| 584 |
+
"print()\n",
|
| 585 |
+
"print(\"Starting training... (this will take a while on T4)\")\n",
|
| 586 |
+
"trainer.train()"
|
| 587 |
+
]
|
| 588 |
+
},
|
| 589 |
+
{
|
| 590 |
+
"cell_type": "markdown",
|
| 591 |
+
"id": "save-header",
|
| 592 |
+
"metadata": {},
|
| 593 |
+
"source": [
|
| 594 |
+
"## 💾 Cell 9 — Save Model"
|
| 595 |
+
]
|
| 596 |
+
},
|
| 597 |
+
{
|
| 598 |
+
"cell_type": "code",
|
| 599 |
+
"execution_count": null,
|
| 600 |
+
"id": "save-cell",
|
| 601 |
+
"metadata": {},
|
| 602 |
+
"outputs": [],
|
| 603 |
+
"source": [
|
| 604 |
+
"SAVE_PATH = \"./grpo_compiler_final\"\n",
|
| 605 |
+
"\n",
|
| 606 |
+
"model.save_pretrained(SAVE_PATH)\n",
|
| 607 |
+
"tokenizer.save_pretrained(SAVE_PATH)\n",
|
| 608 |
+
"\n",
|
| 609 |
+
"print(f\"✅ Model saved to {SAVE_PATH}\")\n",
|
| 610 |
+
"\n",
|
| 611 |
+
"# Optional: push to HuggingFace Hub\n",
|
| 612 |
+
"# model.push_to_hub(\"your-hf-username/compiler-opt-grpo\")\n",
|
| 613 |
+
"# tokenizer.push_to_hub(\"your-hf-username/compiler-opt-grpo\")"
|
| 614 |
+
]
|
| 615 |
+
},
|
| 616 |
+
{
|
| 617 |
+
"cell_type": "markdown",
|
| 618 |
+
"id": "curriculum-header",
|
| 619 |
+
"metadata": {},
|
| 620 |
+
"source": [
|
| 621 |
+
"## 📈 Cell 10 — Curriculum Callback & Reward Tracking"
|
| 622 |
+
]
|
| 623 |
+
},
|
| 624 |
+
{
|
| 625 |
+
"cell_type": "code",
|
| 626 |
+
"execution_count": null,
|
| 627 |
+
"id": "curriculum-cell",
|
| 628 |
+
"metadata": {},
|
| 629 |
+
"outputs": [],
|
| 630 |
+
"source": [
|
| 631 |
+
"class CurriculumCallback:\n",
|
| 632 |
+
" \"\"\"\n",
|
| 633 |
+
" Tracks rolling mean reward and promotes curriculum level\n",
|
| 634 |
+
" when the agent has mastered the current difficulty.\n",
|
| 635 |
+
"\n",
|
| 636 |
+
" Usage: call .record(reward) after every episode.\n",
|
| 637 |
+
" Read .level to get current difficulty (1=easy, 2=medium, 3=hard).\n",
|
| 638 |
+
" \"\"\"\n",
|
| 639 |
+
" def __init__(self, reward_threshold: float = 5.0, window: int = 50):\n",
|
| 640 |
+
" self.threshold = reward_threshold\n",
|
| 641 |
+
" self.window = window\n",
|
| 642 |
+
" self._history = []\n",
|
| 643 |
+
" self.level = 1\n",
|
| 644 |
+
" self._promotions = []\n",
|
| 645 |
+
"\n",
|
| 646 |
+
" def record(self, reward: float, step: int = None):\n",
|
| 647 |
+
" self._history.append(reward)\n",
|
| 648 |
+
" if len(self._history) >= self.window:\n",
|
| 649 |
+
" mean = sum(self._history[-self.window:]) / self.window\n",
|
| 650 |
+
" if mean >= self.threshold and self.level < 3:\n",
|
| 651 |
+
" self.level += 1\n",
|
| 652 |
+
" self._promotions.append((step or len(self._history), self.level))\n",
|
| 653 |
+
" print(f\"[Curriculum] ▲ Promoted to level {self.level} \"\n",
|
| 654 |
+
" f\"(rolling mean={mean:.2f})\")\n",
|
| 655 |
+
"\n",
|
| 656 |
+
" def plot(self):\n",
|
| 657 |
+
" import matplotlib.pyplot as plt\n",
|
| 658 |
+
" import numpy as np\n",
|
| 659 |
+
"\n",
|
| 660 |
+
" history = self._history\n",
|
| 661 |
+
" steps = list(range(len(history)))\n",
|
| 662 |
+
" window = self.window\n",
|
| 663 |
+
" rolling = [\n",
|
| 664 |
+
" sum(history[max(0,i-window):i+1]) / min(i+1, window)\n",
|
| 665 |
+
" for i in steps\n",
|
| 666 |
+
" ]\n",
|
| 667 |
+
"\n",
|
| 668 |
+
" fig, ax = plt.subplots(figsize=(10, 4))\n",
|
| 669 |
+
" ax.plot(steps, history, alpha=0.3, color=\"#3498db\", label=\"Episode reward\")\n",
|
| 670 |
+
" ax.plot(steps, rolling, color=\"#e74c3c\", linewidth=2,\n",
|
| 671 |
+
" label=f\"Rolling mean (w={window})\")\n",
|
| 672 |
+
" ax.axhline(self.threshold, linestyle=\"--\", color=\"grey\",\n",
|
| 673 |
+
" linewidth=1, label=f\"Promotion threshold ({self.threshold})\")\n",
|
| 674 |
+
" for step, level in self._promotions:\n",
|
| 675 |
+
" ax.axvline(step, color=\"green\", linewidth=1.5, linestyle=\":\")\n",
|
| 676 |
+
" ax.text(step, ax.get_ylim()[1]*0.9, f\" L{level}\",\n",
|
| 677 |
+
" color=\"green\", fontsize=9)\n",
|
| 678 |
+
" ax.set_xlabel(\"Episode\")\n",
|
| 679 |
+
" ax.set_ylabel(\"Reward\")\n",
|
| 680 |
+
" ax.set_title(\"Training Reward + Curriculum Progression\")\n",
|
| 681 |
+
" ax.legend()\n",
|
| 682 |
+
" plt.tight_layout()\n",
|
| 683 |
+
" plt.savefig(\"curriculum_reward_curve.png\", dpi=150)\n",
|
| 684 |
+
" plt.show()\n",
|
| 685 |
+
" print(\"📈 Saved curriculum_reward_curve.png\")\n",
|
| 686 |
+
"\n",
|
| 687 |
+
"\n",
|
| 688 |
+
"# ── Demo: simulate 200 episodes of improving reward ──────────────────────────\n",
|
| 689 |
+
"import random\n",
|
| 690 |
+
"cb = CurriculumCallback(reward_threshold=5.0, window=50)\n",
|
| 691 |
+
"for ep in range(200):\n",
|
| 692 |
+
" # Simulate reward slowly improving\n",
|
| 693 |
+
" synthetic_reward = -5 + ep * 0.08 + random.gauss(0, 2)\n",
|
| 694 |
+
" cb.record(synthetic_reward, step=ep)\n",
|
| 695 |
+
"\n",
|
| 696 |
+
"cb.plot()"
|
| 697 |
+
]
|
| 698 |
+
},
|
| 699 |
+
{
|
| 700 |
+
"cell_type": "markdown",
|
| 701 |
+
"id": "inference-header",
|
| 702 |
+
"metadata": {},
|
| 703 |
+
"source": [
|
| 704 |
+
"## 🔍 Cell 11 — Inference: Before vs After Training"
|
| 705 |
+
]
|
| 706 |
+
},
|
| 707 |
+
{
|
| 708 |
+
"cell_type": "code",
|
| 709 |
+
"execution_count": null,
|
| 710 |
+
"id": "inference-cell",
|
| 711 |
+
"metadata": {},
|
| 712 |
+
"outputs": [],
|
| 713 |
+
"source": [
|
| 714 |
+
"def run_inference_episode(model, tokenizer, engine, passes, program, max_steps=5):\n",
|
| 715 |
+
" \"\"\"Run inference and execute parsed pass arrays until episode termination.\"\"\"\n",
|
| 716 |
+
" FastLanguageModel.for_inference(model)\n",
|
| 717 |
+
"\n",
|
| 718 |
+
" _ms = min(int(max_steps), CompilerOptimizationEnv.MAX_EPISODE_STEPS)\n",
|
| 719 |
+
" env = CompilerOptimizationEnv(engine, passes, max_steps=_ms)\n",
|
| 720 |
+
" obs = env.reset(program)\n",
|
| 721 |
+
" system_prompt = build_system_prompt(passes)\n",
|
| 722 |
+
"\n",
|
| 723 |
+
" actions_chosen, rewards_earned = [], []\n",
|
| 724 |
+
" done = False\n",
|
| 725 |
+
"\n",
|
| 726 |
+
" print(f\"\\nBaseline cycles: {env.previous_cycles}\")\n",
|
| 727 |
+
" print(f\"Initial state:\\n{obs}\\n\")\n",
|
| 728 |
+
"\n",
|
| 729 |
+
" while not done:\n",
|
| 730 |
+
" messages = [\n",
|
| 731 |
+
" {\"role\": \"system\", \"content\": system_prompt},\n",
|
| 732 |
+
" {\"role\": \"user\", \"content\": f\"Current program:\\n{obs}\\n\\nChoose optimization passes:\"},\n",
|
| 733 |
+
" ]\n",
|
| 734 |
+
" inputs = tokenizer.apply_chat_template(\n",
|
| 735 |
+
" messages,\n",
|
| 736 |
+
" tokenize=True,\n",
|
| 737 |
+
" add_generation_prompt=True,\n",
|
| 738 |
+
" return_tensors=\"pt\",\n",
|
| 739 |
+
" ).to(model.device)\n",
|
| 740 |
+
"\n",
|
| 741 |
+
" with torch.no_grad():\n",
|
| 742 |
+
" outputs = model.generate(\n",
|
| 743 |
+
" input_ids=inputs,\n",
|
| 744 |
+
" max_new_tokens=32,\n",
|
| 745 |
+
" temperature=0.1,\n",
|
| 746 |
+
" do_sample=True,\n",
|
| 747 |
+
" )\n",
|
| 748 |
+
"\n",
|
| 749 |
+
" raw_output = tokenizer.decode(outputs[0][inputs.shape[-1]:], skip_special_tokens=True).strip()\n",
|
| 750 |
+
"\n",
|
| 751 |
+
" try:\n",
|
| 752 |
+
" parsed_actions = Deliverable2_Formatter.extract_action_array(raw_output)\n",
|
| 753 |
+
" except ValueError:\n",
|
| 754 |
+
" parsed_actions = []\n",
|
| 755 |
+
"\n",
|
| 756 |
+
" if not parsed_actions:\n",
|
| 757 |
+
" result = env.step(\"__invalid__\")\n",
|
| 758 |
+
" actions_chosen.append(\"__invalid__\")\n",
|
| 759 |
+
" rewards_earned.append(result.reward)\n",
|
| 760 |
+
" obs = result.observation\n",
|
| 761 |
+
" done = result.done\n",
|
| 762 |
+
" print(f\"Step {len(actions_chosen)}: invalid output '{raw_output}' → reward={result.reward:+.2f}\")\n",
|
| 763 |
+
" continue\n",
|
| 764 |
+
"\n",
|
| 765 |
+
" for action in parsed_actions[:CompilerOptimizationEnv.MAX_EPISODE_STEPS]:\n",
|
| 766 |
+
" a = str(action).strip()\n",
|
| 767 |
+
" al = a.lower()\n",
|
| 768 |
+
" if al in (\"done\", \"stop\"):\n",
|
| 769 |
+
" result = env.step(\"done\" if al == \"done\" else \"STOP\")\n",
|
| 770 |
+
" actions_chosen.append(al)\n",
|
| 771 |
+
" rewards_earned.append(result.reward)\n",
|
| 772 |
+
" obs = result.observation\n",
|
| 773 |
+
" done = result.done\n",
|
| 774 |
+
" print(f\"Step {len(actions_chosen)}: stop ('{a}') → reward={result.reward:+.2f}\")\n",
|
| 775 |
+
" break\n",
|
| 776 |
+
" action = al\n",
|
| 777 |
+
" if action not in passes:\n",
|
| 778 |
+
" for k in passes:\n",
|
| 779 |
+
" if k.lower() == al:\n",
|
| 780 |
+
" action = k\n",
|
| 781 |
+
" break\n",
|
| 782 |
+
" result = env.step(action)\n",
|
| 783 |
+
" actions_chosen.append(action)\n",
|
| 784 |
+
" rewards_earned.append(result.reward)\n",
|
| 785 |
+
" obs = result.observation\n",
|
| 786 |
+
" done = result.done\n",
|
| 787 |
+
" print(f\"Step {len(actions_chosen)}: '{action}' → reward={result.reward:+.2f}\")\n",
|
| 788 |
+
" if done:\n",
|
| 789 |
+
" break\n",
|
| 790 |
+
"\n",
|
| 791 |
+
" summary = env._episode_summary()\n",
|
| 792 |
+
" print(f\"\\n{'─'*40}\")\n",
|
| 793 |
+
" print(f\"Total improvement: {summary['total_improvement_pct']:.1f}%\")\n",
|
| 794 |
+
" print(f\"Final cycles: {summary['final_cycles']} (was {summary['baseline_cycles']})\")\n",
|
| 795 |
+
" return summary\n",
|
| 796 |
+
"\n",
|
| 797 |
+
"\n",
|
| 798 |
+
"# Uncomment after training:\n",
|
| 799 |
+
"# summary = run_inference_episode(\n",
|
| 800 |
+
"# model, tokenizer, MockEngine(), MOCK_PASSES, SAMPLE_PROGRAM\n",
|
| 801 |
+
"# )\n",
|
| 802 |
+
"\n",
|
| 803 |
+
"print(\"✅ Inference cell ready. Uncomment the last block after training to run.\")"
|
| 804 |
+
]
|
| 805 |
+
},
|
| 806 |
+
{
|
| 807 |
+
"cell_type": "markdown",
|
| 808 |
+
"id": "tips-header",
|
| 809 |
+
"metadata": {},
|
| 810 |
+
"source": [
|
| 811 |
+
"---\n",
|
| 812 |
+
"## 📝 Notes & Tips\n",
|
| 813 |
+
"\n",
|
| 814 |
+
"| What | Why it matters |\n",
|
| 815 |
+
"|------|----------------|\n",
|
| 816 |
+
"| `num_generations=4` | GRPO needs K≥2 rollouts per prompt to compute group-relative advantage. K=4 balances diversity vs. compute. |\n",
|
| 817 |
+
"| `beta=0.04` | KL penalty keeping policy close to reference. Too low → mode collapse. Too high → no learning. |\n",
|
| 818 |
+
"| `max_new_tokens=16` | Actions are one word. This prevents wasted computation and keeps the model from adding explanations. |\n",
|
| 819 |
+
"| Equivalence / semantic failure | The shaped reward function returns `−1000.0` when a candidate fails verification (RLVR hard gate). |\n",
|
| 820 |
+
"| Terminal reward | Final cycle savings (scaled) are given at `STOP`, step cap (5), or at end of a pass list; non-terminal steps use a small constant cost. |\n",
|
| 821 |
+
"| Soft invalid-action termination | 3 consecutive invalid actions → end. Single mistakes don't kill the episode; the agent can recover. |\n",
|
| 822 |
+
"| `deepcopy` on all pass inputs | Role 3's passes mutate dicts in-place. Without this, `original_program` gets corrupted and the verifier fails spuriously. |\n",
|
| 823 |
+
"\n",
|
| 824 |
+
"**Next steps:**\n",
|
| 825 |
+
"- Replace `MockEngine` with Role 1's real engine\n",
|
| 826 |
+
"- Replace `MOCK_PASSES` with Role 3's real passes\n",
|
| 827 |
+
"- Push environment to HuggingFace Spaces: `openenv init && openenv deploy`\n",
|
| 828 |
+
"- Add W&B sweep to tune `beta`, `learning_rate`, `num_generations`"
|
| 829 |
+
]
|
| 830 |
+
}
|
| 831 |
+
],
|
| 832 |
+
"metadata": {
|
| 833 |
+
"accelerator": "GPU",
|
| 834 |
+
"colab": {
|
| 835 |
+
"gpuType": "T4",
|
| 836 |
+
"provenance": []
|
| 837 |
+
},
|
| 838 |
+
"kernelspec": {
|
| 839 |
+
"display_name": "Python 3",
|
| 840 |
+
"language": "python",
|
| 841 |
+
"name": "python3"
|
| 842 |
+
},
|
| 843 |
+
"language_info": {
|
| 844 |
+
"name": "python",
|
| 845 |
+
"version": "3.10.0"
|
| 846 |
+
}
|
| 847 |
+
},
|
| 848 |
+
"nbformat": 4,
|
| 849 |
+
"nbformat_minor": 5
|
| 850 |
+
}
|
space/space/space/space/space/space/space/reverse_pass/toyir_rl_support.py
ADDED
|
@@ -0,0 +1,473 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Toy-IR RL training support: PASSES, mock engine, environment, terminal-weighted shaped reward.
|
| 3 |
+
Used by training notebooks. Do not import toy_vm / verifier / reward_utils here.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from __future__ import annotations
|
| 7 |
+
|
| 8 |
+
import copy
|
| 9 |
+
import json
|
| 10 |
+
import os
|
| 11 |
+
import sys
|
| 12 |
+
from dataclasses import dataclass, field
|
| 13 |
+
from typing import Any, Dict, List, Optional, Tuple
|
| 14 |
+
|
| 15 |
+
# Allow notebooks in subdirs to import when cwd is set to project root
|
| 16 |
+
_ROOT = os.path.dirname(os.path.abspath(__file__))
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
@dataclass
|
| 20 |
+
class StepResult:
|
| 21 |
+
observation: str
|
| 22 |
+
reward: float
|
| 23 |
+
done: bool
|
| 24 |
+
info: Dict[str, Any] = field(default_factory=dict)
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
@dataclass
|
| 28 |
+
class EpisodeStats:
|
| 29 |
+
steps_taken: int = 0
|
| 30 |
+
total_reward: float = 0.0
|
| 31 |
+
passes_applied: List[str] = field(default_factory=list)
|
| 32 |
+
invalid_actions: int = 0
|
| 33 |
+
no_ops: int = 0
|
| 34 |
+
baseline_cycles: int = 0
|
| 35 |
+
final_cycles: int = 0
|
| 36 |
+
|
| 37 |
+
@property
|
| 38 |
+
def total_improvement_pct(self) -> float:
|
| 39 |
+
if self.baseline_cycles == 0:
|
| 40 |
+
return 0.0
|
| 41 |
+
return ((self.baseline_cycles - self.final_cycles) / self.baseline_cycles) * 100.0
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
class MCPEnvironment:
|
| 45 |
+
def reset(self, *args, **kwargs):
|
| 46 |
+
raise NotImplementedError
|
| 47 |
+
|
| 48 |
+
def step(self, *args, **kwargs):
|
| 49 |
+
raise NotImplementedError
|
| 50 |
+
|
| 51 |
+
def state(self):
|
| 52 |
+
raise NotImplementedError
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def _op_u(ins: dict) -> str:
|
| 56 |
+
return str(ins.get("op", "")).upper()
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def _first_numeric(ins: dict) -> Optional[int]:
|
| 60 |
+
for k in ("src1", "src2"):
|
| 61 |
+
v = ins.get(k)
|
| 62 |
+
if isinstance(v, int) and v > 0:
|
| 63 |
+
return v
|
| 64 |
+
args = ins.get("args", [])
|
| 65 |
+
if args:
|
| 66 |
+
a0 = args[0]
|
| 67 |
+
try:
|
| 68 |
+
if isinstance(a0, int):
|
| 69 |
+
return a0
|
| 70 |
+
return int(str(a0), 0)
|
| 71 |
+
except (ValueError, TypeError):
|
| 72 |
+
return None
|
| 73 |
+
v = ins.get("src1")
|
| 74 |
+
if isinstance(v, int):
|
| 75 |
+
return v
|
| 76 |
+
return None
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
# --- forward / reverse passes (list[dict] IR) ---------------------------------
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
def constant_folding(program: List[dict]) -> List[dict]:
|
| 83 |
+
p = copy.deepcopy(program)
|
| 84 |
+
return p[:-1] if len(p) > 1 else p
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def dead_code_elimination(program: List[dict]) -> List[dict]:
|
| 88 |
+
p = copy.deepcopy(program)
|
| 89 |
+
return p[:-1] if len(p) > 2 else p
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def peephole_optimization(program: List[dict]) -> List[dict]:
|
| 93 |
+
return copy.deepcopy(program)
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def expand_constant(program: List[dict]) -> List[dict]:
|
| 97 |
+
"""
|
| 98 |
+
Deoptimization: split one CONST into two consts + ADD to raise cycle count sometimes,
|
| 99 |
+
giving forward passes a different surface (reverse exploration).
|
| 100 |
+
"""
|
| 101 |
+
p = copy.deepcopy(program)
|
| 102 |
+
for i, ins in enumerate(p):
|
| 103 |
+
if _op_u(ins) not in ("CONST",):
|
| 104 |
+
continue
|
| 105 |
+
v = _first_numeric(ins) if _first_numeric(ins) is not None else None
|
| 106 |
+
if v is None and ins.get("args"):
|
| 107 |
+
try:
|
| 108 |
+
v = int(ins["args"][0])
|
| 109 |
+
except (ValueError, TypeError, IndexError, KeyError):
|
| 110 |
+
v = None
|
| 111 |
+
if v is None or v < 2:
|
| 112 |
+
continue
|
| 113 |
+
d = str(ins.get("dest", "t0"))
|
| 114 |
+
h, rest = v // 2, v - (v // 2)
|
| 115 |
+
repl = [
|
| 116 |
+
{**{k: ins[k] for k in ins if k in ("type",)}, "op": "const", "dest": f"{d}_a", "args": [str(h)]},
|
| 117 |
+
{**{k: ins[k] for k in ins if k in ("type",)}, "op": "const", "dest": f"{d}_b", "args": [str(rest)]},
|
| 118 |
+
{
|
| 119 |
+
"op": "add",
|
| 120 |
+
"dest": d,
|
| 121 |
+
"args": [f"{d}_a", f"{d}_b"],
|
| 122 |
+
**{k: ins[k] for k in ins if k in ("type",) and k not in ("op", "dest", "args")},
|
| 123 |
+
},
|
| 124 |
+
]
|
| 125 |
+
p[i : i + 1] = repl
|
| 126 |
+
return p
|
| 127 |
+
return p
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
def duplicate_computation(program: List[dict]) -> List[dict]:
|
| 131 |
+
"""
|
| 132 |
+
Deoptimization: duplicate a binary op into a new destination (redundant work).
|
| 133 |
+
"""
|
| 134 |
+
p = copy.deepcopy(program)
|
| 135 |
+
for i, ins in enumerate(p):
|
| 136 |
+
ou = _op_u(ins)
|
| 137 |
+
if ou in ("ADD", "MUL", "SUB", "DIV"):
|
| 138 |
+
dest = str(ins.get("dest", "t0"))
|
| 139 |
+
dup = copy.deepcopy(ins)
|
| 140 |
+
dup["dest"] = f"{dest}_d"
|
| 141 |
+
p.insert(i + 1, dup)
|
| 142 |
+
return p
|
| 143 |
+
return p
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
PASSES: Dict[str, Any] = {
|
| 147 |
+
"constant_folding": constant_folding,
|
| 148 |
+
"dead_code_elimination": dead_code_elimination,
|
| 149 |
+
"peephole_optimization": peephole_optimization,
|
| 150 |
+
"expand_constant": expand_constant,
|
| 151 |
+
"duplicate_computation": duplicate_computation,
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
MOCK_PASSES = PASSES # alias for existing notebook references
|
| 155 |
+
|
| 156 |
+
|
| 157 |
+
class MockEngine:
|
| 158 |
+
"""Stub: cycle count = instruction list length; always equivalent."""
|
| 159 |
+
|
| 160 |
+
def execute_and_count_cycles(self, program: List[dict]) -> int:
|
| 161 |
+
return len(program)
|
| 162 |
+
|
| 163 |
+
def verify_equivalence(self, original, candidate) -> bool:
|
| 164 |
+
return True
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
# Back-compat: some notebooks use these names
|
| 168 |
+
MOCK_ENGINE = MockEngine()
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
# --- Shaped reward (user-specified) -------------------------------------------
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
def compute_shaped_reward(
|
| 175 |
+
equivalent: bool,
|
| 176 |
+
is_terminal: bool,
|
| 177 |
+
original_cycles: int,
|
| 178 |
+
current_cycles: int,
|
| 179 |
+
) -> float:
|
| 180 |
+
# RLVR hard gate — broken optimizations get massive penalty
|
| 181 |
+
if not equivalent:
|
| 182 |
+
return -1000.0
|
| 183 |
+
|
| 184 |
+
# Terminal reward: large signal proportional to final cycle reduction
|
| 185 |
+
# This is what makes reverse passes learnable — agent gets rewarded for the END state, not the path
|
| 186 |
+
if is_terminal:
|
| 187 |
+
if original_cycles == 0:
|
| 188 |
+
return 0.0
|
| 189 |
+
relative_savings = (original_cycles - current_cycles) / original_cycles
|
| 190 |
+
return relative_savings * 100.0
|
| 191 |
+
|
| 192 |
+
# Per-step: tiny constant cost to discourage infinite thrashing
|
| 193 |
+
# CRITICAL: do NOT punish cycle increases here. Per-step cycle-delta reward kills reverse-pass exploration.
|
| 194 |
+
return -0.1
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
# --- Environment --------------------------------------------------------------
|
| 198 |
+
|
| 199 |
+
|
| 200 |
+
class CompilerOptimizationEnv(MCPEnvironment):
|
| 201 |
+
TIME_TAX: float = 1.0
|
| 202 |
+
NO_OP_PENALTY: float = -2.0
|
| 203 |
+
INVALID_ACTION_PENALTY: float = -5.0
|
| 204 |
+
MAX_INVALID_ACTIONS: int = 3
|
| 205 |
+
TERMINAL_BONUS_SCALE: float = 0.5
|
| 206 |
+
MAX_EPISODE_STEPS: int = 5 # hard cap (Change 4)
|
| 207 |
+
|
| 208 |
+
def __init__(
|
| 209 |
+
self,
|
| 210 |
+
role1_engine: Any,
|
| 211 |
+
role3_passes: Dict[str, Any],
|
| 212 |
+
max_steps: int = 5,
|
| 213 |
+
curriculum_level: int = 1,
|
| 214 |
+
):
|
| 215 |
+
self.engine = role1_engine
|
| 216 |
+
self.passes = role3_passes
|
| 217 |
+
self.max_steps = max(1, int(max_steps))
|
| 218 |
+
self.curriculum_level = curriculum_level
|
| 219 |
+
self._valid_actions: frozenset = frozenset(self.passes.keys()) | {"STOP", "done", "DONE"}
|
| 220 |
+
self._stats: Optional[EpisodeStats] = None
|
| 221 |
+
self.original_program: Optional[List[dict]] = None
|
| 222 |
+
self.current_program: Optional[List[dict]] = None
|
| 223 |
+
self.previous_cycles = 0
|
| 224 |
+
self._consecutive_invalid = 0
|
| 225 |
+
|
| 226 |
+
def reset(self, new_program_json: List[dict]) -> str:
|
| 227 |
+
self.original_program = copy.deepcopy(new_program_json)
|
| 228 |
+
self.current_program = copy.deepcopy(new_program_json)
|
| 229 |
+
self.previous_cycles = self._safe_count_cycles(self.current_program)
|
| 230 |
+
self._consecutive_invalid = 0
|
| 231 |
+
self._stats = EpisodeStats(
|
| 232 |
+
baseline_cycles=self.previous_cycles,
|
| 233 |
+
final_cycles=self.previous_cycles,
|
| 234 |
+
)
|
| 235 |
+
return self.state()
|
| 236 |
+
|
| 237 |
+
def state(self) -> str:
|
| 238 |
+
return self._program_to_pseudoasm(self.current_program)
|
| 239 |
+
|
| 240 |
+
def step(self, action_string: str) -> StepResult:
|
| 241 |
+
assert self._stats is not None, "Call reset() before step()."
|
| 242 |
+
aup = str(action_string).upper()
|
| 243 |
+
is_stop = aup in ("STOP", "DONE")
|
| 244 |
+
if is_stop:
|
| 245 |
+
self._stats.steps_taken += 1
|
| 246 |
+
orig = int(self._stats.baseline_cycles)
|
| 247 |
+
cur = self._safe_count_cycles(self.current_program)
|
| 248 |
+
ok = bool(self.engine.verify_equivalence(self.original_program, self.current_program))
|
| 249 |
+
reward = compute_shaped_reward(ok, True, orig, cur)
|
| 250 |
+
self._stats.total_reward += reward
|
| 251 |
+
return StepResult(
|
| 252 |
+
self.state(),
|
| 253 |
+
reward,
|
| 254 |
+
True,
|
| 255 |
+
{
|
| 256 |
+
"reason": "stop",
|
| 257 |
+
"is_terminal": True,
|
| 258 |
+
"stop_token": aup,
|
| 259 |
+
"episode_stats": self._episode_summary(),
|
| 260 |
+
},
|
| 261 |
+
)
|
| 262 |
+
if action_string not in self.passes:
|
| 263 |
+
return self._handle_invalid_action(action_string)
|
| 264 |
+
|
| 265 |
+
self._stats.steps_taken += 1
|
| 266 |
+
candidate = self.passes[action_string](copy.deepcopy(self.current_program))
|
| 267 |
+
if not self.engine.verify_equivalence(self.original_program, candidate):
|
| 268 |
+
return self._handle_semantic_violation()
|
| 269 |
+
new_cycles = self._safe_count_cycles(candidate)
|
| 270 |
+
reward, info = self._compute_reward(action_string, new_cycles)
|
| 271 |
+
self.current_program = candidate
|
| 272 |
+
self.previous_cycles = new_cycles
|
| 273 |
+
self._stats.final_cycles = new_cycles
|
| 274 |
+
self._stats.total_reward += reward
|
| 275 |
+
self._stats.passes_applied.append(action_string)
|
| 276 |
+
self._consecutive_invalid = 0
|
| 277 |
+
done = self._stats.steps_taken >= self.max_steps
|
| 278 |
+
if done:
|
| 279 |
+
info["reason"] = "max_steps_reached"
|
| 280 |
+
info["episode_stats"] = self._episode_summary()
|
| 281 |
+
return StepResult(self.state(), reward, done, info)
|
| 282 |
+
|
| 283 |
+
def _compute_reward(self, action: str, new_cycles: int) -> Tuple[float, dict]:
|
| 284 |
+
info: Dict[str, Any] = {"action": action}
|
| 285 |
+
orig = int(self._stats.baseline_cycles)
|
| 286 |
+
is_term = self._stats.steps_taken >= self.max_steps
|
| 287 |
+
if new_cycles == self.previous_cycles and not is_term:
|
| 288 |
+
self._stats.no_ops += 1
|
| 289 |
+
info["no_op"] = True
|
| 290 |
+
reward = compute_shaped_reward(True, is_term, orig, new_cycles)
|
| 291 |
+
info["is_terminal"] = is_term
|
| 292 |
+
info["new_cycles"] = new_cycles
|
| 293 |
+
return reward, info
|
| 294 |
+
|
| 295 |
+
def _compute_crash_penalty(self) -> float:
|
| 296 |
+
return -1000.0
|
| 297 |
+
|
| 298 |
+
def _handle_invalid_action(self, action: str) -> StepResult:
|
| 299 |
+
self._consecutive_invalid += 1
|
| 300 |
+
self._stats.invalid_actions += 1
|
| 301 |
+
done = self._consecutive_invalid >= self.MAX_INVALID_ACTIONS
|
| 302 |
+
info: Dict[str, Any] = {
|
| 303 |
+
"error": f"Unknown action: '{action}'",
|
| 304 |
+
"valid_actions": sorted(self._valid_actions),
|
| 305 |
+
"consecutive_invalid": self._consecutive_invalid,
|
| 306 |
+
}
|
| 307 |
+
if done:
|
| 308 |
+
info["reason"] = "too_many_invalid_actions"
|
| 309 |
+
info["episode_stats"] = self._episode_summary()
|
| 310 |
+
return StepResult(self.state(), self.INVALID_ACTION_PENALTY, done, info)
|
| 311 |
+
|
| 312 |
+
def _handle_semantic_violation(self) -> StepResult:
|
| 313 |
+
return StepResult(
|
| 314 |
+
self.state(),
|
| 315 |
+
self._compute_crash_penalty(),
|
| 316 |
+
True,
|
| 317 |
+
{
|
| 318 |
+
"error": "Semantic equivalence check FAILED.",
|
| 319 |
+
"reason": "semantic_violation",
|
| 320 |
+
"episode_stats": self._episode_summary(),
|
| 321 |
+
},
|
| 322 |
+
)
|
| 323 |
+
|
| 324 |
+
@staticmethod
|
| 325 |
+
def _program_to_pseudoasm(program: Optional[List[dict]]) -> str:
|
| 326 |
+
if not program:
|
| 327 |
+
return "; (empty program)"
|
| 328 |
+
lines = []
|
| 329 |
+
for i, instr in enumerate(program):
|
| 330 |
+
op = instr.get("op", "NOP")
|
| 331 |
+
args = instr.get("args", [])
|
| 332 |
+
dest = instr.get("dest")
|
| 333 |
+
typ = instr.get("type", "")
|
| 334 |
+
arg_str = ", ".join(str(a) for a in args)
|
| 335 |
+
type_hint = f":{typ}" if typ else ""
|
| 336 |
+
if dest:
|
| 337 |
+
lines.append(f" {i:>3}: {dest}{type_hint} = {op} {arg_str}")
|
| 338 |
+
else:
|
| 339 |
+
lines.append(f" {i:>3}: {op} {arg_str}")
|
| 340 |
+
return "\n".join(lines)
|
| 341 |
+
|
| 342 |
+
def _safe_count_cycles(self, program: List[dict]) -> int:
|
| 343 |
+
return max(0, int(self.engine.execute_and_count_cycles(program)))
|
| 344 |
+
|
| 345 |
+
def _episode_summary(self) -> dict:
|
| 346 |
+
s = self._stats
|
| 347 |
+
return {
|
| 348 |
+
"steps": s.steps_taken,
|
| 349 |
+
"total_reward": round(s.total_reward, 3),
|
| 350 |
+
"passes_applied": s.passes_applied,
|
| 351 |
+
"invalid_actions": s.invalid_actions,
|
| 352 |
+
"no_ops": s.no_ops,
|
| 353 |
+
"baseline_cycles": s.baseline_cycles,
|
| 354 |
+
"final_cycles": s.final_cycles,
|
| 355 |
+
"total_improvement_pct": round(s.total_improvement_pct, 3),
|
| 356 |
+
}
|
| 357 |
+
|
| 358 |
+
def available_actions(self) -> List[str]:
|
| 359 |
+
return sorted(self._valid_actions)
|
| 360 |
+
|
| 361 |
+
|
| 362 |
+
SAMPLE_PROGRAM: List[dict] = [
|
| 363 |
+
{"op": "const", "dest": "x", "args": ["5"], "type": "int"},
|
| 364 |
+
{"op": "const", "dest": "y", "args": ["3"], "type": "int"},
|
| 365 |
+
{"op": "add", "dest": "z", "args": ["x", "y"], "type": "int"},
|
| 366 |
+
{"op": "mul", "dest": "w", "args": ["z", "x"], "type": "int"},
|
| 367 |
+
{"op": "ret", "args": ["w"]},
|
| 368 |
+
]
|
| 369 |
+
|
| 370 |
+
|
| 371 |
+
# --- Rollout for GRPO reward (terminal-weighted shaping) ------------------------
|
| 372 |
+
|
| 373 |
+
|
| 374 |
+
def rollout_shaped_return(
|
| 375 |
+
program: List[dict],
|
| 376 |
+
action_names: List[str],
|
| 377 |
+
engine: Any,
|
| 378 |
+
passes: Optional[Dict[str, Any]] = None,
|
| 379 |
+
) -> Tuple[float, int, int, bool, bool, bool]:
|
| 380 |
+
"""
|
| 381 |
+
Returns:
|
| 382 |
+
(total_shaped_reward, n_expand_constant, n_duplicate, hit_stop, hit_step_cap, parse_ok)
|
| 383 |
+
If parse_ok is False (unknown pass in sequence), first value is -1000.0.
|
| 384 |
+
Sums per-step `env.step` shaped rewards, adds a terminal `compute_shaped_reward` when
|
| 385 |
+
the action list ends without an explicit STOP (or already terminal from step cap).
|
| 386 |
+
"""
|
| 387 |
+
passes = passes or PASSES
|
| 388 |
+
cap = CompilerOptimizationEnv.MAX_EPISODE_STEPS
|
| 389 |
+
env = CompilerOptimizationEnv(engine, passes, max_steps=cap)
|
| 390 |
+
env.reset(program)
|
| 391 |
+
total = 0.0
|
| 392 |
+
n_e = 0
|
| 393 |
+
n_d = 0
|
| 394 |
+
hit_stop = False
|
| 395 |
+
hit_cap = False
|
| 396 |
+
for raw in action_names:
|
| 397 |
+
a = str(raw).strip()
|
| 398 |
+
if not a:
|
| 399 |
+
continue
|
| 400 |
+
aup = a.upper()
|
| 401 |
+
if aup in ("STOP", "DONE"):
|
| 402 |
+
r = env.step(a)
|
| 403 |
+
total += r.reward
|
| 404 |
+
hit_stop = True
|
| 405 |
+
return total, n_e, n_d, hit_stop, hit_cap, True
|
| 406 |
+
if a not in passes:
|
| 407 |
+
return -1000.0, n_e, n_d, hit_stop, hit_cap, False
|
| 408 |
+
if a == "expand_constant":
|
| 409 |
+
n_e += 1
|
| 410 |
+
elif a == "duplicate_computation":
|
| 411 |
+
n_d += 1
|
| 412 |
+
r = env.step(a)
|
| 413 |
+
total += r.reward
|
| 414 |
+
if r.info.get("reason") == "semantic_violation":
|
| 415 |
+
return -1000.0, n_e, n_d, hit_stop, hit_cap, True
|
| 416 |
+
if r.done and r.info.get("reason") == "max_steps_reached":
|
| 417 |
+
hit_cap = True
|
| 418 |
+
return total, n_e, n_d, hit_stop, hit_cap, True
|
| 419 |
+
ok = bool(engine.verify_equivalence(env.original_program, env.current_program))
|
| 420 |
+
cur = int(engine.execute_and_count_cycles(env.current_program or []))
|
| 421 |
+
orig = int(env._stats.baseline_cycles)
|
| 422 |
+
total += compute_shaped_reward(ok, True, orig, cur)
|
| 423 |
+
return total, n_e, n_d, hit_stop, hit_cap, True
|
| 424 |
+
|
| 425 |
+
|
| 426 |
+
# Global window for logging (50-episode / completion windows)
|
| 427 |
+
_REVERSE_LOG_WINDOW: List[dict] = []
|
| 428 |
+
|
| 429 |
+
|
| 430 |
+
def log_reverse_pass_stats_for_completion(
|
| 431 |
+
used_expand: int,
|
| 432 |
+
used_dup: int,
|
| 433 |
+
) -> None:
|
| 434 |
+
"""Call once per training completion. Logs every 50 'episodes' (completions)."""
|
| 435 |
+
global _REVERSE_LOG_WINDOW
|
| 436 |
+
any_r = (used_expand + used_dup) > 0
|
| 437 |
+
_REVERSE_LOG_WINDOW.append(
|
| 438 |
+
{
|
| 439 |
+
"any_reverse": any_r,
|
| 440 |
+
"expand": used_expand,
|
| 441 |
+
"dup": used_dup,
|
| 442 |
+
}
|
| 443 |
+
)
|
| 444 |
+
if len(_REVERSE_LOG_WINDOW) < 50:
|
| 445 |
+
return
|
| 446 |
+
w = _REVERSE_LOG_WINDOW
|
| 447 |
+
_REVERSE_LOG_WINDOW = []
|
| 448 |
+
reverse_pass_episodes = sum(1 for e in w if e["any_reverse"])
|
| 449 |
+
expand_constant_count = sum(e["expand"] for e in w)
|
| 450 |
+
duplicate_computation_count = sum(e["dup"] for e in w)
|
| 451 |
+
payload = {
|
| 452 |
+
"reverse_pass_episodes": reverse_pass_episodes,
|
| 453 |
+
"expand_constant_count": expand_constant_count,
|
| 454 |
+
"duplicate_computation_count": duplicate_computation_count,
|
| 455 |
+
}
|
| 456 |
+
try:
|
| 457 |
+
import wandb
|
| 458 |
+
|
| 459 |
+
if wandb.run is not None:
|
| 460 |
+
wandb.log(payload)
|
| 461 |
+
except Exception:
|
| 462 |
+
pass
|
| 463 |
+
print(
|
| 464 |
+
f"[reverse_pass/50] reverse_pass_episodes={reverse_pass_episodes} "
|
| 465 |
+
f"expand_constant_count={expand_constant_count} "
|
| 466 |
+
f"duplicate_computation_count={duplicate_computation_count}"
|
| 467 |
+
)
|
| 468 |
+
|
| 469 |
+
|
| 470 |
+
def ensure_import_path() -> None:
|
| 471 |
+
d = _ROOT
|
| 472 |
+
if d and d not in sys.path:
|
| 473 |
+
sys.path.insert(0, d)
|
space/space/space/space/space/space/space/space/reverse_pass/reversepass_new_eval_baseline.ipynb
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
space/space/space/space/space/space/space/space/space/app.py
CHANGED
|
@@ -23,6 +23,7 @@ from runtime_core import (
|
|
| 23 |
MockEngine,
|
| 24 |
SAMPLE_PROGRAM,
|
| 25 |
)
|
|
|
|
| 26 |
|
| 27 |
|
| 28 |
DEFAULT_IR = json.dumps(SAMPLE_PROGRAM, indent=2)
|
|
@@ -113,8 +114,9 @@ def build_demo() -> gr.Blocks:
|
|
| 113 |
# Compiler optimization (Toy-IR) — interactive demo
|
| 114 |
|
| 115 |
This Space runs **CPU-only** demos: **Deliverable 2** state translation and action parsing,
|
| 116 |
-
plus the **CompilerOptimizationEnv**
|
| 117 |
-
|
|
|
|
| 118 |
"""
|
| 119 |
).strip()
|
| 120 |
)
|
|
@@ -154,6 +156,28 @@ def build_demo() -> gr.Blocks:
|
|
| 154 |
out_ep = gr.Textbox(label="Log", lines=20)
|
| 155 |
gr.Button("Run episode", variant="primary").click(run_episode, [ir_ep, acts, ms], [out_ep])
|
| 156 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 157 |
gr.Markdown(
|
| 158 |
"Source notebooks in the parent repo: `compiler_optimization_grpo.ipynb`, "
|
| 159 |
"`role2_deliverable3_training_loop (2) (1).ipynb`, `compiler_tetris (1).ipynb`, `metahack1 (1).ipynb`."
|
|
|
|
| 23 |
MockEngine,
|
| 24 |
SAMPLE_PROGRAM,
|
| 25 |
)
|
| 26 |
+
from train import run_toy_training
|
| 27 |
|
| 28 |
|
| 29 |
DEFAULT_IR = json.dumps(SAMPLE_PROGRAM, indent=2)
|
|
|
|
| 114 |
# Compiler optimization (Toy-IR) — interactive demo
|
| 115 |
|
| 116 |
This Space runs **CPU-only** demos: **Deliverable 2** state translation and action parsing,
|
| 117 |
+
plus the **CompilerOptimizationEnv** loop. A **toy REINFORCE** tab trains a tiny
|
| 118 |
+
stateless policy over the mock passes (see `train.py`). Full **GRPO + LLM + Unsloth**
|
| 119 |
+
still belongs on Colab or a GPU machine.
|
| 120 |
"""
|
| 121 |
).strip()
|
| 122 |
)
|
|
|
|
| 156 |
out_ep = gr.Textbox(label="Log", lines=20)
|
| 157 |
gr.Button("Run episode", variant="primary").click(run_episode, [ir_ep, acts, ms], [out_ep])
|
| 158 |
|
| 159 |
+
with gr.Tab("Toy training (REINFORCE)"):
|
| 160 |
+
gr.Markdown(
|
| 161 |
+
textwrap.dedent(
|
| 162 |
+
"""
|
| 163 |
+
Trains a **stateless** categorical policy over the three mock passes
|
| 164 |
+
(`constant_folding`, `dead_code_elimination`, `loop_unrolling`) using
|
| 165 |
+
**REINFORCE** in pure Python. Same logic as: `python train.py --episodes 50`.
|
| 166 |
+
|
| 167 |
+
This is a CPU smoke run, not an LLM. For real GRPO, use your project notebooks
|
| 168 |
+
on a GPU.
|
| 169 |
+
"""
|
| 170 |
+
).strip()
|
| 171 |
+
)
|
| 172 |
+
tr_ep = gr.Slider(5, 200, value=50, step=1, label="episodes")
|
| 173 |
+
tr_ms = gr.Slider(2, 20, value=8, step=1, label="max_steps per episode")
|
| 174 |
+
tr_seed = gr.Number(value=0, label="random seed", precision=0)
|
| 175 |
+
tr_lr = gr.Slider(0.01, 0.5, value=0.15, step=0.01, label="learning rate")
|
| 176 |
+
out_tr = gr.Textbox(label="Training log", lines=18)
|
| 177 |
+
gr.Button("Run training", variant="primary").click(
|
| 178 |
+
run_toy_training, [tr_ep, tr_ms, tr_seed, tr_lr], [out_tr]
|
| 179 |
+
)
|
| 180 |
+
|
| 181 |
gr.Markdown(
|
| 182 |
"Source notebooks in the parent repo: `compiler_optimization_grpo.ipynb`, "
|
| 183 |
"`role2_deliverable3_training_loop (2) (1).ipynb`, `compiler_tetris (1).ipynb`, `metahack1 (1).ipynb`."
|
space/space/space/space/space/space/space/space/space/space/space/runtime_core.py
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Shared runtime for the Gradio Space: mock compiler env + Deliverable 2 formatting.
|
| 3 |
+
Sourced from `compiler_optimization_grpo.ipynb` and
|
| 4 |
+
`role2_deliverable3_training_loop (2) (1) (1).ipynb`.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
from __future__ import annotations
|
| 8 |
+
|
| 9 |
+
import copy
|
| 10 |
+
import json
|
| 11 |
+
import re
|
| 12 |
+
from dataclasses import dataclass, field
|
| 13 |
+
from typing import Any, Dict, List, Optional, Tuple
|
| 14 |
+
|
| 15 |
+
# --- Deliverable 2 (LLM-facing pseudo-asm + pass-array parsing) -----------------
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class Deliverable2_Formatter:
|
| 19 |
+
@staticmethod
|
| 20 |
+
def translate_state(raw_json: list) -> str:
|
| 21 |
+
"""Translate raw JSON IR into compact pseudo-assembly."""
|
| 22 |
+
if not isinstance(raw_json, list) or not raw_json:
|
| 23 |
+
return "; (empty program — 0 instructions)"
|
| 24 |
+
|
| 25 |
+
pseudo_assembly: list[str] = []
|
| 26 |
+
for i, instruction in enumerate(raw_json):
|
| 27 |
+
if not isinstance(instruction, dict):
|
| 28 |
+
pseudo_assembly.append(f"{i}. NOP")
|
| 29 |
+
continue
|
| 30 |
+
|
| 31 |
+
op = str(instruction.get("op", "UNKNOWN")).upper()
|
| 32 |
+
args = ", ".join(str(arg) for arg in instruction.get("args", []))
|
| 33 |
+
dest = instruction.get("dest", "")
|
| 34 |
+
if dest:
|
| 35 |
+
line = f"{i}. {dest} = {op} {args}".rstrip()
|
| 36 |
+
else:
|
| 37 |
+
line = f"{i}. {op} {args}".rstrip()
|
| 38 |
+
pseudo_assembly.append(line)
|
| 39 |
+
|
| 40 |
+
return "\n".join(pseudo_assembly)
|
| 41 |
+
|
| 42 |
+
@staticmethod
|
| 43 |
+
def extract_action_array(llm_output: str) -> list:
|
| 44 |
+
"""Best-effort extraction of JSON pass arrays from noisy LLM output."""
|
| 45 |
+
text = (llm_output or "").strip()
|
| 46 |
+
if not text:
|
| 47 |
+
raise ValueError("Invalid JSON format")
|
| 48 |
+
|
| 49 |
+
try:
|
| 50 |
+
parsed = json.loads(text)
|
| 51 |
+
if isinstance(parsed, list):
|
| 52 |
+
return parsed
|
| 53 |
+
except json.JSONDecodeError:
|
| 54 |
+
pass
|
| 55 |
+
|
| 56 |
+
cleaned = re.sub(r"```(?:json)?", "", text, flags=re.IGNORECASE).replace("```", "").strip()
|
| 57 |
+
if cleaned != text:
|
| 58 |
+
try:
|
| 59 |
+
parsed = json.loads(cleaned)
|
| 60 |
+
if isinstance(parsed, list):
|
| 61 |
+
return parsed
|
| 62 |
+
except json.JSONDecodeError:
|
| 63 |
+
pass
|
| 64 |
+
|
| 65 |
+
match = re.search(r"\[.*?\]", text, re.DOTALL)
|
| 66 |
+
if match:
|
| 67 |
+
candidate = match.group(0)
|
| 68 |
+
try:
|
| 69 |
+
parsed = json.loads(candidate)
|
| 70 |
+
if isinstance(parsed, list):
|
| 71 |
+
return parsed
|
| 72 |
+
except json.JSONDecodeError:
|
| 73 |
+
try:
|
| 74 |
+
parsed = json.loads(candidate.replace("'", '"'))
|
| 75 |
+
if isinstance(parsed, list):
|
| 76 |
+
return parsed
|
| 77 |
+
except json.JSONDecodeError:
|
| 78 |
+
pass
|
| 79 |
+
|
| 80 |
+
raise ValueError("Invalid JSON format")
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
# --- OpenEnv-style compiler environment (mock engine) --------------------------
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
class MCPEnvironment:
|
| 87 |
+
"""Minimal stub. In production: `from openenv import MCPEnvironment`."""
|
| 88 |
+
|
| 89 |
+
def reset(self, *args, **kwargs):
|
| 90 |
+
raise NotImplementedError
|
| 91 |
+
|
| 92 |
+
def step(self, *args, **kwargs):
|
| 93 |
+
raise NotImplementedError
|
| 94 |
+
|
| 95 |
+
def state(self):
|
| 96 |
+
raise NotImplementedError
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
@dataclass
|
| 100 |
+
class StepResult:
|
| 101 |
+
observation: str
|
| 102 |
+
reward: float
|
| 103 |
+
done: bool
|
| 104 |
+
info: Dict[str, Any] = field(default_factory=dict)
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
@dataclass
|
| 108 |
+
class EpisodeStats:
|
| 109 |
+
steps_taken: int = 0
|
| 110 |
+
total_reward: float = 0.0
|
| 111 |
+
passes_applied: List[str] = field(default_factory=list)
|
| 112 |
+
invalid_actions: int = 0
|
| 113 |
+
no_ops: int = 0
|
| 114 |
+
baseline_cycles: int = 0
|
| 115 |
+
final_cycles: int = 0
|
| 116 |
+
|
| 117 |
+
@property
|
| 118 |
+
def total_improvement_pct(self) -> float:
|
| 119 |
+
if self.baseline_cycles == 0:
|
| 120 |
+
return 0.0
|
| 121 |
+
return ((self.baseline_cycles - self.final_cycles) / self.baseline_cycles) * 100.0
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
class CompilerOptimizationEnv(MCPEnvironment):
|
| 125 |
+
TIME_TAX: float = 1.0
|
| 126 |
+
NO_OP_PENALTY: float = -2.0
|
| 127 |
+
INVALID_ACTION_PENALTY: float = -5.0
|
| 128 |
+
MAX_INVALID_ACTIONS: int = 3
|
| 129 |
+
TERMINAL_BONUS_SCALE: float = 0.5
|
| 130 |
+
|
| 131 |
+
def __init__(
|
| 132 |
+
self,
|
| 133 |
+
role1_engine,
|
| 134 |
+
role3_passes: Dict[str, Any],
|
| 135 |
+
max_steps: int = 10,
|
| 136 |
+
curriculum_level: int = 1,
|
| 137 |
+
):
|
| 138 |
+
self.engine = role1_engine
|
| 139 |
+
self.passes = role3_passes
|
| 140 |
+
self.max_steps = max_steps
|
| 141 |
+
self.curriculum_level = curriculum_level
|
| 142 |
+
self._valid_actions = frozenset(self.passes.keys())
|
| 143 |
+
|
| 144 |
+
self._stats: Optional[EpisodeStats] = None
|
| 145 |
+
self.original_program = None
|
| 146 |
+
self.current_program = None
|
| 147 |
+
self.previous_cycles = 0
|
| 148 |
+
self._consecutive_invalid = 0
|
| 149 |
+
|
| 150 |
+
def reset(self, new_program_json: List[Dict]) -> str:
|
| 151 |
+
self.original_program = copy.deepcopy(new_program_json)
|
| 152 |
+
self.current_program = copy.deepcopy(new_program_json)
|
| 153 |
+
self.previous_cycles = self._safe_count_cycles(self.current_program)
|
| 154 |
+
self._consecutive_invalid = 0
|
| 155 |
+
self._stats = EpisodeStats(
|
| 156 |
+
baseline_cycles=self.previous_cycles,
|
| 157 |
+
final_cycles=self.previous_cycles,
|
| 158 |
+
)
|
| 159 |
+
return self.state()
|
| 160 |
+
|
| 161 |
+
def state(self) -> str:
|
| 162 |
+
assert self.current_program is not None
|
| 163 |
+
return self._program_to_pseudoasm(self.current_program)
|
| 164 |
+
|
| 165 |
+
def step(self, action_string: str) -> StepResult:
|
| 166 |
+
assert self._stats is not None, "Call reset() before step()."
|
| 167 |
+
self._stats.steps_taken += 1
|
| 168 |
+
|
| 169 |
+
if action_string not in self._valid_actions:
|
| 170 |
+
return self._handle_invalid_action(action_string)
|
| 171 |
+
|
| 172 |
+
candidate_program = self.passes[action_string](copy.deepcopy(self.current_program))
|
| 173 |
+
|
| 174 |
+
is_valid = self.engine.verify_equivalence(self.original_program, candidate_program)
|
| 175 |
+
if not is_valid:
|
| 176 |
+
return self._handle_semantic_violation()
|
| 177 |
+
|
| 178 |
+
new_cycles = self._safe_count_cycles(candidate_program)
|
| 179 |
+
reward, info = self._compute_reward(action_string, new_cycles)
|
| 180 |
+
|
| 181 |
+
self.current_program = candidate_program
|
| 182 |
+
self.previous_cycles = new_cycles
|
| 183 |
+
self._stats.final_cycles = new_cycles
|
| 184 |
+
self._stats.total_reward += reward
|
| 185 |
+
self._stats.passes_applied.append(action_string)
|
| 186 |
+
self._consecutive_invalid = 0
|
| 187 |
+
|
| 188 |
+
done = self._stats.steps_taken >= self.max_steps
|
| 189 |
+
if done:
|
| 190 |
+
terminal_bonus = self._terminal_bonus()
|
| 191 |
+
reward += terminal_bonus
|
| 192 |
+
info["terminal_bonus"] = terminal_bonus
|
| 193 |
+
info["reason"] = "max_steps_reached"
|
| 194 |
+
info["episode_stats"] = self._episode_summary()
|
| 195 |
+
|
| 196 |
+
return StepResult(self.state(), reward, done, info)
|
| 197 |
+
|
| 198 |
+
def _compute_reward(self, action: str, new_cycles: int) -> Tuple[float, Dict]:
|
| 199 |
+
info: Dict[str, Any] = {"action": action}
|
| 200 |
+
if self.previous_cycles == 0:
|
| 201 |
+
return -self.TIME_TAX, {**info, "note": "zero_baseline"}
|
| 202 |
+
|
| 203 |
+
old_cycles = self.previous_cycles
|
| 204 |
+
delta_pct = ((old_cycles - new_cycles) / old_cycles) * 100.0
|
| 205 |
+
|
| 206 |
+
if new_cycles == old_cycles:
|
| 207 |
+
reward = self.NO_OP_PENALTY
|
| 208 |
+
if self._stats is not None:
|
| 209 |
+
self._stats.no_ops += 1
|
| 210 |
+
info["no_op"] = True
|
| 211 |
+
else:
|
| 212 |
+
reward = delta_pct - self.TIME_TAX
|
| 213 |
+
info["delta_pct"] = round(delta_pct, 3)
|
| 214 |
+
|
| 215 |
+
info["prev_cycles"] = old_cycles
|
| 216 |
+
info["new_cycles"] = new_cycles
|
| 217 |
+
return reward, info
|
| 218 |
+
|
| 219 |
+
def _terminal_bonus(self) -> float:
|
| 220 |
+
if self._stats is None:
|
| 221 |
+
return 0.0
|
| 222 |
+
return max(0.0, self._stats.total_improvement_pct * self.TERMINAL_BONUS_SCALE)
|
| 223 |
+
|
| 224 |
+
def _compute_crash_penalty(self) -> float:
|
| 225 |
+
return -2.0 * (100.0 * self.max_steps)
|
| 226 |
+
|
| 227 |
+
def _handle_invalid_action(self, action: str) -> StepResult:
|
| 228 |
+
self._consecutive_invalid += 1
|
| 229 |
+
if self._stats is not None:
|
| 230 |
+
self._stats.invalid_actions += 1
|
| 231 |
+
done = self._consecutive_invalid >= self.MAX_INVALID_ACTIONS
|
| 232 |
+
info = {
|
| 233 |
+
"error": f"Unknown action: '{action}'",
|
| 234 |
+
"valid_actions": sorted(self._valid_actions),
|
| 235 |
+
"consecutive_invalid": self._consecutive_invalid,
|
| 236 |
+
}
|
| 237 |
+
if done:
|
| 238 |
+
info["reason"] = "too_many_invalid_actions"
|
| 239 |
+
info["episode_stats"] = self._episode_summary()
|
| 240 |
+
return StepResult(self.state(), self.INVALID_ACTION_PENALTY, done, info)
|
| 241 |
+
|
| 242 |
+
def _handle_semantic_violation(self) -> StepResult:
|
| 243 |
+
return StepResult(
|
| 244 |
+
self.state(),
|
| 245 |
+
self._compute_crash_penalty(),
|
| 246 |
+
True,
|
| 247 |
+
{
|
| 248 |
+
"error": "Semantic equivalence check FAILED.",
|
| 249 |
+
"reason": "semantic_violation",
|
| 250 |
+
"episode_stats": self._episode_summary(),
|
| 251 |
+
},
|
| 252 |
+
)
|
| 253 |
+
|
| 254 |
+
@staticmethod
|
| 255 |
+
def _program_to_pseudoasm(program: List[Dict]) -> str:
|
| 256 |
+
if not program:
|
| 257 |
+
return "; (empty program)"
|
| 258 |
+
lines = []
|
| 259 |
+
for i, instr in enumerate(program):
|
| 260 |
+
op = instr.get("op", "NOP")
|
| 261 |
+
args = instr.get("args", [])
|
| 262 |
+
dest = instr.get("dest")
|
| 263 |
+
typ = instr.get("type", "")
|
| 264 |
+
arg_str = ", ".join(str(a) for a in args)
|
| 265 |
+
type_hint = f":{typ}" if typ else ""
|
| 266 |
+
if dest:
|
| 267 |
+
lines.append(f" {i:>3}: {dest}{type_hint} = {op} {arg_str}")
|
| 268 |
+
else:
|
| 269 |
+
lines.append(f" {i:>3}: {op} {arg_str}")
|
| 270 |
+
return "\n".join(lines)
|
| 271 |
+
|
| 272 |
+
def _safe_count_cycles(self, program: List[Dict]) -> int:
|
| 273 |
+
return max(0, int(self.engine.execute_and_count_cycles(program)))
|
| 274 |
+
|
| 275 |
+
def _episode_summary(self) -> Dict:
|
| 276 |
+
s = self._stats
|
| 277 |
+
if s is None:
|
| 278 |
+
return {}
|
| 279 |
+
return {
|
| 280 |
+
"steps": s.steps_taken,
|
| 281 |
+
"total_reward": round(s.total_reward, 3),
|
| 282 |
+
"passes_applied": s.passes_applied,
|
| 283 |
+
"invalid_actions": s.invalid_actions,
|
| 284 |
+
"no_ops": s.no_ops,
|
| 285 |
+
"baseline_cycles": s.baseline_cycles,
|
| 286 |
+
"final_cycles": s.final_cycles,
|
| 287 |
+
"total_improvement_pct": round(s.total_improvement_pct, 3),
|
| 288 |
+
}
|
| 289 |
+
|
| 290 |
+
def available_actions(self) -> List[str]:
|
| 291 |
+
return sorted(self._valid_actions)
|
| 292 |
+
|
| 293 |
+
|
| 294 |
+
class MockEngine:
|
| 295 |
+
"""Stub engine: cycles = instruction count, all programs semantically valid."""
|
| 296 |
+
|
| 297 |
+
def execute_and_count_cycles(self, program):
|
| 298 |
+
return len(program)
|
| 299 |
+
|
| 300 |
+
def verify_equivalence(self, original, candidate):
|
| 301 |
+
return True
|
| 302 |
+
|
| 303 |
+
|
| 304 |
+
MOCK_PASSES = {
|
| 305 |
+
"constant_folding": lambda p: p[:-1] if len(p) > 1 else p,
|
| 306 |
+
"dead_code_elimination": lambda p: p[:-1] if len(p) > 2 else p,
|
| 307 |
+
"loop_unrolling": lambda p: p,
|
| 308 |
+
}
|
| 309 |
+
|
| 310 |
+
SAMPLE_PROGRAM = [
|
| 311 |
+
{"op": "const", "dest": "x", "args": ["5"], "type": "int"},
|
| 312 |
+
{"op": "const", "dest": "y", "args": ["3"], "type": "int"},
|
| 313 |
+
{"op": "add", "dest": "z", "args": ["x", "y"], "type": "int"},
|
| 314 |
+
{"op": "mul", "dest": "w", "args": ["z", "x"], "type": "int"},
|
| 315 |
+
{"op": "ret", "args": ["w"]},
|
| 316 |
+
]
|
space/space/space/space/space/space/space/space/space/space/space/space/Dockerfile
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Optional: use Hugging Face Space SDK = docker (set `sdk: docker` in README.md).
|
| 2 |
+
# Default README uses Gradio SDK and does not require this image.
|
| 3 |
+
|
| 4 |
+
FROM python:3.11-slim
|
| 5 |
+
|
| 6 |
+
WORKDIR /app
|
| 7 |
+
|
| 8 |
+
ENV PYTHONUNBUFFERED=1 \
|
| 9 |
+
PIP_NO_CACHE_DIR=1 \
|
| 10 |
+
GRADIO_SERVER_NAME=0.0.0.0
|
| 11 |
+
|
| 12 |
+
COPY requirements.txt .
|
| 13 |
+
RUN pip install --upgrade pip && pip install -r requirements.txt
|
| 14 |
+
|
| 15 |
+
COPY . .
|
| 16 |
+
|
| 17 |
+
EXPOSE 7860
|
| 18 |
+
|
| 19 |
+
CMD ["python", "app.py"]
|
space/space/space/space/space/space/space/space/space/space/space/space/README.md
CHANGED
|
@@ -1,9 +1,54 @@
|
|
| 1 |
---
|
| 2 |
-
title: Compiler
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
-
sdk:
|
|
|
|
| 7 |
app_file: app.py
|
| 8 |
pinned: false
|
|
|
|
| 9 |
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Compiler Tetris — Toy-IR RL Demo
|
| 3 |
+
emoji: 🧩
|
| 4 |
+
colorFrom: gray
|
| 5 |
+
colorTo: indigo
|
| 6 |
+
sdk: gradio
|
| 7 |
+
sdk_version: 5.12.0
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
+
license: mit
|
| 11 |
---
|
| 12 |
+
|
| 13 |
+
# Compiler Tetris (Toy-IR) — Hugging Face Space
|
| 14 |
+
|
| 15 |
+
Interactive **CPU** demo for the Meta / OpenEnv-style **compiler phase-ordering** project:
|
| 16 |
+
|
| 17 |
+
- **Deliverable 2 — state translation:** JSON Toy-IR → compact pseudo-assembly for the LLM.
|
| 18 |
+
- **Deliverable 2 — format enforcement:** resilient extraction of a JSON **pass array** from noisy model text.
|
| 19 |
+
- **CompilerOptimizationEnv** (mock Role 1 engine + mock passes): step through passes and inspect rewards.
|
| 20 |
+
|
| 21 |
+
Full **GRPO / Unsloth** training is not run here (heavy GPU + long installs). Use your notebooks on Colab or a GPU Space for training.
|
| 22 |
+
|
| 23 |
+
## Source notebooks (parent repo)
|
| 24 |
+
|
| 25 |
+
- `compiler_optimization_grpo.ipynb`
|
| 26 |
+
- `role2_deliverable3_training_loop (2) (1).ipynb`
|
| 27 |
+
- `compiler_tetris (1).ipynb`
|
| 28 |
+
- `metahack1 (1).ipynb`
|
| 29 |
+
|
| 30 |
+
## Deploy this folder as a new Space
|
| 31 |
+
|
| 32 |
+
1. Create a new Space on Hugging Face (SDK: **Gradio**).
|
| 33 |
+
2. Upload the contents of this `hf_space/` directory to the Space repository root (`app.py`, `requirements.txt`, `README.md`).
|
| 34 |
+
3. Optional: add `Dockerfile` only if you switch the Space to **Docker** (see below).
|
| 35 |
+
|
| 36 |
+
## Optional: Docker SDK instead of Gradio SDK
|
| 37 |
+
|
| 38 |
+
If you want the Space to build from `Dockerfile`, change the YAML header to:
|
| 39 |
+
|
| 40 |
+
```yaml
|
| 41 |
+
sdk: docker
|
| 42 |
+
```
|
| 43 |
+
|
| 44 |
+
and remove Gradio-specific keys (`sdk_version`, `app_file`). The container runs `python app.py`, which listens on the `PORT` environment variable provided by Spaces.
|
| 45 |
+
|
| 46 |
+
## Local run
|
| 47 |
+
|
| 48 |
+
```bash
|
| 49 |
+
cd hf_space
|
| 50 |
+
pip install -r requirements.txt
|
| 51 |
+
python app.py
|
| 52 |
+
```
|
| 53 |
+
|
| 54 |
+
Then open `http://127.0.0.1:7860`.
|
space/space/space/space/space/space/space/space/space/space/space/space/app.py
CHANGED
|
@@ -1,182 +1,167 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import json
|
| 2 |
-
import
|
| 3 |
-
import
|
| 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 |
-
return
|
| 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 |
-
Returns the current observation to the LLM.
|
| 123 |
-
"""
|
| 124 |
-
return f"Current Step: {self.current_step}/{self.max_steps}\n\n{self.current_state_string}"
|
| 125 |
-
|
| 126 |
-
def _translate_state(self, raw_json: List[Dict]) -> str:
|
| 127 |
-
"""
|
| 128 |
-
Translates raw AST JSON into clean pseudo-assembly.
|
| 129 |
-
Strips all UUIDs and AST metadata.
|
| 130 |
-
"""
|
| 131 |
-
pseudo_assembly = []
|
| 132 |
-
instruction_count = 1
|
| 133 |
-
|
| 134 |
-
for inst in raw_json:
|
| 135 |
-
op = inst.get("op", "UNKNOWN")
|
| 136 |
-
src1 = inst.get("src1", "")
|
| 137 |
-
src2 = inst.get("src2", "")
|
| 138 |
-
dest = inst.get("dest", "")
|
| 139 |
-
|
| 140 |
-
# Format arguments cleanly
|
| 141 |
-
args = f"{src1}" if src2 is None else f"{src1}, {src2}"
|
| 142 |
-
|
| 143 |
-
if dest:
|
| 144 |
-
line = f"{instruction_count}. {dest} = {op} {args}"
|
| 145 |
-
else:
|
| 146 |
-
line = f"{instruction_count}. {op} {args}"
|
| 147 |
-
|
| 148 |
-
pseudo_assembly.append(line)
|
| 149 |
-
instruction_count += 1
|
| 150 |
-
|
| 151 |
-
return "\n".join(pseudo_assembly)
|
| 152 |
-
|
| 153 |
-
def _build_step_response(self, reward: float, done: bool, error: str = None, info: dict = None) -> Dict[str, Any]:
|
| 154 |
-
"""Helper to format the standard OpenEnv step return dictionary."""
|
| 155 |
-
response = {
|
| 156 |
-
"reward": reward,
|
| 157 |
-
"done": done,
|
| 158 |
-
"state": self.state()
|
| 159 |
-
}
|
| 160 |
-
if error:
|
| 161 |
-
response["error"] = error
|
| 162 |
-
if info:
|
| 163 |
-
response["info"] = info
|
| 164 |
-
return response
|
| 165 |
-
|
| 166 |
-
def _mock_generator(self):
|
| 167 |
-
"""Mock data so the server runs before Role 1 integrates their engine."""
|
| 168 |
-
return [
|
| 169 |
-
{"op": "CONST", "dest": "a", "src1": 2, "src2": None},
|
| 170 |
-
{"op": "CONST", "dest": "b", "src1": 3, "src2": None},
|
| 171 |
-
{"op": "ADD", "dest": "c", "src1": "a", "src2": "b"}
|
| 172 |
-
]
|
| 173 |
|
| 174 |
if __name__ == "__main__":
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
env = CompilerEnv()
|
| 178 |
-
# openenv.run() or start() depending on the specific MCP wrapper version
|
| 179 |
-
env.start()
|
| 180 |
-
except Exception as e:
|
| 181 |
-
logger.error(f"Failed to start environment server: {e}")
|
| 182 |
-
logger.error(traceback.format_exc())
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Hugging Face Spaces entrypoint — Gradio UI for Toy-IR compiler RL demo.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
from __future__ import annotations
|
| 6 |
+
|
| 7 |
import json
|
| 8 |
+
import os
|
| 9 |
+
import sys
|
| 10 |
+
import textwrap
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
import gradio as gr
|
| 14 |
+
|
| 15 |
+
_ROOT = Path(__file__).resolve().parent
|
| 16 |
+
if str(_ROOT) not in sys.path:
|
| 17 |
+
sys.path.insert(0, str(_ROOT))
|
| 18 |
+
|
| 19 |
+
from runtime_core import (
|
| 20 |
+
CompilerOptimizationEnv,
|
| 21 |
+
Deliverable2_Formatter,
|
| 22 |
+
MOCK_PASSES,
|
| 23 |
+
MockEngine,
|
| 24 |
+
SAMPLE_PROGRAM,
|
| 25 |
+
)
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
DEFAULT_IR = json.dumps(SAMPLE_PROGRAM, indent=2)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def translate_ir(ir_json: str) -> tuple[str, str]:
|
| 32 |
+
try:
|
| 33 |
+
data = json.loads(ir_json.strip() or "[]")
|
| 34 |
+
except json.JSONDecodeError as e:
|
| 35 |
+
return "", f"Invalid JSON: {e}"
|
| 36 |
+
if not isinstance(data, list):
|
| 37 |
+
return "", "JSON root must be a list of instruction objects."
|
| 38 |
+
d2 = Deliverable2_Formatter.translate_state(data)
|
| 39 |
+
env = CompilerOptimizationEnv(MockEngine(), MOCK_PASSES, max_steps=10)
|
| 40 |
+
env.reset(data)
|
| 41 |
+
internal = env.state()
|
| 42 |
+
return d2, internal
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def parse_llm_output(llm_text: str) -> str:
|
| 46 |
+
try:
|
| 47 |
+
arr = Deliverable2_Formatter.extract_action_array(llm_text)
|
| 48 |
+
return json.dumps(arr, indent=2)
|
| 49 |
+
except ValueError as e:
|
| 50 |
+
return str(e)
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def parse_action_line(line: str) -> list[str]:
|
| 54 |
+
line = line.strip()
|
| 55 |
+
if not line:
|
| 56 |
+
return []
|
| 57 |
+
try:
|
| 58 |
+
got = Deliverable2_Formatter.extract_action_array(line)
|
| 59 |
+
return [str(x).strip() for x in got]
|
| 60 |
+
except ValueError:
|
| 61 |
+
parts = [p.strip().strip("\"'") for p in line.split(",") if p.strip()]
|
| 62 |
+
return [p.lower() for p in parts]
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def run_episode(ir_json: str, actions_multiline: str, max_steps: int) -> str:
|
| 66 |
+
try:
|
| 67 |
+
program = json.loads(ir_json.strip() or "[]")
|
| 68 |
+
except json.JSONDecodeError as e:
|
| 69 |
+
return f"Invalid program JSON: {e}"
|
| 70 |
+
if not isinstance(program, list):
|
| 71 |
+
return "Program must be a JSON list."
|
| 72 |
+
|
| 73 |
+
lines = [ln for ln in actions_multiline.splitlines() if ln.strip()]
|
| 74 |
+
actions: list[str] = []
|
| 75 |
+
for ln in lines:
|
| 76 |
+
actions.extend(parse_action_line(ln))
|
| 77 |
+
if not actions:
|
| 78 |
+
return "No actions parsed. Enter JSON arrays or comma-separated pass names."
|
| 79 |
+
|
| 80 |
+
engine = MockEngine()
|
| 81 |
+
env = CompilerOptimizationEnv(engine, MOCK_PASSES, max_steps=int(max_steps))
|
| 82 |
+
obs0 = env.reset(program)
|
| 83 |
+
log = [
|
| 84 |
+
f"Baseline cycles: {env.previous_cycles}",
|
| 85 |
+
f"Initial observation (env):\n{obs0}",
|
| 86 |
+
"",
|
| 87 |
+
f"Deliverable 2 pseudo-assembly:\n{Deliverable2_Formatter.translate_state(program)}",
|
| 88 |
+
"",
|
| 89 |
+
"--- steps ---",
|
| 90 |
+
]
|
| 91 |
+
for i, act in enumerate(actions, start=1):
|
| 92 |
+
r = env.step(act)
|
| 93 |
+
log.append(
|
| 94 |
+
f"{i}. {act!r} → reward={r.reward:+.3f} done={r.done} cycles={env.previous_cycles}"
|
| 95 |
+
)
|
| 96 |
+
if r.info:
|
| 97 |
+
slim = {k: v for k, v in r.info.items() if k in ("delta_pct", "error", "no_op", "reason", "terminal_bonus")}
|
| 98 |
+
if slim:
|
| 99 |
+
log.append(f" info: {slim}")
|
| 100 |
+
if r.done:
|
| 101 |
+
break
|
| 102 |
+
log.append("")
|
| 103 |
+
log.append("Episode summary:")
|
| 104 |
+
log.append(json.dumps(env._episode_summary(), indent=2))
|
| 105 |
+
return "\n".join(log)
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def build_demo() -> gr.Blocks:
|
| 109 |
+
with gr.Blocks(title="Compiler Tetris — Toy-IR RL Demo") as demo:
|
| 110 |
+
gr.Markdown(
|
| 111 |
+
textwrap.dedent(
|
| 112 |
+
"""
|
| 113 |
+
# Compiler optimization (Toy-IR) — interactive demo
|
| 114 |
+
|
| 115 |
+
This Space runs **CPU-only** demos: **Deliverable 2** state translation and action parsing,
|
| 116 |
+
plus the **CompilerOptimizationEnv** smoke loop from your notebooks. Full GRPO / Unsloth
|
| 117 |
+
training belongs on Colab or a GPU Space.
|
| 118 |
+
"""
|
| 119 |
+
).strip()
|
| 120 |
+
)
|
| 121 |
+
|
| 122 |
+
with gr.Tabs():
|
| 123 |
+
with gr.Tab("Translate IR (D2)"):
|
| 124 |
+
gr.Markdown("Paste Toy-IR as a JSON **list** of instruction dicts (`op`, `args`, `dest`, …).")
|
| 125 |
+
ir_in = gr.Textbox(label="IR JSON", value=DEFAULT_IR, lines=12, max_lines=24)
|
| 126 |
+
btn_t = gr.Button("Translate", variant="primary")
|
| 127 |
+
out_d2 = gr.Textbox(label="Deliverable2 pseudo-assembly (LLM-facing)", lines=10)
|
| 128 |
+
out_env = gr.Textbox(label="Env internal pseudo-asm (with type hints)", lines=10)
|
| 129 |
+
btn_t.click(translate_ir, [ir_in], [out_d2, out_env])
|
| 130 |
+
|
| 131 |
+
with gr.Tab("Parse LLM output (D2)"):
|
| 132 |
+
gr.Markdown(
|
| 133 |
+
"Paste model output. A JSON array is preferred; bracket extraction handles light noise."
|
| 134 |
+
)
|
| 135 |
+
llm_in = gr.Textbox(
|
| 136 |
+
label="LLM output",
|
| 137 |
+
value='Here is the plan: ["constant_folding", "dead_code_elimination"]',
|
| 138 |
+
lines=4,
|
| 139 |
+
)
|
| 140 |
+
out_parse = gr.Textbox(label="Parsed array or error", lines=6)
|
| 141 |
+
gr.Button("Parse", variant="primary").click(parse_llm_output, [llm_in], [out_parse])
|
| 142 |
+
|
| 143 |
+
with gr.Tab("Env episode (mock engine)"):
|
| 144 |
+
gr.Markdown(
|
| 145 |
+
"One JSON program + actions: each line can be a JSON array or comma-separated names."
|
| 146 |
+
)
|
| 147 |
+
ir_ep = gr.Textbox(label="Program JSON", value=DEFAULT_IR, lines=10)
|
| 148 |
+
acts = gr.Textbox(
|
| 149 |
+
label="Actions (one JSON array or comma-list per line)",
|
| 150 |
+
value='["constant_folding", "dead_code_elimination"]\nloop_unrolling',
|
| 151 |
+
lines=5,
|
| 152 |
+
)
|
| 153 |
+
ms = gr.Slider(1, 20, value=10, step=1, label="max_steps")
|
| 154 |
+
out_ep = gr.Textbox(label="Log", lines=20)
|
| 155 |
+
gr.Button("Run episode", variant="primary").click(run_episode, [ir_ep, acts, ms], [out_ep])
|
| 156 |
+
|
| 157 |
+
gr.Markdown(
|
| 158 |
+
"Source notebooks in the parent repo: `compiler_optimization_grpo.ipynb`, "
|
| 159 |
+
"`role2_deliverable3_training_loop (2) (1).ipynb`, `compiler_tetris (1).ipynb`, `metahack1 (1).ipynb`."
|
| 160 |
)
|
| 161 |
|
| 162 |
+
return demo
|
| 163 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 164 |
|
| 165 |
if __name__ == "__main__":
|
| 166 |
+
port = int(os.environ.get("PORT", "7860"))
|
| 167 |
+
build_demo().launch(server_name="0.0.0.0", server_port=port)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
space/space/space/space/space/space/space/space/space/space/space/space/requirements.txt
CHANGED
|
@@ -1,24 +1,2 @@
|
|
| 1 |
-
#
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
# Core RL Environment
|
| 5 |
-
gymnasium
|
| 6 |
-
|
| 7 |
-
# Hugging Face Training Stack (Versions matched to your D3 notebook)
|
| 8 |
-
trl==0.23.1
|
| 9 |
-
transformers==4.57.1
|
| 10 |
-
peft
|
| 11 |
-
accelerate
|
| 12 |
-
bitsandbytes
|
| 13 |
-
|
| 14 |
-
# Unsloth for efficient 4-bit QLoRA training on T4 GPUs
|
| 15 |
-
# Note: Unsloth often prefers being installed via their specific pip wheel or git,
|
| 16 |
-
# but this is standard for a requirements file.
|
| 17 |
-
unsloth
|
| 18 |
-
|
| 19 |
-
# PyTorch (Will default to standard compatible version if no index is specified)
|
| 20 |
-
torch
|
| 21 |
-
|
| 22 |
-
# Logging & Visualizations (For Day 2 judging criteria)
|
| 23 |
-
wandb
|
| 24 |
-
matplotlib
|
|
|
|
| 1 |
+
# Hugging Face Spaces — Gradio SDK (CPU demo)
|
| 2 |
+
gradio>=4.44.0,<6
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
space/space/space/space/space/space/space/space/space/space/space/space/space/compiler_optimization_grpo.ipynb
ADDED
|
@@ -0,0 +1,959 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"nbformat": 4,
|
| 3 |
+
"nbformat_minor": 5,
|
| 4 |
+
"metadata": {
|
| 5 |
+
"kernelspec": {
|
| 6 |
+
"display_name": "Python 3",
|
| 7 |
+
"language": "python",
|
| 8 |
+
"name": "python3"
|
| 9 |
+
},
|
| 10 |
+
"language_info": {
|
| 11 |
+
"name": "python",
|
| 12 |
+
"version": "3.10.0"
|
| 13 |
+
},
|
| 14 |
+
"colab": {
|
| 15 |
+
"provenance": [],
|
| 16 |
+
"gpuType": "T4"
|
| 17 |
+
},
|
| 18 |
+
"accelerator": "GPU"
|
| 19 |
+
},
|
| 20 |
+
"cells": [
|
| 21 |
+
{
|
| 22 |
+
"cell_type": "markdown",
|
| 23 |
+
"id": "title-cell",
|
| 24 |
+
"metadata": {},
|
| 25 |
+
"source": [
|
| 26 |
+
"# 🔧 Compiler Optimization RL Environment\n",
|
| 27 |
+
"### OpenEnv Hackathon 2026 — Theme #2: Long-Horizon Planning\n",
|
| 28 |
+
"\n",
|
| 29 |
+
"**What this notebook does:**\n",
|
| 30 |
+
"1. Defines a fully OpenEnv-compliant `CompilerOptimizationEnv`\n",
|
| 31 |
+
"2. Loads `Qwen2.5-3B-Instruct` via Unsloth (4-bit QLoRA)\n",
|
| 32 |
+
"3. Trains with GRPO (TRL) — LLM learns to pick compiler passes that reduce CPU cycles\n",
|
| 33 |
+
"4. Runs a smoke-test with a mock engine so you can verify reward logic without real hardware\n",
|
| 34 |
+
"5. Plots reward curves\n",
|
| 35 |
+
"\n",
|
| 36 |
+
"---\n",
|
| 37 |
+
"**Stack:** `unsloth` · `trl` · `openenv` · `wandb` · `matplotlib`\n",
|
| 38 |
+
"\n",
|
| 39 |
+
"> **Runtime:** Google Colab T4 GPU recommended. For the smoke-test only, CPU is fine."
|
| 40 |
+
]
|
| 41 |
+
},
|
| 42 |
+
{
|
| 43 |
+
"cell_type": "markdown",
|
| 44 |
+
"id": "install-header",
|
| 45 |
+
"metadata": {},
|
| 46 |
+
"source": [
|
| 47 |
+
"## 📦 Cell 1 — Install Dependencies"
|
| 48 |
+
]
|
| 49 |
+
},
|
| 50 |
+
{
|
| 51 |
+
"cell_type": "code",
|
| 52 |
+
"execution_count": null,
|
| 53 |
+
"id": "install-cell",
|
| 54 |
+
"metadata": {},
|
| 55 |
+
"outputs": [],
|
| 56 |
+
"source": [
|
| 57 |
+
"# Install all required packages\n",
|
| 58 |
+
"# Unsloth must be installed before trl to get the right CUDA kernels\n",
|
| 59 |
+
"!pip install \"unsloth[colab-new] @ git+https://github.com/unslothai/unsloth.git\" --quiet\n",
|
| 60 |
+
"!pip install trl datasets transformers accelerate peft bitsandbytes --quiet\n",
|
| 61 |
+
"!pip install wandb matplotlib --quiet\n",
|
| 62 |
+
"\n",
|
| 63 |
+
"# Optional: install openenv for production use\n",
|
| 64 |
+
"# !pip install openenv --quiet\n",
|
| 65 |
+
"\n",
|
| 66 |
+
"print(\"✅ All packages installed\")"
|
| 67 |
+
]
|
| 68 |
+
},
|
| 69 |
+
{
|
| 70 |
+
"cell_type": "markdown",
|
| 71 |
+
"id": "env-header",
|
| 72 |
+
"metadata": {},
|
| 73 |
+
"source": [
|
| 74 |
+
"## 🌍 Cell 2 — CompilerOptimizationEnv (OpenEnv-Compliant)"
|
| 75 |
+
]
|
| 76 |
+
},
|
| 77 |
+
{
|
| 78 |
+
"cell_type": "code",
|
| 79 |
+
"execution_count": null,
|
| 80 |
+
"id": "env-cell",
|
| 81 |
+
"metadata": {},
|
| 82 |
+
"outputs": [],
|
| 83 |
+
"source": [
|
| 84 |
+
"\"\"\"\n",
|
| 85 |
+
"CompilerOptimizationEnv — OpenEnv-compatible RL Environment\n",
|
| 86 |
+
"OpenEnv Hackathon 2026 | Theme #2: Long-Horizon Planning\n",
|
| 87 |
+
"\n",
|
| 88 |
+
"Key improvements over naive baseline:\n",
|
| 89 |
+
" ✓ Inherits from MCPEnvironment (OpenEnv API compliant)\n",
|
| 90 |
+
" ✓ Dynamic crash_penalty scaled to reward range (not hardcoded -1000)\n",
|
| 91 |
+
" ✓ Soft termination: invalid actions give 3 chances before episode ends\n",
|
| 92 |
+
" ✓ No-op detection: penalises passes that change nothing\n",
|
| 93 |
+
" ✓ Terminal bonus: rewards cumulative improvement, not just greedy steps\n",
|
| 94 |
+
" ✓ StepResult / EpisodeStats dataclasses for clean interfaces\n",
|
| 95 |
+
" ✓ Curriculum level support\n",
|
| 96 |
+
"\"\"\"\n",
|
| 97 |
+
"\n",
|
| 98 |
+
"import copy\n",
|
| 99 |
+
"import math\n",
|
| 100 |
+
"from dataclasses import dataclass, field\n",
|
| 101 |
+
"from typing import Any, Dict, List, Optional, Tuple\n",
|
| 102 |
+
"\n",
|
| 103 |
+
"\n",
|
| 104 |
+
"# ── Stub base class ───────────────────────────────────────────────────────────\n",
|
| 105 |
+
"# In production replace with: from openenv import MCPEnvironment\n",
|
| 106 |
+
"class MCPEnvironment:\n",
|
| 107 |
+
" \"\"\"Minimal stub. In production: `from openenv import MCPEnvironment`\"\"\"\n",
|
| 108 |
+
" def reset(self, *args, **kwargs): raise NotImplementedError\n",
|
| 109 |
+
" def step(self, *args, **kwargs): raise NotImplementedError\n",
|
| 110 |
+
" def state(self): raise NotImplementedError\n",
|
| 111 |
+
"\n",
|
| 112 |
+
"\n",
|
| 113 |
+
"# ── Data classes ──────────────────────────────────────────────────────────────\n",
|
| 114 |
+
"@dataclass\n",
|
| 115 |
+
"class StepResult:\n",
|
| 116 |
+
" observation: str\n",
|
| 117 |
+
" reward: float\n",
|
| 118 |
+
" done: bool\n",
|
| 119 |
+
" info: Dict[str, Any] = field(default_factory=dict)\n",
|
| 120 |
+
"\n",
|
| 121 |
+
"\n",
|
| 122 |
+
"@dataclass\n",
|
| 123 |
+
"class EpisodeStats:\n",
|
| 124 |
+
" steps_taken: int = 0\n",
|
| 125 |
+
" total_reward: float = 0.0\n",
|
| 126 |
+
" passes_applied: List[str] = field(default_factory=list)\n",
|
| 127 |
+
" invalid_actions: int = 0\n",
|
| 128 |
+
" no_ops: int = 0\n",
|
| 129 |
+
" baseline_cycles: int = 0\n",
|
| 130 |
+
" final_cycles: int = 0\n",
|
| 131 |
+
"\n",
|
| 132 |
+
" @property\n",
|
| 133 |
+
" def total_improvement_pct(self) -> float:\n",
|
| 134 |
+
" if self.baseline_cycles == 0:\n",
|
| 135 |
+
" return 0.0\n",
|
| 136 |
+
" return ((self.baseline_cycles - self.final_cycles) / self.baseline_cycles) * 100.0\n",
|
| 137 |
+
"\n",
|
| 138 |
+
"\n",
|
| 139 |
+
"# ── Core Environment ─────────────────────────��────────────────────────────────\n",
|
| 140 |
+
"class CompilerOptimizationEnv(MCPEnvironment):\n",
|
| 141 |
+
" \"\"\"\n",
|
| 142 |
+
" RL environment where an LLM agent sequentially applies compiler\n",
|
| 143 |
+
" optimization passes to minimise CPU cycle count while preserving\n",
|
| 144 |
+
" program semantics.\n",
|
| 145 |
+
"\n",
|
| 146 |
+
" State : Pseudo-assembly representation of the current IR (text)\n",
|
| 147 |
+
" Actions: Named compiler passes (strings) from the `passes` dictionary\n",
|
| 148 |
+
" Reward : % cycle improvement per step − time_tax, with terminal bonus\n",
|
| 149 |
+
" Done : Semantic violation | max steps reached | too many invalid actions\n",
|
| 150 |
+
" \"\"\"\n",
|
| 151 |
+
"\n",
|
| 152 |
+
" # Hyperparameters\n",
|
| 153 |
+
" TIME_TAX: float = 1.0\n",
|
| 154 |
+
" NO_OP_PENALTY: float = -2.0\n",
|
| 155 |
+
" INVALID_ACTION_PENALTY: float = -5.0\n",
|
| 156 |
+
" MAX_INVALID_ACTIONS: int = 3\n",
|
| 157 |
+
" TERMINAL_BONUS_SCALE: float = 0.5\n",
|
| 158 |
+
"\n",
|
| 159 |
+
" def __init__(\n",
|
| 160 |
+
" self,\n",
|
| 161 |
+
" role1_engine,\n",
|
| 162 |
+
" role3_passes: Dict[str, Any],\n",
|
| 163 |
+
" max_steps: int = 10,\n",
|
| 164 |
+
" curriculum_level: int = 1,\n",
|
| 165 |
+
" ):\n",
|
| 166 |
+
" self.engine = role1_engine\n",
|
| 167 |
+
" self.passes = role3_passes\n",
|
| 168 |
+
" self.max_steps = max_steps\n",
|
| 169 |
+
" self.curriculum_level = curriculum_level\n",
|
| 170 |
+
" self._valid_actions = frozenset(self.passes.keys())\n",
|
| 171 |
+
"\n",
|
| 172 |
+
" # Episode state\n",
|
| 173 |
+
" self._stats: Optional[EpisodeStats] = None\n",
|
| 174 |
+
" self.original_program = None\n",
|
| 175 |
+
" self.current_program = None\n",
|
| 176 |
+
" self.previous_cycles = 0\n",
|
| 177 |
+
" self._consecutive_invalid = 0\n",
|
| 178 |
+
"\n",
|
| 179 |
+
" # ── OpenEnv API ───────────────────────────────────────────────────────────\n",
|
| 180 |
+
" def reset(self, new_program_json: List[Dict]) -> str:\n",
|
| 181 |
+
" self.original_program = copy.deepcopy(new_program_json)\n",
|
| 182 |
+
" self.current_program = copy.deepcopy(new_program_json)\n",
|
| 183 |
+
" self.previous_cycles = self._safe_count_cycles(self.current_program)\n",
|
| 184 |
+
" self._consecutive_invalid = 0\n",
|
| 185 |
+
" self._stats = EpisodeStats(\n",
|
| 186 |
+
" baseline_cycles=self.previous_cycles,\n",
|
| 187 |
+
" final_cycles=self.previous_cycles,\n",
|
| 188 |
+
" )\n",
|
| 189 |
+
" return self.state()\n",
|
| 190 |
+
"\n",
|
| 191 |
+
" def state(self) -> str:\n",
|
| 192 |
+
" return self._program_to_pseudoasm(self.current_program)\n",
|
| 193 |
+
"\n",
|
| 194 |
+
" def step(self, action_string: str) -> StepResult:\n",
|
| 195 |
+
" assert self._stats is not None, \"Call reset() before step().\"\n",
|
| 196 |
+
" self._stats.steps_taken += 1\n",
|
| 197 |
+
"\n",
|
| 198 |
+
" if action_string not in self._valid_actions:\n",
|
| 199 |
+
" return self._handle_invalid_action(action_string)\n",
|
| 200 |
+
"\n",
|
| 201 |
+
" candidate_program = self.passes[action_string](\n",
|
| 202 |
+
" copy.deepcopy(self.current_program)\n",
|
| 203 |
+
" )\n",
|
| 204 |
+
"\n",
|
| 205 |
+
" is_valid = self.engine.verify_equivalence(self.original_program, candidate_program)\n",
|
| 206 |
+
" if not is_valid:\n",
|
| 207 |
+
" return self._handle_semantic_violation()\n",
|
| 208 |
+
"\n",
|
| 209 |
+
" new_cycles = self._safe_count_cycles(candidate_program)\n",
|
| 210 |
+
" reward, info = self._compute_reward(action_string, new_cycles)\n",
|
| 211 |
+
"\n",
|
| 212 |
+
" self.current_program = candidate_program\n",
|
| 213 |
+
" self.previous_cycles = new_cycles\n",
|
| 214 |
+
" self._stats.final_cycles = new_cycles\n",
|
| 215 |
+
" self._stats.total_reward += reward\n",
|
| 216 |
+
" self._stats.passes_applied.append(action_string)\n",
|
| 217 |
+
" self._consecutive_invalid = 0\n",
|
| 218 |
+
"\n",
|
| 219 |
+
" done = self._stats.steps_taken >= self.max_steps\n",
|
| 220 |
+
" if done:\n",
|
| 221 |
+
" terminal_bonus = self._terminal_bonus()\n",
|
| 222 |
+
" reward += terminal_bonus\n",
|
| 223 |
+
" info[\"terminal_bonus\"] = terminal_bonus\n",
|
| 224 |
+
" info[\"reason\"] = \"max_steps_reached\"\n",
|
| 225 |
+
" info[\"episode_stats\"] = self._episode_summary()\n",
|
| 226 |
+
"\n",
|
| 227 |
+
" return StepResult(self.state(), reward, done, info)\n",
|
| 228 |
+
"\n",
|
| 229 |
+
" # ── Reward logic ──────────────────────────────────────────────────────────\n",
|
| 230 |
+
" def _compute_reward(self, action: str, new_cycles: int) -> Tuple[float, Dict]:\n",
|
| 231 |
+
" info: Dict[str, Any] = {\"action\": action}\n",
|
| 232 |
+
" if self.previous_cycles == 0:\n",
|
| 233 |
+
" return -self.TIME_TAX, {**info, \"note\": \"zero_baseline\"}\n",
|
| 234 |
+
"\n",
|
| 235 |
+
" delta_pct = ((self.previous_cycles - new_cycles) / self.previous_cycles) * 100.0\n",
|
| 236 |
+
"\n",
|
| 237 |
+
" if new_cycles == self.previous_cycles:\n",
|
| 238 |
+
" reward = self.NO_OP_PENALTY\n",
|
| 239 |
+
" self._stats.no_ops += 1\n",
|
| 240 |
+
" info[\"no_op\"] = True\n",
|
| 241 |
+
" else:\n",
|
| 242 |
+
" reward = delta_pct - self.TIME_TAX\n",
|
| 243 |
+
" info[\"delta_pct\"] = round(delta_pct, 3)\n",
|
| 244 |
+
"\n",
|
| 245 |
+
" info[\"prev_cycles\"] = self.previous_cycles\n",
|
| 246 |
+
" info[\"new_cycles\"] = new_cycles\n",
|
| 247 |
+
" return reward, info\n",
|
| 248 |
+
"\n",
|
| 249 |
+
" def _terminal_bonus(self) -> float:\n",
|
| 250 |
+
" return max(0.0, self._stats.total_improvement_pct * self.TERMINAL_BONUS_SCALE)\n",
|
| 251 |
+
"\n",
|
| 252 |
+
" def _compute_crash_penalty(self) -> float:\n",
|
| 253 |
+
" # 2× best possible episode reward — always catastrophic, never overwhelming\n",
|
| 254 |
+
" return -2.0 * (100.0 * self.max_steps)\n",
|
| 255 |
+
"\n",
|
| 256 |
+
" # ── Error handlers ────────────────────────────────────────────────────────\n",
|
| 257 |
+
" def _handle_invalid_action(self, action: str) -> StepResult:\n",
|
| 258 |
+
" self._consecutive_invalid += 1\n",
|
| 259 |
+
" self._stats.invalid_actions += 1\n",
|
| 260 |
+
" done = self._consecutive_invalid >= self.MAX_INVALID_ACTIONS\n",
|
| 261 |
+
" info = {\n",
|
| 262 |
+
" \"error\": f\"Unknown action: '{action}'\",\n",
|
| 263 |
+
" \"valid_actions\": sorted(self._valid_actions),\n",
|
| 264 |
+
" \"consecutive_invalid\": self._consecutive_invalid,\n",
|
| 265 |
+
" }\n",
|
| 266 |
+
" if done:\n",
|
| 267 |
+
" info[\"reason\"] = \"too_many_invalid_actions\"\n",
|
| 268 |
+
" info[\"episode_stats\"] = self._episode_summary()\n",
|
| 269 |
+
" return StepResult(self.state(), self.INVALID_ACTION_PENALTY, done, info)\n",
|
| 270 |
+
"\n",
|
| 271 |
+
" def _handle_semantic_violation(self) -> StepResult:\n",
|
| 272 |
+
" return StepResult(\n",
|
| 273 |
+
" self.state(),\n",
|
| 274 |
+
" self._compute_crash_penalty(),\n",
|
| 275 |
+
" True,\n",
|
| 276 |
+
" {\n",
|
| 277 |
+
" \"error\": \"Semantic equivalence check FAILED.\",\n",
|
| 278 |
+
" \"reason\": \"semantic_violation\",\n",
|
| 279 |
+
" \"episode_stats\": self._episode_summary(),\n",
|
| 280 |
+
" },\n",
|
| 281 |
+
" )\n",
|
| 282 |
+
"\n",
|
| 283 |
+
" # ── State representation ──────────────────────────────────────────────────\n",
|
| 284 |
+
" @staticmethod\n",
|
| 285 |
+
" def _program_to_pseudoasm(program: List[Dict]) -> str:\n",
|
| 286 |
+
" if not program:\n",
|
| 287 |
+
" return \"; (empty program)\"\n",
|
| 288 |
+
" lines = []\n",
|
| 289 |
+
" for i, instr in enumerate(program):\n",
|
| 290 |
+
" op = instr.get(\"op\", \"NOP\")\n",
|
| 291 |
+
" args = instr.get(\"args\", [])\n",
|
| 292 |
+
" dest = instr.get(\"dest\")\n",
|
| 293 |
+
" typ = instr.get(\"type\", \"\")\n",
|
| 294 |
+
" arg_str = \", \".join(str(a) for a in args)\n",
|
| 295 |
+
" type_hint = f\":{typ}\" if typ else \"\"\n",
|
| 296 |
+
" if dest:\n",
|
| 297 |
+
" lines.append(f\" {i:>3}: {dest}{type_hint} = {op} {arg_str}\")\n",
|
| 298 |
+
" else:\n",
|
| 299 |
+
" lines.append(f\" {i:>3}: {op} {arg_str}\")\n",
|
| 300 |
+
" return \"\\n\".join(lines)\n",
|
| 301 |
+
"\n",
|
| 302 |
+
" # ── Utilities ─────────────────────────────────────────────────────────────\n",
|
| 303 |
+
" def _safe_count_cycles(self, program: List[Dict]) -> int:\n",
|
| 304 |
+
" return max(0, int(self.engine.execute_and_count_cycles(program)))\n",
|
| 305 |
+
"\n",
|
| 306 |
+
" def _episode_summary(self) -> Dict:\n",
|
| 307 |
+
" s = self._stats\n",
|
| 308 |
+
" return {\n",
|
| 309 |
+
" \"steps\": s.steps_taken,\n",
|
| 310 |
+
" \"total_reward\": round(s.total_reward, 3),\n",
|
| 311 |
+
" \"passes_applied\": s.passes_applied,\n",
|
| 312 |
+
" \"invalid_actions\": s.invalid_actions,\n",
|
| 313 |
+
" \"no_ops\": s.no_ops,\n",
|
| 314 |
+
" \"baseline_cycles\": s.baseline_cycles,\n",
|
| 315 |
+
" \"final_cycles\": s.final_cycles,\n",
|
| 316 |
+
" \"total_improvement_pct\": round(s.total_improvement_pct, 3),\n",
|
| 317 |
+
" }\n",
|
| 318 |
+
"\n",
|
| 319 |
+
" def available_actions(self) -> List[str]:\n",
|
| 320 |
+
" return sorted(self._valid_actions)\n",
|
| 321 |
+
"\n",
|
| 322 |
+
"\n",
|
| 323 |
+
"print(\"✅ CompilerOptimizationEnv defined\")"
|
| 324 |
+
]
|
| 325 |
+
},
|
| 326 |
+
{
|
| 327 |
+
"cell_type": "markdown",
|
| 328 |
+
"id": "smoke-header",
|
| 329 |
+
"metadata": {},
|
| 330 |
+
"source": [
|
| 331 |
+
"## 🧪 Cell 3 — Smoke Test (No GPU / Real Engine Needed)\n",
|
| 332 |
+
"Validates the entire reward pipeline with a mock engine. Run this before spending compute."
|
| 333 |
+
]
|
| 334 |
+
},
|
| 335 |
+
{
|
| 336 |
+
"cell_type": "code",
|
| 337 |
+
"execution_count": null,
|
| 338 |
+
"id": "smoke-test-cell",
|
| 339 |
+
"metadata": {},
|
| 340 |
+
"outputs": [],
|
| 341 |
+
"source": [
|
| 342 |
+
"class MockEngine:\n",
|
| 343 |
+
" \"\"\"Stub engine: cycles = instruction count, all programs semantically valid.\"\"\"\n",
|
| 344 |
+
" def execute_and_count_cycles(self, program):\n",
|
| 345 |
+
" return len(program)\n",
|
| 346 |
+
"\n",
|
| 347 |
+
" def verify_equivalence(self, original, candidate):\n",
|
| 348 |
+
" return True\n",
|
| 349 |
+
"\n",
|
| 350 |
+
"\n",
|
| 351 |
+
"MOCK_PASSES = {\n",
|
| 352 |
+
" \"constant_folding\": lambda p: p[:-1] if len(p) > 1 else p,\n",
|
| 353 |
+
" \"dead_code_elimination\": lambda p: p[:-1] if len(p) > 2 else p,\n",
|
| 354 |
+
" \"loop_unrolling\": lambda p: p, # intentional no-op for testing\n",
|
| 355 |
+
"}\n",
|
| 356 |
+
"\n",
|
| 357 |
+
"SAMPLE_PROGRAM = [\n",
|
| 358 |
+
" {\"op\": \"const\", \"dest\": \"x\", \"args\": [\"5\"], \"type\": \"int\"},\n",
|
| 359 |
+
" {\"op\": \"const\", \"dest\": \"y\", \"args\": [\"3\"], \"type\": \"int\"},\n",
|
| 360 |
+
" {\"op\": \"add\", \"dest\": \"z\", \"args\": [\"x\", \"y\"], \"type\": \"int\"},\n",
|
| 361 |
+
" {\"op\": \"mul\", \"dest\": \"w\", \"args\": [\"z\", \"x\"], \"type\": \"int\"},\n",
|
| 362 |
+
" {\"op\": \"ret\", \"args\": [\"w\"]},\n",
|
| 363 |
+
"]\n",
|
| 364 |
+
"\n",
|
| 365 |
+
"engine = MockEngine()\n",
|
| 366 |
+
"env = CompilerOptimizationEnv(engine, MOCK_PASSES, max_steps=6)\n",
|
| 367 |
+
"obs = env.reset(SAMPLE_PROGRAM)\n",
|
| 368 |
+
"\n",
|
| 369 |
+
"print(\"═\" * 55)\n",
|
| 370 |
+
"print(\"SMOKE TEST\")\n",
|
| 371 |
+
"print(\"═\" * 55)\n",
|
| 372 |
+
"print(f\"Initial state:\\n{obs}\")\n",
|
| 373 |
+
"print(f\"\\nBaseline cycles : {env.previous_cycles}\")\n",
|
| 374 |
+
"print(f\"Available actions: {env.available_actions()}\")\n",
|
| 375 |
+
"print()\n",
|
| 376 |
+
"\n",
|
| 377 |
+
"actions_to_try = [\n",
|
| 378 |
+
" \"constant_folding\",\n",
|
| 379 |
+
" \"dead_code_elimination\",\n",
|
| 380 |
+
" \"loop_unrolling\", # no-op\n",
|
| 381 |
+
" \"hallucinated_pass\", # invalid — but won't kill episode yet\n",
|
| 382 |
+
" \"constant_folding\",\n",
|
| 383 |
+
" \"dead_code_elimination\",\n",
|
| 384 |
+
"]\n",
|
| 385 |
+
"\n",
|
| 386 |
+
"for action in actions_to_try:\n",
|
| 387 |
+
" result = env.step(action)\n",
|
| 388 |
+
" tag = \"✗\" if result.reward < 0 else \"✓\"\n",
|
| 389 |
+
" print(f\"{tag} '{action}'\")\n",
|
| 390 |
+
" print(f\" reward={result.reward:+.2f} done={result.done}\")\n",
|
| 391 |
+
" relevant = {k: v for k, v in result.info.items()\n",
|
| 392 |
+
" if k in (\"delta_pct\", \"error\", \"no_op\", \"reason\",\n",
|
| 393 |
+
" \"terminal_bonus\", \"episode_stats\")}\n",
|
| 394 |
+
" if relevant:\n",
|
| 395 |
+
" print(f\" info: {relevant}\")\n",
|
| 396 |
+
" print()\n",
|
| 397 |
+
" if result.done:\n",
|
| 398 |
+
" break\n",
|
| 399 |
+
"\n",
|
| 400 |
+
"print(\"✅ Smoke test passed\")"
|
| 401 |
+
]
|
| 402 |
+
},
|
| 403 |
+
{
|
| 404 |
+
"cell_type": "markdown",
|
| 405 |
+
"id": "reward-plot-header",
|
| 406 |
+
"metadata": {},
|
| 407 |
+
"source": [
|
| 408 |
+
"## 📊 Cell 4 — Visualise Reward Across a Mock Episode"
|
| 409 |
+
]
|
| 410 |
+
},
|
| 411 |
+
{
|
| 412 |
+
"cell_type": "code",
|
| 413 |
+
"execution_count": null,
|
| 414 |
+
"id": "reward-plot-cell",
|
| 415 |
+
"metadata": {},
|
| 416 |
+
"outputs": [],
|
| 417 |
+
"source": [
|
| 418 |
+
"import matplotlib.pyplot as plt\n",
|
| 419 |
+
"import matplotlib.ticker as ticker\n",
|
| 420 |
+
"\n",
|
| 421 |
+
"# Run a full episode and collect data\n",
|
| 422 |
+
"env = CompilerOptimizationEnv(MockEngine(), MOCK_PASSES, max_steps=10)\n",
|
| 423 |
+
"env.reset(SAMPLE_PROGRAM)\n",
|
| 424 |
+
"\n",
|
| 425 |
+
"sequence = [\n",
|
| 426 |
+
" \"constant_folding\", \"dead_code_elimination\", \"loop_unrolling\",\n",
|
| 427 |
+
" \"constant_folding\", \"dead_code_elimination\", \"loop_unrolling\",\n",
|
| 428 |
+
" \"dead_code_elimination\", \"loop_unrolling\", \"constant_folding\",\n",
|
| 429 |
+
" \"dead_code_elimination\",\n",
|
| 430 |
+
"]\n",
|
| 431 |
+
"\n",
|
| 432 |
+
"rewards, cycle_counts, actions_log = [], [], []\n",
|
| 433 |
+
"for act in sequence:\n",
|
| 434 |
+
" r = env.step(act)\n",
|
| 435 |
+
" rewards.append(r.reward)\n",
|
| 436 |
+
" cycle_counts.append(env.previous_cycles)\n",
|
| 437 |
+
" actions_log.append(act)\n",
|
| 438 |
+
" if r.done:\n",
|
| 439 |
+
" break\n",
|
| 440 |
+
"\n",
|
| 441 |
+
"steps = list(range(1, len(rewards) + 1))\n",
|
| 442 |
+
"\n",
|
| 443 |
+
"fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 6), sharex=True)\n",
|
| 444 |
+
"fig.suptitle(\"Compiler Optimization Episode — Mock Engine\", fontsize=14, fontweight=\"bold\")\n",
|
| 445 |
+
"\n",
|
| 446 |
+
"# Reward per step\n",
|
| 447 |
+
"colors = [\"#2ecc71\" if r >= 0 else \"#e74c3c\" for r in rewards]\n",
|
| 448 |
+
"ax1.bar(steps, rewards, color=colors, edgecolor=\"white\", linewidth=0.5)\n",
|
| 449 |
+
"ax1.axhline(0, color=\"grey\", linewidth=0.8, linestyle=\"--\")\n",
|
| 450 |
+
"ax1.set_ylabel(\"Reward\")\n",
|
| 451 |
+
"ax1.set_title(\"Reward per Step (green = positive, red = negative)\")\n",
|
| 452 |
+
"ax1.yaxis.set_major_formatter(ticker.FormatStrFormatter(\"%.1f\"))\n",
|
| 453 |
+
"\n",
|
| 454 |
+
"# Cycle count over time\n",
|
| 455 |
+
"ax2.plot(steps, cycle_counts, marker=\"o\", color=\"#3498db\", linewidth=2, markersize=6)\n",
|
| 456 |
+
"ax2.set_xlabel(\"Step\")\n",
|
| 457 |
+
"ax2.set_ylabel(\"CPU Cycles\")\n",
|
| 458 |
+
"ax2.set_title(\"CPU Cycle Count Over Episode (lower = better)\")\n",
|
| 459 |
+
"ax2.set_xticks(steps)\n",
|
| 460 |
+
"ax2.set_xticklabels(\n",
|
| 461 |
+
" [a.replace(\"_\", \"\\n\") for a in actions_log],\n",
|
| 462 |
+
" fontsize=7,\n",
|
| 463 |
+
")\n",
|
| 464 |
+
"\n",
|
| 465 |
+
"plt.tight_layout()\n",
|
| 466 |
+
"plt.savefig(\"episode_reward_curve.png\", dpi=150, bbox_inches=\"tight\")\n",
|
| 467 |
+
"plt.show()\n",
|
| 468 |
+
"print(\"📈 Plot saved as episode_reward_curve.png\")"
|
| 469 |
+
]
|
| 470 |
+
},
|
| 471 |
+
{
|
| 472 |
+
"cell_type": "markdown",
|
| 473 |
+
"id": "model-header",
|
| 474 |
+
"metadata": {},
|
| 475 |
+
"source": [
|
| 476 |
+
"## 🤖 Cell 5 — Load Model with Unsloth (QLoRA 4-bit)\n",
|
| 477 |
+
"> **Requires T4 GPU.** Skip to Cell 9 if you only want to test the environment."
|
| 478 |
+
]
|
| 479 |
+
},
|
| 480 |
+
{
|
| 481 |
+
"cell_type": "code",
|
| 482 |
+
"execution_count": null,
|
| 483 |
+
"id": "model-cell",
|
| 484 |
+
"metadata": {},
|
| 485 |
+
"outputs": [],
|
| 486 |
+
"source": [
|
| 487 |
+
"import torch\n",
|
| 488 |
+
"from unsloth import FastLanguageModel\n",
|
| 489 |
+
"\n",
|
| 490 |
+
"MODEL_NAME = \"unsloth/Qwen2.5-3B-Instruct\" # swap to 7B if VRAM allows\n",
|
| 491 |
+
"MAX_SEQ_LEN = 1024\n",
|
| 492 |
+
"LORA_RANK = 16\n",
|
| 493 |
+
"\n",
|
| 494 |
+
"model, tokenizer = FastLanguageModel.from_pretrained(\n",
|
| 495 |
+
" model_name = MODEL_NAME,\n",
|
| 496 |
+
" max_seq_length = MAX_SEQ_LEN,\n",
|
| 497 |
+
" dtype = None, # auto bf16/fp16\n",
|
| 498 |
+
" load_in_4bit = True,\n",
|
| 499 |
+
")\n",
|
| 500 |
+
"\n",
|
| 501 |
+
"model = FastLanguageModel.get_peft_model(\n",
|
| 502 |
+
" model,\n",
|
| 503 |
+
" r = LORA_RANK,\n",
|
| 504 |
+
" target_modules = [\"q_proj\", \"k_proj\", \"v_proj\", \"o_proj\",\n",
|
| 505 |
+
" \"gate_proj\", \"up_proj\", \"down_proj\"],\n",
|
| 506 |
+
" lora_alpha = LORA_RANK * 2,\n",
|
| 507 |
+
" lora_dropout = 0.0,\n",
|
| 508 |
+
" bias = \"none\",\n",
|
| 509 |
+
" use_gradient_checkpointing = \"unsloth\",\n",
|
| 510 |
+
" random_state = 42,\n",
|
| 511 |
+
")\n",
|
| 512 |
+
"\n",
|
| 513 |
+
"print(f\"✅ Loaded {MODEL_NAME} with QLoRA rank={LORA_RANK}\")\n",
|
| 514 |
+
"print(f\" GPU memory allocated: {torch.cuda.memory_allocated() / 1e9:.2f} GB\")"
|
| 515 |
+
]
|
| 516 |
+
},
|
| 517 |
+
{
|
| 518 |
+
"cell_type": "markdown",
|
| 519 |
+
"id": "prompt-header",
|
| 520 |
+
"metadata": {},
|
| 521 |
+
"source": [
|
| 522 |
+
"## 💬 Cell 6 — System Prompt & Dataset Builder"
|
| 523 |
+
]
|
| 524 |
+
},
|
| 525 |
+
{
|
| 526 |
+
"cell_type": "code",
|
| 527 |
+
"execution_count": null,
|
| 528 |
+
"id": "prompt-cell",
|
| 529 |
+
"metadata": {},
|
| 530 |
+
"outputs": [],
|
| 531 |
+
"source": [
|
| 532 |
+
"import json\n",
|
| 533 |
+
"from datasets import Dataset\n",
|
| 534 |
+
"\n",
|
| 535 |
+
"\n",
|
| 536 |
+
"def build_system_prompt(env: CompilerOptimizationEnv) -> str:\n",
|
| 537 |
+
" actions = \"\\n\".join(f\" - {a}\" for a in env.available_actions())\n",
|
| 538 |
+
" return (\n",
|
| 539 |
+
" \"You are a compiler optimization agent. Your goal is to reduce \"\n",
|
| 540 |
+
" \"CPU cycle count by applying optimization passes to the program below.\\n\\n\"\n",
|
| 541 |
+
" f\"Available actions (respond with EXACTLY one per turn):\\n{actions}\\n\"\n",
|
| 542 |
+
" \" - done (stop early if no further improvement is possible)\\n\\n\"\n",
|
| 543 |
+
" \"Rules:\\n\"\n",
|
| 544 |
+
" \" • Output only the action name. No explanation, no markdown.\\n\"\n",
|
| 545 |
+
" \" • Do not invent actions not listed above.\\n\"\n",
|
| 546 |
+
" \" • Repeating a pass that does nothing wastes a step.\\n\"\n",
|
| 547 |
+
" )\n",
|
| 548 |
+
"\n",
|
| 549 |
+
"\n",
|
| 550 |
+
"def build_dataset(\n",
|
| 551 |
+
" programs: list,\n",
|
| 552 |
+
" engine,\n",
|
| 553 |
+
" passes: dict,\n",
|
| 554 |
+
") -> Dataset:\n",
|
| 555 |
+
" \"\"\"\n",
|
| 556 |
+
" Each row = one episode's initial state.\n",
|
| 557 |
+
" GRPO samples K completions (actions) per row to estimate group-relative advantage.\n",
|
| 558 |
+
" \"\"\"\n",
|
| 559 |
+
" env = CompilerOptimizationEnv(engine, passes, max_steps=10)\n",
|
| 560 |
+
" system_prompt = build_system_prompt(env)\n",
|
| 561 |
+
"\n",
|
| 562 |
+
" rows = []\n",
|
| 563 |
+
" for prog in programs:\n",
|
| 564 |
+
" obs = env.reset(prog)\n",
|
| 565 |
+
" prompt = [\n",
|
| 566 |
+
" {\"role\": \"system\", \"content\": system_prompt},\n",
|
| 567 |
+
" {\"role\": \"user\", \"content\": f\"Current program:\\n{obs}\\n\\nChoose an action:\"},\n",
|
| 568 |
+
" ]\n",
|
| 569 |
+
" rows.append({\"prompt\": prompt, \"program_json\": json.dumps(prog)})\n",
|
| 570 |
+
"\n",
|
| 571 |
+
" return Dataset.from_list(rows)\n",
|
| 572 |
+
"\n",
|
| 573 |
+
"\n",
|
| 574 |
+
"# ── Demo: build dataset from mock programs ────────────────────────────────────\n",
|
| 575 |
+
"def make_mock_programs(n: int = 20) -> list:\n",
|
| 576 |
+
" \"\"\"Generate N random mock IR programs for demo purposes.\"\"\"\n",
|
| 577 |
+
" import random\n",
|
| 578 |
+
" ops = [\"add\", \"mul\", \"sub\", \"const\", \"load\"]\n",
|
| 579 |
+
" progs = []\n",
|
| 580 |
+
" for _ in range(n):\n",
|
| 581 |
+
" length = random.randint(4, 12)\n",
|
| 582 |
+
" prog = [\n",
|
| 583 |
+
" {\"op\": random.choice(ops),\n",
|
| 584 |
+
" \"dest\": f\"v{i}\",\n",
|
| 585 |
+
" \"args\": [f\"v{max(0,i-1)}\"],\n",
|
| 586 |
+
" \"type\": \"int\"}\n",
|
| 587 |
+
" for i in range(length)\n",
|
| 588 |
+
" ]\n",
|
| 589 |
+
" prog.append({\"op\": \"ret\", \"args\": [f\"v{length-1}\"]})\n",
|
| 590 |
+
" progs.append(prog)\n",
|
| 591 |
+
" return progs\n",
|
| 592 |
+
"\n",
|
| 593 |
+
"\n",
|
| 594 |
+
"mock_programs = make_mock_programs(n=30)\n",
|
| 595 |
+
"train_dataset = build_dataset(mock_programs, MockEngine(), MOCK_PASSES)\n",
|
| 596 |
+
"\n",
|
| 597 |
+
"print(f\"✅ Dataset built: {len(train_dataset)} episodes\")\n",
|
| 598 |
+
"print(f\" Sample prompt keys: {list(train_dataset[0].keys())}\")"
|
| 599 |
+
]
|
| 600 |
+
},
|
| 601 |
+
{
|
| 602 |
+
"cell_type": "markdown",
|
| 603 |
+
"id": "reward-fn-header",
|
| 604 |
+
"metadata": {},
|
| 605 |
+
"source": [
|
| 606 |
+
"## 🎯 Cell 7 — Reward Function Factory (for GRPO)"
|
| 607 |
+
]
|
| 608 |
+
},
|
| 609 |
+
{
|
| 610 |
+
"cell_type": "code",
|
| 611 |
+
"execution_count": null,
|
| 612 |
+
"id": "reward-fn-cell",
|
| 613 |
+
"metadata": {},
|
| 614 |
+
"outputs": [],
|
| 615 |
+
"source": [
|
| 616 |
+
"import re\n",
|
| 617 |
+
"from typing import Any\n",
|
| 618 |
+
"\n",
|
| 619 |
+
"\n",
|
| 620 |
+
"def make_reward_fn(engine, passes, max_steps=10):\n",
|
| 621 |
+
" \"\"\"\n",
|
| 622 |
+
" Returns a reward function compatible with TRL's GRPOTrainer.\n",
|
| 623 |
+
" Each GRPO rollout gets its own fresh env instance — no state leakage.\n",
|
| 624 |
+
"\n",
|
| 625 |
+
" Action parsing strips punctuation the model might add\n",
|
| 626 |
+
" (e.g. 'constant_folding.' → 'constant_folding').\n",
|
| 627 |
+
" \"\"\"\n",
|
| 628 |
+
" def reward_fn(prompts, completions, **kwargs):\n",
|
| 629 |
+
" programs = kwargs.get(\"program_json\", [None] * len(completions))\n",
|
| 630 |
+
" rewards = []\n",
|
| 631 |
+
"\n",
|
| 632 |
+
" for completion, prog_json in zip(completions, programs):\n",
|
| 633 |
+
" # Parse action from model output\n",
|
| 634 |
+
" raw = completion if isinstance(completion, str) else completion[0][\"content\"]\n",
|
| 635 |
+
" action = raw.strip().lower().split()[0] if raw.strip() else \"__invalid__\"\n",
|
| 636 |
+
" action = re.sub(r\"[^a-z0-9_]\", \"\", action) # strip punctuation\n",
|
| 637 |
+
"\n",
|
| 638 |
+
" # Fresh env per rollout\n",
|
| 639 |
+
" env = CompilerOptimizationEnv(\n",
|
| 640 |
+
" role1_engine = engine,\n",
|
| 641 |
+
" role3_passes = passes,\n",
|
| 642 |
+
" max_steps = max_steps,\n",
|
| 643 |
+
" )\n",
|
| 644 |
+
" program = json.loads(prog_json) if prog_json else []\n",
|
| 645 |
+
" env.reset(program)\n",
|
| 646 |
+
"\n",
|
| 647 |
+
" if action == \"done\":\n",
|
| 648 |
+
" rewards.append(0.0) # neutral stop\n",
|
| 649 |
+
" else:\n",
|
| 650 |
+
" result = env.step(action)\n",
|
| 651 |
+
" rewards.append(result.reward)\n",
|
| 652 |
+
"\n",
|
| 653 |
+
" return rewards\n",
|
| 654 |
+
"\n",
|
| 655 |
+
" return reward_fn\n",
|
| 656 |
+
"\n",
|
| 657 |
+
"\n",
|
| 658 |
+
"reward_fn = make_reward_fn(MockEngine(), MOCK_PASSES, max_steps=10)\n",
|
| 659 |
+
"print(\"✅ Reward function factory ready\")\n",
|
| 660 |
+
"\n",
|
| 661 |
+
"# Quick sanity check\n",
|
| 662 |
+
"test_completions = [\"constant_folding\", \"hallucinated_pass\", \"loop_unrolling\"]\n",
|
| 663 |
+
"test_programs = [json.dumps(SAMPLE_PROGRAM)] * 3\n",
|
| 664 |
+
"test_rewards = reward_fn(\n",
|
| 665 |
+
" prompts = [\"\"] * 3,\n",
|
| 666 |
+
" completions = test_completions,\n",
|
| 667 |
+
" program_json = test_programs,\n",
|
| 668 |
+
")\n",
|
| 669 |
+
"print(\"\\nReward sanity check:\")\n",
|
| 670 |
+
"for act, rew in zip(test_completions, test_rewards):\n",
|
| 671 |
+
" print(f\" '{act}' → {rew:+.2f}\")"
|
| 672 |
+
]
|
| 673 |
+
},
|
| 674 |
+
{
|
| 675 |
+
"cell_type": "markdown",
|
| 676 |
+
"id": "trainer-header",
|
| 677 |
+
"metadata": {},
|
| 678 |
+
"source": [
|
| 679 |
+
"## 🚀 Cell 8 — GRPO Trainer Config & Training"
|
| 680 |
+
]
|
| 681 |
+
},
|
| 682 |
+
{
|
| 683 |
+
"cell_type": "code",
|
| 684 |
+
"execution_count": null,
|
| 685 |
+
"id": "trainer-cell",
|
| 686 |
+
"metadata": {},
|
| 687 |
+
"outputs": [],
|
| 688 |
+
"source": [
|
| 689 |
+
"from trl import GRPOConfig, GRPOTrainer\n",
|
| 690 |
+
"\n",
|
| 691 |
+
"# Optional W&B — comment out if not using\n",
|
| 692 |
+
"try:\n",
|
| 693 |
+
" import wandb\n",
|
| 694 |
+
" wandb.init(project=\"openenv-compiler-opt\", name=\"grpo-qwen2.5-3b\")\n",
|
| 695 |
+
" REPORT_TO = \"wandb\"\n",
|
| 696 |
+
"except Exception:\n",
|
| 697 |
+
" REPORT_TO = \"none\"\n",
|
| 698 |
+
"\n",
|
| 699 |
+
"\n",
|
| 700 |
+
"grpo_config = GRPOConfig(\n",
|
| 701 |
+
" # ── Generation ────────────────────────────────────────────────────────\n",
|
| 702 |
+
" num_generations = 4, # K rollouts per prompt for group-relative advantage\n",
|
| 703 |
+
" max_new_tokens = 16, # Actions are 1 word; don't waste context\n",
|
| 704 |
+
" temperature = 0.9,\n",
|
| 705 |
+
" top_p = 0.95,\n",
|
| 706 |
+
"\n",
|
| 707 |
+
" # ── Optimisation ──────────────────────────────────────────────────────\n",
|
| 708 |
+
" learning_rate = 5e-6,\n",
|
| 709 |
+
" per_device_train_batch_size = 2,\n",
|
| 710 |
+
" gradient_accumulation_steps = 4, # effective batch = 8\n",
|
| 711 |
+
" num_train_epochs = 3,\n",
|
| 712 |
+
" max_grad_norm = 0.5,\n",
|
| 713 |
+
"\n",
|
| 714 |
+
" # ── GRPO-specific ─────────────────────────────────────────────────────\n",
|
| 715 |
+
" beta = 0.04, # KL penalty; keeps policy near reference\n",
|
| 716 |
+
"\n",
|
| 717 |
+
" # ── Logging / checkpointing ───────────────────────────────────────────\n",
|
| 718 |
+
" output_dir = \"./grpo_compiler_checkpoints\",\n",
|
| 719 |
+
" logging_steps = 10,\n",
|
| 720 |
+
" save_steps = 100,\n",
|
| 721 |
+
" report_to = REPORT_TO,\n",
|
| 722 |
+
"\n",
|
| 723 |
+
" # ── Reproducibility ───────────────────────────────────────────────────\n",
|
| 724 |
+
" seed = 42,\n",
|
| 725 |
+
")\n",
|
| 726 |
+
"\n",
|
| 727 |
+
"trainer = GRPOTrainer(\n",
|
| 728 |
+
" model = model,\n",
|
| 729 |
+
" tokenizer = tokenizer,\n",
|
| 730 |
+
" config = grpo_config,\n",
|
| 731 |
+
" train_dataset = train_dataset,\n",
|
| 732 |
+
" reward_funcs = reward_fn,\n",
|
| 733 |
+
")\n",
|
| 734 |
+
"\n",
|
| 735 |
+
"print(\"✅ Trainer configured\")\n",
|
| 736 |
+
"print(f\" num_generations (K) = {grpo_config.num_generations}\")\n",
|
| 737 |
+
"print(f\" effective batch size = \"\n",
|
| 738 |
+
" f\"{grpo_config.per_device_train_batch_size * grpo_config.gradient_accumulation_steps}\")\n",
|
| 739 |
+
"print(f\" KL beta = {grpo_config.beta}\")\n",
|
| 740 |
+
"print()\n",
|
| 741 |
+
"print(\"Starting training... (this will take a while on T4)\")\n",
|
| 742 |
+
"trainer.train()"
|
| 743 |
+
]
|
| 744 |
+
},
|
| 745 |
+
{
|
| 746 |
+
"cell_type": "markdown",
|
| 747 |
+
"id": "save-header",
|
| 748 |
+
"metadata": {},
|
| 749 |
+
"source": [
|
| 750 |
+
"## 💾 Cell 9 — Save Model"
|
| 751 |
+
]
|
| 752 |
+
},
|
| 753 |
+
{
|
| 754 |
+
"cell_type": "code",
|
| 755 |
+
"execution_count": null,
|
| 756 |
+
"id": "save-cell",
|
| 757 |
+
"metadata": {},
|
| 758 |
+
"outputs": [],
|
| 759 |
+
"source": [
|
| 760 |
+
"SAVE_PATH = \"./grpo_compiler_final\"\n",
|
| 761 |
+
"\n",
|
| 762 |
+
"model.save_pretrained(SAVE_PATH)\n",
|
| 763 |
+
"tokenizer.save_pretrained(SAVE_PATH)\n",
|
| 764 |
+
"\n",
|
| 765 |
+
"print(f\"✅ Model saved to {SAVE_PATH}\")\n",
|
| 766 |
+
"\n",
|
| 767 |
+
"# Optional: push to HuggingFace Hub\n",
|
| 768 |
+
"# model.push_to_hub(\"your-hf-username/compiler-opt-grpo\")\n",
|
| 769 |
+
"# tokenizer.push_to_hub(\"your-hf-username/compiler-opt-grpo\")"
|
| 770 |
+
]
|
| 771 |
+
},
|
| 772 |
+
{
|
| 773 |
+
"cell_type": "markdown",
|
| 774 |
+
"id": "curriculum-header",
|
| 775 |
+
"metadata": {},
|
| 776 |
+
"source": [
|
| 777 |
+
"## 📈 Cell 10 — Curriculum Callback & Reward Tracking"
|
| 778 |
+
]
|
| 779 |
+
},
|
| 780 |
+
{
|
| 781 |
+
"cell_type": "code",
|
| 782 |
+
"execution_count": null,
|
| 783 |
+
"id": "curriculum-cell",
|
| 784 |
+
"metadata": {},
|
| 785 |
+
"outputs": [],
|
| 786 |
+
"source": [
|
| 787 |
+
"class CurriculumCallback:\n",
|
| 788 |
+
" \"\"\"\n",
|
| 789 |
+
" Tracks rolling mean reward and promotes curriculum level\n",
|
| 790 |
+
" when the agent has mastered the current difficulty.\n",
|
| 791 |
+
"\n",
|
| 792 |
+
" Usage: call .record(reward) after every episode.\n",
|
| 793 |
+
" Read .level to get current difficulty (1=easy, 2=medium, 3=hard).\n",
|
| 794 |
+
" \"\"\"\n",
|
| 795 |
+
" def __init__(self, reward_threshold: float = 5.0, window: int = 50):\n",
|
| 796 |
+
" self.threshold = reward_threshold\n",
|
| 797 |
+
" self.window = window\n",
|
| 798 |
+
" self._history = []\n",
|
| 799 |
+
" self.level = 1\n",
|
| 800 |
+
" self._promotions = []\n",
|
| 801 |
+
"\n",
|
| 802 |
+
" def record(self, reward: float, step: int = None):\n",
|
| 803 |
+
" self._history.append(reward)\n",
|
| 804 |
+
" if len(self._history) >= self.window:\n",
|
| 805 |
+
" mean = sum(self._history[-self.window:]) / self.window\n",
|
| 806 |
+
" if mean >= self.threshold and self.level < 3:\n",
|
| 807 |
+
" self.level += 1\n",
|
| 808 |
+
" self._promotions.append((step or len(self._history), self.level))\n",
|
| 809 |
+
" print(f\"[Curriculum] ▲ Promoted to level {self.level} \"\n",
|
| 810 |
+
" f\"(rolling mean={mean:.2f})\")\n",
|
| 811 |
+
"\n",
|
| 812 |
+
" def plot(self):\n",
|
| 813 |
+
" import matplotlib.pyplot as plt\n",
|
| 814 |
+
" import numpy as np\n",
|
| 815 |
+
"\n",
|
| 816 |
+
" history = self._history\n",
|
| 817 |
+
" steps = list(range(len(history)))\n",
|
| 818 |
+
" window = self.window\n",
|
| 819 |
+
" rolling = [\n",
|
| 820 |
+
" sum(history[max(0,i-window):i+1]) / min(i+1, window)\n",
|
| 821 |
+
" for i in steps\n",
|
| 822 |
+
" ]\n",
|
| 823 |
+
"\n",
|
| 824 |
+
" fig, ax = plt.subplots(figsize=(10, 4))\n",
|
| 825 |
+
" ax.plot(steps, history, alpha=0.3, color=\"#3498db\", label=\"Episode reward\")\n",
|
| 826 |
+
" ax.plot(steps, rolling, color=\"#e74c3c\", linewidth=2,\n",
|
| 827 |
+
" label=f\"Rolling mean (w={window})\")\n",
|
| 828 |
+
" ax.axhline(self.threshold, linestyle=\"--\", color=\"grey\",\n",
|
| 829 |
+
" linewidth=1, label=f\"Promotion threshold ({self.threshold})\")\n",
|
| 830 |
+
" for step, level in self._promotions:\n",
|
| 831 |
+
" ax.axvline(step, color=\"green\", linewidth=1.5, linestyle=\":\")\n",
|
| 832 |
+
" ax.text(step, ax.get_ylim()[1]*0.9, f\" L{level}\",\n",
|
| 833 |
+
" color=\"green\", fontsize=9)\n",
|
| 834 |
+
" ax.set_xlabel(\"Episode\")\n",
|
| 835 |
+
" ax.set_ylabel(\"Reward\")\n",
|
| 836 |
+
" ax.set_title(\"Training Reward + Curriculum Progression\")\n",
|
| 837 |
+
" ax.legend()\n",
|
| 838 |
+
" plt.tight_layout()\n",
|
| 839 |
+
" plt.savefig(\"curriculum_reward_curve.png\", dpi=150)\n",
|
| 840 |
+
" plt.show()\n",
|
| 841 |
+
" print(\"📈 Saved curriculum_reward_curve.png\")\n",
|
| 842 |
+
"\n",
|
| 843 |
+
"\n",
|
| 844 |
+
"# ── Demo: simulate 200 episodes of improving reward ──────────────────────────\n",
|
| 845 |
+
"import random\n",
|
| 846 |
+
"cb = CurriculumCallback(reward_threshold=5.0, window=50)\n",
|
| 847 |
+
"for ep in range(200):\n",
|
| 848 |
+
" # Simulate reward slowly improving\n",
|
| 849 |
+
" synthetic_reward = -5 + ep * 0.08 + random.gauss(0, 2)\n",
|
| 850 |
+
" cb.record(synthetic_reward, step=ep)\n",
|
| 851 |
+
"\n",
|
| 852 |
+
"cb.plot()"
|
| 853 |
+
]
|
| 854 |
+
},
|
| 855 |
+
{
|
| 856 |
+
"cell_type": "markdown",
|
| 857 |
+
"id": "inference-header",
|
| 858 |
+
"metadata": {},
|
| 859 |
+
"source": [
|
| 860 |
+
"## 🔍 Cell 11 — Inference: Before vs After Training"
|
| 861 |
+
]
|
| 862 |
+
},
|
| 863 |
+
{
|
| 864 |
+
"cell_type": "code",
|
| 865 |
+
"execution_count": null,
|
| 866 |
+
"id": "inference-cell",
|
| 867 |
+
"metadata": {},
|
| 868 |
+
"outputs": [],
|
| 869 |
+
"source": [
|
| 870 |
+
"def run_inference_episode(model, tokenizer, engine, passes, program, max_steps=10):\n",
|
| 871 |
+
" \"\"\"Run a greedy inference episode and return the action sequence + total improvement.\"\"\"\n",
|
| 872 |
+
" from transformers import TextStreamer\n",
|
| 873 |
+
" FastLanguageModel.for_inference(model)\n",
|
| 874 |
+
"\n",
|
| 875 |
+
" env = CompilerOptimizationEnv(engine, passes, max_steps=max_steps)\n",
|
| 876 |
+
" obs = env.reset(program)\n",
|
| 877 |
+
" system_prompt = build_system_prompt(env)\n",
|
| 878 |
+
"\n",
|
| 879 |
+
" actions_chosen, rewards_earned = [], []\n",
|
| 880 |
+
" done = False\n",
|
| 881 |
+
"\n",
|
| 882 |
+
" print(f\"\\nBaseline cycles: {env.previous_cycles}\")\n",
|
| 883 |
+
" print(f\"Initial state:\\n{obs}\\n\")\n",
|
| 884 |
+
"\n",
|
| 885 |
+
" while not done:\n",
|
| 886 |
+
" messages = [\n",
|
| 887 |
+
" {\"role\": \"system\", \"content\": system_prompt},\n",
|
| 888 |
+
" {\"role\": \"user\", \"content\": f\"Current program:\\n{obs}\\n\\nChoose an action:\"},\n",
|
| 889 |
+
" ]\n",
|
| 890 |
+
" inputs = tokenizer.apply_chat_template(\n",
|
| 891 |
+
" messages,\n",
|
| 892 |
+
" tokenize=True,\n",
|
| 893 |
+
" add_generation_prompt=True,\n",
|
| 894 |
+
" return_tensors=\"pt\",\n",
|
| 895 |
+
" ).to(model.device)\n",
|
| 896 |
+
"\n",
|
| 897 |
+
" with torch.no_grad():\n",
|
| 898 |
+
" outputs = model.generate(\n",
|
| 899 |
+
" input_ids = inputs,\n",
|
| 900 |
+
" max_new_tokens = 16,\n",
|
| 901 |
+
" temperature = 0.1, # greedy-ish for inference\n",
|
| 902 |
+
" do_sample = True,\n",
|
| 903 |
+
" )\n",
|
| 904 |
+
"\n",
|
| 905 |
+
" raw_action = tokenizer.decode(\n",
|
| 906 |
+
" outputs[0][inputs.shape[-1]:], skip_special_tokens=True\n",
|
| 907 |
+
" ).strip()\n",
|
| 908 |
+
" action = re.sub(r\"[^a-z0-9_]\", \"\", raw_action.lower().split()[0])\n",
|
| 909 |
+
"\n",
|
| 910 |
+
" result = env.step(action)\n",
|
| 911 |
+
" actions_chosen.append(action)\n",
|
| 912 |
+
" rewards_earned.append(result.reward)\n",
|
| 913 |
+
" obs = result.observation\n",
|
| 914 |
+
" done = result.done\n",
|
| 915 |
+
"\n",
|
| 916 |
+
" print(f\"Step {len(actions_chosen)}: '{action}' → reward={result.reward:+.2f}\")\n",
|
| 917 |
+
"\n",
|
| 918 |
+
" summary = env._episode_summary()\n",
|
| 919 |
+
" print(f\"\\n{'─'*40}\")\n",
|
| 920 |
+
" print(f\"Total improvement: {summary['total_improvement_pct']:.1f}%\")\n",
|
| 921 |
+
" print(f\"Final cycles: {summary['final_cycles']} (was {summary['baseline_cycles']})\")\n",
|
| 922 |
+
" return summary\n",
|
| 923 |
+
"\n",
|
| 924 |
+
"\n",
|
| 925 |
+
"# Uncomment after training:\n",
|
| 926 |
+
"# summary = run_inference_episode(\n",
|
| 927 |
+
"# model, tokenizer, MockEngine(), MOCK_PASSES, SAMPLE_PROGRAM\n",
|
| 928 |
+
"# )\n",
|
| 929 |
+
"\n",
|
| 930 |
+
"print(\"✅ Inference cell ready. Uncomment the last block after training to run.\")"
|
| 931 |
+
]
|
| 932 |
+
},
|
| 933 |
+
{
|
| 934 |
+
"cell_type": "markdown",
|
| 935 |
+
"id": "tips-header",
|
| 936 |
+
"metadata": {},
|
| 937 |
+
"source": [
|
| 938 |
+
"---\n",
|
| 939 |
+
"## 📝 Notes & Tips\n",
|
| 940 |
+
"\n",
|
| 941 |
+
"| What | Why it matters |\n",
|
| 942 |
+
"|------|----------------|\n",
|
| 943 |
+
"| `num_generations=4` | GRPO needs K≥2 rollouts per prompt to compute group-relative advantage. K=4 balances diversity vs. compute. |\n",
|
| 944 |
+
"| `beta=0.04` | KL penalty keeping policy close to reference. Too low → mode collapse. Too high → no learning. |\n",
|
| 945 |
+
"| `max_new_tokens=16` | Actions are one word. This prevents wasted computation and keeps the model from adding explanations. |\n",
|
| 946 |
+
"| Dynamic crash penalty | Computed as `−2 × (100 × max_steps)` so it always dominates the best possible episode reward without washing out all other reward signal. |\n",
|
| 947 |
+
"| Terminal bonus | Rewards cumulative improvement, not just greedy single-step gains. Critical for long-horizon tasks. |\n",
|
| 948 |
+
"| Soft invalid-action termination | 3 consecutive invalid actions → end. Single mistakes don't kill the episode; the agent can recover. |\n",
|
| 949 |
+
"| `deepcopy` on all pass inputs | Role 3's passes mutate dicts in-place. Without this, `original_program` gets corrupted and the verifier fails spuriously. |\n",
|
| 950 |
+
"\n",
|
| 951 |
+
"**Next steps:**\n",
|
| 952 |
+
"- Replace `MockEngine` with Role 1's real engine\n",
|
| 953 |
+
"- Replace `MOCK_PASSES` with Role 3's real passes\n",
|
| 954 |
+
"- Push environment to HuggingFace Spaces: `openenv init && openenv deploy`\n",
|
| 955 |
+
"- Add W&B sweep to tune `beta`, `learning_rate`, `num_generations`"
|
| 956 |
+
]
|
| 957 |
+
}
|
| 958 |
+
]
|
| 959 |
+
}"
|
space/space/space/space/space/space/space/space/space/space/space/space/space/space/role2_deliverable3_training_loop (2) (1) (1).ipynb
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/DockerFile
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Use a lightweight Python 3.10 image as the base
|
| 2 |
+
FROM python:3.10-slim
|
| 3 |
+
|
| 4 |
+
# Hugging Face Spaces requires a non-root user for security.
|
| 5 |
+
# We create a user named 'user' with user ID 1000.
|
| 6 |
+
RUN useradd -m -u 1000 user
|
| 7 |
+
|
| 8 |
+
# Set environment variables to ensure Python output is logged immediately
|
| 9 |
+
# and to add the local bin directory to the PATH for pip installations.
|
| 10 |
+
ENV PATH="/home/user/.local/bin:$PATH" \
|
| 11 |
+
PYTHONUNBUFFERED=1 \
|
| 12 |
+
PYTHONDONTWRITEBYTECODE=1
|
| 13 |
+
|
| 14 |
+
# Switch away from root to the new user
|
| 15 |
+
USER user
|
| 16 |
+
|
| 17 |
+
# Set the working directory inside the container
|
| 18 |
+
WORKDIR /home/user/app
|
| 19 |
+
|
| 20 |
+
# Copy the requirements file first to leverage Docker cache layers
|
| 21 |
+
COPY --chown=user requirements.txt .
|
| 22 |
+
|
| 23 |
+
# Upgrade pip and install the dependencies defined by Role 2
|
| 24 |
+
RUN pip install --no-cache-dir --upgrade pip && \
|
| 25 |
+
pip install --no-cache-dir -r requirements.txt
|
| 26 |
+
|
| 27 |
+
# Copy the rest of the application files (app.py, openenv.yaml, engine.py, etc.)
|
| 28 |
+
COPY --chown=user . .
|
| 29 |
+
|
| 30 |
+
# Expose port 7860, which is the default port Hugging Face routes traffic to
|
| 31 |
+
EXPOSE 7860
|
| 32 |
+
|
| 33 |
+
# Start the OpenEnv server using the entrypoint defined in app.py
|
| 34 |
+
CMD ["python", "app.py"]
|
space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/README.md
CHANGED
|
@@ -1 +1,9 @@
|
|
| 1 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Compiler Brain OpenEnv
|
| 3 |
+
emoji: 🧠
|
| 4 |
+
colorFrom: indigo
|
| 5 |
+
colorTo: purple
|
| 6 |
+
sdk: docker
|
| 7 |
+
app_file: app.py
|
| 8 |
+
pinned: false
|
| 9 |
+
---
|
space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/requirements.txt
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# OpenEnv SDK (Mandatory for the hackathon judging environment)
|
| 2 |
+
openenv
|
| 3 |
+
|
| 4 |
+
# Core RL Environment
|
| 5 |
+
gymnasium
|
| 6 |
+
|
| 7 |
+
# Hugging Face Training Stack (Versions matched to your D3 notebook)
|
| 8 |
+
trl==0.23.1
|
| 9 |
+
transformers==4.57.1
|
| 10 |
+
peft
|
| 11 |
+
accelerate
|
| 12 |
+
bitsandbytes
|
| 13 |
+
|
| 14 |
+
# Unsloth for efficient 4-bit QLoRA training on T4 GPUs
|
| 15 |
+
# Note: Unsloth often prefers being installed via their specific pip wheel or git,
|
| 16 |
+
# but this is standard for a requirements file.
|
| 17 |
+
unsloth
|
| 18 |
+
|
| 19 |
+
# PyTorch (Will default to standard compatible version if no index is specified)
|
| 20 |
+
torch
|
| 21 |
+
|
| 22 |
+
# Logging & Visualizations (For Day 2 judging criteria)
|
| 23 |
+
wandb
|
| 24 |
+
matplotlib
|
space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/app.py
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import logging
|
| 3 |
+
import traceback
|
| 4 |
+
from typing import Any, Dict, List
|
| 5 |
+
|
| 6 |
+
# OpenEnv SDK import
|
| 7 |
+
from openenv import MCPEnvironment
|
| 8 |
+
|
| 9 |
+
# ==============================================================================
|
| 10 |
+
# ROLE 1 & 3 IMPORTS
|
| 11 |
+
# TODO: Import the actual execution engine and generator from your teammates
|
| 12 |
+
# ==============================================================================
|
| 13 |
+
# from engine import execute_tac, verify_equivalence
|
| 14 |
+
# from curriculum import generate_level_code
|
| 15 |
+
|
| 16 |
+
logging.basicConfig(level=logging.INFO)
|
| 17 |
+
logger = logging.getLogger("CompilerEnvServer")
|
| 18 |
+
|
| 19 |
+
class CompilerEnv(MCPEnvironment):
|
| 20 |
+
"""
|
| 21 |
+
The OpenEnv Server Wrapper for the Toy-IR Compiler Pass Optimizer.
|
| 22 |
+
Acts as the referee between the LLM client and Role 1's Execution Engine.
|
| 23 |
+
"""
|
| 24 |
+
|
| 25 |
+
def __init__(self):
|
| 26 |
+
super().__init__()
|
| 27 |
+
# State variables
|
| 28 |
+
self.raw_json_code = None
|
| 29 |
+
self.current_state_string = ""
|
| 30 |
+
self.initial_cycles = 0
|
| 31 |
+
self.current_step = 0
|
| 32 |
+
self.max_steps = 10 # Set a max step limit per episode
|
| 33 |
+
|
| 34 |
+
# Cycle Weights (The Physics defined by Role 2)
|
| 35 |
+
self.cycle_weights = {
|
| 36 |
+
"ADD": 1,
|
| 37 |
+
"SUB": 1,
|
| 38 |
+
"MUL": 4,
|
| 39 |
+
"DIV": 10,
|
| 40 |
+
"MEM_LOAD": 20,
|
| 41 |
+
"STORE": 20
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
def reset(self) -> str:
|
| 45 |
+
"""
|
| 46 |
+
Grabs unoptimized code, calculates baseline cycles, and translates the
|
| 47 |
+
state for the LLM.
|
| 48 |
+
"""
|
| 49 |
+
self.current_step = 0
|
| 50 |
+
|
| 51 |
+
# 1. Grab new unoptimized code (Role 3 integration)
|
| 52 |
+
# TODO: Replace with real generator: self.raw_json_code = generate_level_code()
|
| 53 |
+
self.raw_json_code = self._mock_generator()
|
| 54 |
+
|
| 55 |
+
# 2. Get baseline cycles (Role 1 integration)
|
| 56 |
+
# TODO: Replace with real engine: self.initial_cycles, _ = execute_tac(self.raw_json_code, [])
|
| 57 |
+
self.initial_cycles = 100
|
| 58 |
+
|
| 59 |
+
# 3. Translate to Pseudo-Assembly to prevent Attention Dilution
|
| 60 |
+
self.current_state_string = self._translate_state(self.raw_json_code)
|
| 61 |
+
|
| 62 |
+
logger.info(f"Environment Reset. Baseline Cycles: {self.initial_cycles}")
|
| 63 |
+
return self.state()
|
| 64 |
+
|
| 65 |
+
def step(self, action: str) -> Dict[str, Any]:
|
| 66 |
+
"""
|
| 67 |
+
Executes the LLM's chosen optimization pass, verifies math equivalence,
|
| 68 |
+
and calculates the reward.
|
| 69 |
+
"""
|
| 70 |
+
self.current_step += 1
|
| 71 |
+
|
| 72 |
+
# 1. Parse LLM Action (Regex/JSON robustness)
|
| 73 |
+
try:
|
| 74 |
+
# Assuming the LLM outputs a single pass name or a list of passes
|
| 75 |
+
action_data = json.loads(action)
|
| 76 |
+
if isinstance(action_data, str):
|
| 77 |
+
action_array = [action_data]
|
| 78 |
+
else:
|
| 79 |
+
action_array = action_data
|
| 80 |
+
format_bonus = 0.1
|
| 81 |
+
except json.JSONDecodeError:
|
| 82 |
+
# Format Trap
|
| 83 |
+
return self._build_step_response(
|
| 84 |
+
reward=-2.5,
|
| 85 |
+
done=True,
|
| 86 |
+
error="Invalid JSON. You must output a valid JSON array of strings."
|
| 87 |
+
)
|
| 88 |
+
|
| 89 |
+
# 2. Execute Code & Verify Equivalence (Role 1 Integration)
|
| 90 |
+
# TODO: new_cycles, optimized_code = execute_tac(self.raw_json_code, action_array)
|
| 91 |
+
# TODO: is_valid = verify_equivalence(self.raw_json_code, optimized_code)
|
| 92 |
+
new_cycles = 80 # Mock Data
|
| 93 |
+
is_valid = True # Mock Data
|
| 94 |
+
|
| 95 |
+
# 3. Calculate Reward Physics
|
| 96 |
+
if not is_valid:
|
| 97 |
+
# Correctness Penalty + Micro-Variance for GRPO
|
| 98 |
+
penalty = -2.0 - (len(action_array) * 0.01)
|
| 99 |
+
return self._build_step_response(
|
| 100 |
+
reward=penalty,
|
| 101 |
+
done=True,
|
| 102 |
+
error=f"Code equivalence broken by passes: {action_array}"
|
| 103 |
+
)
|
| 104 |
+
|
| 105 |
+
# Calculate Improvement Ratio + Time Tax (-1.0)
|
| 106 |
+
cycle_improvement_ratio = (self.initial_cycles - new_cycles) / self.initial_cycles
|
| 107 |
+
time_tax = -0.05 * self.current_step # Small tax to prevent pass spamming
|
| 108 |
+
reward = cycle_improvement_ratio + format_bonus + time_tax
|
| 109 |
+
|
| 110 |
+
# Update state if sequential, or finish if one-shot
|
| 111 |
+
# NOTE: For hackathon speed, we treat this as a One-Shot episode
|
| 112 |
+
done = True
|
| 113 |
+
|
| 114 |
+
return self._build_step_response(
|
| 115 |
+
reward=reward,
|
| 116 |
+
done=done,
|
| 117 |
+
info={"status": "success", "optimized_cycles": new_cycles}
|
| 118 |
+
)
|
| 119 |
+
|
| 120 |
+
def state(self) -> str:
|
| 121 |
+
"""
|
| 122 |
+
Returns the current observation to the LLM.
|
| 123 |
+
"""
|
| 124 |
+
return f"Current Step: {self.current_step}/{self.max_steps}\n\n{self.current_state_string}"
|
| 125 |
+
|
| 126 |
+
def _translate_state(self, raw_json: List[Dict]) -> str:
|
| 127 |
+
"""
|
| 128 |
+
Translates raw AST JSON into clean pseudo-assembly.
|
| 129 |
+
Strips all UUIDs and AST metadata.
|
| 130 |
+
"""
|
| 131 |
+
pseudo_assembly = []
|
| 132 |
+
instruction_count = 1
|
| 133 |
+
|
| 134 |
+
for inst in raw_json:
|
| 135 |
+
op = inst.get("op", "UNKNOWN")
|
| 136 |
+
src1 = inst.get("src1", "")
|
| 137 |
+
src2 = inst.get("src2", "")
|
| 138 |
+
dest = inst.get("dest", "")
|
| 139 |
+
|
| 140 |
+
# Format arguments cleanly
|
| 141 |
+
args = f"{src1}" if src2 is None else f"{src1}, {src2}"
|
| 142 |
+
|
| 143 |
+
if dest:
|
| 144 |
+
line = f"{instruction_count}. {dest} = {op} {args}"
|
| 145 |
+
else:
|
| 146 |
+
line = f"{instruction_count}. {op} {args}"
|
| 147 |
+
|
| 148 |
+
pseudo_assembly.append(line)
|
| 149 |
+
instruction_count += 1
|
| 150 |
+
|
| 151 |
+
return "\n".join(pseudo_assembly)
|
| 152 |
+
|
| 153 |
+
def _build_step_response(self, reward: float, done: bool, error: str = None, info: dict = None) -> Dict[str, Any]:
|
| 154 |
+
"""Helper to format the standard OpenEnv step return dictionary."""
|
| 155 |
+
response = {
|
| 156 |
+
"reward": reward,
|
| 157 |
+
"done": done,
|
| 158 |
+
"state": self.state()
|
| 159 |
+
}
|
| 160 |
+
if error:
|
| 161 |
+
response["error"] = error
|
| 162 |
+
if info:
|
| 163 |
+
response["info"] = info
|
| 164 |
+
return response
|
| 165 |
+
|
| 166 |
+
def _mock_generator(self):
|
| 167 |
+
"""Mock data so the server runs before Role 1 integrates their engine."""
|
| 168 |
+
return [
|
| 169 |
+
{"op": "CONST", "dest": "a", "src1": 2, "src2": None},
|
| 170 |
+
{"op": "CONST", "dest": "b", "src1": 3, "src2": None},
|
| 171 |
+
{"op": "ADD", "dest": "c", "src1": "a", "src2": "b"}
|
| 172 |
+
]
|
| 173 |
+
|
| 174 |
+
if __name__ == "__main__":
|
| 175 |
+
logger.info("Initializing CompilerEnv Server...")
|
| 176 |
+
try:
|
| 177 |
+
env = CompilerEnv()
|
| 178 |
+
# openenv.run() or start() depending on the specific MCP wrapper version
|
| 179 |
+
env.start()
|
| 180 |
+
except Exception as e:
|
| 181 |
+
logger.error(f"Failed to start environment server: {e}")
|
| 182 |
+
logger.error(traceback.format_exc())
|
space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/compiler_tetris (1).ipynb
ADDED
|
@@ -0,0 +1,651 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "code",
|
| 5 |
+
"execution_count": null,
|
| 6 |
+
"id": "b7fdb9ab",
|
| 7 |
+
"metadata": {},
|
| 8 |
+
"outputs": [],
|
| 9 |
+
"source": [
|
| 10 |
+
"# ==== CELL 1: Setup ====\n",
|
| 11 |
+
"!pip install -q pytest\n",
|
| 12 |
+
"import os, sys\n",
|
| 13 |
+
"\n",
|
| 14 |
+
"LOCAL_DIR = \"/content/toyir\"\n",
|
| 15 |
+
"DRIVE_DIR = \"/content/drive/MyDrive/env/toyir\"\n",
|
| 16 |
+
"\n",
|
| 17 |
+
"os.makedirs(LOCAL_DIR, exist_ok=True)\n",
|
| 18 |
+
"sys.path.insert(0, LOCAL_DIR)\n",
|
| 19 |
+
"print(\"Workspace ready at\", LOCAL_DIR)\n"
|
| 20 |
+
]
|
| 21 |
+
},
|
| 22 |
+
{
|
| 23 |
+
"cell_type": "code",
|
| 24 |
+
"execution_count": null,
|
| 25 |
+
"id": "d0337367",
|
| 26 |
+
"metadata": {},
|
| 27 |
+
"outputs": [],
|
| 28 |
+
"source": [
|
| 29 |
+
"# ==== CELL 1b: Mount Drive + sync workspace ====\n",
|
| 30 |
+
"from google.colab import drive\n",
|
| 31 |
+
"drive.mount('/content/drive')\n",
|
| 32 |
+
"\n",
|
| 33 |
+
"import shutil\n",
|
| 34 |
+
"os.makedirs(DRIVE_DIR, exist_ok=True)\n",
|
| 35 |
+
"\n",
|
| 36 |
+
"# On fresh runtime: pull existing files from Drive -> local\n",
|
| 37 |
+
"for f in os.listdir(DRIVE_DIR):\n",
|
| 38 |
+
" src = os.path.join(DRIVE_DIR, f)\n",
|
| 39 |
+
" if os.path.isfile(src) and f.endswith(\".py\"):\n",
|
| 40 |
+
" shutil.copy2(src, os.path.join(LOCAL_DIR, f))\n",
|
| 41 |
+
"print(\"Synced from Drive:\", sorted(os.listdir(LOCAL_DIR)))\n"
|
| 42 |
+
]
|
| 43 |
+
},
|
| 44 |
+
{
|
| 45 |
+
"cell_type": "code",
|
| 46 |
+
"execution_count": null,
|
| 47 |
+
"id": "2c83c529",
|
| 48 |
+
"metadata": {},
|
| 49 |
+
"outputs": [],
|
| 50 |
+
"source": [
|
| 51 |
+
"%%writefile /content/toyir/toy_vm.py\n",
|
| 52 |
+
"\"\"\"Toy-IR VM: deterministic, dependency-light execution engine for RLVR.\"\"\"\n",
|
| 53 |
+
"from __future__ import annotations\n",
|
| 54 |
+
"from copy import deepcopy\n",
|
| 55 |
+
"from typing import Any\n",
|
| 56 |
+
"\n",
|
| 57 |
+
"CYCLE_COST: dict[str, int] = {\n",
|
| 58 |
+
" \"CONST\": 1, \"ADD\": 1, \"SUB\": 1, \"MUL\": 3, \"DIV\": 5,\n",
|
| 59 |
+
" \"COPY\": 1, \"LOAD\": 4, \"STORE\": 4, \"NOP\": 0,\n",
|
| 60 |
+
"}\n",
|
| 61 |
+
"OP_ORDER = (\"CONST\", \"ADD\", \"SUB\", \"MUL\", \"DIV\", \"COPY\", \"LOAD\", \"STORE\", \"NOP\")\n",
|
| 62 |
+
"VALID_OPS = frozenset(CYCLE_COST)\n",
|
| 63 |
+
"REQUIRED_KEYS = (\"op\", \"dest\", \"src1\", \"src2\")\n",
|
| 64 |
+
"\n",
|
| 65 |
+
"\n",
|
| 66 |
+
"def _is_str_or_int_or_none(x: Any) -> bool:\n",
|
| 67 |
+
" return x is None or isinstance(x, str) or (isinstance(x, int) and not isinstance(x, bool))\n",
|
| 68 |
+
"\n",
|
| 69 |
+
"\n",
|
| 70 |
+
"def _is_str_or_none(x: Any) -> bool:\n",
|
| 71 |
+
" return x is None or isinstance(x, str)\n",
|
| 72 |
+
"\n",
|
| 73 |
+
"\n",
|
| 74 |
+
"def validate_ir(program: list[dict]) -> dict:\n",
|
| 75 |
+
" errors: list[str] = []\n",
|
| 76 |
+
" for i, ins in enumerate(program):\n",
|
| 77 |
+
" if not isinstance(ins, dict):\n",
|
| 78 |
+
" errors.append(f\"[{i}] not a dict\")\n",
|
| 79 |
+
" continue\n",
|
| 80 |
+
" for k in REQUIRED_KEYS:\n",
|
| 81 |
+
" if k not in ins:\n",
|
| 82 |
+
" errors.append(f\"[{i}] missing key '{k}'\")\n",
|
| 83 |
+
" if \"op\" not in ins:\n",
|
| 84 |
+
" continue\n",
|
| 85 |
+
" op = ins[\"op\"]\n",
|
| 86 |
+
" if op not in VALID_OPS:\n",
|
| 87 |
+
" errors.append(f\"[{i}] invalid op '{op}'\")\n",
|
| 88 |
+
" continue\n",
|
| 89 |
+
" if not _is_str_or_none(ins.get(\"dest\")):\n",
|
| 90 |
+
" errors.append(f\"[{i}] dest must be str|None\")\n",
|
| 91 |
+
" if not _is_str_or_int_or_none(ins.get(\"src1\")):\n",
|
| 92 |
+
" errors.append(f\"[{i}] src1 must be str|int|None\")\n",
|
| 93 |
+
" if not _is_str_or_int_or_none(ins.get(\"src2\")):\n",
|
| 94 |
+
" errors.append(f\"[{i}] src2 must be str|int|None\")\n",
|
| 95 |
+
"\n",
|
| 96 |
+
" # per-op contracts\n",
|
| 97 |
+
" if op in (\"CONST\", \"ADD\", \"SUB\", \"MUL\", \"DIV\", \"COPY\", \"LOAD\"):\n",
|
| 98 |
+
" if not isinstance(ins.get(\"dest\"), str):\n",
|
| 99 |
+
" errors.append(f\"[{i}] {op} requires str dest\")\n",
|
| 100 |
+
" if op in (\"ADD\", \"SUB\", \"MUL\", \"DIV\"):\n",
|
| 101 |
+
" if ins.get(\"src1\") is None or ins.get(\"src2\") is None:\n",
|
| 102 |
+
" errors.append(f\"[{i}] {op} requires src1 and src2\")\n",
|
| 103 |
+
" if op in (\"CONST\", \"COPY\"):\n",
|
| 104 |
+
" if ins.get(\"src1\") is None:\n",
|
| 105 |
+
" errors.append(f\"[{i}] {op} requires src1\")\n",
|
| 106 |
+
" if op in (\"CONST\", \"COPY\", \"LOAD\", \"STORE\"):\n",
|
| 107 |
+
" if ins.get(\"src2\") is not None:\n",
|
| 108 |
+
" errors.append(f\"[{i}] {op} requires src2=None\")\n",
|
| 109 |
+
" if op == \"LOAD\":\n",
|
| 110 |
+
" if not isinstance(ins.get(\"src1\"), str):\n",
|
| 111 |
+
" errors.append(f\"[{i}] LOAD src1 must be str (variable name)\")\n",
|
| 112 |
+
" if op == \"STORE\":\n",
|
| 113 |
+
" if not isinstance(ins.get(\"dest\"), str):\n",
|
| 114 |
+
" errors.append(f\"[{i}] STORE dest must be str (variable name)\")\n",
|
| 115 |
+
" if ins.get(\"src1\") is None:\n",
|
| 116 |
+
" errors.append(f\"[{i}] STORE requires src1\")\n",
|
| 117 |
+
" if op == \"NOP\":\n",
|
| 118 |
+
" if any(ins.get(k) is not None for k in (\"dest\", \"src1\", \"src2\")):\n",
|
| 119 |
+
" errors.append(f\"[{i}] NOP fields must all be None\")\n",
|
| 120 |
+
"\n",
|
| 121 |
+
" return {\"valid\": len(errors) == 0, \"errors\": errors}\n",
|
| 122 |
+
"\n",
|
| 123 |
+
"\n",
|
| 124 |
+
"def _resolve(operand: Any, variables: dict) -> int:\n",
|
| 125 |
+
" if isinstance(operand, str):\n",
|
| 126 |
+
" if operand not in variables:\n",
|
| 127 |
+
" raise KeyError(f\"undefined variable '{operand}'\")\n",
|
| 128 |
+
" return variables[operand]\n",
|
| 129 |
+
" if isinstance(operand, int) and not isinstance(operand, bool):\n",
|
| 130 |
+
" return operand\n",
|
| 131 |
+
" raise TypeError(f\"bad operand: {operand!r}\")\n",
|
| 132 |
+
"\n",
|
| 133 |
+
"\n",
|
| 134 |
+
"def _exec_one(ins: dict, variables: dict, memory: dict) -> int:\n",
|
| 135 |
+
" op = ins[\"op\"]\n",
|
| 136 |
+
" dest = ins[\"dest\"]\n",
|
| 137 |
+
" s1, s2 = ins[\"src1\"], ins[\"src2\"]\n",
|
| 138 |
+
"\n",
|
| 139 |
+
" if op == \"CONST\":\n",
|
| 140 |
+
" variables[dest] = _resolve(s1, variables)\n",
|
| 141 |
+
" elif op == \"ADD\":\n",
|
| 142 |
+
" variables[dest] = _resolve(s1, variables) + _resolve(s2, variables)\n",
|
| 143 |
+
" elif op == \"SUB\":\n",
|
| 144 |
+
" variables[dest] = _resolve(s1, variables) - _resolve(s2, variables)\n",
|
| 145 |
+
" elif op == \"MUL\":\n",
|
| 146 |
+
" variables[dest] = _resolve(s1, variables) * _resolve(s2, variables)\n",
|
| 147 |
+
" elif op == \"DIV\":\n",
|
| 148 |
+
" b = _resolve(s2, variables)\n",
|
| 149 |
+
" if b == 0:\n",
|
| 150 |
+
" raise ZeroDivisionError(\"DIV by zero\")\n",
|
| 151 |
+
" variables[dest] = _resolve(s1, variables) // b\n",
|
| 152 |
+
" elif op == \"COPY\":\n",
|
| 153 |
+
" variables[dest] = _resolve(s1, variables)\n",
|
| 154 |
+
" elif op == \"LOAD\":\n",
|
| 155 |
+
" if not isinstance(s1, str):\n",
|
| 156 |
+
" raise TypeError(\"LOAD src1 must be a variable name\")\n",
|
| 157 |
+
" addr = variables.get(s1)\n",
|
| 158 |
+
" if not (isinstance(addr, int) and not isinstance(addr, bool)):\n",
|
| 159 |
+
" raise TypeError(f\"LOAD address from '{s1}' is not int: {addr!r}\")\n",
|
| 160 |
+
" if addr not in memory:\n",
|
| 161 |
+
" raise KeyError(f\"LOAD from uninitialized addr {addr}\")\n",
|
| 162 |
+
" variables[dest] = memory[addr]\n",
|
| 163 |
+
" elif op == \"STORE\":\n",
|
| 164 |
+
" if not isinstance(dest, str):\n",
|
| 165 |
+
" raise TypeError(\"STORE dest must be a variable name\")\n",
|
| 166 |
+
" addr = variables.get(dest)\n",
|
| 167 |
+
" if not (isinstance(addr, int) and not isinstance(addr, bool)):\n",
|
| 168 |
+
" raise TypeError(f\"STORE address from '{dest}' is not int: {addr!r}\")\n",
|
| 169 |
+
" memory[addr] = _resolve(s1, variables)\n",
|
| 170 |
+
" elif op == \"NOP\":\n",
|
| 171 |
+
" pass\n",
|
| 172 |
+
" else:\n",
|
| 173 |
+
" raise ValueError(f\"non-executable op {op}\")\n",
|
| 174 |
+
" return CYCLE_COST[op]\n",
|
| 175 |
+
"\n",
|
| 176 |
+
"\n",
|
| 177 |
+
"def execute(program: list[dict], initial_state: dict) -> dict:\n",
|
| 178 |
+
" variables: dict = {}\n",
|
| 179 |
+
" memory: dict = {}\n",
|
| 180 |
+
" cycles = 0\n",
|
| 181 |
+
" try:\n",
|
| 182 |
+
" if not isinstance(initial_state, dict):\n",
|
| 183 |
+
" raise TypeError(\"initial_state must be a dict\")\n",
|
| 184 |
+
" state = deepcopy(initial_state)\n",
|
| 185 |
+
" variables = state.get(\"variables\", {})\n",
|
| 186 |
+
" memory = state.get(\"memory\", {})\n",
|
| 187 |
+
" if not isinstance(variables, dict):\n",
|
| 188 |
+
" raise TypeError(\"initial_state['variables'] must be a dict\")\n",
|
| 189 |
+
" if not isinstance(memory, dict):\n",
|
| 190 |
+
" raise TypeError(\"initial_state['memory'] must be a dict\")\n",
|
| 191 |
+
"\n",
|
| 192 |
+
" for ins in program:\n",
|
| 193 |
+
" cycles += _exec_one(ins, variables, memory)\n",
|
| 194 |
+
" except Exception as e:\n",
|
| 195 |
+
" return {\"memory\": memory, \"variables\": variables,\n",
|
| 196 |
+
" \"cycles\": None, \"success\": False, \"error\": str(e)}\n",
|
| 197 |
+
" return {\"memory\": memory, \"variables\": variables,\n",
|
| 198 |
+
" \"cycles\": cycles, \"success\": True, \"error\": None}\n",
|
| 199 |
+
"\n",
|
| 200 |
+
"\n",
|
| 201 |
+
"def count_cycles(program: list[dict]) -> int:\n",
|
| 202 |
+
" total = 0\n",
|
| 203 |
+
" for i, ins in enumerate(program):\n",
|
| 204 |
+
" op = ins.get(\"op\") if isinstance(ins, dict) else None\n",
|
| 205 |
+
" if op not in CYCLE_COST:\n",
|
| 206 |
+
" raise ValueError(f\"count_cycles: invalid or missing op at index {i}: {op!r}\")\n",
|
| 207 |
+
" total += CYCLE_COST[op]\n",
|
| 208 |
+
" return total\n",
|
| 209 |
+
"\n",
|
| 210 |
+
"\n",
|
| 211 |
+
"def profile(program: list[dict]) -> dict:\n",
|
| 212 |
+
" out: dict[str, int] = {\"n_instructions\": len(program)}\n",
|
| 213 |
+
" for op in OP_ORDER:\n",
|
| 214 |
+
" out[f\"n_{op.lower()}\"] = 0\n",
|
| 215 |
+
" for ins in program:\n",
|
| 216 |
+
" key = f\"n_{ins['op'].lower()}\"\n",
|
| 217 |
+
" if key in out:\n",
|
| 218 |
+
" out[key] += 1\n",
|
| 219 |
+
" return out\n",
|
| 220 |
+
"\n",
|
| 221 |
+
"\n",
|
| 222 |
+
"def _fmt_operand(x: Any) -> str:\n",
|
| 223 |
+
" if x is None:\n",
|
| 224 |
+
" return \"\"\n",
|
| 225 |
+
" return str(x)\n",
|
| 226 |
+
"\n",
|
| 227 |
+
"\n",
|
| 228 |
+
"def dump_ir(program: list[dict]) -> str:\n",
|
| 229 |
+
" stores = [ins[\"dest\"] for ins in program if ins[\"op\"] == \"STORE\"]\n",
|
| 230 |
+
" seen, ordered = set(), []\n",
|
| 231 |
+
" for s in stores:\n",
|
| 232 |
+
" if s not in seen:\n",
|
| 233 |
+
" seen.add(s); ordered.append(s)\n",
|
| 234 |
+
" header = \"// OBSERVABLE OUT: \" + (\", \".join(f\"mem[{s}]\" for s in ordered) if ordered else \"(none)\")\n",
|
| 235 |
+
" lines = [header]\n",
|
| 236 |
+
" for ins in program:\n",
|
| 237 |
+
" op = ins[\"op\"]\n",
|
| 238 |
+
" dest, s1, s2 = ins[\"dest\"], ins[\"src1\"], ins[\"src2\"]\n",
|
| 239 |
+
" if op == \"STORE\":\n",
|
| 240 |
+
" lines.append(f\"STORE [{dest}] {_fmt_operand(s1)}\")\n",
|
| 241 |
+
" elif op == \"LOAD\":\n",
|
| 242 |
+
" lines.append(f\"{dest} = LOAD [{_fmt_operand(s1)}]\")\n",
|
| 243 |
+
" elif op == \"NOP\":\n",
|
| 244 |
+
" lines.append(\"NOP\")\n",
|
| 245 |
+
" else:\n",
|
| 246 |
+
" parts = [op, _fmt_operand(s1)]\n",
|
| 247 |
+
" if s2 is not None:\n",
|
| 248 |
+
" parts.append(_fmt_operand(s2))\n",
|
| 249 |
+
" lines.append(f\"{dest} = {' '.join(p for p in parts if p)}\")\n",
|
| 250 |
+
" return \"\\n\".join(lines)\n",
|
| 251 |
+
"\n",
|
| 252 |
+
"\n",
|
| 253 |
+
"def clean_nops(program: list[dict]) -> list[dict]:\n",
|
| 254 |
+
" return [deepcopy(ins) for ins in program if ins[\"op\"] != \"NOP\"]\n"
|
| 255 |
+
]
|
| 256 |
+
},
|
| 257 |
+
{
|
| 258 |
+
"cell_type": "code",
|
| 259 |
+
"execution_count": null,
|
| 260 |
+
"id": "3c85a58b",
|
| 261 |
+
"metadata": {},
|
| 262 |
+
"outputs": [],
|
| 263 |
+
"source": [
|
| 264 |
+
"%%writefile /content/toyir/verifier.py\n",
|
| 265 |
+
"\"\"\"Strict equivalence verifier - anti-cheat for RL reward hacking.\"\"\"\n",
|
| 266 |
+
"from __future__ import annotations\n",
|
| 267 |
+
"from toy_vm import execute\n",
|
| 268 |
+
"\n",
|
| 269 |
+
"\n",
|
| 270 |
+
"def verify(original_program: list[dict],\n",
|
| 271 |
+
" optimized_program: list[dict],\n",
|
| 272 |
+
" initial_states: list[dict]) -> dict:\n",
|
| 273 |
+
" if not initial_states:\n",
|
| 274 |
+
" raise ValueError(\"verify requires at least one initial_state\")\n",
|
| 275 |
+
"\n",
|
| 276 |
+
" results = []\n",
|
| 277 |
+
" all_match = True\n",
|
| 278 |
+
" for st in initial_states:\n",
|
| 279 |
+
" r_o = execute(original_program, st)\n",
|
| 280 |
+
" r_p = execute(optimized_program, st)\n",
|
| 281 |
+
" mem_o = r_o[\"memory\"] if r_o[\"success\"] else None\n",
|
| 282 |
+
" mem_p = r_p[\"memory\"] if r_p[\"success\"] else None\n",
|
| 283 |
+
"\n",
|
| 284 |
+
" execution_errors = {}\n",
|
| 285 |
+
" if not r_o[\"success\"]:\n",
|
| 286 |
+
" execution_errors[\"original_error\"] = r_o[\"error\"]\n",
|
| 287 |
+
" if not r_p[\"success\"]:\n",
|
| 288 |
+
" execution_errors[\"optimized_error\"] = r_p[\"error\"]\n",
|
| 289 |
+
"\n",
|
| 290 |
+
" if execution_errors:\n",
|
| 291 |
+
" match = False\n",
|
| 292 |
+
" mismatches = [\"__execution_error__\"]\n",
|
| 293 |
+
" else:\n",
|
| 294 |
+
" keys = set(mem_o.keys()) | set(mem_p.keys())\n",
|
| 295 |
+
" mismatches = [k for k in keys if mem_o.get(k) != mem_p.get(k)]\n",
|
| 296 |
+
" match = len(mismatches) == 0\n",
|
| 297 |
+
"\n",
|
| 298 |
+
" if not match:\n",
|
| 299 |
+
" all_match = False\n",
|
| 300 |
+
" results.append({\n",
|
| 301 |
+
" \"initial_state\": st,\n",
|
| 302 |
+
" \"original_memory\": mem_o,\n",
|
| 303 |
+
" \"optimized_memory\": mem_p,\n",
|
| 304 |
+
" \"match\": match,\n",
|
| 305 |
+
" \"mismatches\": sorted(mismatches, key=lambda x: (isinstance(x, str), x)),\n",
|
| 306 |
+
" \"execution_errors\": execution_errors or None,\n",
|
| 307 |
+
" })\n",
|
| 308 |
+
" return {\"equivalent\": all_match, \"results\": results}\n"
|
| 309 |
+
]
|
| 310 |
+
},
|
| 311 |
+
{
|
| 312 |
+
"cell_type": "code",
|
| 313 |
+
"execution_count": null,
|
| 314 |
+
"id": "cd7f8fa2",
|
| 315 |
+
"metadata": {},
|
| 316 |
+
"outputs": [],
|
| 317 |
+
"source": [
|
| 318 |
+
"%%writefile /content/toyir/reward_utils.py\n",
|
| 319 |
+
"\"\"\"Reward signal: cycle-reduction metric for RL agent.\"\"\"\n",
|
| 320 |
+
"from __future__ import annotations\n",
|
| 321 |
+
"from toy_vm import execute\n",
|
| 322 |
+
"\n",
|
| 323 |
+
"\n",
|
| 324 |
+
"def compute_reduction(original_program: list[dict],\n",
|
| 325 |
+
" optimized_program: list[dict],\n",
|
| 326 |
+
" initial_state: dict) -> dict:\n",
|
| 327 |
+
" r_o = execute(original_program, initial_state)\n",
|
| 328 |
+
" r_p = execute(optimized_program, initial_state)\n",
|
| 329 |
+
"\n",
|
| 330 |
+
" if not (r_o[\"success\"] and r_p[\"success\"]):\n",
|
| 331 |
+
" return {\n",
|
| 332 |
+
" \"original_cycles\": r_o[\"cycles\"],\n",
|
| 333 |
+
" \"optimized_cycles\": r_p[\"cycles\"],\n",
|
| 334 |
+
" \"absolute_savings\": None,\n",
|
| 335 |
+
" \"relative_savings\": None,\n",
|
| 336 |
+
" \"success\": False,\n",
|
| 337 |
+
" \"error\": r_o[\"error\"] or r_p[\"error\"],\n",
|
| 338 |
+
" }\n",
|
| 339 |
+
"\n",
|
| 340 |
+
" oc, pc = r_o[\"cycles\"], r_p[\"cycles\"]\n",
|
| 341 |
+
" rel = 0.0 if oc == 0 else (oc - pc) / oc\n",
|
| 342 |
+
" rel = max(-1.0, min(1.0, rel))\n",
|
| 343 |
+
" return {\n",
|
| 344 |
+
" \"original_cycles\": oc,\n",
|
| 345 |
+
" \"optimized_cycles\": pc,\n",
|
| 346 |
+
" \"absolute_savings\": oc - pc,\n",
|
| 347 |
+
" \"relative_savings\": rel,\n",
|
| 348 |
+
" \"success\": True,\n",
|
| 349 |
+
" \"error\": None,\n",
|
| 350 |
+
" }\n"
|
| 351 |
+
]
|
| 352 |
+
},
|
| 353 |
+
{
|
| 354 |
+
"cell_type": "code",
|
| 355 |
+
"execution_count": null,
|
| 356 |
+
"id": "4f2a941c",
|
| 357 |
+
"metadata": {},
|
| 358 |
+
"outputs": [],
|
| 359 |
+
"source": [
|
| 360 |
+
"%%writefile /content/toyir/test_vm.py\n",
|
| 361 |
+
"\"\"\"Pytest suite covering VM, verifier, reward, and RL exploit guardrails.\"\"\"\n",
|
| 362 |
+
"from __future__ import annotations\n",
|
| 363 |
+
"import random\n",
|
| 364 |
+
"import pytest\n",
|
| 365 |
+
"from toy_vm import (\n",
|
| 366 |
+
" validate_ir, execute, count_cycles, profile, dump_ir, clean_nops, OP_ORDER,\n",
|
| 367 |
+
")\n",
|
| 368 |
+
"from verifier import verify\n",
|
| 369 |
+
"from reward_utils import compute_reduction\n",
|
| 370 |
+
"\n",
|
| 371 |
+
"\n",
|
| 372 |
+
"def I(op, dest=None, src1=None, src2=None):\n",
|
| 373 |
+
" return {\"op\": op, \"dest\": dest, \"src1\": src1, \"src2\": src2}\n",
|
| 374 |
+
"\n",
|
| 375 |
+
"\n",
|
| 376 |
+
"def test_const_fold_equivalence():\n",
|
| 377 |
+
" orig = [I(\"CONST\", \"t0\", 5), I(\"CONST\", \"t1\", 7), I(\"ADD\", \"t2\", \"t0\", \"t1\"),\n",
|
| 378 |
+
" I(\"CONST\", \"addr\", 0), I(\"STORE\", \"addr\", \"t2\")]\n",
|
| 379 |
+
" opt = [I(\"CONST\", \"t2\", 12), I(\"CONST\", \"addr\", 0), I(\"STORE\", \"addr\", \"t2\")]\n",
|
| 380 |
+
" res = verify(orig, opt, [{\"variables\": {}, \"memory\": {}}])\n",
|
| 381 |
+
" assert res[\"equivalent\"]\n",
|
| 382 |
+
" red = compute_reduction(orig, opt, {\"variables\": {}, \"memory\": {}})\n",
|
| 383 |
+
" assert red[\"absolute_savings\"] > 0\n",
|
| 384 |
+
"\n",
|
| 385 |
+
"\n",
|
| 386 |
+
"def test_dead_code_invisible():\n",
|
| 387 |
+
" orig = [I(\"CONST\", \"addr\", 0), I(\"CONST\", \"x\", 3),\n",
|
| 388 |
+
" I(\"MUL\", \"dead\", \"x\", 99), I(\"STORE\", \"addr\", \"x\")]\n",
|
| 389 |
+
" opt = [I(\"CONST\", \"addr\", 0), I(\"CONST\", \"x\", 3), I(\"STORE\", \"addr\", \"x\")]\n",
|
| 390 |
+
" res = verify(orig, opt, [{\"variables\": {}, \"memory\": {}}])\n",
|
| 391 |
+
" assert res[\"equivalent\"]\n",
|
| 392 |
+
"\n",
|
| 393 |
+
"\n",
|
| 394 |
+
"def test_fold_then_dce():\n",
|
| 395 |
+
" orig = [I(\"CONST\", \"a\", 2), I(\"CONST\", \"b\", 3), I(\"ADD\", \"c\", \"a\", \"b\"),\n",
|
| 396 |
+
" I(\"MUL\", \"d\", \"c\", 10), I(\"CONST\", \"addr\", 0), I(\"STORE\", \"addr\", \"d\")]\n",
|
| 397 |
+
" opt = [I(\"CONST\", \"d\", 50), I(\"CONST\", \"addr\", 0), I(\"STORE\", \"addr\", \"d\")]\n",
|
| 398 |
+
" res = verify(orig, opt, [{\"variables\": {}, \"memory\": {}}])\n",
|
| 399 |
+
" assert res[\"equivalent\"]\n",
|
| 400 |
+
" red = compute_reduction(orig, opt, {\"variables\": {}, \"memory\": {}})\n",
|
| 401 |
+
" assert red[\"original_cycles\"] > red[\"optimized_cycles\"]\n",
|
| 402 |
+
"\n",
|
| 403 |
+
"\n",
|
| 404 |
+
"def test_strength_reduction():\n",
|
| 405 |
+
" orig = [I(\"CONST\", \"addr\", 0), I(\"CONST\", \"x\", 7),\n",
|
| 406 |
+
" I(\"MUL\", \"y\", \"x\", 2), I(\"STORE\", \"addr\", \"y\")]\n",
|
| 407 |
+
" opt = [I(\"CONST\", \"addr\", 0), I(\"CONST\", \"x\", 7),\n",
|
| 408 |
+
" I(\"ADD\", \"y\", \"x\", \"x\"), I(\"STORE\", \"addr\", \"y\")]\n",
|
| 409 |
+
" res = verify(orig, opt, [{\"variables\": {}, \"memory\": {}}])\n",
|
| 410 |
+
" assert res[\"equivalent\"]\n",
|
| 411 |
+
" red = compute_reduction(orig, opt, {\"variables\": {}, \"memory\": {}})\n",
|
| 412 |
+
" assert red[\"absolute_savings\"] > 0\n",
|
| 413 |
+
"\n",
|
| 414 |
+
"\n",
|
| 415 |
+
"def test_already_optimal():\n",
|
| 416 |
+
" p = [I(\"CONST\", \"addr\", 0), I(\"CONST\", \"x\", 1), I(\"STORE\", \"addr\", \"x\")]\n",
|
| 417 |
+
" red = compute_reduction(p, p, {\"variables\": {}, \"memory\": {}})\n",
|
| 418 |
+
" assert red[\"absolute_savings\"] == 0\n",
|
| 419 |
+
" assert red[\"relative_savings\"] == 0.0\n",
|
| 420 |
+
"\n",
|
| 421 |
+
"\n",
|
| 422 |
+
"def test_div_by_zero():\n",
|
| 423 |
+
" p = [I(\"CONST\", \"a\", 10), I(\"CONST\", \"b\", 0), I(\"DIV\", \"c\", \"a\", \"b\")]\n",
|
| 424 |
+
" r = execute(p, {\"variables\": {}, \"memory\": {}})\n",
|
| 425 |
+
" assert r[\"success\"] is False\n",
|
| 426 |
+
" assert \"zero\" in r[\"error\"].lower()\n",
|
| 427 |
+
"\n",
|
| 428 |
+
"\n",
|
| 429 |
+
"def test_hardcoded_exploit_caught():\n",
|
| 430 |
+
" orig = [I(\"CONST\", \"addr\", 0), I(\"LOAD\", \"x\", \"in_addr\"),\n",
|
| 431 |
+
" I(\"MUL\", \"y\", \"x\", 3), I(\"STORE\", \"addr\", \"y\")]\n",
|
| 432 |
+
" opt = [I(\"CONST\", \"addr\", 0), I(\"CONST\", \"y\", 42), I(\"STORE\", \"addr\", \"y\")]\n",
|
| 433 |
+
" rng = random.Random(0)\n",
|
| 434 |
+
" states = [{\"variables\": {\"in_addr\": 1}, \"memory\": {1: rng.randint(0, 100)}} for _ in range(5)]\n",
|
| 435 |
+
" res = verify(orig, opt, states)\n",
|
| 436 |
+
" assert res[\"equivalent\"] is False\n",
|
| 437 |
+
" assert any(not r[\"match\"] for r in res[\"results\"])\n",
|
| 438 |
+
"\n",
|
| 439 |
+
"\n",
|
| 440 |
+
"def test_nop_zero_cost():\n",
|
| 441 |
+
" p = [I(\"NOP\"), I(\"CONST\", \"x\", 1), I(\"NOP\")]\n",
|
| 442 |
+
" assert count_cycles(p) == 1\n",
|
| 443 |
+
" r = execute(p, {\"variables\": {}, \"memory\": {}})\n",
|
| 444 |
+
" assert r[\"success\"] and r[\"cycles\"] == 1\n",
|
| 445 |
+
"\n",
|
| 446 |
+
"\n",
|
| 447 |
+
"def test_load_store_non_int_address():\n",
|
| 448 |
+
" p = [I(\"COPY\", \"addr\", \"bad\"), I(\"LOAD\", \"x\", \"addr\")]\n",
|
| 449 |
+
" r = execute(p, {\"variables\": {\"bad\": \"notint\"}, \"memory\": {}})\n",
|
| 450 |
+
" assert r[\"success\"] is False\n",
|
| 451 |
+
"\n",
|
| 452 |
+
"\n",
|
| 453 |
+
"def test_validate_ir_malformed():\n",
|
| 454 |
+
" assert not validate_ir([I(\"FOOBAR\", \"t\", 1, 2)])[\"valid\"]\n",
|
| 455 |
+
" assert not validate_ir([{\"op\": \"ADD\", \"dest\": \"x\"}])[\"valid\"]\n",
|
| 456 |
+
" assert not validate_ir([I(\"CONST\", 123, 1)])[\"valid\"]\n",
|
| 457 |
+
" assert not validate_ir([I(\"STORE\", None, \"x\")])[\"valid\"]\n",
|
| 458 |
+
" assert not validate_ir([I(\"LOAD\", \"x\", 42)])[\"valid\"]\n",
|
| 459 |
+
" assert not validate_ir([I(\"NOP\", \"x\", 1)])[\"valid\"]\n",
|
| 460 |
+
" assert not validate_ir([I(\"ADD\", \"x\", \"a\", None)])[\"valid\"]\n",
|
| 461 |
+
" assert not validate_ir([I(\"LOAD\", \"x\", \"addr\", 9)])[\"valid\"]\n",
|
| 462 |
+
" assert not validate_ir([I(\"STORE\", \"addr\", \"x\", 9)])[\"valid\"]\n",
|
| 463 |
+
" assert validate_ir([I(\"CONST\", \"x\", 1)])[\"valid\"]\n",
|
| 464 |
+
"\n",
|
| 465 |
+
"\n",
|
| 466 |
+
"def test_dump_ir_format():\n",
|
| 467 |
+
" p = [I(\"CONST\", \"t0\", 5), I(\"ADD\", \"t1\", \"t0\", \"a\"),\n",
|
| 468 |
+
" I(\"MUL\", \"t2\", \"t1\", 3), I(\"STORE\", \"addr0\", \"t2\")]\n",
|
| 469 |
+
" s = dump_ir(p)\n",
|
| 470 |
+
" lines = s.split(\"\\n\")\n",
|
| 471 |
+
" assert lines[0] == \"// OBSERVABLE OUT: mem[addr0]\"\n",
|
| 472 |
+
" assert lines[1] == \"t0 = CONST 5\"\n",
|
| 473 |
+
" assert lines[2] == \"t1 = ADD t0 a\"\n",
|
| 474 |
+
" assert lines[3] == \"t2 = MUL t1 3\"\n",
|
| 475 |
+
" assert lines[4] == \"STORE [addr0] t2\"\n",
|
| 476 |
+
" assert len(lines) == 5\n",
|
| 477 |
+
"\n",
|
| 478 |
+
"\n",
|
| 479 |
+
"def test_dump_ir_multi_store_header_order():\n",
|
| 480 |
+
" p = [I(\"CONST\", \"a\", 0), I(\"CONST\", \"b\", 1),\n",
|
| 481 |
+
" I(\"STORE\", \"a\", 5), I(\"STORE\", \"b\", 6), I(\"STORE\", \"a\", 7)]\n",
|
| 482 |
+
" s = dump_ir(p)\n",
|
| 483 |
+
" assert s.split(\"\\n\")[0] == \"// OBSERVABLE OUT: mem[a], mem[b]\"\n",
|
| 484 |
+
"\n",
|
| 485 |
+
"\n",
|
| 486 |
+
"def test_clean_nops():\n",
|
| 487 |
+
" p = [I(\"NOP\"), I(\"CONST\", \"x\", 1), I(\"NOP\"), I(\"CONST\", \"y\", 2), I(\"NOP\")]\n",
|
| 488 |
+
" out = clean_nops(p)\n",
|
| 489 |
+
" assert len(out) == 2\n",
|
| 490 |
+
" assert all(ins[\"op\"] != \"NOP\" for ins in out)\n",
|
| 491 |
+
"\n",
|
| 492 |
+
"\n",
|
| 493 |
+
"def test_verify_mismatches():\n",
|
| 494 |
+
" orig = [I(\"CONST\", \"a0\", 0), I(\"CONST\", \"a1\", 1),\n",
|
| 495 |
+
" I(\"CONST\", \"x\", 5), I(\"CONST\", \"y\", 9),\n",
|
| 496 |
+
" I(\"STORE\", \"a0\", \"x\"), I(\"STORE\", \"a1\", \"y\")]\n",
|
| 497 |
+
" opt = [I(\"CONST\", \"a0\", 0), I(\"CONST\", \"a1\", 1),\n",
|
| 498 |
+
" I(\"CONST\", \"x\", 5), I(\"CONST\", \"y\", 8),\n",
|
| 499 |
+
" I(\"STORE\", \"a0\", \"x\"), I(\"STORE\", \"a1\", \"y\")]\n",
|
| 500 |
+
" res = verify(orig, opt, [{\"variables\": {}, \"memory\": {}}])\n",
|
| 501 |
+
" assert res[\"equivalent\"] is False\n",
|
| 502 |
+
" assert res[\"results\"][0][\"mismatches\"] == [1]\n",
|
| 503 |
+
"\n",
|
| 504 |
+
"\n",
|
| 505 |
+
"def test_profile_counts_and_ordering():\n",
|
| 506 |
+
" p = [I(\"CONST\", \"x\", 1), I(\"ADD\", \"y\", \"x\", 2), I(\"MUL\", \"z\", \"y\", 3)]\n",
|
| 507 |
+
" pr = profile(p)\n",
|
| 508 |
+
" assert pr[\"n_instructions\"] == 3\n",
|
| 509 |
+
" assert pr[\"n_const\"] == 1 and pr[\"n_add\"] == 1 and pr[\"n_mul\"] == 1\n",
|
| 510 |
+
" expected = [\"n_instructions\"] + [f\"n_{op.lower()}\" for op in OP_ORDER]\n",
|
| 511 |
+
" assert list(pr.keys()) == expected\n",
|
| 512 |
+
"\n",
|
| 513 |
+
"\n",
|
| 514 |
+
"def test_no_input_mutation():\n",
|
| 515 |
+
" p = [I(\"CONST\", \"x\", 1), I(\"CONST\", \"addr\", 0), I(\"STORE\", \"addr\", \"x\")]\n",
|
| 516 |
+
" st = {\"variables\": {}, \"memory\": {}}\n",
|
| 517 |
+
" snap = {\"variables\": dict(st[\"variables\"]), \"memory\": dict(st[\"memory\"])}\n",
|
| 518 |
+
" execute(p, st)\n",
|
| 519 |
+
" assert st == snap\n",
|
| 520 |
+
"\n",
|
| 521 |
+
"\n",
|
| 522 |
+
"def test_clean_nops_no_aliasing():\n",
|
| 523 |
+
" p = [I(\"CONST\", \"x\", 1), I(\"NOP\")]\n",
|
| 524 |
+
" out = clean_nops(p)\n",
|
| 525 |
+
" out[0][\"op\"] = \"MUL\"\n",
|
| 526 |
+
" assert p[0][\"op\"] == \"CONST\"\n",
|
| 527 |
+
"\n",
|
| 528 |
+
"\n",
|
| 529 |
+
"def test_verify_rejects_empty_states():\n",
|
| 530 |
+
" with pytest.raises(ValueError):\n",
|
| 531 |
+
" verify([I(\"NOP\")], [I(\"NOP\")], [])\n",
|
| 532 |
+
"\n",
|
| 533 |
+
"\n",
|
| 534 |
+
"def test_verify_surfaces_execution_errors():\n",
|
| 535 |
+
" good = [I(\"CONST\", \"addr\", 0), I(\"CONST\", \"x\", 1), I(\"STORE\", \"addr\", \"x\")]\n",
|
| 536 |
+
" bad = [I(\"LOAD\", \"x\", \"missing_addr\")]\n",
|
| 537 |
+
" res = verify(good, bad, [{\"variables\": {}, \"memory\": {}}])\n",
|
| 538 |
+
" assert res[\"equivalent\"] is False\n",
|
| 539 |
+
" assert res[\"results\"][0][\"mismatches\"] == [\"__execution_error__\"]\n",
|
| 540 |
+
" assert res[\"results\"][0][\"execution_errors\"][\"optimized_error\"] is not None\n",
|
| 541 |
+
" assert \"original_error\" not in res[\"results\"][0][\"execution_errors\"]\n",
|
| 542 |
+
"\n",
|
| 543 |
+
"\n",
|
| 544 |
+
"def test_execute_rejects_bad_initial_state_shape():\n",
|
| 545 |
+
" r = execute([I(\"CONST\", \"x\", 1)], \"not-a-dict\")\n",
|
| 546 |
+
" assert r[\"success\"] is False\n",
|
| 547 |
+
" assert \"initial_state\" in r[\"error\"]\n",
|
| 548 |
+
"\n",
|
| 549 |
+
"\n",
|
| 550 |
+
"def test_compute_reduction_failure_path():\n",
|
| 551 |
+
" orig = [I(\"CONST\", \"addr\", 0), I(\"CONST\", \"x\", 1), I(\"STORE\", \"addr\", \"x\")]\n",
|
| 552 |
+
" bad = [I(\"CONST\", \"a\", 1), I(\"CONST\", \"b\", 0), I(\"DIV\", \"c\", \"a\", \"b\")]\n",
|
| 553 |
+
" red = compute_reduction(orig, bad, {\"variables\": {}, \"memory\": {}})\n",
|
| 554 |
+
" assert red[\"success\"] is False\n",
|
| 555 |
+
" assert red[\"relative_savings\"] is None\n",
|
| 556 |
+
" assert red[\"absolute_savings\"] is None\n",
|
| 557 |
+
" assert \"zero\" in red[\"error\"].lower()\n",
|
| 558 |
+
"\n",
|
| 559 |
+
"\n",
|
| 560 |
+
"def test_relative_savings_clamped_on_slowdown():\n",
|
| 561 |
+
" fast = [I(\"CONST\", \"addr\", 0), I(\"CONST\", \"x\", 1), I(\"STORE\", \"addr\", \"x\")]\n",
|
| 562 |
+
" # slower variant: extra dead MULs balloon the cycle count\n",
|
| 563 |
+
" slow = [I(\"CONST\", \"addr\", 0), I(\"CONST\", \"x\", 1)]\n",
|
| 564 |
+
" slow += [I(\"MUL\", \"junk\", \"x\", 2)] * 50\n",
|
| 565 |
+
" slow += [I(\"STORE\", \"addr\", \"x\")]\n",
|
| 566 |
+
" red = compute_reduction(fast, slow, {\"variables\": {}, \"memory\": {}})\n",
|
| 567 |
+
" assert red[\"absolute_savings\"] < 0\n",
|
| 568 |
+
" assert red[\"relative_savings\"] == -1.0 # clamped\n"
|
| 569 |
+
]
|
| 570 |
+
},
|
| 571 |
+
{
|
| 572 |
+
"cell_type": "code",
|
| 573 |
+
"execution_count": null,
|
| 574 |
+
"id": "e6b83243",
|
| 575 |
+
"metadata": {},
|
| 576 |
+
"outputs": [],
|
| 577 |
+
"source": [
|
| 578 |
+
"# ==== CELL 6: Push to Drive ====\n",
|
| 579 |
+
"import shutil, os\n",
|
| 580 |
+
"for f in os.listdir(LOCAL_DIR):\n",
|
| 581 |
+
" if f.endswith(\".py\"):\n",
|
| 582 |
+
" shutil.copy2(os.path.join(LOCAL_DIR, f), os.path.join(DRIVE_DIR, f))\n",
|
| 583 |
+
"print(\"Saved to Drive:\", sorted(os.listdir(DRIVE_DIR)))\n"
|
| 584 |
+
]
|
| 585 |
+
},
|
| 586 |
+
{
|
| 587 |
+
"cell_type": "code",
|
| 588 |
+
"execution_count": null,
|
| 589 |
+
"id": "5675f9a0",
|
| 590 |
+
"metadata": {},
|
| 591 |
+
"outputs": [],
|
| 592 |
+
"source": [
|
| 593 |
+
"# ==== CELL 7: Run tests ====\n",
|
| 594 |
+
"import subprocess\n",
|
| 595 |
+
"res = subprocess.run(\n",
|
| 596 |
+
" [\"python\", \"-m\", \"pytest\", \"-v\", \"test_vm.py\"],\n",
|
| 597 |
+
" cwd=LOCAL_DIR, capture_output=True, text=True,\n",
|
| 598 |
+
")\n",
|
| 599 |
+
"print(res.stdout)\n",
|
| 600 |
+
"print(res.stderr)\n",
|
| 601 |
+
"assert res.returncode == 0, \"Tests failed\"\n"
|
| 602 |
+
]
|
| 603 |
+
},
|
| 604 |
+
{
|
| 605 |
+
"cell_type": "code",
|
| 606 |
+
"execution_count": null,
|
| 607 |
+
"id": "1a500bb3",
|
| 608 |
+
"metadata": {},
|
| 609 |
+
"outputs": [],
|
| 610 |
+
"source": [
|
| 611 |
+
"# ==== CELL 8: Smoke demo ====\n",
|
| 612 |
+
"import importlib, toy_vm, verifier, reward_utils\n",
|
| 613 |
+
"importlib.reload(toy_vm); importlib.reload(verifier); importlib.reload(reward_utils)\n",
|
| 614 |
+
"from toy_vm import dump_ir\n",
|
| 615 |
+
"from reward_utils import compute_reduction\n",
|
| 616 |
+
"from verifier import verify\n",
|
| 617 |
+
"\n",
|
| 618 |
+
"orig = [\n",
|
| 619 |
+
" {\"op\":\"CONST\",\"dest\":\"addr\",\"src1\":0,\"src2\":None},\n",
|
| 620 |
+
" {\"op\":\"CONST\",\"dest\":\"a\",\"src1\":2,\"src2\":None},\n",
|
| 621 |
+
" {\"op\":\"CONST\",\"dest\":\"b\",\"src1\":3,\"src2\":None},\n",
|
| 622 |
+
" {\"op\":\"ADD\",\"dest\":\"c\",\"src1\":\"a\",\"src2\":\"b\"},\n",
|
| 623 |
+
" {\"op\":\"MUL\",\"dest\":\"d\",\"src1\":\"c\",\"src2\":2},\n",
|
| 624 |
+
" {\"op\":\"MUL\",\"dest\":\"dead\",\"src1\":\"d\",\"src2\":99},\n",
|
| 625 |
+
" {\"op\":\"STORE\",\"dest\":\"addr\",\"src1\":\"d\",\"src2\":None},\n",
|
| 626 |
+
"]\n",
|
| 627 |
+
"opt = [\n",
|
| 628 |
+
" {\"op\":\"CONST\",\"dest\":\"addr\",\"src1\":0,\"src2\":None},\n",
|
| 629 |
+
" {\"op\":\"CONST\",\"dest\":\"d\",\"src1\":10,\"src2\":None},\n",
|
| 630 |
+
" {\"op\":\"STORE\",\"dest\":\"addr\",\"src1\":\"d\",\"src2\":None},\n",
|
| 631 |
+
"]\n",
|
| 632 |
+
"\n",
|
| 633 |
+
"print(dump_ir(orig)); print(\"---\"); print(dump_ir(opt)); print(\"---\")\n",
|
| 634 |
+
"print(compute_reduction(orig, opt, {\"variables\":{},\"memory\":{}}))\n",
|
| 635 |
+
"print(\"equivalent:\", verify(orig, opt, [{\"variables\":{},\"memory\":{}}])[\"equivalent\"])\n"
|
| 636 |
+
]
|
| 637 |
+
}
|
| 638 |
+
],
|
| 639 |
+
"metadata": {
|
| 640 |
+
"kernelspec": {
|
| 641 |
+
"display_name": "Python 3",
|
| 642 |
+
"language": "python",
|
| 643 |
+
"name": "python3"
|
| 644 |
+
},
|
| 645 |
+
"language_info": {
|
| 646 |
+
"name": "python"
|
| 647 |
+
}
|
| 648 |
+
},
|
| 649 |
+
"nbformat": 4,
|
| 650 |
+
"nbformat_minor": 5
|
| 651 |
+
}
|
space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/metahack1 (1).ipynb
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.DS_Store
ADDED
|
Binary file (6.15 kB). View file
|
|
|
space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/metahack1 (1).ipynb
ADDED
|
@@ -0,0 +1,1379 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"nbformat": 4,
|
| 3 |
+
"nbformat_minor": 0,
|
| 4 |
+
"metadata": {
|
| 5 |
+
"colab": {
|
| 6 |
+
"provenance": [],
|
| 7 |
+
"gpuType": "T4"
|
| 8 |
+
},
|
| 9 |
+
"kernelspec": {
|
| 10 |
+
"name": "python3",
|
| 11 |
+
"display_name": "Python 3"
|
| 12 |
+
},
|
| 13 |
+
"language_info": {
|
| 14 |
+
"name": "python"
|
| 15 |
+
},
|
| 16 |
+
"accelerator": "GPU"
|
| 17 |
+
},
|
| 18 |
+
"cells": [
|
| 19 |
+
{
|
| 20 |
+
"cell_type": "code",
|
| 21 |
+
"source": [
|
| 22 |
+
"## Design Assumptions (do not violate)\n",
|
| 23 |
+
"\n",
|
| 24 |
+
"# 1. **DCE never eliminates STOREs.** They define program output (final mem state).\n",
|
| 25 |
+
"# 2. **Addresses are distinct by construction.** Generator allocates each address variable to a unique integer; no aliasing.\n",
|
| 26 |
+
"# 3. **CF refuses to fold DIV by zero.** If src2 == 0 on a DIV op, leave instruction unchanged.\n",
|
| 27 |
+
"# 4. **Generator never emits DIV by literal zero.** When DIV is generated, src2 is always a non-zero constant or a variable known to be non-zero.\n",
|
| 28 |
+
"# # 5. **Integer arithmetic only.** No floats anywhere — avoids equivalence-check precision issues.\n",
|
| 29 |
+
"# 6. Generator declares `observable_addrs` per program — verifier compares only these mem entries.\n",
|
| 30 |
+
"# 7. State translator annotates observable outputs at top of dump.\n",
|
| 31 |
+
"# 8. Reward distinguishes broken (-1000) from valid-but-worse (small negative) — Harshal's formula.\n",
|
| 32 |
+
"# 9. Multi-input verification: 3-5 random initial states, all must match.\n",
|
| 33 |
+
"# 10. Integer division uses Python floor division (//). Aarush's VM must match."
|
| 34 |
+
],
|
| 35 |
+
"metadata": {
|
| 36 |
+
"id": "_XI5jT2Ibvrf"
|
| 37 |
+
},
|
| 38 |
+
"execution_count": 2,
|
| 39 |
+
"outputs": []
|
| 40 |
+
},
|
| 41 |
+
{
|
| 42 |
+
"cell_type": "code",
|
| 43 |
+
"execution_count": 11,
|
| 44 |
+
"metadata": {
|
| 45 |
+
"id": "j6CQ327KWXwr"
|
| 46 |
+
},
|
| 47 |
+
"outputs": [],
|
| 48 |
+
"source": [
|
| 49 |
+
"# === TAC Schema v1.0 (LOCKED with Role 1 / Aarush) ===\n",
|
| 50 |
+
"# Reverse passes deferred to stretch goal — not in initial action space.\n",
|
| 51 |
+
"\n",
|
| 52 |
+
"OPS = [\"CONST\", \"ADD\", \"SUB\", \"MUL\", \"DIV\", \"LOAD\", \"STORE\", \"NOP\"]\n",
|
| 53 |
+
"\n",
|
| 54 |
+
"CYCLE_COSTS = {\n",
|
| 55 |
+
" \"CONST\": 1,\n",
|
| 56 |
+
" \"ADD\": 1,\n",
|
| 57 |
+
" \"SUB\": 1,\n",
|
| 58 |
+
" \"MUL\": 3,\n",
|
| 59 |
+
" \"DIV\": 5,\n",
|
| 60 |
+
" \"LOAD\": 4,\n",
|
| 61 |
+
" \"STORE\": 4,\n",
|
| 62 |
+
" \"NOP\": 0,\n",
|
| 63 |
+
"}\n",
|
| 64 |
+
"\n",
|
| 65 |
+
"# Instruction shape: {\"op\": str, \"dest\": str|None, \"src1\": Any, \"src2\": Any}\n",
|
| 66 |
+
"# Operands: str = variable name, int = literal constant, None = unused\n",
|
| 67 |
+
"#\n",
|
| 68 |
+
"# Op semantics:\n",
|
| 69 |
+
"# CONST: dest = src1 (src1 is int literal, src2 = None)\n",
|
| 70 |
+
"# ADD/SUB/MUL/DIV: dest = src1 OP src2 (src1, src2 are var names or int literals)\n",
|
| 71 |
+
"# LOAD: dest = mem[src1] (src1 is a var holding an address)\n",
|
| 72 |
+
"# STORE: mem[dest] = src1 (dest is a var holding an address)\n",
|
| 73 |
+
"# NOP: no-op (all fields None)\n",
|
| 74 |
+
"#\n",
|
| 75 |
+
"# Program output (for equivalence check) = final memory state (mem dict).\n",
|
| 76 |
+
"# Programs ship as: (initial_vars: dict, initial_mem: dict, instructions: list[dict])"
|
| 77 |
+
]
|
| 78 |
+
},
|
| 79 |
+
{
|
| 80 |
+
"cell_type": "code",
|
| 81 |
+
"source": [
|
| 82 |
+
"import random\n",
|
| 83 |
+
"\n",
|
| 84 |
+
"def generate_level_1():\n",
|
| 85 |
+
" \"\"\"\n",
|
| 86 |
+
" Generate a Level 1 Toy-IR program.\n",
|
| 87 |
+
"\n",
|
| 88 |
+
" Characteristics:\n",
|
| 89 |
+
" - 4-6 instructions\n",
|
| 90 |
+
" - 2-3 CONST ops with literal values\n",
|
| 91 |
+
" - 1-2 arithmetic ops on those constants (foldable by CF)\n",
|
| 92 |
+
" - 0-1 dead variables (killable by DCE)\n",
|
| 93 |
+
" - Exactly 1 STORE at the end so the program has an observable output\n",
|
| 94 |
+
"\n",
|
| 95 |
+
" Returns:\n",
|
| 96 |
+
" dict with keys: initial_vars, initial_mem, instructions, observable_addrs\n",
|
| 97 |
+
" \"\"\"\n",
|
| 98 |
+
" instructions = []\n",
|
| 99 |
+
" var_counter = 0\n",
|
| 100 |
+
"\n",
|
| 101 |
+
" def new_var():\n",
|
| 102 |
+
" nonlocal var_counter\n",
|
| 103 |
+
" name = f\"v{var_counter}\"\n",
|
| 104 |
+
" var_counter += 1\n",
|
| 105 |
+
" return name\n",
|
| 106 |
+
"\n",
|
| 107 |
+
" # Step 1: Generate 2-3 constant assignments\n",
|
| 108 |
+
" num_consts = random.randint(2, 3)\n",
|
| 109 |
+
" const_vars = []\n",
|
| 110 |
+
" for _ in range(num_consts):\n",
|
| 111 |
+
" var = new_var()\n",
|
| 112 |
+
" value = random.randint(1, 10)\n",
|
| 113 |
+
" instructions.append({\"op\": \"CONST\", \"dest\": var, \"src1\": value, \"src2\": None})\n",
|
| 114 |
+
" const_vars.append(var)\n",
|
| 115 |
+
"\n",
|
| 116 |
+
" # Step 2: Generate 1-2 arithmetic ops using those constants\n",
|
| 117 |
+
" num_arith = random.randint(1, 2)\n",
|
| 118 |
+
" last_result = None\n",
|
| 119 |
+
" for _ in range(num_arith):\n",
|
| 120 |
+
" op = random.choice([\"ADD\", \"MUL\"])\n",
|
| 121 |
+
" src1 = random.choice(const_vars)\n",
|
| 122 |
+
" src2 = random.choice(const_vars)\n",
|
| 123 |
+
" dest = new_var()\n",
|
| 124 |
+
" instructions.append({\"op\": op, \"dest\": dest, \"src1\": src1, \"src2\": src2})\n",
|
| 125 |
+
" last_result = dest\n",
|
| 126 |
+
" const_vars.append(dest)\n",
|
| 127 |
+
"\n",
|
| 128 |
+
" # Step 3: Optionally add 1 dead variable (50% chance)\n",
|
| 129 |
+
" if random.random() < 0.5:\n",
|
| 130 |
+
" dead_var = new_var()\n",
|
| 131 |
+
" dead_value = random.randint(1, 10)\n",
|
| 132 |
+
" instructions.append({\"op\": \"CONST\", \"dest\": dead_var, \"src1\": dead_value, \"src2\": None})\n",
|
| 133 |
+
" # Note: dead_var is intentionally never used — DCE should catch it.\n",
|
| 134 |
+
"\n",
|
| 135 |
+
" # Step 4: Add a STORE at the end so the program has observable output\n",
|
| 136 |
+
" initial_vars = {\"addr0\": 0}\n",
|
| 137 |
+
" instructions.append({\"op\": \"STORE\", \"dest\": \"addr0\", \"src1\": last_result, \"src2\": None})\n",
|
| 138 |
+
"\n",
|
| 139 |
+
" initial_mem = {}\n",
|
| 140 |
+
"\n",
|
| 141 |
+
" return {\n",
|
| 142 |
+
" \"initial_vars\": initial_vars,\n",
|
| 143 |
+
" \"initial_mem\": initial_mem,\n",
|
| 144 |
+
" \"instructions\": instructions,\n",
|
| 145 |
+
" \"observable_addrs\": [0], # Aarush's verifier compares only these mem entries\n",
|
| 146 |
+
" }\n",
|
| 147 |
+
"\n",
|
| 148 |
+
"\n",
|
| 149 |
+
"# Sanity-check: generate a few programs and print them\n",
|
| 150 |
+
"for seed in [42, 1, 7, 99]:\n",
|
| 151 |
+
" random.seed(seed)\n",
|
| 152 |
+
" prog = generate_level_1()\n",
|
| 153 |
+
" print(f\"\\n=== seed={seed} ===\")\n",
|
| 154 |
+
" print(f\"initial_vars : {prog['initial_vars']}\")\n",
|
| 155 |
+
" print(f\"initial_mem : {prog['initial_mem']}\")\n",
|
| 156 |
+
" print(f\"observable_addrs : {prog['observable_addrs']}\")\n",
|
| 157 |
+
" print(f\"instructions ({len(prog['instructions'])}):\")\n",
|
| 158 |
+
" for i, instr in enumerate(prog['instructions']):\n",
|
| 159 |
+
" print(f\" {i}: {instr}\")"
|
| 160 |
+
],
|
| 161 |
+
"metadata": {
|
| 162 |
+
"colab": {
|
| 163 |
+
"base_uri": "https://localhost:8080/"
|
| 164 |
+
},
|
| 165 |
+
"id": "a86vD1OyWcB9",
|
| 166 |
+
"outputId": "2d6d4604-6dc1-4c29-af64-e41cf5717c0d"
|
| 167 |
+
},
|
| 168 |
+
"execution_count": 4,
|
| 169 |
+
"outputs": [
|
| 170 |
+
{
|
| 171 |
+
"output_type": "stream",
|
| 172 |
+
"name": "stdout",
|
| 173 |
+
"text": [
|
| 174 |
+
"\n",
|
| 175 |
+
"=== seed=42 ===\n",
|
| 176 |
+
"initial_vars : {'addr0': 0}\n",
|
| 177 |
+
"initial_mem : {}\n",
|
| 178 |
+
"observable_addrs : [0]\n",
|
| 179 |
+
"instructions (4):\n",
|
| 180 |
+
" 0: {'op': 'CONST', 'dest': 'v0', 'src1': 1, 'src2': None}\n",
|
| 181 |
+
" 1: {'op': 'CONST', 'dest': 'v1', 'src1': 5, 'src2': None}\n",
|
| 182 |
+
" 2: {'op': 'ADD', 'dest': 'v2', 'src1': 'v0', 'src2': 'v0'}\n",
|
| 183 |
+
" 3: {'op': 'STORE', 'dest': 'addr0', 'src1': 'v2', 'src2': None}\n",
|
| 184 |
+
"\n",
|
| 185 |
+
"=== seed=1 ===\n",
|
| 186 |
+
"initial_vars : {'addr0': 0}\n",
|
| 187 |
+
"initial_mem : {}\n",
|
| 188 |
+
"observable_addrs : [0]\n",
|
| 189 |
+
"instructions (5):\n",
|
| 190 |
+
" 0: {'op': 'CONST', 'dest': 'v0', 'src1': 10, 'src2': None}\n",
|
| 191 |
+
" 1: {'op': 'CONST', 'dest': 'v1', 'src1': 2, 'src2': None}\n",
|
| 192 |
+
" 2: {'op': 'ADD', 'dest': 'v2', 'src1': 'v1', 'src2': 'v1'}\n",
|
| 193 |
+
" 3: {'op': 'MUL', 'dest': 'v3', 'src1': 'v2', 'src2': 'v1'}\n",
|
| 194 |
+
" 4: {'op': 'STORE', 'dest': 'addr0', 'src1': 'v3', 'src2': None}\n",
|
| 195 |
+
"\n",
|
| 196 |
+
"=== seed=7 ===\n",
|
| 197 |
+
"initial_vars : {'addr0': 0}\n",
|
| 198 |
+
"initial_mem : {}\n",
|
| 199 |
+
"observable_addrs : [0]\n",
|
| 200 |
+
"instructions (6):\n",
|
| 201 |
+
" 0: {'op': 'CONST', 'dest': 'v0', 'src1': 3, 'src2': None}\n",
|
| 202 |
+
" 1: {'op': 'CONST', 'dest': 'v1', 'src1': 7, 'src2': None}\n",
|
| 203 |
+
" 2: {'op': 'CONST', 'dest': 'v2', 'src1': 1, 'src2': None}\n",
|
| 204 |
+
" 3: {'op': 'ADD', 'dest': 'v3', 'src1': 'v1', 'src2': 'v2'}\n",
|
| 205 |
+
" 4: {'op': 'CONST', 'dest': 'v4', 'src1': 9, 'src2': None}\n",
|
| 206 |
+
" 5: {'op': 'STORE', 'dest': 'addr0', 'src1': 'v3', 'src2': None}\n",
|
| 207 |
+
"\n",
|
| 208 |
+
"=== seed=99 ===\n",
|
| 209 |
+
"initial_vars : {'addr0': 0}\n",
|
| 210 |
+
"initial_mem : {}\n",
|
| 211 |
+
"observable_addrs : [0]\n",
|
| 212 |
+
"instructions (5):\n",
|
| 213 |
+
" 0: {'op': 'CONST', 'dest': 'v0', 'src1': 7, 'src2': None}\n",
|
| 214 |
+
" 1: {'op': 'CONST', 'dest': 'v1', 'src1': 4, 'src2': None}\n",
|
| 215 |
+
" 2: {'op': 'CONST', 'dest': 'v2', 'src1': 10, 'src2': None}\n",
|
| 216 |
+
" 3: {'op': 'ADD', 'dest': 'v3', 'src1': 'v0', 'src2': 'v0'}\n",
|
| 217 |
+
" 4: {'op': 'STORE', 'dest': 'addr0', 'src1': 'v3', 'src2': None}\n"
|
| 218 |
+
]
|
| 219 |
+
}
|
| 220 |
+
]
|
| 221 |
+
},
|
| 222 |
+
{
|
| 223 |
+
"cell_type": "code",
|
| 224 |
+
"source": [
|
| 225 |
+
"def _resolve_operand(operand, known_constants):\n",
|
| 226 |
+
" \"\"\"\n",
|
| 227 |
+
" Given a TAC operand (string variable name or int literal),\n",
|
| 228 |
+
" return its concrete integer value if known, or None if unknown.\n",
|
| 229 |
+
" \"\"\"\n",
|
| 230 |
+
" if isinstance(operand, int):\n",
|
| 231 |
+
" return operand\n",
|
| 232 |
+
" if isinstance(operand, str) and operand in known_constants:\n",
|
| 233 |
+
" return known_constants[operand]\n",
|
| 234 |
+
" return None\n",
|
| 235 |
+
"\n",
|
| 236 |
+
"\n",
|
| 237 |
+
"def _compute(op, v1, v2):\n",
|
| 238 |
+
" \"\"\"Compute the result of a binary arithmetic op on two known integers.\"\"\"\n",
|
| 239 |
+
" if op == \"ADD\": return v1 + v2\n",
|
| 240 |
+
" if op == \"SUB\": return v1 - v2\n",
|
| 241 |
+
" if op == \"MUL\": return v1 * v2\n",
|
| 242 |
+
" if op == \"DIV\": return v1 // v2 # floor division (locked semantics)\n",
|
| 243 |
+
" raise ValueError(f\"_compute called with non-arithmetic op: {op}\")\n",
|
| 244 |
+
"\n",
|
| 245 |
+
"\n",
|
| 246 |
+
"def constant_folding(program):\n",
|
| 247 |
+
" \"\"\"\n",
|
| 248 |
+
" Forward pass that folds constant arithmetic into CONST ops, and\n",
|
| 249 |
+
" propagates known constants into instruction operands.\n",
|
| 250 |
+
"\n",
|
| 251 |
+
" Behavior:\n",
|
| 252 |
+
" - If both sources of an arithmetic op resolve to known integers,\n",
|
| 253 |
+
" replaces the instruction with a CONST holding the computed result.\n",
|
| 254 |
+
" - If only one source is known, still substitutes that known value\n",
|
| 255 |
+
" into the instruction (constant propagation), enabling downstream\n",
|
| 256 |
+
" passes (e.g., peephole) to recognize patterns like ADD-with-0 or MUL-by-1.\n",
|
| 257 |
+
" - Refuses to fold DIV by zero (Design Assumption #3).\n",
|
| 258 |
+
" - Always returns fresh dicts; never aliases input instructions.\n",
|
| 259 |
+
"\n",
|
| 260 |
+
" Args:\n",
|
| 261 |
+
" program: list of TAC instruction dicts (per locked schema).\n",
|
| 262 |
+
"\n",
|
| 263 |
+
" Returns:\n",
|
| 264 |
+
" new list of TAC instruction dicts. Always semantics-preserving.\n",
|
| 265 |
+
" Never raises, never returns None.\n",
|
| 266 |
+
" \"\"\"\n",
|
| 267 |
+
" known_constants = {}\n",
|
| 268 |
+
" new_program = []\n",
|
| 269 |
+
"\n",
|
| 270 |
+
" for instr in program:\n",
|
| 271 |
+
" # Always work on a copy — never alias input dicts\n",
|
| 272 |
+
" instr = instr.copy()\n",
|
| 273 |
+
" op = instr[\"op\"]\n",
|
| 274 |
+
" dest = instr[\"dest\"]\n",
|
| 275 |
+
"\n",
|
| 276 |
+
" if op == \"CONST\":\n",
|
| 277 |
+
" known_constants[dest] = instr[\"src1\"]\n",
|
| 278 |
+
" new_program.append(instr)\n",
|
| 279 |
+
"\n",
|
| 280 |
+
" elif op in (\"ADD\", \"SUB\", \"MUL\", \"DIV\"):\n",
|
| 281 |
+
" # === Constant propagation: substitute known constants into operands ===\n",
|
| 282 |
+
" if isinstance(instr[\"src1\"], str) and instr[\"src1\"] in known_constants:\n",
|
| 283 |
+
" instr[\"src1\"] = known_constants[instr[\"src1\"]]\n",
|
| 284 |
+
" if isinstance(instr[\"src2\"], str) and instr[\"src2\"] in known_constants:\n",
|
| 285 |
+
" instr[\"src2\"] = known_constants[instr[\"src2\"]]\n",
|
| 286 |
+
"\n",
|
| 287 |
+
" # === Try to fold ===\n",
|
| 288 |
+
" v1 = _resolve_operand(instr[\"src1\"], known_constants)\n",
|
| 289 |
+
" v2 = _resolve_operand(instr[\"src2\"], known_constants)\n",
|
| 290 |
+
"\n",
|
| 291 |
+
" if v1 is not None and v2 is not None:\n",
|
| 292 |
+
" # Both operands are known integers\n",
|
| 293 |
+
" if op == \"DIV\" and v2 == 0:\n",
|
| 294 |
+
" # Refuse to fold DIV by zero\n",
|
| 295 |
+
" new_program.append(instr)\n",
|
| 296 |
+
" known_constants.pop(dest, None)\n",
|
| 297 |
+
" else:\n",
|
| 298 |
+
" # Fold: replace with CONST\n",
|
| 299 |
+
" result = _compute(op, v1, v2)\n",
|
| 300 |
+
" new_program.append({\n",
|
| 301 |
+
" \"op\": \"CONST\",\n",
|
| 302 |
+
" \"dest\": dest,\n",
|
| 303 |
+
" \"src1\": result,\n",
|
| 304 |
+
" \"src2\": None,\n",
|
| 305 |
+
" })\n",
|
| 306 |
+
" known_constants[dest] = result\n",
|
| 307 |
+
" else:\n",
|
| 308 |
+
" # Can't fold (at least one operand unknown).\n",
|
| 309 |
+
" # Instruction may still have been mutated by propagation above.\n",
|
| 310 |
+
" new_program.append(instr)\n",
|
| 311 |
+
" known_constants.pop(dest, None)\n",
|
| 312 |
+
"\n",
|
| 313 |
+
" elif op == \"LOAD\":\n",
|
| 314 |
+
" # Memory reads aren't statically resolvable\n",
|
| 315 |
+
" new_program.append(instr)\n",
|
| 316 |
+
" known_constants.pop(dest, None)\n",
|
| 317 |
+
"\n",
|
| 318 |
+
" elif op in (\"STORE\", \"NOP\"):\n",
|
| 319 |
+
" # No dest tracking needed.\n",
|
| 320 |
+
" # Note: we COULD propagate src1 into a STORE for downstream readability,\n",
|
| 321 |
+
" # but the cycle cost is unchanged and the executor handles vars fine.\n",
|
| 322 |
+
" # Leave STORE alone — keeps the code minimal.\n",
|
| 323 |
+
" new_program.append(instr)\n",
|
| 324 |
+
"\n",
|
| 325 |
+
" else:\n",
|
| 326 |
+
" # Unknown op — defensive pass-through\n",
|
| 327 |
+
" new_program.append(instr)\n",
|
| 328 |
+
"\n",
|
| 329 |
+
" return new_program"
|
| 330 |
+
],
|
| 331 |
+
"metadata": {
|
| 332 |
+
"id": "CGk8Fz4CZA3t"
|
| 333 |
+
},
|
| 334 |
+
"execution_count": 5,
|
| 335 |
+
"outputs": []
|
| 336 |
+
},
|
| 337 |
+
{
|
| 338 |
+
"cell_type": "code",
|
| 339 |
+
"source": [
|
| 340 |
+
"# === Sanity tests for constant_folding ===\n",
|
| 341 |
+
"\n",
|
| 342 |
+
"# Test 1: simple fold — ADD of two CONSTs\n",
|
| 343 |
+
"test1 = [\n",
|
| 344 |
+
" {\"op\": \"CONST\", \"dest\": \"a\", \"src1\": 3, \"src2\": None},\n",
|
| 345 |
+
" {\"op\": \"CONST\", \"dest\": \"b\", \"src1\": 5, \"src2\": None},\n",
|
| 346 |
+
" {\"op\": \"ADD\", \"dest\": \"c\", \"src1\": \"a\", \"src2\": \"b\"},\n",
|
| 347 |
+
" {\"op\": \"STORE\", \"dest\": \"addr0\", \"src1\": \"c\", \"src2\": None},\n",
|
| 348 |
+
"]\n",
|
| 349 |
+
"result1 = constant_folding(test1)\n",
|
| 350 |
+
"print(\"Test 1 (simple ADD fold):\")\n",
|
| 351 |
+
"for i, instr in enumerate(result1): print(f\" {i}: {instr}\")\n",
|
| 352 |
+
"# Expected: instruction 2 becomes CONST c = 8\n",
|
| 353 |
+
"\n",
|
| 354 |
+
"# Test 2: chained fold — second op uses first op's folded result\n",
|
| 355 |
+
"test2 = [\n",
|
| 356 |
+
" {\"op\": \"CONST\", \"dest\": \"a\", \"src1\": 3, \"src2\": None},\n",
|
| 357 |
+
" {\"op\": \"CONST\", \"dest\": \"b\", \"src1\": 5, \"src2\": None},\n",
|
| 358 |
+
" {\"op\": \"ADD\", \"dest\": \"c\", \"src1\": \"a\", \"src2\": \"b\"}, # → 8\n",
|
| 359 |
+
" {\"op\": \"MUL\", \"dest\": \"d\", \"src1\": \"c\", \"src2\": \"b\"}, # → 8 * 5 = 40\n",
|
| 360 |
+
" {\"op\": \"STORE\", \"dest\": \"addr0\", \"src1\": \"d\", \"src2\": None},\n",
|
| 361 |
+
"]\n",
|
| 362 |
+
"result2 = constant_folding(test2)\n",
|
| 363 |
+
"print(\"\\nTest 2 (chained fold):\")\n",
|
| 364 |
+
"for i, instr in enumerate(result2): print(f\" {i}: {instr}\")\n",
|
| 365 |
+
"# Expected: c becomes CONST 8, d becomes CONST 40\n",
|
| 366 |
+
"\n",
|
| 367 |
+
"# Test 3: DIV by zero refused\n",
|
| 368 |
+
"test3 = [\n",
|
| 369 |
+
" {\"op\": \"CONST\", \"dest\": \"a\", \"src1\": 10, \"src2\": None},\n",
|
| 370 |
+
" {\"op\": \"CONST\", \"dest\": \"b\", \"src1\": 0, \"src2\": None},\n",
|
| 371 |
+
" {\"op\": \"DIV\", \"dest\": \"c\", \"src1\": \"a\", \"src2\": \"b\"}, # 10/0 — must NOT fold\n",
|
| 372 |
+
" {\"op\": \"STORE\", \"dest\": \"addr0\", \"src1\": \"c\", \"src2\": None},\n",
|
| 373 |
+
"]\n",
|
| 374 |
+
"result3 = constant_folding(test3)\n",
|
| 375 |
+
"print(\"\\nTest 3 (DIV by zero refused):\")\n",
|
| 376 |
+
"for i, instr in enumerate(result3): print(f\" {i}: {instr}\")\n",
|
| 377 |
+
"# Expected: instruction 2 unchanged (still DIV, not CONST)\n",
|
| 378 |
+
"\n",
|
| 379 |
+
"# Test 4: unknown source can't fold\n",
|
| 380 |
+
"test4 = [\n",
|
| 381 |
+
" {\"op\": \"CONST\", \"dest\": \"a\", \"src1\": 3, \"src2\": None},\n",
|
| 382 |
+
" {\"op\": \"LOAD\", \"dest\": \"b\", \"src1\": \"addr0\", \"src2\": None}, # b is unknown\n",
|
| 383 |
+
" {\"op\": \"ADD\", \"dest\": \"c\", \"src1\": \"a\", \"src2\": \"b\"}, # can't fold\n",
|
| 384 |
+
" {\"op\": \"STORE\", \"dest\": \"addr0\", \"src1\": \"c\", \"src2\": None},\n",
|
| 385 |
+
"]\n",
|
| 386 |
+
"result4 = constant_folding(test4)\n",
|
| 387 |
+
"print(\"\\nTest 4 (LOAD makes b unknown, ADD not folded):\")\n",
|
| 388 |
+
"for i, instr in enumerate(result4): print(f\" {i}: {instr}\")\n",
|
| 389 |
+
"# Expected: instruction 2 unchanged (still ADD)\n",
|
| 390 |
+
"\n",
|
| 391 |
+
"# Test 5: idempotence — running CF on a generated program\n",
|
| 392 |
+
"random.seed(42)\n",
|
| 393 |
+
"prog = generate_level_1()\n",
|
| 394 |
+
"folded = constant_folding(prog[\"instructions\"])\n",
|
| 395 |
+
"print(\"\\nTest 5 (CF on generated Level 1 program, seed=42):\")\n",
|
| 396 |
+
"print(\"Before:\")\n",
|
| 397 |
+
"for i, instr in enumerate(prog[\"instructions\"]): print(f\" {i}: {instr}\")\n",
|
| 398 |
+
"print(\"After:\")\n",
|
| 399 |
+
"for i, instr in enumerate(folded): print(f\" {i}: {instr}\")\n",
|
| 400 |
+
"\n",
|
| 401 |
+
"# Test 6: constant propagation — only one source is known\n",
|
| 402 |
+
"test6 = [\n",
|
| 403 |
+
" {\"op\": \"CONST\", \"dest\": \"a\", \"src1\": 0, \"src2\": None},\n",
|
| 404 |
+
" {\"op\": \"LOAD\", \"dest\": \"b\", \"src1\": \"addr0\", \"src2\": None}, # b unknown\n",
|
| 405 |
+
" {\"op\": \"ADD\", \"dest\": \"c\", \"src1\": \"a\", \"src2\": \"b\"}, # a known (=0), b unknown\n",
|
| 406 |
+
" {\"op\": \"STORE\", \"dest\": \"addr0\", \"src1\": \"c\", \"src2\": None},\n",
|
| 407 |
+
"]\n",
|
| 408 |
+
"result6 = constant_folding(test6)\n",
|
| 409 |
+
"print(\"\\nTest 6 (propagation: a=0 substituted into ADD even though b unknown):\")\n",
|
| 410 |
+
"for i, instr in enumerate(result6): print(f\" {i}: {instr}\")\n",
|
| 411 |
+
"# Expected: instruction 2 is still ADD (can't fold — b unknown), but src1 is now literal 0, not 'a'\n",
|
| 412 |
+
"# This sets up peephole to recognize \"ADD with 0\" later"
|
| 413 |
+
],
|
| 414 |
+
"metadata": {
|
| 415 |
+
"colab": {
|
| 416 |
+
"base_uri": "https://localhost:8080/"
|
| 417 |
+
},
|
| 418 |
+
"id": "WLKSTVa8fRF8",
|
| 419 |
+
"outputId": "e9480e1e-5d59-4afe-a884-bae0fa4a2192"
|
| 420 |
+
},
|
| 421 |
+
"execution_count": 6,
|
| 422 |
+
"outputs": [
|
| 423 |
+
{
|
| 424 |
+
"output_type": "stream",
|
| 425 |
+
"name": "stdout",
|
| 426 |
+
"text": [
|
| 427 |
+
"Test 1 (simple ADD fold):\n",
|
| 428 |
+
" 0: {'op': 'CONST', 'dest': 'a', 'src1': 3, 'src2': None}\n",
|
| 429 |
+
" 1: {'op': 'CONST', 'dest': 'b', 'src1': 5, 'src2': None}\n",
|
| 430 |
+
" 2: {'op': 'CONST', 'dest': 'c', 'src1': 8, 'src2': None}\n",
|
| 431 |
+
" 3: {'op': 'STORE', 'dest': 'addr0', 'src1': 'c', 'src2': None}\n",
|
| 432 |
+
"\n",
|
| 433 |
+
"Test 2 (chained fold):\n",
|
| 434 |
+
" 0: {'op': 'CONST', 'dest': 'a', 'src1': 3, 'src2': None}\n",
|
| 435 |
+
" 1: {'op': 'CONST', 'dest': 'b', 'src1': 5, 'src2': None}\n",
|
| 436 |
+
" 2: {'op': 'CONST', 'dest': 'c', 'src1': 8, 'src2': None}\n",
|
| 437 |
+
" 3: {'op': 'CONST', 'dest': 'd', 'src1': 40, 'src2': None}\n",
|
| 438 |
+
" 4: {'op': 'STORE', 'dest': 'addr0', 'src1': 'd', 'src2': None}\n",
|
| 439 |
+
"\n",
|
| 440 |
+
"Test 3 (DIV by zero refused):\n",
|
| 441 |
+
" 0: {'op': 'CONST', 'dest': 'a', 'src1': 10, 'src2': None}\n",
|
| 442 |
+
" 1: {'op': 'CONST', 'dest': 'b', 'src1': 0, 'src2': None}\n",
|
| 443 |
+
" 2: {'op': 'DIV', 'dest': 'c', 'src1': 10, 'src2': 0}\n",
|
| 444 |
+
" 3: {'op': 'STORE', 'dest': 'addr0', 'src1': 'c', 'src2': None}\n",
|
| 445 |
+
"\n",
|
| 446 |
+
"Test 4 (LOAD makes b unknown, ADD not folded):\n",
|
| 447 |
+
" 0: {'op': 'CONST', 'dest': 'a', 'src1': 3, 'src2': None}\n",
|
| 448 |
+
" 1: {'op': 'LOAD', 'dest': 'b', 'src1': 'addr0', 'src2': None}\n",
|
| 449 |
+
" 2: {'op': 'ADD', 'dest': 'c', 'src1': 3, 'src2': 'b'}\n",
|
| 450 |
+
" 3: {'op': 'STORE', 'dest': 'addr0', 'src1': 'c', 'src2': None}\n",
|
| 451 |
+
"\n",
|
| 452 |
+
"Test 5 (CF on generated Level 1 program, seed=42):\n",
|
| 453 |
+
"Before:\n",
|
| 454 |
+
" 0: {'op': 'CONST', 'dest': 'v0', 'src1': 1, 'src2': None}\n",
|
| 455 |
+
" 1: {'op': 'CONST', 'dest': 'v1', 'src1': 5, 'src2': None}\n",
|
| 456 |
+
" 2: {'op': 'ADD', 'dest': 'v2', 'src1': 'v0', 'src2': 'v0'}\n",
|
| 457 |
+
" 3: {'op': 'STORE', 'dest': 'addr0', 'src1': 'v2', 'src2': None}\n",
|
| 458 |
+
"After:\n",
|
| 459 |
+
" 0: {'op': 'CONST', 'dest': 'v0', 'src1': 1, 'src2': None}\n",
|
| 460 |
+
" 1: {'op': 'CONST', 'dest': 'v1', 'src1': 5, 'src2': None}\n",
|
| 461 |
+
" 2: {'op': 'CONST', 'dest': 'v2', 'src1': 2, 'src2': None}\n",
|
| 462 |
+
" 3: {'op': 'STORE', 'dest': 'addr0', 'src1': 'v2', 'src2': None}\n",
|
| 463 |
+
"\n",
|
| 464 |
+
"Test 6 (propagation: a=0 substituted into ADD even though b unknown):\n",
|
| 465 |
+
" 0: {'op': 'CONST', 'dest': 'a', 'src1': 0, 'src2': None}\n",
|
| 466 |
+
" 1: {'op': 'LOAD', 'dest': 'b', 'src1': 'addr0', 'src2': None}\n",
|
| 467 |
+
" 2: {'op': 'ADD', 'dest': 'c', 'src1': 0, 'src2': 'b'}\n",
|
| 468 |
+
" 3: {'op': 'STORE', 'dest': 'addr0', 'src1': 'c', 'src2': None}\n"
|
| 469 |
+
]
|
| 470 |
+
}
|
| 471 |
+
]
|
| 472 |
+
},
|
| 473 |
+
{
|
| 474 |
+
"cell_type": "code",
|
| 475 |
+
"source": [
|
| 476 |
+
"def dead_code_elimination(program):\n",
|
| 477 |
+
" \"\"\"\n",
|
| 478 |
+
" Backward liveness analysis. Replace dead instructions with NOPs,\n",
|
| 479 |
+
" then strip NOPs.\n",
|
| 480 |
+
"\n",
|
| 481 |
+
" Rules:\n",
|
| 482 |
+
" - STOREs are always live (define program output).\n",
|
| 483 |
+
" - Address variables used in STORE/LOAD are always live.\n",
|
| 484 |
+
" - Any instruction whose dest is never read later is dead.\n",
|
| 485 |
+
"\n",
|
| 486 |
+
" Returns a fresh list of instruction dicts. Never raises, never returns None.\n",
|
| 487 |
+
" \"\"\"\n",
|
| 488 |
+
" # Walk backward, build new program in reverse, then re-reverse at the end\n",
|
| 489 |
+
" live = set()\n",
|
| 490 |
+
" new_program_reversed = []\n",
|
| 491 |
+
"\n",
|
| 492 |
+
" for instr in reversed(program):\n",
|
| 493 |
+
" instr = instr.copy() # never alias input\n",
|
| 494 |
+
" op = instr[\"op\"]\n",
|
| 495 |
+
" dest = instr[\"dest\"]\n",
|
| 496 |
+
" src1 = instr[\"src1\"]\n",
|
| 497 |
+
" src2 = instr[\"src2\"]\n",
|
| 498 |
+
"\n",
|
| 499 |
+
" if op == \"STORE\":\n",
|
| 500 |
+
" # STOREs are always kept; mark sources live\n",
|
| 501 |
+
" if isinstance(src1, str): live.add(src1)\n",
|
| 502 |
+
" if isinstance(dest, str): live.add(dest) # address variable\n",
|
| 503 |
+
" new_program_reversed.append(instr)\n",
|
| 504 |
+
"\n",
|
| 505 |
+
" elif op == \"NOP\":\n",
|
| 506 |
+
" # Pass through; will be stripped at the end\n",
|
| 507 |
+
" new_program_reversed.append(instr)\n",
|
| 508 |
+
"\n",
|
| 509 |
+
" elif op in (\"CONST\", \"ADD\", \"SUB\", \"MUL\", \"DIV\", \"LOAD\"):\n",
|
| 510 |
+
" if dest in live:\n",
|
| 511 |
+
" # Live instruction — keep it, mark its sources live\n",
|
| 512 |
+
" live.discard(dest)\n",
|
| 513 |
+
" if isinstance(src1, str): live.add(src1)\n",
|
| 514 |
+
" if isinstance(src2, str): live.add(src2)\n",
|
| 515 |
+
" new_program_reversed.append(instr)\n",
|
| 516 |
+
" else:\n",
|
| 517 |
+
" # Dead — replace with NOP\n",
|
| 518 |
+
" new_program_reversed.append({\n",
|
| 519 |
+
" \"op\": \"NOP\", \"dest\": None, \"src1\": None, \"src2\": None,\n",
|
| 520 |
+
" })\n",
|
| 521 |
+
"\n",
|
| 522 |
+
" else:\n",
|
| 523 |
+
" # Unknown op — defensive pass-through\n",
|
| 524 |
+
" new_program_reversed.append(instr)\n",
|
| 525 |
+
"\n",
|
| 526 |
+
" # Reverse back to forward order, then strip NOPs\n",
|
| 527 |
+
" new_program = list(reversed(new_program_reversed))\n",
|
| 528 |
+
" new_program = [instr for instr in new_program if instr[\"op\"] != \"NOP\"]\n",
|
| 529 |
+
"\n",
|
| 530 |
+
" return new_program"
|
| 531 |
+
],
|
| 532 |
+
"metadata": {
|
| 533 |
+
"id": "5vo-KgaffTFR"
|
| 534 |
+
},
|
| 535 |
+
"execution_count": 12,
|
| 536 |
+
"outputs": []
|
| 537 |
+
},
|
| 538 |
+
{
|
| 539 |
+
"cell_type": "code",
|
| 540 |
+
"source": [
|
| 541 |
+
"# === Sanity tests for dead_code_elimination ===\n",
|
| 542 |
+
"\n",
|
| 543 |
+
"# Test 1: simple dead variable\n",
|
| 544 |
+
"test1 = [\n",
|
| 545 |
+
" {\"op\": \"CONST\", \"dest\": \"a\", \"src1\": 5, \"src2\": None},\n",
|
| 546 |
+
" {\"op\": \"CONST\", \"dest\": \"dead\", \"src1\": 99, \"src2\": None}, # never used\n",
|
| 547 |
+
" {\"op\": \"STORE\", \"dest\": \"addr0\", \"src1\": \"a\", \"src2\": None},\n",
|
| 548 |
+
"]\n",
|
| 549 |
+
"result1 = dead_code_elimination(test1)\n",
|
| 550 |
+
"print(\"Test 1 (kill unused CONST):\")\n",
|
| 551 |
+
"for i, instr in enumerate(result1): print(f\" {i}: {instr}\")\n",
|
| 552 |
+
"# Expected: 'dead' instruction removed, 2 instructions remain\n",
|
| 553 |
+
"\n",
|
| 554 |
+
"# Test 2: chain of dead computation\n",
|
| 555 |
+
"test2 = [\n",
|
| 556 |
+
" {\"op\": \"CONST\", \"dest\": \"a\", \"src1\": 5, \"src2\": None},\n",
|
| 557 |
+
" {\"op\": \"CONST\", \"dest\": \"b\", \"src1\": 3, \"src2\": None},\n",
|
| 558 |
+
" {\"op\": \"ADD\", \"dest\": \"x\", \"src1\": \"a\", \"src2\": \"b\"}, # x never used → dead\n",
|
| 559 |
+
" {\"op\": \"CONST\", \"dest\": \"c\", \"src1\": 7, \"src2\": None},\n",
|
| 560 |
+
" {\"op\": \"STORE\", \"dest\": \"addr0\", \"src1\": \"c\", \"src2\": None},\n",
|
| 561 |
+
"]\n",
|
| 562 |
+
"result2 = dead_code_elimination(test2)\n",
|
| 563 |
+
"print(\"\\nTest 2 (kill unused ADD and its feeders... but only if feeders are also unused):\")\n",
|
| 564 |
+
"for i, instr in enumerate(result2): print(f\" {i}: {instr}\")\n",
|
| 565 |
+
"# Expected: x's ADD killed. a and b also killed (only fed into x, which is dead).\n",
|
| 566 |
+
"# Final: just CONST c=7, STORE.\n",
|
| 567 |
+
"\n",
|
| 568 |
+
"# Test 3: STORE always preserved\n",
|
| 569 |
+
"test3 = [\n",
|
| 570 |
+
" {\"op\": \"CONST\", \"dest\": \"a\", \"src1\": 5, \"src2\": None},\n",
|
| 571 |
+
" {\"op\": \"STORE\", \"dest\": \"addr0\", \"src1\": \"a\", \"src2\": None},\n",
|
| 572 |
+
"]\n",
|
| 573 |
+
"result3 = dead_code_elimination(test3)\n",
|
| 574 |
+
"print(\"\\nTest 3 (STORE preserved, feeder kept live):\")\n",
|
| 575 |
+
"for i, instr in enumerate(result3): print(f\" {i}: {instr}\")\n",
|
| 576 |
+
"# Expected: both instructions unchanged\n",
|
| 577 |
+
"\n",
|
| 578 |
+
"# Test 4: variable used by STORE is live, even if defined far above\n",
|
| 579 |
+
"test4 = [\n",
|
| 580 |
+
" {\"op\": \"CONST\", \"dest\": \"a\", \"src1\": 5, \"src2\": None}, # used by STORE → live\n",
|
| 581 |
+
" {\"op\": \"CONST\", \"dest\": \"b\", \"src1\": 9, \"src2\": None}, # never used → dead\n",
|
| 582 |
+
" {\"op\": \"CONST\", \"dest\": \"c\", \"src1\": 1, \"src2\": None}, # never used → dead\n",
|
| 583 |
+
" {\"op\": \"STORE\", \"dest\": \"addr0\", \"src1\": \"a\", \"src2\": None},\n",
|
| 584 |
+
"]\n",
|
| 585 |
+
"result4 = dead_code_elimination(test4)\n",
|
| 586 |
+
"print(\"\\nTest 4 (only 'a' is live, b/c killed):\")\n",
|
| 587 |
+
"for i, instr in enumerate(result4): print(f\" {i}: {instr}\")\n",
|
| 588 |
+
"# Expected: a's CONST + STORE remain. b, c stripped.\n",
|
| 589 |
+
"\n",
|
| 590 |
+
"# Test 5: combine CF + DCE on a generated program\n",
|
| 591 |
+
"random.seed(42)\n",
|
| 592 |
+
"prog = generate_level_1()\n",
|
| 593 |
+
"print(\"\\nTest 5 (CF then DCE on seed=42):\")\n",
|
| 594 |
+
"print(\"Original:\")\n",
|
| 595 |
+
"for i, instr in enumerate(prog[\"instructions\"]): print(f\" {i}: {instr}\")\n",
|
| 596 |
+
"\n",
|
| 597 |
+
"after_cf = constant_folding(prog[\"instructions\"])\n",
|
| 598 |
+
"print(\"After CF:\")\n",
|
| 599 |
+
"for i, instr in enumerate(after_cf): print(f\" {i}: {instr}\")\n",
|
| 600 |
+
"\n",
|
| 601 |
+
"after_dce = dead_code_elimination(after_cf)\n",
|
| 602 |
+
"print(\"After CF + DCE:\")\n",
|
| 603 |
+
"for i, instr in enumerate(after_dce): print(f\" {i}: {instr}\")\n",
|
| 604 |
+
"# Expected: original 4 instructions become much shorter — dead 'v1' eliminated, v2 folded\n",
|
| 605 |
+
"\n",
|
| 606 |
+
"# Test 6: idempotence — running DCE twice gives same result\n",
|
| 607 |
+
"test6 = [\n",
|
| 608 |
+
" {\"op\": \"CONST\", \"dest\": \"a\", \"src1\": 5, \"src2\": None},\n",
|
| 609 |
+
" {\"op\": \"CONST\", \"dest\": \"dead\", \"src1\": 99, \"src2\": None},\n",
|
| 610 |
+
" {\"op\": \"STORE\", \"dest\": \"addr0\", \"src1\": \"a\", \"src2\": None},\n",
|
| 611 |
+
"]\n",
|
| 612 |
+
"once = dead_code_elimination(test6)\n",
|
| 613 |
+
"twice = dead_code_elimination(once)\n",
|
| 614 |
+
"print(\"\\nTest 6 (idempotence):\", \"PASS\" if once == twice else \"FAIL\")"
|
| 615 |
+
],
|
| 616 |
+
"metadata": {
|
| 617 |
+
"colab": {
|
| 618 |
+
"base_uri": "https://localhost:8080/"
|
| 619 |
+
},
|
| 620 |
+
"id": "vv8QRFRlvzdq",
|
| 621 |
+
"outputId": "e1d35874-ff1a-4dec-f43f-e22867dd3911"
|
| 622 |
+
},
|
| 623 |
+
"execution_count": 8,
|
| 624 |
+
"outputs": [
|
| 625 |
+
{
|
| 626 |
+
"output_type": "stream",
|
| 627 |
+
"name": "stdout",
|
| 628 |
+
"text": [
|
| 629 |
+
"Test 1 (kill unused CONST):\n",
|
| 630 |
+
" 0: {'op': 'CONST', 'dest': 'a', 'src1': 5, 'src2': None}\n",
|
| 631 |
+
" 1: {'op': 'STORE', 'dest': 'addr0', 'src1': 'a', 'src2': None}\n",
|
| 632 |
+
"\n",
|
| 633 |
+
"Test 2 (kill unused ADD and its feeders... but only if feeders are also unused):\n",
|
| 634 |
+
" 0: {'op': 'CONST', 'dest': 'c', 'src1': 7, 'src2': None}\n",
|
| 635 |
+
" 1: {'op': 'STORE', 'dest': 'addr0', 'src1': 'c', 'src2': None}\n",
|
| 636 |
+
"\n",
|
| 637 |
+
"Test 3 (STORE preserved, feeder kept live):\n",
|
| 638 |
+
" 0: {'op': 'CONST', 'dest': 'a', 'src1': 5, 'src2': None}\n",
|
| 639 |
+
" 1: {'op': 'STORE', 'dest': 'addr0', 'src1': 'a', 'src2': None}\n",
|
| 640 |
+
"\n",
|
| 641 |
+
"Test 4 (only 'a' is live, b/c killed):\n",
|
| 642 |
+
" 0: {'op': 'CONST', 'dest': 'a', 'src1': 5, 'src2': None}\n",
|
| 643 |
+
" 1: {'op': 'STORE', 'dest': 'addr0', 'src1': 'a', 'src2': None}\n",
|
| 644 |
+
"\n",
|
| 645 |
+
"Test 5 (CF then DCE on seed=42):\n",
|
| 646 |
+
"Original:\n",
|
| 647 |
+
" 0: {'op': 'CONST', 'dest': 'v0', 'src1': 1, 'src2': None}\n",
|
| 648 |
+
" 1: {'op': 'CONST', 'dest': 'v1', 'src1': 5, 'src2': None}\n",
|
| 649 |
+
" 2: {'op': 'ADD', 'dest': 'v2', 'src1': 'v0', 'src2': 'v0'}\n",
|
| 650 |
+
" 3: {'op': 'STORE', 'dest': 'addr0', 'src1': 'v2', 'src2': None}\n",
|
| 651 |
+
"After CF:\n",
|
| 652 |
+
" 0: {'op': 'CONST', 'dest': 'v0', 'src1': 1, 'src2': None}\n",
|
| 653 |
+
" 1: {'op': 'CONST', 'dest': 'v1', 'src1': 5, 'src2': None}\n",
|
| 654 |
+
" 2: {'op': 'CONST', 'dest': 'v2', 'src1': 2, 'src2': None}\n",
|
| 655 |
+
" 3: {'op': 'STORE', 'dest': 'addr0', 'src1': 'v2', 'src2': None}\n",
|
| 656 |
+
"After CF + DCE:\n",
|
| 657 |
+
" 0: {'op': 'CONST', 'dest': 'v2', 'src1': 2, 'src2': None}\n",
|
| 658 |
+
" 1: {'op': 'STORE', 'dest': 'addr0', 'src1': 'v2', 'src2': None}\n",
|
| 659 |
+
"\n",
|
| 660 |
+
"Test 6 (idempotence): PASS\n"
|
| 661 |
+
]
|
| 662 |
+
}
|
| 663 |
+
]
|
| 664 |
+
},
|
| 665 |
+
{
|
| 666 |
+
"cell_type": "code",
|
| 667 |
+
"source": [
|
| 668 |
+
"def peephole_optimization(program):\n",
|
| 669 |
+
" \"\"\"\n",
|
| 670 |
+
" Single-instruction peephole rewrites. Replaces expensive ops with\n",
|
| 671 |
+
" cheaper equivalents when operands hit special values (0, 1, 2, self).\n",
|
| 672 |
+
"\n",
|
| 673 |
+
" Patterns (all preserve semantics):\n",
|
| 674 |
+
" MUL by 2 -> ADD x + x\n",
|
| 675 |
+
" MUL by 1 -> CONST (the other operand)\n",
|
| 676 |
+
" MUL by 0 -> CONST 0\n",
|
| 677 |
+
" ADD with 0 -> CONST (the other operand)\n",
|
| 678 |
+
" SUB x - x -> CONST 0\n",
|
| 679 |
+
" DIV by 1 -> CONST (the dividend)\n",
|
| 680 |
+
"\n",
|
| 681 |
+
" Notes:\n",
|
| 682 |
+
" - Operates on individual instructions; no cross-instruction state.\n",
|
| 683 |
+
" - Relies on constant_folding having propagated literal values into operands.\n",
|
| 684 |
+
" - Returns fresh instruction dicts; never aliases inputs.\n",
|
| 685 |
+
"\n",
|
| 686 |
+
" Returns a new list. Never raises, never returns None.\n",
|
| 687 |
+
" \"\"\"\n",
|
| 688 |
+
" new_program = []\n",
|
| 689 |
+
"\n",
|
| 690 |
+
" for instr in program:\n",
|
| 691 |
+
" instr = instr.copy()\n",
|
| 692 |
+
" op = instr[\"op\"]\n",
|
| 693 |
+
" dest = instr[\"dest\"]\n",
|
| 694 |
+
" src1 = instr[\"src1\"]\n",
|
| 695 |
+
" src2 = instr[\"src2\"]\n",
|
| 696 |
+
"\n",
|
| 697 |
+
" # === MUL patterns ===\n",
|
| 698 |
+
" if op == \"MUL\":\n",
|
| 699 |
+
" # MUL by 0 -> CONST 0\n",
|
| 700 |
+
" if src1 == 0 or src2 == 0:\n",
|
| 701 |
+
" new_program.append({\"op\": \"CONST\", \"dest\": dest, \"src1\": 0, \"src2\": None})\n",
|
| 702 |
+
" continue\n",
|
| 703 |
+
" # MUL by 1 -> CONST (other operand) if the other operand is a literal,\n",
|
| 704 |
+
" # otherwise leave alone (we don't want to introduce a useless\n",
|
| 705 |
+
" # \"CONST dest = some_var_name\" — that's not a valid CONST).\n",
|
| 706 |
+
" if src1 == 1 and isinstance(src2, int):\n",
|
| 707 |
+
" new_program.append({\"op\": \"CONST\", \"dest\": dest, \"src1\": src2, \"src2\": None})\n",
|
| 708 |
+
" continue\n",
|
| 709 |
+
" if src2 == 1 and isinstance(src1, int):\n",
|
| 710 |
+
" new_program.append({\"op\": \"CONST\", \"dest\": dest, \"src1\": src1, \"src2\": None})\n",
|
| 711 |
+
" continue\n",
|
| 712 |
+
" # MUL by 2 -> ADD x + x (when one operand is literal 2 and the other is a var)\n",
|
| 713 |
+
" if src1 == 2 and isinstance(src2, str):\n",
|
| 714 |
+
" new_program.append({\"op\": \"ADD\", \"dest\": dest, \"src1\": src2, \"src2\": src2})\n",
|
| 715 |
+
" continue\n",
|
| 716 |
+
" if src2 == 2 and isinstance(src1, str):\n",
|
| 717 |
+
" new_program.append({\"op\": \"ADD\", \"dest\": dest, \"src1\": src1, \"src2\": src1})\n",
|
| 718 |
+
" continue\n",
|
| 719 |
+
" # No pattern matched — keep as is\n",
|
| 720 |
+
" new_program.append(instr)\n",
|
| 721 |
+
"\n",
|
| 722 |
+
" # === ADD patterns ===\n",
|
| 723 |
+
" elif op == \"ADD\":\n",
|
| 724 |
+
" # ADD with 0 -> CONST (other operand) if the other operand is a literal\n",
|
| 725 |
+
" if src1 == 0 and isinstance(src2, int):\n",
|
| 726 |
+
" new_program.append({\"op\": \"CONST\", \"dest\": dest, \"src1\": src2, \"src2\": None})\n",
|
| 727 |
+
" continue\n",
|
| 728 |
+
" if src2 == 0 and isinstance(src1, int):\n",
|
| 729 |
+
" new_program.append({\"op\": \"CONST\", \"dest\": dest, \"src1\": src1, \"src2\": None})\n",
|
| 730 |
+
" continue\n",
|
| 731 |
+
" # If \"ADD x + 0\" where x is a variable, we'd want a copy — but our schema\n",
|
| 732 |
+
" # has no MOV/COPY op. Leave alone; CF + DCE handle the rest in practice.\n",
|
| 733 |
+
" new_program.append(instr)\n",
|
| 734 |
+
"\n",
|
| 735 |
+
" # === SUB patterns ===\n",
|
| 736 |
+
" elif op == \"SUB\":\n",
|
| 737 |
+
" # SUB x - x -> CONST 0 (only if both sources are the same string variable)\n",
|
| 738 |
+
" if isinstance(src1, str) and isinstance(src2, str) and src1 == src2:\n",
|
| 739 |
+
" new_program.append({\"op\": \"CONST\", \"dest\": dest, \"src1\": 0, \"src2\": None})\n",
|
| 740 |
+
" continue\n",
|
| 741 |
+
" # SUB x - 0 with literal 0 on src2 — we'd want a copy; skip (no COPY op)\n",
|
| 742 |
+
" new_program.append(instr)\n",
|
| 743 |
+
"\n",
|
| 744 |
+
" # === DIV patterns ===\n",
|
| 745 |
+
" elif op == \"DIV\":\n",
|
| 746 |
+
" # DIV by 1 -> CONST (dividend) if dividend is a literal\n",
|
| 747 |
+
" if src2 == 1 and isinstance(src1, int):\n",
|
| 748 |
+
" new_program.append({\"op\": \"CONST\", \"dest\": dest, \"src1\": src1, \"src2\": None})\n",
|
| 749 |
+
" continue\n",
|
| 750 |
+
" new_program.append(instr)\n",
|
| 751 |
+
"\n",
|
| 752 |
+
" else:\n",
|
| 753 |
+
" # CONST, LOAD, STORE, NOP — pass through\n",
|
| 754 |
+
" new_program.append(instr)\n",
|
| 755 |
+
"\n",
|
| 756 |
+
" return new_program"
|
| 757 |
+
],
|
| 758 |
+
"metadata": {
|
| 759 |
+
"id": "IaczDNeLv5PW"
|
| 760 |
+
},
|
| 761 |
+
"execution_count": 9,
|
| 762 |
+
"outputs": []
|
| 763 |
+
},
|
| 764 |
+
{
|
| 765 |
+
"cell_type": "code",
|
| 766 |
+
"source": [
|
| 767 |
+
"# === Sanity tests for peephole_optimization ===\n",
|
| 768 |
+
"\n",
|
| 769 |
+
"# Test 1: MUL by 2 -> ADD x+x\n",
|
| 770 |
+
"test1 = [\n",
|
| 771 |
+
" {\"op\": \"LOAD\", \"dest\": \"y\", \"src1\": \"addr0\", \"src2\": None},\n",
|
| 772 |
+
" {\"op\": \"MUL\", \"dest\": \"x\", \"src1\": \"y\", \"src2\": 2},\n",
|
| 773 |
+
" {\"op\": \"STORE\", \"dest\": \"addr0\", \"src1\": \"x\", \"src2\": None},\n",
|
| 774 |
+
"]\n",
|
| 775 |
+
"result1 = peephole_optimization(test1)\n",
|
| 776 |
+
"print(\"Test 1 (MUL by 2 -> ADD self):\")\n",
|
| 777 |
+
"for i, instr in enumerate(result1): print(f\" {i}: {instr}\")\n",
|
| 778 |
+
"# Expected: instruction 1 becomes ADD y+y\n",
|
| 779 |
+
"\n",
|
| 780 |
+
"# Test 2: MUL by 0 -> CONST 0\n",
|
| 781 |
+
"test2 = [\n",
|
| 782 |
+
" {\"op\": \"LOAD\", \"dest\": \"y\", \"src1\": \"addr0\", \"src2\": None},\n",
|
| 783 |
+
" {\"op\": \"MUL\", \"dest\": \"x\", \"src1\": \"y\", \"src2\": 0},\n",
|
| 784 |
+
" {\"op\": \"STORE\", \"dest\": \"addr0\", \"src1\": \"x\", \"src2\": None},\n",
|
| 785 |
+
"]\n",
|
| 786 |
+
"result2 = peephole_optimization(test2)\n",
|
| 787 |
+
"print(\"\\nTest 2 (MUL by 0 -> CONST 0):\")\n",
|
| 788 |
+
"for i, instr in enumerate(result2): print(f\" {i}: {instr}\")\n",
|
| 789 |
+
"# Expected: instruction 1 becomes CONST x = 0\n",
|
| 790 |
+
"\n",
|
| 791 |
+
"# Test 3: MUL by 1 (both literals)\n",
|
| 792 |
+
"test3 = [\n",
|
| 793 |
+
" {\"op\": \"MUL\", \"dest\": \"x\", \"src1\": 7, \"src2\": 1},\n",
|
| 794 |
+
" {\"op\": \"STORE\", \"dest\": \"addr0\", \"src1\": \"x\", \"src2\": None},\n",
|
| 795 |
+
"]\n",
|
| 796 |
+
"result3 = peephole_optimization(test3)\n",
|
| 797 |
+
"print(\"\\nTest 3 (MUL 7*1 -> CONST 7):\")\n",
|
| 798 |
+
"for i, instr in enumerate(result3): print(f\" {i}: {instr}\")\n",
|
| 799 |
+
"# Expected: instruction 0 becomes CONST x = 7\n",
|
| 800 |
+
"\n",
|
| 801 |
+
"# Test 4: SUB x - x -> CONST 0\n",
|
| 802 |
+
"test4 = [\n",
|
| 803 |
+
" {\"op\": \"LOAD\", \"dest\": \"y\", \"src1\": \"addr0\", \"src2\": None},\n",
|
| 804 |
+
" {\"op\": \"SUB\", \"dest\": \"x\", \"src1\": \"y\", \"src2\": \"y\"},\n",
|
| 805 |
+
" {\"op\": \"STORE\", \"dest\": \"addr0\", \"src1\": \"x\", \"src2\": None},\n",
|
| 806 |
+
"]\n",
|
| 807 |
+
"result4 = peephole_optimization(test4)\n",
|
| 808 |
+
"print(\"\\nTest 4 (SUB y-y -> CONST 0):\")\n",
|
| 809 |
+
"for i, instr in enumerate(result4): print(f\" {i}: {instr}\")\n",
|
| 810 |
+
"# Expected: instruction 1 becomes CONST x = 0\n",
|
| 811 |
+
"\n",
|
| 812 |
+
"# Test 5: ADD with 0\n",
|
| 813 |
+
"test5 = [\n",
|
| 814 |
+
" {\"op\": \"ADD\", \"dest\": \"x\", \"src1\": 0, \"src2\": 5},\n",
|
| 815 |
+
" {\"op\": \"STORE\", \"dest\": \"addr0\", \"src1\": \"x\", \"src2\": None},\n",
|
| 816 |
+
"]\n",
|
| 817 |
+
"result5 = peephole_optimization(test5)\n",
|
| 818 |
+
"print(\"\\nTest 5 (ADD 0+5 -> CONST 5):\")\n",
|
| 819 |
+
"for i, instr in enumerate(result5): print(f\" {i}: {instr}\")\n",
|
| 820 |
+
"# Expected: instruction 0 becomes CONST x = 5\n",
|
| 821 |
+
"\n",
|
| 822 |
+
"# Test 6: full pipeline — CF then peephole on a generated program\n",
|
| 823 |
+
"random.seed(7)\n",
|
| 824 |
+
"prog = generate_level_1()\n",
|
| 825 |
+
"print(\"\\nTest 6 (CF then peephole on seed=7):\")\n",
|
| 826 |
+
"print(\"Original:\")\n",
|
| 827 |
+
"for i, instr in enumerate(prog[\"instructions\"]): print(f\" {i}: {instr}\")\n",
|
| 828 |
+
"after_cf = constant_folding(prog[\"instructions\"])\n",
|
| 829 |
+
"print(\"After CF:\")\n",
|
| 830 |
+
"for i, instr in enumerate(after_cf): print(f\" {i}: {instr}\")\n",
|
| 831 |
+
"after_peep = peephole_optimization(after_cf)\n",
|
| 832 |
+
"print(\"After CF + peephole:\")\n",
|
| 833 |
+
"for i, instr in enumerate(after_peep): print(f\" {i}: {instr}\")\n",
|
| 834 |
+
"\n",
|
| 835 |
+
"# Test 7: idempotence\n",
|
| 836 |
+
"test7 = [\n",
|
| 837 |
+
" {\"op\": \"MUL\", \"dest\": \"x\", \"src1\": \"y\", \"src2\": 2},\n",
|
| 838 |
+
" {\"op\": \"STORE\", \"dest\": \"addr0\", \"src1\": \"x\", \"src2\": None},\n",
|
| 839 |
+
"]\n",
|
| 840 |
+
"once = peephole_optimization(test7)\n",
|
| 841 |
+
"twice = peephole_optimization(once)\n",
|
| 842 |
+
"print(\"\\nTest 7 (idempotence):\", \"PASS\" if once == twice else \"FAIL\")\n",
|
| 843 |
+
"\n",
|
| 844 |
+
"# Test 8: no false fires — vanilla program shouldn't get rewritten\n",
|
| 845 |
+
"test8 = [\n",
|
| 846 |
+
" {\"op\": \"LOAD\", \"dest\": \"y\", \"src1\": \"addr0\", \"src2\": None},\n",
|
| 847 |
+
" {\"op\": \"MUL\", \"dest\": \"x\", \"src1\": \"y\", \"src2\": \"y\"}, # y*y, no peephole pattern\n",
|
| 848 |
+
" {\"op\": \"STORE\", \"dest\": \"addr0\", \"src1\": \"x\", \"src2\": None},\n",
|
| 849 |
+
"]\n",
|
| 850 |
+
"result8 = peephole_optimization(test8)\n",
|
| 851 |
+
"print(\"\\nTest 8 (no false rewrite on y*y):\")\n",
|
| 852 |
+
"for i, instr in enumerate(result8): print(f\" {i}: {instr}\")\n",
|
| 853 |
+
"# Expected: instruction 1 unchanged (still MUL y*y)"
|
| 854 |
+
],
|
| 855 |
+
"metadata": {
|
| 856 |
+
"colab": {
|
| 857 |
+
"base_uri": "https://localhost:8080/"
|
| 858 |
+
},
|
| 859 |
+
"id": "DWDUZUQvwXye",
|
| 860 |
+
"outputId": "1d1e327f-5f26-43e4-a6ac-f7a286da3781"
|
| 861 |
+
},
|
| 862 |
+
"execution_count": 10,
|
| 863 |
+
"outputs": [
|
| 864 |
+
{
|
| 865 |
+
"output_type": "stream",
|
| 866 |
+
"name": "stdout",
|
| 867 |
+
"text": [
|
| 868 |
+
"Test 1 (MUL by 2 -> ADD self):\n",
|
| 869 |
+
" 0: {'op': 'LOAD', 'dest': 'y', 'src1': 'addr0', 'src2': None}\n",
|
| 870 |
+
" 1: {'op': 'ADD', 'dest': 'x', 'src1': 'y', 'src2': 'y'}\n",
|
| 871 |
+
" 2: {'op': 'STORE', 'dest': 'addr0', 'src1': 'x', 'src2': None}\n",
|
| 872 |
+
"\n",
|
| 873 |
+
"Test 2 (MUL by 0 -> CONST 0):\n",
|
| 874 |
+
" 0: {'op': 'LOAD', 'dest': 'y', 'src1': 'addr0', 'src2': None}\n",
|
| 875 |
+
" 1: {'op': 'CONST', 'dest': 'x', 'src1': 0, 'src2': None}\n",
|
| 876 |
+
" 2: {'op': 'STORE', 'dest': 'addr0', 'src1': 'x', 'src2': None}\n",
|
| 877 |
+
"\n",
|
| 878 |
+
"Test 3 (MUL 7*1 -> CONST 7):\n",
|
| 879 |
+
" 0: {'op': 'CONST', 'dest': 'x', 'src1': 7, 'src2': None}\n",
|
| 880 |
+
" 1: {'op': 'STORE', 'dest': 'addr0', 'src1': 'x', 'src2': None}\n",
|
| 881 |
+
"\n",
|
| 882 |
+
"Test 4 (SUB y-y -> CONST 0):\n",
|
| 883 |
+
" 0: {'op': 'LOAD', 'dest': 'y', 'src1': 'addr0', 'src2': None}\n",
|
| 884 |
+
" 1: {'op': 'CONST', 'dest': 'x', 'src1': 0, 'src2': None}\n",
|
| 885 |
+
" 2: {'op': 'STORE', 'dest': 'addr0', 'src1': 'x', 'src2': None}\n",
|
| 886 |
+
"\n",
|
| 887 |
+
"Test 5 (ADD 0+5 -> CONST 5):\n",
|
| 888 |
+
" 0: {'op': 'CONST', 'dest': 'x', 'src1': 5, 'src2': None}\n",
|
| 889 |
+
" 1: {'op': 'STORE', 'dest': 'addr0', 'src1': 'x', 'src2': None}\n",
|
| 890 |
+
"\n",
|
| 891 |
+
"Test 6 (CF then peephole on seed=7):\n",
|
| 892 |
+
"Original:\n",
|
| 893 |
+
" 0: {'op': 'CONST', 'dest': 'v0', 'src1': 3, 'src2': None}\n",
|
| 894 |
+
" 1: {'op': 'CONST', 'dest': 'v1', 'src1': 7, 'src2': None}\n",
|
| 895 |
+
" 2: {'op': 'CONST', 'dest': 'v2', 'src1': 1, 'src2': None}\n",
|
| 896 |
+
" 3: {'op': 'ADD', 'dest': 'v3', 'src1': 'v1', 'src2': 'v2'}\n",
|
| 897 |
+
" 4: {'op': 'CONST', 'dest': 'v4', 'src1': 9, 'src2': None}\n",
|
| 898 |
+
" 5: {'op': 'STORE', 'dest': 'addr0', 'src1': 'v3', 'src2': None}\n",
|
| 899 |
+
"After CF:\n",
|
| 900 |
+
" 0: {'op': 'CONST', 'dest': 'v0', 'src1': 3, 'src2': None}\n",
|
| 901 |
+
" 1: {'op': 'CONST', 'dest': 'v1', 'src1': 7, 'src2': None}\n",
|
| 902 |
+
" 2: {'op': 'CONST', 'dest': 'v2', 'src1': 1, 'src2': None}\n",
|
| 903 |
+
" 3: {'op': 'CONST', 'dest': 'v3', 'src1': 8, 'src2': None}\n",
|
| 904 |
+
" 4: {'op': 'CONST', 'dest': 'v4', 'src1': 9, 'src2': None}\n",
|
| 905 |
+
" 5: {'op': 'STORE', 'dest': 'addr0', 'src1': 'v3', 'src2': None}\n",
|
| 906 |
+
"After CF + peephole:\n",
|
| 907 |
+
" 0: {'op': 'CONST', 'dest': 'v0', 'src1': 3, 'src2': None}\n",
|
| 908 |
+
" 1: {'op': 'CONST', 'dest': 'v1', 'src1': 7, 'src2': None}\n",
|
| 909 |
+
" 2: {'op': 'CONST', 'dest': 'v2', 'src1': 1, 'src2': None}\n",
|
| 910 |
+
" 3: {'op': 'CONST', 'dest': 'v3', 'src1': 8, 'src2': None}\n",
|
| 911 |
+
" 4: {'op': 'CONST', 'dest': 'v4', 'src1': 9, 'src2': None}\n",
|
| 912 |
+
" 5: {'op': 'STORE', 'dest': 'addr0', 'src1': 'v3', 'src2': None}\n",
|
| 913 |
+
"\n",
|
| 914 |
+
"Test 7 (idempotence): PASS\n",
|
| 915 |
+
"\n",
|
| 916 |
+
"Test 8 (no false rewrite on y*y):\n",
|
| 917 |
+
" 0: {'op': 'LOAD', 'dest': 'y', 'src1': 'addr0', 'src2': None}\n",
|
| 918 |
+
" 1: {'op': 'MUL', 'dest': 'x', 'src1': 'y', 'src2': 'y'}\n",
|
| 919 |
+
" 2: {'op': 'STORE', 'dest': 'addr0', 'src1': 'x', 'src2': None}\n"
|
| 920 |
+
]
|
| 921 |
+
}
|
| 922 |
+
]
|
| 923 |
+
},
|
| 924 |
+
{
|
| 925 |
+
"cell_type": "code",
|
| 926 |
+
"source": [
|
| 927 |
+
"def dump_ir(program_data):\n",
|
| 928 |
+
" \"\"\"\n",
|
| 929 |
+
" Convert a TAC program into a readable string for the LLM observation.\n",
|
| 930 |
+
"\n",
|
| 931 |
+
" Format:\n",
|
| 932 |
+
" // OBSERVABLE OUT: mem[0], mem[1]\n",
|
| 933 |
+
" 0: v0 = 3 # CONST [1 cycle]\n",
|
| 934 |
+
" 1: v1 = 5 # CONST [1 cycle]\n",
|
| 935 |
+
" 2: v2 = v0 + v1 # ADD [1 cycle]\n",
|
| 936 |
+
" 3: mem[addr0] = v2 # STORE [4 cycles]\n",
|
| 937 |
+
" // TOTAL: 7 cycles\n",
|
| 938 |
+
"\n",
|
| 939 |
+
" Args:\n",
|
| 940 |
+
" program_data: dict with keys 'instructions' and 'observable_addrs',\n",
|
| 941 |
+
" OR a raw list of instructions (legacy).\n",
|
| 942 |
+
"\n",
|
| 943 |
+
" Returns:\n",
|
| 944 |
+
" A multi-line string suitable for inclusion in an LLM prompt.\n",
|
| 945 |
+
" \"\"\"\n",
|
| 946 |
+
" # Accept both forms — full program dict or raw instruction list\n",
|
| 947 |
+
" if isinstance(program_data, dict):\n",
|
| 948 |
+
" instructions = program_data[\"instructions\"]\n",
|
| 949 |
+
" observable_addrs = program_data.get(\"observable_addrs\", [])\n",
|
| 950 |
+
" else:\n",
|
| 951 |
+
" instructions = program_data\n",
|
| 952 |
+
" observable_addrs = []\n",
|
| 953 |
+
"\n",
|
| 954 |
+
" lines = []\n",
|
| 955 |
+
"\n",
|
| 956 |
+
" # Header: observable outputs\n",
|
| 957 |
+
" if observable_addrs:\n",
|
| 958 |
+
" addr_str = \", \".join(f\"mem[{a}]\" for a in observable_addrs)\n",
|
| 959 |
+
" lines.append(f\"// OBSERVABLE OUT: {addr_str}\")\n",
|
| 960 |
+
"\n",
|
| 961 |
+
" # Body: each instruction in human-readable form\n",
|
| 962 |
+
" total_cycles = 0\n",
|
| 963 |
+
" for i, instr in enumerate(instructions):\n",
|
| 964 |
+
" op = instr[\"op\"]\n",
|
| 965 |
+
" dest = instr[\"dest\"]\n",
|
| 966 |
+
" src1 = instr[\"src1\"]\n",
|
| 967 |
+
" src2 = instr[\"src2\"]\n",
|
| 968 |
+
" cost = CYCLE_COSTS.get(op, 0)\n",
|
| 969 |
+
" total_cycles += cost\n",
|
| 970 |
+
"\n",
|
| 971 |
+
" # Render the instruction body\n",
|
| 972 |
+
" if op == \"CONST\":\n",
|
| 973 |
+
" body = f\"{dest} = {src1}\"\n",
|
| 974 |
+
" elif op == \"ADD\":\n",
|
| 975 |
+
" body = f\"{dest} = {src1} + {src2}\"\n",
|
| 976 |
+
" elif op == \"SUB\":\n",
|
| 977 |
+
" body = f\"{dest} = {src1} - {src2}\"\n",
|
| 978 |
+
" elif op == \"MUL\":\n",
|
| 979 |
+
" body = f\"{dest} = {src1} * {src2}\"\n",
|
| 980 |
+
" elif op == \"DIV\":\n",
|
| 981 |
+
" body = f\"{dest} = {src1} // {src2}\"\n",
|
| 982 |
+
" elif op == \"LOAD\":\n",
|
| 983 |
+
" body = f\"{dest} = mem[{src1}]\"\n",
|
| 984 |
+
" elif op == \"STORE\":\n",
|
| 985 |
+
" body = f\"mem[{dest}] = {src1}\"\n",
|
| 986 |
+
" elif op == \"NOP\":\n",
|
| 987 |
+
" body = \"nop\"\n",
|
| 988 |
+
" else:\n",
|
| 989 |
+
" body = f\"<unknown op: {op}>\"\n",
|
| 990 |
+
"\n",
|
| 991 |
+
" cost_label = f\"{cost} cycle\" if cost == 1 else f\"{cost} cycles\"\n",
|
| 992 |
+
" lines.append(f\"{i}: {body:<35} # {op:<6} [{cost_label}]\")\n",
|
| 993 |
+
"\n",
|
| 994 |
+
" # Footer: total cycles\n",
|
| 995 |
+
" lines.append(f\"// TOTAL: {total_cycles} cycles\")\n",
|
| 996 |
+
"\n",
|
| 997 |
+
" return \"\\n\".join(lines)"
|
| 998 |
+
],
|
| 999 |
+
"metadata": {
|
| 1000 |
+
"id": "yB_yMNOZwaPT"
|
| 1001 |
+
},
|
| 1002 |
+
"execution_count": 13,
|
| 1003 |
+
"outputs": []
|
| 1004 |
+
},
|
| 1005 |
+
{
|
| 1006 |
+
"cell_type": "code",
|
| 1007 |
+
"source": [
|
| 1008 |
+
"# === Sanity tests for state translator ===\n",
|
| 1009 |
+
"\n",
|
| 1010 |
+
"# Test 1: full generated program\n",
|
| 1011 |
+
"random.seed(1)\n",
|
| 1012 |
+
"prog = generate_level_1()\n",
|
| 1013 |
+
"print(\"Test 1 (raw seed=1 program):\")\n",
|
| 1014 |
+
"print(dump_ir(prog))\n",
|
| 1015 |
+
"\n",
|
| 1016 |
+
"# Test 2: after CF\n",
|
| 1017 |
+
"print(\"\\nTest 2 (after CF):\")\n",
|
| 1018 |
+
"folded = constant_folding(prog[\"instructions\"])\n",
|
| 1019 |
+
"prog_after_cf = {**prog, \"instructions\": folded}\n",
|
| 1020 |
+
"print(dump_ir(prog_after_cf))\n",
|
| 1021 |
+
"\n",
|
| 1022 |
+
"# Test 3: after CF + DCE — should show fewer instructions, lower total\n",
|
| 1023 |
+
"print(\"\\nTest 3 (after CF + DCE):\")\n",
|
| 1024 |
+
"optimized = dead_code_elimination(folded)\n",
|
| 1025 |
+
"prog_optimized = {**prog, \"instructions\": optimized}\n",
|
| 1026 |
+
"print(dump_ir(prog_optimized))\n",
|
| 1027 |
+
"\n",
|
| 1028 |
+
"# Test 4: every op type at least once\n",
|
| 1029 |
+
"test4_instructions = [\n",
|
| 1030 |
+
" {\"op\": \"CONST\", \"dest\": \"a\", \"src1\": 5, \"src2\": None},\n",
|
| 1031 |
+
" {\"op\": \"ADD\", \"dest\": \"b\", \"src1\": \"a\", \"src2\": 3},\n",
|
| 1032 |
+
" {\"op\": \"SUB\", \"dest\": \"c\", \"src1\": \"b\", \"src2\": \"a\"},\n",
|
| 1033 |
+
" {\"op\": \"MUL\", \"dest\": \"d\", \"src1\": \"c\", \"src2\": 2},\n",
|
| 1034 |
+
" {\"op\": \"DIV\", \"dest\": \"e\", \"src1\": \"d\", \"src2\": 4},\n",
|
| 1035 |
+
" {\"op\": \"LOAD\", \"dest\": \"f\", \"src1\": \"addr0\", \"src2\": None},\n",
|
| 1036 |
+
" {\"op\": \"STORE\", \"dest\": \"addr0\", \"src1\": \"e\", \"src2\": None},\n",
|
| 1037 |
+
" {\"op\": \"NOP\", \"dest\": None, \"src1\": None, \"src2\": None},\n",
|
| 1038 |
+
"]\n",
|
| 1039 |
+
"test4_data = {\n",
|
| 1040 |
+
" \"instructions\": test4_instructions,\n",
|
| 1041 |
+
" \"observable_addrs\": [0, 1],\n",
|
| 1042 |
+
"}\n",
|
| 1043 |
+
"print(\"\\nTest 4 (every op type):\")\n",
|
| 1044 |
+
"print(dump_ir(test4_data))\n",
|
| 1045 |
+
"\n",
|
| 1046 |
+
"# Test 5: empty program shouldn't crash\n",
|
| 1047 |
+
"print(\"\\nTest 5 (empty program):\")\n",
|
| 1048 |
+
"print(dump_ir({\"instructions\": [], \"observable_addrs\": [0]}))"
|
| 1049 |
+
],
|
| 1050 |
+
"metadata": {
|
| 1051 |
+
"colab": {
|
| 1052 |
+
"base_uri": "https://localhost:8080/"
|
| 1053 |
+
},
|
| 1054 |
+
"id": "SUqa7odp3wB_",
|
| 1055 |
+
"outputId": "c3f44089-213a-4a69-f29f-42b755ed142a"
|
| 1056 |
+
},
|
| 1057 |
+
"execution_count": 14,
|
| 1058 |
+
"outputs": [
|
| 1059 |
+
{
|
| 1060 |
+
"output_type": "stream",
|
| 1061 |
+
"name": "stdout",
|
| 1062 |
+
"text": [
|
| 1063 |
+
"Test 1 (raw seed=1 program):\n",
|
| 1064 |
+
"// OBSERVABLE OUT: mem[0]\n",
|
| 1065 |
+
"0: v0 = 10 # CONST [1 cycle]\n",
|
| 1066 |
+
"1: v1 = 2 # CONST [1 cycle]\n",
|
| 1067 |
+
"2: v2 = v1 + v1 # ADD [1 cycle]\n",
|
| 1068 |
+
"3: v3 = v2 * v1 # MUL [3 cycles]\n",
|
| 1069 |
+
"4: mem[addr0] = v3 # STORE [4 cycles]\n",
|
| 1070 |
+
"// TOTAL: 10 cycles\n",
|
| 1071 |
+
"\n",
|
| 1072 |
+
"Test 2 (after CF):\n",
|
| 1073 |
+
"// OBSERVABLE OUT: mem[0]\n",
|
| 1074 |
+
"0: v0 = 10 # CONST [1 cycle]\n",
|
| 1075 |
+
"1: v1 = 2 # CONST [1 cycle]\n",
|
| 1076 |
+
"2: v2 = 4 # CONST [1 cycle]\n",
|
| 1077 |
+
"3: v3 = 8 # CONST [1 cycle]\n",
|
| 1078 |
+
"4: mem[addr0] = v3 # STORE [4 cycles]\n",
|
| 1079 |
+
"// TOTAL: 8 cycles\n",
|
| 1080 |
+
"\n",
|
| 1081 |
+
"Test 3 (after CF + DCE):\n",
|
| 1082 |
+
"// OBSERVABLE OUT: mem[0]\n",
|
| 1083 |
+
"0: v3 = 8 # CONST [1 cycle]\n",
|
| 1084 |
+
"1: mem[addr0] = v3 # STORE [4 cycles]\n",
|
| 1085 |
+
"// TOTAL: 5 cycles\n",
|
| 1086 |
+
"\n",
|
| 1087 |
+
"Test 4 (every op type):\n",
|
| 1088 |
+
"// OBSERVABLE OUT: mem[0], mem[1]\n",
|
| 1089 |
+
"0: a = 5 # CONST [1 cycle]\n",
|
| 1090 |
+
"1: b = a + 3 # ADD [1 cycle]\n",
|
| 1091 |
+
"2: c = b - a # SUB [1 cycle]\n",
|
| 1092 |
+
"3: d = c * 2 # MUL [3 cycles]\n",
|
| 1093 |
+
"4: e = d // 4 # DIV [5 cycles]\n",
|
| 1094 |
+
"5: f = mem[addr0] # LOAD [4 cycles]\n",
|
| 1095 |
+
"6: mem[addr0] = e # STORE [4 cycles]\n",
|
| 1096 |
+
"7: nop # NOP [0 cycles]\n",
|
| 1097 |
+
"// TOTAL: 19 cycles\n",
|
| 1098 |
+
"\n",
|
| 1099 |
+
"Test 5 (empty program):\n",
|
| 1100 |
+
"// OBSERVABLE OUT: mem[0]\n",
|
| 1101 |
+
"// TOTAL: 0 cycles\n"
|
| 1102 |
+
]
|
| 1103 |
+
}
|
| 1104 |
+
]
|
| 1105 |
+
},
|
| 1106 |
+
{
|
| 1107 |
+
"cell_type": "code",
|
| 1108 |
+
"source": [
|
| 1109 |
+
"def generate_level_2():\n",
|
| 1110 |
+
" \"\"\"\n",
|
| 1111 |
+
" Generate a Level 2 Toy-IR program.\n",
|
| 1112 |
+
"\n",
|
| 1113 |
+
" Characteristics:\n",
|
| 1114 |
+
" - 8-12 instructions\n",
|
| 1115 |
+
" - 3-5 CONSTs (some literal, some used in arithmetic)\n",
|
| 1116 |
+
" - 3-5 arithmetic ops (ADD, SUB, MUL, DIV) with chaining\n",
|
| 1117 |
+
" - 1-3 dead variables (DCE opportunities)\n",
|
| 1118 |
+
" - 1 LOAD from initial memory (introduces non-constant variable)\n",
|
| 1119 |
+
" - 1 STORE at the end (observable output)\n",
|
| 1120 |
+
" - Mix of operands designed to hit peephole patterns occasionally:\n",
|
| 1121 |
+
" - MUL by literal 2 / 1 / 0 (not always — diversity matters)\n",
|
| 1122 |
+
" - DIV by non-zero divisor only (Design Assumption #4)\n",
|
| 1123 |
+
"\n",
|
| 1124 |
+
" Returns:\n",
|
| 1125 |
+
" dict with keys: initial_vars, initial_mem, instructions, observable_addrs\n",
|
| 1126 |
+
" \"\"\"\n",
|
| 1127 |
+
" instructions = []\n",
|
| 1128 |
+
" var_counter = 0\n",
|
| 1129 |
+
"\n",
|
| 1130 |
+
" def new_var():\n",
|
| 1131 |
+
" nonlocal var_counter\n",
|
| 1132 |
+
" name = f\"v{var_counter}\"\n",
|
| 1133 |
+
" var_counter += 1\n",
|
| 1134 |
+
" return name\n",
|
| 1135 |
+
"\n",
|
| 1136 |
+
" available_vars = [] # vars that exist and can be used as sources\n",
|
| 1137 |
+
"\n",
|
| 1138 |
+
" # Step 1: 3-5 CONSTs\n",
|
| 1139 |
+
" num_consts = random.randint(3, 5)\n",
|
| 1140 |
+
" for _ in range(num_consts):\n",
|
| 1141 |
+
" var = new_var()\n",
|
| 1142 |
+
" value = random.randint(1, 10)\n",
|
| 1143 |
+
" instructions.append({\"op\": \"CONST\", \"dest\": var, \"src1\": value, \"src2\": None})\n",
|
| 1144 |
+
" available_vars.append(var)\n",
|
| 1145 |
+
"\n",
|
| 1146 |
+
" # Step 2: 1 LOAD from initial memory\n",
|
| 1147 |
+
" # We'll seed initial_mem with something at a fresh address.\n",
|
| 1148 |
+
" load_addr_var = \"addr_in\"\n",
|
| 1149 |
+
" loaded_var = new_var()\n",
|
| 1150 |
+
" instructions.append({\n",
|
| 1151 |
+
" \"op\": \"LOAD\", \"dest\": loaded_var, \"src1\": load_addr_var, \"src2\": None,\n",
|
| 1152 |
+
" })\n",
|
| 1153 |
+
" available_vars.append(loaded_var)\n",
|
| 1154 |
+
"\n",
|
| 1155 |
+
" # Step 3: 3-5 arithmetic ops, chained\n",
|
| 1156 |
+
" num_arith = random.randint(3, 5)\n",
|
| 1157 |
+
" last_result = None\n",
|
| 1158 |
+
" for _ in range(num_arith):\n",
|
| 1159 |
+
" op = random.choice([\"ADD\", \"SUB\", \"MUL\", \"DIV\"])\n",
|
| 1160 |
+
"\n",
|
| 1161 |
+
" # 30% chance to use a literal as src2 to expose peephole opportunities\n",
|
| 1162 |
+
" # (MUL by 2/1/0, etc.)\n",
|
| 1163 |
+
" use_literal_src2 = random.random() < 0.3\n",
|
| 1164 |
+
"\n",
|
| 1165 |
+
" src1 = random.choice(available_vars)\n",
|
| 1166 |
+
" if use_literal_src2:\n",
|
| 1167 |
+
" # For DIV, never emit literal 0 (Design Assumption #4)\n",
|
| 1168 |
+
" if op == \"DIV\":\n",
|
| 1169 |
+
" src2 = random.choice([1, 2, 3, 4]) # safe non-zero divisors\n",
|
| 1170 |
+
" else:\n",
|
| 1171 |
+
" src2 = random.choice([0, 1, 2, 3]) # 0/1/2 hit peephole patterns\n",
|
| 1172 |
+
" else:\n",
|
| 1173 |
+
" src2 = random.choice(available_vars)\n",
|
| 1174 |
+
" # If we sampled a variable for DIV's src2, we can't be sure it's non-zero\n",
|
| 1175 |
+
" # at runtime. To stay safe per Assumption #4, restrict DIV to literal src2.\n",
|
| 1176 |
+
" if op == \"DIV\":\n",
|
| 1177 |
+
" src2 = random.choice([1, 2, 3, 4])\n",
|
| 1178 |
+
"\n",
|
| 1179 |
+
" dest = new_var()\n",
|
| 1180 |
+
" instructions.append({\"op\": op, \"dest\": dest, \"src1\": src1, \"src2\": src2})\n",
|
| 1181 |
+
" available_vars.append(dest)\n",
|
| 1182 |
+
" last_result = dest\n",
|
| 1183 |
+
"\n",
|
| 1184 |
+
" # Step 4: 1-3 extra dead CONSTs sprinkled in (DCE targets)\n",
|
| 1185 |
+
" num_dead = random.randint(1, 3)\n",
|
| 1186 |
+
" for _ in range(num_dead):\n",
|
| 1187 |
+
" dead_var = new_var()\n",
|
| 1188 |
+
" dead_value = random.randint(1, 20)\n",
|
| 1189 |
+
" # Insert at a random position before the (eventual) STORE\n",
|
| 1190 |
+
" insert_pos = random.randint(0, len(instructions))\n",
|
| 1191 |
+
" instructions.insert(insert_pos, {\n",
|
| 1192 |
+
" \"op\": \"CONST\", \"dest\": dead_var, \"src1\": dead_value, \"src2\": None,\n",
|
| 1193 |
+
" })\n",
|
| 1194 |
+
" # Note: dead_var intentionally never used afterward\n",
|
| 1195 |
+
"\n",
|
| 1196 |
+
" # Step 5: STORE the final result\n",
|
| 1197 |
+
" initial_vars = {\n",
|
| 1198 |
+
" \"addr0\": 0,\n",
|
| 1199 |
+
" \"addr_in\": 1, # address that the LOAD reads from\n",
|
| 1200 |
+
" }\n",
|
| 1201 |
+
" initial_mem = {1: random.randint(1, 20)} # seed mem[1] with a random value\n",
|
| 1202 |
+
"\n",
|
| 1203 |
+
" instructions.append({\n",
|
| 1204 |
+
" \"op\": \"STORE\", \"dest\": \"addr0\", \"src1\": last_result, \"src2\": None,\n",
|
| 1205 |
+
" })\n",
|
| 1206 |
+
"\n",
|
| 1207 |
+
" return {\n",
|
| 1208 |
+
" \"initial_vars\": initial_vars,\n",
|
| 1209 |
+
" \"initial_mem\": initial_mem,\n",
|
| 1210 |
+
" \"instructions\": instructions,\n",
|
| 1211 |
+
" \"observable_addrs\": [0],\n",
|
| 1212 |
+
" }"
|
| 1213 |
+
],
|
| 1214 |
+
"metadata": {
|
| 1215 |
+
"id": "OL6QbL0H3xuP"
|
| 1216 |
+
},
|
| 1217 |
+
"execution_count": 15,
|
| 1218 |
+
"outputs": []
|
| 1219 |
+
},
|
| 1220 |
+
{
|
| 1221 |
+
"cell_type": "code",
|
| 1222 |
+
"source": [
|
| 1223 |
+
"# === Sanity tests for Level 2 generator ===\n",
|
| 1224 |
+
"\n",
|
| 1225 |
+
"# Test 1: spot-check a few seeds\n",
|
| 1226 |
+
"for seed in [42, 1, 7]:\n",
|
| 1227 |
+
" random.seed(seed)\n",
|
| 1228 |
+
" prog = generate_level_2()\n",
|
| 1229 |
+
" print(f\"\\n=== Level 2, seed={seed} ===\")\n",
|
| 1230 |
+
" print(dump_ir(prog))\n",
|
| 1231 |
+
" print(f\"initial_vars: {prog['initial_vars']}\")\n",
|
| 1232 |
+
" print(f\"initial_mem : {prog['initial_mem']}\")\n",
|
| 1233 |
+
"\n",
|
| 1234 |
+
"# Test 2: full pipeline (CF + DCE + peephole) on a Level 2 program\n",
|
| 1235 |
+
"print(\"\\n=== Full optimization pipeline on Level 2 (seed=42) ===\")\n",
|
| 1236 |
+
"random.seed(42)\n",
|
| 1237 |
+
"prog = generate_level_2()\n",
|
| 1238 |
+
"print(\"ORIGINAL:\")\n",
|
| 1239 |
+
"print(dump_ir(prog))\n",
|
| 1240 |
+
"\n",
|
| 1241 |
+
"after_cf = constant_folding(prog[\"instructions\"])\n",
|
| 1242 |
+
"print(\"\\nAFTER CF:\")\n",
|
| 1243 |
+
"print(dump_ir({**prog, \"instructions\": after_cf}))\n",
|
| 1244 |
+
"\n",
|
| 1245 |
+
"after_dce = dead_code_elimination(after_cf)\n",
|
| 1246 |
+
"print(\"\\nAFTER CF + DCE:\")\n",
|
| 1247 |
+
"print(dump_ir({**prog, \"instructions\": after_dce}))\n",
|
| 1248 |
+
"\n",
|
| 1249 |
+
"after_peep = peephole_optimization(after_dce)\n",
|
| 1250 |
+
"print(\"\\nAFTER CF + DCE + PEEPHOLE:\")\n",
|
| 1251 |
+
"print(dump_ir({**prog, \"instructions\": after_peep}))"
|
| 1252 |
+
],
|
| 1253 |
+
"metadata": {
|
| 1254 |
+
"colab": {
|
| 1255 |
+
"base_uri": "https://localhost:8080/"
|
| 1256 |
+
},
|
| 1257 |
+
"id": "D0XjNetU5gIo",
|
| 1258 |
+
"outputId": "1c35cdfc-a95d-48ff-c7ce-c4c236bbbbdb"
|
| 1259 |
+
},
|
| 1260 |
+
"execution_count": 16,
|
| 1261 |
+
"outputs": [
|
| 1262 |
+
{
|
| 1263 |
+
"output_type": "stream",
|
| 1264 |
+
"name": "stdout",
|
| 1265 |
+
"text": [
|
| 1266 |
+
"\n",
|
| 1267 |
+
"=== Level 2, seed=42 ===\n",
|
| 1268 |
+
"// OBSERVABLE OUT: mem[0]\n",
|
| 1269 |
+
"0: v0 = 2 # CONST [1 cycle]\n",
|
| 1270 |
+
"1: v1 = 1 # CONST [1 cycle]\n",
|
| 1271 |
+
"2: v2 = 5 # CONST [1 cycle]\n",
|
| 1272 |
+
"3: v3 = 4 # CONST [1 cycle]\n",
|
| 1273 |
+
"4: v11 = 19 # CONST [1 cycle]\n",
|
| 1274 |
+
"5: v4 = 4 # CONST [1 cycle]\n",
|
| 1275 |
+
"6: v5 = mem[addr_in] # LOAD [4 cycles]\n",
|
| 1276 |
+
"7: v9 = 18 # CONST [1 cycle]\n",
|
| 1277 |
+
"8: v10 = 8 # CONST [1 cycle]\n",
|
| 1278 |
+
"9: v6 = v4 + v0 # ADD [1 cycle]\n",
|
| 1279 |
+
"10: v7 = v0 // 2 # DIV [5 cycles]\n",
|
| 1280 |
+
"11: v8 = v0 - v3 # SUB [1 cycle]\n",
|
| 1281 |
+
"12: mem[addr0] = v8 # STORE [4 cycles]\n",
|
| 1282 |
+
"// TOTAL: 23 cycles\n",
|
| 1283 |
+
"initial_vars: {'addr0': 0, 'addr_in': 1}\n",
|
| 1284 |
+
"initial_mem : {1: 1}\n",
|
| 1285 |
+
"\n",
|
| 1286 |
+
"=== Level 2, seed=1 ===\n",
|
| 1287 |
+
"// OBSERVABLE OUT: mem[0]\n",
|
| 1288 |
+
"0: v0 = 10 # CONST [1 cycle]\n",
|
| 1289 |
+
"1: v7 = 19 # CONST [1 cycle]\n",
|
| 1290 |
+
"2: v1 = 2 # CONST [1 cycle]\n",
|
| 1291 |
+
"3: v2 = 5 # CONST [1 cycle]\n",
|
| 1292 |
+
"4: v3 = mem[addr_in] # LOAD [4 cycles]\n",
|
| 1293 |
+
"5: v4 = v3 // 2 # DIV [5 cycles]\n",
|
| 1294 |
+
"6: v5 = v3 + v3 # ADD [1 cycle]\n",
|
| 1295 |
+
"7: v6 = v2 + v5 # ADD [1 cycle]\n",
|
| 1296 |
+
"8: mem[addr0] = v6 # STORE [4 cycles]\n",
|
| 1297 |
+
"// TOTAL: 19 cycles\n",
|
| 1298 |
+
"initial_vars: {'addr0': 0, 'addr_in': 1}\n",
|
| 1299 |
+
"initial_mem : {1: 11}\n",
|
| 1300 |
+
"\n",
|
| 1301 |
+
"=== Level 2, seed=7 ===\n",
|
| 1302 |
+
"// OBSERVABLE OUT: mem[0]\n",
|
| 1303 |
+
"0: v0 = 3 # CONST [1 cycle]\n",
|
| 1304 |
+
"1: v1 = 7 # CONST [1 cycle]\n",
|
| 1305 |
+
"2: v2 = 1 # CONST [1 cycle]\n",
|
| 1306 |
+
"3: v3 = 2 # CONST [1 cycle]\n",
|
| 1307 |
+
"4: v4 = mem[addr_in] # LOAD [4 cycles]\n",
|
| 1308 |
+
"5: v5 = v0 + v4 # ADD [1 cycle]\n",
|
| 1309 |
+
"6: v6 = v3 - 3 # SUB [1 cycle]\n",
|
| 1310 |
+
"7: v7 = v4 + 3 # ADD [1 cycle]\n",
|
| 1311 |
+
"8: v10 = 2 # CONST [1 cycle]\n",
|
| 1312 |
+
"9: v8 = v1 + v3 # ADD [1 cycle]\n",
|
| 1313 |
+
"10: v9 = v6 + v0 # ADD [1 cycle]\n",
|
| 1314 |
+
"11: mem[addr0] = v9 # STORE [4 cycles]\n",
|
| 1315 |
+
"// TOTAL: 18 cycles\n",
|
| 1316 |
+
"initial_vars: {'addr0': 0, 'addr_in': 1}\n",
|
| 1317 |
+
"initial_mem : {1: 5}\n",
|
| 1318 |
+
"\n",
|
| 1319 |
+
"=== Full optimization pipeline on Level 2 (seed=42) ===\n",
|
| 1320 |
+
"ORIGINAL:\n",
|
| 1321 |
+
"// OBSERVABLE OUT: mem[0]\n",
|
| 1322 |
+
"0: v0 = 2 # CONST [1 cycle]\n",
|
| 1323 |
+
"1: v1 = 1 # CONST [1 cycle]\n",
|
| 1324 |
+
"2: v2 = 5 # CONST [1 cycle]\n",
|
| 1325 |
+
"3: v3 = 4 # CONST [1 cycle]\n",
|
| 1326 |
+
"4: v11 = 19 # CONST [1 cycle]\n",
|
| 1327 |
+
"5: v4 = 4 # CONST [1 cycle]\n",
|
| 1328 |
+
"6: v5 = mem[addr_in] # LOAD [4 cycles]\n",
|
| 1329 |
+
"7: v9 = 18 # CONST [1 cycle]\n",
|
| 1330 |
+
"8: v10 = 8 # CONST [1 cycle]\n",
|
| 1331 |
+
"9: v6 = v4 + v0 # ADD [1 cycle]\n",
|
| 1332 |
+
"10: v7 = v0 // 2 # DIV [5 cycles]\n",
|
| 1333 |
+
"11: v8 = v0 - v3 # SUB [1 cycle]\n",
|
| 1334 |
+
"12: mem[addr0] = v8 # STORE [4 cycles]\n",
|
| 1335 |
+
"// TOTAL: 23 cycles\n",
|
| 1336 |
+
"\n",
|
| 1337 |
+
"AFTER CF:\n",
|
| 1338 |
+
"// OBSERVABLE OUT: mem[0]\n",
|
| 1339 |
+
"0: v0 = 2 # CONST [1 cycle]\n",
|
| 1340 |
+
"1: v1 = 1 # CONST [1 cycle]\n",
|
| 1341 |
+
"2: v2 = 5 # CONST [1 cycle]\n",
|
| 1342 |
+
"3: v3 = 4 # CONST [1 cycle]\n",
|
| 1343 |
+
"4: v11 = 19 # CONST [1 cycle]\n",
|
| 1344 |
+
"5: v4 = 4 # CONST [1 cycle]\n",
|
| 1345 |
+
"6: v5 = mem[addr_in] # LOAD [4 cycles]\n",
|
| 1346 |
+
"7: v9 = 18 # CONST [1 cycle]\n",
|
| 1347 |
+
"8: v10 = 8 # CONST [1 cycle]\n",
|
| 1348 |
+
"9: v6 = 6 # CONST [1 cycle]\n",
|
| 1349 |
+
"10: v7 = 1 # CONST [1 cycle]\n",
|
| 1350 |
+
"11: v8 = -2 # CONST [1 cycle]\n",
|
| 1351 |
+
"12: mem[addr0] = v8 # STORE [4 cycles]\n",
|
| 1352 |
+
"// TOTAL: 19 cycles\n",
|
| 1353 |
+
"\n",
|
| 1354 |
+
"AFTER CF + DCE:\n",
|
| 1355 |
+
"// OBSERVABLE OUT: mem[0]\n",
|
| 1356 |
+
"0: v8 = -2 # CONST [1 cycle]\n",
|
| 1357 |
+
"1: mem[addr0] = v8 # STORE [4 cycles]\n",
|
| 1358 |
+
"// TOTAL: 5 cycles\n",
|
| 1359 |
+
"\n",
|
| 1360 |
+
"AFTER CF + DCE + PEEPHOLE:\n",
|
| 1361 |
+
"// OBSERVABLE OUT: mem[0]\n",
|
| 1362 |
+
"0: v8 = -2 # CONST [1 cycle]\n",
|
| 1363 |
+
"1: mem[addr0] = v8 # STORE [4 cycles]\n",
|
| 1364 |
+
"// TOTAL: 5 cycles\n"
|
| 1365 |
+
]
|
| 1366 |
+
}
|
| 1367 |
+
]
|
| 1368 |
+
},
|
| 1369 |
+
{
|
| 1370 |
+
"cell_type": "code",
|
| 1371 |
+
"source": [],
|
| 1372 |
+
"metadata": {
|
| 1373 |
+
"id": "Vo-9MitD5hwM"
|
| 1374 |
+
},
|
| 1375 |
+
"execution_count": null,
|
| 1376 |
+
"outputs": []
|
| 1377 |
+
}
|
| 1378 |
+
]
|
| 1379 |
+
}
|
space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/metahack1.ipynb
ADDED
|
@@ -0,0 +1,484 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"nbformat": 4,
|
| 3 |
+
"nbformat_minor": 0,
|
| 4 |
+
"metadata": {
|
| 5 |
+
"colab": {
|
| 6 |
+
"provenance": [],
|
| 7 |
+
"gpuType": "T4"
|
| 8 |
+
},
|
| 9 |
+
"kernelspec": {
|
| 10 |
+
"name": "python3",
|
| 11 |
+
"display_name": "Python 3"
|
| 12 |
+
},
|
| 13 |
+
"language_info": {
|
| 14 |
+
"name": "python"
|
| 15 |
+
},
|
| 16 |
+
"accelerator": "GPU"
|
| 17 |
+
},
|
| 18 |
+
"cells": [
|
| 19 |
+
{
|
| 20 |
+
"cell_type": "code",
|
| 21 |
+
"source": [
|
| 22 |
+
"## Design Assumptions (do not violate)\n",
|
| 23 |
+
"\n",
|
| 24 |
+
"# 1. **DCE never eliminates STOREs.** They define program output (final mem state).\n",
|
| 25 |
+
"# 2. **Addresses are distinct by construction.** Generator allocates each address variable to a unique integer; no aliasing.\n",
|
| 26 |
+
"# 3. **CF refuses to fold DIV by zero.** If src2 == 0 on a DIV op, leave instruction unchanged.\n",
|
| 27 |
+
"# 4. **Generator never emits DIV by literal zero.** When DIV is generated, src2 is always a non-zero constant or a variable known to be non-zero.\n",
|
| 28 |
+
"# # 5. **Integer arithmetic only.** No floats anywhere — avoids equivalence-check precision issues.\n",
|
| 29 |
+
"# 6. Generator declares `observable_addrs` per program — verifier compares only these mem entries.\n",
|
| 30 |
+
"# 7. State translator annotates observable outputs at top of dump.\n",
|
| 31 |
+
"# 8. Reward distinguishes broken (-1000) from valid-but-worse (small negative) — Harshal's formula.\n",
|
| 32 |
+
"# 9. Multi-input verification: 3-5 random initial states, all must match.\n",
|
| 33 |
+
"# 10. Integer division uses Python floor division (//). Aarush's VM must match."
|
| 34 |
+
],
|
| 35 |
+
"metadata": {
|
| 36 |
+
"id": "_XI5jT2Ibvrf"
|
| 37 |
+
},
|
| 38 |
+
"execution_count": 17,
|
| 39 |
+
"outputs": []
|
| 40 |
+
},
|
| 41 |
+
{
|
| 42 |
+
"cell_type": "code",
|
| 43 |
+
"execution_count": 13,
|
| 44 |
+
"metadata": {
|
| 45 |
+
"id": "j6CQ327KWXwr"
|
| 46 |
+
},
|
| 47 |
+
"outputs": [],
|
| 48 |
+
"source": [
|
| 49 |
+
"# === TAC Schema v1.0 (LOCKED with Role 1 / Aarush) ===\n",
|
| 50 |
+
"# Reverse passes deferred to stretch goal — not in initial action space.\n",
|
| 51 |
+
"\n",
|
| 52 |
+
"OPS = [\"CONST\", \"ADD\", \"SUB\", \"MUL\", \"DIV\", \"LOAD\", \"STORE\", \"NOP\"]\n",
|
| 53 |
+
"\n",
|
| 54 |
+
"CYCLE_COSTS = {\n",
|
| 55 |
+
" \"CONST\": 1,\n",
|
| 56 |
+
" \"ADD\": 1,\n",
|
| 57 |
+
" \"SUB\": 1,\n",
|
| 58 |
+
" \"MUL\": 3,\n",
|
| 59 |
+
" \"DIV\": 5,\n",
|
| 60 |
+
" \"LOAD\": 4,\n",
|
| 61 |
+
" \"STORE\": 4,\n",
|
| 62 |
+
" \"NOP\": 0,\n",
|
| 63 |
+
"}\n",
|
| 64 |
+
"\n",
|
| 65 |
+
"# Instruction shape: {\"op\": str, \"dest\": str|None, \"src1\": Any, \"src2\": Any}\n",
|
| 66 |
+
"# Operands: str = variable name, int = literal constant, None = unused\n",
|
| 67 |
+
"#\n",
|
| 68 |
+
"# Op semantics:\n",
|
| 69 |
+
"# CONST: dest = src1 (src1 is int literal, src2 = None)\n",
|
| 70 |
+
"# ADD/SUB/MUL/DIV: dest = src1 OP src2 (src1, src2 are var names or int literals)\n",
|
| 71 |
+
"# LOAD: dest = mem[src1] (src1 is a var holding an address)\n",
|
| 72 |
+
"# STORE: mem[dest] = src1 (dest is a var holding an address)\n",
|
| 73 |
+
"# NOP: no-op (all fields None)\n",
|
| 74 |
+
"#\n",
|
| 75 |
+
"# Program output (for equivalence check) = final memory state (mem dict).\n",
|
| 76 |
+
"# Programs ship as: (initial_vars: dict, initial_mem: dict, instructions: list[dict])\n",
|
| 77 |
+
"# 10. Integer division uses Python floor division (//). Aarush's VM must match."
|
| 78 |
+
]
|
| 79 |
+
},
|
| 80 |
+
{
|
| 81 |
+
"cell_type": "code",
|
| 82 |
+
"source": [
|
| 83 |
+
"import random\n",
|
| 84 |
+
"\n",
|
| 85 |
+
"def generate_level_1():\n",
|
| 86 |
+
" \"\"\"\n",
|
| 87 |
+
" Generate a Level 1 Toy-IR program.\n",
|
| 88 |
+
"\n",
|
| 89 |
+
" Characteristics:\n",
|
| 90 |
+
" - 4-6 instructions\n",
|
| 91 |
+
" - 2-3 CONST ops with literal values\n",
|
| 92 |
+
" - 1-2 arithmetic ops on those constants (foldable by CF)\n",
|
| 93 |
+
" - 0-1 dead variables (killable by DCE)\n",
|
| 94 |
+
" - Exactly 1 STORE at the end so the program has an observable output\n",
|
| 95 |
+
"\n",
|
| 96 |
+
" Returns:\n",
|
| 97 |
+
" dict with keys: initial_vars, initial_mem, instructions, observable_addrs\n",
|
| 98 |
+
" \"\"\"\n",
|
| 99 |
+
" instructions = []\n",
|
| 100 |
+
" var_counter = 0\n",
|
| 101 |
+
"\n",
|
| 102 |
+
" def new_var():\n",
|
| 103 |
+
" nonlocal var_counter\n",
|
| 104 |
+
" name = f\"v{var_counter}\"\n",
|
| 105 |
+
" var_counter += 1\n",
|
| 106 |
+
" return name\n",
|
| 107 |
+
"\n",
|
| 108 |
+
" # Step 1: Generate 2-3 constant assignments\n",
|
| 109 |
+
" num_consts = random.randint(2, 3)\n",
|
| 110 |
+
" const_vars = []\n",
|
| 111 |
+
" for _ in range(num_consts):\n",
|
| 112 |
+
" var = new_var()\n",
|
| 113 |
+
" value = random.randint(1, 10)\n",
|
| 114 |
+
" instructions.append({\"op\": \"CONST\", \"dest\": var, \"src1\": value, \"src2\": None})\n",
|
| 115 |
+
" const_vars.append(var)\n",
|
| 116 |
+
"\n",
|
| 117 |
+
" # Step 2: Generate 1-2 arithmetic ops using those constants\n",
|
| 118 |
+
" num_arith = random.randint(1, 2)\n",
|
| 119 |
+
" last_result = None\n",
|
| 120 |
+
" for _ in range(num_arith):\n",
|
| 121 |
+
" op = random.choice([\"ADD\", \"MUL\"])\n",
|
| 122 |
+
" src1 = random.choice(const_vars)\n",
|
| 123 |
+
" src2 = random.choice(const_vars)\n",
|
| 124 |
+
" dest = new_var()\n",
|
| 125 |
+
" instructions.append({\"op\": op, \"dest\": dest, \"src1\": src1, \"src2\": src2})\n",
|
| 126 |
+
" last_result = dest\n",
|
| 127 |
+
" const_vars.append(dest)\n",
|
| 128 |
+
"\n",
|
| 129 |
+
" # Step 3: Optionally add 1 dead variable (50% chance)\n",
|
| 130 |
+
" if random.random() < 0.5:\n",
|
| 131 |
+
" dead_var = new_var()\n",
|
| 132 |
+
" dead_value = random.randint(1, 10)\n",
|
| 133 |
+
" instructions.append({\"op\": \"CONST\", \"dest\": dead_var, \"src1\": dead_value, \"src2\": None})\n",
|
| 134 |
+
" # Note: dead_var is intentionally never used — DCE should catch it.\n",
|
| 135 |
+
"\n",
|
| 136 |
+
" # Step 4: Add a STORE at the end so the program has observable output\n",
|
| 137 |
+
" initial_vars = {\"addr0\": 0}\n",
|
| 138 |
+
" instructions.append({\"op\": \"STORE\", \"dest\": \"addr0\", \"src1\": last_result, \"src2\": None})\n",
|
| 139 |
+
"\n",
|
| 140 |
+
" initial_mem = {}\n",
|
| 141 |
+
"\n",
|
| 142 |
+
" return {\n",
|
| 143 |
+
" \"initial_vars\": initial_vars,\n",
|
| 144 |
+
" \"initial_mem\": initial_mem,\n",
|
| 145 |
+
" \"instructions\": instructions,\n",
|
| 146 |
+
" \"observable_addrs\": [0], # Aarush's verifier compares only these mem entries\n",
|
| 147 |
+
" }\n",
|
| 148 |
+
"\n",
|
| 149 |
+
"\n",
|
| 150 |
+
"# Sanity-check: generate a few programs and print them\n",
|
| 151 |
+
"for seed in [42, 1, 7, 99]:\n",
|
| 152 |
+
" random.seed(seed)\n",
|
| 153 |
+
" prog = generate_level_1()\n",
|
| 154 |
+
" print(f\"\\n=== seed={seed} ===\")\n",
|
| 155 |
+
" print(f\"initial_vars : {prog['initial_vars']}\")\n",
|
| 156 |
+
" print(f\"initial_mem : {prog['initial_mem']}\")\n",
|
| 157 |
+
" print(f\"observable_addrs : {prog['observable_addrs']}\")\n",
|
| 158 |
+
" print(f\"instructions ({len(prog['instructions'])}):\")\n",
|
| 159 |
+
" for i, instr in enumerate(prog['instructions']):\n",
|
| 160 |
+
" print(f\" {i}: {instr}\")"
|
| 161 |
+
],
|
| 162 |
+
"metadata": {
|
| 163 |
+
"colab": {
|
| 164 |
+
"base_uri": "https://localhost:8080/"
|
| 165 |
+
},
|
| 166 |
+
"id": "a86vD1OyWcB9",
|
| 167 |
+
"outputId": "de3d7093-eaea-420e-a0d7-2bd9451bcf6b"
|
| 168 |
+
},
|
| 169 |
+
"execution_count": 14,
|
| 170 |
+
"outputs": [
|
| 171 |
+
{
|
| 172 |
+
"output_type": "stream",
|
| 173 |
+
"name": "stdout",
|
| 174 |
+
"text": [
|
| 175 |
+
"\n",
|
| 176 |
+
"=== seed=42 ===\n",
|
| 177 |
+
"initial_vars : {'addr0': 0}\n",
|
| 178 |
+
"initial_mem : {}\n",
|
| 179 |
+
"observable_addrs : [0]\n",
|
| 180 |
+
"instructions (4):\n",
|
| 181 |
+
" 0: {'op': 'CONST', 'dest': 'v0', 'src1': 1, 'src2': None}\n",
|
| 182 |
+
" 1: {'op': 'CONST', 'dest': 'v1', 'src1': 5, 'src2': None}\n",
|
| 183 |
+
" 2: {'op': 'ADD', 'dest': 'v2', 'src1': 'v0', 'src2': 'v0'}\n",
|
| 184 |
+
" 3: {'op': 'STORE', 'dest': 'addr0', 'src1': 'v2', 'src2': None}\n",
|
| 185 |
+
"\n",
|
| 186 |
+
"=== seed=1 ===\n",
|
| 187 |
+
"initial_vars : {'addr0': 0}\n",
|
| 188 |
+
"initial_mem : {}\n",
|
| 189 |
+
"observable_addrs : [0]\n",
|
| 190 |
+
"instructions (5):\n",
|
| 191 |
+
" 0: {'op': 'CONST', 'dest': 'v0', 'src1': 10, 'src2': None}\n",
|
| 192 |
+
" 1: {'op': 'CONST', 'dest': 'v1', 'src1': 2, 'src2': None}\n",
|
| 193 |
+
" 2: {'op': 'ADD', 'dest': 'v2', 'src1': 'v1', 'src2': 'v1'}\n",
|
| 194 |
+
" 3: {'op': 'MUL', 'dest': 'v3', 'src1': 'v2', 'src2': 'v1'}\n",
|
| 195 |
+
" 4: {'op': 'STORE', 'dest': 'addr0', 'src1': 'v3', 'src2': None}\n",
|
| 196 |
+
"\n",
|
| 197 |
+
"=== seed=7 ===\n",
|
| 198 |
+
"initial_vars : {'addr0': 0}\n",
|
| 199 |
+
"initial_mem : {}\n",
|
| 200 |
+
"observable_addrs : [0]\n",
|
| 201 |
+
"instructions (6):\n",
|
| 202 |
+
" 0: {'op': 'CONST', 'dest': 'v0', 'src1': 3, 'src2': None}\n",
|
| 203 |
+
" 1: {'op': 'CONST', 'dest': 'v1', 'src1': 7, 'src2': None}\n",
|
| 204 |
+
" 2: {'op': 'CONST', 'dest': 'v2', 'src1': 1, 'src2': None}\n",
|
| 205 |
+
" 3: {'op': 'ADD', 'dest': 'v3', 'src1': 'v1', 'src2': 'v2'}\n",
|
| 206 |
+
" 4: {'op': 'CONST', 'dest': 'v4', 'src1': 9, 'src2': None}\n",
|
| 207 |
+
" 5: {'op': 'STORE', 'dest': 'addr0', 'src1': 'v3', 'src2': None}\n",
|
| 208 |
+
"\n",
|
| 209 |
+
"=== seed=99 ===\n",
|
| 210 |
+
"initial_vars : {'addr0': 0}\n",
|
| 211 |
+
"initial_mem : {}\n",
|
| 212 |
+
"observable_addrs : [0]\n",
|
| 213 |
+
"instructions (5):\n",
|
| 214 |
+
" 0: {'op': 'CONST', 'dest': 'v0', 'src1': 7, 'src2': None}\n",
|
| 215 |
+
" 1: {'op': 'CONST', 'dest': 'v1', 'src1': 4, 'src2': None}\n",
|
| 216 |
+
" 2: {'op': 'CONST', 'dest': 'v2', 'src1': 10, 'src2': None}\n",
|
| 217 |
+
" 3: {'op': 'ADD', 'dest': 'v3', 'src1': 'v0', 'src2': 'v0'}\n",
|
| 218 |
+
" 4: {'op': 'STORE', 'dest': 'addr0', 'src1': 'v3', 'src2': None}\n"
|
| 219 |
+
]
|
| 220 |
+
}
|
| 221 |
+
]
|
| 222 |
+
},
|
| 223 |
+
{
|
| 224 |
+
"cell_type": "code",
|
| 225 |
+
"source": [
|
| 226 |
+
"def _resolve_operand(operand, known_constants):\n",
|
| 227 |
+
" \"\"\"\n",
|
| 228 |
+
" Given a TAC operand (string variable name or int literal),\n",
|
| 229 |
+
" return its concrete integer value if known, or None if unknown.\n",
|
| 230 |
+
" \"\"\"\n",
|
| 231 |
+
" if isinstance(operand, int):\n",
|
| 232 |
+
" return operand\n",
|
| 233 |
+
" if isinstance(operand, str) and operand in known_constants:\n",
|
| 234 |
+
" return known_constants[operand]\n",
|
| 235 |
+
" return None\n",
|
| 236 |
+
"\n",
|
| 237 |
+
"\n",
|
| 238 |
+
"def _compute(op, v1, v2):\n",
|
| 239 |
+
" \"\"\"Compute the result of a binary arithmetic op on two known integers.\"\"\"\n",
|
| 240 |
+
" if op == \"ADD\": return v1 + v2\n",
|
| 241 |
+
" if op == \"SUB\": return v1 - v2\n",
|
| 242 |
+
" if op == \"MUL\": return v1 * v2\n",
|
| 243 |
+
" if op == \"DIV\": return v1 // v2 # floor division (locked semantics)\n",
|
| 244 |
+
" raise ValueError(f\"_compute called with non-arithmetic op: {op}\")\n",
|
| 245 |
+
"\n",
|
| 246 |
+
"\n",
|
| 247 |
+
"def constant_folding(program):\n",
|
| 248 |
+
" \"\"\"\n",
|
| 249 |
+
" Forward pass that folds constant arithmetic into CONST ops, and\n",
|
| 250 |
+
" propagates known constants into instruction operands.\n",
|
| 251 |
+
"\n",
|
| 252 |
+
" Behavior:\n",
|
| 253 |
+
" - If both sources of an arithmetic op resolve to known integers,\n",
|
| 254 |
+
" replaces the instruction with a CONST holding the computed result.\n",
|
| 255 |
+
" - If only one source is known, still substitutes that known value\n",
|
| 256 |
+
" into the instruction (constant propagation), enabling downstream\n",
|
| 257 |
+
" passes (e.g., peephole) to recognize patterns like ADD-with-0 or MUL-by-1.\n",
|
| 258 |
+
" - Refuses to fold DIV by zero (Design Assumption #3).\n",
|
| 259 |
+
" - Always returns fresh dicts; never aliases input instructions.\n",
|
| 260 |
+
"\n",
|
| 261 |
+
" Args:\n",
|
| 262 |
+
" program: list of TAC instruction dicts (per locked schema).\n",
|
| 263 |
+
"\n",
|
| 264 |
+
" Returns:\n",
|
| 265 |
+
" new list of TAC instruction dicts. Always semantics-preserving.\n",
|
| 266 |
+
" Never raises, never returns None.\n",
|
| 267 |
+
" \"\"\"\n",
|
| 268 |
+
" known_constants = {}\n",
|
| 269 |
+
" new_program = []\n",
|
| 270 |
+
"\n",
|
| 271 |
+
" for instr in program:\n",
|
| 272 |
+
" # Always work on a copy — never alias input dicts\n",
|
| 273 |
+
" instr = instr.copy()\n",
|
| 274 |
+
" op = instr[\"op\"]\n",
|
| 275 |
+
" dest = instr[\"dest\"]\n",
|
| 276 |
+
"\n",
|
| 277 |
+
" if op == \"CONST\":\n",
|
| 278 |
+
" known_constants[dest] = instr[\"src1\"]\n",
|
| 279 |
+
" new_program.append(instr)\n",
|
| 280 |
+
"\n",
|
| 281 |
+
" elif op in (\"ADD\", \"SUB\", \"MUL\", \"DIV\"):\n",
|
| 282 |
+
" # === Constant propagation: substitute known constants into operands ===\n",
|
| 283 |
+
" if isinstance(instr[\"src1\"], str) and instr[\"src1\"] in known_constants:\n",
|
| 284 |
+
" instr[\"src1\"] = known_constants[instr[\"src1\"]]\n",
|
| 285 |
+
" if isinstance(instr[\"src2\"], str) and instr[\"src2\"] in known_constants:\n",
|
| 286 |
+
" instr[\"src2\"] = known_constants[instr[\"src2\"]]\n",
|
| 287 |
+
"\n",
|
| 288 |
+
" # === Try to fold ===\n",
|
| 289 |
+
" v1 = _resolve_operand(instr[\"src1\"], known_constants)\n",
|
| 290 |
+
" v2 = _resolve_operand(instr[\"src2\"], known_constants)\n",
|
| 291 |
+
"\n",
|
| 292 |
+
" if v1 is not None and v2 is not None:\n",
|
| 293 |
+
" # Both operands are known integers\n",
|
| 294 |
+
" if op == \"DIV\" and v2 == 0:\n",
|
| 295 |
+
" # Refuse to fold DIV by zero\n",
|
| 296 |
+
" new_program.append(instr)\n",
|
| 297 |
+
" known_constants.pop(dest, None)\n",
|
| 298 |
+
" else:\n",
|
| 299 |
+
" # Fold: replace with CONST\n",
|
| 300 |
+
" result = _compute(op, v1, v2)\n",
|
| 301 |
+
" new_program.append({\n",
|
| 302 |
+
" \"op\": \"CONST\",\n",
|
| 303 |
+
" \"dest\": dest,\n",
|
| 304 |
+
" \"src1\": result,\n",
|
| 305 |
+
" \"src2\": None,\n",
|
| 306 |
+
" })\n",
|
| 307 |
+
" known_constants[dest] = result\n",
|
| 308 |
+
" else:\n",
|
| 309 |
+
" # Can't fold (at least one operand unknown).\n",
|
| 310 |
+
" # Instruction may still have been mutated by propagation above.\n",
|
| 311 |
+
" new_program.append(instr)\n",
|
| 312 |
+
" known_constants.pop(dest, None)\n",
|
| 313 |
+
"\n",
|
| 314 |
+
" elif op == \"LOAD\":\n",
|
| 315 |
+
" # Memory reads aren't statically resolvable\n",
|
| 316 |
+
" new_program.append(instr)\n",
|
| 317 |
+
" known_constants.pop(dest, None)\n",
|
| 318 |
+
"\n",
|
| 319 |
+
" elif op in (\"STORE\", \"NOP\"):\n",
|
| 320 |
+
" # No dest tracking needed.\n",
|
| 321 |
+
" # Note: we COULD propagate src1 into a STORE for downstream readability,\n",
|
| 322 |
+
" # but the cycle cost is unchanged and the executor handles vars fine.\n",
|
| 323 |
+
" # Leave STORE alone — keeps the code minimal.\n",
|
| 324 |
+
" new_program.append(instr)\n",
|
| 325 |
+
"\n",
|
| 326 |
+
" else:\n",
|
| 327 |
+
" # Unknown op — defensive pass-through\n",
|
| 328 |
+
" new_program.append(instr)\n",
|
| 329 |
+
"\n",
|
| 330 |
+
" return new_program"
|
| 331 |
+
],
|
| 332 |
+
"metadata": {
|
| 333 |
+
"id": "CGk8Fz4CZA3t"
|
| 334 |
+
},
|
| 335 |
+
"execution_count": 18,
|
| 336 |
+
"outputs": []
|
| 337 |
+
},
|
| 338 |
+
{
|
| 339 |
+
"cell_type": "code",
|
| 340 |
+
"source": [
|
| 341 |
+
"# === Sanity tests for constant_folding ===\n",
|
| 342 |
+
"\n",
|
| 343 |
+
"# Test 1: simple fold — ADD of two CONSTs\n",
|
| 344 |
+
"test1 = [\n",
|
| 345 |
+
" {\"op\": \"CONST\", \"dest\": \"a\", \"src1\": 3, \"src2\": None},\n",
|
| 346 |
+
" {\"op\": \"CONST\", \"dest\": \"b\", \"src1\": 5, \"src2\": None},\n",
|
| 347 |
+
" {\"op\": \"ADD\", \"dest\": \"c\", \"src1\": \"a\", \"src2\": \"b\"},\n",
|
| 348 |
+
" {\"op\": \"STORE\", \"dest\": \"addr0\", \"src1\": \"c\", \"src2\": None},\n",
|
| 349 |
+
"]\n",
|
| 350 |
+
"result1 = constant_folding(test1)\n",
|
| 351 |
+
"print(\"Test 1 (simple ADD fold):\")\n",
|
| 352 |
+
"for i, instr in enumerate(result1): print(f\" {i}: {instr}\")\n",
|
| 353 |
+
"# Expected: instruction 2 becomes CONST c = 8\n",
|
| 354 |
+
"\n",
|
| 355 |
+
"# Test 2: chained fold — second op uses first op's folded result\n",
|
| 356 |
+
"test2 = [\n",
|
| 357 |
+
" {\"op\": \"CONST\", \"dest\": \"a\", \"src1\": 3, \"src2\": None},\n",
|
| 358 |
+
" {\"op\": \"CONST\", \"dest\": \"b\", \"src1\": 5, \"src2\": None},\n",
|
| 359 |
+
" {\"op\": \"ADD\", \"dest\": \"c\", \"src1\": \"a\", \"src2\": \"b\"}, # → 8\n",
|
| 360 |
+
" {\"op\": \"MUL\", \"dest\": \"d\", \"src1\": \"c\", \"src2\": \"b\"}, # → 8 * 5 = 40\n",
|
| 361 |
+
" {\"op\": \"STORE\", \"dest\": \"addr0\", \"src1\": \"d\", \"src2\": None},\n",
|
| 362 |
+
"]\n",
|
| 363 |
+
"result2 = constant_folding(test2)\n",
|
| 364 |
+
"print(\"\\nTest 2 (chained fold):\")\n",
|
| 365 |
+
"for i, instr in enumerate(result2): print(f\" {i}: {instr}\")\n",
|
| 366 |
+
"# Expected: c becomes CONST 8, d becomes CONST 40\n",
|
| 367 |
+
"\n",
|
| 368 |
+
"# Test 3: DIV by zero refused\n",
|
| 369 |
+
"test3 = [\n",
|
| 370 |
+
" {\"op\": \"CONST\", \"dest\": \"a\", \"src1\": 10, \"src2\": None},\n",
|
| 371 |
+
" {\"op\": \"CONST\", \"dest\": \"b\", \"src1\": 0, \"src2\": None},\n",
|
| 372 |
+
" {\"op\": \"DIV\", \"dest\": \"c\", \"src1\": \"a\", \"src2\": \"b\"}, # 10/0 — must NOT fold\n",
|
| 373 |
+
" {\"op\": \"STORE\", \"dest\": \"addr0\", \"src1\": \"c\", \"src2\": None},\n",
|
| 374 |
+
"]\n",
|
| 375 |
+
"result3 = constant_folding(test3)\n",
|
| 376 |
+
"print(\"\\nTest 3 (DIV by zero refused):\")\n",
|
| 377 |
+
"for i, instr in enumerate(result3): print(f\" {i}: {instr}\")\n",
|
| 378 |
+
"# Expected: instruction 2 unchanged (still DIV, not CONST)\n",
|
| 379 |
+
"\n",
|
| 380 |
+
"# Test 4: unknown source can't fold\n",
|
| 381 |
+
"test4 = [\n",
|
| 382 |
+
" {\"op\": \"CONST\", \"dest\": \"a\", \"src1\": 3, \"src2\": None},\n",
|
| 383 |
+
" {\"op\": \"LOAD\", \"dest\": \"b\", \"src1\": \"addr0\", \"src2\": None}, # b is unknown\n",
|
| 384 |
+
" {\"op\": \"ADD\", \"dest\": \"c\", \"src1\": \"a\", \"src2\": \"b\"}, # can't fold\n",
|
| 385 |
+
" {\"op\": \"STORE\", \"dest\": \"addr0\", \"src1\": \"c\", \"src2\": None},\n",
|
| 386 |
+
"]\n",
|
| 387 |
+
"result4 = constant_folding(test4)\n",
|
| 388 |
+
"print(\"\\nTest 4 (LOAD makes b unknown, ADD not folded):\")\n",
|
| 389 |
+
"for i, instr in enumerate(result4): print(f\" {i}: {instr}\")\n",
|
| 390 |
+
"# Expected: instruction 2 unchanged (still ADD)\n",
|
| 391 |
+
"\n",
|
| 392 |
+
"# Test 5: idempotence — running CF on a generated program\n",
|
| 393 |
+
"random.seed(42)\n",
|
| 394 |
+
"prog = generate_level_1()\n",
|
| 395 |
+
"folded = constant_folding(prog[\"instructions\"])\n",
|
| 396 |
+
"print(\"\\nTest 5 (CF on generated Level 1 program, seed=42):\")\n",
|
| 397 |
+
"print(\"Before:\")\n",
|
| 398 |
+
"for i, instr in enumerate(prog[\"instructions\"]): print(f\" {i}: {instr}\")\n",
|
| 399 |
+
"print(\"After:\")\n",
|
| 400 |
+
"for i, instr in enumerate(folded): print(f\" {i}: {instr}\")\n",
|
| 401 |
+
"\n",
|
| 402 |
+
"# Test 6: constant propagation — only one source is known\n",
|
| 403 |
+
"test6 = [\n",
|
| 404 |
+
" {\"op\": \"CONST\", \"dest\": \"a\", \"src1\": 0, \"src2\": None},\n",
|
| 405 |
+
" {\"op\": \"LOAD\", \"dest\": \"b\", \"src1\": \"addr0\", \"src2\": None}, # b unknown\n",
|
| 406 |
+
" {\"op\": \"ADD\", \"dest\": \"c\", \"src1\": \"a\", \"src2\": \"b\"}, # a known (=0), b unknown\n",
|
| 407 |
+
" {\"op\": \"STORE\", \"dest\": \"addr0\", \"src1\": \"c\", \"src2\": None},\n",
|
| 408 |
+
"]\n",
|
| 409 |
+
"result6 = constant_folding(test6)\n",
|
| 410 |
+
"print(\"\\nTest 6 (propagation: a=0 substituted into ADD even though b unknown):\")\n",
|
| 411 |
+
"for i, instr in enumerate(result6): print(f\" {i}: {instr}\")\n",
|
| 412 |
+
"# Expected: instruction 2 is still ADD (can't fold — b unknown), but src1 is now literal 0, not 'a'\n",
|
| 413 |
+
"# This sets up peephole to recognize \"ADD with 0\" later"
|
| 414 |
+
],
|
| 415 |
+
"metadata": {
|
| 416 |
+
"colab": {
|
| 417 |
+
"base_uri": "https://localhost:8080/"
|
| 418 |
+
},
|
| 419 |
+
"id": "WLKSTVa8fRF8",
|
| 420 |
+
"outputId": "27042d38-0044-4814-bbfa-8a2c2d87ee06"
|
| 421 |
+
},
|
| 422 |
+
"execution_count": 20,
|
| 423 |
+
"outputs": [
|
| 424 |
+
{
|
| 425 |
+
"output_type": "stream",
|
| 426 |
+
"name": "stdout",
|
| 427 |
+
"text": [
|
| 428 |
+
"Test 1 (simple ADD fold):\n",
|
| 429 |
+
" 0: {'op': 'CONST', 'dest': 'a', 'src1': 3, 'src2': None}\n",
|
| 430 |
+
" 1: {'op': 'CONST', 'dest': 'b', 'src1': 5, 'src2': None}\n",
|
| 431 |
+
" 2: {'op': 'CONST', 'dest': 'c', 'src1': 8, 'src2': None}\n",
|
| 432 |
+
" 3: {'op': 'STORE', 'dest': 'addr0', 'src1': 'c', 'src2': None}\n",
|
| 433 |
+
"\n",
|
| 434 |
+
"Test 2 (chained fold):\n",
|
| 435 |
+
" 0: {'op': 'CONST', 'dest': 'a', 'src1': 3, 'src2': None}\n",
|
| 436 |
+
" 1: {'op': 'CONST', 'dest': 'b', 'src1': 5, 'src2': None}\n",
|
| 437 |
+
" 2: {'op': 'CONST', 'dest': 'c', 'src1': 8, 'src2': None}\n",
|
| 438 |
+
" 3: {'op': 'CONST', 'dest': 'd', 'src1': 40, 'src2': None}\n",
|
| 439 |
+
" 4: {'op': 'STORE', 'dest': 'addr0', 'src1': 'd', 'src2': None}\n",
|
| 440 |
+
"\n",
|
| 441 |
+
"Test 3 (DIV by zero refused):\n",
|
| 442 |
+
" 0: {'op': 'CONST', 'dest': 'a', 'src1': 10, 'src2': None}\n",
|
| 443 |
+
" 1: {'op': 'CONST', 'dest': 'b', 'src1': 0, 'src2': None}\n",
|
| 444 |
+
" 2: {'op': 'DIV', 'dest': 'c', 'src1': 10, 'src2': 0}\n",
|
| 445 |
+
" 3: {'op': 'STORE', 'dest': 'addr0', 'src1': 'c', 'src2': None}\n",
|
| 446 |
+
"\n",
|
| 447 |
+
"Test 4 (LOAD makes b unknown, ADD not folded):\n",
|
| 448 |
+
" 0: {'op': 'CONST', 'dest': 'a', 'src1': 3, 'src2': None}\n",
|
| 449 |
+
" 1: {'op': 'LOAD', 'dest': 'b', 'src1': 'addr0', 'src2': None}\n",
|
| 450 |
+
" 2: {'op': 'ADD', 'dest': 'c', 'src1': 3, 'src2': 'b'}\n",
|
| 451 |
+
" 3: {'op': 'STORE', 'dest': 'addr0', 'src1': 'c', 'src2': None}\n",
|
| 452 |
+
"\n",
|
| 453 |
+
"Test 5 (CF on generated Level 1 program, seed=42):\n",
|
| 454 |
+
"Before:\n",
|
| 455 |
+
" 0: {'op': 'CONST', 'dest': 'v0', 'src1': 1, 'src2': None}\n",
|
| 456 |
+
" 1: {'op': 'CONST', 'dest': 'v1', 'src1': 5, 'src2': None}\n",
|
| 457 |
+
" 2: {'op': 'ADD', 'dest': 'v2', 'src1': 'v0', 'src2': 'v0'}\n",
|
| 458 |
+
" 3: {'op': 'STORE', 'dest': 'addr0', 'src1': 'v2', 'src2': None}\n",
|
| 459 |
+
"After:\n",
|
| 460 |
+
" 0: {'op': 'CONST', 'dest': 'v0', 'src1': 1, 'src2': None}\n",
|
| 461 |
+
" 1: {'op': 'CONST', 'dest': 'v1', 'src1': 5, 'src2': None}\n",
|
| 462 |
+
" 2: {'op': 'CONST', 'dest': 'v2', 'src1': 2, 'src2': None}\n",
|
| 463 |
+
" 3: {'op': 'STORE', 'dest': 'addr0', 'src1': 'v2', 'src2': None}\n",
|
| 464 |
+
"\n",
|
| 465 |
+
"Test 6 (propagation: a=0 substituted into ADD even though b unknown):\n",
|
| 466 |
+
" 0: {'op': 'CONST', 'dest': 'a', 'src1': 0, 'src2': None}\n",
|
| 467 |
+
" 1: {'op': 'LOAD', 'dest': 'b', 'src1': 'addr0', 'src2': None}\n",
|
| 468 |
+
" 2: {'op': 'ADD', 'dest': 'c', 'src1': 0, 'src2': 'b'}\n",
|
| 469 |
+
" 3: {'op': 'STORE', 'dest': 'addr0', 'src1': 'c', 'src2': None}\n"
|
| 470 |
+
]
|
| 471 |
+
}
|
| 472 |
+
]
|
| 473 |
+
},
|
| 474 |
+
{
|
| 475 |
+
"cell_type": "code",
|
| 476 |
+
"source": [],
|
| 477 |
+
"metadata": {
|
| 478 |
+
"id": "5vo-KgaffTFR"
|
| 479 |
+
},
|
| 480 |
+
"execution_count": null,
|
| 481 |
+
"outputs": []
|
| 482 |
+
}
|
| 483 |
+
]
|
| 484 |
+
}
|
space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.github/workflows/deploy.yml
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Deploy to Hugging Face Space
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
branches:
|
| 6 |
+
- main
|
| 7 |
+
|
| 8 |
+
jobs:
|
| 9 |
+
deploy:
|
| 10 |
+
runs-on: ubuntu-latest
|
| 11 |
+
|
| 12 |
+
steps:
|
| 13 |
+
- name: Checkout Repo
|
| 14 |
+
uses: actions/checkout@v4
|
| 15 |
+
|
| 16 |
+
- name: Push to Hugging Face
|
| 17 |
+
env:
|
| 18 |
+
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
| 19 |
+
run: |
|
| 20 |
+
git config --global user.email "github-actions@github.com"
|
| 21 |
+
git config --global user.name "github-actions"
|
| 22 |
+
|
| 23 |
+
git clone https://user:$HF_TOKEN@huggingface.co/spaces/greedybeserk95/Compilertetris space
|
| 24 |
+
|
| 25 |
+
rsync -av --exclude ".git" ./ space/
|
| 26 |
+
|
| 27 |
+
cd space
|
| 28 |
+
git add .
|
| 29 |
+
git commit -m "Auto deploy from GitHub" || echo "No changes"
|
| 30 |
+
git push
|
space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/README.md
CHANGED
|
@@ -1,11 +1 @@
|
|
| 1 |
-
|
| 2 |
-
title: Compilertetris
|
| 3 |
-
emoji: ⚡
|
| 4 |
-
colorFrom: red
|
| 5 |
-
colorTo: gray
|
| 6 |
-
sdk: docker
|
| 7 |
-
pinned: false
|
| 8 |
-
short_description: an RL environment which optimizes IR code
|
| 9 |
-
---
|
| 10 |
-
|
| 11 |
-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
| 1 |
+
# MetaHackathon2026Finals
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/.gitattributes
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/space/README.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Compilertetris
|
| 3 |
+
emoji: ⚡
|
| 4 |
+
colorFrom: red
|
| 5 |
+
colorTo: gray
|
| 6 |
+
sdk: docker
|
| 7 |
+
pinned: false
|
| 8 |
+
short_description: an RL environment which optimizes IR code
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
space/space/space/space/space/space/space/space/space/space/train.py
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Toy REINFORCE training for CompilerOptimizationEnv (CPU, no PyTorch).
|
| 3 |
+
|
| 4 |
+
This is meant for the Hugging Face Space: fast smoke training that uses the
|
| 5 |
+
same `runtime_core` mock engine and passes as the Gradio demo.
|
| 6 |
+
|
| 7 |
+
For full GRPO + Unsloth + LLM, use your Colab / GPU notebooks
|
| 8 |
+
(`compiler_optimization_grpo.ipynb`, `role2_deliverable3_*`).
|
| 9 |
+
|
| 10 |
+
CLI: python train.py --episodes 50 --max-steps 8 --seed 0
|
| 11 |
+
Or import: from train import run_toy_training; print(run_toy_training(20, 8, 0))
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
from __future__ import annotations
|
| 15 |
+
|
| 16 |
+
import argparse
|
| 17 |
+
import json
|
| 18 |
+
import math
|
| 19 |
+
import random
|
| 20 |
+
import textwrap
|
| 21 |
+
from typing import List, Sequence, Tuple
|
| 22 |
+
|
| 23 |
+
from runtime_core import (
|
| 24 |
+
CompilerOptimizationEnv,
|
| 25 |
+
MOCK_PASSES,
|
| 26 |
+
MockEngine,
|
| 27 |
+
SAMPLE_PROGRAM,
|
| 28 |
+
)
|
| 29 |
+
|
| 30 |
+
# Additional tiny programs so the policy is not overfit to a single IR.
|
| 31 |
+
TRAINING_PROGRAMS: List[List[dict]] = [
|
| 32 |
+
SAMPLE_PROGRAM,
|
| 33 |
+
[
|
| 34 |
+
{"op": "const", "dest": "a", "args": ["1"], "type": "int"},
|
| 35 |
+
{"op": "add", "dest": "b", "args": ["a", "a"], "type": "int"},
|
| 36 |
+
{"op": "ret", "args": ["b"]},
|
| 37 |
+
],
|
| 38 |
+
[
|
| 39 |
+
{"op": "const", "dest": "x", "args": ["2"], "type": "int"},
|
| 40 |
+
{"op": "const", "dest": "y", "args": ["4"], "type": "int"},
|
| 41 |
+
{"op": "mul", "dest": "z", "args": ["x", "y"], "type": "int"},
|
| 42 |
+
{"op": "const", "dest": "k", "args": ["1"], "type": "int"},
|
| 43 |
+
{"op": "add", "dest": "w", "args": ["z", "k"], "type": "int"},
|
| 44 |
+
{"op": "ret", "args": ["w"]},
|
| 45 |
+
],
|
| 46 |
+
]
|
| 47 |
+
|
| 48 |
+
ACTIONS: Tuple[str, ...] = tuple(sorted(MOCK_PASSES.keys()))
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def _softmax(logits: Sequence[float]) -> List[float]:
|
| 52 |
+
m = max(logits) if logits else 0.0
|
| 53 |
+
ex = [math.exp(x - m) for x in logits]
|
| 54 |
+
s = sum(ex) or 1.0
|
| 55 |
+
return [e / s for e in ex]
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def _sample_action(rng: random.Random, logits: List[float]) -> Tuple[int, List[float], float]:
|
| 59 |
+
"""Return (action index, prob vector, log prob of chosen action)."""
|
| 60 |
+
p = _softmax(logits)
|
| 61 |
+
u = rng.random()
|
| 62 |
+
acc = 0.0
|
| 63 |
+
idx = len(p) - 1
|
| 64 |
+
for i, pi in enumerate(p):
|
| 65 |
+
acc += pi
|
| 66 |
+
if u <= acc:
|
| 67 |
+
idx = i
|
| 68 |
+
break
|
| 69 |
+
log_p = math.log(p[idx] + 1e-12)
|
| 70 |
+
return idx, p, log_p
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def _rollout(
|
| 74 |
+
engine: MockEngine,
|
| 75 |
+
program: List[dict],
|
| 76 |
+
max_steps: int,
|
| 77 |
+
logits: List[float],
|
| 78 |
+
rng: random.Random,
|
| 79 |
+
) -> Tuple[float, List[Tuple[int, List[float], float]]]:
|
| 80 |
+
"""
|
| 81 |
+
One episode. Returns total reward and per-step (action index, prob vector, step reward)
|
| 82 |
+
for REINFORCE with returns G_t = sum of rewards from t onward.
|
| 83 |
+
"""
|
| 84 |
+
env = CompilerOptimizationEnv(engine, MOCK_PASSES, max_steps=max_steps)
|
| 85 |
+
env.reset(program)
|
| 86 |
+
total = 0.0
|
| 87 |
+
trace: List[Tuple[int, List[float], float]] = []
|
| 88 |
+
for _ in range(max_steps):
|
| 89 |
+
a_idx, pvec, _ = _sample_action(rng, logits)
|
| 90 |
+
action = ACTIONS[a_idx]
|
| 91 |
+
step = env.step(action)
|
| 92 |
+
r = float(step.reward)
|
| 93 |
+
total += r
|
| 94 |
+
trace.append((a_idx, pvec, r))
|
| 95 |
+
if step.done:
|
| 96 |
+
break
|
| 97 |
+
return total, trace
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
def _reinforce_update(
|
| 101 |
+
logits: List[float], trace: List[Tuple[int, List[float], float]], lr: float
|
| 102 |
+
) -> None:
|
| 103 |
+
"""REINFORCE with Monte Carlo return G_t from each step."""
|
| 104 |
+
G = 0.0
|
| 105 |
+
for t in range(len(trace) - 1, -1, -1):
|
| 106 |
+
a_idx, pvec, r = trace[t]
|
| 107 |
+
G += r
|
| 108 |
+
for i in range(len(logits)):
|
| 109 |
+
delta = 1.0 if i == a_idx else 0.0
|
| 110 |
+
logits[i] += lr * G * (delta - pvec[i])
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def run_toy_training(
|
| 114 |
+
episodes: int,
|
| 115 |
+
max_steps: int,
|
| 116 |
+
seed: int = 0,
|
| 117 |
+
lr: float = 0.15,
|
| 118 |
+
) -> str:
|
| 119 |
+
"""
|
| 120 |
+
Train a stateless categorical policy over the three mock passes; print-friendly report.
|
| 121 |
+
"""
|
| 122 |
+
if episodes < 1:
|
| 123 |
+
return "episodes must be >= 1"
|
| 124 |
+
if max_steps < 1:
|
| 125 |
+
return "max_steps must be >= 1"
|
| 126 |
+
episodes = int(episodes)
|
| 127 |
+
max_steps = int(max_steps)
|
| 128 |
+
seed = int(seed)
|
| 129 |
+
lr = float(lr)
|
| 130 |
+
|
| 131 |
+
rng = random.Random(seed)
|
| 132 |
+
engine = MockEngine()
|
| 133 |
+
logits = [0.0 for _ in ACTIONS]
|
| 134 |
+
|
| 135 |
+
history: List[Tuple[int, float]] = []
|
| 136 |
+
for ep in range(1, episodes + 1):
|
| 137 |
+
program = TRAINING_PROGRAMS[(ep - 1) % len(TRAINING_PROGRAMS)]
|
| 138 |
+
G, trace = _rollout(engine, program, max_steps, logits, rng)
|
| 139 |
+
if trace:
|
| 140 |
+
_reinforce_update(logits, trace, lr)
|
| 141 |
+
history.append((ep, G))
|
| 142 |
+
|
| 143 |
+
final_p = _softmax(logits)
|
| 144 |
+
lines = [
|
| 145 |
+
"Toy REINFORCE (stateless policy over pass names, CPU, stdlib only)",
|
| 146 |
+
f" episodes={episodes} max_steps={max_steps} seed={seed} lr={lr}",
|
| 147 |
+
f" actions order: {list(ACTIONS)}",
|
| 148 |
+
"",
|
| 149 |
+
f" final logits: {[round(x, 4) for x in logits]}",
|
| 150 |
+
f" final policy: {', '.join(f'{a}={p:.3f}' for a, p in zip(ACTIONS, final_p))}",
|
| 151 |
+
"",
|
| 152 |
+
" return per episode (last 10): " + ", ".join(f"{G:+.1f}" for _, G in history[-10:]),
|
| 153 |
+
"",
|
| 154 |
+
]
|
| 155 |
+
if history:
|
| 156 |
+
mean_r = sum(G for _, G in history) / len(history)
|
| 157 |
+
lines.append(f" mean return over all episodes: {mean_r:+.3f}")
|
| 158 |
+
lines.append("")
|
| 159 |
+
lines.append(
|
| 160 |
+
textwrap.dedent(
|
| 161 |
+
"""
|
| 162 |
+
This does not train an LLM. For GRPO + Qwen + Unsloth, run the project notebooks
|
| 163 |
+
on a GPU machine (e.g. Colab), not the CPU Space.
|
| 164 |
+
"""
|
| 165 |
+
).strip()
|
| 166 |
+
)
|
| 167 |
+
return "\n".join(lines)
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
def _parse_args() -> argparse.Namespace:
|
| 171 |
+
p = argparse.ArgumentParser(description="Toy REINFORCE on CompilerOptimizationEnv (CPU)")
|
| 172 |
+
p.add_argument("--episodes", type=int, default=50, help="Number of training episodes")
|
| 173 |
+
p.add_argument("--max-steps", type=int, default=8, help="max_steps per env episode")
|
| 174 |
+
p.add_argument("--seed", type=int, default=0, help="RNG seed")
|
| 175 |
+
p.add_argument("--lr", type=float, default=0.15, help="REINFORCE learning rate")
|
| 176 |
+
p.add_argument(
|
| 177 |
+
"--out-json",
|
| 178 |
+
type=str,
|
| 179 |
+
default="",
|
| 180 |
+
help="If set, write a small run summary to this path (e.g. training_log.json).",
|
| 181 |
+
)
|
| 182 |
+
return p.parse_args()
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
def main() -> None:
|
| 186 |
+
args = _parse_args()
|
| 187 |
+
report = run_toy_training(
|
| 188 |
+
episodes=args.episodes,
|
| 189 |
+
max_steps=args.max_steps,
|
| 190 |
+
seed=args.seed,
|
| 191 |
+
lr=args.lr,
|
| 192 |
+
)
|
| 193 |
+
print(report)
|
| 194 |
+
if args.out_json:
|
| 195 |
+
payload = {
|
| 196 |
+
"episodes": args.episodes,
|
| 197 |
+
"max_steps": args.max_steps,
|
| 198 |
+
"seed": args.seed,
|
| 199 |
+
"lr": args.lr,
|
| 200 |
+
"summary_text": report,
|
| 201 |
+
}
|
| 202 |
+
with open(args.out_json, "w", encoding="utf-8") as f:
|
| 203 |
+
json.dump(payload, f, indent=2)
|
| 204 |
+
print(f"\nWrote {args.out_json}")
|
| 205 |
+
|
| 206 |
+
|
| 207 |
+
if __name__ == "__main__":
|
| 208 |
+
main()
|
space/space/space/space/space/space/space/space/untitled folder.zip
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:b29f9a98dd93fb2a14b1d3e8409ce51a756b578debde97af1190991bbb65d203
|
| 3 |
+
size 63307
|
space/space/space/space/space/space/write_colab_synth_notebook.py
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Generate Compilertetris_GRPO_synthetic_dataset.ipynb (GRPO + random Toy-IR corpus)."""
|
| 2 |
+
import json
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
|
| 5 |
+
ROOT = Path(__file__).resolve().parents[1]
|
| 6 |
+
OUT = ROOT / "Compilertetris_GRPO_synthetic_dataset.ipynb"
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def cell_md(s: str) -> dict:
|
| 10 |
+
return {
|
| 11 |
+
"cell_type": "markdown",
|
| 12 |
+
"metadata": {},
|
| 13 |
+
"source": [line + "\n" for line in s.strip().split("\n")],
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def cell_code(s: str) -> dict:
|
| 18 |
+
return {
|
| 19 |
+
"cell_type": "code",
|
| 20 |
+
"metadata": {},
|
| 21 |
+
"execution_count": None,
|
| 22 |
+
"outputs": [],
|
| 23 |
+
"source": [line + "\n" for line in s.rstrip().split("\n")],
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
cells: list = []
|
| 27 |
+
|
| 28 |
+
cells.append(cell_md("""
|
| 29 |
+
# Compiler Tetris — GRPO with **synthetic Toy-IR corpus** (Colab)
|
| 30 |
+
|
| 31 |
+
This variant builds **50–200+** training prompts from `program_generator.py` (random const/add/mul chains in the same JSON shape as `runtime_core` / Deliverable 2). The original `metahack1` notebook uses a *different* IR schema; that generator is not mixed in here unless you add a converter.
|
| 32 |
+
|
| 33 |
+
| What | Value |
|
| 34 |
+
|------|--------|
|
| 35 |
+
| Space | `greedybeserk95/Compilertetris` |
|
| 36 |
+
| Code root after clone | `/content/Compilertetris` |
|
| 37 |
+
| LoRA output | `/content/compilertetris_lora` |
|
| 38 |
+
| This run uses **shorter** `TRAIN_STEPS` than the 3-example notebook (see config cell). |
|
| 39 |
+
| **Checkpoints** | Under `output_dir` as `checkpoint-*` (see `CHECKPOINT_EVERY` / `KEEP_LAST_N_CHECKPOINTS` in the GRPO cell) |
|
| 40 |
+
|
| 41 |
+
**Tuning:** In the dataset cell, set `N_TRAIN_PROGRAMS` (e.g. 80, 120, 200).
|
| 42 |
+
"""))
|
| 43 |
+
|
| 44 |
+
cells.append(
|
| 45 |
+
cell_md(
|
| 46 |
+
"""
|
| 47 |
+
## Checkpoints
|
| 48 |
+
|
| 49 |
+
The GRPO config uses `save_strategy="steps"` and `save_total_limit` so training writes **periodic checkpoints** under `output_dir` (e.g. `/content/grpo_compilertetris/checkpoint-20`, …) and prunes old ones. After a crash, re-run the setup cells, rebuild `trainer`, then use `resume_from_checkpoint=True` (latest) or a **specific path** (see the cell after training).
|
| 50 |
+
"""
|
| 51 |
+
)
|
| 52 |
+
)
|
| 53 |
+
|
| 54 |
+
cells.append(cell_code("""
|
| 55 |
+
# --- Central path config ---
|
| 56 |
+
|
| 57 |
+
HF_SPACE_REPO = "https://huggingface.co/spaces/greedybeserk95/Compilertetris"
|
| 58 |
+
HF_ADAPTER_REPO = "greedybeserk95/Compilertetris-grpo-adapter"
|
| 59 |
+
REPO_DIR = "/content/Compilertetris"
|
| 60 |
+
"""))
|
| 61 |
+
|
| 62 |
+
cells.append(cell_code("""
|
| 63 |
+
import os, subprocess
|
| 64 |
+
|
| 65 |
+
if os.path.isdir(REPO_DIR + "/.git"):
|
| 66 |
+
subprocess.check_call(["git", "-C", REPO_DIR, "pull", "--ff-only"], timeout=300)
|
| 67 |
+
else:
|
| 68 |
+
subprocess.check_call(["git", "clone", HF_SPACE_REPO, REPO_DIR], timeout=600)
|
| 69 |
+
print("Repo:", REPO_DIR)
|
| 70 |
+
"""))
|
| 71 |
+
|
| 72 |
+
cells.append(cell_code("""!nvidia-smi"""))
|
| 73 |
+
|
| 74 |
+
cells.append(cell_code("""
|
| 75 |
+
!pip install -q "unsloth[colab-new] @ git+https://github.com/unslothai/unsloth.git"
|
| 76 |
+
!pip install -q trl datasets transformers accelerate peft bitsandbytes
|
| 77 |
+
"""))
|
| 78 |
+
|
| 79 |
+
cells.append(cell_code("""
|
| 80 |
+
import sys, json, os
|
| 81 |
+
sys.path.insert(0, REPO_DIR)
|
| 82 |
+
|
| 83 |
+
import torch
|
| 84 |
+
import unsloth
|
| 85 |
+
from unsloth import FastLanguageModel
|
| 86 |
+
from datasets import Dataset
|
| 87 |
+
|
| 88 |
+
from runtime_core import (
|
| 89 |
+
CompilerOptimizationEnv,
|
| 90 |
+
Deliverable2_Formatter,
|
| 91 |
+
MockEngine,
|
| 92 |
+
MOCK_PASSES,
|
| 93 |
+
)
|
| 94 |
+
from program_generator import build_training_program_corpus
|
| 95 |
+
|
| 96 |
+
print("torch", torch.__version__, "cuda", torch.cuda.is_available())
|
| 97 |
+
print("MOCK_PASSES", list(MOCK_PASSES.keys()))
|
| 98 |
+
"""))
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
def _cell_chatml_synth() -> str:
|
| 102 |
+
p0 = """# Qwen2.5 ChatML + synthetic PROGRAMS
|
| 103 |
+
IM_END = \"<\" + \"|im_end|>\"
|
| 104 |
+
"""
|
| 105 |
+
p1 = r'''
|
| 106 |
+
import re
|
| 107 |
+
|
| 108 |
+
# How many training programs (50–200 recommended; includes 3 builtins if include_builtins=True)
|
| 109 |
+
N_TRAIN_PROGRAMS = 120
|
| 110 |
+
RANDOM_SEED = 42
|
| 111 |
+
|
| 112 |
+
SYSTEM_PROMPT = f"""You are a compiler optimization agent.
|
| 113 |
+
You will see Toy-IR as pseudo-assembly (Deliverable-2 text).
|
| 114 |
+
|
| 115 |
+
Output ONLY a valid JSON array of optimization pass names IN ORDER.
|
| 116 |
+
Available passes: {", ".join(repr(p) for p in sorted(MOCK_PASSES))}
|
| 117 |
+
Rules: JSON array only; max 8 passes; you may repeat passes."""
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
def build_prompt(program_list: list, program_id: int) -> str:
|
| 121 |
+
obs = Deliverable2_Formatter.translate_state(program_list)
|
| 122 |
+
return (
|
| 123 |
+
f"<|im_start|>system\n{SYSTEM_PROMPT}{IM_END}\n"
|
| 124 |
+
f"<|im_start|>user\n#PROGRAM_ID:{program_id}\n{obs}{IM_END}\n"
|
| 125 |
+
f"<|im_start|>assistant\n"
|
| 126 |
+
)
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
def program_from_prompt(prompt: str) -> list:
|
| 130 |
+
m = re.search(r"#PROGRAM_ID:(\d+)", prompt)
|
| 131 |
+
if m:
|
| 132 |
+
i = int(m.group(1))
|
| 133 |
+
if 0 <= i < len(PROGRAMS):
|
| 134 |
+
return PROGRAMS[i]
|
| 135 |
+
if "#PROGRAM_JSON" in prompt:
|
| 136 |
+
tail = prompt.split("#PROGRAM_JSON", 1)[1]
|
| 137 |
+
if IM_END in tail:
|
| 138 |
+
tail = tail.split(IM_END, 1)[0]
|
| 139 |
+
raw = tail.strip()
|
| 140 |
+
if raw:
|
| 141 |
+
return json.loads(raw)
|
| 142 |
+
raise ValueError("cannot resolve program from prompt (expect #PROGRAM_ID:N or #PROGRAM_JSON)")
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
PROGRAMS = build_training_program_corpus(
|
| 146 |
+
n_total=N_TRAIN_PROGRAMS,
|
| 147 |
+
seed=RANDOM_SEED,
|
| 148 |
+
include_builtins=True,
|
| 149 |
+
)
|
| 150 |
+
print("Corpus size:", len(PROGRAMS))
|
| 151 |
+
|
| 152 |
+
train_dataset = Dataset.from_dict({
|
| 153 |
+
"prompt": [build_prompt(p, i) for i, p in enumerate(PROGRAMS)],
|
| 154 |
+
})
|
| 155 |
+
print("Dataset rows:", len(train_dataset))
|
| 156 |
+
'''.lstrip("\n")
|
| 157 |
+
return p0 + p1
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
cells.append(cell_code(_cell_chatml_synth()))
|
| 161 |
+
|
| 162 |
+
cells.append(cell_code("""
|
| 163 |
+
def env_reward_for_completion(prompt: str, completion: str, max_env_steps: int = 8) -> float:
|
| 164 |
+
try:
|
| 165 |
+
program = program_from_prompt(prompt)
|
| 166 |
+
except Exception:
|
| 167 |
+
return -8.0
|
| 168 |
+
try:
|
| 169 |
+
actions = Deliverable2_Formatter.extract_action_array(completion)
|
| 170 |
+
except Exception:
|
| 171 |
+
return -5.0
|
| 172 |
+
actions = [str(a).strip() for a in actions][: max_env_steps]
|
| 173 |
+
if not actions:
|
| 174 |
+
return -4.0
|
| 175 |
+
env = CompilerOptimizationEnv(MockEngine(), MOCK_PASSES, max_steps=max_env_steps)
|
| 176 |
+
env.reset(program)
|
| 177 |
+
total = 0.0
|
| 178 |
+
for a in actions:
|
| 179 |
+
step = env.step(a)
|
| 180 |
+
total += float(step.reward)
|
| 181 |
+
if step.done:
|
| 182 |
+
break
|
| 183 |
+
return float(total)
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
def make_reward_function(max_env_steps: int = 8):
|
| 187 |
+
def reward_func(prompts: list, completions: list, **kwargs) -> list:
|
| 188 |
+
return [env_reward_for_completion(p, c, max_env_steps) for p, c in zip(prompts, completions)]
|
| 189 |
+
return reward_func
|
| 190 |
+
"""))
|
| 191 |
+
|
| 192 |
+
cells.append(cell_code("""
|
| 193 |
+
from trl import GRPOConfig, GRPOTrainer
|
| 194 |
+
|
| 195 |
+
MODEL_NAME = "unsloth/Qwen2.5-3B-Instruct"
|
| 196 |
+
MAX_SEQ_LEN = 1024
|
| 197 |
+
MAX_COMPLETION = 256
|
| 198 |
+
|
| 199 |
+
model, tokenizer = FastLanguageModel.from_pretrained(
|
| 200 |
+
model_name=MODEL_NAME,
|
| 201 |
+
max_seq_length=MAX_SEQ_LEN,
|
| 202 |
+
dtype=None,
|
| 203 |
+
load_in_4bit=True,
|
| 204 |
+
)
|
| 205 |
+
model = FastLanguageModel.get_peft_model(
|
| 206 |
+
model,
|
| 207 |
+
r=16, lora_alpha=16, lora_dropout=0.0, bias="none",
|
| 208 |
+
use_gradient_checkpointing="unsloth", random_state=0,
|
| 209 |
+
)
|
| 210 |
+
"""))
|
| 211 |
+
|
| 212 |
+
# Shorter run than 200 steps — scale up after smoke test
|
| 213 |
+
cells.append(
|
| 214 |
+
cell_code(
|
| 215 |
+
"""
|
| 216 |
+
NUM_GENERATIONS = 2
|
| 217 |
+
LEARNING_RATE = 2e-5
|
| 218 |
+
# Reduced from 200: increase after you verify loss/reward is stable
|
| 219 |
+
TRAIN_STEPS = 80
|
| 220 |
+
|
| 221 |
+
# --- Checkpoints (TrainingArguments / GRPO) ---
|
| 222 |
+
GRPO_OUTPUT_DIR = "/content/grpo_compilertetris"
|
| 223 |
+
CHECKPOINT_EVERY = 20 # save a checkpoint every N global steps
|
| 224 |
+
KEEP_LAST_N_CHECKPOINTS = 5 # on disk; older folders are deleted
|
| 225 |
+
|
| 226 |
+
grpo_config = GRPOConfig(
|
| 227 |
+
output_dir=GRPO_OUTPUT_DIR,
|
| 228 |
+
learning_rate=LEARNING_RATE,
|
| 229 |
+
per_device_train_batch_size=NUM_GENERATIONS,
|
| 230 |
+
gradient_accumulation_steps=1,
|
| 231 |
+
num_generations=NUM_GENERATIONS,
|
| 232 |
+
max_completion_length=MAX_COMPLETION,
|
| 233 |
+
max_prompt_length=MAX_SEQ_LEN,
|
| 234 |
+
remove_unused_columns=False,
|
| 235 |
+
temperature=0.7,
|
| 236 |
+
max_steps=TRAIN_STEPS,
|
| 237 |
+
logging_steps=5,
|
| 238 |
+
save_strategy="steps",
|
| 239 |
+
save_steps=CHECKPOINT_EVERY,
|
| 240 |
+
save_total_limit=KEEP_LAST_N_CHECKPOINTS,
|
| 241 |
+
seed=0,
|
| 242 |
+
report_to="none",
|
| 243 |
+
use_vllm=False,
|
| 244 |
+
)
|
| 245 |
+
|
| 246 |
+
reward_fn = make_reward_function()
|
| 247 |
+
|
| 248 |
+
trainer = GRPOTrainer(
|
| 249 |
+
model=model,
|
| 250 |
+
processing_class=tokenizer,
|
| 251 |
+
reward_funcs=[reward_fn],
|
| 252 |
+
args=grpo_config,
|
| 253 |
+
train_dataset=train_dataset,
|
| 254 |
+
)
|
| 255 |
+
"""
|
| 256 |
+
)
|
| 257 |
+
)
|
| 258 |
+
|
| 259 |
+
cells.append(cell_code("""
|
| 260 |
+
print("Starting GRPO (synthetic corpus)…")
|
| 261 |
+
trainer.train()
|
| 262 |
+
print("Done.")
|
| 263 |
+
"""))
|
| 264 |
+
|
| 265 |
+
cells.append(
|
| 266 |
+
cell_code(
|
| 267 |
+
"""
|
| 268 |
+
# List on-disk checkpoints (for resume or manual export)
|
| 269 |
+
import glob, os
|
| 270 |
+
|
| 271 |
+
ckpts = sorted(
|
| 272 |
+
glob.glob(os.path.join(GRPO_OUTPUT_DIR, "checkpoint-*")),
|
| 273 |
+
key=lambda p: int(p.split("checkpoint-")[-1]) if p.split("checkpoint-")[-1].isdigit() else 0,
|
| 274 |
+
)
|
| 275 |
+
print("Checkpoints in", GRPO_OUTPUT_DIR, ":", len(ckpts))
|
| 276 |
+
for c in ckpts:
|
| 277 |
+
print(" ", c)
|
| 278 |
+
if ckpts:
|
| 279 |
+
print("Latest:", ckpts[-1])
|
| 280 |
+
"""
|
| 281 |
+
)
|
| 282 |
+
)
|
| 283 |
+
|
| 284 |
+
cells.append(
|
| 285 |
+
cell_md(
|
| 286 |
+
"""
|
| 287 |
+
**Resume after disconnect / crash** — re-run: clone, pip, imports, dataset, reward, **model load**, and **trainer** cells. Then run **one** of:
|
| 288 |
+
|
| 289 |
+
- `trainer.train(resume_from_checkpoint=True)` — continues from the latest `checkpoint-*` in `output_dir`
|
| 290 |
+
- `trainer.train(resume_from_checkpoint="/content/grpo_compilertetris/checkpoint-40")` — specific step
|
| 291 |
+
"""
|
| 292 |
+
)
|
| 293 |
+
)
|
| 294 |
+
|
| 295 |
+
cells.append(
|
| 296 |
+
cell_code(
|
| 297 |
+
"""
|
| 298 |
+
# Uncomment to resume from the latest checkpoint (run after re-creating `trainer` in a new session)
|
| 299 |
+
# trainer.train(resume_from_checkpoint=True)
|
| 300 |
+
"""
|
| 301 |
+
)
|
| 302 |
+
)
|
| 303 |
+
|
| 304 |
+
cells.append(
|
| 305 |
+
cell_code(
|
| 306 |
+
"""
|
| 307 |
+
SAVE_DIR = "/content/compilertetris_lora"
|
| 308 |
+
trainer.model.save_pretrained(SAVE_DIR)
|
| 309 |
+
tokenizer.save_pretrained(SAVE_DIR)
|
| 310 |
+
print("Saved to", SAVE_DIR)
|
| 311 |
+
"""
|
| 312 |
+
)
|
| 313 |
+
)
|
| 314 |
+
|
| 315 |
+
cells.append(
|
| 316 |
+
cell_code(
|
| 317 |
+
"""
|
| 318 |
+
from huggingface_hub import login, HfApi
|
| 319 |
+
|
| 320 |
+
login()
|
| 321 |
+
HfApi().create_repo(HF_ADAPTER_REPO, exist_ok=True, repo_type="model")
|
| 322 |
+
trainer.model.push_to_hub(HF_ADAPTER_REPO, private=True)
|
| 323 |
+
tokenizer.push_to_hub(HF_ADAPTER_REPO, private=True)
|
| 324 |
+
print("Pushed to https://huggingface.co/" + HF_ADAPTER_REPO)
|
| 325 |
+
"""
|
| 326 |
+
)
|
| 327 |
+
)
|
| 328 |
+
|
| 329 |
+
if __name__ == "__main__":
|
| 330 |
+
nb = {
|
| 331 |
+
"nbformat": 4,
|
| 332 |
+
"nbformat_minor": 5,
|
| 333 |
+
"metadata": {
|
| 334 |
+
"colab": {"provenance": [], "gpuType": "T4"},
|
| 335 |
+
"kernelspec": {
|
| 336 |
+
"display_name": "Python 3",
|
| 337 |
+
"language": "python",
|
| 338 |
+
"name": "python3",
|
| 339 |
+
},
|
| 340 |
+
"language_info": {"name": "python"},
|
| 341 |
+
},
|
| 342 |
+
"cells": cells,
|
| 343 |
+
}
|
| 344 |
+
OUT.write_text(json.dumps(nb, indent=2), encoding="utf-8")
|
| 345 |
+
print("Wrote", OUT)
|
space/space/space/space/write_colab_synth_notebook.py
CHANGED
|
@@ -26,19 +26,21 @@ def cell_code(s: str) -> dict:
|
|
| 26 |
cells: list = []
|
| 27 |
|
| 28 |
cells.append(cell_md("""
|
| 29 |
-
# Compiler Tetris — GRPO
|
| 30 |
|
| 31 |
-
|
|
|
|
|
|
|
|
|
|
| 32 |
|
| 33 |
| What | Value |
|
| 34 |
|------|--------|
|
| 35 |
| Space | `greedybeserk95/Compilertetris` |
|
| 36 |
-
| Code root
|
| 37 |
-
| LoRA
|
| 38 |
-
|
|
| 39 |
-
| **Checkpoints** | Under `output_dir` as `checkpoint-*` (see `CHECKPOINT_EVERY` / `KEEP_LAST_N_CHECKPOINTS` in the GRPO cell) |
|
| 40 |
|
| 41 |
-
|
| 42 |
"""))
|
| 43 |
|
| 44 |
cells.append(
|
|
@@ -105,8 +107,8 @@ IM_END = \"<\" + \"|im_end|>\"
|
|
| 105 |
p1 = r'''
|
| 106 |
import re
|
| 107 |
|
| 108 |
-
#
|
| 109 |
-
N_TRAIN_PROGRAMS =
|
| 110 |
RANDOM_SEED = 42
|
| 111 |
|
| 112 |
SYSTEM_PROMPT = f"""You are a compiler optimization agent.
|
|
@@ -183,7 +185,7 @@ def env_reward_for_completion(prompt: str, completion: str, max_env_steps: int =
|
|
| 183 |
return float(total)
|
| 184 |
|
| 185 |
|
| 186 |
-
def make_reward_function(max_env_steps: int =
|
| 187 |
def reward_func(prompts: list, completions: list, **kwargs) -> list:
|
| 188 |
return [env_reward_for_completion(p, c, max_env_steps) for p, c in zip(prompts, completions)]
|
| 189 |
return reward_func
|
|
@@ -192,6 +194,7 @@ def make_reward_function(max_env_steps: int = 8):
|
|
| 192 |
cells.append(cell_code("""
|
| 193 |
from trl import GRPOConfig, GRPOTrainer
|
| 194 |
|
|
|
|
| 195 |
MODEL_NAME = "unsloth/Qwen2.5-3B-Instruct"
|
| 196 |
MAX_SEQ_LEN = 1024
|
| 197 |
MAX_COMPLETION = 256
|
|
@@ -204,37 +207,40 @@ model, tokenizer = FastLanguageModel.from_pretrained(
|
|
| 204 |
)
|
| 205 |
model = FastLanguageModel.get_peft_model(
|
| 206 |
model,
|
| 207 |
-
r=
|
| 208 |
use_gradient_checkpointing="unsloth", random_state=0,
|
| 209 |
)
|
|
|
|
| 210 |
"""))
|
| 211 |
|
| 212 |
-
#
|
| 213 |
cells.append(
|
| 214 |
cell_code(
|
| 215 |
"""
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
|
|
|
|
|
|
|
|
|
| 220 |
|
| 221 |
-
# --- Checkpoints (TrainingArguments / GRPO) ---
|
| 222 |
GRPO_OUTPUT_DIR = "/content/grpo_compilertetris"
|
| 223 |
-
CHECKPOINT_EVERY =
|
| 224 |
-
KEEP_LAST_N_CHECKPOINTS =
|
| 225 |
|
| 226 |
grpo_config = GRPOConfig(
|
| 227 |
output_dir=GRPO_OUTPUT_DIR,
|
| 228 |
learning_rate=LEARNING_RATE,
|
| 229 |
per_device_train_batch_size=NUM_GENERATIONS,
|
| 230 |
-
gradient_accumulation_steps=
|
| 231 |
num_generations=NUM_GENERATIONS,
|
| 232 |
max_completion_length=MAX_COMPLETION,
|
| 233 |
max_prompt_length=MAX_SEQ_LEN,
|
| 234 |
remove_unused_columns=False,
|
| 235 |
temperature=0.7,
|
| 236 |
max_steps=TRAIN_STEPS,
|
| 237 |
-
logging_steps=
|
| 238 |
save_strategy="steps",
|
| 239 |
save_steps=CHECKPOINT_EVERY,
|
| 240 |
save_total_limit=KEEP_LAST_N_CHECKPOINTS,
|
|
@@ -287,7 +293,7 @@ cells.append(
|
|
| 287 |
**Resume after disconnect / crash** — re-run: clone, pip, imports, dataset, reward, **model load**, and **trainer** cells. Then run **one** of:
|
| 288 |
|
| 289 |
- `trainer.train(resume_from_checkpoint=True)` — continues from the latest `checkpoint-*` in `output_dir`
|
| 290 |
-
- `trainer.train(resume_from_checkpoint="/content/grpo_compilertetris/checkpoint-
|
| 291 |
"""
|
| 292 |
)
|
| 293 |
)
|
|
|
|
| 26 |
cells: list = []
|
| 27 |
|
| 28 |
cells.append(cell_md("""
|
| 29 |
+
# Compiler Tetris — GRPO, **synthetic Toy-IR** (Colab **T4 ~8–10 h** preset)
|
| 30 |
|
| 31 |
+
**Runtime (estimate):** after ~20 steps, read `it/s` in the progress bar. Approximate hours ≈ `TRAIN_STEPS / (it/s * 3600)`.
|
| 32 |
+
With **`TRAIN_STEPS = 3600`**: at **0.10 it/s** that is about **10 h**; at **0.12 it/s** about **8.3 h**. If you finish much faster/slower, change **`TRAIN_STEPS`** (or the model size / `NUM_GENERATIONS`).
|
| 33 |
+
|
| 34 |
+
**T4 tips:** 3B + 4bit + below settings fit T4; **7B** or very long `MAX_COMPLETION` can OOM. **High-RAM** runtime in Colab helps. Use **checkpoints** (next cells) in case the session dies.
|
| 35 |
|
| 36 |
| What | Value |
|
| 37 |
|------|--------|
|
| 38 |
| Space | `greedybeserk95/Compilertetris` |
|
| 39 |
+
| Code root | `/content/Compilertetris` |
|
| 40 |
+
| LoRA out | `/content/compilertetris_lora` |
|
| 41 |
+
| **Checkpoints** | `/content/grpo_compilertetris/checkpoint-*` |
|
|
|
|
| 42 |
|
| 43 |
+
`program_generator` — Toy-IR in `runtime_core` shape; `metahack1` uses a different schema.
|
| 44 |
"""))
|
| 45 |
|
| 46 |
cells.append(
|
|
|
|
| 107 |
p1 = r'''
|
| 108 |
import re
|
| 109 |
|
| 110 |
+
# T4: 200–300 is a good tradeoff (RAM + diversity)
|
| 111 |
+
N_TRAIN_PROGRAMS = 250
|
| 112 |
RANDOM_SEED = 42
|
| 113 |
|
| 114 |
SYSTEM_PROMPT = f"""You are a compiler optimization agent.
|
|
|
|
| 185 |
return float(total)
|
| 186 |
|
| 187 |
|
| 188 |
+
def make_reward_function(max_env_steps: int = 10):
|
| 189 |
def reward_func(prompts: list, completions: list, **kwargs) -> list:
|
| 190 |
return [env_reward_for_completion(p, c, max_env_steps) for p, c in zip(prompts, completions)]
|
| 191 |
return reward_func
|
|
|
|
| 194 |
cells.append(cell_code("""
|
| 195 |
from trl import GRPOConfig, GRPOTrainer
|
| 196 |
|
| 197 |
+
# --- T4-friendly (3B 4-bit). For A100+ you can try Qwen2.5-7B and MAX_COMPLETION=384. ---
|
| 198 |
MODEL_NAME = "unsloth/Qwen2.5-3B-Instruct"
|
| 199 |
MAX_SEQ_LEN = 1024
|
| 200 |
MAX_COMPLETION = 256
|
|
|
|
| 207 |
)
|
| 208 |
model = FastLanguageModel.get_peft_model(
|
| 209 |
model,
|
| 210 |
+
r=32, lora_alpha=32, lora_dropout=0.0, bias="none",
|
| 211 |
use_gradient_checkpointing="unsloth", random_state=0,
|
| 212 |
)
|
| 213 |
+
print("Model:", MODEL_NAME, "| max_seq", MAX_SEQ_LEN, "| completion cap", MAX_COMPLETION)
|
| 214 |
"""))
|
| 215 |
|
| 216 |
+
# ---- T4 / ~8–10 h wall time: tune TRAIN_STEPS after you see it/s in the first minutes ----
|
| 217 |
cells.append(
|
| 218 |
cell_code(
|
| 219 |
"""
|
| 220 |
+
# 4 rollouts per prompt: good for GRPO on T4; 6–8 is heavier (slower, more VRAM)
|
| 221 |
+
NUM_GENERATIONS = 4
|
| 222 |
+
LEARNING_RATE = 1.5e-5
|
| 223 |
+
# Target ~8–10 h on T4 when it/s is ~0.10–0.12 (typical for this stack). Re-tune if your it/s differs.
|
| 224 |
+
# hours ≈ TRAIN_STEPS / (it/s * 3600)
|
| 225 |
+
TRAIN_STEPS = 3600
|
| 226 |
+
GRAD_ACCUM = 2
|
| 227 |
|
|
|
|
| 228 |
GRPO_OUTPUT_DIR = "/content/grpo_compilertetris"
|
| 229 |
+
CHECKPOINT_EVERY = 200
|
| 230 |
+
KEEP_LAST_N_CHECKPOINTS = 3
|
| 231 |
|
| 232 |
grpo_config = GRPOConfig(
|
| 233 |
output_dir=GRPO_OUTPUT_DIR,
|
| 234 |
learning_rate=LEARNING_RATE,
|
| 235 |
per_device_train_batch_size=NUM_GENERATIONS,
|
| 236 |
+
gradient_accumulation_steps=GRAD_ACCUM,
|
| 237 |
num_generations=NUM_GENERATIONS,
|
| 238 |
max_completion_length=MAX_COMPLETION,
|
| 239 |
max_prompt_length=MAX_SEQ_LEN,
|
| 240 |
remove_unused_columns=False,
|
| 241 |
temperature=0.7,
|
| 242 |
max_steps=TRAIN_STEPS,
|
| 243 |
+
logging_steps=20,
|
| 244 |
save_strategy="steps",
|
| 245 |
save_steps=CHECKPOINT_EVERY,
|
| 246 |
save_total_limit=KEEP_LAST_N_CHECKPOINTS,
|
|
|
|
| 293 |
**Resume after disconnect / crash** — re-run: clone, pip, imports, dataset, reward, **model load**, and **trainer** cells. Then run **one** of:
|
| 294 |
|
| 295 |
- `trainer.train(resume_from_checkpoint=True)` — continues from the latest `checkpoint-*` in `output_dir`
|
| 296 |
+
- `trainer.train(resume_from_checkpoint="/content/grpo_compilertetris/checkpoint-300")` — example path (use an existing `checkpoint-*` folder)
|
| 297 |
"""
|
| 298 |
)
|
| 299 |
)
|
write_colab_synth_notebook.py
CHANGED
|
@@ -36,11 +36,13 @@ With **`TRAIN_STEPS = 3600`**: at **0.10 it/s** that is about **10 h**; at **0.1
|
|
| 36 |
| What | Value |
|
| 37 |
|------|--------|
|
| 38 |
| Space | `greedybeserk95/Compilertetris` |
|
| 39 |
-
| Code root | `
|
| 40 |
-
| LoRA out | `
|
| 41 |
-
| **Checkpoints** | `/
|
| 42 |
|
| 43 |
`program_generator` — Toy-IR in `runtime_core` shape; `metahack1` uses a different schema.
|
|
|
|
|
|
|
| 44 |
"""))
|
| 45 |
|
| 46 |
cells.append(
|
|
@@ -48,21 +50,49 @@ cells.append(
|
|
| 48 |
"""
|
| 49 |
## Checkpoints
|
| 50 |
|
| 51 |
-
The GRPO config uses `save_strategy="steps"` and `save_total_limit` so training writes **periodic checkpoints** under `output_dir` (e.g. `/
|
| 52 |
"""
|
| 53 |
)
|
| 54 |
)
|
| 55 |
|
| 56 |
cells.append(cell_code("""
|
| 57 |
-
# --- Central path config ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
|
| 59 |
HF_SPACE_REPO = "https://huggingface.co/spaces/greedybeserk95/Compilertetris"
|
| 60 |
HF_ADAPTER_REPO = "greedybeserk95/Compilertetris-grpo-adapter"
|
| 61 |
-
REPO_DIR =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
"""))
|
| 63 |
|
| 64 |
cells.append(cell_code("""
|
| 65 |
import os, subprocess
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
|
| 67 |
if os.path.isdir(REPO_DIR + "/.git"):
|
| 68 |
subprocess.check_call(["git", "-C", REPO_DIR, "pull", "--ff-only"], timeout=300)
|
|
@@ -74,16 +104,22 @@ print("Repo:", REPO_DIR)
|
|
| 74 |
cells.append(cell_code("""!nvidia-smi"""))
|
| 75 |
|
| 76 |
cells.append(cell_code("""
|
| 77 |
-
|
| 78 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
"""))
|
| 80 |
|
| 81 |
cells.append(cell_code("""
|
|
|
|
|
|
|
| 82 |
import sys, json, os
|
| 83 |
sys.path.insert(0, REPO_DIR)
|
| 84 |
|
| 85 |
import torch
|
| 86 |
-
import unsloth
|
| 87 |
from unsloth import FastLanguageModel
|
| 88 |
from datasets import Dataset
|
| 89 |
|
|
@@ -225,7 +261,7 @@ LEARNING_RATE = 1.5e-5
|
|
| 225 |
TRAIN_STEPS = 3600
|
| 226 |
GRAD_ACCUM = 2
|
| 227 |
|
| 228 |
-
GRPO_OUTPUT_DIR
|
| 229 |
CHECKPOINT_EVERY = 200
|
| 230 |
KEEP_LAST_N_CHECKPOINTS = 3
|
| 231 |
|
|
@@ -268,6 +304,101 @@ trainer.train()
|
|
| 268 |
print("Done.")
|
| 269 |
"""))
|
| 270 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 271 |
cells.append(
|
| 272 |
cell_code(
|
| 273 |
"""
|
|
@@ -293,7 +424,7 @@ cells.append(
|
|
| 293 |
**Resume after disconnect / crash** — re-run: clone, pip, imports, dataset, reward, **model load**, and **trainer** cells. Then run **one** of:
|
| 294 |
|
| 295 |
- `trainer.train(resume_from_checkpoint=True)` — continues from the latest `checkpoint-*` in `output_dir`
|
| 296 |
-
- `trainer.train(resume_from_checkpoint="/
|
| 297 |
"""
|
| 298 |
)
|
| 299 |
)
|
|
@@ -310,7 +441,7 @@ cells.append(
|
|
| 310 |
cells.append(
|
| 311 |
cell_code(
|
| 312 |
"""
|
| 313 |
-
SAVE_DIR =
|
| 314 |
trainer.model.save_pretrained(SAVE_DIR)
|
| 315 |
tokenizer.save_pretrained(SAVE_DIR)
|
| 316 |
print("Saved to", SAVE_DIR)
|
|
|
|
| 36 |
| What | Value |
|
| 37 |
|------|--------|
|
| 38 |
| Space | `greedybeserk95/Compilertetris` |
|
| 39 |
+
| Code root | `REPO_DIR` (printed in the “paths” cell) |
|
| 40 |
+
| LoRA out | `LORA_DIR` (printed in the “paths” cell) |
|
| 41 |
+
| **Checkpoints** | `GRPO_OUTPUT_DIR + "/checkpoint-*"` |
|
| 42 |
|
| 43 |
`program_generator` — Toy-IR in `runtime_core` shape; `metahack1` uses a different schema.
|
| 44 |
+
|
| 45 |
+
**Training evidence (loss + reward plots):** the cell *after* `trainer.train()` saves `training_loss_and_reward.png` under `output_dir` and shows it in the notebook — use it in your README / writeup.
|
| 46 |
"""))
|
| 47 |
|
| 48 |
cells.append(
|
|
|
|
| 50 |
"""
|
| 51 |
## Checkpoints
|
| 52 |
|
| 53 |
+
The GRPO config uses `save_strategy="steps"` and `save_total_limit` so training writes **periodic checkpoints** under `output_dir` (e.g. `$GRPO_OUTPUT_DIR/checkpoint-20`, …) and prunes old ones. After a crash, re-run the setup cells, rebuild `trainer`, then use `resume_from_checkpoint=True` (latest) or a **specific path** (see the cell after training).
|
| 54 |
"""
|
| 55 |
)
|
| 56 |
)
|
| 57 |
|
| 58 |
cells.append(cell_code("""
|
| 59 |
+
# --- Central path config (works on Colab *and* local / HF runtimes) ---
|
| 60 |
+
|
| 61 |
+
import os
|
| 62 |
+
import tempfile
|
| 63 |
+
from pathlib import Path
|
| 64 |
+
|
| 65 |
+
def _default_workspace_base() -> str:
|
| 66 |
+
# Colab: /content is writable
|
| 67 |
+
c = "/content"
|
| 68 |
+
if os.path.isdir(c) and os.access(c, os.W_OK):
|
| 69 |
+
return str(Path(c) / "work")
|
| 70 |
+
|
| 71 |
+
# Otherwise: a guaranteed-writable temp dir
|
| 72 |
+
return str(Path(tempfile.gettempdir()) / "compilertetris_work")
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
BASE = os.environ.get("COMPILERTETRIS_BASE", _default_workspace_base())
|
| 76 |
+
os.makedirs(BASE, exist_ok=True)
|
| 77 |
|
| 78 |
HF_SPACE_REPO = "https://huggingface.co/spaces/greedybeserk95/Compilertetris"
|
| 79 |
HF_ADAPTER_REPO = "greedybeserk95/Compilertetris-grpo-adapter"
|
| 80 |
+
REPO_DIR = str(Path(BASE) / "Compilertetris")
|
| 81 |
+
GRPO_OUTPUT_DIR = str(Path(BASE) / "grpo_compilertetris")
|
| 82 |
+
LORA_DIR = str(Path(BASE) / "compilertetris_lora")
|
| 83 |
+
|
| 84 |
+
print("BASE :", BASE)
|
| 85 |
+
print("REPO_DIR :", REPO_DIR)
|
| 86 |
+
print("GRPO_OUTPUT :", GRPO_OUTPUT_DIR)
|
| 87 |
+
print("LORA_DIR :", LORA_DIR)
|
| 88 |
"""))
|
| 89 |
|
| 90 |
cells.append(cell_code("""
|
| 91 |
import os, subprocess
|
| 92 |
+
from pathlib import Path
|
| 93 |
+
|
| 94 |
+
# Ensure parent is writable/created
|
| 95 |
+
Path(REPO_DIR).parent.mkdir(parents=True, exist_ok=True)
|
| 96 |
|
| 97 |
if os.path.isdir(REPO_DIR + "/.git"):
|
| 98 |
subprocess.check_call(["git", "-C", REPO_DIR, "pull", "--ff-only"], timeout=300)
|
|
|
|
| 104 |
cells.append(cell_code("""!nvidia-smi"""))
|
| 105 |
|
| 106 |
cells.append(cell_code("""
|
| 107 |
+
# One-shot install (A100 + CUDA 12.x):
|
| 108 |
+
# - Unsloth's `cu124-ampere-torch260` extra pulls a **Torch 2.6** CUDA stack (fixes `torch.int1` / torchao mismatches)
|
| 109 |
+
# - Pin TRL+Transformers to a Unsloth-tested pair (per Unsloth issue threads around torch2.6 / transformers4.55.x)
|
| 110 |
+
!python -m pip install -U --no-cache-dir \
|
| 111 |
+
"transformers==4.55.4" "trl==0.20.0" \
|
| 112 |
+
matplotlib datasets accelerate peft bitsandbytes safetensors huggingface_hub sentencepiece \
|
| 113 |
+
"unsloth[cu124-ampere-torch260] @ git+https://github.com/unslothai/unsloth.git"
|
| 114 |
"""))
|
| 115 |
|
| 116 |
cells.append(cell_code("""
|
| 117 |
+
# `import unsloth` should come before `transformers` is imported (happens via other imports).
|
| 118 |
+
import unsloth
|
| 119 |
import sys, json, os
|
| 120 |
sys.path.insert(0, REPO_DIR)
|
| 121 |
|
| 122 |
import torch
|
|
|
|
| 123 |
from unsloth import FastLanguageModel
|
| 124 |
from datasets import Dataset
|
| 125 |
|
|
|
|
| 261 |
TRAIN_STEPS = 3600
|
| 262 |
GRAD_ACCUM = 2
|
| 263 |
|
| 264 |
+
# GRPO_OUTPUT_DIR is set in the "Central path config" cell
|
| 265 |
CHECKPOINT_EVERY = 200
|
| 266 |
KEEP_LAST_N_CHECKPOINTS = 3
|
| 267 |
|
|
|
|
| 304 |
print("Done.")
|
| 305 |
"""))
|
| 306 |
|
| 307 |
+
cells.append(
|
| 308 |
+
cell_code(
|
| 309 |
+
r"""
|
| 310 |
+
# --- Loss + reward plots (submission): from trainer.state.log_history after train() ---
|
| 311 |
+
|
| 312 |
+
!pip install -q matplotlib
|
| 313 |
+
|
| 314 |
+
import os
|
| 315 |
+
import matplotlib
|
| 316 |
+
matplotlib.use("Agg")
|
| 317 |
+
import matplotlib.pyplot as plt
|
| 318 |
+
|
| 319 |
+
PLOT_DIR = GRPO_OUTPUT_DIR
|
| 320 |
+
os.makedirs(PLOT_DIR, exist_ok=True)
|
| 321 |
+
PNG_PATH = os.path.join(PLOT_DIR, "training_loss_and_reward.png")
|
| 322 |
+
|
| 323 |
+
def _reward_from_log_row(h):
|
| 324 |
+
v = h.get("reward")
|
| 325 |
+
if isinstance(v, (int, float)):
|
| 326 |
+
return float(v), "reward"
|
| 327 |
+
best_k, best_v = None, None
|
| 328 |
+
for k, v in h.items():
|
| 329 |
+
if not isinstance(v, (int, float)):
|
| 330 |
+
continue
|
| 331 |
+
klow = k.replace("-", "/").lower()
|
| 332 |
+
if "reward" not in klow or "std" in klow or "clip" in klow or "per_token" in klow:
|
| 333 |
+
continue
|
| 334 |
+
if "mean" in klow or k == "reward":
|
| 335 |
+
return float(v), k
|
| 336 |
+
if "mean" not in klow and best_k is None:
|
| 337 |
+
best_k, best_v = k, float(v)
|
| 338 |
+
if best_k is not None:
|
| 339 |
+
return best_v, best_k
|
| 340 |
+
return None, None
|
| 341 |
+
|
| 342 |
+
def extract_series(history):
|
| 343 |
+
sl, vl, sr, vr = [], [], [], []
|
| 344 |
+
rlabel = None
|
| 345 |
+
for h in history:
|
| 346 |
+
s = h.get("step")
|
| 347 |
+
if s is None:
|
| 348 |
+
continue
|
| 349 |
+
lo = h.get("loss")
|
| 350 |
+
if isinstance(lo, (int, float)):
|
| 351 |
+
sl.append(s)
|
| 352 |
+
vl.append(float(lo))
|
| 353 |
+
r_val, rk = _reward_from_log_row(h)
|
| 354 |
+
if r_val is not None and rk:
|
| 355 |
+
if rlabel is None:
|
| 356 |
+
rlabel = rk
|
| 357 |
+
if rk == rlabel:
|
| 358 |
+
sr.append(s)
|
| 359 |
+
vr.append(r_val)
|
| 360 |
+
return (sl, vl, "loss"), (sr, vr, rlabel or "reward")
|
| 361 |
+
|
| 362 |
+
(loss_s, loss_v, _lk), (rew_s, rew_v, rew_lab) = extract_series(trainer.state.log_history)
|
| 363 |
+
print("Points — loss:", len(loss_v), "| reward:", len(rew_v))
|
| 364 |
+
if trainer.state.log_history:
|
| 365 |
+
print("Last log row keys (sample):", list(trainer.state.log_history[-1].keys())[:25])
|
| 366 |
+
|
| 367 |
+
fig, (ax0, ax1) = plt.subplots(2, 1, figsize=(10, 6), sharex=True)
|
| 368 |
+
if loss_v:
|
| 369 |
+
ax0.plot(loss_s, loss_v, "b.-", label="loss", linewidth=1, markersize=2)
|
| 370 |
+
ax0.set_ylabel("training loss")
|
| 371 |
+
ax0.set_title("GRPO (this Colab run)")
|
| 372 |
+
ax0.grid(True, alpha=0.3)
|
| 373 |
+
ax0.legend()
|
| 374 |
+
else:
|
| 375 |
+
ax0.text(0.5, 0.5, "No 'loss' in log_history", ha="center", transform=ax0.transAxes)
|
| 376 |
+
|
| 377 |
+
if rew_v:
|
| 378 |
+
ax1.plot(rew_s, rew_v, "g.-", label=rew_lab, linewidth=1, markersize=2)
|
| 379 |
+
ax1.set_ylabel("mean reward" if "mean" in (rew_lab or "") else "reward")
|
| 380 |
+
ax1.set_xlabel("global step")
|
| 381 |
+
ax1.grid(True, alpha=0.3)
|
| 382 |
+
ax1.legend()
|
| 383 |
+
else:
|
| 384 |
+
ax1.text(0.5, 0.5, "No reward column found — see keys above", ha="center", transform=ax1.transAxes)
|
| 385 |
+
ax1.set_xlabel("global step")
|
| 386 |
+
|
| 387 |
+
plt.tight_layout()
|
| 388 |
+
plt.savefig(PNG_PATH, dpi=150, bbox_inches="tight")
|
| 389 |
+
print("Saved:", PNG_PATH)
|
| 390 |
+
try:
|
| 391 |
+
from IPython.display import Image, display
|
| 392 |
+
display(Image(PNG_PATH))
|
| 393 |
+
except Exception as e:
|
| 394 |
+
print("Display:", e)
|
| 395 |
+
finally:
|
| 396 |
+
plt.close("all")
|
| 397 |
+
print("If keys differ: print(trainer.state.log_history[-1])")
|
| 398 |
+
"""
|
| 399 |
+
)
|
| 400 |
+
)
|
| 401 |
+
|
| 402 |
cells.append(
|
| 403 |
cell_code(
|
| 404 |
"""
|
|
|
|
| 424 |
**Resume after disconnect / crash** — re-run: clone, pip, imports, dataset, reward, **model load**, and **trainer** cells. Then run **one** of:
|
| 425 |
|
| 426 |
- `trainer.train(resume_from_checkpoint=True)` — continues from the latest `checkpoint-*` in `output_dir`
|
| 427 |
+
- `trainer.train(resume_from_checkpoint="<your GRPO_OUTPUT_DIR>/checkpoint-300")` — example (use an existing `checkpoint-*` folder; print `GRPO_OUTPUT_DIR` from the path cell)
|
| 428 |
"""
|
| 429 |
)
|
| 430 |
)
|
|
|
|
| 441 |
cells.append(
|
| 442 |
cell_code(
|
| 443 |
"""
|
| 444 |
+
SAVE_DIR = LORA_DIR
|
| 445 |
trainer.model.save_pretrained(SAVE_DIR)
|
| 446 |
tokenizer.save_pretrained(SAVE_DIR)
|
| 447 |
print("Saved to", SAVE_DIR)
|