diff options
| author | YurenHao0426 <Blackhao0426@gmail.com> | 2026-07-30 16:34:30 -0500 |
|---|---|---|
| committer | YurenHao0426 <Blackhao0426@gmail.com> | 2026-07-30 16:34:30 -0500 |
| commit | 716205eba862076c1d0fbbff016601272e4614f2 (patch) | |
| tree | dc8c31dcfefa49d53c98d305fe35419da76e58c9 /experiments | |
| parent | 906d17e896329e4d2dd8d7f728e4d5a234afe0e4 (diff) | |
experiment: package portable A6000 crossover
Diffstat (limited to 'experiments')
| -rw-r--r-- | experiments/analyze_resnet_crossover_r2.py | 29 | ||||
| -rw-r--r-- | experiments/analyze_transformer_crossover_t2.py | 30 | ||||
| -rwxr-xr-x | experiments/bootstrap_plain_cnn.sh | 35 | ||||
| -rw-r--r-- | experiments/crossover_hardware.py | 124 | ||||
| -rwxr-xr-x | experiments/crossover_hardware_smoke.py | 29 | ||||
| -rwxr-xr-x | experiments/prepare_shakespeare_char.py | 77 | ||||
| -rw-r--r-- | experiments/resnet_crossover_r2.py | 62 | ||||
| -rwxr-xr-x | experiments/run_a6000_matrix.py | 182 | ||||
| -rw-r--r-- | experiments/transformer_crossover_native.py | 5 | ||||
| -rw-r--r-- | experiments/transformer_crossover_t2.py | 62 |
10 files changed, 542 insertions, 93 deletions
diff --git a/experiments/analyze_resnet_crossover_r2.py b/experiments/analyze_resnet_crossover_r2.py index 3c2bf46..ce89165 100644 --- a/experiments/analyze_resnet_crossover_r2.py +++ b/experiments/analyze_resnet_crossover_r2.py @@ -18,6 +18,7 @@ from experiments.resnet_crossover_r2 import ( registry_sha256, selector_report, ) +from experiments.crossover_hardware import assert_hardware_report def sha256(path): @@ -216,7 +217,9 @@ def work_metrics(record, job, history): } -def audit_job(job, expected_source, expected_selector): +def audit_job( + job, expected_source, expected_selector, hardware_policy +): manifest_path = job["output"] + ".manifest.json" if not os.path.isfile(manifest_path): raise AssertionError(f"missing R2 manifest: {job['experiment_name']}") @@ -240,8 +243,7 @@ def audit_job(job, expected_source, expected_selector): assert manifest["source"] == expected_source assert manifest["selector"] == expected_selector hardware = manifest["hardware_lock"] - assert hardware["physical_gpu_index"] in (5, 7) - assert hardware["physical_gpu_uuid"] + assert_hardware_report(hardware, hardware_policy) common = { "cell_id": f"resnet{job['depth']}::{job['method']}", "method": job["method"], @@ -331,8 +333,14 @@ def audit_job(job, expected_source, expected_selector): expected_ordinary *= job["depth"] assert work["ordinary_training_examples"] == expected_ordinary hardware_record = record["hardware"] - assert hardware_record["cuda_visible_devices"] in ("5", "7") - assert hardware_record["cuda_device_name"] == "NVIDIA GeForce GTX 1080" + assert ( + hardware_record["cuda_visible_devices"] + == hardware["cuda_visible_devices"] + ) + assert ( + hardware_record["cuda_device_name"] + == hardware["physical_gpu_name"] + ) parameter_count = architecture.get("forward_parameter_count") if parameter_count is None: parameter_count = architecture["forward_parameters"] @@ -370,9 +378,15 @@ def main(): assert launch["selector"] == selector assert launch["registry_sha256"] == registry_sha256(jobs) assert launch["num_jobs"] == 27 - assert launch["allowed_physical_gpus"] == [5, 7] + hardware_policy = launch["hardware_policy"] + assert ( + launch["allowed_physical_gpus"] + == hardware_policy["allowed_physical_gpu_indices"] + ) source = launch["source"] - records = [audit_job(job, source, selector) for job in jobs] + records = [ + audit_job(job, source, selector, hardware_policy) for job in jobs + ] assert len(records) == 27 assert len({record["cell_id"] for record in records}) == 27 assert { @@ -401,6 +415,7 @@ def main(): "test_policy": "none", "source": source, "selector": selector, + "hardware_policy": hardware_policy, "launch_lock": { "path": os.path.relpath(launch_path, ROOT), "sha256": sha256(launch_path), diff --git a/experiments/analyze_transformer_crossover_t2.py b/experiments/analyze_transformer_crossover_t2.py index 5eeb226..74c3ef6 100644 --- a/experiments/analyze_transformer_crossover_t2.py +++ b/experiments/analyze_transformer_crossover_t2.py @@ -18,6 +18,7 @@ from experiments.transformer_crossover_t2 import ( selector_report, t2_jobs, ) +from experiments.crossover_hardware import assert_hardware_report TRAIN_HASH = ( @@ -79,7 +80,7 @@ def expected_work(record, job): return work -def audit_completed(job, manifest, source): +def audit_completed(job, manifest, source, hardware_policy): assert manifest["output_exists"] is True assert manifest["output_sha256"] == sha256(job["output"]) record = read_json(job["output"]) @@ -164,8 +165,13 @@ def audit_completed(job, manifest, source): ) assert record["work"]["logical_task_loss_queries"] == 0 hardware = record["hardware"] - assert hardware["cuda_visible_devices"] in ("5", "7") - assert hardware["device_name"] == "NVIDIA GeForce GTX 1080" + locked_hardware = manifest["hardware_lock"] + assert_hardware_report(locked_hardware, hardware_policy) + assert ( + hardware["cuda_visible_devices"] + == locked_hardware["cuda_visible_devices"] + ) + assert hardware["device_name"] == locked_hardware["physical_gpu_name"] assert hardware["peak_memory_allocated_bytes"] is not None assert hardware["peak_memory_reserved_bytes"] is not None return { @@ -189,7 +195,7 @@ def audit_completed(job, manifest, source): } -def audit_job(job, source, selector): +def audit_job(job, source, selector, hardware_policy): manifest_path = job["output"] + ".manifest.json" if not os.path.isfile(manifest_path): raise AssertionError(f"missing T2 manifest: {job['experiment_name']}") @@ -211,8 +217,7 @@ def audit_job(job, source, selector): assert manifest["source"] == source assert manifest["selector"] == selector hardware = manifest["hardware_lock"] - assert hardware["physical_gpu_index"] in (5, 7) - assert hardware["physical_gpu_uuid"] + assert_hardware_report(hardware, hardware_policy) common = { "cell_id": f"transformer{job['depth']}::{job['method']}", "method": job["method"], @@ -228,7 +233,7 @@ def audit_job(job, source, selector): if manifest["status"] == "completed": return { **common, - **audit_completed(job, manifest, source), + **audit_completed(job, manifest, source, hardware_policy), } assert manifest["status"] in { "timeout", "nonzero_exit", "missing_output" @@ -271,9 +276,15 @@ def main(): assert launch["selector"] == selector assert launch["registry_sha256"] == registry_sha256(jobs) assert launch["num_jobs"] == 27 - assert launch["allowed_physical_gpus"] == [5, 7] + hardware_policy = launch["hardware_policy"] + assert ( + launch["allowed_physical_gpus"] + == hardware_policy["allowed_physical_gpu_indices"] + ) source = launch["source"] - records = [audit_job(job, source, selector) for job in jobs] + records = [ + audit_job(job, source, selector, hardware_policy) for job in jobs + ] assert len(records) == 27 assert len({record["cell_id"] for record in records}) == 27 assert { @@ -294,6 +305,7 @@ def main(): "test_policy": "none", "source": source, "selector": selector, + "hardware_policy": hardware_policy, "launch_lock": { "path": os.path.relpath(launch_path, ROOT), "sha256": sha256(launch_path), diff --git a/experiments/bootstrap_plain_cnn.sh b/experiments/bootstrap_plain_cnn.sh new file mode 100755 index 0000000..0d6145f --- /dev/null +++ b/experiments/bootstrap_plain_cnn.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ "$#" -ne 1 ]]; then + echo "usage: $0 /absolute/path/to/dualprop-sdil" >&2 + exit 2 +fi + +target="$1" +if [[ "$target" != /* ]]; then + echo "target must be an absolute path" >&2 + exit 2 +fi +if [[ -e "$target" ]]; then + echo "refusing to overwrite existing target: $target" >&2 + exit 2 +fi + +main_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +patch_root="$main_root/external/dualprop_patches" +base_revision="7b2595b34421e1483a721dbfdeff8cdabda3a1ff" + +if [[ "$(find "$patch_root" -maxdepth 1 -name '*.patch' | wc -l)" -ne 19 ]]; then + echo "expected 19 frozen Dual Propagation patches" >&2 + exit 1 +fi + +git clone https://github.com/Rasmuskh/dualprop_icml_2024.git "$target" +git -C "$target" checkout -b sdil-a6000 "$base_revision" +git -C "$target" config user.name "SDIL replication runner" +git -C "$target" config user.email "sdil-replication@invalid.example" +git -C "$target" am --committer-date-is-author-date "$patch_root"/*.patch + +echo "plain-CNN runner ready at $target" +git -C "$target" log -1 --oneline diff --git a/experiments/crossover_hardware.py b/experiments/crossover_hardware.py new file mode 100644 index 0000000..6ace655 --- /dev/null +++ b/experiments/crossover_hardware.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +"""Hardware profiles shared by portable crossover launchers and audits.""" +import os +import subprocess + + +PROFILES = { + "timan107_gtx1080": { + "allowed_physical_gpu_indices": [5, 7], + "allowed_device_names": ["NVIDIA GeForce GTX 1080"], + }, + "a6000": { + "allowed_physical_gpu_indices": None, + "allowed_device_names": ["NVIDIA RTX A6000"], + }, +} +DEFAULT_PROFILE = "timan107_gtx1080" + + +def profile_choices(): + return tuple(PROFILES) + + +def policy(profile, physical_gpu_index=None): + if profile not in PROFILES: + raise ValueError(f"unknown crossover hardware profile: {profile}") + configured = PROFILES[profile] + allowed = configured["allowed_physical_gpu_indices"] + if allowed is None: + if physical_gpu_index is None: + raise ValueError( + f"{profile} policy requires a resolved physical GPU index" + ) + allowed = [int(physical_gpu_index)] + return { + "profile": profile, + "allowed_physical_gpu_indices": list(allowed), + "allowed_device_names": list(configured["allowed_device_names"]), + "single_gpu_per_process": True, + } + + +def _query_physical_gpus(): + query = subprocess.run( + [ + "nvidia-smi", + "--query-gpu=index,uuid,name,memory.total", + "--format=csv,noheader,nounits", + ], + check=True, + capture_output=True, + text=True, + ).stdout.splitlines() + rows = {} + for line in query: + index, uuid, name, memory = [ + value.strip() for value in line.split(",", 3) + ] + rows[index] = { + "uuid": uuid, + "name": name, + "total_memory_mib": int(memory), + } + return rows + + +def physical_gpu_report(profile, dry_run=False): + visible = os.environ.get("CUDA_VISIBLE_DEVICES") + if dry_run: + return { + "hardware_profile": profile, + "cuda_visible_devices": visible, + "physical_gpu_index": None, + "physical_gpu_uuid": None, + "physical_gpu_name": None, + "total_memory_mib": None, + } + if visible is None or not visible.isdigit(): + raise RuntimeError( + "formal crossover jobs require CUDA_VISIBLE_DEVICES to contain " + "one physical numeric GPU index" + ) + physical_index = int(visible) + hardware_policy = policy(profile, physical_index) + if physical_index not in hardware_policy["allowed_physical_gpu_indices"]: + raise RuntimeError( + f"physical GPU {physical_index} is not allowed by {profile}" + ) + rows = _query_physical_gpus() + if visible not in rows: + raise RuntimeError(f"physical GPU {visible} not found") + row = rows[visible] + if row["name"] not in hardware_policy["allowed_device_names"]: + raise RuntimeError( + f"{profile} requires one of " + f"{hardware_policy['allowed_device_names']}, found {row['name']}" + ) + return { + "hardware_profile": profile, + "cuda_visible_devices": visible, + "physical_gpu_index": physical_index, + "physical_gpu_uuid": row["uuid"], + "physical_gpu_name": row["name"], + "total_memory_mib": row["total_memory_mib"], + } + + +def policy_for_report(profile, report): + return policy(profile, report["physical_gpu_index"]) + + +def assert_hardware_report(report, hardware_policy): + assert report["hardware_profile"] == hardware_policy["profile"] + assert ( + report["physical_gpu_index"] + in hardware_policy["allowed_physical_gpu_indices"] + ) + assert report["physical_gpu_uuid"] + assert report["physical_gpu_name"] in hardware_policy[ + "allowed_device_names" + ] + assert report["cuda_visible_devices"] == str( + report["physical_gpu_index"] + ) diff --git a/experiments/crossover_hardware_smoke.py b/experiments/crossover_hardware_smoke.py new file mode 100755 index 0000000..89c11c2 --- /dev/null +++ b/experiments/crossover_hardware_smoke.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +"""Dependency-free checks for frozen crossover hardware profiles.""" +from crossover_hardware import ( + assert_hardware_report, + policy, + policy_for_report, +) + + +def main(): + report = { + "hardware_profile": "a6000", + "cuda_visible_devices": "3", + "physical_gpu_index": 3, + "physical_gpu_uuid": "GPU-smoke", + "physical_gpu_name": "NVIDIA RTX A6000", + "total_memory_mib": 49140, + } + a6000 = policy_for_report("a6000", report) + assert a6000["allowed_physical_gpu_indices"] == [3] + assert_hardware_report(report, a6000) + gtx = policy("timan107_gtx1080") + assert gtx["allowed_physical_gpu_indices"] == [5, 7] + assert gtx["allowed_device_names"] == ["NVIDIA GeForce GTX 1080"] + print("crossover hardware smoke: all checks passed") + + +if __name__ == "__main__": + main() diff --git a/experiments/prepare_shakespeare_char.py b/experiments/prepare_shakespeare_char.py new file mode 100755 index 0000000..f09fc80 --- /dev/null +++ b/experiments/prepare_shakespeare_char.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +"""Download and deterministically tokenize the frozen Tiny Shakespeare data.""" +import argparse +import hashlib +import os +import pickle +import urllib.request + +import numpy as np + + +URL = ( + "https://raw.githubusercontent.com/karpathy/char-rnn/master/" + "data/tinyshakespeare/input.txt" +) +EXPECTED = { + "input.txt": + "86c4e6aa9db7c042ec79f339dcb96d42b0075e16b8fc2e86bf0ca57e2dc565ed", + "train.bin": + "6ec305602a99ac2802745a134e1f5e33e2231b4855525b00b9aebb730ac2626f", + "val.bin": + "d37d30cc0c8327c270d493299c3dca54135f6d5f1c9ef60cda78076e311204b1", +} + + +def sha256(path): + digest = hashlib.sha256() + with open(path, "rb") as handle: + for chunk in iter(lambda: handle.read(1024 * 1024), b""): + digest.update(chunk) + return digest.hexdigest() + + +def verify(path, expected): + actual = sha256(path) + if actual != expected: + raise RuntimeError( + f"hash mismatch for {path}: expected {expected}, found {actual}" + ) + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--out-dir", required=True) + args = parser.parse_args() + root = os.path.abspath(args.out_dir) + os.makedirs(root, exist_ok=True) + input_path = os.path.join(root, "input.txt") + if not os.path.isfile(input_path): + urllib.request.urlretrieve(URL, input_path) + verify(input_path, EXPECTED["input.txt"]) + + with open(input_path, encoding="utf-8") as handle: + data = handle.read() + chars = sorted(set(data)) + stoi = {char: index for index, char in enumerate(chars)} + itos = {index: char for char, index in stoi.items()} + encoded = np.asarray([stoi[char] for char in data], dtype=np.uint16) + boundary = int(len(encoded) * 0.9) + encoded[:boundary].tofile(os.path.join(root, "train.bin")) + encoded[boundary:].tofile(os.path.join(root, "val.bin")) + with open(os.path.join(root, "meta.pkl"), "wb") as handle: + pickle.dump( + {"vocab_size": len(chars), "itos": itos, "stoi": stoi}, + handle, + ) + + for name, expected in EXPECTED.items(): + verify(os.path.join(root, name), expected) + print( + f"prepared Tiny Shakespeare at {root}: " + f"{boundary} train, {len(encoded) - boundary} validation tokens" + ) + + +if __name__ == "__main__": + main() diff --git a/experiments/resnet_crossover_r2.py b/experiments/resnet_crossover_r2.py index a9d5dfe..a7e78d7 100644 --- a/experiments/resnet_crossover_r2.py +++ b/experiments/resnet_crossover_r2.py @@ -10,6 +10,15 @@ import time ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.insert(0, ROOT) +from experiments.crossover_hardware import ( + DEFAULT_PROFILE, + physical_gpu_report, + policy_for_report, + profile_choices, +) + + PROTOCOL = os.path.join(ROOT, "RESNET_CROSSOVER.md") RESULT_ROOT = os.path.join(ROOT, "results", "resnet_crossover") DEFAULT_SELECTOR = os.path.join(RESULT_ROOT, "p1_selector.json") @@ -113,6 +122,7 @@ def source_report(selector_path): os.path.join(ROOT, "experiments", "analyze_resnet_crossover_r2.py"), os.path.join(ROOT, "experiments", "resnet_crossover_native.py"), os.path.join(ROOT, "experiments", "conv_run.py"), + os.path.join(ROOT, "experiments", "crossover_hardware.py"), os.path.join(ROOT, "sdil", "conv_crossover.py"), os.path.join(ROOT, "sdil", "conv.py"), os.path.join(ROOT, "sdil", "data.py"), @@ -295,7 +305,7 @@ def registry_sha256(jobs): return hashlib.sha256(encoded).hexdigest() -def ensure_launch(source, selector, jobs): +def ensure_launch(source, selector, jobs, hardware_policy): path = os.path.join(RESULT_ROOT, "r2_launch.json") expected = { "stage": "r2", @@ -303,7 +313,9 @@ def ensure_launch(source, selector, jobs): "selector": selector, "registry_sha256": registry_sha256(jobs), "num_jobs": len(jobs), - "allowed_physical_gpus": [5, 7], + "hardware_policy": hardware_policy, + "allowed_physical_gpus": + hardware_policy["allowed_physical_gpu_indices"], } if os.path.isfile(path): if read_json(path) != expected: @@ -326,40 +338,6 @@ def assert_source_unchanged(source): raise RuntimeError(f"ResNet R2 source drift: {relative}") -def physical_gpu_report(dry_run=False): - visible = os.environ.get("CUDA_VISIBLE_DEVICES") - if dry_run: - return { - "cuda_visible_devices": visible, - "physical_gpu_index": None, - "physical_gpu_uuid": None, - } - if visible not in {"5", "7"}: - raise RuntimeError("ResNet R2 requires physical GPU 5 or 7") - query = subprocess.run( - [ - "nvidia-smi", - "--query-gpu=index,uuid,name", - "--format=csv,noheader,nounits", - ], - check=True, - capture_output=True, - text=True, - ).stdout.splitlines() - rows = {} - for line in query: - index, uuid, name = [value.strip() for value in line.split(",", 2)] - rows[index] = {"uuid": uuid, "name": name} - if visible not in rows: - raise RuntimeError(f"physical GPU {visible} not found") - return { - "cuda_visible_devices": visible, - "physical_gpu_index": int(visible), - "physical_gpu_uuid": rows[visible]["uuid"], - "physical_gpu_name": rows[visible]["name"], - } - - def run_job(job, source, selector, gpu, dry_run): if not dry_run: assert_source_unchanged(source) @@ -414,16 +392,24 @@ def main(): parser.add_argument("--num-shards", type=int, default=1) parser.add_argument("--method", choices=METHODS) parser.add_argument("--depth", type=int, choices=DEPTHS) + parser.add_argument( + "--hardware-profile", + choices=profile_choices(), + default=os.environ.get("SDIL_HARDWARE_PROFILE", DEFAULT_PROFILE), + ) parser.add_argument("--dry-run", action="store_true") args = parser.parse_args() if not 0 <= args.shard_index < args.num_shards: raise ValueError("invalid ResNet R2 shard") selector = selector_report(args.selector) source = source_report(args.selector) - gpu = physical_gpu_report(args.dry_run) + gpu = physical_gpu_report(args.hardware_profile, args.dry_run) complete_jobs = r2_jobs(selector["selected_rates"]) if not args.dry_run: - launch = ensure_launch(source, selector, complete_jobs) + hardware_policy = policy_for_report(args.hardware_profile, gpu) + launch = ensure_launch( + source, selector, complete_jobs, hardware_policy + ) print(f"R2 launch lock: {launch}", flush=True) jobs = complete_jobs if args.method: diff --git a/experiments/run_a6000_matrix.py b/experiments/run_a6000_matrix.py new file mode 100755 index 0000000..e5330ef --- /dev/null +++ b/experiments/run_a6000_matrix.py @@ -0,0 +1,182 @@ +#!/usr/bin/env python3 +"""Sequential, restart-safe launcher for the complete A6000 81-cell matrix.""" +import argparse +import os +import subprocess +import sys + + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.insert(0, ROOT) +from experiments.crossover_hardware import physical_gpu_report +from experiments.resnet_crossover_r2 import selector_report as resnet_selector +from experiments.transformer_crossover_t2 import ( + selector_report as transformer_selector, +) + + +def run(command, cwd, environment, dry_run): + rendered = " ".join(command) + print(f"[{cwd}] {rendered}", flush=True) + if not dry_run: + subprocess.run(command, cwd=cwd, env=environment, check=True) + + +def require_file(path, label): + if not os.path.isfile(path): + raise RuntimeError(f"{label} is not frozen in this run packet: {path}") + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--plain-root", required=True) + parser.add_argument("--plain-python", required=True) + parser.add_argument( + "--family", + choices=("all", "plain", "resnet", "transformer"), + default="all", + ) + parser.add_argument("--dry-run", action="store_true") + args = parser.parse_args() + + plain_root = os.path.abspath(args.plain_root) + plain_python = os.path.abspath(args.plain_python) + require_file(plain_python, "plain-CNN Python executable") + require_file( + os.path.join(plain_root, "crossover_grid.py"), + "patched plain-CNN runner", + ) + gpu = physical_gpu_report("a6000", args.dry_run) + print(f"hardware lock: {gpu}", flush=True) + + environment = os.environ.copy() + environment["SDIL_HARDWARE_PROFILE"] = "a6000" + environment["SDIL_MAIN_ROOT"] = ROOT + families = ( + ("plain", "resnet", "transformer") + if args.family == "all" else (args.family,) + ) + + plain_audit = os.path.join(plain_root, "runs", "plain-p2-audit.json") + if "plain" in families: + run( + [ + plain_python, + "tests/local_rules_smoke.py", + ], + plain_root, + environment, + args.dry_run, + ) + run( + [ + plain_python, + "crossover_grid.py", + "--stage", "p2", + "--hardware-profile", "a6000", + ], + plain_root, + environment, + args.dry_run, + ) + run( + [ + plain_python, + "analyze_p2.py", + "--output", plain_audit, + ], + plain_root, + environment, + args.dry_run, + ) + + resnet_selector_path = os.path.join( + ROOT, "results", "resnet_crossover", "p1_selector.json" + ) + if "resnet" in families: + require_file(resnet_selector_path, "complete ResNet P1 selector") + if not args.dry_run: + resnet_selector(resnet_selector_path) + run( + [ + sys.executable, + "experiments/resnet_crossover_smoke.py", + ], + ROOT, + environment, + args.dry_run, + ) + run( + [ + sys.executable, + "experiments/resnet_crossover_r2.py", + "--hardware-profile", "a6000", + ], + ROOT, + environment, + args.dry_run, + ) + run( + [ + sys.executable, + "experiments/analyze_resnet_crossover_r2.py", + ], + ROOT, + environment, + args.dry_run, + ) + + transformer_selector_path = os.path.join( + ROOT, "results", "transformer_crossover", "p1_selector.json" + ) + if "transformer" in families: + require_file( + transformer_selector_path, "complete Transformer T1 selector" + ) + if not args.dry_run: + transformer_selector(transformer_selector_path) + run( + [ + sys.executable, + "experiments/transformer_feedback_smoke.py", + ], + ROOT, + environment, + args.dry_run, + ) + run( + [ + sys.executable, + "experiments/transformer_crossover_t2.py", + "--hardware-profile", "a6000", + ], + ROOT, + environment, + args.dry_run, + ) + run( + [ + sys.executable, + "experiments/analyze_transformer_crossover_t2.py", + ], + ROOT, + environment, + args.dry_run, + ) + + if args.family == "all": + require_file(plain_audit, "complete plain-CNN P2 audit") + run( + [ + sys.executable, + "experiments/analyze_crossover_81.py", + "--plain", plain_audit, + ], + ROOT, + environment, + args.dry_run, + ) + + +if __name__ == "__main__": + main() diff --git a/experiments/transformer_crossover_native.py b/experiments/transformer_crossover_native.py index ca47751..6a0269e 100644 --- a/experiments/transformer_crossover_native.py +++ b/experiments/transformer_crossover_native.py @@ -25,7 +25,10 @@ METHODS = ( "bp", "fa", "dfa", "pepita", "ff", "ep", "dualprop", "clean_kp", "sdil", ) -DEFAULT_DATA_DIR = "/home/yurenh2/local-transformer/data/shakespeare_char" +DEFAULT_DATA_DIR = os.environ.get( + "SDIL_TRANSFORMER_DATA_DIR", + "/home/yurenh2/local-transformer/data/shakespeare_char", +) def file_sha256(path): diff --git a/experiments/transformer_crossover_t2.py b/experiments/transformer_crossover_t2.py index 17f61a2..4f12e56 100644 --- a/experiments/transformer_crossover_t2.py +++ b/experiments/transformer_crossover_t2.py @@ -10,6 +10,15 @@ import time ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.insert(0, ROOT) +from experiments.crossover_hardware import ( + DEFAULT_PROFILE, + physical_gpu_report, + policy_for_report, + profile_choices, +) + + PROTOCOL = os.path.join(ROOT, "TRANSFORMER_CROSSOVER.md") RESULT_ROOT = os.path.join(ROOT, "results", "transformer_crossover") DEFAULT_SELECTOR = os.path.join(RESULT_ROOT, "p1_selector.json") @@ -123,6 +132,7 @@ def source_report(selector_path): os.path.join( ROOT, "experiments", "transformer_feedback_smoke.py" ), + os.path.join(ROOT, "experiments", "crossover_hardware.py"), os.path.join(ROOT, "sdil", "transformer.py"), os.path.abspath(selector_path), ] @@ -235,7 +245,7 @@ def registry_sha256(jobs): return hashlib.sha256(encoded).hexdigest() -def ensure_launch(source, selector, jobs): +def ensure_launch(source, selector, jobs, hardware_policy): path = os.path.join(RESULT_ROOT, "t2_launch.json") expected = { "stage": "t2", @@ -243,7 +253,9 @@ def ensure_launch(source, selector, jobs): "selector": selector, "registry_sha256": registry_sha256(jobs), "num_jobs": len(jobs), - "allowed_physical_gpus": [5, 7], + "hardware_policy": hardware_policy, + "allowed_physical_gpus": + hardware_policy["allowed_physical_gpu_indices"], } if os.path.isfile(path): if read_json(path) != expected: @@ -274,40 +286,6 @@ def assert_source_unchanged(source): raise RuntimeError("Transformer T2 environment drift") -def physical_gpu_report(dry_run=False): - visible = os.environ.get("CUDA_VISIBLE_DEVICES") - if dry_run: - return { - "cuda_visible_devices": visible, - "physical_gpu_index": None, - "physical_gpu_uuid": None, - } - if visible not in {"5", "7"}: - raise RuntimeError("Transformer T2 requires physical GPU 5 or 7") - query = subprocess.run( - [ - "nvidia-smi", - "--query-gpu=index,uuid,name", - "--format=csv,noheader,nounits", - ], - check=True, - capture_output=True, - text=True, - ).stdout.splitlines() - rows = {} - for line in query: - index, uuid, name = [value.strip() for value in line.split(",", 2)] - rows[index] = {"uuid": uuid, "name": name} - if visible not in rows: - raise RuntimeError(f"physical GPU {visible} not found") - return { - "cuda_visible_devices": visible, - "physical_gpu_index": int(visible), - "physical_gpu_uuid": rows[visible]["uuid"], - "physical_gpu_name": rows[visible]["name"], - } - - def run_job(job, source, selector, gpu, dry_run): if not dry_run: assert_source_unchanged(source) @@ -361,16 +339,24 @@ def main(): parser.add_argument("--num-shards", type=int, default=1) parser.add_argument("--method", choices=METHODS) parser.add_argument("--depth", type=int, choices=DEPTHS) + parser.add_argument( + "--hardware-profile", + choices=profile_choices(), + default=os.environ.get("SDIL_HARDWARE_PROFILE", DEFAULT_PROFILE), + ) parser.add_argument("--dry-run", action="store_true") args = parser.parse_args() if not 0 <= args.shard_index < args.num_shards: raise ValueError("invalid Transformer T2 shard") selector = selector_report(args.selector) source = source_report(args.selector) - gpu = physical_gpu_report(args.dry_run) + gpu = physical_gpu_report(args.hardware_profile, args.dry_run) complete_jobs = t2_jobs(selector["selected_rates"]) if not args.dry_run: - launch = ensure_launch(source, selector, complete_jobs) + hardware_policy = policy_for_report(args.hardware_profile, gpu) + launch = ensure_launch( + source, selector, complete_jobs, hardware_policy + ) print(f"T2 launch lock: {launch}", flush=True) jobs = complete_jobs if args.method: |
