"""Inference engine end-to-end test (extensions_built_in/inference_engine). Starts the engine in-process on an ephemeral loopback port, sends real generate requests over HTTP, decodes the streamed frames and asserts on them. Model/sample settings come from testing/test_model_loading.py's MODEL_TESTS (tiny sizes: this proves the path, not quality). Usage: python testing/test_inference_engine.py --arch zimage # one arch python testing/test_inference_engine.py --swap zimage,wan21 # sequential # model switches python testing/test_inference_engine.py --arch zimage --cancel # cancel mid-run python testing/test_inference_engine.py --all # every arch, # subprocess each --allow-download permit hub downloads (default HF_HUB_OFFLINE=1 -> SKIP) --device cuda:0 """ import argparse import http.client import json import os import shutil import subprocess import sys import time TOOLKIT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, TOOLKIT_ROOT) from dotenv import load_dotenv load_dotenv(os.path.join(TOOLKIT_ROOT, ".env")) OUTPUT_ROOT = os.path.join(TOOLKIT_ROOT, "testing", ".engine_test_outputs") from testing.test_model_loading import MODEL_TESTS, SKIP_MARKERS, classify_error # noqa: E402 class Client: def __init__(self, host, port, token): self.host, self.port, self.token = host, port, token def _conn(self): return http.client.HTTPConnection(self.host, self.port, timeout=3600) def _headers(self): return {"Content-Type": "application/json", "X-Engine-Token": self.token} def get(self, path): c = self._conn() c.request("GET", path, headers=self._headers()) r = c.getresponse() return r.status, json.loads(r.read()) def post_json(self, path, body=None): c = self._conn() c.request("POST", path, body=json.dumps(body or {}), headers=self._headers()) r = c.getresponse() return r.status, json.loads(r.read()) def upload(self, path, name): c = self._conn() with open(path, "rb") as f: data = f.read() c.request("POST", f"/assets?name={name}", body=data, headers={"X-Engine-Token": self.token}) r = c.getresponse() return r.status, json.loads(r.read()) def generate_stream(self, body): """Yields (header, payload) frames as they arrive.""" from extensions_built_in.inference_engine.protocol import FrameReader c = self._conn() c.request("POST", "/generate", body=json.dumps(body), headers=self._headers()) r = c.getresponse() if r.status != 200: raise RuntimeError(f"/generate -> {r.status}: {r.read()[:400]}") def chunks(): while True: data = r.read1(65536) # read1 returns the available chunk instead of blocking for 64KB if not data: return yield data reader = FrameReader(chunks()) for frame in reader: yield frame c.close() def start_engine(device: str, out_root: str): from extensions_built_in.inference_engine.engine import Engine from extensions_built_in.inference_engine.server import create_app, serve_in_thread import threading engine = Engine(device=device, output_folder=os.path.join(out_root, "outputs"), assets_folder=os.path.join(out_root, "assets")) token = "test-token" server, thread, host, port = serve_in_thread(create_app(engine, token)) loop = threading.Thread(target=engine.run_forever, daemon=True, name="engine-loop") loop.start() return engine, server, Client(host, port, token) def stop_engine(engine, server): engine.stop() server.should_exit = True engine.unload() QUANTIZE_ALL = False # --quantize: force convrot8 on transformer + TE (small GPUs) def request_body(arch: str, entry: dict, client: Client, out_dir: str, tiny=True, stream=None): model = dict(entry["model"], arch=arch) if QUANTIZE_ALL: model["quantize"] = True model["quantize_te"] = True if model.get("quantize"): model.setdefault("qtype", "convrot8") if model.get("quantize_te"): model.setdefault("qtype_te", "convrot8") sample = dict(entry["sample"]) sample["prompt"] = "a photo of a cat sitting on a wooden table" if tiny and not entry.get("size_locked"): # 384 divides every bucket size (16/32/64); video runs also keep the # frame count small so the swap tests fit next to a resident TE cap = 384 if tiny == "tiny" else 512 sample["width"] = min(sample["width"], cap) sample["height"] = min(sample["height"], cap) if tiny == "tiny" and "num_frames" in sample: sample["num_frames"] = min(sample["num_frames"], 9) if entry.get("needs_control_image"): from PIL import Image ctrl_path = os.path.join(out_dir, "ctrl.png") Image.new("RGB", (sample["width"], sample["height"]), (128, 128, 128)).save(ctrl_path) status, asset = client.upload(ctrl_path, "ctrl.png") assert status == 200, asset sample["ctrl_img"] = asset["path"] return {"model": model, "sample": sample, "stream": stream or {"latents": "raw", "every_n_steps": 1, "max_frames": 2}} def run_request(client: Client, body: dict, cancel_after: int = 0) -> dict: """Consume one streamed generation; returns a summary of the frames.""" import numpy as np from extensions_built_in.inference_engine.protocol import payload_to_array seen = {"start": 0, "status": 0, "progress": 0, "latent": 0, "result": 0, "error": 0, "end": 0} summary = {"frames": seen, "results": [], "latent_shapes": [], "error": None, "cancelled": False, "request_id": None, "preview": None} for header, payload in client.generate_stream(body): t = header["type"] seen[t] = seen.get(t, 0) + 1 summary["request_id"] = header.get("request_id") if t == "start": summary["preview"] = header.get("preview") summary["modality"] = header.get("modality") elif t == "status": print(f" [status] {header.get('message')}") elif t == "progress": print(f" [progress] {header['step']}/{header['total']} {header['elapsed']}s", flush=True) if cancel_after and header["step"] >= cancel_after: st, res = client.post_json(f"/cancel/{summary['request_id']}") assert st == 200 and res["ok"], res cancel_after = 0 elif t == "latent": arr = payload_to_array(header, payload) assert np.isfinite(arr.astype(np.float32)).all(), "non-finite latent" summary["latent_shapes"].append((header["layout"], list(arr.shape))) elif t == "result": summary["results"].append(header) print(f" [result] {header['kind']} {header['path']} ({header['bytes']} bytes)") elif t == "error": summary["error"] = header.get("message") summary["cancelled"] = bool(header.get("cancelled")) if header.get("traceback"): print(header["traceback"]) elif t == "end": summary["status"] = header.get("status") return summary def assert_success(summary: dict, modality: str): assert summary["frames"]["start"] == 1, summary assert summary["frames"]["end"] == 1, summary assert summary["error"] is None, summary["error"] assert summary["frames"]["progress"] >= 1, "no progress frames" assert summary["frames"]["latent"] >= 1, "no latent frames" assert summary["results"], "no result frames" for r in summary["results"]: assert os.path.isfile(r["path"]) and r["bytes"] > 1024, r kinds = {r["kind"] for r in summary["results"]} assert modality in kinds or (modality == "video" and "image" in kinds), (modality, kinds) def run_one(arch: str, device: str, cancel: bool = False) -> dict: from toolkit.models.registry import get_arch_entry entry = MODEL_TESTS[arch] out_root = os.path.join(OUTPUT_ROOT, arch.replace("/", "_")) shutil.rmtree(out_root, ignore_errors=True) os.makedirs(out_root, exist_ok=True) engine, server, client = start_engine(device, out_root) try: st, health = client.get("/health") assert st == 200 and health["ok"], health st, models = client.get("/models") assert st == 200 and any(m["arch"] == arch for m in models["archs"]), f"{arch} missing from /models" modality = get_arch_entry(arch)["modality"] body = request_body(arch, entry, client, out_root) t0 = time.perf_counter() if cancel: s = run_request(client, body, cancel_after=2) assert s["cancelled"], f"expected a cancelled stream, got {s}" assert s["frames"]["end"] == 1 print(" cancel: ok, re-running the same request") s = run_request(client, body) gen_seconds = time.perf_counter() - t0 assert_success(s, modality) st, health = client.get("/health") assert health["active"]["arch"] == arch, health return { "arch": arch, "status": "PASS", "seconds": round(gen_seconds, 2), "frames": s["frames"], "latent_shapes": s["latent_shapes"][:2], "results": [r["path"] for r in s["results"]], "preview_format": (s["preview"] or {}).get("format"), "stats": engine.stats, } finally: stop_engine(engine, server) def run_swap(archs, device: str) -> dict: out_root = os.path.join(OUTPUT_ROOT, "swap_" + "_".join(a.replace("/", "_") for a in archs)) shutil.rmtree(out_root, ignore_errors=True) os.makedirs(out_root, exist_ok=True) engine, server, client = start_engine(device, out_root) from toolkit.models.registry import get_arch_entry timeline = [] try: for i, arch in enumerate(archs): body = request_body(arch, MODEL_TESTS[arch], client, out_root, tiny="tiny") t0 = time.perf_counter() s = run_request(client, body) assert_success(s, get_arch_entry(arch)["modality"]) pool = engine.pool.stats() resident = [(e["cls"], round(e["bytes"] / 1e9, 2), e["device"]) for e in pool["resident"]] entry = { "arch": arch, "seconds": round(time.perf_counter() - t0, 2), "holder_loads": engine.stats["holder_loads"], "oom_retries": engine.stats["oom_retries"], "pool_hits": pool["hits"], "pool_misses": pool["misses"], "pool_evictions": pool["evictions"], "resident_gb": round(pool["resident_bytes"] / 1e9, 2), "resident": resident, } timeline.append(entry) print(f" {arch}: {json.dumps(entry)}") # after a request only this holder's components may stay resident assert pool["untouched"] == 0, f"stale components left resident: {pool}" if i > 0 and archs[i - 1] == arch: assert entry["holder_loads"] == timeline[i - 1]["holder_loads"], "same model should reuse the holder" if len(set(archs)) > 1: assert engine.pool.hits > 0, "expected shared components between the swapped archs" return {"arch": ",".join(archs), "status": "PASS", "timeline": timeline} finally: stop_engine(engine, server) def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--arch", type=str, default=None) parser.add_argument("--swap", type=str, default=None, help="comma-separated archs to run back to back") parser.add_argument("--cancel", action="store_true") parser.add_argument("--all", action="store_true") parser.add_argument("--list", action="store_true") parser.add_argument("--device", type=str, default="cuda:0") parser.add_argument("--allow-download", action="store_true") parser.add_argument("--json-result", type=str, default=None) parser.add_argument("--quantize", action="store_true", help="force convrot8 quantization of transformer + TE") args = parser.parse_args() global QUANTIZE_ALL QUANTIZE_ALL = args.quantize if args.list: for arch in MODEL_TESTS: print(arch) return os.environ.setdefault("CUDA_DEVICE_ORDER", "PCI_BUS_ID") if not args.allow_download: os.environ.setdefault("HF_HUB_OFFLINE", "1") def _record(result): if args.json_result: with open(args.json_result, "w") as f: json.dump(result, f) print(f"[{result['status']}] {result['arch']}" + (f" - {result.get('error', '')}" if result["status"] != "PASS" else f" {json.dumps({k: v for k, v in result.items() if k not in ('arch', 'status')})}")) if result["status"] == "FAIL": sys.exit(1) if args.swap: archs = [a.strip() for a in args.swap.split(",") if a.strip()] try: result = run_swap(archs, args.device) except BaseException as err: import traceback traceback.print_exc() result = {"arch": args.swap, "status": classify_error(err), "error": f"{type(err).__name__}: {err}"} _record(result) return if args.arch: if args.arch not in MODEL_TESTS: raise SystemExit(f"arch {args.arch!r} is not registered; --list shows options") try: result = run_one(args.arch, args.device, cancel=args.cancel) except BaseException as err: status = classify_error(err) if status == "FAIL": import traceback traceback.print_exc() result = {"arch": args.arch, "status": status, "error": f"{type(err).__name__}: {err}"} _record(result) return if not args.all: parser.print_help() return results = [] total = len(MODEL_TESTS) t0 = time.perf_counter() for i, arch in enumerate(MODEL_TESTS, start=1): print(f"\n===== {arch} ({i}/{total}) =====", flush=True) result_path = os.path.join(OUTPUT_ROOT, f".{arch.replace('/', '_')}.result.json") os.makedirs(OUTPUT_ROOT, exist_ok=True) cmd = [sys.executable, os.path.abspath(__file__), "--arch", arch, "--device", args.device, "--json-result", result_path] if args.allow_download: cmd.append("--allow-download") if args.quantize: cmd.append("--quantize") proc = subprocess.run(cmd, cwd=TOOLKIT_ROOT) if os.path.exists(result_path): with open(result_path) as f: results.append(json.load(f)) os.remove(result_path) else: results.append({"arch": arch, "status": "FAIL", "error": f"subprocess died (exit {proc.returncode})"}) tally = {"PASS": 0, "FAIL": 0, "SKIP": 0} for r in results: tally[r["status"]] = tally.get(r["status"], 0) + 1 elapsed = time.perf_counter() - t0 print(f">>> progress: {i}/{total} - {tally['PASS']} pass, {tally['FAIL']} fail, {tally['SKIP']} skip - elapsed {elapsed / 60:.0f}m", flush=True) print("\n===== summary =====") counts = {"PASS": 0, "FAIL": 0, "SKIP": 0} for r in results: counts[r["status"]] = counts.get(r["status"], 0) + 1 line = f"[{r['status']}] {r['arch']}" if r["status"] != "PASS": line += f" - {r.get('error', '')[:160]}" print(line) print(f"\n{counts['PASS']} passed, {counts['FAIL']} failed, {counts['SKIP']} skipped") with open(os.path.join(OUTPUT_ROOT, "report.json"), "w") as f: json.dump(results, f, indent=2) if counts["FAIL"]: sys.exit(1) if __name__ == "__main__": main()