thr3shr / backend /tests /test_inference_engine.py
Dinamush
feat: add GPU model unload and improve classify cancel
b123708
Raw
History Blame Contribute Delete
5.32 kB
from __future__ import annotations
from pathlib import Path
import numpy as np
import pytest
from PIL import Image
def _make_sfw_fixture(path: Path, size: tuple[int, int] = (512, 640)) -> Path:
"""Solid + soft blob — deterministic, non-explicit test image."""
img = Image.new("RGB", size, (210, 190, 230))
for x in range(180, 330):
for y in range(140, 360):
img.putpixel((x, y), (240, 210, 200))
path.parent.mkdir(parents=True, exist_ok=True)
img.save(path)
return path
def test_wd_preprocess_matches_imgutils(tmp_path: Path) -> None:
from imgutils.tagging.wd14 import _prepare_image_for_tagging
from app.inference_engine import preprocess_wd14
image_path = _make_sfw_fixture(tmp_path / "wd.png")
expected = _prepare_image_for_tagging(str(image_path), 448)
actual = preprocess_wd14(image_path, 448)
assert actual.shape == expected.shape
np.testing.assert_allclose(actual, expected, rtol=0, atol=1e-5)
def test_ml_preprocess_matches_imgutils(tmp_path: Path) -> None:
from imgutils.data import load_image
from imgutils.tagging.mldanbooru import _resize_align, _to_tensor
from app.inference_engine import preprocess_mldanbooru
image_path = _make_sfw_fixture(tmp_path / "ml.png", size=(640, 480))
pil = load_image(str(image_path), mode="RGB")
expected = _to_tensor(_resize_align(pil, 448, True))[None, ...]
actual = preprocess_mldanbooru(image_path, size=448, keep_ratio=True)
assert actual.shape == expected.shape
np.testing.assert_allclose(actual, expected, rtol=0, atol=1e-5)
def test_force_cpu_providers(monkeypatch: pytest.MonkeyPatch) -> None:
from app import inference_engine as eng
monkeypatch.setenv("FORCE_CPU_INFERENCE", "true")
providers = eng.ort_providers()
assert providers == ["CPUExecutionProvider"]
monkeypatch.setenv("FORCE_CPU_INFERENCE", "false")
providers = eng.ort_providers()
assert providers[0] in {"CUDAExecutionProvider", "CPUExecutionProvider"}
assert "CPUExecutionProvider" in providers
@pytest.mark.live_onnx
def test_engine_scores_close_to_imgutils_wd(tmp_path: Path) -> None:
from imgutils.tagging import get_wd14_tags
from app.inference_engine import InferenceEngine
from app.services import _normalize_score_tags, _parse_wd14_raw
image_path = _make_sfw_fixture(tmp_path / "live_wd.png")
raw = get_wd14_tags(
str(image_path),
model_name="SwinV2_v3",
general_threshold=0.35,
no_underline=False,
drop_overlap=False,
fmt="general",
)
expected = _parse_wd14_raw(raw)
engine = InferenceEngine()
engine.warm("wd_swinv2_v3")
actual = engine.score_one(
image_path,
tagger_model="wd_swinv2_v3",
wd_general_threshold=0.35,
)
assert set(actual) == set(expected)
for tag, score in expected.items():
# GPU/ORT nondeterminism can exceed 1e-4; tagging decisions use ~1e-3.
assert abs(actual[tag] - score) < 1e-3, tag
@pytest.mark.live_onnx
def test_engine_scores_close_to_imgutils_ml(tmp_path: Path) -> None:
from imgutils.tagging import get_mldanbooru_tags
from app.inference_engine import InferenceEngine
from app.services import _normalize_score_tags, _parse_mldanbooru_raw
image_path = _make_sfw_fixture(tmp_path / "live_ml.png")
raw = get_mldanbooru_tags(
str(image_path),
threshold=0.0,
size=448,
keep_ratio=True,
drop_overlap=False,
use_real_name=False,
)
expected = _normalize_score_tags(_parse_mldanbooru_raw(raw))
engine = InferenceEngine()
engine.warm("ml_danbooru")
actual = engine.score_one(
image_path,
tagger_model="ml_danbooru",
wd_general_threshold=0.35,
)
assert set(actual) == set(expected)
for tag, score in expected.items():
assert abs(actual[tag] - score) < 1e-3, tag
@pytest.mark.live_onnx
def test_wd_batch_matches_single(tmp_path: Path) -> None:
from app.inference_engine import InferenceEngine
paths = [
_make_sfw_fixture(tmp_path / "a.png", (448, 448)),
_make_sfw_fixture(tmp_path / "b.png", (512, 384)),
_make_sfw_fixture(tmp_path / "c.png", (600, 600)),
]
engine = InferenceEngine()
engine.warm("wd_swinv2_v3")
singles = [
engine.score_one(p, tagger_model="wd_swinv2_v3", wd_general_threshold=0.35)
for p in paths
]
batched = engine.score_many(
paths,
tagger_model="wd_swinv2_v3",
wd_general_threshold=0.35,
batch_size=3,
)
assert len(batched) == 3
for single, batch in zip(singles, batched):
assert set(single) == set(batch)
for tag, score in single.items():
# Batched CUDA runs can differ slightly from N=1 (cuDNN algorithms).
assert abs(batch[tag] - score) < 1e-3, tag
def test_engine_clear_drops_cached_sessions() -> None:
from app.inference_engine import InferenceEngine
engine = InferenceEngine()
engine._sessions["wd_swinv2_v3"] = object()
engine._sessions["ml_danbooru"] = object()
assert engine.loaded_models() == ["ml_danbooru", "wd_swinv2_v3"]
assert engine.clear() == ["ml_danbooru", "wd_swinv2_v3"]
assert engine.loaded_models() == []