unsloth/studio/backend/tests/test_diffusion_memory.py
Daniel Han a905895297
Studio: refuse a diffusion or video load that cannot fit unified memory (#8213)
* Studio: refuse a diffusion or video load that cannot fit unified memory

On a unified-memory host the memory planner short-circuits before it ever
compares the resident requirement against the safe budget. plan_diffusion_memory
sees `not can_offload or device_memory.is_unified`, records "unified/system
memory: CPU offload frees no device memory", and returns OFFLOAD_NONE. That is
the correct placement (the CPU and GPU share one pool, so offload moves bytes
within that pool and frees nothing) but it is a placement, not a fit, and
nothing downstream refuses.

There is also no torch OOM to catch on that path: _mps_or_cpu_target sets
PYTORCH_MPS_HIGH_WATERMARK_RATIO=0.0, which disables the MPS allocator's hard
limit outright, so an oversized load ends as an OS kill with no Python
exception. The user sees Studio disappear.

Driving the shipped planner against synthetic unified-memory targets (free
modelled at 80 percent of RAM, the share left once the OS, a browser and Studio
are up), every supported video family is affected below 64 GB of RAM:

  family                  weights   16G   24G   32G   64G   96G   128G
  ltx-2                       65G    NO    NO    NO    NO    NO    fits
  wan2.2-ti2v-5b              25G    NO    NO    NO  fits  fits    fits
  wan2.2-t2v-a14b             66G    NO    NO    NO    NO    NO    fits
  hunyuanvideo-1.5            33G    NO    NO    NO  fits  fits    fits
  hunyuanvideo-1.5-720p       33G    NO    NO    NO  fits  fits    fits

Add a load-time refusal on that path, in both loaders, naming the family, the
memory required, the memory available and the most useful thing to change.

Design notes:

- Weights only. The refusal budgets model_dense_mib plus the flat base
  overhead, not the per-call runtime headroom. The headroom is a coarse
  activation and VAE-decode estimate, and the mps path already enables VAE
  tiling and slicing, which cuts the decode peak that estimate does not model,
  so counting it would refuse marginal loads that would in fact complete. The
  weights are unavoidable resident bytes sized from measured per-family
  component tables or on-disk checkpoint size. This is the same rule as the
  llama.cpp unified-memory APU guard, which budgets weights and lets KV and
  context auto-reduce, and it is what keeps the refusal off the merely tight
  cases: on a 64 GB Mac hunyuanvideo-1.5 needs 39 GB with headroom against a
  38 GB budget and is still allowed through.

- Unified device memory only. memory_kind == "unified_memory" (Apple Silicon,
  integrated CUDA). Plain CPU reports "system_memory", has swap, and is an
  opt-in fringe path, so it keeps today's behaviour exactly. Discrete VRAM is
  untouched by construction: it still has a real fallback ladder, where an
  oversized model streams from host RAM under group or whole-module offload,
  and refusing there would break loads that work today.

- Not in the planner. plan_diffusion_memory stays a pure sizing function, and
  is unchanged. Both loaders call it speculatively (the image loader re-plans
  candidate quantisations, and both re-plan against a settled snapshot), so a
  planner that raised would turn those probes into load failures instead of
  letting a smaller candidate win. The check is a separate helper called once,
  on the plan the loader has committed to, after the previous pipeline has been
  evicted so the free reading is the memory this load actually gets, and before
  any weight is materialised.

- RuntimeError, matching the llama.cpp APU refusal. Both call sites run inside
  load_pipeline on the loader thread, where _run_load stringifies the exception
  onto load_progress and the UI toasts it verbatim, so the 409 mapping of the
  synchronous route never applies and no traceback reaches a 500.

- Escape hatch. UNSLOTH_DIFFUSION_ALLOW_OVERSIZED_LOAD=1 attempts the load
  anyway, since the estimate could be conservative and a Mac user otherwise has
  no override. The refusal also fails open on any unknown input (no free
  reading, no model size), matching the planner's own "budget or model size
  unknown; staying resident".

The discrete CUDA path has no equivalent refusal and does not gain one here.

Tests cover the message contents, the weights-only rule at the boundary, the
fail-open cases, the override, the RuntimeError type, the full decision matrix
against the real video family tables at 16, 24, 32, 64, 96 and 128 GB, and both
loader call sites for the refused, allowed, overridden and discrete cases.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Judge the unified-memory refusal on what actually ends up resident

Four gaps in the refusal this PR adds, all of which either refuse a load that fits or
miss one that does not:

- The dense transformer-quant fast path was never checked. On unified memory the planner
  returns 'none' for any size, so the dense re-plan's offload-policy test could not fire
  and an oversized dense build proceeded to be OS-killed. Size it explicitly and decline
  to the packed GGUF instead.
- A full pipeline's weight term is cached shard bytes, a download size. Z-Image and Lumina
  ship fp32 shards that halve on the bf16 cast, so the refusal rejected loads that fit.
  Judge it on the size table's resident totals, which only ever lower the estimate.
- A hosted pre-cast fp8 text encoder replaces the dense one during assembly, so budgeting
  the table's dense encoder refused video loads that fit. This overlaps only on integrated
  CUDA, where the pre-cast gate's cuda requirement and unified memory both hold.
- Tearing down a pipeline on Apple Silicon left its buffers reserved in torch's MPS
  caching allocator, which reads as used system memory, so a swap that fits was refused.
  Empty that cache on teardown and before the reading the committed plan is judged on.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Drop the duplicate pre-cast encoder sizing now that main scales it

main's video plan now scales the text-encoder term by te_prequant_budget_scale, which is
the same correction measured off the real artifacts rather than the generic fp8 factor.
Subtracting it again before the refusal would have counted the saving twice, so the
refusal reads the plan directly.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Judge the unified-memory refusal on the plan the load will take

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Size the pre-quantised transformer against unified memory before taking it

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Refuse the video load on the dense build peak, and price a pre-cast image encoder

* Substitute the size table only for a base it recognises, and drop double-counted companions

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Add MiniMax-H3 to the unified-memory refusal matrix

7989 added minimax-h3 to the video family table, and the decision-matrix test
is a deliberate tripwire for exactly that: it asserts the shipped families and
the expected refusals stay in step, so a new family fails the suite until its
row is written.

The row is derived from the shipped tables rather than chosen: at 144.2 GB of
bf16 weights H3 is the largest video family by a wide margin and is refused at
every RAM size in the matrix, 128 GiB included. That is the right answer, not a
gap -- the dense pipeline fits no Mac modelled here, and a user on one reaches
H3 through the GGUF or prequantized artifacts.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Run the unified-memory refusal on the modular video load, and price the pre-cast encoder in the prequant fit check

MiniMax-H3 returns into _load_h3_modular_pipeline above load_pipeline's refusal, so the one
family whose dense component set is 144.2 GB -- the one the refusal matrix declines at every
Mac RAM size it models -- was the only one that never reached the check. load_components
builds every component dense and the ComponentsManager's CPU offload frees nothing on unified
memory, so that set is an OS kill with no torch OOM to catch. Size it before any weight is
opened, with the denoiser priced at its steady quantised size when a hosted pre-quantized
checkpoint is about to be seeded in its place.

DenseQuantEstimate.companions_mib is always the dense encoder plus the VAE, but the assembly
the image prequant fit check sizes is handed text_encoder_quant and injects the pre-cast
encoder, so a footprint that fits was declined on bytes never materialised. Apply the same
te_prequant_budget_scale the load-level resident plan already applies.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Size the H3 modular refusal from the hosted checkpoint's measured footprint

The generic _QUANT_STEADY_FACTOR describes a torchao quantisation of the dense denoiser. H3's
hosted checkpoints are quantized AND structurally pruned (the curve-form adaLN, ~40% of the
released parameters), so 0.55 x 66.3 GB budgets 36.5 GB against a measured ~20.3 GB
(MiniMax-H3-FP8.pt 20,260,192,855 bytes, MiniMax-H3-INT8.pt 20,253,894,865, Hub metadata
2026-08-09), and the 16 GB gap refuses a supported prequant load that fits.

Record the measured resident size on the family and use it when a scheme is requested; the
generic factor stays the fallback for a family that publishes none.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Re-check the modular H3 fit when the hosted denoiser does not land

load_prequantized_transformer is best-effort by contract: a missing, corrupt, stale or
base-mismatched checkpoint drops to the released bfloat16 components. The refusal had already
sized the load at the ~20.3 GB checkpoint, so load_components then built the 66.3 GB dense
denoiser with nothing left to stop it -- on a unified-memory host whose budget fits one and not
the other, that is the OS kill the guard exists to prevent. Re-run the check on the dense set
when a scheme was requested and none was seeded, still before any weight is opened.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-08-09 19:32:29 -07:00

1814 lines
76 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 diffusion memory planner (``diffusion_memory.py``).
Hermetic and CPU-only: no torch, diffusers, GPU, or network. The device target
and the device-memory snapshot are constructed directly, so the planner's policy
matrix and the applier's pipeline calls are exercised in isolation.
"""
from __future__ import annotations
import types
import pytest
from core.inference.diffusion_memory import (
DEFAULT_GROUP_BLOCKS,
DEFAULT_IMAGE_HEIGHT,
DEFAULT_IMAGE_WIDTH,
MEMORY_MODE_BALANCED,
MEMORY_MODE_FAST,
MEMORY_MODE_LOW_VRAM,
OFFLOAD_GROUP,
OFFLOAD_MODEL,
OFFLOAD_NONE,
OFFLOAD_SEQUENTIAL,
OFFLOAD_STREAMING,
DeviceMemory,
MemoryPlan,
apply_memory_plan,
estimate_gguf_resident_mib,
estimate_image_runtime_mib,
normalize_memory_mode,
plan_diffusion_memory,
refine_memory_plan_for_components,
snapshot_device_memory,
)
def _target(
*,
device = "cuda",
backend = "cuda",
supports_offload = True,
):
"""A duck-typed stand-in for DiffusionDeviceTarget (only the fields the
planner / snapshot read)."""
return types.SimpleNamespace(
device = device,
backend = backend,
supports_model_cpu_offload = supports_offload,
)
def _discrete(free_mib, total_mib = None):
return DeviceMemory("cuda", "cuda", "discrete_vram", free_mib, total_mib or free_mib)
# ── mode normalisation ────────────────────────────────────────────────────────
def test_normalize_memory_mode_accepts_and_rejects():
assert normalize_memory_mode(None) is None
assert normalize_memory_mode(" ") is None
assert normalize_memory_mode("LOW-VRAM") == "low_vram"
assert normalize_memory_mode("Balanced") == "balanced"
with pytest.raises(ValueError):
normalize_memory_mode("ultra")
# ── filename / size estimates ─────────────────────────────────────────────────
def test_estimate_gguf_resident_mib_matches_packed_size():
# GGUF weights stay packed (uint8) on device and diffusers dequantises per-matmul, so the resident footprint is about the
# on-disk size at any quant level (measured on Z-Image-Turbo); a small margin covers allocator overhead.
assert estimate_gguf_resident_mib(1000) == 1050
assert estimate_gguf_resident_mib(7220) == 7581
assert estimate_gguf_resident_mib(None) is None
def test_estimate_image_runtime_scales_with_pixels_and_family():
base = estimate_image_runtime_mib(width = DEFAULT_IMAGE_WIDTH, height = DEFAULT_IMAGE_HEIGHT)
bigger = estimate_image_runtime_mib(width = 2048, height = 2048)
assert bigger > base
# Distilled / turbo families get a discount.
turbo = estimate_image_runtime_mib(
width = DEFAULT_IMAGE_WIDTH, height = DEFAULT_IMAGE_HEIGHT, family = "z-image-turbo"
)
assert turbo < base
# ── planner: device classes ───────────────────────────────────────────────────
def test_cpu_target_never_offloads_but_tiles():
plan = plan_diffusion_memory(
target = _target(device = "cpu", backend = "cpu", supports_offload = False),
device_memory = DeviceMemory("cpu", "cpu", "system_memory", 8000, 16000),
model_dense_mib = 4000,
runtime_headroom_mib = 2000,
)
assert plan.offload_policy == OFFLOAD_NONE
# CPU/MPS have no separate device pool, so VAE tiling is on to cap the spike.
assert plan.vae_tiling and plan.vae_slicing
def test_mps_unified_never_auto_offloads():
plan = plan_diffusion_memory(
target = _target(device = "mps", backend = "mps", supports_offload = False),
device_memory = DeviceMemory("mps", "mps", "unified_memory", 4000, 32000),
model_dense_mib = 20000,
runtime_headroom_mib = 4000,
)
assert plan.offload_policy == OFFLOAD_NONE
assert any("unified" in r for r in plan.reasons)
def test_unified_cuda_skips_offload_even_if_offload_capable():
# An integrated CUDA SoC reports unified memory; CPU offload would free nothing.
plan = plan_diffusion_memory(
target = _target(device = "cuda", backend = "cuda", supports_offload = True),
device_memory = DeviceMemory("cuda", "cuda", "unified_memory", 2000, 16000),
model_dense_mib = 12000,
runtime_headroom_mib = 4000,
)
assert plan.offload_policy == OFFLOAD_NONE
# ── planner: auto budget tiers on a discrete GPU ──────────────────────────────
def test_auto_resident_when_roomy():
# 80 GB card, ~16 GB model: fits with headroom, so stay resident (bit-identical).
plan = plan_diffusion_memory(
target = _target(),
device_memory = _discrete(80000),
model_dense_mib = 12000,
runtime_headroom_mib = 4000,
)
assert plan.offload_policy == OFFLOAD_NONE
assert plan.vae_tiling is False and plan.vae_slicing is False # roomy -> no tiling
def test_auto_model_offload_on_tight_fit():
# 24 GB free -> reserve 2400 -> budget 21600, 0.85*budget = 18360. required 21000 is over that but under budget, so whole-module offload.
plan = plan_diffusion_memory(
target = _target(),
device_memory = _discrete(24000, 24000),
model_dense_mib = 16000,
runtime_headroom_mib = 4000,
base_overhead_mib = 1000,
)
assert plan.offload_policy == OFFLOAD_MODEL
assert plan.vae_tiling is True # offloading -> device is tight -> tile
def test_auto_group_offload_when_transformer_overflows_but_companions_fit():
# A big transformer pushes the resident total over budget while the companions still fit, so stream the transformer.
plan = plan_diffusion_memory(
target = _target(),
device_memory = _discrete(8000, 8000),
model_dense_mib = 40000,
companion_dense_mib = 1500,
runtime_headroom_mib = 1000,
base_overhead_mib = 1000,
)
assert plan.offload_policy == OFFLOAD_GROUP
# Group keeps the VAE resident, so balanced uses exact slicing but NOT lossy tiling and stays bit-identical.
assert plan.vae_slicing is True and plan.vae_tiling is False
def test_auto_model_offload_when_companions_exceed_budget():
# The text encoder itself is too big to stay resident -> offload everything.
plan = plan_diffusion_memory(
target = _target(),
device_memory = _discrete(8000, 8000),
model_dense_mib = 40000,
companion_dense_mib = 30000,
runtime_headroom_mib = 4000,
)
assert plan.offload_policy == OFFLOAD_MODEL
def test_auto_model_offload_when_companion_size_unknown():
# Without a companion estimate the planner can't prove group fits, so it takes the safest cut.
plan = plan_diffusion_memory(
target = _target(),
device_memory = _discrete(8000, 8000),
model_dense_mib = 40000,
runtime_headroom_mib = 4000,
)
assert plan.offload_policy == OFFLOAD_MODEL
def test_auto_stays_resident_when_budget_unknown():
plan = plan_diffusion_memory(
target = _target(),
device_memory = _discrete(None, None),
model_dense_mib = 40000,
runtime_headroom_mib = 4000,
)
assert plan.offload_policy == OFFLOAD_NONE
assert any("unknown" in r for r in plan.reasons)
# ── planner: explicit modes + cpu_offload override ────────────────────────────
def test_explicit_modes_force_policy_regardless_of_budget():
roomy = _discrete(80000)
assert (
plan_diffusion_memory(
target = _target(),
device_memory = roomy,
model_dense_mib = 1000,
runtime_headroom_mib = 1000,
requested_mode = MEMORY_MODE_FAST,
).offload_policy
== OFFLOAD_NONE
)
assert (
plan_diffusion_memory(
target = _target(),
device_memory = roomy,
model_dense_mib = 1000,
runtime_headroom_mib = 1000,
requested_mode = MEMORY_MODE_BALANCED,
).offload_policy
== OFFLOAD_GROUP
)
assert (
plan_diffusion_memory(
target = _target(),
device_memory = roomy,
model_dense_mib = 1000,
runtime_headroom_mib = 1000,
requested_mode = MEMORY_MODE_LOW_VRAM,
).offload_policy
== OFFLOAD_MODEL
)
def test_fast_falls_back_to_model_offload_when_it_does_not_fit():
plan = plan_diffusion_memory(
target = _target(),
device_memory = _discrete(8000, 8000),
model_dense_mib = 40000,
runtime_headroom_mib = 4000,
requested_mode = MEMORY_MODE_FAST,
)
assert plan.offload_policy == OFFLOAD_MODEL
def test_explicit_cpu_offload_overrides_resident_auto_choice():
# A roomy GPU would stay resident under auto, but cpu_offload=True forces offload.
plan = plan_diffusion_memory(
target = _target(),
device_memory = _discrete(80000),
model_dense_mib = 4000,
runtime_headroom_mib = 2000,
explicit_offload = True,
)
assert plan.offload_policy == OFFLOAD_MODEL
assert any("explicit cpu_offload" in r for r in plan.reasons)
def test_explicit_memory_mode_wins_over_legacy_cpu_offload():
# memory_mode is documented to override cpu_offload, so fast + the legacy flag stays resident instead of downgrading to offload.
plan = plan_diffusion_memory(
target = _target(),
device_memory = _discrete(80000),
model_dense_mib = 4000,
runtime_headroom_mib = 2000,
requested_mode = MEMORY_MODE_FAST,
explicit_offload = True,
)
assert plan.offload_policy == OFFLOAD_NONE
assert not any("explicit cpu_offload" in r for r in plan.reasons)
def test_explicit_cpu_offload_ignored_on_cpu_target():
plan = plan_diffusion_memory(
target = _target(device = "cpu", backend = "cpu", supports_offload = False),
device_memory = DeviceMemory("cpu", "cpu", "system_memory", 8000, 16000),
model_dense_mib = 4000,
runtime_headroom_mib = 2000,
explicit_offload = True,
)
assert plan.offload_policy == OFFLOAD_NONE
# ── snapshot ──────────────────────────────────────────────────────────────────
def test_snapshot_cpu_target_uses_system_memory(monkeypatch):
import core.inference.diffusion_memory as mem
monkeypatch.setattr(mem, "_system_memory_mib", lambda: (16000, 9000))
snap = snapshot_device_memory(_target(device = "cpu", backend = "cpu"))
assert snap.memory_kind == "system_memory"
assert snap.free_mib == 9000 and snap.total_mib == 16000
def test_snapshot_cuda_reads_mem_get_info(monkeypatch):
import sys
fake_torch = types.ModuleType("torch")
fake_torch.cuda = types.SimpleNamespace(
mem_get_info = lambda: (10 * 1024 * 1024 * 1024, 24 * 1024 * 1024 * 1024),
get_device_properties = lambda i: types.SimpleNamespace(integrated = False),
)
monkeypatch.setitem(sys.modules, "torch", fake_torch)
snap = snapshot_device_memory(_target())
assert snap.memory_kind == "discrete_vram"
assert snap.free_mib == 10 * 1024 and snap.total_mib == 24 * 1024
def test_snapshot_never_raises_on_probe_failure(monkeypatch):
import sys
fake_torch = types.ModuleType("torch")
def _boom():
raise RuntimeError("no cuda")
fake_torch.cuda = types.SimpleNamespace(mem_get_info = _boom)
monkeypatch.setitem(sys.modules, "torch", fake_torch)
snap = snapshot_device_memory(_target())
assert snap.free_mib is None and snap.total_mib is None
# ── applier ───────────────────────────────────────────────────────────────────
class _RecordingPipe:
def __init__(self) -> None:
self.calls: list[str] = []
self.offload_device = None
def to(self, device):
self.calls.append(f"to:{device}")
return self
def enable_model_cpu_offload(self, device = None):
self.calls.append("model_offload")
self.offload_device = device
def enable_sequential_cpu_offload(self, device = None):
self.calls.append("sequential_offload")
self.offload_device = device
def enable_vae_tiling(self):
self.calls.append("vae_tiling")
def enable_vae_slicing(self):
self.calls.append("vae_slicing")
def _plan(policy, *, tiling):
return plan_diffusion_memory(
target = _target(),
device_memory = _discrete(80000) if policy == OFFLOAD_NONE else _discrete(4000, 8000),
model_dense_mib = 1000 if policy == OFFLOAD_NONE else 40000,
runtime_headroom_mib = 1000,
requested_mode = {
OFFLOAD_NONE: MEMORY_MODE_FAST,
OFFLOAD_GROUP: MEMORY_MODE_BALANCED,
OFFLOAD_MODEL: MEMORY_MODE_LOW_VRAM,
}[policy],
)
def _manual_plan(policy, *, tiling):
"""Build a plan for a policy the auto/explicit modes no longer emit (sequential)."""
return MemoryPlan(
requested_mode = "manual",
offload_policy = policy,
vae_tiling = tiling,
vae_slicing = tiling,
device_memory = _discrete(4000, 8000),
estimates = {},
)
def test_apply_none_places_resident():
pipe = _RecordingPipe()
effective, tiled = apply_memory_plan(pipe, _plan(OFFLOAD_NONE, tiling = False), device = "cuda")
assert pipe.calls == ["to:cuda"] # no tiling on a roomy resident run
assert effective == OFFLOAD_NONE and tiled is False
def test_apply_model_offload_engages_offload_and_tiling():
pipe = _RecordingPipe()
effective, tiled = apply_memory_plan(pipe, _plan(OFFLOAD_MODEL, tiling = True), device = "cuda")
assert "model_offload" in pipe.calls
assert "to:cuda" not in pipe.calls # offload owns placement; never both
assert "vae_tiling" in pipe.calls and "vae_slicing" in pipe.calls
assert effective == OFFLOAD_MODEL and tiled is True
assert pipe.offload_device == "cuda" # device threaded to enable_model_cpu_offload
def test_apply_model_offload_passes_target_device():
# enable_model_cpu_offload defaults to CUDA, so a non-CUDA accelerator (e.g. Intel XPU) must have its device forwarded.
pipe = _RecordingPipe()
apply_memory_plan(pipe, _plan(OFFLOAD_MODEL, tiling = False), device = "xpu")
assert pipe.offload_device == "xpu"
def test_apply_vae_tiling_falls_back_to_vae_submodule():
# Z-Image-style pipeline: no pipeline-level enable_vae_tiling, only pipe.vae.
class _VaeOnly:
def __init__(self):
self.vae = types.SimpleNamespace(
tiled = False,
sliced = False,
enable_tiling = self._tile,
enable_slicing = self._slice,
)
def _tile(self):
self.vae.tiled = True
def _slice(self):
self.vae.sliced = True
def enable_model_cpu_offload(self, device = None):
self.offloaded = True
pipe = _VaeOnly()
effective, tiled = apply_memory_plan(pipe, _plan(OFFLOAD_MODEL, tiling = True), device = "cuda")
assert tiled is True and pipe.vae.tiled and pipe.vae.sliced
def test_apply_group_falls_back_to_model_without_transformer():
# The recording pipe has no .transformer, so group offload cannot engage and the applier falls back to whole-module offload.
pipe = _RecordingPipe()
effective, _ = apply_memory_plan(pipe, _plan(OFFLOAD_GROUP, tiling = True), device = "cuda")
assert effective == OFFLOAD_MODEL and "model_offload" in pipe.calls
def _install_fake_torch_and_hooks(monkeypatch, apply_group_offloading):
"""Fake torch.nn.Module + diffusers.hooks.apply_group_offloading for _apply_group_offload."""
import sys
class _Mod: # stands in for a torch.nn.Module instance (a streamed transformer)
pass
fake_torch = types.ModuleType("torch")
fake_torch.nn = types.SimpleNamespace(Module = _Mod)
fake_torch.device = lambda d: types.SimpleNamespace(type = str(d).split(":")[0])
monkeypatch.setitem(sys.modules, "torch", fake_torch)
if "diffusers" not in sys.modules:
monkeypatch.setitem(sys.modules, "diffusers", types.ModuleType("diffusers"))
fake_hooks = types.ModuleType("diffusers.hooks")
fake_hooks.apply_group_offloading = apply_group_offloading
monkeypatch.setitem(sys.modules, "diffusers.hooks", fake_hooks)
return _Mod
def test_apply_group_partial_hooks_propagates_not_crash_fallback(monkeypatch):
# A dual-DiT pipe whose second transformer fails group offload AFTER the first installed hooks is left partial, which
# enable_model_cpu_offload rejects, so the applier must PROPAGATE the failure instead of letting the fallback crash.
import core.inference.diffusion_memory as mem
calls = {"n": 0}
def _apply(module, **kw):
calls["n"] += 1
if calls["n"] >= 2:
raise RuntimeError("OOM on second DiT")
Mod = _install_fake_torch_and_hooks(monkeypatch, _apply)
class _DualPipe:
transformer = Mod()
transformer_2 = Mod()
components: dict = {}
with pytest.raises(RuntimeError, match = "OOM on second DiT"):
mem._apply_group_offload(_DualPipe(), "cuda", logger = None)
assert calls["n"] == 2 # first installed hooks, second failed -> propagated
def test_apply_group_single_transformer_failure_falls_back(monkeypatch):
# A single-DiT pipe whose group offload fails with NO hooks installed returns False so the caller falls back cleanly.
import core.inference.diffusion_memory as mem
def _apply(module, **kw):
raise RuntimeError("OOM before any hook")
Mod = _install_fake_torch_and_hooks(monkeypatch, _apply)
class _SinglePipe:
transformer = Mod()
components: dict = {}
assert mem._apply_group_offload(_SinglePipe(), "cuda", logger = None) is False
def test_apply_group_fallback_enables_vae_tiling():
# A balanced/group plan keeps the VAE resident (tiling off), so when group offload cannot engage and we drop to whole-module offload the applier must turn tiling ON.
plan = _plan(OFFLOAD_GROUP, tiling = True)
assert plan.vae_tiling is False # group plan leaves tiling off by design
pipe = _RecordingPipe() # no .transformer -> group offload falls back to model
effective, tiled = apply_memory_plan(pipe, plan, device = "cuda")
assert effective == OFFLOAD_MODEL
assert tiled is True and "vae_tiling" in pipe.calls
def _install_sized_torch(monkeypatch):
import sys
class Tensor:
def __init__(self, size_mib):
self.size_mib = size_mib
def numel(self):
return self.size_mib * 1024 * 1024
def element_size(self):
return 1
class Module:
def __init__(self, size_mib = 0):
self.tensor = Tensor(size_mib)
self.moved_to = None
def parameters(self, recurse = True):
return [self.tensor]
def buffers(self, recurse = True):
return []
def to(self, device):
self.moved_to = device
return self
fake_torch = types.ModuleType("torch")
fake_torch.nn = types.SimpleNamespace(Module = Module)
fake_torch.device = lambda value: types.SimpleNamespace(type = str(value).split(":")[0])
monkeypatch.setitem(sys.modules, "torch", fake_torch)
return Module
def test_refine_model_offload_streams_only_when_a_component_exceeds_budget(monkeypatch):
Module = _install_sized_torch(monkeypatch)
plan = MemoryPlan(
requested_mode = "low_vram",
offload_policy = OFFLOAD_MODEL,
vae_tiling = True,
vae_slicing = True,
device_memory = _discrete(8000),
estimates = {"safe_device_budget_mib": 6000},
)
fitting_transformer = Module(4000)
fits = types.SimpleNamespace(
transformer = fitting_transformer,
components = {"transformer": fitting_transformer, "text_encoder": Module(5500)},
)
assert refine_memory_plan_for_components(fits, plan) is plan
transformer = Module(2500)
oversized = types.SimpleNamespace(
transformer = transformer,
components = {"transformer": transformer, "text_encoder": Module(7500)},
)
refined = refine_memory_plan_for_components(oversized, plan)
assert refined.offload_policy == OFFLOAD_STREAMING
assert refined.estimates["largest_component_mib"] == 7500
assert any("text_encoder" in reason for reason in refined.reasons)
def test_refine_keeps_model_offload_when_streaming_cannot_help(monkeypatch):
"""Only a component streaming can actually hook justifies leaving whole-module offload."""
Module = _install_sized_torch(monkeypatch)
plan = MemoryPlan(
requested_mode = "low_vram",
offload_policy = OFFLOAD_MODEL,
vae_tiling = True,
vae_slicing = True,
device_memory = _discrete(8000),
estimates = {"safe_device_budget_mib": 6000},
)
# The oversized component has no granular hook, so streaming would onload it whole anyway.
transformer = Module(2500)
fat_vae = types.SimpleNamespace(
transformer = transformer,
components = {
"transformer": transformer,
"text_encoder": Module(1200),
"vae": Module(7500),
},
)
assert refine_memory_plan_for_components(fat_vae, plan) is plan
# Each component fits, but streaming holds every unstreamed one at once: 3000 + 3500 > 6000.
transformer = Module(2000)
fat_resident = types.SimpleNamespace(
transformer = transformer,
components = {
"transformer": transformer,
"text_encoder": Module(6500),
"vae": Module(3000),
"image_encoder": Module(3500),
},
)
assert refine_memory_plan_for_components(fat_resident, plan) is plan
# Same shape with a resident set that fits still streams, and reports what stays behind.
transformer = Module(2000)
slim_resident = types.SimpleNamespace(
transformer = transformer,
components = {
"transformer": transformer,
"text_encoder": Module(6500),
"vae": Module(300),
"image_encoder": Module(500),
},
)
refined = refine_memory_plan_for_components(slim_resident, plan)
assert refined.offload_policy == OFFLOAD_STREAMING
assert refined.estimates["streaming_resident_mib"] == 800
def test_apply_streaming_uses_block_and_leaf_hooks_with_bounded_cpu_memory(monkeypatch):
Module = _install_sized_torch(monkeypatch)
calls = []
def _apply(
module,
*,
onload_device,
offload_device,
offload_type,
num_blocks_per_group = None,
use_stream = False,
non_blocking = False,
record_stream = True,
low_cpu_mem_usage = False,
):
calls.append(
(
module,
offload_type,
num_blocks_per_group,
use_stream,
non_blocking,
record_stream,
low_cpu_mem_usage,
)
)
import sys
if "diffusers" not in sys.modules:
monkeypatch.setitem(sys.modules, "diffusers", types.ModuleType("diffusers"))
hooks = types.ModuleType("diffusers.hooks")
hooks.apply_group_offloading = _apply
monkeypatch.setitem(sys.modules, "diffusers.hooks", hooks)
transformer = Module(2500)
text_encoder = Module(7500)
vae = Module(200)
pipe = types.SimpleNamespace(
transformer = transformer,
components = {
"transformer": transformer,
"text_encoder": text_encoder,
"vae": vae,
},
)
effective, _ = apply_memory_plan(
pipe, _manual_plan(OFFLOAD_STREAMING, tiling = True), device = "cuda"
)
assert effective == OFFLOAD_STREAMING
assert vae.moved_to.type == "cuda"
assert [(call[1], call[2]) for call in calls] == [
("block_level", DEFAULT_GROUP_BLOCKS),
("leaf_level", None),
]
assert all(call[3:] == (True, True, False, True) for call in calls)
def test_apply_streaming_does_not_fall_back_to_known_oom_path(monkeypatch):
import core.inference.diffusion_memory as mem
def _fail(*args, **kwargs):
raise RuntimeError("granular streaming offload could not be enabled")
monkeypatch.setattr(mem, "_apply_streaming_offload", _fail)
pipe = _RecordingPipe()
with pytest.raises(RuntimeError, match = "granular streaming offload could not be enabled"):
apply_memory_plan(pipe, _manual_plan(OFFLOAD_STREAMING, tiling = True), device = "cuda")
assert "model_offload" not in pipe.calls
def test_apply_sequential_offload():
pipe = _RecordingPipe()
effective, _ = apply_memory_plan(
pipe, _manual_plan(OFFLOAD_SEQUENTIAL, tiling = True), device = "cuda"
)
assert "sequential_offload" in pipe.calls and "to:cuda" not in pipe.calls
assert effective == OFFLOAD_SEQUENTIAL
assert pipe.offload_device == "cuda" # device threaded to sequential offload too
def test_apply_sequential_falls_back_to_model_offload_when_unsupported():
# Sequential offload is unreliable for GGUF on some diffusers versions, so the applier falls back to whole-module and reports what ran.
class _NoSeqPipe(_RecordingPipe):
def enable_sequential_cpu_offload(self, device = None):
raise RuntimeError("sequential offload not supported for this transformer")
pipe = _NoSeqPipe()
effective, _ = apply_memory_plan(
pipe, _manual_plan(OFFLOAD_SEQUENTIAL, tiling = True), device = "cuda"
)
assert effective == OFFLOAD_MODEL
assert "model_offload" in pipe.calls
def test_apply_tolerates_pipe_without_vae_savers():
# A pipeline missing enable_vae_* must not crash the applier.
class _Bare:
def __init__(self):
self.moved = None
def to(self, device):
self.moved = device
bare = _Bare()
_, tiled = apply_memory_plan(bare, _plan(OFFLOAD_NONE, tiling = False), device = "cpu")
assert bare.moved == "cpu" and tiled is False
# ── settled snapshot + capacity-fit retry helpers ────────────────────────────
def test_settled_snapshot_takes_max_free_over_reads(monkeypatch):
# A transient foreign allocation can only SHRINK free, so the settled snapshot keeps the max free across reads (60 GB on an idle 183 GB card).
from core.inference import diffusion_memory as dm
reads = [
DeviceMemory("cuda", "cuda", "discrete_vram", free_mib = 60_000, total_mib = 183_359),
DeviceMemory("cuda", "cuda", "discrete_vram", free_mib = 170_000, total_mib = 183_359),
DeviceMemory("cuda", "cuda", "discrete_vram", free_mib = 170_000, total_mib = 183_359),
]
monkeypatch.setattr(dm, "snapshot_device_memory", lambda target: reads.pop(0))
snap = dm.settled_snapshot_device_memory(_target(device = "cuda"), attempts = 3, delay_s = 0)
assert snap.free_mib == 170_000
def test_settled_snapshot_stops_early_when_device_already_idle(monkeypatch):
# First read already within the reserve of total: no transient to wait out, one read only.
from core.inference import diffusion_memory as dm
calls = []
def fake_snapshot(target):
calls.append(1)
return DeviceMemory("cuda", "cuda", "discrete_vram", free_mib = 170_000, total_mib = 183_359)
monkeypatch.setattr(dm, "snapshot_device_memory", fake_snapshot)
snap = dm.settled_snapshot_device_memory(_target(device = "cuda"), attempts = 3, delay_s = 0)
assert snap.free_mib == 170_000
assert calls == [1]
def _fake_torch_allocator(monkeypatch, *, reserved: int, allocated: int):
"""Stub torch.cuda's allocator counters for the reclaimable snapshot."""
import sys
torch = types.ModuleType("torch")
torch.cuda = types.SimpleNamespace(
memory_reserved = lambda: reserved,
memory_allocated = lambda: allocated,
)
monkeypatch.setitem(sys.modules, "torch", torch)
def test_reclaimable_snapshot_credits_cached_blocks_without_flushing(monkeypatch):
# mem_get_info counts every block the caching allocator holds for reuse as USED, so a warm
# card reads as nearly full. The generate-time guard must not mistake that for a shortfall,
# and must not pay empty_cache() per image to find out.
from core.inference import diffusion_memory as dm
monkeypatch.setattr(
dm,
"snapshot_device_memory",
lambda target: DeviceMemory(
"cuda", "cuda", "discrete_vram", free_mib = 2_000, total_mib = 16_302
),
)
# 6 GiB reserved, 2 GiB in live tensors: 4 GiB is cached and reclaimable.
_fake_torch_allocator(monkeypatch, reserved = 6 * 1024**3, allocated = 2 * 1024**3)
snap = dm.reclaimable_snapshot_device_memory(_target(device = "cuda"))
assert snap.free_mib == 2_000 + 4 * 1024
def test_reclaimable_snapshot_never_claims_more_than_the_card(monkeypatch):
# The credit is arithmetic, so a bogus allocator reading must not invent memory the card
# does not have and talk the guard out of a real refusal.
from core.inference import diffusion_memory as dm
monkeypatch.setattr(
dm,
"snapshot_device_memory",
lambda target: DeviceMemory(
"cuda", "cuda", "discrete_vram", free_mib = 15_000, total_mib = 16_302
),
)
_fake_torch_allocator(monkeypatch, reserved = 40 * 1024**3, allocated = 0)
assert dm.reclaimable_snapshot_device_memory(_target(device = "cuda")).free_mib == 16_302
def test_reclaimable_snapshot_falls_back_when_the_allocator_is_unreadable(monkeypatch):
# No allocator reading: the plain driver-level snapshot still stands, unchanged.
from core.inference import diffusion_memory as dm
import sys
monkeypatch.setattr(
dm,
"snapshot_device_memory",
lambda target: DeviceMemory(
"cuda", "cuda", "discrete_vram", free_mib = 2_000, total_mib = 16_302
),
)
torch = types.ModuleType("torch")
def _boom():
raise RuntimeError("no cuda context")
torch.cuda = types.SimpleNamespace(memory_reserved = _boom, memory_allocated = _boom)
monkeypatch.setitem(sys.modules, "torch", torch)
assert dm.reclaimable_snapshot_device_memory(_target(device = "cuda")).free_mib == 2_000
def test_reclaimable_snapshot_passthrough_off_cuda(monkeypatch):
# Only the CUDA caching allocator is modelled here; every other device keeps its plain read.
from core.inference import diffusion_memory as dm
monkeypatch.setattr(
dm,
"snapshot_device_memory",
lambda target: DeviceMemory(
"mps", "mps", "unified_memory", free_mib = 8_000, total_mib = 16_000
),
)
_fake_torch_allocator(monkeypatch, reserved = 6 * 1024**3, allocated = 0)
assert dm.reclaimable_snapshot_device_memory(_target(device = "mps")).free_mib == 8_000
def test_settled_snapshot_passthrough_off_cuda(monkeypatch):
# Non-cuda targets keep the single-read behaviour (no settle loop).
from core.inference import diffusion_memory as dm
calls = []
def fake_snapshot(target):
calls.append(1)
return DeviceMemory("mps", "mps", "unified_memory", free_mib = 8_000, total_mib = 16_000)
monkeypatch.setattr(dm, "snapshot_device_memory", fake_snapshot)
snap = dm.settled_snapshot_device_memory(_target(device = "mps"), attempts = 3, delay_s = 0)
assert snap.memory_kind == "unified_memory"
assert calls == [1]
def test_plan_fits_total_capacity():
# True exactly when required fits (total - reserve) * 0.85: the decline can then only come from the instantaneous free reading, so a settled retry helps.
from core.inference.diffusion_memory import plan_fits_total_capacity
def plan(
required,
total,
kind = "discrete_vram",
):
return types.SimpleNamespace(
estimates = {"resident_required_mib": required},
device_memory = DeviceMemory("cuda", "cuda", kind, free_mib = 1, total_mib = total),
)
# FLUX.2-dev int8 incident numbers: 90,228 required on a 183,359 MiB card, so it fits.
assert plan_fits_total_capacity(plan(90_228, 183_359)) is True
# Larger than the capacity margin (0.85 * (183,359 - 18,335) = 140,270), so no retry.
assert plan_fits_total_capacity(plan(150_000, 183_359)) is False
# Unknown sizes keep today's behaviour (no retry).
assert plan_fits_total_capacity(plan(None, 183_359)) is False
assert plan_fits_total_capacity(plan(90_228, None)) is False
assert plan_fits_total_capacity(types.SimpleNamespace()) is False
# ── the unified-memory oversize refusal ───────────────────────────────────────
# Apple Silicon shares one CPU/GPU pool, so the planner's OFFLOAD_NONE there is a placement
# with no fallback tier, and PYTORCH_MPS_HIGH_WATERMARK_RATIO=0.0 removes the allocator's hard
# limit: an oversized load is killed by the OS with no Python exception. These cover the
# load-time refusal that replaces that SIGKILL with a message.
_MPS_TOTAL_MIB = 16 * 1024
_MPS_FREE_MIB = int(_MPS_TOTAL_MIB * 0.80) # RAM free once macOS + a browser + Studio are up
def _unified_plan(
*,
model_dense_mib,
runtime_headroom_mib = 3072,
free_mib = _MPS_FREE_MIB,
total_mib = _MPS_TOTAL_MIB,
kind = "unified_memory",
device = "mps",
):
"""A plan straight from the shipped planner, so the budget arithmetic under test is the
real arithmetic rather than a hand-written copy of it."""
return plan_diffusion_memory(
target = _target(device = device, backend = device, supports_offload = False),
device_memory = DeviceMemory(device, device, kind, free_mib, total_mib),
model_dense_mib = model_dense_mib,
runtime_headroom_mib = runtime_headroom_mib,
)
def test_unified_oversize_refuses_and_names_family_and_both_numbers():
from core.inference.diffusion_memory import unified_memory_shortfall_message
# 16 GiB Mac, 12.8 GiB free, 20% unified reserve, so about 9.5 GiB of budget. 24 GiB cannot fit.
plan = _unified_plan(model_dense_mib = 24 * 1024)
assert plan.offload_policy == OFFLOAD_NONE # the planner still has no fallback to offer
message = unified_memory_shortfall_message(plan, family = "wan2.2-ti2v-5b")
assert message is not None
assert "wan2.2-ti2v-5b" in message
# Weights + the flat base overhead, and the safe budget, both rendered in GB.
assert "about 26 GB of memory for its weights" in message # 24 GiB weights + 2 GiB overhead
assert "about 10 GB is usable" in message # 12.8 GiB free, less 20% of 16 GiB
assert "13 GB currently free" in message
# The most useful thing the user can change, and the escape hatch.
assert "smaller or more quantized model" in message
assert "UNSLOTH_DIFFUSION_ALLOW_OVERSIZED_LOAD=1" in message
def test_unified_oversize_ignores_the_soft_runtime_headroom():
"""The refusal budgets WEIGHTS only. A load whose weights fit but whose weights + activation
headroom do not is marginal, and VAE tiling/slicing (always on for mps) cuts the decode peak
the headroom estimate does not model, so it must NOT be refused."""
from core.inference.diffusion_memory import unified_memory_shortfall_message
budget = _MPS_FREE_MIB - max(2048, int(_MPS_TOTAL_MIB * 0.20))
weights = budget - 2048 # weights + the 2048 base overhead land exactly on the budget
plan = _unified_plan(model_dense_mib = weights, runtime_headroom_mib = 8192)
assert plan.estimates["resident_required_mib"] > budget # refused if headroom counted
assert unified_memory_shortfall_message(plan) is None
# One MiB more of weights does tip it over.
assert unified_memory_shortfall_message(_unified_plan(model_dense_mib = weights + 1)) is not None
def test_unified_oversize_never_refuses_discrete_vram():
"""Discrete VRAM keeps its fallback ladder: an oversized model streams from host RAM under
group / whole-module offload, so refusing it would break a load that works today."""
from core.inference.diffusion_memory import unified_memory_shortfall_message
plan = plan_diffusion_memory(
target = _target(device = "cuda", backend = "cuda", supports_offload = True),
device_memory = DeviceMemory("cuda", "cuda", "discrete_vram", 12_000, 16_384),
model_dense_mib = 80 * 1024,
runtime_headroom_mib = 6963,
)
assert plan.offload_policy == OFFLOAD_MODEL
assert unified_memory_shortfall_message(plan, family = "ltx-2") is None
def test_unified_oversize_never_refuses_plain_cpu_system_memory():
# A CPU target reports system_memory, has swap, and is an opt-in fringe path: unchanged.
from core.inference.diffusion_memory import unified_memory_shortfall_message
plan = _unified_plan(model_dense_mib = 80 * 1024, kind = "system_memory", device = "cpu")
assert unified_memory_shortfall_message(plan) is None
@pytest.mark.parametrize(
"kwargs",
[
{"free_mib": None}, # psutil unavailable: budget unknown
{"model_dense_mib": None}, # unscannable checkpoint: size unknown
],
)
def test_unified_oversize_fails_open_on_unknown_inputs(kwargs):
"""Matches the planner's own "budget or model size unknown; staying resident": never turn a
failed probe into a refusal."""
from core.inference.diffusion_memory import unified_memory_shortfall_message
kwargs = {"model_dense_mib": 80 * 1024, **kwargs}
assert unified_memory_shortfall_message(_unified_plan(**kwargs)) is None
def test_unified_oversize_env_override_attempts_the_load_anyway(monkeypatch):
from core.inference.diffusion_memory import (
UNIFIED_OVERSIZE_ENV,
raise_on_unified_memory_shortfall,
unified_memory_shortfall_message,
)
plan = _unified_plan(model_dense_mib = 80 * 1024)
assert unified_memory_shortfall_message(plan) is not None
for value in ("1", "true", "YES", "on"):
monkeypatch.setenv(UNIFIED_OVERSIZE_ENV, value)
assert unified_memory_shortfall_message(plan) is None
raise_on_unified_memory_shortfall(plan) # must not raise
monkeypatch.setenv(UNIFIED_OVERSIZE_ENV, "0")
assert unified_memory_shortfall_message(plan) is not None
def test_unified_oversize_message_survives_a_malformed_plan():
from core.inference.diffusion_memory import unified_memory_shortfall_message
assert unified_memory_shortfall_message(types.SimpleNamespace()) is None
def test_raise_on_unified_memory_shortfall_raises_runtime_error_with_the_message():
"""RuntimeError matches the llama.cpp unified-memory APU refusal. Both loaders call this on
a worker thread inside load_pipeline, where _run_load stringifies it onto load_progress, so
the text reaches the UI toast and the 409 mapping of the synchronous route never applies."""
from core.inference.diffusion_memory import (
raise_on_unified_memory_shortfall,
unified_memory_shortfall_message,
)
plan = _unified_plan(model_dense_mib = 80 * 1024)
with pytest.raises(RuntimeError) as excinfo:
raise_on_unified_memory_shortfall(plan, family = "ltx-2")
assert str(excinfo.value) == unified_memory_shortfall_message(plan, family = "ltx-2")
# A plan that fits is a silent no-op.
raise_on_unified_memory_shortfall(_unified_plan(model_dense_mib = 1024))
def test_unified_oversize_decision_matrix_for_the_real_video_families():
"""The shipped video family tables against real Mac RAM sizes: the refusal must fire exactly
where the weights genuinely cannot fit, and must stay silent where they can.
``video_families`` is a pure table module (no torch, no diffusers), so this stays hermetic.
``free`` is modelled at 80% of RAM, the share left once macOS, a browser and Studio are up.
"""
from core.inference.video_families import _FAMILIES
from core.inference.diffusion_memory import (
DEFAULT_BASE_OVERHEAD_MIB,
estimate_video_runtime_mib,
unified_memory_shortfall_message,
)
mib_per_gb = 1000.0**3 / (1024.0 * 1024.0) # the tables are DECIMAL GB
# family: the RAM sizes (GiB) at which the load must be REFUSED.
expected_refusals = {
# 144.2 GB of bf16 weights, the largest video family by a wide margin: refused at every
# size in this matrix, 128 GiB included. That is the correct answer rather than a gap in
# the table -- the dense pipeline cannot fit any Mac modelled here, and a user on one
# reaches H3 through the GGUF or prequantized artifacts instead.
"minimax-h3": {16, 24, 32, 64, 96, 128},
"ltx-2": {16, 24, 32, 64, 96},
"wan2.2-ti2v-5b": {16, 24, 32},
"wan2.2-t2v-a14b": {16, 24, 32, 64, 96},
"hunyuanvideo-1.5": {16, 24, 32},
"hunyuanvideo-1.5-720p": {16, 24, 32},
}
assert {f.name for f in _FAMILIES} == set(
expected_refusals
), "a video family was added or renamed: extend the expected refusal matrix"
for fam in _FAMILIES:
width, height = fam.resolution_presets[0]
dense = int(sum(fam.bf16_components_gb) * mib_per_gb)
headroom = estimate_video_runtime_mib(
width = width, height = height, num_frames = fam.default_num_frames
)
for ram_gib in (16, 24, 32, 64, 96, 128):
total = ram_gib * 1024
plan = _unified_plan(
model_dense_mib = dense,
runtime_headroom_mib = headroom,
free_mib = int(total * 0.80),
total_mib = total,
)
# The planner never has an alternative to offer on unified memory: that is the bug.
assert plan.offload_policy == OFFLOAD_NONE
refused = unified_memory_shortfall_message(plan, family = fam.name) is not None
assert refused is (ram_gib in expected_refusals[fam.name]), (
f"{fam.name} at {ram_gib} GiB: refused={refused}, "
f"weights+overhead={dense + DEFAULT_BASE_OVERHEAD_MIB} MiB, "
f"budget={plan.estimates['safe_device_budget_mib']} MiB"
)
# -- the unified refusal is judged on resident sizes, not download sizes --------
def test_unified_memory_policy_cannot_express_a_misfit():
"""Why the dense-quant fast path needed its own explicit check: on unified memory the planner
returns OFFLOAD_NONE for ANY size (offload just shuffles bytes within one pool), so the
"policy is not none" test the dense re-plan relied on can never fire there."""
from core.inference.diffusion_memory import (
DeviceMemory,
OFFLOAD_NONE,
plan_diffusion_memory,
unified_memory_shortfall_message,
)
target = type("T", (), {"supports_model_cpu_offload": True, "device": "cuda"})()
memory = DeviceMemory("cuda", "cuda", "unified_memory", 8_000, 16_000)
plan = plan_diffusion_memory(
target = target,
device_memory = memory,
model_dense_mib = 90_000, # wildly oversized
runtime_headroom_mib = 1_000,
)
assert plan.offload_policy == OFFLOAD_NONE
# ... but the explicit sizing check does see the shortfall.
assert unified_memory_shortfall_message(plan, family = "flux.1") is not None
# ── the streamed-text-encoder group tier ──────────────────────────────────────
# A text encoder runs ONCE, before step 0, but group offload places every non-streamed component
# resident, so its bytes are reserved for the whole denoise. Where that is the only thing pushing
# the group floor over budget, streaming the encoders too keeps the tier instead of dropping to
# whole-module offload (measured 48m25s for a 20-step 1024x1024 image on a 16 GB card).
# The 16 GB card from the report: free 15,870 of 16,305 MiB, reserve max(2048, 10%) = 2048, so the
# safe budget is exactly the 13,822 MiB the failing plan was measured against.
_16G_FREE_MIB = 15_870
_16G_TOTAL_MIB = 16_305
_16G_BUDGET_MIB = 13_822
# Z-Image at int8, straight from the report: transformer 6451 + companions 7820 = 14,271 resident,
# of which the text encoders are 7629 (8.0 of the 8.2 GB companion table) and the VAE is the rest.
_ZIMAGE_MODEL_DENSE_MIB = 14_271
_ZIMAGE_COMPANION_MIB = 7_820
_ZIMAGE_TEXT_ENCODER_MIB = 7_629
def test_safe_budget_matches_the_reported_16g_card():
# Anchors every number below: if the reserve rule changes, this fails first rather than
# silently moving the floors the rest of this section is calibrated against.
from core.inference.diffusion_memory import _safe_device_budget_mib
assert _safe_device_budget_mib(_discrete(_16G_FREE_MIB, _16G_TOTAL_MIB)) == _16G_BUDGET_MIB
def _zimage_plan(text_encoder_dense_mib):
return plan_diffusion_memory(
target = _target(),
device_memory = _discrete(_16G_FREE_MIB, _16G_TOTAL_MIB),
model_dense_mib = _ZIMAGE_MODEL_DENSE_MIB,
companion_dense_mib = _ZIMAGE_COMPANION_MIB,
text_encoder_dense_mib = text_encoder_dense_mib,
runtime_headroom_mib = 8192,
base_overhead_mib = 2048,
)
def test_streamed_text_encoders_rescue_the_48_minute_plan():
# required 14,271 + 8192 + 2048 = 24,511 against a 13,822 budget: not resident either way.
# group floor 7820 + 8192 + 2048 = 18,060 > 13,822, which is what dropped this to whole-module
# offload. With the encoders streamed the floor is 191 + 8192 + 2048 = 10,431 and fits.
before = _zimage_plan(None)
assert before.offload_policy == OFFLOAD_MODEL
assert before.stream_text_encoders is False
assert before.estimates["group_floor_streamed_te_mib"] is None
after = _zimage_plan(_ZIMAGE_TEXT_ENCODER_MIB)
assert after.offload_policy == OFFLOAD_GROUP
assert after.stream_text_encoders is True
assert after.estimates["resident_required_mib"] == 24_511
assert after.estimates["group_floor_mib"] == 18_060
assert after.estimates["group_floor_streamed_te_mib"] == 10_431
assert any("text encoders" in reason for reason in after.reasons)
# Group keeps the VAE resident, so the decode stays bit-identical (sliced, not tiled).
assert after.vae_slicing is True and after.vae_tiling is False
# The flag has to reach the applier, which reads the public dict in status/logging too.
assert after.as_public_dict()["stream_text_encoders"] is True
@pytest.mark.parametrize("mode", [None, MEMORY_MODE_FAST])
def test_plain_group_is_preferred_over_streaming_the_text_encoders(mode):
# Where the companions fit as they are, the encoders stay resident: streaming them is a small
# loss for no gain, so the new tier must only engage as a rescue from whole-module offload.
# Both branches that pick a tier have to agree on that order, hence both modes here: auto has
# its own if/elif chain and `fast` goes through _offload_tier, so covering one leaves the
# other free to prefer the wrong tier.
plan = plan_diffusion_memory(
target = _target(),
device_memory = _discrete(_16G_FREE_MIB, _16G_TOTAL_MIB),
model_dense_mib = 40_000,
companion_dense_mib = 1_500,
text_encoder_dense_mib = 1_400,
runtime_headroom_mib = 8192,
base_overhead_mib = 2048,
requested_mode = mode,
)
assert plan.offload_policy == OFFLOAD_GROUP
assert plan.stream_text_encoders is False
def test_streamed_text_encoders_still_fall_through_when_even_that_floor_is_over():
# A VAE alone over budget has nothing left to give up, so whole-module offload still wins.
plan = plan_diffusion_memory(
target = _target(),
device_memory = _discrete(_16G_FREE_MIB, _16G_TOTAL_MIB),
model_dense_mib = 40_000,
companion_dense_mib = 20_000,
text_encoder_dense_mib = 1_000,
runtime_headroom_mib = 8192,
base_overhead_mib = 2048,
)
assert plan.offload_policy == OFFLOAD_MODEL
assert plan.stream_text_encoders is False
def test_fast_mode_also_reaches_the_streamed_text_encoder_tier():
# `fast` has its own does-not-fit branch; it must offer the same ladder as auto, or an explicit
# fast request on a 16 GB card lands on the 48-minute tier the auto path now avoids.
plan = plan_diffusion_memory(
target = _target(),
device_memory = _discrete(_16G_FREE_MIB, _16G_TOTAL_MIB),
model_dense_mib = _ZIMAGE_MODEL_DENSE_MIB,
companion_dense_mib = _ZIMAGE_COMPANION_MIB,
text_encoder_dense_mib = _ZIMAGE_TEXT_ENCODER_MIB,
runtime_headroom_mib = 8192,
base_overhead_mib = 2048,
requested_mode = MEMORY_MODE_FAST,
)
assert plan.offload_policy == OFFLOAD_GROUP
assert plan.stream_text_encoders is True
def test_text_encoder_split_larger_than_the_companions_clamps_at_zero():
# The two terms can come from different sources (a cache walk and a family table), so a split
# that exceeds the total must floor at 0 rather than produce a negative resident requirement.
plan = plan_diffusion_memory(
target = _target(),
device_memory = _discrete(_16G_FREE_MIB, _16G_TOTAL_MIB),
model_dense_mib = 40_000,
companion_dense_mib = 7_820,
text_encoder_dense_mib = 9_999,
runtime_headroom_mib = 8192,
base_overhead_mib = 2048,
)
assert plan.estimates["group_floor_streamed_te_mib"] == 10_240 # 0 + 8192 + 2048
def _legacy_offload_policy(
*, budget, model_dense_mib, companion_dense_mib, runtime_headroom_mib, base_overhead_mib
):
"""The auto-path decision as it stood BEFORE the streamed-text-encoder tier, written out
independently. The back-compat fence below compares the shipped planner against it, so a
change that leaks the new tier into a caller that passed no split fails here."""
required = model_dense_mib + runtime_headroom_mib + base_overhead_mib
if required <= int(budget * 0.85):
return OFFLOAD_NONE
if companion_dense_mib is None:
return OFFLOAD_MODEL
group_floor = companion_dense_mib + runtime_headroom_mib + base_overhead_mib
return OFFLOAD_GROUP if group_floor <= budget else OFFLOAD_MODEL
def test_no_text_encoder_split_reproduces_the_previous_decision():
# The back-compat fence. Every existing caller passes no split (the keyword defaults to None),
# so across the size matrix the planner must land exactly where it did before, and must never
# report the new tier.
from core.inference.diffusion_memory import _safe_device_budget_mib
for free, total in ((6_000, 8_192), (11_000, 12_288), (15_870, 16_305), (80_000, 81_920)):
for model_dense in (2_000, 14_271, 40_000):
for companion in (None, 200, 7_820, 30_000):
for headroom in (1_000, 8_192):
memory = _discrete(free, total)
plan = plan_diffusion_memory(
target = _target(),
device_memory = memory,
model_dense_mib = model_dense,
companion_dense_mib = companion,
runtime_headroom_mib = headroom,
base_overhead_mib = 2048,
)
expected = _legacy_offload_policy(
budget = _safe_device_budget_mib(memory),
model_dense_mib = model_dense,
companion_dense_mib = companion,
runtime_headroom_mib = headroom,
base_overhead_mib = 2048,
)
where = (free, total, model_dense, companion, headroom)
assert plan.offload_policy == expected, where
assert plan.stream_text_encoders is False, where
assert plan.estimates["group_floor_streamed_te_mib"] is None, where
def _stream_te_pipe(monkeypatch):
"""A pipe with two text encoders and a VAE, plus a record of which modules got group-offload
hooks and which were placed resident."""
applied: list[Any] = []
Mod = _install_fake_torch_and_hooks(monkeypatch, lambda module, **kw: applied.append(module))
class _Comp(Mod):
def __init__(self, name):
self.name = name
self.placed = None
def to(self, device):
self.placed = device
return self
transformer = _Comp("transformer")
text_encoder = _Comp("text_encoder")
text_encoder_2 = _Comp("text_encoder_2")
vae = _Comp("vae")
class _Pipe:
pass
pipe = _Pipe()
pipe.transformer = transformer
pipe.components = {
"transformer": transformer,
"text_encoder": text_encoder,
"text_encoder_2": text_encoder_2,
"vae": vae,
}
return pipe, applied, transformer, text_encoder, text_encoder_2, vae
def test_apply_group_offload_leaves_text_encoders_resident_by_default(monkeypatch):
# The unchanged path: only the transformer streams, every other component is placed resident.
import core.inference.diffusion_memory as mem
pipe, applied, transformer, te, te2, vae = _stream_te_pipe(monkeypatch)
assert mem._apply_group_offload(pipe, "cuda", logger = None) is True
assert applied == [transformer]
assert te.placed is not None and te2.placed is not None and vae.placed is not None
def test_apply_group_offload_streams_text_encoders_when_asked(monkeypatch):
# With the flag on, every text_encoder* module gets group-offload hooks and is NOT placed
# resident. Placing them would defeat the whole point: their bytes are what did not fit.
import core.inference.diffusion_memory as mem
pipe, applied, transformer, te, te2, vae = _stream_te_pipe(monkeypatch)
assert mem._apply_group_offload(pipe, "cuda", logger = None, stream_text_encoders = True) is True
assert applied == [transformer, te, te2]
assert te.placed is None and te2.placed is None
assert vae.placed is not None # the VAE is the companion the tier keeps resident
def _stream_te_kwargs(monkeypatch, **call_kw):
"""The kwargs _apply_group_offload hands diffusers, with a signature-complete fake.
The other fakes here take **kw, which makes the signature gating in _apply_group_offload read
as "diffusers does not support this". This one declares the real parameters so the gate is
actually exercised."""
seen: dict = {}
def _apply(
module,
onload_device = None,
offload_device = None,
offload_type = None,
num_blocks_per_group = None,
non_blocking = False,
use_stream = False,
record_stream = False,
low_cpu_mem_usage = False,
**_,
):
seen[getattr(module, "name", "?")] = dict(
use_stream = use_stream,
non_blocking = non_blocking,
record_stream = record_stream,
low_cpu_mem_usage = low_cpu_mem_usage,
offload_type = offload_type,
num_blocks_per_group = num_blocks_per_group,
)
import core.inference.diffusion_memory as mem
pipe, _applied, *_ = _stream_te_pipe(monkeypatch)
# SWAP, never re-install: a second _install_fake_torch_and_hooks mints a fresh stand-in Module
# class, and the components built by the first call then fail isinstance -- so only the
# transformer (which is taken unconditionally) reached diffusers and every assertion here was
# silently checking one module instead of four.
_swap_group_offloading(monkeypatch, _apply)
assert mem._apply_group_offload(pipe, "cuda", logger = None, **call_kw) is True
return seen
def test_the_split_leaves_the_weights_only_estimate_terms_untouched():
# The unified-memory load refusal sizes a load from `model_dense_mib + base_overhead_mib`
# against `safe_device_budget_mib`, and deliberately excludes the soft runtime headroom. The
# text-encoder split adds a NEW term and a new floor; it must not move any of those three, or
# a refusal calibrated against them would silently change meaning.
without = _zimage_plan(None).estimates
with_split = _zimage_plan(_ZIMAGE_TEXT_ENCODER_MIB).estimates
for key in ("safe_device_budget_mib", "model_dense_mib", "base_overhead_mib"):
assert without[key] == with_split[key], key
# The split itself is additive: it is visible, and the pre-existing floor is unchanged.
assert without["group_floor_mib"] == with_split["group_floor_mib"]
assert without["text_encoder_dense_mib"] is None
assert with_split["text_encoder_dense_mib"] == _ZIMAGE_TEXT_ENCODER_MIB
assert with_split["group_floor_streamed_te_mib"] < with_split["group_floor_mib"]
def test_streaming_the_text_encoders_does_not_pin_host_memory(monkeypatch):
# diffusers pins EVERY offloaded parameter in host RAM on the copy-stream path. That is a fair
# trade when group offload was already the plan, but this tier is only ever a rescue FROM
# whole-module offload, which pins nothing, on a card too small to hold the companions. Those
# hosts are not reliably RAM-rich, and #8188's machine got into trouble precisely by turning a
# device shortfall into unswappable host memory. The encoders run once per call, so the slower
# unpinned copy is paid once rather than per step.
seen = _stream_te_kwargs(monkeypatch, stream_text_encoders = True)
assert seen, "the applier never reached diffusers"
assert all(kw["low_cpu_mem_usage"] is True for kw in seen.values()), seen
def test_the_unchanged_group_tier_still_pins_for_speed(monkeypatch):
# The existing group plan is untouched: it was chosen because the companions FIT, so the host
# is not being rescued and the pinned fast copy is the right default.
seen = _stream_te_kwargs(monkeypatch)
assert seen, "the applier never reached diffusers"
assert all(kw["low_cpu_mem_usage"] is False for kw in seen.values()), seen
# ... and the CUDA copy-stream overlap is still requested, which is what makes pinning matter.
assert all(kw["use_stream"] and kw["non_blocking"] for kw in seen.values()), seen
def _swap_group_offloading(monkeypatch, apply_group_offloading):
"""Replace apply_group_offloading on the ALREADY-installed fake diffusers.hooks.
_install_fake_torch_and_hooks mints a fresh stand-in Module class each call, so calling it a
second time would leave the components built by the first call failing isinstance."""
import sys
monkeypatch.setattr(
sys.modules["diffusers.hooks"], "apply_group_offloading", apply_group_offloading
)
def test_a_text_encoder_that_refuses_group_offload_stays_resident(monkeypatch):
# A text encoder is a far less well-trodden target for block-level group offloading than a
# DiT. Before this tier existed the encoders were simply resident, so a refusal must degrade
# back to that, not fail the load: by the time the encoders are reached the transformer already
# carries hooks, and whole-module offload is no longer available as a fallback, so joining the
# all-or-nothing DiT loop would turn a slow-but-working load into a hard failure.
import core.inference.diffusion_memory as mem
applied: list[Any] = []
def _apply(module, **kw):
if getattr(module, "name", "").startswith("text_encoder"):
raise ValueError("no block list on this text encoder")
applied.append(module)
pipe, _unused, transformer, te, te2, vae = _stream_te_pipe(monkeypatch)
# Swap only the hook: re-installing the fake torch would mint a NEW Module class and every
# isinstance check against the already-built components would go false.
_swap_group_offloading(monkeypatch, _apply)
assert mem._apply_group_offload(pipe, "cuda", logger = None, stream_text_encoders = True) is True
# The transformer still streams, and the refusing encoders are placed resident instead.
assert applied == [transformer]
assert te.placed is not None and te2.placed is not None
assert vae.placed is not None
def test_one_refusing_text_encoder_does_not_cost_the_other_its_streaming(monkeypatch):
# Each encoder is decided on its own: a family where one encoder refuses and another does not
# must still stream the one that works, or a single awkward component silently reverts the
# whole rescue.
import core.inference.diffusion_memory as mem
applied: list[Any] = []
def _apply(module, **kw):
if getattr(module, "name", "") == "text_encoder":
raise ValueError("no block list on this text encoder")
applied.append(module)
pipe, _unused, transformer, te, te2, _vae = _stream_te_pipe(monkeypatch)
_swap_group_offloading(monkeypatch, _apply)
assert mem._apply_group_offload(pipe, "cuda", logger = None, stream_text_encoders = True) is True
assert applied == [transformer, te2]
assert te.placed is not None # the refusing one is resident
assert te2.placed is None # the working one still streams
def test_a_failing_dit_keeps_its_all_or_nothing_semantics(monkeypatch):
# The tolerance is scoped to the encoders. A DiT that fails AFTER another installed hooks
# still propagates, because a partially hooked denoiser is not something the pipeline can run
# or fall back from. This is the pre-existing contract and the new tier must not soften it.
import core.inference.diffusion_memory as mem
calls = {"n": 0}
def _apply(module, **kw):
calls["n"] += 1
if calls["n"] >= 2:
raise RuntimeError("OOM on the second DiT")
Mod = _install_fake_torch_and_hooks(monkeypatch, _apply)
class _DualPipe:
pass
pipe = _DualPipe()
pipe.transformer = Mod()
pipe.transformer_2 = Mod()
pipe.components = {"transformer": pipe.transformer, "transformer_2": pipe.transformer_2}
with pytest.raises(RuntimeError, match = "OOM on the second DiT"):
mem._apply_group_offload(pipe, "cuda", logger = None, stream_text_encoders = True)
def test_apply_memory_plan_threads_the_stream_flag_to_the_group_applier(monkeypatch):
# End of the wire: the planner's decision has to reach _apply_group_offload, or the plan says
# group-with-streamed-encoders while the pipeline still places them resident and OOMs.
import core.inference.diffusion_memory as mem
seen = {}
def _fake(
pipe,
device,
logger,
*,
stream_text_encoders = False,
):
seen["stream_text_encoders"] = stream_text_encoders
return True
monkeypatch.setattr(mem, "_apply_group_offload", _fake)
apply_memory_plan(_RecordingPipe(), _zimage_plan(_ZIMAGE_TEXT_ENCODER_MIB), device = "cuda")
assert seen["stream_text_encoders"] is True
apply_memory_plan(_RecordingPipe(), _plan(OFFLOAD_GROUP, tiling = True), device = "cuda")
assert seen["stream_text_encoders"] is False
# ── the generate-time activation guard ────────────────────────────────────────
# The load-time plan budgets the 1024x1024 default because load time cannot know the request, so a
# much larger frame was never compared against anything: at 1088x1920 the plan reserved half the
# working memory the pass needs. On Linux that raises OutOfMemoryError; on Windows WDDM the driver
# serves the overflow from system RAM instead, so ~27 GB lands on a 16 GB card with no exception
# and the desktop stops responding. These cover the pre-sampling refusal that replaces that.
# The Z-Image-Turbo GGUF hint from the report: the base repo carries the distilled marker, so the
# estimate here is the discounted one (0.85), which is the honest 13,872 MiB the issue measured.
_TURBO_HINT = (
"z-image Z-Image-Turbo-Q4_K_S.gguf unsloth/Z-Image-Turbo-GGUF Tongyi-MAI/Z-Image-Turbo"
)
def _shortfall(
width,
height,
memory = None,
**kw,
):
from core.inference.diffusion_memory import image_activation_shortfall_message
return image_activation_shortfall_message(
device_memory = memory if memory is not None else _discrete(_16G_FREE_MIB, _16G_TOTAL_MIB),
width = width,
height = height,
family = kw.pop("family", _TURBO_HINT),
**kw,
)
def test_estimate_image_runtime_scales_with_the_real_dimensions():
# The regression fence for the estimator itself: it already scales, it was simply never called
# with anything. 1088x1920 is 1.99x the area of 1024x1024, so the headroom must be ~2x.
base = estimate_image_runtime_mib(width = 1024, height = 1024)
tall = estimate_image_runtime_mib(width = 1088, height = 1920)
assert base == 8192
assert tall == 16_320
assert 1.95 < tall / base < 2.05
# And with the distilled discount that the base repo now contributes, the report's number.
assert estimate_image_runtime_mib(width = 1088, height = 1920, family = _TURBO_HINT) == 13_872
def test_guard_refuses_the_oversized_frame_and_passes_the_default_one():
# 13,872 MiB of working memory against a 13,822 MiB budget on the reported card: refuse.
message = _shortfall(1088, 1920)
assert message is not None
# Everything the user needs to act: what they asked for, what it costs, what they have.
assert "1088x1920" in message
# The TOTAL the decision compared, not the activations alone: 13,872 MiB of activations plus
# the 2,048 MiB of fixed overhead. Quoting 13.55 GB against a 13.50 GB budget was a refusal
# whose own numbers were close enough to read as a bug, and on the 15.92 GiB card in the
# report (14,254 MiB usable) it read as an outright contradiction.
assert "15.55 GB" in message # needed, overhead included
assert "2.00 GB of fixed overhead" in message
assert "13.50 GB" in message # usable
assert "15.50 GB" in message # currently free
assert "smaller resolution" in message
assert "UNSLOTH_DIFFUSION_ALLOW_OVERSIZED_GENERATE" in message
# The same card at the default resolution needs 6963 MiB and must go straight through.
assert _shortfall(1024, 1024) is None
def test_guard_counts_the_base_overhead_alongside_the_activations():
# The CUDA context, scheduler state and fragmentation allowance have to coexist with this
# pass's tensors, and the load-time plan already sums them additively. Leaving the overhead
# out left the guard silent by a few hundred MiB on the exact card #8188 was reported from:
# a 15.92 GiB card gives a 14,254 MiB budget, which 13,872 MiB of activations fits and
# 13,872 + 2048 does not.
from core.inference.diffusion_memory import _safe_device_budget_mib
reported_card = _discrete(16_302, 16_302) # idle 15.92 GiB card
assert _safe_device_budget_mib(reported_card) == 14_254
assert _shortfall(1088, 1920, memory = reported_card) is not None
# ... and setting the overhead to zero is exactly what makes it silent again, so the term is
# load-bearing rather than decorative.
assert _shortfall(1088, 1920, memory = reported_card, base_overhead_mib = 0) is None
def test_guard_never_refuses_at_or_below_the_resolution_the_load_planned_for():
# The load's flat headroom is a PLANNING figure: it picks an offload tier, and the tier it
# picks runs 1024x1024 on cards whose entire budget is below that figure. Treating it as a
# hard limit there would refuse generations that complete today, so the guard is confined to
# requests LARGER than what was planned. An 8 GB card is the case that proves it.
small = _discrete(
int(8 * 1024 * 0.97), 8 * 1024
) # safe budget 5898 MiB, under the 6963 default
assert _shortfall(1024, 1024, memory = small) is None
assert _shortfall(512, 512, memory = small) is None
# It still refuses the genuinely oversized frame on that same card.
assert _shortfall(1088, 1920, memory = small) is not None
def test_guard_scales_with_batch_size():
# Batch multiplies the activations exactly as area does, so the same overrun must be caught.
assert _shortfall(1024, 1024, batch_size = 1) is None
assert _shortfall(1024, 1024, batch_size = 4) is not None
assert "at a batch of 4" in _shortfall(1024, 1024, batch_size = 4)
def test_guard_is_skipped_on_unified_memory():
# Offload means something different where the CPU and GPU share one pool, "free" is a moving
# target shared with the OS, and the load-time unified refusal already owns that device class.
unified = DeviceMemory("cuda", "cuda", "unified_memory", _16G_FREE_MIB, _16G_TOTAL_MIB)
assert _shortfall(1088, 1920, memory = unified) is None
mps = DeviceMemory("mps", "mps", "unified_memory", _16G_FREE_MIB, _16G_TOTAL_MIB)
assert _shortfall(1088, 1920, memory = mps) is None
def test_guard_is_skipped_when_free_memory_is_unknown():
# No reading, no verdict: the planner's own rule for an unknown budget is to stay out of it.
blind = DeviceMemory("cuda", "cuda", "discrete_vram", None, _16G_TOTAL_MIB)
assert _shortfall(1088, 1920, memory = blind) is None
def test_guard_is_skipped_off_cuda_and_rocm():
# ROCm reports device "cuda", so that stays covered. XPU / CPU keep today's behaviour: their
# allocators differ and this estimate was measured against a discrete VRAM pool.
xpu = DeviceMemory("xpu", "xpu", "discrete_vram", _16G_FREE_MIB, _16G_TOTAL_MIB)
assert _shortfall(1088, 1920, memory = xpu) is None
cpu = DeviceMemory("cpu", "cpu", "system_memory", _16G_FREE_MIB, _16G_TOTAL_MIB)
assert _shortfall(1088, 1920, memory = cpu) is None
def test_guard_env_override_lets_an_oversized_generation_through(monkeypatch):
from core.inference.diffusion_memory import OVERSIZED_GENERATE_ENV
for value in ("1", "true", "YES", " on "):
monkeypatch.setenv(OVERSIZED_GENERATE_ENV, value)
assert _shortfall(1088, 1920) is None, value
# Anything else is not an override, so the refusal stands.
for value in ("0", "false", "", "maybe"):
monkeypatch.setenv(OVERSIZED_GENERATE_ENV, value)
assert _shortfall(1088, 1920) is not None, value
def test_guard_fails_open_when_the_probe_raises():
# A broken probe must never cost a user a generation that would have worked.
class _Exploding:
device = "cuda"
memory_kind = "discrete_vram"
is_unified = False
total_mib = _16G_TOTAL_MIB
@property
def free_mib(self):
raise RuntimeError("mem_get_info exploded")
assert _shortfall(1088, 1920, memory = _Exploding()) is None
def test_raiser_raises_valueerror_so_the_route_answers_400():
# ValueError, not RuntimeError: /images/generate maps ValueError to a 400 carrying the reason,
# while RuntimeError there is reserved for the not-loaded / cancelled sentinels and otherwise
# becomes an opaque 500 with the reason stripped.
from core.inference.diffusion_memory import raise_on_image_activation_shortfall
with pytest.raises(ValueError, match = "1088x1920"):
raise_on_image_activation_shortfall(
device_memory = _discrete(_16G_FREE_MIB, _16G_TOTAL_MIB),
width = 1088,
height = 1920,
family = _TURBO_HINT,
)
# And it is a no-op wherever the message function declines to produce a verdict.
raise_on_image_activation_shortfall(
device_memory = _discrete(_16G_FREE_MIB, _16G_TOTAL_MIB),
width = 1024,
height = 1024,
family = _TURBO_HINT,
)
def _zimage_plan_on(memory, text_encoder_dense_mib):
return plan_diffusion_memory(
target = _target(),
device_memory = memory,
model_dense_mib = _ZIMAGE_MODEL_DENSE_MIB,
companion_dense_mib = _ZIMAGE_COMPANION_MIB,
text_encoder_dense_mib = text_encoder_dense_mib,
runtime_headroom_mib = 8192,
base_overhead_mib = 2048,
)
def test_default_resolution_plans_identically_across_card_sizes():
# The cross-check between the two fixes: at the default resolution the guard is silent on every
# card, and the plan a discrete CUDA target reaches with no text-encoder split is the plan it
# reached before either change. Neither fix leaks into the other's territory.
from core.inference.diffusion_memory import _safe_device_budget_mib
for gigabytes in (8, 12, 16, 24, 32, 48, 80):
total = gigabytes * 1024
memory = _discrete(int(total * 0.97), total)
assert _shortfall(1024, 1024, memory = memory) is None, gigabytes
expected = _legacy_offload_policy(
budget = _safe_device_budget_mib(memory),
model_dense_mib = _ZIMAGE_MODEL_DENSE_MIB,
companion_dense_mib = _ZIMAGE_COMPANION_MIB,
runtime_headroom_mib = 8192,
base_overhead_mib = 2048,
)
assert _zimage_plan_on(memory, None).offload_policy == expected, gigabytes
def test_the_refusal_reports_the_number_it_compared():
"""A refusal that quotes less than it compared reads as a bug in the guard. On the 15.92 GiB
card in #8188 (14,254 MiB usable) it printed "needs about 13.55 GB ... only about 13.92 GB
usable" and then refused, which is a contradiction on its face."""
from core.inference.diffusion_memory import DEFAULT_BASE_OVERHEAD_MIB
message = _shortfall(1088, 1920, base_overhead_mib = 1_024)
assert message is not None
# 13,872 MiB of activations + 1,024 MiB of overhead.
assert "14.55 GB of working memory" in message
assert "1.00 GB of fixed overhead" in message
# And with no overhead at all the two numbers are the activations, unchanged.
assert "13.55 GB of working memory" in _shortfall(1088, 1920, base_overhead_mib = 0)
assert DEFAULT_BASE_OVERHEAD_MIB > 0
def test_the_activation_refusal_is_its_own_error_type():
"""The OpenAI-compatible route sanitises every exception into a bare 500, so the one message
written FOR the caller needs a type it can recognise. Still a ValueError, so /images/generate
keeps mapping it to a 400 with the reason."""
import pytest as _pytest
from core.inference.diffusion_memory import (
ImageActivationShortfallError,
raise_on_image_activation_shortfall,
)
assert issubclass(ImageActivationShortfallError, ValueError)
with _pytest.raises(ImageActivationShortfallError):
raise_on_image_activation_shortfall(
device_memory = _discrete(_16G_FREE_MIB, _16G_TOTAL_MIB),
width = 1088,
height = 1920,
family = _TURBO_HINT,
)
# A request the guard passes still raises nothing at all.
raise_on_image_activation_shortfall(
device_memory = _discrete(_16G_FREE_MIB, _16G_TOTAL_MIB),
width = 1024,
height = 1024,
family = _TURBO_HINT,
)
def test_the_streamed_encoders_are_hooked_leaf_by_leaf(monkeypatch):
# A text encoder is not a stack of uniform blocks, which is why _streamable_components and
# _apply_streaming_offload both classify every text_encoder* as leaf_level. Handing this path
# the DiTs' block_level kwargs made the plan and the application disagree: block level on an
# encoder with no top-level ModuleList groups the whole thing as one unit, which is the
# residency the streamed-encoder floor was picked to avoid.
seen = _stream_te_kwargs(monkeypatch, stream_text_encoders = True)
assert seen, "the applier never reached diffusers"
encoders = {name: kw for name, kw in seen.items() if str(name).startswith("text_encoder")}
denoisers = {name: kw for name, kw in seen.items() if not str(name).startswith("text_encoder")}
assert encoders and denoisers, seen
for name, kw in encoders.items():
assert kw["offload_type"] == "leaf_level", name
# leaf level has no blocks; diffusers only requires the count for block_level.
assert kw["num_blocks_per_group"] is None, name
for name, kw in denoisers.items():
assert kw["offload_type"] == "block_level", name
assert kw["num_blocks_per_group"] is not None, name
def test_the_batch_remedy_appears_only_when_a_batch_was_budgeted():
"""The guard budgets one image everywhere except a Windows multi-image chunk, so a refusal
almost always means one image does not fit. Telling that caller to use a smaller batch points
them at the one change that provably cannot alter the decision."""
single = _shortfall(1088, 1920)
assert single is not None
assert "smaller batch size" not in single
assert "smaller resolution," in single
batched = _shortfall(1024, 1024, batch_size = 4)
assert batched is not None
assert "at a batch of 4" in batched
assert "or a smaller batch size" in batched