etomoscow/mff_lora / code /scripts /launch_alpha_grid.py
etomoscow's picture
download
raw
11.3 kB
"""Run the all-block alpha grid sequentially on a configured CUDA device."""
from __future__ import annotations
import argparse
import json
import os
import subprocess
import sys
import time
from pathlib import Path
GPU_DEVICES = {1: "0", 2: "1"}
MODEL_PATH_ENV = {
"llama": "MFFLORA_LLAMA_MODEL_PATH",
"qwen3": "MFFLORA_QWEN3_MODEL_PATH",
"ministral": "MFFLORA_MINISTRAL_MODEL_PATH",
"gemma4": "MFFLORA_GEMMA4_MODEL_PATH",
}
BLOCKS = [
("qwen3", "xnli"),
("ministral", "xnli"),
("gemma4", "xnli"),
("llama", "rte"),
("qwen3", "rte"),
("ministral", "rte"),
("gemma4", "rte"),
]
METHODS = ["fpeft_low", "fpeft_bi_high", "random_orthogonal", "peft_scaled_random"]
ALPHAS = [0.25, 0.5, 1.0, 2.0]
SEEDS = [42, 1337, 2024, 7, 123]
EXPECTED_CELLS_PER_JOB = len(METHODS) * len(SEEDS)
def _block_name(model: str, task: str) -> str:
return f"{model}_{task}"
def _alpha_tag(alpha: float) -> str:
return f"alpha{alpha:g}"
def _commands(
repo: Path,
output_root: Path,
seeds: list[int] = SEEDS,
alphas: list[float] = ALPHAS,
) -> dict[str, list[str]]:
commands = {}
for alpha in alphas:
for model, task in BLOCKS:
block = _block_name(model, task)
job = f"{_alpha_tag(alpha)}__{block}"
output_dir = output_root / _alpha_tag(alpha) / "comparison" / block
command = [
sys.executable,
str(repo / "experiments/broad_sweep/run.py"),
"--model-id", model,
"--dataset", task,
"--methods", *METHODS,
"--ranks", "32",
"--seeds", *(str(seed) for seed in seeds),
"--alpha-fpeft", str(alpha),
"--alpha-baseline", str(alpha),
"--output-dir", str(output_dir),
"--no-wandb",
]
if model == "llama" and task == "rte":
command.extend([
"--batch-size", "1", "--grad-accum", "16", "--max-length", "128",
"--gradient-checkpointing",
])
model_path = os.environ.get(MODEL_PATH_ENV[model])
if model_path and Path(model_path).is_dir():
command[command.index("--dataset"):command.index("--dataset")] = [
"--model-path", model_path
]
commands[job] = command
return commands
def _inspect(path: Path, seeds: list[int] = SEEDS) -> dict[str, int | bool]:
result = {
"complete": 0,
"errors": 0,
"retryable_errors": 0,
"non_retryable_errors": 0,
"constant": 0,
"total": 0,
"finished": False,
}
if not path.exists():
return result
data = json.loads(path.read_text())
result["total"] = len(data)
for method in METHODS:
for seed in seeds:
value = data.get(f"{method}__rank32__seed{seed}", {})
if value.get("status") == "complete":
result["complete"] += 1
distribution = value.get("prediction_distribution")
if distribution and max(distribution) == sum(distribution):
result["constant"] += 1
elif value.get("status") == "error":
result["errors"] += 1
if value.get("retryable") is True:
result["retryable_errors"] += 1
else:
result["non_retryable_errors"] += 1
result["finished"] = result["complete"] == len(METHODS) * len(seeds)
return result
def _should_retry(state: dict[str, int | bool]) -> bool:
return bool(state["errors"]) and state["retryable_errors"] == state["errors"]
def _can_resume(state: dict[str, int | bool]) -> bool:
return state["non_retryable_errors"] == 0
def _write_json(path: Path, value: dict) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
temporary = path.with_suffix(path.suffix + ".tmp")
temporary.write_text(json.dumps(value, indent=2) + "\n")
temporary.replace(path)
def _gpu_state() -> str:
result = subprocess.run(
["nvidia-smi", "--query-gpu=index,memory.used,utilization.gpu", "--format=csv,noheader"],
capture_output=True,
text=True,
check=False,
)
return result.stdout.strip()
def _heartbeat(
path: Path,
*,
alpha: float | None,
block: str | None,
pid: int | None,
pending: list[str],
done: list[str],
attempts: dict[str, int],
state: dict | None = None,
gpu: int = 1,
gpu_device: str = "0",
) -> None:
record = {
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
"gpu": gpu,
"gpu_device": gpu_device,
"alpha": alpha,
"block": block,
"pid": pid,
"pending": pending,
"done": done,
"attempts": attempts,
"gpu_state": _gpu_state(),
}
if state is not None:
record["cell_state"] = state
path.parent.mkdir(parents=True, exist_ok=True)
with path.open("a", encoding="utf-8") as handle:
handle.write(json.dumps(record) + "\n")
def _parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser()
parser.add_argument("--repo", default=".")
parser.add_argument("--gpu", type=int, default=1)
parser.add_argument("--output-root", default="outputs/paper_upgrade_alpha_grid_20260720")
parser.add_argument("--heartbeat-seconds", type=int, default=600)
parser.add_argument("--poll-seconds", type=int, default=30)
parser.add_argument("--max-attempts", type=int, default=3)
parser.add_argument("--budget-hours", type=float, default=120.0)
parser.add_argument("--seeds", nargs="+", type=int, default=SEEDS)
parser.add_argument("--alphas", nargs="+", type=float, default=ALPHAS)
parser.add_argument("--dry-run", action="store_true")
return parser.parse_args()
def main() -> None:
args = _parse_args()
if args.gpu not in GPU_DEVICES:
raise ValueError("alpha-grid launcher supports configured CUDA devices 1 or 2")
gpu_device = GPU_DEVICES[args.gpu]
repo = Path(args.repo).resolve()
output_root = repo / args.output_root
output_root.mkdir(parents=True, exist_ok=True)
commands = _commands(output_root=output_root, repo=repo, seeds=args.seeds, alphas=args.alphas)
if args.dry_run:
for job, command in commands.items():
print(job, " ".join(command))
return
manifest_path = output_root / "manifest.json"
watchdog_path = output_root / "watchdog.jsonl"
manifest = json.loads(manifest_path.read_text()) if manifest_path.exists() else {
"gpu": args.gpu,
"gpu_device": gpu_device,
"mode": "sequential",
"methods": METHODS,
"alphas": args.alphas,
"seeds": args.seeds,
"jobs": {},
}
manifest["gpu"] = args.gpu
manifest["gpu_device"] = gpu_device
_write_json(manifest_path, manifest)
started = time.monotonic()
done: list[str] = []
for job, command in commands.items():
output_dir = Path(command[command.index("--output-dir") + 1])
results_path = output_dir / "results.json"
state = _inspect(results_path, args.seeds)
if state["finished"]:
done.append(job)
manifest["jobs"][job] = {"status": "complete", **state}
_write_json(manifest_path, manifest)
continue
if results_path.exists() and not _can_resume(state):
raise RuntimeError(f"job has non-retryable cell errors: {job}")
for attempt in range(1, args.max_attempts + 1):
if time.monotonic() - started >= args.budget_hours * 3600:
raise TimeoutError("alpha-grid GPU-hour budget exhausted")
log_path = output_root / "logs" / f"{job}.attempt{attempt}.log"
log_path.parent.mkdir(parents=True, exist_ok=True)
env = os.environ.copy()
env["CUDA_VISIBLE_DEVICES"] = gpu_device
env["PYTORCH_ALLOC_CONF"] = "expandable_segments:True"
manifest["jobs"][job] = {"status": "running", "attempt": attempt}
_write_json(manifest_path, manifest)
with log_path.open("w", encoding="utf-8") as log:
process = subprocess.Popen(command, cwd=repo, env=env, stdout=log, stderr=subprocess.STDOUT)
last_heartbeat = 0.0
while process.poll() is None:
state = _inspect(results_path, args.seeds)
if state["constant"] >= 3:
process.terminate()
process.wait()
raise RuntimeError(f"constant-class stop condition in {job}")
now = time.monotonic()
if now - started >= args.budget_hours * 3600:
process.terminate()
process.wait()
raise TimeoutError("alpha-grid GPU-hour budget exhausted")
if now - last_heartbeat >= args.heartbeat_seconds:
alpha = float(job.split("__", 1)[0].removeprefix("alpha"))
block = job.split("__", 1)[1]
_heartbeat(
watchdog_path,
alpha=alpha,
block=block,
pid=process.pid,
pending=[name for name in commands if name not in done and name != job],
done=done,
attempts={name: manifest["jobs"].get(name, {}).get("attempt", 0) for name in commands},
state=state,
gpu=args.gpu,
gpu_device=gpu_device,
)
last_heartbeat = now
time.sleep(args.poll_seconds)
state = _inspect(results_path, args.seeds)
if state["constant"] >= 3:
raise RuntimeError(f"constant-class stop condition in {job}")
if state["finished"]:
done.append(job)
manifest["jobs"][job] = {"status": "complete", "attempt": attempt, **state}
_write_json(manifest_path, manifest)
break
if not _can_resume(state):
raise RuntimeError(f"job failed without retryable cell errors: {job}")
status = "retrying" if _should_retry(state) else "resuming"
manifest["jobs"][job] = {"status": status, "attempt": attempt, **state}
_write_json(manifest_path, manifest)
if attempt < args.max_attempts:
time.sleep(2 ** (attempt - 1))
else:
raise RuntimeError(f"job failed after {args.max_attempts} attempts: {job}")
_heartbeat(
watchdog_path,
alpha=None,
block=None,
pid=None,
pending=[],
done=done,
attempts={name: manifest["jobs"].get(name, {}).get("attempt", 0) for name in commands},
gpu=args.gpu,
gpu_device=gpu_device,
)
print(json.dumps({"done": done, "cells": len(done) * EXPECTED_CELLS_PER_JOB}, indent=2))
if __name__ == "__main__":
main()

Xet Storage Details

Size:
11.3 kB
·
Xet hash:
d7587cd891c139f5aeffc7355ba4a33ce3b4610b6ccadbf76bd17d0ae675a2e6

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.