Files
Artificial-Sweetener-Simple…/tests/test_quant_cache.py
T

319 lines
11 KiB
Python

# SimpleSyrup - workflow-focused ComfyUI extensions for image generation
# Copyright (C) 2026 Artificial Sweetener and contributors
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Tests for readable quant cache persistence, leases, and global LRU policy."""
from __future__ import annotations
import hashlib
import json
import time
from dataclasses import replace
from pathlib import Path
import pytest
from simple_syrup.domain.anima_quantization import (
MXFP8_PROFILE,
NVFP4_MIXED_PROFILE,
AnimaQuantizationRecipe,
)
from simple_syrup.domain.quant_cache import (
QuantCacheIdentity,
SourceCheckpointIdentity,
)
from simple_syrup.runtime import quant_cache_repository as repository_module
from simple_syrup.runtime.quant_cache_leases import QuantCacheLeaseRegistry
from simple_syrup.runtime.quant_cache_repository import (
MANIFEST_FILENAME,
README_FILENAME,
QuantCacheArtifact,
QuantCacheRepository,
)
from simple_syrup.services.quant_cache_service import QuantCacheService
def test_repository_creates_a_readable_global_cache_layout(tmp_path: Path) -> None:
"""Users browsing models/SyrupQuants can identify ownership and artifacts."""
repository = QuantCacheRepository(tmp_path / "models" / "SyrupQuants")
identity = _identity(tmp_path, "Anima/base model.safetensors", b"source-a")
artifact = _commit(repository, identity, b"quantized")
assert (
(repository.root / README_FILENAME)
.read_text(encoding="utf-8")
.startswith("SimpleSyrup Quantized Model Cache")
)
assert artifact.path.relative_to(repository.root).as_posix() == (
"Anima/base_model/nvfp4-mixed/"
f"{identity.source.sha256[:12]}-profile-3-recipe-2/"
"base_model--nvfp4-mixed.safetensors"
)
payload = json.loads(artifact.manifest_path.read_text(encoding="utf-8"))
assert payload["source_model"] == "Anima/base model.safetensors"
assert payload["profile_id"] == "nvfp4-mixed"
assert payload["profile_version"] == 3
assert payload["quantization_formats"] == ["float8_e4m3fn", "nvfp4"]
assert repository.relative_display_path() == "models/SyrupQuants"
def test_repository_finds_only_an_unchanged_source_signature(tmp_path: Path) -> None:
"""Fast cache hits invalidate when the authoritative source file changes."""
repository = QuantCacheRepository(tmp_path / "SyrupQuants")
identity = _identity(tmp_path, "anima.safetensors", b"source")
artifact = _commit(repository, identity, b"quant")
found = repository.find_current(
source_model=identity.source.display_name,
source_path=identity.source.path,
source_size_bytes=identity.source.size_bytes,
source_modified_ns=identity.source.modified_ns,
profile=identity.profile,
recipe=AnimaQuantizationRecipe(),
)
changed = repository.find_current(
source_model=identity.source.display_name,
source_path=identity.source.path,
source_size_bytes=identity.source.size_bytes + 1,
source_modified_ns=identity.source.modified_ns,
profile=identity.profile,
recipe=AnimaQuantizationRecipe(),
)
assert found is not None
assert found.path == artifact.path
assert changed is None
def test_profiles_have_isolated_readable_cache_identities(tmp_path: Path) -> None:
"""Mixed and MXFP8 recipes never share a path or cache artifact."""
repository = QuantCacheRepository(tmp_path / "SyrupQuants")
mixed = _identity(tmp_path, "anima.safetensors", b"source")
mxfp8 = replace(mixed, profile=MXFP8_PROFILE)
assert mixed.stable_key != mxfp8.stable_key
assert repository.artifact_directory(mixed) != repository.artifact_directory(mxfp8)
def test_global_lru_preserves_reserved_artifacts_and_clears_inactive(
tmp_path: Path,
) -> None:
"""Cache clearing never deletes an artifact reserved for active model work."""
repository = QuantCacheRepository(tmp_path / "SyrupQuants")
old = _commit(
repository,
_identity(tmp_path, "old.safetensors", b"old"),
b"old-quant",
)
new = _commit(
repository,
_identity(tmp_path, "new.safetensors", b"new"),
b"new-quantized",
)
_set_last_used(old, "2026-01-01T00:00:00+00:00")
_set_last_used(new, "2026-02-01T00:00:00+00:00")
leases = QuantCacheLeaseRegistry()
reservation = leases.reserve(old.path)
service = QuantCacheService(repository, leases)
result = service.clear_inactive()
assert result.removed_artifacts == 1
assert old.path.is_file()
assert not new.path.exists()
assert service.status().active_artifact_count == 1
reservation.release()
second = service.clear_inactive()
assert second.removed_artifacts == 1
assert not old.path.exists()
def test_lru_evicts_oldest_artifact_until_under_budget(tmp_path: Path) -> None:
"""The global byte budget removes least-recently-used artifacts first."""
repository = QuantCacheRepository(tmp_path / "SyrupQuants")
old = _commit(
repository,
_identity(tmp_path, "old.safetensors", b"old-source"),
b"12345",
)
new = _commit(
repository,
_identity(tmp_path, "new.safetensors", b"new-source"),
b"1234567",
)
_set_last_used(old, "2026-01-01T00:00:00+00:00")
_set_last_used(new, "2026-02-01T00:00:00+00:00")
result = QuantCacheService(repository).enforce_limit(7)
assert result.removed_artifacts == 1
assert not old.path.exists()
assert new.path.is_file()
assert result.remaining_bytes == 7
def test_v1_artifact_is_never_reused_but_remains_clearable(tmp_path: Path) -> None:
"""Legacy soft quants participate in cleanup without matching v2 profiles."""
repository = QuantCacheRepository(tmp_path / "SyrupQuants")
identity = _identity(tmp_path, "anima.safetensors", b"source")
legacy_directory = repository.root / "Anima" / "anima" / "nvfp4" / "legacy"
legacy_directory.mkdir(parents=True)
artifact_path = legacy_directory / "anima--nvfp4.safetensors"
artifact_path.write_bytes(b"legacy")
legacy_payload = {
"schema_version": 1,
"managed_by": "SimpleSyrup",
"source_model": identity.source.display_name,
"source_path": str(identity.source.path),
"source_sha256": identity.source.sha256,
"source_size_bytes": identity.source.size_bytes,
"source_modified_ns": identity.source.modified_ns,
"quantization_format": "nvfp4",
"model_family": "Anima",
"recipe_version": 1,
"artifact_file": artifact_path.name,
"artifact_size_bytes": artifact_path.stat().st_size,
"created_at": "2026-01-01T00:00:00+00:00",
"last_used_at": "2026-01-01T00:00:00+00:00",
}
(legacy_directory / MANIFEST_FILENAME).write_text(
json.dumps(legacy_payload), encoding="utf-8"
)
assert (
repository.find_current(
source_model=identity.source.display_name,
source_path=identity.source.path,
source_size_bytes=identity.source.size_bytes,
source_modified_ns=identity.source.modified_ns,
profile=identity.profile,
recipe=AnimaQuantizationRecipe(),
)
is None
)
assert len(repository.list_artifacts()) == 1
assert QuantCacheService(repository).clear_inactive().removed_artifacts == 1
assert not artifact_path.exists()
def test_manifest_replace_retries_one_transient_windows_sharing_violation(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Preserve atomic publication when a reader briefly holds the destination."""
repository = QuantCacheRepository(tmp_path / "SyrupQuants")
artifact = _commit(
repository,
_identity(tmp_path, "anima.safetensors", b"source"),
b"quantized",
)
original_replace = Path.replace
attempts = 0
sleeps: list[float] = []
def replace(source: Path, target: Path) -> Path:
"""Fail once like Windows, then execute the real atomic replacement."""
nonlocal attempts
attempts += 1
if attempts == 1:
raise PermissionError(13, "Access is denied", str(target))
return original_replace(source, target)
monkeypatch.setattr(Path, "replace", replace)
monkeypatch.setattr(time, "sleep", sleeps.append)
touched = repository.touch(artifact)
assert attempts == 2
assert sleeps == [repository_module._MANIFEST_REPLACE_RETRY_SECONDS]
assert touched.manifest_path.is_file()
def test_manifest_replace_propagates_exhausted_permission_error(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Fail closed when the destination never becomes atomically replaceable."""
repository = QuantCacheRepository(tmp_path / "SyrupQuants")
artifact = _commit(
repository,
_identity(tmp_path, "anima.safetensors", b"source"),
b"quantized",
)
attempts = 0
def deny(source: Path, target: Path) -> Path:
"""Keep the destination unavailable through every bounded attempt."""
del source
nonlocal attempts
attempts += 1
raise PermissionError(13, "Access is denied", str(target))
monkeypatch.setattr(Path, "replace", deny)
monkeypatch.setattr(time, "sleep", lambda seconds: None)
with pytest.raises(PermissionError, match="Access is denied"):
repository.touch(artifact)
assert attempts == repository_module._MANIFEST_REPLACE_ATTEMPTS
assert list(artifact.manifest_path.parent.glob("*.tmp")) == []
def _identity(
tmp_path: Path,
display_name: str,
content: bytes,
) -> QuantCacheIdentity:
"""Create an identity backed by an authoritative test source file."""
source_path = tmp_path / f"source-{hashlib.sha256(content).hexdigest()[:8]}.bin"
source_path.write_bytes(content)
stat = source_path.stat()
return QuantCacheIdentity(
source=SourceCheckpointIdentity(
display_name=display_name,
path=source_path.resolve(),
size_bytes=stat.st_size,
modified_ns=stat.st_mtime_ns,
sha256=hashlib.sha256(content).hexdigest(),
),
profile=NVFP4_MIXED_PROFILE,
model_family="Anima",
recipe_version=2,
)
def _commit(
repository: QuantCacheRepository,
identity: QuantCacheIdentity,
content: bytes,
) -> QuantCacheArtifact:
"""Publish one tiny managed artifact through the production repository."""
build = repository.create_build_directory(identity)
(build / repository.artifact_filename(identity)).write_bytes(content)
return repository.commit(identity, build)
def _set_last_used(artifact: QuantCacheArtifact, timestamp: str) -> None:
"""Set deterministic LRU order in one human-readable manifest."""
manifest = replace(artifact.manifest, last_used_at=timestamp)
artifact.manifest_path.write_text(
json.dumps(manifest.to_payload(), indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)
assert artifact.manifest_path.name == MANIFEST_FILENAME