unsloth/scripts/perf_verify.py
Daniel Han 395816cf7e Studio diffusion (Phase 7): accuracy-preserving speed pass
Re-review of the diffusion stack (#6675/#6679/#6680) surfaced one real accuracy
bug and a dead-on-arrival speed path; this fixes both and adds the lossless /
near-lossless wins, all measured on a B200.

Correctness:
- TF32 global-state leak (fix). speed_mode=max flipped torch.backends.*.allow_tf32
  process-wide and never restored them, so a later `off` load silently inherited
  TF32 and was no longer bit-identical. Added snapshot_backend_flags /
  restore_backend_flags (TF32 + cudnn.benchmark), captured before the speed layer
  runs and restored on unload. Verified: load max -> unload -> load off is now
  byte-identical (PSNR inf) to a fresh off.
- sd-cli timeout could hang forever. _run() blocked in `for line in stdout` and
  only checked the timeout after EOF, so a child stuck in model load / GPU init
  with no output ignored the timeout. Drained stdout on a reader thread with a
  wall-clock deadline. Added a silent-hang regression test.

Speed (diffusers path), near-lossless, opt-in tiers:
- Regional torch.compile now runs on the GGUF transformer. The is_gguf gate (and
  Z-Image's supports_torch_compile=False) were stale: compile_repeated_blocks
  compiles and runs ~2.2x faster on the GGUF Z-Image transformer on
  torch 2.9.1 / diffusers 0.38 (the per-op dequant stays eager, the rest of the
  block compiles). Measured: off 1.80s -> default 0.82s/gen (+54.7%), PSNR 37.7 dB
  vs eager -- far above the Q4 quant noise floor (~21 dB), so it does not move
  output quality. Gate relaxed; default tier delivers it.
- cudnn.benchmark added to the default tier (autotunes the fixed-shape VAE convs).
- torch.inference_mode() around the pipeline call (lossless, strictly faster than
  the no_grad diffusers uses internally).

Memory path:
- VAE tiling (not bit-identical >1MP) restricted to the model/sequential/CPU tiers;
  the balanced (group) tier keeps exact slicing only, so it is now bit-identical to
  the resident image (verified PSNR inf) and slightly faster.
- Group offload adds non_blocking + record_stream on the CUDA stream path to
  overlap each block's H2D copy with compute (lossless; gated on the installed
  diffusers signature so older versions still work).

Native (sd.cpp) path:
- native_speed_flags: a first-class speed knob (default -> --diffusion-fa, a
  near-lossless CUDA win that was previously only added on offload tiers; max also
  -> --diffusion-conv-direct). conv-direct stays opt-in: measured +45% on CUDA, so
  it is never auto-on. Engine generate() merges it, de-duped against offload flags.

Default profile: a GGUF model with no explicit speed_mode now resolves to the
`default` profile (resolve_speed_mode), since compile's perturbation sits below the
quantisation noise floor and so does not reduce quality versus the dense reference;
out of the box a GGUF Z-Image generation drops from 1.80s to 0.81s. Dense models
stay `off` / bit-identical, and an explicit speed_mode -- including "off" -- is
always honored, so the byte-identical path remains one flag away and is the
regression reference.

Tooling: scripts/compile_probe.py (eager vs compiled GGUF probe), scripts/
perf_verify.py (the B200 verification above), and diffusion_bench.py gains
--speed-mode so the speed tiers are benchmarkable.

Tests: 183 passing (was 166); new coverage for the backend-flag snapshot/restore,
GGUF compile eligibility, the balanced tiling/slicing split, native_speed_flags +
the engine de-dup, and the sd-cli silent-hang timeout.
2026-06-26 03:18:44 +00:00

130 lines
5.2 KiB
Python

# SPDX-License-Identifier: AGPL-3.0-only
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
"""GPU verification for the diffusion performance pass (Phase 7).
Drives the real ``DiffusionBackend`` through several loads in one process and
checks, at a fixed seed:
1. speed: ``default`` (compile + cudnn.benchmark + channels_last) vs ``off``
-- expect a large denoise speedup at high PSNR (near-lossless).
2. the TF32-leak fix: load ``max`` (flips global TF32 / cudnn.benchmark), unload,
then load ``off`` -- the ``off`` image must be byte-identical (PSNR inf) to a
fresh ``off`` baseline, proving the globals were restored on unload.
3. ``balanced`` is now bit-identical: with VAE tiling restricted to the low tiers,
streamed (group) offload should match the resident image (PSNR inf).
Run on one CUDA GPU with the GGUF + base repo cached.
"""
from __future__ import annotations
import argparse
import sys
import time
from pathlib import Path
import numpy as np
_BACKEND_ROOT = Path(__file__).resolve().parent.parent / "studio" / "backend"
if str(_BACKEND_ROOT) not in sys.path:
sys.path.insert(0, str(_BACKEND_ROOT))
def _psnr(a: "np.ndarray", b: "np.ndarray") -> float:
a = a.astype(np.float64)
b = b.astype(np.float64)
mse = float(np.mean((a - b) ** 2))
return float("inf") if mse == 0.0 else float(10.0 * np.log10((255.0**2) / mse))
def main(argv = None) -> int:
p = argparse.ArgumentParser()
p.add_argument("--model", default = "unsloth/Z-Image-Turbo-GGUF")
p.add_argument("--gguf", default = "z-image-turbo-Q4_K_M.gguf")
p.add_argument("--prompt", default = "A cinematic photograph of a red fox in a snowy forest at dawn, highly detailed")
p.add_argument("--steps", type = int, default = 8)
p.add_argument("--seed", type = int, default = 42)
p.add_argument("--width", type = int, default = 1024)
p.add_argument("--height", type = int, default = 1024)
p.add_argument("--out-dir", default = "outputs/perf_verify")
args = p.parse_args(argv)
import os
import torch
from core.inference.diffusion import DiffusionBackend
out = Path(args.out_dir)
out.mkdir(parents = True, exist_ok = True)
backend = DiffusionBackend()
token = os.environ.get("HF_TOKEN")
def load(mode_speed = None, mode_mem = None):
backend.begin_load(
args.model, gguf_filename = args.gguf, hf_token = token,
speed_mode = mode_speed, memory_mode = mode_mem,
)
deadline = time.time() + 2400
while time.time() < deadline:
ph = backend.load_progress().get("phase")
if ph == "ready":
return backend.status()
if ph == "error":
raise RuntimeError(f"load error: {backend.load_progress()}")
time.sleep(0.5)
raise RuntimeError("load timed out")
def gen():
torch.cuda.synchronize()
t0 = time.time()
img = backend.generate(
prompt = args.prompt, width = args.width, height = args.height,
steps = args.steps, guidance = 0.0, seed = args.seed, batch_size = 1,
)["images"][0]
torch.cuda.synchronize()
return img, time.time() - t0
def timed(mode_speed, *, warmup, iters, mem = None, tag = ""):
st = load(mode_speed, mem)
for _ in range(warmup):
gen()
lats = []
img = None
for _ in range(iters):
img, dt = gen()
lats.append(dt)
img.save(out / f"{tag}.png")
backend.unload()
med = sorted(lats)[len(lats) // 2]
print(f" [{tag}] speed={mode_speed} mem={mem} optims={st.get('speed_optims')} "
f"tiling={st.get('vae_tiling')} median={med:.3f}s", flush = True)
return np.array(img), med
print("== 1. speed: off vs default ==", flush = True)
off_img, off_t = timed("off", warmup = 1, iters = 3, tag = "off")
def_img, def_t = timed("default", warmup = 1, iters = 3, tag = "default")
print(f" PSNR(default vs off) = {_psnr(off_img, def_img):.1f} dB", flush = True)
print(f" speedup: off {off_t:.3f}s -> default {def_t:.3f}s "
f"({(off_t-def_t)/off_t*100:+.1f}%)", flush = True)
print("== 2. TF32-leak fix: max then off must be byte-identical ==", flush = True)
timed("max", warmup = 0, iters = 1, tag = "max") # flips + should restore globals
off2_img, _ = timed("off", warmup = 0, iters = 1, tag = "off2")
leak_psnr = _psnr(off_img, off2_img)
print(f" PSNR(off-after-max vs off) = {leak_psnr:.1f} dB "
f"({'OK byte-identical' if leak_psnr == float('inf') else 'LEAK! globals not restored'})", flush = True)
print("== 3. balanced is bit-identical (tiling off) ==", flush = True)
bal_img, bal_t = timed("off", warmup = 0, iters = 1, mem = "balanced", tag = "balanced")
bal_psnr = _psnr(off_img, bal_img)
print(f" PSNR(balanced vs off) = {bal_psnr:.1f} dB "
f"({'OK bit-identical' if bal_psnr == float('inf') else 'differs'})", flush = True)
ok = (leak_psnr == float("inf")) and (def_t < off_t) and (_psnr(off_img, def_img) >= 30)
print(f"\nPERF-VERIFY {'OK' if ok else 'CHECK'}", flush = True)
return 0 if ok else 1
if __name__ == "__main__":
sys.exit(main())