unsloth/studio/backend/tests/test_diffusion_speed.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

227 lines
8.5 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
"""Unit tests for the opt-in diffusion speed layer (``diffusion_speed.py``).
Hermetic: torch is stubbed via ``sys.modules`` only where a path needs it, so the
gating logic and the best-effort applier run without a GPU or real diffusers.
"""
from __future__ import annotations
import sys
import types
import pytest
from core.inference.diffusion_speed import (
SPEED_DEFAULT,
SPEED_MAX,
SPEED_OFF,
apply_speed_optims,
compile_eligible,
normalize_speed_mode,
resolve_speed_mode,
restore_backend_flags,
snapshot_backend_flags,
)
def _target(
*,
device = "cuda",
dtype = "bfloat16",
compile_ok = True,
):
return types.SimpleNamespace(
device = device,
dtype = dtype,
supports_default_torch_compile = compile_ok,
)
def _family(*, compile_ok = True):
return types.SimpleNamespace(supports_torch_compile = compile_ok)
def _stub_torch(monkeypatch):
torch = types.ModuleType("torch")
torch.bfloat16 = "bfloat16" # _is_bfloat16 compares by identity then str fallback
torch.channels_last = "channels_last"
torch.backends = types.SimpleNamespace(
cuda = types.SimpleNamespace(matmul = types.SimpleNamespace(allow_tf32 = False)),
cudnn = types.SimpleNamespace(allow_tf32 = False, benchmark = False),
)
monkeypatch.setitem(sys.modules, "torch", torch)
return torch
# ── normalisation ─────────────────────────────────────────────────────────────
def test_normalize_speed_mode():
assert normalize_speed_mode(None) == SPEED_OFF
assert normalize_speed_mode("") == SPEED_OFF
assert normalize_speed_mode("MAX") == SPEED_MAX
with pytest.raises(ValueError):
normalize_speed_mode("ludicrous")
def test_resolve_speed_mode_gguf_auto_default():
# Unset (None) -> default for GGUF (near-lossless), off for dense.
assert resolve_speed_mode(None, is_gguf = True) == SPEED_DEFAULT
assert resolve_speed_mode(None, is_gguf = False) == SPEED_OFF
# An explicit value is honored verbatim, including an explicit opt-out to off.
assert resolve_speed_mode("off", is_gguf = True) == SPEED_OFF
assert resolve_speed_mode("max", is_gguf = True) == SPEED_MAX
assert resolve_speed_mode("max", is_gguf = False) == SPEED_MAX
# ── compile gating ────────────────────────────────────────────────────────────
def test_compile_eligible_requires_bf16_cuda_friendly(monkeypatch):
_stub_torch(monkeypatch)
# The happy path: bf16, CUDA, compile-friendly family.
assert compile_eligible(_target(), is_gguf = False, family = _family()) is True
# GGUF is now compile-eligible too (measured ~2.3x, PSNR ~37 dB vs eager).
assert compile_eligible(_target(), is_gguf = True, family = _family()) is True
# fp16 (non-bf16) is excluded.
assert compile_eligible(_target(dtype = "float16"), is_gguf = False, family = _family()) is False
# A family flagged not compile-friendly is excluded.
assert compile_eligible(_target(), is_gguf = False, family = _family(compile_ok = False)) is False
# No compile support (e.g. XPU/MPS) is excluded.
assert compile_eligible(_target(compile_ok = False), is_gguf = False, family = _family()) is False
# ── backend-flag snapshot / restore (TF32 / cudnn.benchmark leak guard) ────────
def test_snapshot_restore_backend_flags(monkeypatch):
torch = _stub_torch(monkeypatch)
snap = snapshot_backend_flags()
assert snap == {"matmul_tf32": False, "cudnn_tf32": False, "cudnn_benchmark": False}
# An opt-in max run flips the globals on...
torch.backends.cuda.matmul.allow_tf32 = True
torch.backends.cudnn.allow_tf32 = True
torch.backends.cudnn.benchmark = True
# ...and restore puts them back, so a later `off` load is bit-identical again.
restore_backend_flags(snap)
assert torch.backends.cuda.matmul.allow_tf32 is False
assert torch.backends.cudnn.allow_tf32 is False
assert torch.backends.cudnn.benchmark is False
def test_restore_backend_flags_tolerates_none():
restore_backend_flags(None) # no torch needed, no-op
# ── applier ───────────────────────────────────────────────────────────────────
class _Pipe:
def __init__(
self,
*,
with_compile = False,
with_fuse = False,
) -> None:
self.vae = types.SimpleNamespace(mem_format = None, to = self._vae_to)
self.transformer = types.SimpleNamespace()
if with_compile:
self.transformer.compile_repeated_blocks = self._compile
if with_fuse:
self.fuse_qkv_projections = self._fuse
self.compiled = False
self.fused = False
def _vae_to(self, *, memory_format):
self.vae.mem_format = memory_format
def _compile(self, *, fullgraph, dynamic):
self.compiled = True
def _fuse(self):
self.fused = True
def test_speed_off_applies_nothing(monkeypatch):
torch = _stub_torch(monkeypatch)
pipe = _Pipe(with_compile = True, with_fuse = True)
applied = apply_speed_optims(
pipe, _target(), is_gguf = False, family = _family(), speed_mode = SPEED_OFF
)
assert applied == {
"channels_last": False, "cudnn_benchmark": False,
"tf32": False, "fused_qkv": False, "compiled": False,
}
assert pipe.vae.mem_format is None and pipe.compiled is False
# off must not touch any process-wide flag (bit-identical reference path).
assert torch.backends.cudnn.benchmark is False
def test_speed_default_channels_last_compile_and_cudnn_benchmark(monkeypatch):
torch = _stub_torch(monkeypatch)
pipe = _Pipe(with_compile = True)
applied = apply_speed_optims(
pipe, _target(), is_gguf = False, family = _family(), speed_mode = SPEED_DEFAULT
)
assert applied["channels_last"] is True and pipe.vae.mem_format == torch.channels_last
assert applied["compiled"] is True and pipe.compiled is True
# default also autotunes the VAE convs but does NOT flip TF32 or fuse QKV.
assert applied["cudnn_benchmark"] is True and torch.backends.cudnn.benchmark is True
assert applied["tf32"] is False and applied["fused_qkv"] is False
def test_speed_default_compiles_gguf(monkeypatch):
_stub_torch(monkeypatch)
pipe = _Pipe(with_compile = True)
applied = apply_speed_optims(
pipe, _target(), is_gguf = True, family = _family(), speed_mode = SPEED_DEFAULT
)
assert applied["channels_last"] is True
# GGUF now compiles (the big near-lossless win).
assert applied["compiled"] is True and pipe.compiled is True
def test_speed_default_cudnn_benchmark_only_on_cuda(monkeypatch):
_stub_torch(monkeypatch)
pipe = _Pipe(with_compile = True)
applied = apply_speed_optims(
pipe, _target(device = "mps", compile_ok = False), is_gguf = True,
family = _family(), speed_mode = SPEED_DEFAULT,
)
assert applied["cudnn_benchmark"] is False # not CUDA -> no autotune flip
def test_speed_max_enables_tf32_and_fused_qkv(monkeypatch):
torch = _stub_torch(monkeypatch)
pipe = _Pipe(with_compile = True, with_fuse = True)
applied = apply_speed_optims(
pipe, _target(), is_gguf = False, family = _family(), speed_mode = SPEED_MAX
)
assert applied["tf32"] is True and torch.backends.cuda.matmul.allow_tf32 is True
assert applied["fused_qkv"] is True and pipe.fused is True
def test_speed_max_tf32_only_on_cuda(monkeypatch):
_stub_torch(monkeypatch)
pipe = _Pipe()
applied = apply_speed_optims(
pipe,
_target(device = "mps", compile_ok = False),
is_gguf = True,
family = _family(),
speed_mode = SPEED_MAX,
)
assert applied["tf32"] is False # not CUDA -> no TF32
def test_apply_tolerates_missing_optims(monkeypatch):
_stub_torch(monkeypatch)
# A bare pipe (no vae.to, no compile, no fuse) must not crash.
bare = types.SimpleNamespace(vae = None, transformer = types.SimpleNamespace())
applied = apply_speed_optims(
bare, _target(), is_gguf = False, family = _family(), speed_mode = SPEED_MAX
)
assert applied["channels_last"] is False and applied["fused_qkv"] is False