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.
130 lines
5.2 KiB
Python
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())
|