headroom/tests/test_transforms/test_kompress_compressor.py
Raúl 1a2688b57f
test(kompress): close patch-coverage gaps from #2716 (#2721)
## Description

Codecov flagged 9 uncovered lines on #2716 after it merged:
`hf_entry_known_absent`'s own body
in `headroom/onnx_runtime.py` was only ever exercised indirectly (every
existing test in
`tests/test_transforms/test_kompress_compressor.py` monkeypatched it
away rather than calling the
real implementation), and `_load_pytorch_weights` /
`_load_kompress_pytorch` in
`headroom/transforms/kompress_compressor.py` had three untested
branches: the double cache-miss
under `allow_download=False` (merged.pt confirmed absent AND the plain
fallback also not cached),
a genuine non-404 download failure propagating instead of silently
falling back, and the
already-cached fast path in `_load_kompress_pytorch`.

## Type of Change

- [ ] Bug fix
- [ ] New feature
- [x] Test coverage improvement, no production code change

## Changes Made

- `tests/test_onnx_runtime.py`: added `_write_fake_hf_cache` (builds a
minimal on-disk HF hub
cache layout, including the `.no_exist/<hash>/<filename>` marker
huggingface_hub writes after a
real 404) and three direct tests of `hf_entry_known_absent` against the
real
  `huggingface_hub.try_to_load_from_cache`, not a mock of it.
- `tests/test_transforms/test_kompress_compressor.py`: added
  `test_cache_only_raises_when_confirmed_absent_but_plain_also_missing`,
`test_genuine_download_failure_propagates_instead_of_falling_back`, and
a new
`TestLoadKompressPytorchCaching` class covering the already-cached fast
path.

## Testing

```text
$ .venv/bin/python3 -m pytest tests/test_onnx_runtime.py tests/test_transforms/test_kompress_compressor.py -q
51 passed

$ .venv/bin/python3 -m pytest tests/ -k "kompress or onnx_runtime" -q --cov=headroom.transforms.kompress_compressor --cov=headroom.onnx_runtime --cov-report=term-missing
# before: onnx_runtime.py Missing includes 132-136 (hf_entry_known_absent's entire body);
#         kompress_compressor.py Missing includes 805-806, 818, 836
# after:  none of those lines appear in Missing anymore
191 passed, 7 skipped

$ .venv/bin/python3 -m ruff format --check tests/test_onnx_runtime.py tests/test_transforms/test_kompress_compressor.py
2 files already formatted
$ .venv/bin/python3 -m ruff check tests/test_onnx_runtime.py tests/test_transforms/test_kompress_compressor.py
All checks passed!
```

## Review Readiness

- Test-only, additive diff (113 insertions, 0 deletions, 0 lines touched
outside the two test
  files). No behavior change possible.

## Checklist

- [x] My code follows the project's style guidelines
- [x] I have performed a self-review of my code
- [x] New and existing unit tests pass locally with my changes
- [x] I did not edit `CHANGELOG.md`

## Additional Notes

Not closing: the remaining branch-partial on the `device == "auto"`
cuda/mps/cpu selection in
`_load_kompress_pytorch` (would need mocking `torch.cuda.is_available()`
/
`torch.backends.mps.is_available()` for marginal benefit); left as-is.
2026-08-02 10:45:44 -07:00

814 lines
32 KiB
Python

"""Tests for Kompress compressor.
Covers:
- Lazy imports: module importable without torch installed
- is_kompress_available(): correct detection of [ml] extra
- KompressConfig / KompressResult: dataclass defaults
- KompressCompressor: passthrough for short content, fallback on error
- Transform interface: apply() method
"""
import logging
from types import SimpleNamespace
from unittest.mock import MagicMock, patch
# ── Import safety (the whole point of the fix) ─────────────────────────
class TestLazyImports:
"""The module must be importable without torch/transformers."""
def test_is_kompress_available_importable(self) -> None:
"""is_kompress_available can be imported even without torch."""
from headroom.transforms.kompress_compressor import is_kompress_available
# Should return bool (True or False depending on environment)
result = is_kompress_available()
assert isinstance(result, bool)
def test_module_import_without_torch(self) -> None:
"""Importing the module with torch blocked should not raise."""
import sys
# Block torch AND onnxruntime imports
with patch.dict(
sys.modules,
{"torch": None, "torch.nn": None, "onnxruntime": None},
):
from headroom.transforms.kompress_compressor import (
_is_pytorch_available,
)
# Without both torch and onnxruntime, should return False
assert _is_pytorch_available() is False
# Note: is_kompress_available() may still return True if onnxruntime
# was already imported before patching. Test the individual checkers.
def test_dataclasses_importable_without_torch(self) -> None:
"""KompressConfig, KompressResult, KompressCompressor are importable without torch."""
from headroom.transforms.kompress_compressor import (
KompressCompressor, # noqa: F401
KompressConfig,
KompressResult,
)
# These don't need torch to instantiate
config = KompressConfig()
assert config.device == "auto"
assert config.enable_ccr is True
result = KompressResult(
compressed="hello",
original="hello world",
original_tokens=2,
compressed_tokens=1,
compression_ratio=0.5,
)
assert result.tokens_saved == 1
assert result.savings_percentage == 50.0
class TestKompressBackendSelection:
def test_selected_backend_aliases(self, monkeypatch) -> None:
import headroom.transforms.kompress_compressor as kmod
monkeypatch.setenv("HEADROOM_KOMPRESS_BACKEND", "mps")
assert kmod._selected_backend() == "pytorch_mps"
monkeypatch.setenv("HEADROOM_KOMPRESS_BACKEND", "coreml")
assert kmod._selected_backend() == "onnx_coreml"
monkeypatch.setenv("HEADROOM_KOMPRESS_BACKEND", "cpu")
assert kmod._selected_backend() == "onnx_cpu"
monkeypatch.setenv("HEADROOM_KOMPRESS_BACKEND", "unknown")
assert kmod._selected_backend() == "auto"
def test_unrecognized_backend_warns_and_falls_back_to_auto(self, monkeypatch, caplog) -> None:
import headroom.transforms.kompress_compressor as kmod
monkeypatch.setenv("HEADROOM_KOMPRESS_BACKEND", "tpu")
with caplog.at_level(logging.WARNING, logger=kmod.logger.name):
assert kmod._selected_backend() == "auto"
assert any(
"unrecognized" in record.getMessage() and "tpu" in record.getMessage()
for record in caplog.records
)
def test_valid_backend_values_do_not_warn(self, monkeypatch, caplog) -> None:
import headroom.transforms.kompress_compressor as kmod
with caplog.at_level(logging.WARNING, logger=kmod.logger.name):
for value in ("auto", "onnx", "cpu", "coreml", "mps", "torch", "ONNX-CPU"):
monkeypatch.setenv("HEADROOM_KOMPRESS_BACKEND", value)
kmod._selected_backend()
monkeypatch.delenv("HEADROOM_KOMPRESS_BACKEND", raising=False)
kmod._selected_backend()
assert not caplog.records
def test_forced_pytorch_mps_backend_uses_mps_device(self, monkeypatch) -> None:
import headroom.transforms.kompress_compressor as kmod
calls: list[tuple[str, str]] = []
monkeypatch.setenv("HEADROOM_KOMPRESS_BACKEND", "pytorch_mps")
monkeypatch.setattr(kmod, "_kompress_cache", {})
monkeypatch.setattr(
kmod,
"_load_kompress_pytorch",
lambda model_id, device, *, allow_download=True: (
calls.append((model_id, device)) or ("model", "tokenizer", "pytorch")
),
)
assert kmod._load_kompress("model-a", device="auto") == ("model", "tokenizer", "pytorch")
assert calls == [("model-a", "mps")]
def test_forced_coreml_backend_uses_onnx_coreml(self, monkeypatch) -> None:
import headroom.transforms.kompress_compressor as kmod
calls: list[tuple[str, bool]] = []
monkeypatch.setenv("HEADROOM_KOMPRESS_BACKEND", "onnx_coreml")
monkeypatch.setattr(kmod, "_kompress_cache", {})
monkeypatch.setattr(
kmod,
"_load_kompress_onnx",
lambda model_id, *, use_coreml=False, allow_download=True: (
calls.append((model_id, use_coreml)) or ("model", "tokenizer", "onnx_coreml")
),
)
assert kmod._load_kompress("model-b") == ("model", "tokenizer", "onnx_coreml")
assert calls == [("model-b", True)]
def test_auto_backend_preserves_onnx_first(self, monkeypatch) -> None:
import headroom.transforms.kompress_compressor as kmod
calls: list[str] = []
monkeypatch.delenv("HEADROOM_KOMPRESS_BACKEND", raising=False)
monkeypatch.setattr(kmod, "_kompress_cache", {})
monkeypatch.setattr(kmod, "_is_onnx_available", lambda: True)
monkeypatch.setattr(kmod, "_is_pytorch_available", lambda: True)
monkeypatch.setattr(
kmod,
"_load_kompress_onnx",
lambda model_id, *, use_coreml=False, allow_download=True: (
calls.append("onnx") or ("model", "tokenizer", "onnx")
),
)
monkeypatch.setattr(
kmod,
"_load_kompress_pytorch",
lambda model_id, device, *, allow_download=True: (
calls.append("pytorch") or ("model", "tokenizer", "pytorch")
),
)
assert kmod._load_kompress("model-c") == ("model", "tokenizer", "onnx")
assert calls == ["onnx"]
def test_onnx_session_options_read_thread_caps(self, monkeypatch) -> None:
import headroom.transforms.kompress_compressor as kmod
created: list[SimpleNamespace] = []
class FakeSessionOptions:
def __init__(self) -> None:
self.intra_op_num_threads = None
self.inter_op_num_threads = None
self.enable_cpu_mem_arena = True
self.enable_mem_pattern = True
fake_ort = SimpleNamespace(
SessionOptions=lambda: created.append(FakeSessionOptions()) or created[-1]
)
monkeypatch.setenv("HEADROOM_KOMPRESS_ONNX_INTRA_THREADS", "2")
monkeypatch.setenv("HEADROOM_KOMPRESS_ONNX_INTER_THREADS", "1")
options = kmod._onnx_session_options(fake_ort)
assert options.intra_op_num_threads == 2
assert options.inter_op_num_threads == 1
assert options.enable_cpu_mem_arena is False
assert options.enable_mem_pattern is False
# ── KompressResult ──────────────────────────────────────────────────────
class TestKompressResult:
def test_tokens_saved(self) -> None:
from headroom.transforms.kompress_compressor import KompressResult
r = KompressResult(
compressed="a b",
original="a b c d",
original_tokens=4,
compressed_tokens=2,
compression_ratio=0.5,
)
assert r.tokens_saved == 2
def test_tokens_saved_no_negative(self) -> None:
from headroom.transforms.kompress_compressor import KompressResult
r = KompressResult(
compressed="a b c d e",
original="a b c",
original_tokens=3,
compressed_tokens=5,
compression_ratio=1.67,
)
assert r.tokens_saved == 0
def test_savings_percentage_zero_tokens(self) -> None:
from headroom.transforms.kompress_compressor import KompressResult
r = KompressResult(
compressed="",
original="",
original_tokens=0,
compressed_tokens=0,
compression_ratio=1.0,
)
assert r.savings_percentage == 0.0
def test_default_model(self) -> None:
from headroom.transforms.kompress_compressor import HF_MODEL_ID, KompressResult
r = KompressResult(
compressed="x",
original="x y",
original_tokens=2,
compressed_tokens=1,
compression_ratio=0.5,
)
assert r.model_used == HF_MODEL_ID
# ── KompressCompressor (without model) ──────────────────────────────────
class TestKompressCompressorPassthrough:
"""Test compressor behavior that doesn't require the actual model."""
def test_short_content_passthrough(self) -> None:
"""Content under 10 words should pass through unchanged."""
from headroom.transforms.kompress_compressor import KompressCompressor
compressor = KompressCompressor()
result = compressor.compress("hello world")
assert result.compressed == "hello world"
assert result.compression_ratio == 1.0
assert result.original_tokens == 2
assert result.compressed_tokens == 2
def test_empty_content_passthrough(self) -> None:
from headroom.transforms.kompress_compressor import KompressCompressor
compressor = KompressCompressor()
result = compressor.compress("")
assert result.compressed == ""
assert result.compression_ratio == 1.0
def test_fallback_on_model_error(self) -> None:
"""If _load_kompress fails, compress should return passthrough."""
from headroom.transforms.kompress_compressor import KompressCompressor
compressor = KompressCompressor()
long_text = " ".join(f"word{i}" for i in range(20))
with patch(
"headroom.transforms.kompress_compressor._load_kompress",
side_effect=RuntimeError("no model"),
):
result = compressor.compress(long_text)
assert result.compressed == long_text
assert result.compression_ratio == 1.0
# ── Transform interface ─────────────────────────────────────────────────
class TestKompressTransformInterface:
def test_apply_short_messages_unchanged(self) -> None:
"""Messages with <10 words should pass through apply() unchanged."""
from headroom.transforms.kompress_compressor import KompressCompressor
compressor = KompressCompressor()
messages = [
{"role": "user", "content": "hello"},
{"role": "tool", "content": "short"},
]
tokenizer = MagicMock()
tokenizer.count_text = MagicMock(return_value=5)
result = compressor.apply(messages, tokenizer)
assert len(result.messages) == 2
assert result.messages[0]["content"] == "hello"
assert result.messages[1]["content"] == "short"
def test_apply_preserves_user_messages(self) -> None:
"""User messages should never be compressed."""
from headroom.transforms.kompress_compressor import KompressCompressor
compressor = KompressCompressor()
long_text = " ".join(f"word{i}" for i in range(50))
messages = [{"role": "user", "content": long_text}]
tokenizer = MagicMock()
tokenizer.count_text = MagicMock(return_value=50)
with patch(
"headroom.transforms.kompress_compressor._load_kompress",
side_effect=RuntimeError("should not be called"),
):
result = compressor.apply(messages, tokenizer)
assert result.messages[0]["content"] == long_text
# ── compress_batch ──────────────────────────────────────────────────────
class TestKompressCompressorBatch:
"""Tests for the batched compression API (compress_batch).
These exercise the non-model paths — passthrough handling, argument
validation, order preservation, and fallback behavior on model-load
failure. The actual batched inference path is covered by integration
tests that require the model to be downloaded.
"""
def test_empty_batch_returns_empty_list(self) -> None:
from headroom.transforms.kompress_compressor import KompressCompressor
compressor = KompressCompressor()
result = compressor.compress_batch([])
assert result == []
def test_all_short_texts_passthrough_without_model(self) -> None:
"""Texts under 10 words must passthrough; model never loaded."""
from headroom.transforms.kompress_compressor import KompressCompressor
compressor = KompressCompressor()
contents = ["hello", "world", "short text here"]
with patch(
"headroom.transforms.kompress_compressor._load_kompress",
side_effect=AssertionError("model should not be loaded for short texts"),
):
results = compressor.compress_batch(contents)
assert len(results) == 3
for i, r in enumerate(results):
assert r.compressed == contents[i]
assert r.compression_ratio == 1.0
def test_order_preserved(self) -> None:
"""Output order must match input order even when model load fails."""
from headroom.transforms.kompress_compressor import KompressCompressor
compressor = KompressCompressor()
long_texts = [
" ".join(f"alpha{i}" for i in range(20)),
" ".join(f"beta{i}" for i in range(20)),
" ".join(f"gamma{i}" for i in range(20)),
]
with patch(
"headroom.transforms.kompress_compressor._load_kompress",
side_effect=RuntimeError("no model"),
):
results = compressor.compress_batch(long_texts)
assert len(results) == 3
assert results[0].original.startswith("alpha0")
assert results[1].original.startswith("beta0")
assert results[2].original.startswith("gamma0")
def test_mixed_short_and_long_passthrough_on_model_failure(self) -> None:
"""Short texts passthrough; long texts fall back to passthrough on model failure."""
from headroom.transforms.kompress_compressor import KompressCompressor
compressor = KompressCompressor()
contents = [
"short",
" ".join(f"word{i}" for i in range(20)), # triggers model path
"also short",
]
with patch(
"headroom.transforms.kompress_compressor._load_kompress",
side_effect=RuntimeError("no model"),
):
results = compressor.compress_batch(contents)
assert len(results) == 3
assert results[0].compressed == "short"
assert results[0].compression_ratio == 1.0
assert results[1].compression_ratio == 1.0 # passthrough fallback
assert results[2].compressed == "also short"
def test_ratio_list_length_mismatch_raises(self) -> None:
"""If target_ratio is a list it must match contents length."""
from headroom.transforms.kompress_compressor import KompressCompressor
compressor = KompressCompressor()
contents = ["a b c", "d e f"]
# Too short
try:
compressor.compress_batch(contents, target_ratio=[0.5])
raise AssertionError("expected ValueError for length mismatch")
except ValueError as e:
assert "length" in str(e).lower()
# Too long
try:
compressor.compress_batch(contents, target_ratio=[0.5, 0.5, 0.5])
raise AssertionError("expected ValueError for length mismatch")
except ValueError as e:
assert "length" in str(e).lower()
def test_batch_of_one_equivalent_to_single_compress_on_short_text(self) -> None:
"""Batch-of-one with short text should produce identical passthrough."""
from headroom.transforms.kompress_compressor import KompressCompressor
compressor = KompressCompressor()
text = "hello world"
single = compressor.compress(text)
batch = compressor.compress_batch([text])
assert len(batch) == 1
assert batch[0].compressed == single.compressed
assert batch[0].compression_ratio == single.compression_ratio
assert batch[0].original_tokens == single.original_tokens
def test_uniform_ratio_scalar(self) -> None:
"""A scalar target_ratio must apply to every text in the batch."""
from headroom.transforms.kompress_compressor import KompressCompressor
compressor = KompressCompressor()
# Short texts — passthrough regardless of ratio
contents = ["short a", "short b", "short c"]
results = compressor.compress_batch(contents, target_ratio=0.3)
assert len(results) == 3
for r, original in zip(results, contents, strict=True):
assert r.compressed == original # short passthrough
def test_per_item_ratio_list_with_nones(self) -> None:
"""A list of ratios with some None entries must be accepted."""
from headroom.transforms.kompress_compressor import KompressCompressor
compressor = KompressCompressor()
contents = ["short a", "short b", "short c"]
ratios: list[float | None] = [0.5, None, 0.25]
# Short texts always passthrough; validating the list shape alone.
results = compressor.compress_batch(contents, target_ratio=ratios)
assert len(results) == 3
# ── unload_kompress_model ───────────────────────────────────────────────
class TestUnloadKompressModel:
def test_unload_when_no_model(self) -> None:
import headroom.transforms.kompress_compressor as kmod
from headroom.transforms.kompress_compressor import unload_kompress_model
# Ensure no model is loaded (previous tests may have set the cache)
kmod._kompress_cache.clear()
# Should return False when no model is loaded
assert unload_kompress_model() is False
# ── onnx_coreml backend gating (issue #2442) ────────────────────────────
class TestOnnxBackendPrefixGating:
"""Non-CPU ONNX backends (onnx_coreml, onnx_cpu) must take the ONNX path.
The bug: sites gated on the exact string ``backend == "onnx"`` misclassified
``onnx_coreml`` as PyTorch and called ``next(model.parameters())`` on the
``_OnnxModel`` wrapper, which has no ``.parameters()`` — crashing every call
and silently disabling Kompress. The fix uses ``backend.startswith("onnx")``.
"""
class _FakeOnnxModel:
"""Mimics the ONNX wrapper: has get_keep_mask but no .parameters()."""
def get_keep_mask(self, input_ids, attention_mask): # noqa: ANN001, ANN201
return [[True]]
@staticmethod
def _fake_tokenizer(words, **kwargs): # noqa: ANN001, ANN205
# ONNX path must request numpy tensors, never torch.
assert kwargs.get("return_tensors") == "np"
return {"input_ids": [[1, 2]], "attention_mask": [[1, 1]]}
def test_timed_canary_onnx_coreml_skips_pytorch_device_dispatch(self) -> None:
from headroom.transforms.kompress_compressor import KompressCompressor
compressor = KompressCompressor()
model = self._FakeOnnxModel() # no .parameters()
# Must not raise AttributeError: '_OnnxModel' object has no attribute
# 'parameters'; returns a float wall-clock duration.
elapsed = compressor._timed_canary(model, self._fake_tokenizer, "onnx_coreml")
assert isinstance(elapsed, float)
def test_timed_canary_pytorch_still_dispatches_to_device(self) -> None:
# Negative control: the PyTorch branch DOES touch .parameters(), so the
# paramless fake model raises there — proving the test above is only
# green because onnx_coreml correctly skips that branch.
import pytest
from headroom.transforms.kompress_compressor import KompressCompressor
compressor = KompressCompressor()
model = self._FakeOnnxModel()
def pt_tokenizer(words, **kwargs): # noqa: ANN001, ANN202
assert kwargs.get("return_tensors") == "pt"
return {"input_ids": [[1, 2]], "attention_mask": [[1, 1]]}
with pytest.raises(AttributeError):
compressor._timed_canary(model, pt_tokenizer, "pytorch")
class TestPytorchWeightLoading:
"""_load_pytorch_weights must load the merged v2 checkpoint format correctly,
fall back to the plain format only when the repo genuinely has no merged.pt,
and refuse to run on a state-dict mismatch instead of silently ignoring it.
"""
@staticmethod
def _make_model(torch):
import torch.nn as nn
model = nn.Module()
model.encoder = nn.Linear(4, 4)
model.token_head = nn.Linear(4, 2)
model.span_conv = nn.Sequential(nn.Conv1d(4, 4, 1), nn.GELU())
return model
def test_merged_checkpoint_loads_into_matching_submodules(self, tmp_path, monkeypatch) -> None:
import pytest
torch = pytest.importorskip("torch")
import headroom.transforms.kompress_compressor as kmod
model = self._make_model(torch)
ckpt_path = tmp_path / "merged.pt"
torch.save(
{
"encoder_state_dict": model.encoder.state_dict(),
"token_head_state_dict": model.token_head.state_dict(),
"span_conv_state_dict": model.span_conv.state_dict(),
},
ckpt_path,
)
fresh_model = self._make_model(torch)
monkeypatch.setattr(kmod, "hf_hub_download_local_first", lambda *a, **k: str(ckpt_path))
kmod._load_pytorch_weights(fresh_model, "some/repo", allow_download=True)
for name, param in model.encoder.state_dict().items():
assert torch.equal(param, fresh_model.encoder.state_dict()[name])
def test_merged_checkpoint_missing_section_raises(self, tmp_path, monkeypatch) -> None:
import pytest
torch = pytest.importorskip("torch")
import headroom.transforms.kompress_compressor as kmod
ckpt_path = tmp_path / "merged.pt"
torch.save({"encoder_state_dict": {}}, ckpt_path)
fresh_model = self._make_model(torch)
monkeypatch.setattr(kmod, "hf_hub_download_local_first", lambda *a, **k: str(ckpt_path))
with pytest.raises(RuntimeError, match="missing"):
kmod._load_pytorch_weights(fresh_model, "some/repo", allow_download=True)
def test_merged_checkpoint_key_mismatch_raises_instead_of_silently_dropping(
self, tmp_path, monkeypatch
) -> None:
"""Regression test for the bug this loader used to have: loading a
state-dict that does not match the module tree (e.g. an unmerged PEFT
checkpoint) must fail loudly, not silently skip the mismatched keys.
"""
import pytest
torch = pytest.importorskip("torch")
import headroom.transforms.kompress_compressor as kmod
ckpt_path = tmp_path / "merged.pt"
torch.save(
{
# Wrong prefix, mimics the unmerged PEFT structure documented
# in scripts/export_kompress_v2_onnx.py.
"encoder_state_dict": {"base_model.model.weight": torch.zeros(4, 4)},
"token_head_state_dict": {},
"span_conv_state_dict": {},
},
ckpt_path,
)
fresh_model = self._make_model(torch)
monkeypatch.setattr(kmod, "hf_hub_download_local_first", lambda *a, **k: str(ckpt_path))
with pytest.raises(RuntimeError, match="state_dict mismatch"):
kmod._load_pytorch_weights(fresh_model, "some/repo", allow_download=True)
def test_missing_merged_pt_falls_back_to_plain_safetensors(self, tmp_path, monkeypatch) -> None:
import pytest
torch = pytest.importorskip("torch")
safetensors_torch = pytest.importorskip("safetensors.torch")
import headroom.transforms.kompress_compressor as kmod
model = self._make_model(torch)
weights_path = tmp_path / "model.safetensors"
safetensors_torch.save_file(dict(model.state_dict()), str(weights_path))
fresh_model = self._make_model(torch)
def fake_download(model_id, filename, *, allow_network=True, **kwargs): # noqa: ANN001
if filename == "merged.pt":
raise kmod.EntryNotFoundError("no merged.pt in this repo")
assert filename == "model.safetensors"
return str(weights_path)
monkeypatch.setattr(kmod, "hf_hub_download_local_first", fake_download)
kmod._load_pytorch_weights(fresh_model, "some/v1/repo", allow_download=True)
for name, param in model.state_dict().items():
assert torch.equal(param, fresh_model.state_dict()[name])
def test_plain_safetensors_key_mismatch_raises(self, tmp_path, monkeypatch) -> None:
import pytest
torch = pytest.importorskip("torch")
safetensors_torch = pytest.importorskip("safetensors.torch")
import headroom.transforms.kompress_compressor as kmod
weights_path = tmp_path / "model.safetensors"
safetensors_torch.save_file({"totally.unrelated.key": torch.zeros(2)}, str(weights_path))
fresh_model = self._make_model(torch)
def fake_download(model_id, filename, *, allow_network=True, **kwargs): # noqa: ANN001
if filename == "merged.pt":
raise kmod.EntryNotFoundError("no merged.pt in this repo")
return str(weights_path)
monkeypatch.setattr(kmod, "hf_hub_download_local_first", fake_download)
with pytest.raises(RuntimeError, match="state_dict mismatch"):
kmod._load_pytorch_weights(fresh_model, "some/v1/repo", allow_download=True)
def test_cache_only_miss_raises_kompress_model_not_cached(self, monkeypatch) -> None:
import pytest
pytest.importorskip("torch")
import headroom.transforms.kompress_compressor as kmod
def fake_download(model_id, filename, *, allow_network=True, **kwargs): # noqa: ANN001
raise kmod.LocalEntryNotFoundError("not cached")
monkeypatch.setattr(kmod, "hf_hub_download_local_first", fake_download)
monkeypatch.setattr(kmod, "hf_entry_known_absent", lambda *a, **k: False)
with pytest.raises(kmod.KompressModelNotCached):
kmod._load_pytorch_weights(SimpleNamespace(), "some/repo", allow_download=False)
def test_cache_only_defers_instead_of_using_stale_plain_checkpoint(self, monkeypatch) -> None:
"""Regression test: if merged.pt is not cached yet and we have no
confirmation it is genuinely absent upstream, a stale model.safetensors
left over from a previous (pre-fix) run must NOT be used as a silent
fallback - that would reintroduce the original bug for exactly the
upgrade scenario that motivated this fix. It must defer instead.
"""
import pytest
pytest.importorskip("torch")
import headroom.transforms.kompress_compressor as kmod
plain_download_calls: list[str] = []
def fake_download(model_id, filename, *, allow_network=True, **kwargs): # noqa: ANN001
if filename == "merged.pt":
raise kmod.LocalEntryNotFoundError("merged.pt not cached yet")
plain_download_calls.append(filename)
return "/fake/cached/model.safetensors"
monkeypatch.setattr(kmod, "hf_hub_download_local_first", fake_download)
# Nothing has ever confirmed merged.pt is absent from this repo -
# simulates a v2-style repo mid-upgrade, not a v1-style repo.
monkeypatch.setattr(kmod, "hf_entry_known_absent", lambda *a, **k: False)
with pytest.raises(kmod.KompressModelNotCached):
kmod._load_pytorch_weights(
SimpleNamespace(), "chopratejas/kompress-v2-base", allow_download=False
)
assert plain_download_calls == [], (
"must not fall back to model.safetensors without confirming merged.pt is absent"
)
def test_cache_only_uses_plain_checkpoint_when_merged_pt_confirmed_absent(
self, tmp_path, monkeypatch
) -> None:
import pytest
torch = pytest.importorskip("torch")
safetensors_torch = pytest.importorskip("safetensors.torch")
import headroom.transforms.kompress_compressor as kmod
model = self._make_model(torch)
weights_path = tmp_path / "model.safetensors"
safetensors_torch.save_file(dict(model.state_dict()), str(weights_path))
fresh_model = self._make_model(torch)
def fake_download(model_id, filename, *, allow_network=True, **kwargs): # noqa: ANN001
if filename == "merged.pt":
raise kmod.LocalEntryNotFoundError("merged.pt not cached")
return str(weights_path)
monkeypatch.setattr(kmod, "hf_hub_download_local_first", fake_download)
# A prior real network lookup already confirmed this repo has no
# merged.pt at all (the v1-style case), so the plain fallback is safe.
monkeypatch.setattr(kmod, "hf_entry_known_absent", lambda *a, **k: True)
kmod._load_pytorch_weights(fresh_model, "chopratejas/kompress-base", allow_download=False)
for name, param in model.state_dict().items():
assert torch.equal(param, fresh_model.state_dict()[name])
def test_cache_only_raises_when_confirmed_absent_but_plain_also_missing(
self, monkeypatch
) -> None:
"""merged.pt confirmed absent, but the plain fallback isn't cached either:
still nothing to load from, so this must defer rather than raise a
confusing lower-level error.
"""
import pytest
pytest.importorskip("torch")
import headroom.transforms.kompress_compressor as kmod
def fake_download(model_id, filename, *, allow_network=True, **kwargs): # noqa: ANN001
raise kmod.LocalEntryNotFoundError(f"{filename} not cached")
monkeypatch.setattr(kmod, "hf_hub_download_local_first", fake_download)
monkeypatch.setattr(kmod, "hf_entry_known_absent", lambda *a, **k: True)
with pytest.raises(kmod.KompressModelNotCached):
kmod._load_pytorch_weights(
SimpleNamespace(), "chopratejas/kompress-base", allow_download=False
)
def test_genuine_download_failure_propagates_instead_of_falling_back(self, monkeypatch) -> None:
"""A real network/download failure (not a 404, not a cache miss under
allow_download=False) must propagate as-is, not be swallowed into a
silent fallback to the plain format.
"""
import pytest
pytest.importorskip("torch")
import headroom.transforms.kompress_compressor as kmod
def fake_download(model_id, filename, *, allow_network=True, **kwargs): # noqa: ANN001
raise OSError("connection reset")
monkeypatch.setattr(kmod, "hf_hub_download_local_first", fake_download)
with pytest.raises(OSError, match="connection reset"):
kmod._load_pytorch_weights(SimpleNamespace(), "some/repo", allow_download=True)
class TestLoadKompressPytorchCaching:
def test_returns_cached_entry_without_reloading(self, monkeypatch) -> None:
import pytest
pytest.importorskip("torch")
import headroom.transforms.kompress_compressor as kmod
sentinel = ("cached-model", "cached-tokenizer", "pytorch")
monkeypatch.setattr(kmod, "_kompress_cache", {"some/repo": sentinel})
def boom(*a, **k): # noqa: ANN001, ANN202
raise AssertionError("should not attempt to reload an already-cached model")
monkeypatch.setattr(kmod, "_load_pytorch_weights", boom)
assert kmod._load_kompress_pytorch("some/repo") == sentinel