Studio diffusion (Phase 4): native stable-diffusion.cpp engine for CPU/Mac

Adds the CPU / Apple-Silicon tier of the two-engine strategy, mirroring the
chat backend's llama.cpp shell-out. Diffusers stays the default on CUDA / ROCm
/ XPU; this covers the hardware diffusers serves poorly, consuming the same
split GGUF assets Studio already curates.

- sd_cpp_args.py: pure sd-cli command builder. Maps the family to its
  text-encoder flag (Z-Image Qwen3 to --llm, Qwen-Image to --qwen2vl, FLUX.1
  CLIP-L + T5), and the diffusers memory policy (none/group/model/sequential)
  to sd.cpp's offload flags (--offload-to-cpu / --clip-on-cpu / --vae-on-cpu /
  --vae-tiling / --diffusion-fa), so one user knob drives both engines.
- sd_cpp_engine.py: SdCppEngine over a located sd-cli. find_sd_cpp_binary()
  with the same precedence as the llama finder (env override, then the Studio
  install root, then in-tree, then PATH), an is_available/version probe, and a
  one-shot subprocess generate that streams progress and returns the PNG.
  runtime_env() prepends the binary's directory to the platform library path
  so a prebuilt's bundled libstable-diffusion.so resolves.
  select_diffusion_engine() is the pure routing decision (GPU backends to
  diffusers, CPU/MPS to native when present).
- install_sd_cpp_prebuilt.py: resolve + download the per-host prebuilt
  (macOS-arm64/Metal, Linux x86_64 CPU, Vulkan/ROCm/Windows variants) into the
  Studio install root. resolve_release_asset() is a pure, unit-tested
  host-to-asset matrix.
- scripts/sd_cpp_smoke.py: end-to-end native generation harness.

Tests (CPU-only, subprocess/filesystem stubbed): 49 new across args, engine,
routing, runtime env, and the installer resolver. Full diffusion suite 166
passing.

Verified on a B200 box: built sd-cli (CUDA) and the prebuilt (CPU) both
generate Z-Image-Turbo Q4_K end to end through SdCppEngine: balanced (group
offload, 5.0s gen), low_vram (full CPU offload + VAE tiling, 13.4s), and the
dynamically-linked CPU prebuilt (50.4s on CPU), all producing coherent images.
This commit is contained in:
Daniel Han 2026-06-25 15:49:39 +00:00
commit a7b8f825da
7 changed files with 1298 additions and 0 deletions

View file

@ -0,0 +1,156 @@
# 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 sd-cli command builder (``sd_cpp_args.py``).
Pure: no torch, no subprocess, no files. Just argv construction and the
policy -> offload-flag / family -> text-encoder-flag mappings.
"""
from __future__ import annotations
import pytest
from core.inference.diffusion_memory import (
OFFLOAD_GROUP,
OFFLOAD_MODEL,
OFFLOAD_NONE,
OFFLOAD_SEQUENTIAL,
)
from core.inference.sd_cpp_args import (
SdCppGenParams,
SdCppModelFiles,
build_sd_cpp_command,
offload_flags,
text_encoder_flags_for_family,
)
def _pair(cmd: list[str], flag: str):
"""Value following ``flag`` in ``cmd``, or None if the flag is absent."""
return cmd[cmd.index(flag) + 1] if flag in cmd else None
# ── family text-encoder wiring ──────────────────────────────────────────────
def test_te_flags_by_family():
assert text_encoder_flags_for_family("z-image") == ("--llm",)
assert text_encoder_flags_for_family("qwen-image") == ("--qwen2vl",)
assert text_encoder_flags_for_family("flux.1") == ("--clip_l", "--t5xxl")
assert text_encoder_flags_for_family("flux.2-klein") == ("--llm",)
assert text_encoder_flags_for_family("unknown") == ()
# ── offload policy -> sd-cli flags ──────────────────────────────────────────
def test_offload_none_is_empty():
assert offload_flags(OFFLOAD_NONE) == []
def test_offload_group_streams_with_flash_attention():
flags = offload_flags(OFFLOAD_GROUP)
assert "--offload-to-cpu" in flags
assert "--diffusion-fa" in flags
# group keeps CLIP/VAE resident -> no per-component cpu flags
assert "--clip-on-cpu" not in flags
assert "--vae-on-cpu" not in flags
def test_offload_model_pushes_everything_to_cpu_and_tiles():
flags = offload_flags(OFFLOAD_MODEL)
for expected in ("--offload-to-cpu", "--clip-on-cpu", "--vae-on-cpu", "--vae-tiling", "--diffusion-fa"):
assert expected in flags
# sequential maps the same as model
assert offload_flags(OFFLOAD_SEQUENTIAL) == flags
def test_offload_forced_flags_dedup():
# vae_tiling/diffusion_fa forced on with a policy that already sets them
flags = offload_flags(OFFLOAD_MODEL, vae_tiling = True, diffusion_fa = True)
assert flags.count("--vae-tiling") == 1
assert flags.count("--diffusion-fa") == 1
# forced on top of a no-offload policy
none_forced = offload_flags(OFFLOAD_NONE, vae_tiling = True, diffusion_fa = True)
assert none_forced == ["--diffusion-fa", "--vae-tiling"]
# ── full command construction ───────────────────────────────────────────────
def test_build_zimage_command_minimal():
files = SdCppModelFiles(
diffusion_model = "/m/z.gguf", vae = "/m/ae.sft", llm = "/m/qwen3.gguf",
)
params = SdCppGenParams(prompt = "a cat", width = 512, height = 768, steps = 8, cfg_scale = 1.0, seed = 42)
cmd = build_sd_cpp_command("/bin/sd-cli", files, params, output_path = "/out/x.png")
assert cmd[0] == "/bin/sd-cli"
assert _pair(cmd, "--mode") == "img_gen"
assert _pair(cmd, "--diffusion-model") == "/m/z.gguf"
assert _pair(cmd, "--vae") == "/m/ae.sft"
assert _pair(cmd, "--llm") == "/m/qwen3.gguf"
assert _pair(cmd, "--prompt") == "a cat"
assert _pair(cmd, "--width") == "512"
assert _pair(cmd, "--height") == "768"
assert _pair(cmd, "--steps") == "8"
assert _pair(cmd, "--cfg-scale") == "1" # 1.0 -> "1"
assert _pair(cmd, "--seed") == "42"
assert _pair(cmd, "--output") == "/out/x.png"
# unset encoders are omitted
assert "--t5xxl" not in cmd
assert "--qwen2vl" not in cmd
def test_build_flux1_dual_text_encoders():
files = SdCppModelFiles(
diffusion_model = "/m/flux.gguf", vae = "/m/ae.sft",
clip_l = "/m/clip_l.sft", t5xxl = "/m/t5.gguf",
)
params = SdCppGenParams(prompt = "x", guidance = 3.5)
cmd = build_sd_cpp_command("/bin/sd-cli", files, params, output_path = "/o.png")
assert _pair(cmd, "--clip_l") == "/m/clip_l.sft"
assert _pair(cmd, "--t5xxl") == "/m/t5.gguf"
assert _pair(cmd, "--guidance") == "3.5"
assert "--llm" not in cmd
def test_build_appends_offload_and_extra_args_last():
files = SdCppModelFiles(diffusion_model = "/m/z.gguf")
params = SdCppGenParams(prompt = "x")
off = offload_flags(OFFLOAD_GROUP)
cmd = build_sd_cpp_command(
"/bin/sd-cli", files, params, output_path = "/o.png",
offload = off, threads = 8, verbose = True, extra_args = ["--rng", "cuda"],
)
assert "--offload-to-cpu" in cmd
assert _pair(cmd, "--threads") == "8"
assert "-v" in cmd
# extra args come after everything Studio set (last-wins for power users)
assert cmd[-2:] == ["--rng", "cuda"]
def test_build_negative_prompt_and_batch():
files = SdCppModelFiles(diffusion_model = "/m/z.gguf")
params = SdCppGenParams(prompt = "x", negative_prompt = "blurry", batch_count = 3)
cmd = build_sd_cpp_command("/bin/sd-cli", files, params, output_path = "/o.png")
assert _pair(cmd, "--negative-prompt") == "blurry"
assert _pair(cmd, "--batch-count") == "3"
def test_build_omits_unset_optional_params():
files = SdCppModelFiles(diffusion_model = "/m/z.gguf")
params = SdCppGenParams(prompt = "x") # no steps/cfg/seed/sampler
cmd = build_sd_cpp_command("/bin/sd-cli", files, params, output_path = "/o.png")
for flag in ("--steps", "--cfg-scale", "--guidance", "--seed", "--sampling-method", "--batch-count", "--threads", "-v"):
assert flag not in cmd
def test_build_requires_diffusion_model_and_prompt():
with pytest.raises(ValueError):
build_sd_cpp_command("/bin/sd-cli", SdCppModelFiles(diffusion_model = ""),
SdCppGenParams(prompt = "x"), output_path = "/o.png")
with pytest.raises(ValueError):
build_sd_cpp_command("/bin/sd-cli", SdCppModelFiles(diffusion_model = "/m/z.gguf"),
SdCppGenParams(prompt = " "), output_path = "/o.png")