dora7's picture
Albedo SN97 workspace v16: RECORD, merged v16, adapters v13/v15/v16-dpo, packs, harness, evals
2abcc30 verified
Raw
History Blame Contribute Delete
14.2 kB
from __future__ import annotations
import argparse
import json
import os
import subprocess
import sys
from pathlib import Path
from .constants import (
DEFAULT_ADAPTER_DIR,
DEFAULT_DATA_ROOT,
DEFAULT_DPO_ADAPTER_DIR,
DEFAULT_DPO_EXPORT_DIR,
DEFAULT_DPO_PACK,
DEFAULT_EXPORT_DIR,
DEFAULT_PACK_DIR,
DEFAULT_RL_ADAPTER_DIR,
DEFAULT_RL_EXPORT_DIR,
GENESIS_DIR,
)
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description="Albedo challenger SFT → export → duel → GRPO")
sub = parser.add_subparsers(dest="cmd", required=True)
p = sub.add_parser("pack", help="build the SFT jsonl pack from official parquet")
p.add_argument("--dataset-root", type=Path, default=DEFAULT_DATA_ROOT)
p.add_argument("--out-dir", type=Path, default=DEFAULT_PACK_DIR)
p.add_argument("--max-examples", type=int, default=20_000)
p.add_argument("--seed", default="sft-pack")
p.add_argument("--n-salts", type=int, default=2)
p.add_argument("--submit-frac", type=float, default=0.20)
p.add_argument("--edit-frac", type=float, default=0.35)
p.add_argument(
"--expand-submit-salts",
action="store_true",
help="repeat each gold submit under every salt so all markers/tails are seen",
)
p.add_argument("--smoke", action="store_true", help="64-example dry pack")
p = sub.add_parser("sft", help="LoRA SFT from genesis on a packed jsonl")
p.add_argument("--pack", type=Path, default=None)
p.add_argument("--out-dir", type=Path, default=DEFAULT_ADAPTER_DIR)
p.add_argument("--model", type=Path, default=GENESIS_DIR)
p.add_argument("--max-steps", type=int, default=None)
p.add_argument("--epochs", type=float, default=1.0)
p.add_argument("--max-seq-len", type=int, default=16384)
p.add_argument("--lr", type=float, default=1e-4)
p.add_argument("--smoke", action="store_true")
p = sub.add_parser("export", help="merge LoRA and copy genesis metadata byte-for-byte")
p.add_argument("--adapter", type=Path, default=DEFAULT_ADAPTER_DIR)
p.add_argument("--genesis", type=Path, default=GENESIS_DIR)
p.add_argument("--out-dir", type=Path, default=DEFAULT_EXPORT_DIR)
p.add_argument("--base", type=Path, default=None)
p = sub.add_parser("check", help="sha256 metadata vs genesis + albedo check-model")
p.add_argument("--path", type=Path, required=True)
p.add_argument("--genesis", type=Path, default=GENESIS_DIR)
p = sub.add_parser("duel", help="local_eval challenger vs genesis + go/no-go")
p.add_argument("--challenger", type=Path, default=DEFAULT_EXPORT_DIR)
p.add_argument("--king", type=Path, default=GENESIS_DIR)
p.add_argument("--samples", type=int, default=8)
p.add_argument("--turns", type=int, default=12)
p.add_argument("--seed", default="local-eval")
p.add_argument("--skip-king", action="store_true")
p = sub.add_parser("chain-pack", help="SFT rows for live sanity chain (edit→submit→reject→edit→submit)")
p.add_argument("--dataset-root", type=Path, default=DEFAULT_DATA_ROOT)
p.add_argument("--out-dir", type=Path, default=DEFAULT_PACK_DIR)
p.add_argument("--max-groups", type=int, default=200)
p.add_argument("--seed", default="chain-pack")
p.add_argument("--n-salts", type=int, default=3)
p.add_argument("--mix-pack", type=Path, default=None)
p.add_argument("--mix-n", type=int, default=2000)
p.add_argument(
"--mix-edit-n",
type=int,
default=0,
help="official at_edit rows to mix (Stage A). Overrides --mix-n when set with --mix-submit-n",
)
p.add_argument(
"--mix-submit-n",
type=int,
default=0,
help="official submit-after-edit rows to mix (Stage A)",
)
p.add_argument("--smoke", action="store_true")
p = sub.add_parser(
"chain-gold",
help="compile 6-turn sessions that pass evaluate_chain, then slice for SFT",
)
p.add_argument("--dataset-root", type=Path, default=DEFAULT_DATA_ROOT)
p.add_argument("--out-dir", type=Path, default=DEFAULT_PACK_DIR)
p.add_argument("--max-groups", type=int, default=150)
p.add_argument("--seed", default="chain-gold")
p.add_argument("--n-salts", type=int, default=3)
p.add_argument("--mix-pack", type=Path, default=None)
p.add_argument("--mix-edit-n", type=int, default=800)
p.add_argument(
"--mix-submit-n",
type=int,
default=0,
help="official post-edit submit rows to mix (keep small vs edits)",
)
p.add_argument("--smoke", action="store_true")
p = sub.add_parser("chain", help="local live-sanity chain eval (reject first submit)")
p.add_argument("--challenger", type=Path, default=DEFAULT_RL_EXPORT_DIR)
p.add_argument(
"--samples",
type=int,
default=6,
help="3 live-gate files first, then random extras (default 6 = 3+3 transfer)",
)
p.add_argument("--turns", type=int, default=32)
p.add_argument("--seed", default="chain-eval")
p.add_argument(
"--no-live-gate",
action="store_true",
help="skip the 3 oversampled gate files; draw random samples only",
)
p = sub.add_parser(
"dpo-pack",
help="chosen/rejected pairs for the live empty-submit policy (not the local 3 files)",
)
p.add_argument("--out-dir", type=Path, default=DEFAULT_PACK_DIR)
p.add_argument("--seed", default="dpo-live-fault-v16")
p.add_argument(
"--narrow",
action="store_true",
help="only after-submit empty-echo pairs on toml/config paths (v16 random-6 fails)",
)
p = sub.add_parser("dpo", help="LoRA DPO on live-fault pairs (prefer this over GRPO)")
p.add_argument("--pack", type=Path, default=DEFAULT_DPO_PACK)
p.add_argument("--model", type=Path, default=Path("/workspace/models/albedo-challenger-v15"))
p.add_argument("--out-dir", type=Path, default=DEFAULT_DPO_ADAPTER_DIR)
p.add_argument("--max-steps", type=int, default=30)
p.add_argument("--max-seq-len", type=int, default=4096)
p.add_argument("--lr", type=float, default=5e-6)
p.add_argument("--beta", type=float, default=0.1)
p.add_argument("--smoke", action="store_true")
p = sub.add_parser(
"policy",
help="one-turn live-fault prefixes: next command must be sed -i, never a marker",
)
p.add_argument("--challenger", type=Path, default=DEFAULT_DPO_EXPORT_DIR)
p = sub.add_parser("rl", help="light GRPO on the SFT export (not proxy_score)")
p.add_argument("--pack", type=Path, default=None)
p.add_argument("--model", type=Path, default=DEFAULT_EXPORT_DIR)
p.add_argument("--out-dir", type=Path, default=DEFAULT_RL_ADAPTER_DIR)
p.add_argument("--max-steps", type=int, default=40)
p.add_argument("--smoke", action="store_true")
p = sub.add_parser("inspect", help="print a few packed examples (markers / think / bash)")
p.add_argument("--pack", type=Path, required=True)
p.add_argument("--n", type=int, default=8)
p = sub.add_parser(
"judge",
help="official GLM-5.2 checklist on a finished local duel (needs OpenRouter)",
)
p.add_argument(
"--run",
type=Path,
default=Path("/workspace/data/eval-runs/20260824T182728Z-cdd1388b"),
help="eval-run directory (default: v11 8x12)",
)
p.add_argument("--limit", type=int, default=2)
p.add_argument("--dry-run", action="store_true")
args = parser.parse_args(argv)
if args.cmd == "pack":
from .pack import pack
pack(
dataset_root=args.dataset_root,
out_dir=args.out_dir,
max_examples=64 if args.smoke else args.max_examples,
seed="smoke" if args.smoke else args.seed,
n_salts=args.n_salts,
submit_frac=args.submit_frac,
edit_frac=args.edit_frac,
expand_submit_salts=args.expand_submit_salts,
)
return 0
if args.cmd == "sft":
_maybe_accelerate()
from .sft import default_pack, train_sft
train_sft(
pack_path=args.pack or default_pack(),
output_dir=args.out_dir,
model_dir=args.model,
max_seq_len=args.max_seq_len,
max_steps=args.max_steps,
num_epochs=args.epochs,
lr=args.lr,
smoke=args.smoke,
)
return 0
if args.cmd == "export":
from .export import export_merged
export_merged(
adapter_dir=args.adapter,
genesis_dir=args.genesis,
out_dir=args.out_dir,
base_dir=args.base,
)
return 0
if args.cmd == "check":
from .check import check_model
report = check_model(args.path, args.genesis)
return 0 if report["ok"] else 1
if args.cmd == "duel":
from .duel import duel
report = duel(
challenger=args.challenger,
king=args.king,
samples=args.samples,
turns=args.turns,
seed=args.seed,
skip_king=args.skip_king,
)
return 0 if report["go"] else 2
if args.cmd == "chain-pack":
from .chain_pack import pack_chain
mix = args.mix_pack
if mix is None:
candidate = args.out_dir / "sft-4000-sft-pack.jsonl"
mix = candidate if candidate.is_file() else None
pack_chain(
dataset_root=args.dataset_root,
out_dir=args.out_dir,
max_groups=8 if args.smoke else args.max_groups,
seed="smoke" if args.smoke else args.seed,
n_salts=args.n_salts,
mix_pack=mix,
mix_n=0 if args.smoke else args.mix_n,
mix_edit_n=0 if args.smoke else args.mix_edit_n,
mix_submit_n=0 if args.smoke else args.mix_submit_n,
)
return 0
if args.cmd == "chain-gold":
from .chain_gold import pack_chain_gold
mix = args.mix_pack
if mix is None:
candidate = args.out_dir / "sft-4000-sft-pack.jsonl"
mix = candidate if candidate.is_file() else None
pack_chain_gold(
dataset_root=args.dataset_root,
out_dir=args.out_dir,
max_groups=4 if args.smoke else args.max_groups,
seed="smoke" if args.smoke else args.seed,
n_salts=args.n_salts,
mix_pack=mix,
mix_edit_n=0 if args.smoke else args.mix_edit_n,
mix_submit_n=0 if args.smoke else args.mix_submit_n,
)
return 0
if args.cmd == "chain":
from .chain_eval import run_chain
report = run_chain(
challenger=args.challenger,
samples=args.samples,
turns=args.turns,
seed=args.seed,
live_gate=not args.no_live_gate,
)
return 0 if report["go"] else 2
if args.cmd == "dpo-pack":
from .dpo_pack import pack_dpo
pack_dpo(out_dir=args.out_dir, seed=args.seed, narrow=args.narrow)
return 0
if args.cmd == "dpo":
_maybe_accelerate()
from .dpo import train_dpo
train_dpo(
pack_path=args.pack,
output_dir=args.out_dir,
model_dir=args.model,
max_seq_len=args.max_seq_len,
max_steps=args.max_steps,
lr=args.lr,
beta=args.beta,
smoke=args.smoke,
)
return 0
if args.cmd == "policy":
from .policy_eval import run_policy
report = run_policy(challenger=args.challenger)
return 0 if report["go"] else 2
if args.cmd == "rl":
_maybe_accelerate()
from .rl import train_grpo
train_grpo(
pack_path=args.pack,
model_dir=args.model,
output_dir=args.out_dir,
max_steps=args.max_steps,
smoke=args.smoke,
)
return 0
if args.cmd == "inspect":
return _inspect(args.pack, args.n)
if args.cmd == "judge":
from local_eval.judge import run_judge
result = run_judge(run_dir=args.run, limit=args.limit, dry_run=args.dry_run)
if args.dry_run:
return 0
return 0 if result.get("state") == "succeeded" else 2
return 2
def _inspect(pack_path: Path, n: int) -> int:
from albedo_eval_service.shared.observation_format import first_bash_block
from albedo_eval_service.shared.submit_protocol import ANY_MARKER_RE
rows = [
json.loads(line)
for line in Path(pack_path).read_text().splitlines()
if line.strip()
]
print(f"n={len(rows)} showing {min(n, len(rows))}", flush=True)
for row in rows[:n]:
completion = row.get("completion") or ""
think_ok = "<think>" in completion and "</think>" in completion
bash = first_bash_block(completion)
print("=" * 72, flush=True)
print(
f"{row.get('sample_id')} kind={row.get('kind')} phase={row.get('phase')} "
f"rewrite={row.get('rewrite_mode')} marker={row.get('submit_marker')!r}",
flush=True,
)
print(f" think_closed={think_ok} bash={bash[:140]!r}", flush=True)
if row.get("kind") == "submit":
print(f" submit={row.get('submit_command')!r} marker_in_bash={bool(ANY_MARKER_RE.search(bash or ''))}", flush=True)
return 0
def _maybe_accelerate() -> None:
"""Re-exec under accelerate when several GPUs are free and we are not already launched."""
if os.environ.get("LOCAL_RANK") is not None or os.environ.get("ALBEDO_NO_ACCELERATE"):
return
try:
import torch
nproc = torch.cuda.device_count()
except Exception:
return
if nproc <= 1:
return
cmd = [
sys.executable,
"-m",
"accelerate.commands.launch",
"--num_processes",
str(nproc),
"--mixed_precision",
"bf16",
"-m",
"local_train",
*sys.argv[1:],
]
print("launch:", " ".join(cmd), flush=True)
raise SystemExit(subprocess.call(cmd))