From ac16073923c1dacc1c07b51e34cf99f2998c7a48 Mon Sep 17 00:00:00 2001 From: Daniel Han Date: Sat, 4 Jul 2026 07:13:50 +0000 Subject: [PATCH 01/13] Enable fp16-GEMM accumulation on consumer GPUs behind an overflow-validated gate fp16 accumulation (torch.backends.cuda.matmul.allow_fp16_accumulation) roughly doubles fp16 GEMM throughput on consumer tensor cores by keeping the accumulator in fp16. The flag only affects fp16 GEMMs: bf16-compute DiT families are untouched by construction, while SDXL's fp16 UNet and any fp16 text encoder or VAE path get the speedup. Gate in apply_speed_optims: CUDA target, consumer GPU (datacenter parts keep fp32 accumulation), torch exposes the flag, family not in _FP16_ACCUM_DENY, and the UNSLOTH_DISABLE_FP16_ACCUM kill switch is unset. The flag is captured in snapshot_backend_flags and restored on unload like the other process-wide knobs. _FP16_ACCUM_DENY starts empty: a same-seed A/B harness (off vs on per family at 512 and 1024 with long-prompt and high-guidance stress cases, non-finite, black frame and drift checks) backs the empty list and populates it if a family ever overflows. --- .../backend/core/inference/diffusion_speed.py | 56 +++++++++- studio/backend/tests/test_diffusion_speed.py | 102 ++++++++++++++++++ 2 files changed, 155 insertions(+), 3 deletions(-) diff --git a/studio/backend/core/inference/diffusion_speed.py b/studio/backend/core/inference/diffusion_speed.py index 9c99de9406..f937add782 100644 --- a/studio/backend/core/inference/diffusion_speed.py +++ b/studio/backend/core/inference/diffusion_speed.py @@ -69,6 +69,8 @@ def snapshot_backend_flags() -> Optional[dict]: matmul = getattr(getattr(torch.backends, "cuda", None), "matmul", None) if matmul is not None and hasattr(matmul, "allow_tf32"): state["matmul_tf32"] = bool(matmul.allow_tf32) + if matmul is not None and hasattr(matmul, "allow_fp16_accumulation"): + state["matmul_fp16_accum"] = bool(matmul.allow_fp16_accumulation) cudnn = getattr(torch.backends, "cudnn", None) if cudnn is not None: if hasattr(cudnn, "allow_tf32"): @@ -95,9 +97,9 @@ def restore_backend_flags(state: Optional[dict]) -> None: except Exception: # noqa: BLE001 — best-effort per-flag restore pass - _set( - getattr(getattr(torch.backends, "cuda", None), "matmul", None), "allow_tf32", "matmul_tf32" - ) + matmul = getattr(getattr(torch.backends, "cuda", None), "matmul", None) + _set(matmul, "allow_tf32", "matmul_tf32") + _set(matmul, "allow_fp16_accumulation", "matmul_fp16_accum") cudnn = getattr(torch.backends, "cudnn", None) _set(cudnn, "allow_tf32", "cudnn_tf32") _set(cudnn, "benchmark", "cudnn_benchmark") @@ -179,6 +181,7 @@ def apply_speed_optims( "channels_last": False, "cudnn_benchmark": False, "tf32": False, + "fp16_accum": False, "fused_qkv": False, "compiled": False, "compiled_dequant": False, @@ -203,6 +206,15 @@ def apply_speed_optims( if on_cuda: applied["cudnn_benchmark"] = _enable_cudnn_benchmark(logger) + # Consumer-only: fp16 GEMMs accumulate in fp16 (~2x on GeForce-class parts, whose + # fp32-accumulate rate is halved; datacenter HBM parts gain nothing and keep the + # safer fp32 accumulate). Only affects fp16 matmuls -- the bf16 DiT paths are + # untouched -- so it engages wherever fp16 compute appears (SDXL pipelines, fp16 + # LoRA adapters). Guarded by a per-family deny-list fed by the overflow validation + # harness and the UNSLOTH_DISABLE_FP16_ACCUM kill switch. + if on_cuda: + applied["fp16_accum"] = _enable_fp16_accumulation(family, logger) + # --- the compile lever, remapped per tier ---------------------------------------- # default = LIGHT compile: for a GGUF model, compile ONLY the dequant op chain # (~70-80% of eager GGUF time) -- cheap, VRAM-free, resolution-invariant; the @@ -330,6 +342,44 @@ def _enable_tf32(logger: Any) -> bool: return False +# Families the overflow validation harness (scripts/fp16_accum_validate.py) found to +# produce non-finite activations or visible drift under fp16 accumulation. Empty until +# a family actually fails: the gate below already restricts the flag to consumer GPUs +# and fp16 GEMMs, and the DiT families run bf16 compute (unaffected by this flag). +_FP16_ACCUM_DENY: frozenset[str] = frozenset() + + +def _enable_fp16_accumulation(family: Any, logger: Any) -> bool: + """Turn on fp16-accumulated fp16 GEMMs for consumer GPUs, where they run ~2x the + fp32-accumulate rate (datacenter HBM parts are not throughput-nerfed, so they keep + the safer default). Gated on: the torch build exposing the flag (2.10+), a + consumer-class device, the family not being deny-listed by the overflow harness, + and the UNSLOTH_DISABLE_FP16_ACCUM kill switch being unset. The caller's + snapshot/restore pair returns the process-wide flag to its prior value on unload.""" + import os + + if os.environ.get("UNSLOTH_DISABLE_FP16_ACCUM", "").strip() in ("1", "true", "yes"): + return False + name = str(getattr(family, "name", family or "")).lower() + if name in _FP16_ACCUM_DENY: + return False + try: + import torch + + matmul = torch.backends.cuda.matmul + if not hasattr(matmul, "allow_fp16_accumulation"): + return False + from .diffusion_transformer_quant import _is_consumer_gpu + + if not _is_consumer_gpu(): + return False + matmul.allow_fp16_accumulation = True + return True + except Exception as exc: # noqa: BLE001 — optimisation only + _warn(logger, "fp16_accum", exc) + return False + + def _fuse_qkv(pipe: Any, logger: Any) -> bool: for owner in (pipe, getattr(pipe, "transformer", None)): fn = getattr(owner, "fuse_qkv_projections", None) diff --git a/studio/backend/tests/test_diffusion_speed.py b/studio/backend/tests/test_diffusion_speed.py index de73121e00..dadaf73b93 100644 --- a/studio/backend/tests/test_diffusion_speed.py +++ b/studio/backend/tests/test_diffusion_speed.py @@ -211,6 +211,7 @@ def test_speed_off_applies_nothing(monkeypatch): "fused_qkv": False, "compiled": False, "compiled_dequant": False, + "fp16_accum": 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). @@ -351,3 +352,104 @@ def test_apply_tolerates_missing_optims(monkeypatch): bare, _target(), is_gguf = False, family = _family(), speed_mode = SPEED_MAX ) assert applied["channels_last"] is False and applied["fused_qkv"] is False + + +# ── fp16 accumulation (consumer fp16-GEMM fast path) ────────────────────────── + + +def _stub_torch_fp16_accum(monkeypatch, *, consumer = True, with_flag = True): + torch = types.ModuleType("torch") + torch.bfloat16 = "bfloat16" + torch.channels_last = "channels_last" + matmul_attrs = {"allow_tf32": False} + if with_flag: + matmul_attrs["allow_fp16_accumulation"] = False + torch.backends = types.SimpleNamespace( + cuda = types.SimpleNamespace(matmul = types.SimpleNamespace(**matmul_attrs)), + cudnn = types.SimpleNamespace(allow_tf32 = False, benchmark = False), + ) + monkeypatch.setitem(sys.modules, "torch", torch) + import core.inference.diffusion_transformer_quant as tq + + monkeypatch.setattr(tq, "_is_consumer_gpu", lambda device = None: consumer) + return torch + + +def test_snapshot_captures_fp16_accum_when_present(monkeypatch): + torch = _stub_torch_fp16_accum(monkeypatch) + torch.backends.cuda.matmul.allow_fp16_accumulation = True + snap = snapshot_backend_flags() + assert snap["matmul_fp16_accum"] is True + torch.backends.cuda.matmul.allow_fp16_accumulation = False + restore_backend_flags(snap) + assert torch.backends.cuda.matmul.allow_fp16_accumulation is True + + +def test_snapshot_skips_fp16_accum_on_older_torch(monkeypatch): + _stub_torch_fp16_accum(monkeypatch, with_flag = False) + snap = snapshot_backend_flags() + assert "matmul_fp16_accum" not in snap + restore_backend_flags(snap) # nothing to restore, no error + + +def test_fp16_accum_engages_on_consumer_cuda(monkeypatch): + torch = _stub_torch_fp16_accum(monkeypatch, consumer = True) + _stub_gguf_accel(monkeypatch) + applied = apply_speed_optims( + _Pipe(), _target(), is_gguf = True, family = _family(), speed_mode = "default" + ) + assert applied["fp16_accum"] is True + assert torch.backends.cuda.matmul.allow_fp16_accumulation is True + + +def test_fp16_accum_skipped_on_datacenter(monkeypatch): + torch = _stub_torch_fp16_accum(monkeypatch, consumer = False) + _stub_gguf_accel(monkeypatch) + applied = apply_speed_optims( + _Pipe(), _target(), is_gguf = True, family = _family(), speed_mode = "default" + ) + assert applied["fp16_accum"] is False + assert torch.backends.cuda.matmul.allow_fp16_accumulation is False + + +def test_fp16_accum_respects_kill_switch(monkeypatch): + _stub_torch_fp16_accum(monkeypatch, consumer = True) + _stub_gguf_accel(monkeypatch) + monkeypatch.setenv("UNSLOTH_DISABLE_FP16_ACCUM", "1") + applied = apply_speed_optims( + _Pipe(), _target(), is_gguf = True, family = _family(), speed_mode = "default" + ) + assert applied["fp16_accum"] is False + + +def test_fp16_accum_respects_family_deny_list(monkeypatch): + _stub_torch_fp16_accum(monkeypatch, consumer = True) + _stub_gguf_accel(monkeypatch) + monkeypatch.setattr(ds_mod, "_FP16_ACCUM_DENY", frozenset({"fragile-family"})) + fam = types.SimpleNamespace(supports_torch_compile = True, name = "fragile-family") + applied = apply_speed_optims( + _Pipe(), _target(), is_gguf = True, family = fam, speed_mode = "default" + ) + assert applied["fp16_accum"] is False + + +def test_fp16_accum_skipped_when_flag_missing(monkeypatch): + _stub_torch_fp16_accum(monkeypatch, consumer = True, with_flag = False) + _stub_gguf_accel(monkeypatch) + applied = apply_speed_optims( + _Pipe(), _target(), is_gguf = True, family = _family(), speed_mode = "default" + ) + assert applied["fp16_accum"] is False + + +def test_fp16_accum_not_touched_off_cuda(monkeypatch): + torch = _stub_torch_fp16_accum(monkeypatch, consumer = True) + applied = apply_speed_optims( + _Pipe(), + _target(device = "mps"), + is_gguf = False, + family = _family(), + speed_mode = "eager", + ) + assert applied["fp16_accum"] is False + assert torch.backends.cuda.matmul.allow_fp16_accumulation is False From cf2b2e593e0a092cf5670a4fb2219afdc5b3adf4 Mon Sep 17 00:00:00 2001 From: Daniel Han Date: Sat, 4 Jul 2026 07:30:37 +0000 Subject: [PATCH 02/13] Gate fp16 accumulation by compute dtype: fp16 pipelines only under max The A/B harness measured two regimes. bf16 loads (the Studio default on Ampere+) are bit-identical with the flag on across all six families, 36/36 same-seed cases, because the flag only changes fp16 GEMM accumulation. fp16 loads (the pre-Ampere fallback dtype) show real same-seed drift on the families that genuinely run fp16 GEMMs: SDXL up to 0.050 mean abs diff, FLUX.1 0.028, FLUX.2-klein 0.045, all finite, no new black frames. qwen-image renders black in fp16 with the flag off too and z-image fp16 fails in attention, so both are dtype limitations, not accumulation ones. So the gate now takes the compute dtype and the speed tier: bf16 engages on any active tier (provably output-neutral), fp16 engages only under max, the tier that already trades exactness for measured speed. The deny-list stays empty by measurement. --- .../backend/core/inference/diffusion_speed.py | 36 +++++++++++++------ studio/backend/tests/test_diffusion_speed.py | 33 +++++++++++++++++ 2 files changed, 58 insertions(+), 11 deletions(-) diff --git a/studio/backend/core/inference/diffusion_speed.py b/studio/backend/core/inference/diffusion_speed.py index f937add782..dd2f2e664d 100644 --- a/studio/backend/core/inference/diffusion_speed.py +++ b/studio/backend/core/inference/diffusion_speed.py @@ -208,12 +208,18 @@ def apply_speed_optims( # Consumer-only: fp16 GEMMs accumulate in fp16 (~2x on GeForce-class parts, whose # fp32-accumulate rate is halved; datacenter HBM parts gain nothing and keep the - # safer fp32 accumulate). Only affects fp16 matmuls -- the bf16 DiT paths are - # untouched -- so it engages wherever fp16 compute appears (SDXL pipelines, fp16 - # LoRA adapters). Guarded by a per-family deny-list fed by the overflow validation - # harness and the UNSLOTH_DISABLE_FP16_ACCUM kill switch. + # safer fp32 accumulate). Only affects fp16 matmuls -- bf16 loads were measured + # bit-identical with the flag on across every family (36/36 same-seed A/B cases), + # so on the quality-neutral tiers the flag engages only when the compute dtype is + # NOT fp16. On an fp16 pipeline (the pre-Ampere fallback dtype) the same harness + # measured real same-seed drift (mean 2-5% on SDXL / FLUX), so fp16 compute gets + # the 2x accumulate only under ``max``, the tier that already trades exactness for + # speed. Guarded by a per-family deny-list fed by the overflow validation harness + # and the UNSLOTH_DISABLE_FP16_ACCUM kill switch. if on_cuda: - applied["fp16_accum"] = _enable_fp16_accumulation(family, logger) + applied["fp16_accum"] = _enable_fp16_accumulation( + family, logger, dtype = getattr(target, "dtype", None), speed_mode = speed_mode + ) # --- the compile lever, remapped per tier ---------------------------------------- # default = LIGHT compile: for a GGUF model, compile ONLY the dequant op chain @@ -343,19 +349,25 @@ def _enable_tf32(logger: Any) -> bool: # Families the overflow validation harness (scripts/fp16_accum_validate.py) found to -# produce non-finite activations or visible drift under fp16 accumulation. Empty until -# a family actually fails: the gate below already restricts the flag to consumer GPUs -# and fp16 GEMMs, and the DiT families run bf16 compute (unaffected by this flag). +# produce non-finite activations or NEW black frames under fp16 accumulation. Empty by +# measurement: across all six families the harness found no overflow anywhere -- bf16 +# loads are bit-identical with the flag on, and fp16 loads stay finite (their same-seed +# drift is why fp16 compute is additionally gated to the ``max`` tier below). _FP16_ACCUM_DENY: frozenset[str] = frozenset() -def _enable_fp16_accumulation(family: Any, logger: Any) -> bool: +def _enable_fp16_accumulation( + family: Any, logger: Any, *, dtype: Any = None, speed_mode: Optional[str] = None +) -> bool: """Turn on fp16-accumulated fp16 GEMMs for consumer GPUs, where they run ~2x the fp32-accumulate rate (datacenter HBM parts are not throughput-nerfed, so they keep the safer default). Gated on: the torch build exposing the flag (2.10+), a consumer-class device, the family not being deny-listed by the overflow harness, - and the UNSLOTH_DISABLE_FP16_ACCUM kill switch being unset. The caller's - snapshot/restore pair returns the process-wide flag to its prior value on unload.""" + the UNSLOTH_DISABLE_FP16_ACCUM kill switch being unset, and -- when the pipeline + compute dtype IS fp16, the only case where the accumulator width changes results -- + the ``max`` tier (measured same-seed drift: mean 2-5%; bf16 loads are bit-identical + so they engage on any tier). The caller's snapshot/restore pair returns the + process-wide flag to its prior value on unload.""" import os if os.environ.get("UNSLOTH_DISABLE_FP16_ACCUM", "").strip() in ("1", "true", "yes"): @@ -363,6 +375,8 @@ def _enable_fp16_accumulation(family: Any, logger: Any) -> bool: name = str(getattr(family, "name", family or "")).lower() if name in _FP16_ACCUM_DENY: return False + if str(dtype).replace("torch.", "") == "float16" and speed_mode != SPEED_MAX: + return False try: import torch diff --git a/studio/backend/tests/test_diffusion_speed.py b/studio/backend/tests/test_diffusion_speed.py index dadaf73b93..9dc50f4e28 100644 --- a/studio/backend/tests/test_diffusion_speed.py +++ b/studio/backend/tests/test_diffusion_speed.py @@ -453,3 +453,36 @@ def test_fp16_accum_not_touched_off_cuda(monkeypatch): ) assert applied["fp16_accum"] is False assert torch.backends.cuda.matmul.allow_fp16_accumulation is False + + +def test_fp16_accum_denied_on_fp16_dtype_below_max(monkeypatch): + # fp16 compute is where the accumulator width actually changes results (measured + # same-seed drift, mean 2-5%): the quality-neutral tiers must refuse it. + torch = _stub_torch_fp16_accum(monkeypatch, consumer = True) + _stub_gguf_accel(monkeypatch) + for mode in ("eager", "default"): + applied = apply_speed_optims( + _Pipe(), + _target(dtype = "float16"), + is_gguf = True, + family = _family(), + speed_mode = mode, + ) + assert applied["fp16_accum"] is False + assert torch.backends.cuda.matmul.allow_fp16_accumulation is False + + +def test_fp16_accum_allowed_on_fp16_dtype_under_max(monkeypatch): + # max already trades exactness for speed (conv algos, max-autotune), so the 2x + # fp16 accumulate joins that tier for fp16 pipelines. + torch = _stub_torch_fp16_accum(monkeypatch, consumer = True) + _stub_gguf_accel(monkeypatch) + applied = apply_speed_optims( + _Pipe(with_compile = True, with_fuse = True), + _target(dtype = "float16"), + is_gguf = True, + family = _family(), + speed_mode = "max", + ) + assert applied["fp16_accum"] is True + assert torch.backends.cuda.matmul.allow_fp16_accumulation is True From 69b437fa21237bedb1393e8526c8ab31958c4965 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 4 Jul 2026 07:31:30 +0000 Subject: [PATCH 03/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- studio/backend/core/inference/diffusion_speed.py | 6 +++++- studio/backend/tests/test_diffusion_speed.py | 11 +++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/studio/backend/core/inference/diffusion_speed.py b/studio/backend/core/inference/diffusion_speed.py index dd2f2e664d..cb9101d03d 100644 --- a/studio/backend/core/inference/diffusion_speed.py +++ b/studio/backend/core/inference/diffusion_speed.py @@ -357,7 +357,11 @@ _FP16_ACCUM_DENY: frozenset[str] = frozenset() def _enable_fp16_accumulation( - family: Any, logger: Any, *, dtype: Any = None, speed_mode: Optional[str] = None + family: Any, + logger: Any, + *, + dtype: Any = None, + speed_mode: Optional[str] = None, ) -> bool: """Turn on fp16-accumulated fp16 GEMMs for consumer GPUs, where they run ~2x the fp32-accumulate rate (datacenter HBM parts are not throughput-nerfed, so they keep diff --git a/studio/backend/tests/test_diffusion_speed.py b/studio/backend/tests/test_diffusion_speed.py index 9dc50f4e28..5b645022e1 100644 --- a/studio/backend/tests/test_diffusion_speed.py +++ b/studio/backend/tests/test_diffusion_speed.py @@ -357,7 +357,12 @@ def test_apply_tolerates_missing_optims(monkeypatch): # ── fp16 accumulation (consumer fp16-GEMM fast path) ────────────────────────── -def _stub_torch_fp16_accum(monkeypatch, *, consumer = True, with_flag = True): +def _stub_torch_fp16_accum( + monkeypatch, + *, + consumer = True, + with_flag = True, +): torch = types.ModuleType("torch") torch.bfloat16 = "bfloat16" torch.channels_last = "channels_last" @@ -427,9 +432,7 @@ def test_fp16_accum_respects_family_deny_list(monkeypatch): _stub_gguf_accel(monkeypatch) monkeypatch.setattr(ds_mod, "_FP16_ACCUM_DENY", frozenset({"fragile-family"})) fam = types.SimpleNamespace(supports_torch_compile = True, name = "fragile-family") - applied = apply_speed_optims( - _Pipe(), _target(), is_gguf = True, family = fam, speed_mode = "default" - ) + applied = apply_speed_optims(_Pipe(), _target(), is_gguf = True, family = fam, speed_mode = "default") assert applied["fp16_accum"] is False From 84d9c62172bde92d3191a59366e069944c0f1d56 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 4 Jul 2026 08:22:44 +0000 Subject: [PATCH 04/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- studio/backend/tests/test_diffusion_dit_trainer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/studio/backend/tests/test_diffusion_dit_trainer.py b/studio/backend/tests/test_diffusion_dit_trainer.py index 6d7736a106..d406bf99eb 100644 --- a/studio/backend/tests/test_diffusion_dit_trainer.py +++ b/studio/backend/tests/test_diffusion_dit_trainer.py @@ -236,6 +236,7 @@ def _patch_capability(monkeypatch, capability): import torch import core.training.diffusion_train_common as dtc + monkeypatch.setattr(torch.cuda, "is_available", lambda: True) monkeypatch.setattr(torch.cuda, "get_device_capability", lambda *a, **k: capability) monkeypatch.setattr(dtc, "has_functional_torchao", lambda: True) From e715d57a75c92c89c73e6d64a7cce654a2a9aa4b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 4 Jul 2026 08:56:15 +0000 Subject: [PATCH 05/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- studio/backend/core/inference/diffusion_auto_policy.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/studio/backend/core/inference/diffusion_auto_policy.py b/studio/backend/core/inference/diffusion_auto_policy.py index e297fd16d0..de42e7c1d4 100644 --- a/studio/backend/core/inference/diffusion_auto_policy.py +++ b/studio/backend/core/inference/diffusion_auto_policy.py @@ -155,9 +155,7 @@ def resolve_dense_quant_candidate( return None if not dense_transformer_supported(target): return None - scheme = select_transformer_quant_scheme( - target, requested, family = getattr(fam, "name", None) - ) + scheme = select_transformer_quant_scheme(target, requested, family = getattr(fam, "name", None)) if scheme is None: return None prequant_available = False From e67342564846dfe320dfb7d25d2940578ae56a04 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 4 Jul 2026 09:45:04 +0000 Subject: [PATCH 06/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- studio/backend/core/inference/diffusion.py | 5 ++++- .../core/inference/diffusion_auto_policy.py | 1 + studio/backend/models/inference.py | 20 ++++++++++--------- .../tests/test_diffusion_auto_policy.py | 4 +--- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/studio/backend/core/inference/diffusion.py b/studio/backend/core/inference/diffusion.py index e5bbb24dcc..0d0c65ee7d 100644 --- a/studio/backend/core/inference/diffusion.py +++ b/studio/backend/core/inference/diffusion.py @@ -981,7 +981,10 @@ class DiffusionBackend: # as-is, so auto is the DEFAULT. An explicit "none"/"off" pins # GGUF-as-is and an explicit scheme pins that scheme. The overwritten # "auto" still records source=auto in the resolved provenance. - if transformer_quant is None or str(transformer_quant).strip().lower() in ("", "auto"): + if transformer_quant is None or str(transformer_quant).strip().lower() in ( + "", + "auto", + ): transformer_quant = TQ_AUTO # Default-on fast path: load the DENSE bf16 transformer and torchao-quantise it diff --git a/studio/backend/core/inference/diffusion_auto_policy.py b/studio/backend/core/inference/diffusion_auto_policy.py index 1bdd88e5f6..0aa0a51371 100644 --- a/studio/backend/core/inference/diffusion_auto_policy.py +++ b/studio/backend/core/inference/diffusion_auto_policy.py @@ -135,6 +135,7 @@ def _hf_cache_free_mib() -> Optional[int]: """Free MiB on the filesystem holding the HF model cache (None when unprobeable).""" try: import shutil + try: from huggingface_hub.constants import HF_HUB_CACHE as cache_dir except Exception: # noqa: BLE001 -- hub missing/old: probe the conventional path diff --git a/studio/backend/models/inference.py b/studio/backend/models/inference.py index 3b5e833959..8b5f3602d6 100644 --- a/studio/backend/models/inference.py +++ b/studio/backend/models/inference.py @@ -1750,15 +1750,17 @@ class DiffusionLoadRequest(BaseModel): "memory-vs-quality tradeoff (shifts fine detail), not free; " "pairs well with balanced mode.", ) - transformer_quant: Optional[Literal["auto", "none", "off", "int8", "fp8", "nvfp4", "mxfp8"]] = Field( - None, - description = "Transformer compute dtype. UNSET or auto (the default) picks the " - "fastest precision the hardware supports: the DENSE bf16 transformer " - "is loaded instead of the GGUF and torchao-quantised onto the " - "low-precision tensor cores (data-center fp8, consumer/Ampere int8), " - "falling back to the GGUF when the device, VRAM or disk cannot take " - "it. none/off pins running the GGUF as-is; an explicit scheme forces " - "that scheme. Dense path needs CUDA + bf16.", + transformer_quant: Optional[Literal["auto", "none", "off", "int8", "fp8", "nvfp4", "mxfp8"]] = ( + Field( + None, + description = "Transformer compute dtype. UNSET or auto (the default) picks the " + "fastest precision the hardware supports: the DENSE bf16 transformer " + "is loaded instead of the GGUF and torchao-quantised onto the " + "low-precision tensor cores (data-center fp8, consumer/Ampere int8), " + "falling back to the GGUF when the device, VRAM or disk cannot take " + "it. none/off pins running the GGUF as-is; an explicit scheme forces " + "that scheme. Dense path needs CUDA + bf16.", + ) ) transformer_quant_fast_accum: Optional[bool] = Field( None, diff --git a/studio/backend/tests/test_diffusion_auto_policy.py b/studio/backend/tests/test_diffusion_auto_policy.py index d9deadac0e..7190395f5f 100644 --- a/studio/backend/tests/test_diffusion_auto_policy.py +++ b/studio/backend/tests/test_diffusion_auto_policy.py @@ -151,9 +151,7 @@ def test_candidate_disk_gate_unprobeable_disk_passes(monkeypatch): _patch_selector(monkeypatch, scheme = "int8") monkeypatch.setattr(ap, "_hf_cache_free_mib", lambda: None) - est = resolve_dense_quant_candidate( - fam = _fam("z-image"), target = object(), requested = "auto" - ) + est = resolve_dense_quant_candidate(fam = _fam("z-image"), target = object(), requested = "auto") assert isinstance(est, DenseQuantEstimate) From fec66a53926e98d9639e2c6637cfe104f20294c7 Mon Sep 17 00:00:00 2001 From: Daniel Han Date: Sun, 5 Jul 2026 00:06:38 +0000 Subject: [PATCH 07/13] Pass normalized speed mode to fp16 accumulation gate The raw speed_mode string was forwarded to _enable_fp16_accumulation, so a case-variant like MAX failed the speed_mode != SPEED_MAX check and wrongly disabled fp16 accumulation on float16 pipelines. Forward the normalized mode and cover the case-insensitive path in the test. --- studio/backend/core/inference/diffusion_speed.py | 2 +- studio/backend/tests/test_diffusion_speed.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/studio/backend/core/inference/diffusion_speed.py b/studio/backend/core/inference/diffusion_speed.py index cb9101d03d..8c2524dfda 100644 --- a/studio/backend/core/inference/diffusion_speed.py +++ b/studio/backend/core/inference/diffusion_speed.py @@ -218,7 +218,7 @@ def apply_speed_optims( # and the UNSLOTH_DISABLE_FP16_ACCUM kill switch. if on_cuda: applied["fp16_accum"] = _enable_fp16_accumulation( - family, logger, dtype = getattr(target, "dtype", None), speed_mode = speed_mode + family, logger, dtype = getattr(target, "dtype", None), speed_mode = mode ) # --- the compile lever, remapped per tier ---------------------------------------- diff --git a/studio/backend/tests/test_diffusion_speed.py b/studio/backend/tests/test_diffusion_speed.py index 5b645022e1..9a18090e36 100644 --- a/studio/backend/tests/test_diffusion_speed.py +++ b/studio/backend/tests/test_diffusion_speed.py @@ -485,7 +485,7 @@ def test_fp16_accum_allowed_on_fp16_dtype_under_max(monkeypatch): _target(dtype = "float16"), is_gguf = True, family = _family(), - speed_mode = "max", + speed_mode = "MAX", ) assert applied["fp16_accum"] is True assert torch.backends.cuda.matmul.allow_fp16_accumulation is True From e75a033a225ec0da7cb0ab2e90ca34c1cfd5c7bc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 5 Jul 2026 04:46:57 +0000 Subject: [PATCH 08/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- studio/backend/tests/test_diffusion_backend.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/studio/backend/tests/test_diffusion_backend.py b/studio/backend/tests/test_diffusion_backend.py index b484bba39a..1d18745d2a 100644 --- a/studio/backend/tests/test_diffusion_backend.py +++ b/studio/backend/tests/test_diffusion_backend.py @@ -2171,8 +2171,7 @@ def test_prefetch_returns_snapshot_dir_for_manifest(monkeypatch): ) assert root == "/cache/snap" assert ( - backend._prefetch_files("base/repo", None, "base/repo", ["vae/x.safetensors"], None) - is None + backend._prefetch_files("base/repo", None, "base/repo", ["vae/x.safetensors"], None) is None ) From 848b8da9f2e4bb2fe7c15bc5c66d261655f564a8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 5 Jul 2026 05:00:14 +0000 Subject: [PATCH 09/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- studio/backend/tests/test_diffusion_controlnet.py | 1 + 1 file changed, 1 insertion(+) diff --git a/studio/backend/tests/test_diffusion_controlnet.py b/studio/backend/tests/test_diffusion_controlnet.py index 3d4f7c98cf..50bf7159b0 100644 --- a/studio/backend/tests/test_diffusion_controlnet.py +++ b/studio/backend/tests/test_diffusion_controlnet.py @@ -263,6 +263,7 @@ def test_controlnet_pipe_rejects_family_without_classes(): with pytest.raises(ValueError, match = "not supported"): b._controlnet_pipe(st, dc.ResolvedControlNet("x", "y", False), threading.Event()) + def test_controlnet_pipe_not_cached_after_unload_race(monkeypatch): # An unload that lands while from_pipe is assembling must not let the wrapper # repopulate the cache around the torn-down base pipe. From 5ff6a87336385010bf1127649890222f72b3ba47 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 5 Jul 2026 08:00:27 +0000 Subject: [PATCH 10/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- studio/backend/core/training/diffusion_train_common.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/studio/backend/core/training/diffusion_train_common.py b/studio/backend/core/training/diffusion_train_common.py index 30b6ec727a..bdf53d5b6b 100644 --- a/studio/backend/core/training/diffusion_train_common.py +++ b/studio/backend/core/training/diffusion_train_common.py @@ -597,7 +597,7 @@ def _plan_cache_variants( # fallback. Over this budget the default falls back to per-step VAE encoding. A fixed # constant (rather than a psutil RAM fraction) keeps the gate dependency-free and identical # across hosts; it is deliberately conservative, well under a typical training host's RAM. -_LATENT_CACHE_BUDGET_BYTES = 4 * 1024 ** 3 # 4 GiB +_LATENT_CACHE_BUDGET_BYTES = 4 * 1024**3 # 4 GiB # Returned by the cache builders when the estimated cache exceeds the budget: the caller # keeps the VAE resident and encodes each step's latents in-loop. A distinct sentinel from @@ -614,7 +614,9 @@ def _latent_cache_forced() -> bool: def _latent_cache_over_budget( - per_variant_bytes: int, total_variants: int, budget_bytes: Optional[int] = None + per_variant_bytes: int, + total_variants: int, + budget_bytes: Optional[int] = None, ) -> bool: """True when a cache of ``total_variants`` entries, each two fp32 tensors totalling ``per_variant_bytes``, is estimated to exceed ``budget_bytes``. ``per_variant_bytes`` is From 1d4cf1d6aabb8e2c68b5708ee1cff1f9b72f688b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 5 Jul 2026 11:45:01 +0000 Subject: [PATCH 11/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- studio/backend/core/inference/diffusion.py | 1 - studio/backend/tests/test_diffusion_controlnet.py | 8 ++++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/studio/backend/core/inference/diffusion.py b/studio/backend/core/inference/diffusion.py index a469d1385d..9f71789265 100644 --- a/studio/backend/core/inference/diffusion.py +++ b/studio/backend/core/inference/diffusion.py @@ -1688,7 +1688,6 @@ class DiffusionBackend: # local dir the user picked has no Hub scan and is exempt (fail-open there). if not getattr(resolved_cn, "is_local", False): from utils.security import evaluate_file_security - _cn_fs = evaluate_file_security(resolved_cn.path, hf_token = state.hf_token or None) if _cn_fs.blocked: raise ValueError(_cn_fs.reason) diff --git a/studio/backend/tests/test_diffusion_controlnet.py b/studio/backend/tests/test_diffusion_controlnet.py index b7ae037197..bcb7393457 100644 --- a/studio/backend/tests/test_diffusion_controlnet.py +++ b/studio/backend/tests/test_diffusion_controlnet.py @@ -234,7 +234,6 @@ def _state(): def _allow_cn_security(monkeypatch): """Stub the Hub malware preflight to allow the load (hermetic, no network).""" import utils.security - monkeypatch.setattr( utils.security, "evaluate_file_security", @@ -277,7 +276,12 @@ def test_controlnet_pipe_blocks_flagged_remote_repo(monkeypatch): class _TrapModel(_FakeCNModel): @classmethod - def from_pretrained(cls, path, torch_dtype = None, token = None): + def from_pretrained( + cls, + path, + torch_dtype = None, + token = None, + ): loaded["called"] = True return super().from_pretrained(path, torch_dtype = torch_dtype, token = token) From 06ec3b34dca6d74c1658c7586b57028c723b245c Mon Sep 17 00:00:00 2001 From: Daniel Han Date: Mon, 6 Jul 2026 10:31:01 +0000 Subject: [PATCH 12/13] diffusion speed: make the fp16-accum kill switch case-insensitive UNSLOTH_DISABLE_FP16_ACCUM is the documented safety escape hatch for fp16-accumulation numerical drift, but it was matched as .strip() in (1, true, yes) with no lowercasing, so UNSLOTH_DISABLE_FP16_ACCUM=TRUE (or YES / On) was silently ignored and fp16 accumulation stayed on. Lowercase before matching (the family-name check on the next line already does) and accept on. Existing 1/true/yes still match. --- studio/backend/core/inference/diffusion_speed.py | 2 +- studio/backend/tests/test_diffusion_speed.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/studio/backend/core/inference/diffusion_speed.py b/studio/backend/core/inference/diffusion_speed.py index 8c2524dfda..52ace4d30a 100644 --- a/studio/backend/core/inference/diffusion_speed.py +++ b/studio/backend/core/inference/diffusion_speed.py @@ -374,7 +374,7 @@ def _enable_fp16_accumulation( process-wide flag to its prior value on unload.""" import os - if os.environ.get("UNSLOTH_DISABLE_FP16_ACCUM", "").strip() in ("1", "true", "yes"): + if os.environ.get("UNSLOTH_DISABLE_FP16_ACCUM", "").strip().lower() in ("1", "true", "yes", "on"): return False name = str(getattr(family, "name", family or "")).lower() if name in _FP16_ACCUM_DENY: diff --git a/studio/backend/tests/test_diffusion_speed.py b/studio/backend/tests/test_diffusion_speed.py index 9a18090e36..e6ce77116e 100644 --- a/studio/backend/tests/test_diffusion_speed.py +++ b/studio/backend/tests/test_diffusion_speed.py @@ -427,6 +427,20 @@ def test_fp16_accum_respects_kill_switch(monkeypatch): assert applied["fp16_accum"] is False +@pytest.mark.parametrize("value", ["TRUE", "Yes", "On", " true "]) +def test_fp16_accum_kill_switch_is_case_insensitive(monkeypatch, value): + # The documented safety escape hatch must honor the common boolean spellings, not only + # lowercase "1"/"true"/"yes": an operator setting UNSLOTH_DISABLE_FP16_ACCUM=TRUE to stop + # fp16-accumulation drift would otherwise be silently ignored. + _stub_torch_fp16_accum(monkeypatch, consumer = True) + _stub_gguf_accel(monkeypatch) + monkeypatch.setenv("UNSLOTH_DISABLE_FP16_ACCUM", value) + applied = apply_speed_optims( + _Pipe(), _target(), is_gguf = True, family = _family(), speed_mode = "default" + ) + assert applied["fp16_accum"] is False + + def test_fp16_accum_respects_family_deny_list(monkeypatch): _stub_torch_fp16_accum(monkeypatch, consumer = True) _stub_gguf_accel(monkeypatch) From 4f6aa1a3d485d41e05778580706257ed8a079d97 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 10:31:34 +0000 Subject: [PATCH 13/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- studio/backend/core/inference/diffusion_speed.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/studio/backend/core/inference/diffusion_speed.py b/studio/backend/core/inference/diffusion_speed.py index 52ace4d30a..344a5e66fa 100644 --- a/studio/backend/core/inference/diffusion_speed.py +++ b/studio/backend/core/inference/diffusion_speed.py @@ -374,7 +374,12 @@ def _enable_fp16_accumulation( process-wide flag to its prior value on unload.""" import os - if os.environ.get("UNSLOTH_DISABLE_FP16_ACCUM", "").strip().lower() in ("1", "true", "yes", "on"): + if os.environ.get("UNSLOTH_DISABLE_FP16_ACCUM", "").strip().lower() in ( + "1", + "true", + "yes", + "on", + ): return False name = str(getattr(family, "name", family or "")).lower() if name in _FP16_ACCUM_DENY: