e93507a09c
Lockfile supply-chain audit / lockfile supply-chain audit (push) Has been cancelled
Windows Studio GGUF CI / GPU prebuilt resolves without Visual Studio (push) Has been cancelled
Windows Studio GGUF CI / setup.ps1 unit tests (VS 2026 / CMake guard) (push) Has been cancelled
Windows Studio GGUF CI / real-VS detection (VS 2022) (push) Has been cancelled
Windows Studio GGUF CI / real-VS detection (VS 2026) (push) Has been cancelled
Windows Studio GGUF CI / VC++ runtime detect + install round-trip (windows-2025-vs2026) (push) Has been cancelled
Windows Studio GGUF CI / VC++ runtime detect + install round-trip (windows-latest) (push) Has been cancelled
Windows Studio Update CI / Studio Updating Tests (push) Has been cancelled
Wheel CI / Wheel build + content sanity + import smoke (push) Has been cancelled
Lint CI / Source lint (Python + shell + YAML + JSON + safety nets) (push) Has been cancelled
MLX CI on Mac M1 / dispatch (push) Has been cancelled
Security audit / advisory audit (pip + npm + cargo) (push) Has been cancelled
Security audit / pip scan-packages :: extras (push) Has been cancelled
Security audit / pip scan-packages :: studio (push) Has been cancelled
Security audit / pip scan-packages :: hf-stack (push) Has been cancelled
Security audit / npm scan-packages (Studio frontend tarballs) (push) Has been cancelled
Security audit / workflow-trigger lint (pull_request_target / cache-poisoning) (push) Has been cancelled
Security audit / pytest tests/security (push) Has been cancelled
Security audit / npm provenance + new install-script diff (push) Has been cancelled
Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Backend CI / (Python 3.10) (push) Has been cancelled
Backend CI / (Python 3.11) (push) Has been cancelled
Backend CI / (Python 3.12) (push) Has been cancelled
Backend CI / (Python 3.13) (push) Has been cancelled
Backend CI / Repo tests (CPU) (push) Has been cancelled
Frontend CI / Frontend build + bundle sanity (push) Has been cancelled
Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Studio GGUF CI / JSON, images (push) Has been cancelled
Mac Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Mac Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Mac Studio GGUF CI / JSON, images (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-14) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-15) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-26) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-15-intel) (push) Has been cancelled
Mac Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-26-intel) (push) Has been cancelled
Mac Studio UI CI / Chat UI Tests (push) Has been cancelled
Studio Tauri CI / Tauri Linux debug build (no codesign) (push) Has been cancelled
Mac Studio Update CI / Studio Updating Tests (push) Has been cancelled
Studio UI CI / Chat UI Tests (push) Has been cancelled
Windows Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Windows Studio UI CI / Chat UI Tests (push) Has been cancelled
Studio Update CI / Studio Updating Tests (push) Has been cancelled
Core / Core (HF=default + TRL=default) (push) Has been cancelled
Core / Core (HF=4.57.6 + TRL<1) (push) Has been cancelled
Core / Core (HF=latest + TRL=latest) (push) Has been cancelled
Core / llama.cpp build + smoke (push) Has been cancelled
Windows Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Windows Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Windows Studio GGUF CI / JSON, images (push) Has been cancelled
Windows Studio GGUF CI / Studio install + inference without Visual Studio (push) Has been cancelled
Studio export capability / capability (macos-latest) (push) Has been cancelled
Studio export capability / capability (ubuntu-latest) (push) Has been cancelled
Studio export capability / capability (windows-latest) (push) Has been cancelled
Cross-platform parity / parity (macos-latest) (push) Has been cancelled
Cross-platform parity / parity (windows-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
Studio load-orchestrator CI / test (push) Has been cancelled
184 lines
7.6 KiB
Python
184 lines
7.6 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
|
|
|
|
"""Tests for LlamaCppBackend._classify_llama_start_failure.
|
|
|
|
When llama-server exits before becoming healthy, load_model turns its
|
|
captured stdout/stderr into a user-facing reason. A diffusion/image GGUF
|
|
(FLUX, Qwen-Image, ...) is a valid file with plenty of memory, so the
|
|
generic "invalid file or out of memory" message is misleading (issue
|
|
#5842). These tests pin the classification.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
import types as _types
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
_BACKEND_DIR = str(Path(__file__).resolve().parent.parent)
|
|
if _BACKEND_DIR not in sys.path:
|
|
sys.path.insert(0, _BACKEND_DIR)
|
|
|
|
# Match sibling tests' stubbing so the module imports in a lightweight
|
|
# env without fastapi.
|
|
_loggers_stub = _types.ModuleType("loggers")
|
|
_loggers_stub.get_logger = lambda name: __import__("logging").getLogger(name)
|
|
sys.modules.setdefault("loggers", _loggers_stub)
|
|
# Give the structlog stub a real get_logger: a bare ModuleType poisons
|
|
# sys.modules for later tests that call structlog.get_logger at import time.
|
|
_structlog_stub = _types.ModuleType("structlog")
|
|
_structlog_stub.get_logger = lambda *a, **k: __import__("logging").getLogger("structlog")
|
|
sys.modules.setdefault("structlog", _structlog_stub)
|
|
if not hasattr(sys.modules["structlog"], "get_logger"):
|
|
sys.modules["structlog"].get_logger = _structlog_stub.get_logger
|
|
|
|
from core.inference.llama_cpp import LlamaCppBackend # noqa: E402
|
|
|
|
_classify = LlamaCppBackend._classify_llama_start_failure
|
|
|
|
# Real llama-server failure lines (lower-cased downstream anyway).
|
|
_QWEN_IMAGE_OUT = (
|
|
"load_model: loading model 'qwen-image-edit-2511-Q4_K_M.gguf'\n"
|
|
"llama_model_load: error loading model: unknown model architecture: 'qwen_image'\n"
|
|
"llama_model_load_from_file_impl: failed to load model"
|
|
)
|
|
_OOM_OUT = (
|
|
"ggml_backend_cuda_buffer_type_alloc_buffer: allocating 12000.00 MiB on "
|
|
"device 0: cudaMalloc failed: out of memory"
|
|
)
|
|
|
|
|
|
class TestDiffusionArchitectures:
|
|
def test_qwen_image_routes_to_images_page(self):
|
|
msg = _classify(_QWEN_IMAGE_OUT, "/models/qwen-image.gguf", "local/qwen-image")
|
|
assert "diffusion" in msg.lower()
|
|
assert "Images page" in msg
|
|
assert "qwen_image" in msg
|
|
# Must NOT keep blaming memory / file validity.
|
|
assert "out of memory" not in msg.lower()
|
|
assert "enough memory" not in msg.lower()
|
|
|
|
# Parametrize over the production set so new arches are auto-covered.
|
|
@pytest.mark.parametrize("arch", sorted(LlamaCppBackend._DIFFUSION_ARCHES))
|
|
def test_every_diffusion_arch_is_recognised(self, arch):
|
|
out = f"error loading model: unknown model architecture: '{arch}'"
|
|
msg = _classify(out, f"/models/{arch}.gguf", f"local/{arch}")
|
|
assert "diffusion" in msg.lower()
|
|
assert "Images page" in msg
|
|
assert arch in msg
|
|
|
|
|
|
class TestUnsupportedNonDiffusionArchitecture:
|
|
def test_unknown_llm_arch_says_unsupported_not_oom(self):
|
|
out = "error loading model: unknown model architecture: 'some_new_llm'"
|
|
msg = _classify(out, "/models/x.gguf", "local/x")
|
|
assert "some_new_llm" in msg
|
|
assert "architecture" in msg.lower()
|
|
# Specific, not the misleading memory message.
|
|
assert "enough memory" not in msg.lower()
|
|
assert "diffusion" not in msg.lower()
|
|
|
|
# Exact match: a chat arch merely containing a diffusion token (wan,
|
|
# sd1, flux, ...) must not be routed to the Images page.
|
|
@pytest.mark.parametrize(
|
|
"arch",
|
|
[
|
|
"taiwan", # contains "wan"
|
|
"swan_llm", # contains "wan"
|
|
"fluxion", # contains "flux"
|
|
"sd1234", # contains "sd1"
|
|
"sd3_chat", # contains "sd3"
|
|
"aura2_text", # contains "aura"
|
|
"cosmos_reason", # contains "cosmos"
|
|
"qwen_image_text", # contains "qwen_image"
|
|
],
|
|
)
|
|
def test_arch_containing_diffusion_token_is_not_misrouted(self, arch):
|
|
out = f"error loading model: unknown model architecture: '{arch}'"
|
|
msg = _classify(out, f"/models/{arch}.gguf", f"local/{arch}")
|
|
assert arch in msg
|
|
assert "does not support" in msg.lower()
|
|
assert "diffusion" not in msg.lower()
|
|
assert "Images page" not in msg
|
|
|
|
|
|
class TestOllamaAndFallback:
|
|
_OLLAMA_GGUF = (
|
|
f"/home/u/.ollama{__import__('os').sep}ollama_links" f"{__import__('os').sep}m.gguf"
|
|
)
|
|
|
|
def test_ollama_compat_message_still_works(self):
|
|
out = "llama_model_load: error loading model: key not found"
|
|
msg = _classify(out, self._OLLAMA_GGUF, "ollama/llama3")
|
|
assert "Ollama" in msg
|
|
|
|
def test_ollama_unknown_arch_keeps_ollama_guidance(self):
|
|
# Ollama + non-diffusion unknown arch keeps the Ollama hint, not the
|
|
# generic llama.cpp "unsupported" message.
|
|
out = "error loading model: unknown model architecture: 'some_new_llm'"
|
|
msg = _classify(out, self._OLLAMA_GGUF, "ollama/some-new")
|
|
assert "Ollama" in msg
|
|
assert "directly through Ollama" in msg
|
|
assert "does not support" not in msg.lower()
|
|
|
|
def test_ollama_diffusion_arch_still_routes_to_images(self):
|
|
# Diffusion routing wins over the Ollama hint.
|
|
out = "error loading model: unknown model architecture: 'flux'"
|
|
msg = _classify(out, self._OLLAMA_GGUF, "ollama/flux")
|
|
assert "diffusion" in msg.lower()
|
|
assert "Images page" in msg
|
|
|
|
def test_generic_oom_keeps_memory_message(self):
|
|
msg = _classify(_OOM_OUT, "/models/big.gguf", "local/big")
|
|
assert "enough memory" in msg.lower()
|
|
assert "diffusion" not in msg.lower()
|
|
|
|
def test_empty_output_is_safe(self):
|
|
msg = _classify("", None, None)
|
|
assert "llama-server failed to start" in msg
|
|
|
|
def test_health_timeout_names_probe_not_generic(self):
|
|
# A live server that never returns 200 on /health must name the probe and
|
|
# proxy/context causes, not blame a bad GGUF (#5740).
|
|
msg = _classify(
|
|
"llama-server health check timed out after 600.0s", "/models/x.gguf", "local/x"
|
|
)
|
|
assert "/health" in msg
|
|
assert "NO_PROXY" in msg
|
|
assert "GGUF file is valid" not in msg
|
|
|
|
|
|
class TestOsKillReturncode:
|
|
"""SIGKILL (-9) with no diagnostic output is the OOM killer and gets a named,
|
|
actionable message; SIGTERM (-15) is also unload/cancel/supervisor stop, so it
|
|
stays neutral; a recognized output still wins; a hard fault (-11) keeps the
|
|
generic fallback."""
|
|
|
|
def test_sigkill_with_no_output_names_oom(self):
|
|
msg = _classify("", "/models/big-bf16.gguf", "local/big", -9)
|
|
assert "signal 9" in msg
|
|
assert "out of memory" in msg.lower()
|
|
assert ".wslconfig" in msg
|
|
assert "GGUF file is valid" not in msg
|
|
|
|
def test_sigterm_is_neutral_not_oom(self):
|
|
msg = _classify("", "/models/big-bf16.gguf", "local/big", -15)
|
|
assert "signal 15" in msg
|
|
assert "terminated" in msg.lower()
|
|
assert "out of memory" not in msg.lower()
|
|
|
|
def test_specific_output_wins_over_os_kill_code(self):
|
|
msg = _classify(_QWEN_IMAGE_OUT, "/models/qwen-image.gguf", "local/qwen-image", -9)
|
|
assert "diffusion" in msg.lower()
|
|
assert "out of memory" not in msg.lower()
|
|
|
|
def test_signal_crash_code_keeps_generic_message(self):
|
|
# -11 is handled by the retry ladder; if it reaches here with no output
|
|
# it gets the generic fallback, not the OOM message.
|
|
msg = _classify("", "/models/x.gguf", "local/x", -11)
|
|
assert "GGUF file is valid" in msg
|
|
assert "out of memory" not in msg.lower()
|