unsloth/studio/backend/tests/test_diffusion_precision.py
Daniel Han 817e14ad19 Studio diffusion (Phase 2B): opt-in fp8 text-encoder layerwise casting
Add a text_encoder_fp8 knob that casts the companion text encoder(s) to fp8 (e4m3)
storage via diffusers apply_layerwise_casting, upcasting per layer to the bf16
compute dtype while normalisations and embeddings stay full precision. Applied
before placement, gated to CUDA + bf16, best-effort (a failure leaves the encoder
dense). status reports which encoders were cast.

Verified on Z-Image (B200, balanced/group mode where the encoder stays resident):
generation peak VRAM dropped 37% (10840 -> 6791 MB, below the lowest-VRAM offload)
at near-resident speed. It is a memory-vs-quality tradeoff, not free -- ~20 dB PSNR
vs the bf16 encoder, a larger shift than one transformer quant step -- so it is off
by default and documented as such, with the Phase 5 harness to size the cost.

127 CPU tests pass.
2026-06-25 13:55:08 +00:00

111 lines
4.3 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 fp8 text-encoder casting (``diffusion_precision.py``).
Hermetic: torch + diffusers.hooks are stubbed via ``sys.modules`` so the gating and
the apply path run without a GPU or real diffusers.
"""
from __future__ import annotations
import sys
import types
import pytest
from core.inference.diffusion_precision import (
apply_fp8_text_encoder,
fp8_text_encoder_supported,
)
def _target(*, device = "cuda", dtype = "bfloat16"):
return types.SimpleNamespace(device = device, dtype = dtype)
def _stub_torch(monkeypatch, *, with_fp8 = True):
torch = types.ModuleType("torch")
torch.bfloat16 = "bfloat16"
torch.float16 = "float16"
if with_fp8:
torch.float8_e4m3fn = "float8_e4m3fn"
monkeypatch.setitem(sys.modules, "torch", torch)
return torch
def _stub_diffusers_hooks(monkeypatch, recorder):
hooks = types.ModuleType("diffusers.hooks")
casting = types.ModuleType("diffusers.hooks.layerwise_casting")
casting.DEFAULT_SKIP_MODULES_PATTERN = ("norm",)
def _apply(module, *, storage_dtype, compute_dtype, skip_modules_pattern):
recorder.append((module, storage_dtype, compute_dtype, skip_modules_pattern))
hooks.apply_layerwise_casting = _apply
monkeypatch.setitem(sys.modules, "diffusers.hooks", hooks)
monkeypatch.setitem(sys.modules, "diffusers.hooks.layerwise_casting", casting)
# ── gating ────────────────────────────────────────────────────────────────────
def test_fp8_supported_requires_cuda_bf16_and_fp8_dtype(monkeypatch):
_stub_torch(monkeypatch, with_fp8 = True)
assert fp8_text_encoder_supported(_target()) is True
assert fp8_text_encoder_supported(_target(device = "cpu")) is False
assert fp8_text_encoder_supported(_target(dtype = "float16")) is False
def test_fp8_unsupported_without_fp8_dtype(monkeypatch):
_stub_torch(monkeypatch, with_fp8 = False)
assert fp8_text_encoder_supported(_target()) is False
# ── apply ─────────────────────────────────────────────────────────────────────
def test_apply_disabled_returns_empty(monkeypatch):
_stub_torch(monkeypatch)
pipe = types.SimpleNamespace(text_encoder = object())
assert apply_fp8_text_encoder(pipe, _target(), enable = False) == []
def test_apply_casts_all_present_text_encoders(monkeypatch):
_stub_torch(monkeypatch)
recorder: list = []
_stub_diffusers_hooks(monkeypatch, recorder)
te1, te3 = object(), object()
# text_encoder + text_encoder_3 present, text_encoder_2 absent.
pipe = types.SimpleNamespace(text_encoder = te1, text_encoder_2 = None, text_encoder_3 = te3)
cast = apply_fp8_text_encoder(pipe, _target(), enable = True)
assert cast == ["text_encoder", "text_encoder_3"]
# Casts to fp8 storage with bf16 compute and skips norms.
assert {r[0] for r in recorder} == {te1, te3}
assert all(r[1] == "float8_e4m3fn" and r[2] == "bfloat16" for r in recorder)
def test_apply_unsupported_target_is_noop(monkeypatch):
_stub_torch(monkeypatch)
recorder: list = []
_stub_diffusers_hooks(monkeypatch, recorder)
pipe = types.SimpleNamespace(text_encoder = object())
assert apply_fp8_text_encoder(pipe, _target(device = "cpu"), enable = True) == []
assert recorder == []
def test_apply_tolerates_casting_failure(monkeypatch):
_stub_torch(monkeypatch)
hooks = types.ModuleType("diffusers.hooks")
casting = types.ModuleType("diffusers.hooks.layerwise_casting")
casting.DEFAULT_SKIP_MODULES_PATTERN = ("norm",)
def _boom(module, **kwargs):
raise RuntimeError("fp8 not supported for this layer")
hooks.apply_layerwise_casting = _boom
monkeypatch.setitem(sys.modules, "diffusers.hooks", hooks)
monkeypatch.setitem(sys.modules, "diffusers.hooks.layerwise_casting", casting)
pipe = types.SimpleNamespace(text_encoder = object())
# A casting failure leaves the encoder dense and reports nothing cast.
assert apply_fp8_text_encoder(pipe, _target(), enable = True) == []