mirror of
https://github.com/headroomlabs-ai/headroom.git
synced 2026-08-27 14:17:10 -04:00
## Description With `HEADROOM_KOMPRESS_BACKEND=onnx_coreml`, every Kompress compression call and the startup canary crash with `'_OnnxModel' object has no attribute 'parameters'`, so Kompress silently degrades to passthrough and `/health` reports `kompress: unhealthy, backend: null`. Root cause: `headroom/transforms/kompress_compressor.py` gated the ONNX-vs-PyTorch branch with an exact string match `backend == "onnx"`. But `_load_kompress_onnx` returns `onnx_coreml` (CoreML) or `onnx_cpu` — never the bare string `onnx`. So under `onnx_coreml` the code built PyTorch tensors and dispatched to a device via `next(model.parameters())`, which the `_OnnxModel` wrapper doesn't implement. This is the accelerated backend Apple Silicon users reach for, so the fast path is exactly the broken one. Fixes #2442 ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [ ] Performance improvement - [ ] Code refactoring (no functional changes) ## Changes Made - Change the four exact-match `backend == "onnx"` sites in `headroom/transforms/kompress_compressor.py` to `backend.startswith("onnx")`, matching the convention already used by `_model_device_type`: `_timed_canary`, `compress`, `compress_batch`, and the batch-parallelism guard in `_should_use_sequential_fallback`. - Update the guard comment ("ONNX CPU provider" → "ONNX EPs") since it now covers all ONNX execution providers. - Add regression tests exercising `_timed_canary` on `onnx_coreml` (must take the numpy path and never touch `.parameters()`) with a negative control proving the PyTorch branch still dispatches to a device. - Leave `CHANGELOG.md` untouched — release-please generates it from conventional commits. - Out of scope: the secondary `/health` under-reporting the issue flags as informational (deferred-preload warmup object never flips to `loaded`). ## Testing - [x] Unit tests pass (`python -m pytest tests/test_transforms/test_kompress_compressor.py::TestOnnxBackendPrefixGating -q`) - [x] Linting passes (`ruff check`, `ruff format --check` on the two changed files) - [x] Type checking passes (`mypy headroom/transforms/kompress_compressor.py --ignore-missing-imports`) - [x] New tests added for new functionality ### Test Output ```text $ python -m pytest tests/test_transforms/test_kompress_compressor.py::TestOnnxBackendPrefixGating -q collected 2 items tests\test_transforms\test_kompress_compressor.py .. [100%] 2 passed in 2.20s $ ruff check headroom/transforms/kompress_compressor.py tests/test_transforms/test_kompress_compressor.py All checks passed! ``` ## Real Behavior Proof - Environment: Windows 11, Python 3.13, local dev checkout on a branch off upstream/main (no Apple Silicon / CoreML hardware available) - Exact command / steps: Ran the new `TestOnnxBackendPrefixGating` regression; then temporarily reverted one site back to `backend == "onnx"` and re-ran to confirm the test discriminates. - Observed result: With the fix, `_timed_canary(model, tokenizer, "onnx_coreml")` returns a float and never touches `.parameters()`. Reverting one site makes the onnx_coreml test fail (it takes the `pt` tensor path and hits the paramless model), proving the test catches the exact bug. The issue reporter separately verified the fix on real Apple Silicon hardware (onnxruntime 1.27.0, CoreMLExecutionProvider): zero occurrences of the error afterward and compression completing on the CoreML session. - Not tested: End-to-end run on real CoreML hardware from this environment — reproduced via the unit-level device-dispatch seam instead; hardware confirmation is in the issue. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| __init__.py | ||
| test_code_compressor.py | ||
| test_code_compressor_cjk.py | ||
| test_content_router.py | ||
| test_detect_fallback_1123.py | ||
| test_diff_compressor.py | ||
| test_diff_compressor_rust_parity.py | ||
| test_html_extractor.py | ||
| test_kompress_compressor.py | ||
| test_kompress_deadline.py | ||
| test_kompress_remote.py | ||
| test_kompress_size_gate.py | ||
| test_ort_dylib.py | ||
| test_pipeline_waste_signal_limit.py | ||
| test_read_lifecycle.py | ||
| test_smart_crusher_attribution.py | ||
| test_smart_crusher_audit_safe.py | ||
| test_smart_crusher_bugs.py | ||
| test_smart_crusher_ccr_retrieve_exemption.py | ||
| test_smart_crusher_ccr_roundtrip.py | ||
| test_smart_crusher_lossless_default.py | ||
| test_smart_crusher_rust_parity.py | ||
| test_tag_protector.py | ||
| test_text_crusher.py | ||
| test_text_crusher_cjk_eval.py | ||
| test_text_crusher_parity.py | ||
| test_text_crusher_routing.py | ||
| test_tree_sitter_thread_safety.py | ||