#!/usr/bin/env python3 """Launch the resident Fast-WAM runtime and run the action-expert denoise loop on the NPU WITHOUT reload, verify parity vs the PyTorch golden, and measure latency. Fast-WAM's on-device unit here is the MoT **action expert** (`action_step`), which cross- attends to the video KV cache produced by the video world-model DiT (`video_prefill`). The video prefill runs host-side for now (its context binary needs a MolmoAct2-style layer-split, see README), so the host: 1. loads the video KV cache + context (here: from golden/, i.e. the video_prefill output), 2. runs the flow-matching Euler loop: for each of num_inference_steps, feed the current latents_action + timestep -> pred_noise (NPU), then update latents with the Wan continuous flow-match step latents += pred * delta, 3. verifies the single-step output vs golden (parity gate), reports latency. Pipeline (on-device portion): action_step ×N (resident). No per-inference reload. """ import sys, os, socket, struct, subprocess, time import numpy as np WS = os.environ.get("FASTWAM_WS", "/root/fastwam_workspace") RT = f"{WS}/runtime" ROLES = ["action_step"] PORT0 = 5801 STEPS = int(os.environ.get("FASTWAM_STEPS", "10")) # num_inference_steps SHIFT = float(os.environ.get("FASTWAM_SHIFT", "5.0")) # action_scheduler infer_shift NUM_TRAIN = int(os.environ.get("FASTWAM_NUM_TRAIN_TIMESTEPS", "1000")) def recvall(s, n): b = bytearray() while len(b) < n: c = s.recv(min(n - len(b), 8 << 20)) if not c: raise ConnectionError() b += c return bytes(b) def recv_tensors(s): n = struct.unpack("=0.999})", flush=True) # ---- full N-step flow-match loop (latency) ---- timesteps, deltas = build_schedule(STEPS, SHIFT, NUM_TRAIN) def run_loop(timing=None): la = la0.copy() for i in range(STEPS): ts = np.array([timesteps[i]], np.float32) s = time.time() o = call(socks["action_step"], {**const, "latents_action": la, "timestep": ts}) if timing is not None: timing["action_step"] = timing.get("action_step", 0) + time.time() - s pred = np.ascontiguousarray(o[key], np.float32).reshape(la.shape) la = la + pred * deltas[i] # Wan continuous flow-match step: x += pred*delta return la run_loop() # warmup N = 5 timing = {} t0 = time.time() for _ in range(N): run_loop(timing) total = (time.time() - t0) / N print(f"\n=== RESIDENT LATENCY (avg of {N}, 1 session, NO reload, {STEPS}-step flow-match) ===", flush=True) print(f" action_step (x{STEPS}) {timing['action_step']/N*1000:7.1f} ms (incl. video-KV TCP each step)", flush=True) print(f" {'per-step (wall)':18} {timing['action_step']/N/STEPS*1000:7.1f} ms", flush=True) print(f" {'TOTAL':18} {total*1000:7.1f} ms", flush=True) print(" (pure NPU ctx.Inference per step -> tmp/w_action_step.log [infer-ms])", flush=True) for s in socks.values(): try: s.close() except OSError: pass time.sleep(1) for p in procs.values(): try: p.terminate() except OSError: pass return 0 if __name__ == "__main__": sys.exit(main())