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
181 lines
5.9 KiB
Python
181 lines
5.9 KiB
Python
"""CPU-only behavioral routing tests for the export API.
|
|
|
|
With the heavy save helpers monkeypatched, confirm each `save_method` / `quantization_method`
|
|
reaches the correct export path with the correct arguments. A bare object stands in for the
|
|
model, so these run on CPU-only CI with no GPU and no real weights, yet they catch routing
|
|
regressions that pure AST checks cannot (e.g. wrong scheme/suffix/outtype passed through).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
import unsloth.save as save_mod
|
|
|
|
|
|
class _FakeModel:
|
|
"""Minimal model stand-in; routing reads nothing meaningful off it before dispatch."""
|
|
|
|
config = type(
|
|
"cfg", (), {"_name_or_path": "fake/model", "architectures": ["LlamaForCausalLM"]}
|
|
)()
|
|
|
|
|
|
# -- merged_* -> compressed-tensors dispatch ---------------------------------------------
|
|
|
|
|
|
def test_merged_fp8_routes_to_compressed(monkeypatch, tmp_path):
|
|
seen = {}
|
|
monkeypatch.setattr(save_mod, "_unsloth_save_compressed_tensors", lambda **kw: seen.update(kw))
|
|
monkeypatch.setattr(save_mod, "unsloth_generic_save", lambda **kw: seen.update(generic = True))
|
|
save_mod.unsloth_generic_save_pretrained_merged(
|
|
_FakeModel(),
|
|
str(tmp_path),
|
|
tokenizer = object(),
|
|
save_method = "fp8",
|
|
)
|
|
assert seen.get("scheme") == "FP8_DYNAMIC"
|
|
assert seen.get("suffix") == "fp8"
|
|
assert seen.get("needs_calibration") is False
|
|
assert "generic" not in seen, "compressed save_method must not fall through to the plain merge"
|
|
|
|
|
|
def test_merged_nvfp4_marks_calibration(monkeypatch, tmp_path):
|
|
seen = {}
|
|
monkeypatch.setattr(save_mod, "_unsloth_save_compressed_tensors", lambda **kw: seen.update(kw))
|
|
monkeypatch.setattr(save_mod, "unsloth_generic_save", lambda **kw: None)
|
|
save_mod.unsloth_generic_save_pretrained_merged(
|
|
_FakeModel(),
|
|
str(tmp_path),
|
|
tokenizer = object(),
|
|
save_method = "nvfp4",
|
|
)
|
|
assert seen.get("scheme") == "NVFP4"
|
|
assert seen.get("needs_calibration") is True
|
|
|
|
|
|
def test_merged_16bit_does_not_route_compressed(monkeypatch, tmp_path):
|
|
calls = {"compressed": 0, "generic": 0}
|
|
monkeypatch.setattr(
|
|
save_mod,
|
|
"_unsloth_save_compressed_tensors",
|
|
lambda **kw: calls.__setitem__("compressed", calls["compressed"] + 1),
|
|
)
|
|
monkeypatch.setattr(
|
|
save_mod,
|
|
"unsloth_generic_save",
|
|
lambda **kw: calls.__setitem__("generic", calls["generic"] + 1),
|
|
)
|
|
save_mod.unsloth_generic_save_pretrained_merged(
|
|
_FakeModel(),
|
|
str(tmp_path),
|
|
tokenizer = object(),
|
|
save_method = "merged_16bit",
|
|
)
|
|
assert calls["compressed"] == 0, "merged_16bit must not hit the compressed export"
|
|
assert calls["generic"] == 1, "merged_16bit must go through the normal merge path"
|
|
|
|
|
|
# -- save_method='lora' -> LoRA GGUF dispatch --------------------------------------------
|
|
|
|
|
|
def test_gguf_lora_passes_valid_outtype(monkeypatch, tmp_path):
|
|
seen = {}
|
|
monkeypatch.setattr(
|
|
save_mod,
|
|
"_unsloth_save_lora_gguf",
|
|
lambda model, tok, sd, outtype = None: seen.update(outtype = outtype),
|
|
)
|
|
save_mod.unsloth_save_pretrained_gguf(
|
|
_FakeModel(),
|
|
str(tmp_path),
|
|
tokenizer = object(),
|
|
save_method = "lora",
|
|
quantization_method = "q8_0",
|
|
)
|
|
assert seen.get("outtype") == "q8_0"
|
|
|
|
|
|
def test_gguf_lora_invalid_outtype_falls_back_to_f16(monkeypatch, tmp_path):
|
|
seen = {}
|
|
monkeypatch.setattr(
|
|
save_mod,
|
|
"_unsloth_save_lora_gguf",
|
|
lambda model, tok, sd, outtype = None: seen.update(outtype = outtype),
|
|
)
|
|
save_mod.unsloth_save_pretrained_gguf(
|
|
_FakeModel(),
|
|
str(tmp_path),
|
|
tokenizer = object(),
|
|
save_method = "lora",
|
|
quantization_method = "q4_k_m",
|
|
)
|
|
assert (
|
|
seen.get("outtype") == "f16"
|
|
), "a GGUF model quant (q4_k_m) is not a valid LoRA outtype -> f16"
|
|
|
|
|
|
def test_gguf_lora_push_to_hub_is_rejected(tmp_path):
|
|
with pytest.raises(ValueError):
|
|
save_mod.unsloth_save_pretrained_gguf(
|
|
_FakeModel(),
|
|
"repo/id",
|
|
tokenizer = object(),
|
|
save_method = "lora",
|
|
push_to_hub = True,
|
|
)
|
|
|
|
|
|
# -- torchao PTQ / QAT dispatch ------------------------------------------------------------
|
|
|
|
|
|
def test_torchao_ptq_routes_to_given_config(monkeypatch, tmp_path):
|
|
seen = {}
|
|
monkeypatch.setattr(
|
|
save_mod, "_unsloth_save_torchao_with_given_config", lambda **kw: seen.update(given = True)
|
|
)
|
|
monkeypatch.setattr(
|
|
save_mod,
|
|
"_unsloth_save_torchao_with_attached_config",
|
|
lambda **kw: seen.update(attached = True),
|
|
)
|
|
save_mod.unsloth_save_pretrained_torchao(
|
|
_FakeModel(),
|
|
str(tmp_path),
|
|
tokenizer = object(),
|
|
torchao_config = object(),
|
|
)
|
|
assert seen.get("given") and not seen.get("attached")
|
|
|
|
|
|
def test_torchao_qat_routes_to_attached_config(monkeypatch, tmp_path):
|
|
seen = {}
|
|
monkeypatch.setattr(
|
|
save_mod, "_unsloth_save_torchao_with_given_config", lambda **kw: seen.update(given = True)
|
|
)
|
|
monkeypatch.setattr(
|
|
save_mod,
|
|
"_unsloth_save_torchao_with_attached_config",
|
|
lambda **kw: seen.update(attached = True),
|
|
)
|
|
model = _FakeModel()
|
|
model._torchao_config = object() # simulates a model trained with qat_scheme
|
|
save_mod.unsloth_save_pretrained_torchao(
|
|
model,
|
|
str(tmp_path),
|
|
tokenizer = object(),
|
|
torchao_config = None,
|
|
)
|
|
assert seen.get("attached") and not seen.get("given")
|
|
|
|
|
|
def test_torchao_requires_config_or_qat(tmp_path):
|
|
# No torchao_config and no attached QAT config is a user error, surfaced eagerly.
|
|
with pytest.raises(AssertionError):
|
|
save_mod.unsloth_save_pretrained_torchao(
|
|
_FakeModel(),
|
|
str(tmp_path),
|
|
tokenizer = object(),
|
|
torchao_config = None,
|
|
)
|