unsloth/studio/backend/tests/test_sd_cpp_h3_matrix.py
Long Yixing 22cb12ec65
Studio: validate legacy sd binary discovery (#8560)
* fix(studio): validate legacy sd discovery

* studio: separate sd.cpp identity from MiniMax-H3 capability

The legacy PATH probe answers "is this stable-diffusion.cpp". The H3 gate was answering
"does this build carry H3" and reporting the result as if it had answered the first, so an
unrelated binary reached through SD_CLI_PATH was described as a stable-diffusion.cpp build
predating MiniMax-H3 -- true of every program that is not stable-diffusion.cpp, and the
sentence that sent #8507 looking for a newer build of something never installed.

- share one predicate, help_text_identifies_sd_cpp, between the two callers
- the H3 gate takes ONE --help and derives both answers from it, asking identity first.
  --ref-video is a plain option name that unrelated reference-video tools also expose, so
  returning on the marker alone readmitted the same class of program through the override
- a user-supplied binary that is not stable-diffusion.cpp gets a message saying so, and the
  log lines on the managed path name the actual fault
- memoize the identity verdict per file revision (path + mtime + ctime + size). Discovery
  runs on every load and ensure_sd_cpp_binary resolves twice, so an unrelated "sd" was
  executed once per discovery. Only a verdict the probe produced is cached: a timeout
  leaves the file unchanged, so caching that "no" would blacklist a genuine build until
  Studio restarts

* studio: vet the MiniMax-H3 binary before downloading its bundle

ensure_h3_sd_cpp_binary exists to refuse a build that cannot run H3 before the four-file
bundle is fetched, and its own error said so. _run_load_h3_native called it after the
download loop, so every refusal still cost tens of GB, and a None return -- auto-install
off, an unsupported platform, no network, or a managed copy something else is running out
of -- was not rejected until later still.

- move the preflight and the accelerator fallback above the asset resolution
- check cancel_event before it: the preflight may install the prebuilt and takes no
  cancel_event, so a load cancelled before its worker started paid for an install nobody
  was waiting for. The download loop used to be the first check
- refuse a preflight that produced no binary there, rather than after the downloads. The
  claimed re-vet keeps its own check; that one guards a replacement arriving mid-download
- test_sd_cpp_h3_matrix.py covers platform x GPU vendor x binary state, because the
  combinations are what broke: a CPU-only prebuilt on a CUDA host reaches its refusal
  through the fallback, and that path downloaded the bundle on the way to failing

* studio: apply the repo kwarg-spacing format hook

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

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

* studio: assert the managed-path fault string, and correct which sources reach it

Mutation testing found the one production change in this branch that could be reverted
silently: forcing fault back to the H3 wording left every test green. That log line is
the same wrong diagnosis #8507 was reported as, written to the log instead of the user.

The comment above it also claimed only an env override reaches that branch. An in-tree
developer build and a PATH sd-cli do too, since the identity gate covers only the sd stem.

* studio: do not memoize an identity probe that learned nothing

A binary that cannot load a shared library exits 127 from the dynamic loader with nothing
identifying on either stream. That is a CompletedProcess, not an exception, so the previous
rule cached it as a definitive "not stable-diffusion.cpp" against a file that never
changed, and installing the missing library would not get it re-probed until restart.

Memoize a decisive verdict only: identifying output settles it whatever the exit code, and
otherwise only a clean exit is evidence of anything. The case this memo exists for is
unaffected, since Debian/Ubuntu's sd answers --help with rc 0.

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

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

* studio: close two windows the H3 preflight move opened

Both found in review of the move itself.

Re-check cancellation on the way out of the preflight. The ensure takes no cancel_event
and can spend minutes downloading and extracting the prebuilt, so a cancel arriving during
it is already late; a CPU or MPS target then skipped the claimed accelerator probe (the
only other cancel-aware step) and paid four sequential model_info round trips before the
download loop noticed.

Re-vet EVERY binary after the download, not only a managed one. The managed-only test was
written when the ensure sat right there, so a user-supplied build had been vetted
microseconds earlier and only a concurrent install could have moved underneath it. Vetting
before the download makes that window the whole download -- long enough to rebuild or
repoint an SD_CLI_PATH copy, after which the replacement became the recorded identity that
every later generation compares against.

* studio: re-vet identity too, and bound how long an identity verdict answers

Both from review of the previous round.

The post-download re-vet asked only the capability question. --ref-video is a plain option
name that unrelated reference-video tools expose too, so a swap to one of those cleared a
marker-only check and was then recorded as the vetted build. It now applies the same
identity-and-capability pair the preflight does, off one --help.

And the memo key is not a content revision on Windows: st_ctime there is the CREATION
time, which an in-place overwrite preserves, so a same-sized write that also restores mtime
leaves the whole tuple unchanged. Hashing the binary per lookup would trade the exec this
memo avoids for a full read on a path walked every load, so verdicts expire instead. That
bounds what the key cannot see on any platform, and a minute is still far longer than the
several resolutions inside one load.

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

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

* studio: re-check the accelerator for every binary, on a decisive reading

native_device is decided on the accelerator reading and then committed for the life of the
runtime, and moving that reading before the download made the gap between deciding and
committing the whole fetch. The re-check still only ran for a managed binary, so a user's
own build rebuilt inside that window committed a GPU device around a CPU executable --
offload policy and an arbiter claim written against hardware nothing is running on.

The comparison needs a decisive reading, which is why this adds the verdict form rather
than reusing sd_cpp_lists_accelerator_device: that one folds "could not tell" into True,
and against a recorded False it would refuse the very CPU fallback that recorded it.

* studio: only re-probe the device list where there is a baseline to compare it to

Hoisting the verdict out of the condition made every H3 load spawn --list-devices, including
the CPU and MPS targets that deliberately never record a baseline and so cannot use the
answer. On a build that hangs on that flag it cost the full probe timeout before readiness,
for a value the comparison then discarded.

---------

Co-authored-by: danielhanchen <danielhanchen@gmail.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-08-12 20:05:54 -07:00

432 lines
17 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
"""The MiniMax-H3 native preflight across the host matrix.
``_run_load_h3_native`` decides three things before it commits a runtime: which binary to run,
which device to commit it on, and -- since #8507 -- whether to spend the four-file download at all.
Those interact: the accelerator probe only runs on a GPU target, the CPU fallback rewrites both the
binary and the device, and every refusal has to happen before the bundle is fetched.
Parametrised over ``platform x GPU vendor x binary state`` because the combinations are what broke:
a CPU-only prebuilt on a CUDA host has its own fallback path, and a refusal reached through that
fallback used to download the bundle first on the way to failing.
"""
from __future__ import annotations
import threading
import types
from pathlib import Path
import pytest
from core.inference.video import VideoBackend, _detect_load_family
from core.inference.video_families import VIDEO_CANCELLED_MSG
H3_REPO = "leejet/MiniMax-H3-GGUF"
H3_FILE = "minimax_h3_fl2va-Q4_K_M.gguf"
_BANNER = "stable-diffusion.cpp version unknown, commit unknown\n"
_H3_HELP = _BANNER + " --ref-video MiniMax-H3 Ref2VA reference video frame directory\n"
_PRE_H3_HELP = _BANNER + " -M, --mode run mode, one of [img_gen, vid_gen, upscale]\n"
# Debian/Ubuntu's find-and-replace `sd`, and Homebrew ships the same tool on macOS.
_UNRELATED_HELP = "sd 1.0.0\nFind & replace CLI\n\nUSAGE:\n sd <find> <replace-with>\n"
# (label, DiffusionDeviceTarget.backend, .device)
HARDWARE = [
("nvidia", "cuda", "cuda"),
("amd", "rocm", "cuda"),
("apple", "mps", "mps"),
("cpu", "cpu", "cpu"),
]
# sys.platform values. WSL is a Linux platform string with a Windows kernel underneath, so it is
# the linux row -- listed separately because it is where PATH picks up a Windows-side install.
PLATFORMS = ["linux", "wsl", "darwin", "win32"]
class _PlanInfo:
def __init__(self, siblings) -> None:
self.siblings = siblings
class _Engine:
def __init__(self, binary) -> None:
self.binary = binary
def version(self):
return "stub-version"
@pytest.fixture
def h3_host(monkeypatch, tmp_path):
"""Drive `_run_load_h3_native` on a chosen host with a chosen binary, recording downloads."""
from core.inference import video as video_mod
from core.inference import sd_cpp_backend, sd_cpp_engine
def _setup(
*,
platform: str,
backend: str,
device: str,
help_text: str | None,
managed: bool = False,
lists_accelerator: bool = True,
):
monkeypatch.setattr(
sd_cpp_engine.sys, "platform", "linux" if platform == "wsl" else platform
)
monkeypatch.setattr(
video_mod,
"resolve_diffusion_device_target",
lambda: types.SimpleNamespace(backend = backend, device = device, dtype = None),
)
monkeypatch.setattr(sd_cpp_backend, "_install_allowed", lambda: True)
monkeypatch.setattr(sd_cpp_backend, "is_managed_binary", lambda _b: managed)
monkeypatch.setattr(sd_cpp_engine, "SdCppEngine", _Engine)
binary = None if help_text is None else "/opt/sd/sd-cli"
monkeypatch.setattr(sd_cpp_backend, "ensure_sd_cpp_binary", lambda **_kwargs: binary)
device_probes: list[str] = []
def _probe(_binary, *args):
if args == ("--list-devices",):
device_probes.append(_binary)
if lists_accelerator:
return "CUDA0\tNVIDIA H100 PCIe\nCPU\tIntel(R) Xeon(R)\n"
return "CPU\tIntel(R) Xeon(R)\n"
return help_text
monkeypatch.setattr(sd_cpp_backend, "_sd_cpp_probe_output", _probe)
asset_calls: list[str] = []
class _Api:
def __init__(self, **_kwargs):
pass
def model_info(self, repo, *_args, **_kwargs):
asset_calls.append(repo)
return _PlanInfo([])
monkeypatch.setattr("huggingface_hub.HfApi", _Api)
downloads: list[str] = []
def _download(_repo, wanted, *_args, **_kwargs):
downloads.append(wanted)
path = tmp_path / Path(wanted).name
path.write_bytes(b"x")
return str(path)
monkeypatch.setattr("utils.hf_xet_fallback.hf_hub_download_with_xet_fallback", _download)
def run(cancel_event: threading.Event | None = None):
fam = _detect_load_family(H3_REPO, None, "minimax-h3")
assert fam is not None
backend_obj = VideoBackend()
backend_obj._run_load_h3_native(
fam = fam,
token = None,
cancel_event = cancel_event or threading.Event(),
repo_id = H3_REPO,
gguf_filename = H3_FILE,
)
return backend_obj
return types.SimpleNamespace(
run = run,
downloads = downloads,
asset_calls = asset_calls,
device_probes = device_probes,
)
return _setup
@pytest.mark.parametrize("platform", PLATFORMS)
@pytest.mark.parametrize("hw_label,backend,device", HARDWARE)
@pytest.mark.parametrize(
"state,help_text,expected",
[
("unrelated_binary", _UNRELATED_HELP, "is not stable-diffusion.cpp"),
("pre_h3_binary", _PRE_H3_HELP, "does not advertise MiniMax-H3"),
("no_binary", None, "could not be installed or started"),
],
)
def test_h3_preflight_refuses_before_downloading(
h3_host, platform, hw_label, backend, device, state, help_text, expected
):
"""Every refusal, on every host, costs zero downloads.
The bundle is tens of GB. A refusal that arrives after it has been fetched is the failure mode
the H3 gate was written to prevent, and it was reachable on all of these hosts: the gate ran
after the download loop, and a None binary was not rejected until later still.
"""
host = h3_host(platform = platform, backend = backend, device = device, help_text = help_text)
with pytest.raises(RuntimeError, match = expected):
host.run()
assert host.downloads == []
@pytest.mark.parametrize("platform", PLATFORMS)
@pytest.mark.parametrize("hw_label,backend,device", HARDWARE)
def test_h3_preflight_admits_a_capable_build_and_downloads_once(
h3_host, platform, hw_label, backend, device
):
"""The other direction: a genuine H3 build is not refused anywhere, and the load proceeds to
fetch exactly the four files. Guards against an identity check that is too strict."""
host = h3_host(platform = platform, backend = backend, device = device, help_text = _H3_HELP)
backend_obj = host.run()
assert len(host.downloads) == 4
assert backend_obj._state is not None
# A GPU target keeps its device when the build offers an accelerator; CPU/MPS are unchanged.
assert backend_obj._state.device == device
@pytest.mark.parametrize("platform", PLATFORMS)
@pytest.mark.parametrize(
"hw_label,backend,device", [h for h in HARDWARE if h[1] in ("cuda", "rocm")]
)
def test_h3_gpu_host_falls_back_to_the_cpu_build(h3_host, platform, hw_label, backend, device):
"""Upstream publishes no Linux CUDA archive, so a GPU host routinely ends up on the CPU
prebuilt. It must still load, committed on the CPU rather than on a GPU it never ran on."""
host = h3_host(
platform = platform,
backend = backend,
device = device,
help_text = _H3_HELP,
lists_accelerator = False,
)
backend_obj = host.run()
assert len(host.downloads) == 4
assert backend_obj._state is not None
assert backend_obj._state.device == "cpu"
@pytest.mark.parametrize("platform", PLATFORMS)
@pytest.mark.parametrize("hw_label,backend,device", HARDWARE)
def test_h3_cancellation_during_the_preflight_stops_before_the_asset_calls(
h3_host, platform, hw_label, backend, device, monkeypatch
):
"""The ensure takes no cancel_event and can spend minutes installing the prebuilt, so a cancel
arriving during it is already late. It must not then cost four more model_info round trips."""
from core.inference import sd_cpp_backend
cancelled = threading.Event()
host = h3_host(platform = platform, backend = backend, device = device, help_text = _H3_HELP)
original = sd_cpp_backend.ensure_h3_sd_cpp_binary
def _cancel_midway(**kwargs):
cancelled.set() # the user hits cancel while the install is running
return original(**kwargs)
monkeypatch.setattr(sd_cpp_backend, "ensure_h3_sd_cpp_binary", _cancel_midway)
with pytest.raises(RuntimeError, match = VIDEO_CANCELLED_MSG):
host.run(cancel_event = cancelled)
assert host.downloads == []
# The point is not that it eventually raises -- the download loop always would. It is that a
# cancelled load stops before paying for the four sequential size-estimate round trips.
assert host.asset_calls == []
@pytest.mark.parametrize("platform", PLATFORMS)
@pytest.mark.parametrize("hw_label,backend,device", HARDWARE)
def test_h3_revets_a_user_supplied_binary_swapped_during_the_download(
h3_host, platform, hw_label, backend, device, monkeypatch
):
"""Vetting before the download means the vet-to-commit window is now the whole download.
A user-supplied binary is not ours to reinstall, so nothing else guards it: if the file at that
path changes while the bundle is fetched, the re-vet is the only thing standing between the
replacement and being recorded as the identity every later generation compares against."""
from core.inference import sd_cpp_backend
host = h3_host(platform = platform, backend = backend, device = device, help_text = _H3_HELP)
# The preflight sees an H3 build; by the time the download is done the path holds a pre-H3 one.
swapped = {"done": False}
real_probe = sd_cpp_backend._sd_cpp_probe_output
def _probe(binary, *args):
if args == ("--help",) and swapped["done"]:
return _PRE_H3_HELP
return real_probe(binary, *args)
def _download(*args, **kwargs):
swapped["done"] = True
return _real_download(*args, **kwargs)
_real_download = None
import utils.hf_xet_fallback as xet
_real_download = xet.hf_hub_download_with_xet_fallback
monkeypatch.setattr(sd_cpp_backend, "_sd_cpp_probe_output", _probe)
monkeypatch.setattr(xet, "hf_hub_download_with_xet_fallback", _download)
with pytest.raises(RuntimeError, match = "changed while this model was loading"):
host.run()
assert len(host.downloads) == 4 # the swap is caught after the fetch, not before it
@pytest.mark.parametrize("platform", PLATFORMS)
@pytest.mark.parametrize("hw_label,backend,device", HARDWARE)
def test_h3_revet_checks_identity_not_just_the_h3_marker(
h3_host, platform, hw_label, backend, device, monkeypatch
):
"""The post-download re-vet asks both of the preflight's questions, not only capability.
--ref-video is a plain option name that unrelated reference-video tools expose too, so a swap
to one of those would clear a marker-only re-check and then be recorded by _sd_cli_identity as
the vetted build every later generation compares against."""
from core.inference import sd_cpp_backend
host = h3_host(platform = platform, backend = backend, device = device, help_text = _H3_HELP)
swapped = {"done": False}
real_probe = sd_cpp_backend._sd_cpp_probe_output
def _probe(binary, *args):
# Not sd.cpp, but it does carry the H3 marker -- capability alone would wave it through.
if args == ("--help",) and swapped["done"]:
return "reference-video-cli 2.1\n --ref-video PATH reference clip\n"
return real_probe(binary, *args)
import utils.hf_xet_fallback as xet
real_download = xet.hf_hub_download_with_xet_fallback
def _download(*args, **kwargs):
swapped["done"] = True
return real_download(*args, **kwargs)
monkeypatch.setattr(sd_cpp_backend, "_sd_cpp_probe_output", _probe)
monkeypatch.setattr(xet, "hf_hub_download_with_xet_fallback", _download)
with pytest.raises(RuntimeError, match = "changed while this model was loading"):
host.run()
assert len(host.downloads) == 4
@pytest.mark.parametrize("platform", PLATFORMS)
@pytest.mark.parametrize(
"hw_label,backend,device", [h for h in HARDWARE if h[1] in ("cuda", "rocm")]
)
def test_h3_revet_catches_a_user_binary_whose_accelerator_changed(
h3_host, platform, hw_label, backend, device, monkeypatch
):
"""native_device is decided on the accelerator reading and then committed for the life of the
runtime, so the reading has to still hold at commit time -- for a user's own build too, not
only a managed one. A GPU device committed around a CPU binary means offload policy and an
arbiter claim written against hardware nothing is running on."""
from core.inference import sd_cpp_backend
host = h3_host(platform = platform, backend = backend, device = device, help_text = _H3_HELP)
swapped = {"done": False}
real_probe = sd_cpp_backend._sd_cpp_probe_output
def _probe(binary, *args):
# Still an H3-capable sd.cpp, so identity and capability both pass; only the device list
# changed, which is exactly the case the H3 re-vet cannot see.
if args == ("--list-devices",) and swapped["done"]:
return "CPU\tIntel(R) Xeon(R)\n"
return real_probe(binary, *args)
import utils.hf_xet_fallback as xet
real_download = xet.hf_hub_download_with_xet_fallback
def _download(*args, **kwargs):
swapped["done"] = True
return real_download(*args, **kwargs)
monkeypatch.setattr(sd_cpp_backend, "_sd_cpp_probe_output", _probe)
monkeypatch.setattr(xet, "hf_hub_download_with_xet_fallback", _download)
with pytest.raises(RuntimeError, match = "built for a different accelerator"):
host.run()
@pytest.mark.parametrize("platform", PLATFORMS)
@pytest.mark.parametrize(
"hw_label,backend,device", [h for h in HARDWARE if h[1] in ("cuda", "rocm")]
)
def test_h3_revet_tolerates_an_unreadable_accelerator_reprobe(
h3_host, platform, hw_label, backend, device, monkeypatch
):
""" "Could not tell" is not "it changed". sd_cpp_lists_accelerator_device folds an unreadable
probe into True, so comparing THAT against a recorded False would refuse the CPU fallback that
recorded it -- the load must proceed on the reading it already has."""
from core.inference import sd_cpp_backend
host = h3_host(platform = platform, backend = backend, device = device, help_text = _H3_HELP)
swapped = {"done": False}
real_probe = sd_cpp_backend._sd_cpp_probe_output
def _probe(binary, *args):
if args == ("--list-devices",) and swapped["done"]:
return None # older build that rejects the flag, or an unreadable probe
return real_probe(binary, *args)
import utils.hf_xet_fallback as xet
real_download = xet.hf_hub_download_with_xet_fallback
def _download(*args, **kwargs):
swapped["done"] = True
return real_download(*args, **kwargs)
monkeypatch.setattr(sd_cpp_backend, "_sd_cpp_probe_output", _probe)
monkeypatch.setattr(xet, "hf_hub_download_with_xet_fallback", _download)
backend_obj = host.run()
assert backend_obj._state is not None
assert backend_obj._state.device == device
@pytest.mark.parametrize("platform", PLATFORMS)
@pytest.mark.parametrize("hw_label,backend,device", HARDWARE)
def test_h3_probes_devices_only_where_the_answer_is_used(
h3_host, platform, hw_label, backend, device
):
"""--list-devices costs a subprocess, and the full probe timeout when a build hangs on it.
A CPU or MPS target never records a baseline, so neither the decision nor the re-check can use
the answer -- it must not be asked. A GPU target asks exactly twice: once to decide, once under
the claim to confirm the decision still holds."""
host = h3_host(platform = platform, backend = backend, device = device, help_text = _H3_HELP)
host.run()
expected = 2 if backend not in ("cpu", "mps") else 0
assert len(host.device_probes) == expected
@pytest.mark.parametrize("platform", PLATFORMS)
@pytest.mark.parametrize("hw_label,backend,device", HARDWARE)
def test_h3_cancellation_precedes_the_binary_install(h3_host, platform, hw_label, backend, device):
"""The preflight can download and extract the sd-cli prebuilt and takes no cancel_event, so a
load cancelled before its worker started must not reach it."""
from core.inference import sd_cpp_backend
host = h3_host(platform = platform, backend = backend, device = device, help_text = _H3_HELP)
ensures: list[str] = []
original = sd_cpp_backend.ensure_h3_sd_cpp_binary
def _spy(**kwargs):
ensures.append("called")
return original(**kwargs)
sd_cpp_backend.ensure_h3_sd_cpp_binary = _spy
try:
cancelled = threading.Event()
cancelled.set()
with pytest.raises(RuntimeError):
host.run(cancel_event = cancelled)
finally:
sd_cpp_backend.ensure_h3_sd_cpp_binary = original
assert ensures == []
assert host.downloads == []