Download tools/time_checkpoints.py from aireet/viggle-animate-workflow: direct link, hf CLI and curl.
- Browser
- Download file 4.3 kB
-
https://huggingface.co/aireet/viggle-animate-workflow/resolve/main/tools/time_checkpoints.py
- Command line
-
hf download hf://aireet/viggle-animate-workflow/tools/time_checkpoints.py
-
curl -L -o time_checkpoints.py https://huggingface.co/aireet/viggle-animate-workflow/resolve/main/tools/time_checkpoints.py
4.3 kB
| """Time the same workflow across several checkpoints, twice each (cold + warm), via the ComfyUI API. | |
| python3 time_checkpoints.py --host 127.0.0.1 --port 8188 --workflow smoke-prompt.json \\ | |
| --checkpoints name1.safetensors name2.safetensors | |
| Prints, per run: wall time (submit -> finished) and the server's own "Prompt executed" figure is | |
| captured separately from the container log. The first run of each checkpoint includes loading it | |
| from disk; the second is the steady state. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import sys | |
| import time | |
| import urllib.parse | |
| import urllib.request | |
| import uuid | |
| from pathlib import Path | |
| def post(url: str, payload: dict) -> dict: | |
| req = urllib.request.Request(url, data=json.dumps(payload).encode(), headers={"Content-Type": "application/json"}) | |
| with urllib.request.urlopen(req, timeout=120) as resp: | |
| return json.loads(resp.read()) | |
| def get(url: str) -> dict: | |
| with urllib.request.urlopen(url, timeout=60) as resp: | |
| return json.loads(resp.read()) | |
| def run_once(base: str, graph: dict, checkpoint: str, prefix: str, timeout: float, seed: int | None = None) -> dict: | |
| graph["13"]["inputs"]["unet_name"] = checkpoint | |
| graph["21"]["inputs"]["filename_prefix"] = prefix | |
| for node in graph.values(): | |
| if node.get("class_type") == "UNETLoader": | |
| node["inputs"]["unet_name"] = checkpoint | |
| if node.get("class_type") == "VHS_VideoCombine": | |
| node["inputs"]["filename_prefix"] = prefix | |
| if node.get("class_type") == "RandomNoise" and seed is not None: | |
| # ComfyUI caches per-node results: an unchanged seed makes the whole sampler chain a | |
| # cache hit (~0.7 s), so a *real* warm re-run needs a new noise seed. | |
| node["inputs"]["noise_seed"] = seed | |
| t0 = time.time() | |
| started = post(f"{base}/prompt", {"prompt": graph, "client_id": str(uuid.uuid4())})["prompt_id"] | |
| first_seen = None | |
| while time.time() - t0 < timeout: | |
| history = get(f"{base}/history/{started}") | |
| entry = history.get(started) | |
| if entry and entry.get("outputs"): | |
| status = entry.get("status", {}) | |
| if status.get("status_str") != "success": | |
| return {"ok": False, "error": json.dumps(status)[:200], "wall": time.time() - t0} | |
| exec_ms = None | |
| for name, payload in status.get("messages", []): | |
| if name == "execution_start": | |
| first_seen = payload.get("timestamp") | |
| if name == "execution_success": | |
| exec_ms = payload.get("timestamp", 0) - (first_seen or 0) | |
| return {"ok": True, "wall": time.time() - t0, "exec": (exec_ms or 0) / 1000.0} | |
| time.sleep(1) | |
| return {"ok": False, "error": "timeout", "wall": time.time() - t0} | |
| def main(argv: list[str] | None = None) -> int: | |
| ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) | |
| ap.add_argument("--host", required=True) | |
| ap.add_argument("--port", type=int, default=8188) | |
| ap.add_argument("--workflow", required=True) | |
| ap.add_argument("--checkpoints", nargs="+", required=True) | |
| ap.add_argument("--repeats", type=int, default=2) | |
| ap.add_argument("--seed-base", type=int, default=2347709, help="seed for run 1; run N uses base+N-1") | |
| ap.add_argument("--timeout", type=float, default=900) | |
| args = ap.parse_args(argv) | |
| base = f"http://{args.host}:{args.port}" | |
| graph = json.loads(Path(args.workflow).read_text()) | |
| graph = graph.get("prompt", graph) | |
| print(f"{'checkpoint':<48s} {'run':>3s} {'seed':>9s} {'wall s':>8s} {'service s':>10s}") | |
| for ckpt in args.checkpoints: | |
| for i in range(1, args.repeats + 1): | |
| prefix = f"timing/{Path(ckpt).stem[:28]}_{i}" | |
| seed = args.seed_base + i - 1 | |
| result = run_once(base, json.loads(json.dumps(graph)), ckpt, prefix, args.timeout, seed=seed) | |
| if result["ok"]: | |
| print(f"{ckpt:<48s} {i:>3d} {seed:>9d} {result['wall']:>8.1f} {result['exec']:>10.1f}") | |
| else: | |
| print(f"{ckpt:<48s} {i:>3d} {seed:>9d} FAILED: {result['error']}") | |
| sys.stdout.flush() | |
| return 0 | |
| if __name__ == "__main__": | |
| sys.exit(main()) | |