#!/usr/bin/env python3 """One-GPU verification of API-parent-first OpProf shutdown.""" from __future__ import annotations import json import os import shlex import shutil import signal import subprocess import time import urllib.request from pathlib import Path from typing import Any WORKDIR = Path("/home/admin/cpfs/wjh/opprof-phase3-dash0-20260712") RUN_DIR = WORKDIR / "runs/e-b-shutdown-verification" SOURCE = Path( "/home/admin/cpfs/wjh/opprof-phase2-dash0-20260711/vllm-v0.24.0" ) VENV = Path("/tmp/wjh-opprof-phase2-dash0-20260711/.venv") MODEL = Path("/home/admin/cpfs/wjh/models/Qwen/Qwen3-30B-A3B") CLIENT = WORKDIR / "scripts/opprof_phase3_client.py" MANIFEST = Path("/home/admin/cpfs/wjh/opprof-phase3-private/manifests/P01.jsonl") PORT = 8010 def atomic_json(path: Path, value: Any) -> None: path.parent.mkdir(parents=True, exist_ok=True) tmp = path.with_name(path.name + f".tmp.{os.getpid()}") with tmp.open("w", encoding="utf-8") as f: json.dump(value, f, sort_keys=True, indent=2) f.write("\n") f.flush() os.fsync(f.fileno()) os.replace(tmp, path) def run_text(command: list[str], check: bool = True) -> str: result = subprocess.run( command, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT ) if check and result.returncode: raise RuntimeError( f"command failed ({result.returncode}): {shlex.join(command)}\n" f"{result.stdout}" ) return result.stdout def gpu_rows() -> list[dict[str, int]]: output = run_text( [ "nvidia-smi", "--query-gpu=index,memory.used,utilization.gpu", "--format=csv,noheader,nounits", ] ) rows = [] for line in output.strip().splitlines(): index, memory, utilization = [int(part.strip()) for part in line.split(",")] rows.append( {"index": index, "memory_mib": memory, "utilization_pct": utilization} ) return rows def compute_apps() -> str: return run_text( [ "nvidia-smi", "--query-compute-apps=gpu_uuid,pid,process_name,used_memory", "--format=csv,noheader,nounits", ], check=False, ).strip() def group_alive(pgid: int, process: subprocess.Popen[Any] | None = None) -> bool: if process is not None: process.poll() # reap an exited API parent before probing its group try: os.killpg(pgid, 0) return True except ProcessLookupError: return False def wait_ready(process: subprocess.Popen[Any], timeout: float = 300) -> None: deadline = time.monotonic() + timeout while time.monotonic() < deadline: if process.poll() is not None: raise RuntimeError("server exited before readiness") try: with urllib.request.urlopen( f"http://127.0.0.1:{PORT}/health", timeout=1 ) as response: if response.status == 200: return except Exception: pass time.sleep(1) raise TimeoutError("server readiness timeout") def shutdown_parent_first(process: subprocess.Popen[Any]) -> dict[str, Any]: called_at = time.time() os.kill(process.pid, signal.SIGINT) deadline = time.monotonic() + 120 while time.monotonic() < deadline and group_alive(process.pid, process): time.sleep(1) graceful = not group_alive(process.pid, process) if not graceful: for sig in (signal.SIGTERM, signal.SIGKILL): try: os.killpg(process.pid, sig) except ProcessLookupError: break time.sleep(5) return { "api_parent_pid": process.pid, "sigint_wall_time": called_at, "group_gone_wall_time": time.time(), "graceful": graceful, "returncode": process.poll(), } def wait_stable_zero() -> list[list[dict[str, int]]]: samples = [] consecutive = 0 deadline = time.monotonic() + 120 while time.monotonic() < deadline and consecutive < 3: rows = gpu_rows() samples.append(rows) zero = rows[0]["memory_mib"] == 0 and not compute_apps() consecutive = consecutive + 1 if zero else 0 if consecutive < 3: time.sleep(5) if consecutive < 3: raise RuntimeError("GPU0 did not reach three stable zero samples") return samples def validate_footer() -> dict[str, Any]: files = sorted((RUN_DIR / "opprof").glob("*.jsonl")) if len(files) != 1: raise RuntimeError(f"expected one OpProf JSONL, found {len(files)}") lines = files[0].read_text().splitlines() decoded = [json.loads(line) for line in lines] if not decoded or decoded[-1].get("record_type") != "footer": raise RuntimeError("Layer-1 footer missing") records, footer = decoded[:-1], decoded[-1] indices = [record["step_index"] for record in records] invariants = { "all_schema_1": all(item.get("schema") == 1 for item in decoded), "one_footer_last": sum( item.get("record_type") == "footer" for item in decoded ) == 1, "steps_contiguous": indices == list(range(len(indices))), "written_matches_records": footer["written_records"] == len(records), "encoded_balanced": footer["encoded_records"] == footer["written_records"] + footer["dropped_records"], "zero_drops": footer["dropped_records"] == 0 and all(record["dropped_records_before"] == 0 for record in records), } if not all(invariants.values()): raise RuntimeError(f"footer accounting invalid: {invariants}, {footer}") return { "path": str(files[0]), "bytes": files[0].stat().st_size, "records": len(records), "footer": footer, "invariants": invariants, } def main() -> None: if RUN_DIR.exists(): raise RuntimeError(f"refusing to overwrite {RUN_DIR}") RUN_DIR.mkdir(parents=True) state: dict[str, Any] = { "schema": 1, "status": "preflight", "started_at": time.time(), "gpu": 0, } atomic_json(RUN_DIR / "state.json", state) before = gpu_rows() if before[0]["memory_mib"] != 0 or before[0]["utilization_pct"] != 0: raise RuntimeError(f"GPU0 is not idle: {before[0]}") if compute_apps(): raise RuntimeError("a compute process exists before verification") (RUN_DIR / "gpu-before.json").write_text(json.dumps(before, indent=2) + "\n") (RUN_DIR / "clocks-before.txt").write_text( run_text(["nvidia-smi", "-q", "-d", "CLOCK"]) ) profile_config = { "profiler": "torch", "torch_profiler_dir": "/tmp/wjh-opprof-p3-footer-verify", "ignore_frontend": True, "wait_iterations": 0, "warmup_iterations": 2, "active_iterations": 8, } trace_dir = Path(profile_config["torch_profiler_dir"]) if trace_dir.exists(): shutil.rmtree(trace_dir) trace_dir.mkdir() server_command = [ "taskset", "-c", "0-19", str(VENV / "bin/vllm"), "serve", str(MODEL), "--host", "127.0.0.1", "--port", str(PORT), "--tensor-parallel-size", "1", "--enable-chunked-prefill", "--enable-prefix-caching", "--profiler-config", json.dumps(profile_config, separators=(",", ":")), ] client_command = [ "taskset", "-c", "0-19", str(VENV / "bin/python"), str(CLIENT), "run", "--manifest", str(MANIFEST), "--base-url", f"http://127.0.0.1:{PORT}", "--model", str(MODEL), "--load-point", "saturation", "--request-rate", "inf", "--max-concurrency", "256", "--ignore-eos", "--temperature", "0", "--warmup-seconds", "30", "--clean-segment-seconds", "40", "--num-clean-segments", "3", "--drain-timeout-seconds", "120", "--workload-seed", "20260712", "--result-dir", str(RUN_DIR / "client"), ] with (RUN_DIR / "commands.log").open("w") as f: f.write( "GPU_COMMAND shutdown footer server: " + shlex.join(server_command) + " ; expected=60-180s startup + 150-180s load\n" ) f.write( "GPU_COMMAND shutdown footer client: " + shlex.join(client_command) + " ; expected=30s warmup + 120s clean + drain\n" ) server_log = (RUN_DIR / "server.log").open("ab", buffering=0) env = os.environ.copy() env.update( { "CUDA_VISIBLE_DEVICES": "0", "VLLM_OPPROF_DIR": str(RUN_DIR / "opprof"), "HF_HUB_OFFLINE": "1", "TRANSFORMERS_OFFLINE": "1", "PYTHONUNBUFFERED": "1", } ) server: subprocess.Popen[Any] | None = None try: print( "GPU_COMMAND shutdown-fix verification: P01/C00 GPU0, " "30s warmup + 120s clean, expected 4-6 wall-min", flush=True, ) server = subprocess.Popen( server_command, cwd=SOURCE, env=env, stdout=server_log, stderr=subprocess.STDOUT, start_new_session=True, ) state.update({"status": "server_starting", "server_pid": server.pid}) atomic_json(RUN_DIR / "state.json", state) wait_ready(server) state.update({"status": "client_running", "server_ready_at": time.time()}) atomic_json(RUN_DIR / "state.json", state) with (RUN_DIR / "client.log").open("ab", buffering=0) as client_log: result = subprocess.run( client_command, cwd=WORKDIR, stdout=client_log, stderr=subprocess.STDOUT, ) if result.returncode: raise RuntimeError(f"client exited {result.returncode}") state["status"] = "parent_first_shutdown" atomic_json(RUN_DIR / "state.json", state) shutdown = shutdown_parent_first(server) if not shutdown["graceful"]: raise RuntimeError(f"server required escalation: {shutdown}") footer = validate_footer() zero_samples = wait_stable_zero() client_result = json.loads((RUN_DIR / "client/result.json").read_text()) result_json = { "schema": 1, "status": "pass", "gpu": 0, "shutdown": shutdown, "footer": footer, "clean": client_result["clean"], "failed_records": client_result["failed_records"], "gpu_zero_samples": zero_samples, "gpu_seconds": zero_samples and time.time() - state["started_at"], } atomic_json(RUN_DIR / "result.json", result_json) state.update({"status": "complete", "completed_at": time.time()}) atomic_json(RUN_DIR / "state.json", state) print(json.dumps(result_json, sort_keys=True), flush=True) except Exception as error: state.update({"status": "failed", "failure": repr(error)}) atomic_json(RUN_DIR / "state.json", state) if server is not None and group_alive(server.pid, server): try: os.killpg(server.pid, signal.SIGKILL) except ProcessLookupError: pass raise finally: server_log.close() if __name__ == "__main__": main()