fix(kompress): load merged.pt for the v2 checkpoint instead of the unmerged PEFT safetensors (#2716)

# PR draft: fix(kompress): load merged.pt for the v2 checkpoint instead
of the unmerged PEFT safetensors

Branch: `rnoz/fix-kompress-merged-checkpoint` (off `upstream/main`). Two
commits.
Issue: https://github.com/headroomlabs-ai/headroom/issues/2714 (filed,
open).

---

## Description

`_load_kompress_pytorch` in `headroom/transforms/kompress_compressor.py`
downloaded `model.safetensors` from the default model repo
`chopratejas/kompress-v2-base` and loaded it with `strict=False`,
discarding the missing/unexpected key report. That file is the unmerged
PEFT checkpoint (encoder keys prefixed `encoder.base_model.model...`),
which never matches `HeadroomCompressorModel`'s plain `encoder.*` keys.
The LoRA-adapted encoder weights were silently dropped while
`token_head`/`span_conv` happened to match and loaded fine, so the model
ran with a stock, non-adapted `answerdotai/ModernBERT-base` encoder
feeding correctly trained decision heads, with no error and a healthy
status reported everywhere.

`scripts/export_kompress_v2_onnx.py` already documents this exact
mismatch and loads the correct `merged.pt` sub-state-dicts for its own
export path. This PR mirrors that same loading logic into the runtime
PyTorch loader, with a fallback to the plain `model.safetensors` format
for repos that never shipped a `merged.pt` (verified against the v1
`chopratejas/kompress-base` repo via the public HF API, which has no
`merged.pt`). Both paths now check the missing/unexpected key report and
raise instead of silently proceeding on a mismatch.

A second commit fixes a gap an adversarial review caught in the first:
the cache-only (`allow_download=False`, startup preload) path could not
tell "this repo genuinely has no merged.pt" apart from "merged.pt exists
but is not downloaded yet", so it would have fallen back to a stale
`model.safetensors` left over from before this fix on exactly the
upgrade path this PR is meant to close. It now uses `huggingface_hub`'s
own `.no_exist` cache marker (via a new `hf_entry_known_absent()` helper
in `headroom/onnx_runtime.py`) to make that distinction without a
network call, and only falls back when absence is confirmed; otherwise
it raises `KompressModelNotCached` so the caller defers instead of
guessing.

Closes #2714

## Type of Change

- [x] Bug fix (non-breaking change that fixes an issue)

## Changes Made

- `headroom/transforms/kompress_compressor.py`: added
`_load_merged_state_dict`, `_load_plain_state_dict`, and
`_load_pytorch_weights`, replacing the inline `model.safetensors`
download + `load_state_dict(strict=False)` call in
`_load_kompress_pytorch`. `merged.pt` is tried first; the plain format
is only used when its absence is confirmed.
- `headroom/onnx_runtime.py`: added `hf_entry_known_absent()`, a thin
wrapper around `huggingface_hub.try_to_load_from_cache()` that reads the
on-disk `.no_exist` marker HF writes after a real 404, so cache-only
code can distinguish "confirmed absent" from "never checked" without
hitting the network.
- `tests/test_transforms/test_kompress_compressor.py`: added
`TestPytorchWeightLoading` (8 tests) covering the merged-checkpoint
happy path, missing-section and key-mismatch failures, the plain-format
fallback for repos without `merged.pt`, and the cache-only ambiguity fix
(confirmed-absent vs unconfirmed).

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check`)
- [x] Type checking passes (`mypy`)
- [x] Manual testing performed (real download against the live
`chopratejas/kompress-v2-base` repo, not just mocks)

### Test Output

```text
$ .venv/bin/python3 -m pytest tests/ -k kompress -q
178 passed, 7 skipped in 22.39s

$ .venv/bin/python3 -m ruff check headroom/onnx_runtime.py headroom/transforms/kompress_compressor.py tests/test_transforms/test_kompress_compressor.py
All checks passed!

$ .venv/bin/python3 -m ruff format --check headroom/onnx_runtime.py headroom/transforms/kompress_compressor.py tests/test_transforms/test_kompress_compressor.py
3 files already formatted

$ .venv/bin/python3 -m mypy headroom/onnx_runtime.py headroom/transforms/kompress_compressor.py
Success: no issues found in 2 source files
```

## Real Behavior Proof

Ran the actual fixed loader against the live
`chopratejas/kompress-v2-base` HF repo (not a mock), before and after
each fix:

```text
# BEFORE (parsed the real cached model.safetensors header by hand, no safetensors lib needed):
total tensors: 316
  encoder.base_model.model.embeddings.norm.weight
  encoder.base_model.model.embeddings.tok_embeddings.weight
  ...(all 310 encoder tensors share this prefix)...
  span_conv.0.bias / span_conv.0.weight / span_conv.2.bias / span_conv.2.weight
  token_head.bias / token_head.weight
exact prefix match count with plain "encoder.<rest>": 0

# This confirms the pre-fix code's model.load_state_dict(state_dict, strict=False)
# silently dropped every encoder weight (0 keys match HeadroomCompressorModel.encoder),
# while token_head/span_conv happened to match and loaded.

# AFTER (commit 1, real merged.pt download + load):
$ .venv/bin/python3 -c "
import headroom.transforms.kompress_compressor as kmod
model = kmod._get_model_class()()
kmod._load_pytorch_weights(model, 'chopratejas/kompress-v2-base', allow_download=True)
print('SUCCESS: 0 missing/unexpected keys across all three sections')
"
SUCCESS: 0 missing/unexpected keys across all three sections

# AFTER (full pipeline, real end-to-end compression through the public API):
$ .venv/bin/python3 -c "
import headroom.transforms.kompress_compressor as kmod
compressor = kmod.KompressCompressor()
result = compressor.compress(sample_traceback_plus_boilerplate_text)
print(result.original_tokens, result.compressed_tokens, result.tokens_saved)
print('ValueError: bad input' in result.compressed)
"
497 454 43
True   # must-keep line (the actual error) survived compression

# AFTER (commit 2, cache-only ambiguity): unit tests
# test_cache_only_defers_instead_of_using_stale_plain_checkpoint: PASSED
# test_cache_only_uses_plain_checkpoint_when_merged_pt_confirmed_absent: PASSED
```

## Review Readiness

- [x] I have performed a self-review
- [x] An independent adversarial review pass was run on both commits
before this PR was opened; its one finding (the cache-only ambiguity) is
fixed in commit 2, verified with new regression tests
- [x] This PR is ready for human review

## Checklist

- [x] My code follows the project's style guidelines
- [x] I have performed a self-review of my code
- [x] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation (N/A:
internal loader behavior, no public API or config surface changed)
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective (regression
tests for both the original silent-drop bug and the cache-only ambiguity
found in review)
- [x] New and existing unit tests pass locally with my changes
- [x] I did not edit `CHANGELOG.md`

## Additional Notes

- Both `merged.pt` and the plain `model.safetensors` fallback now raise
loudly on any state-dict mismatch instead of proceeding with
partially-loaded weights, closing the general silent-failure class this
bug belonged to, not just this one instance of it.
- No other call sites of `_load_kompress_pytorch` or its removed inline
code exist; its public signature is unchanged.
This commit is contained in:
Raúl 2026-08-02 17:10:26 +02:00 committed by GitHub
parent 6d5516dcb8
commit 46da91b2f1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 333 additions and 13 deletions

View file

@ -117,6 +117,25 @@ def hf_hub_download_local_first(
return str(hf_hub_download(repo_id, filename, revision=revision))
def hf_entry_known_absent(repo_id: str, filename: str, *, revision: str | None = None) -> bool:
"""True only if a prior network lookup already confirmed ``filename`` does
not exist in ``repo_id`` at the resolved revision.
Backed by ``huggingface_hub``'s own cache of negative lookups (the
``.no_exist`` marker written after a real 404), so this never makes a
network call itself. Returns ``False`` both when the file is cached and
when nothing is known yet about it, on purpose: callers in a cache-only
(``allow_network=False``) code path can use this to tell "confirmed
missing upstream, safe to use a fallback file" apart from "just never
checked yet, do not guess."
"""
from huggingface_hub import _CACHED_NO_EXIST, try_to_load_from_cache
revision = _resolve_revision(repo_id, revision)
result = try_to_load_from_cache(repo_id, filename, revision=revision)
return result is _CACHED_NO_EXIST
def create_cpu_session_options(
ort: Any,
*,

View file

@ -29,6 +29,7 @@ from ..config import TransformResult
from ..onnx_runtime import (
ONNX_CPU_ARENA_ENV,
create_cpu_session_options,
hf_entry_known_absent,
hf_hub_download_local_first,
trim_process_heap,
)
@ -721,6 +722,104 @@ def _load_modernbert_tokenizer(auto_tokenizer: Any, *, allow_download: bool) ->
raise
# Sub-state-dict keys inside a merged v2-style checkpoint (see
# scripts/export_kompress_v2_onnx.py, which this mirrors).
_MERGED_CHECKPOINT_KEYS = ("encoder_state_dict", "token_head_state_dict", "span_conv_state_dict")
def _load_merged_state_dict(model: Any, ckpt_path: str, model_id: str) -> None:
"""Load a merged v2-style checkpoint (LoRA already folded into the encoder).
The checkpoint is a dict of per-submodule state-dicts
(``encoder_state_dict`` / ``token_head_state_dict`` / ``span_conv_state_dict``)
rather than a single flat state-dict, so each piece is loaded into its
matching submodule directly instead of via a single ``load_state_dict``
call on the whole model.
"""
import torch
ckpt = torch.load(ckpt_path, map_location="cpu")
missing_sections = [k for k in _MERGED_CHECKPOINT_KEYS if k not in ckpt]
if missing_sections:
raise RuntimeError(
f"merged.pt for {model_id} is missing {missing_sections}; found keys: "
f"{sorted(ckpt)}. This checkpoint format is not what the loader expects."
)
for section, submodule in (
("encoder_state_dict", model.encoder),
("token_head_state_dict", model.token_head),
("span_conv_state_dict", model.span_conv),
):
missing, unexpected = submodule.load_state_dict(ckpt[section], strict=False)
if missing or unexpected:
raise RuntimeError(
f"{model_id} {section}: state_dict mismatch against {type(submodule).__name__} "
f"(missing={list(missing)[:5]}, unexpected={list(unexpected)[:5]}). "
"The checkpoint no longer matches HeadroomCompressorModel's architecture."
)
def _load_plain_state_dict(model: Any, weights_path: str, model_id: str) -> None:
"""Load a plain, already-merged full state-dict (the pre-v2 / non-PEFT format)."""
from safetensors.torch import load_file
state_dict = load_file(weights_path)
missing, unexpected = model.load_state_dict(state_dict, strict=False)
if missing or unexpected:
raise RuntimeError(
f"{model_id} model.safetensors: state_dict mismatch against "
f"HeadroomCompressorModel (missing={list(missing)[:5]}, "
f"unexpected={list(unexpected)[:5]}). Refusing to run with unloaded weights."
)
def _load_pytorch_weights(model: Any, model_id: str, *, allow_download: bool) -> None:
"""Load PyTorch weights into ``model``, preferring the merged v2 checkpoint.
``merged.pt`` (when the repo ships one) holds LoRA-merged sub-state-dicts
keyed by submodule name. In a PEFT-trained repo, ``model.safetensors`` is
the *unmerged* adapter checkpoint (encoder keys prefixed
``encoder.base_model.model...``) and does not map onto this module tree at
all, so it is only used as a fallback for repos that never shipped a
merged checkpoint (e.g. the original non-LoRA kompress-base).
In cache-only mode (``allow_download=False``) a ``merged.pt`` cache miss is
ambiguous: it could mean the repo has no merged checkpoint (safe to use the
plain fallback), or it could mean the repo has one but it just is not
downloaded yet (in which case a *stale* cached ``model.safetensors`` from a
prior run must not be used, since for a PEFT repo it is the wrong format).
``hf_entry_known_absent`` disambiguates without a network call, using
HuggingFace Hub's own cache of confirmed-404 lookups.
"""
try:
ckpt_path = hf_hub_download_local_first(model_id, "merged.pt", allow_network=allow_download)
except _NOT_CACHED_ERRORS as exc:
if not allow_download:
if not hf_entry_known_absent(model_id, "merged.pt"):
raise KompressModelNotCached(model_id) from exc
try:
weights_path = hf_hub_download_local_first(
model_id, "model.safetensors", allow_network=False
)
except _NOT_CACHED_ERRORS:
raise KompressModelNotCached(model_id) from exc
_load_plain_state_dict(model, weights_path, model_id)
return
if isinstance(exc, EntryNotFoundError):
# merged.pt genuinely does not exist in this repo (confirmed by a
# real network lookup, not just a cache miss) - fall back to the
# plain format instead of treating it as a download failure.
weights_path = hf_hub_download_local_first(
model_id, "model.safetensors", allow_network=allow_download
)
_load_plain_state_dict(model, weights_path, model_id)
return
raise
else:
_load_merged_state_dict(model, ckpt_path, model_id)
def _load_kompress_pytorch(
model_id: str, device: str = "auto", *, allow_download: bool = True
) -> tuple[Any, Any, str]:
@ -738,22 +837,10 @@ def _load_kompress_pytorch(
logger.info("Downloading Kompress PyTorch model from %s ...", model_id)
try:
weights_path = hf_hub_download_local_first(
model_id, "model.safetensors", allow_network=allow_download
)
except _NOT_CACHED_ERRORS as exc:
if not allow_download:
raise KompressModelNotCached(model_id) from exc
raise
HeadroomCompressorModel = _get_model_class()
model = HeadroomCompressorModel()
from safetensors.torch import load_file
state_dict = load_file(weights_path)
model.load_state_dict(state_dict, strict=False)
_load_pytorch_weights(model, model_id, allow_download=allow_download)
if device == "auto":
if torch.cuda.is_available():

View file

@ -539,3 +539,217 @@ class TestOnnxBackendPrefixGating:
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])