headroom/tests/test_onnx_runtime.py
Abhay Singh 5c561bd913
fix(onnx): stop ONNX thread pools from spinning idle cores (#2495) (#2540)
## Description

Fixes #2495 (tokensave / the proxy using ~100% of all cores). ONNX
Runtime's intra-op (and inter-op) thread pools **spin-wait on every core
between inferences** by default. Headroom is a long-lived process that
keeps ONNX models loaded — the kompress code compressor ("tokensave"),
the image technique/SigLIP routers, and the memory embedder — so once a
model is loaded, its idle thread pool keeps every core busy even when no
compression is running. That matches the report exactly: CPU climbs to
~100% of all cores "after a period of time" and the whole machine slows
down, with no obvious trigger.

`create_cpu_session_options` (the shared factory every CPU ONNX session
goes through) configured threads and the memory arena but never touched
spinning, so ORT's default (spin enabled) was in effect everywhere.

## Fix

Disable intra-op and inter-op thread spinning in
`create_cpu_session_options` so idle ORT threads block instead of
spin-waiting. This applies to every ONNX session built through the
factory (kompress + the image routers). It:

- is **best-effort per key** (wrapped in try/except) so an older ORT
build that doesn't recognize a config key still creates a session;
- is **overridable** via `HEADROOM_ONNX_ALLOW_SPINNING=1` for a
dedicated/batch box that wants ORT's peak-throughput spinning;
- does not change active-inference throughput meaningfully — blocking
threads wake on new work with only microsecond-scale latency, which is
the recommended setting for a server/proxy with idle periods.

The memory embedder already builds its own options with
`intra_op_num_threads=1`; this change is orthogonal and additionally
quiets its idle spinning if it were ever routed through the factory.

## 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

- `headroom/onnx_runtime.py`: add `ONNX_ALLOW_SPINNING_ENV` +
`onnx_thread_spinning_enabled()`; disable
`session.intra_op.allow_spinning` / `session.inter_op.allow_spinning` in
`create_cpu_session_options` unless spinning is explicitly re-enabled.
- `tests/test_onnx_runtime.py`: spinning is disabled by default (both
keys), `HEADROOM_ONNX_ALLOW_SPINNING=1` re-enables it, an explicit `0`
disables it, and a config key an older ORT rejects doesn't break session
creation.

## Testing

- [x] Unit tests pass (`pytest`)
- [x] Linting passes (`ruff check .`)
- [x] Type checking passes (`mypy headroom`)
- [x] New tests added for new functionality
- [ ] Manual testing performed

### Test Output

```text
$ python -m pytest tests/test_onnx_runtime.py -q
11 passed

# with the fix reverted the new symbols don't exist, so the spinning tests
# fail at import — the pre-fix factory left ORT's spinning at its (enabled) default

$ uvx ruff@0.15.17 check headroom/onnx_runtime.py tests/test_onnx_runtime.py
All checks passed!
$ uvx mypy@1.20.2 --ignore-missing-imports headroom/onnx_runtime.py
Success: no issues found in 1 source file
```

## Real Behavior Proof

- Environment: Windows 11, Python 3.12, project venv (`uv sync --extra
proxy`, onnxruntime 1.23.2 installed), `uvx ruff@0.15.17` / `uvx
mypy@1.20.2`, pytest in the venv.
- Exact command / steps: built a real `onnxruntime.SessionOptions` via
`create_cpu_session_options(ort)` and read back
`session.intra_op.allow_spinning` / `session.inter_op.allow_spinning`;
repeated with `HEADROOM_ONNX_ALLOW_SPINNING=1`.
- Observed result: by default both keys read back `"0"` (spinning
disabled); with `HEADROOM_ONNX_ALLOW_SPINNING=1` neither key is set
(ORT's default spinning restored). Against a real ORT the pre-fix
factory set neither key, so ORT's default (spinning enabled) applied —
the idle all-cores burn. Ran against the actual module and real
onnxruntime.
- Not tested: a live multi-hour VS Code + Claude session measuring CPU
before/after (the spinning-disable is the documented ORT remedy for
idle-CPU in a long-lived process; the config change itself is verified
end to end against real ORT).

## Review Readiness

- [x] I have performed a self-review
- [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
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] I have updated the CHANGELOG.md if applicable

---------

Co-authored-by: JD Davis <jd@jds-macbook-air.tail2a279.ts.net>
2026-08-08 01:33:57 -05:00

217 lines
7.8 KiB
Python

import os
import sys
from headroom.onnx_runtime import (
ONNX_ALLOW_SPINNING_ENV,
ONNX_CPU_ARENA_ENV,
cpu_arena_enabled,
create_cpu_session_options,
hf_entry_known_absent,
onnx_thread_spinning_enabled,
)
class _FakeSessionOptions:
def __init__(self):
self.intra_op_num_threads = None
self.inter_op_num_threads = None
self.enable_cpu_mem_arena = True
self.enable_mem_pattern = True
self.config_entries: dict[str, str] = {}
def add_session_config_entry(self, key: str, value: str) -> None:
self.config_entries[key] = value
class _FakeOrt:
SessionOptions = _FakeSessionOptions
class _FakeSessionOptionsWithoutToggles:
def __init__(self):
self.intra_op_num_threads = None
self.inter_op_num_threads = None
def add_session_config_entry(self, key: str, value: str) -> None:
# No config storage on this stand-in; ORT here just accepts the call.
return None
class _FakeOrtWithoutToggles:
SessionOptions = _FakeSessionOptionsWithoutToggles
def test_create_cpu_session_options_disables_retention_features(monkeypatch):
"""Non-Windows keeps the legacy low-RSS behavior: arena + mem pattern off."""
monkeypatch.delenv(ONNX_CPU_ARENA_ENV, raising=False)
monkeypatch.setattr(sys, "platform", "linux")
options = create_cpu_session_options(
_FakeOrt,
intra_op_num_threads=1,
inter_op_num_threads=2,
)
assert options.intra_op_num_threads == 1
assert options.inter_op_num_threads == 2
assert options.enable_cpu_mem_arena is False
assert options.enable_mem_pattern is False
def test_create_cpu_session_options_darwin_unchanged(monkeypatch):
monkeypatch.delenv(ONNX_CPU_ARENA_ENV, raising=False)
monkeypatch.setattr(sys, "platform", "darwin")
options = create_cpu_session_options(_FakeOrt)
assert options.enable_cpu_mem_arena is False
assert options.enable_mem_pattern is False
def test_create_cpu_session_options_keeps_arena_on_windows(monkeypatch):
"""Disabling the arena on Windows degrades inference by orders of
magnitude (onnxruntime#11627) — ORT defaults must stay untouched there."""
monkeypatch.delenv(ONNX_CPU_ARENA_ENV, raising=False)
monkeypatch.setattr(sys, "platform", "win32")
options = create_cpu_session_options(_FakeOrt, intra_op_num_threads=3)
assert options.enable_cpu_mem_arena is True
assert options.enable_mem_pattern is True
assert options.intra_op_num_threads == 3
def test_arena_env_override_forces_on(monkeypatch):
monkeypatch.setattr(sys, "platform", "linux")
monkeypatch.setenv(ONNX_CPU_ARENA_ENV, "1")
assert cpu_arena_enabled() is True
options = create_cpu_session_options(_FakeOrt)
assert options.enable_cpu_mem_arena is True
def test_arena_env_override_forces_off(monkeypatch):
monkeypatch.setattr(sys, "platform", "win32")
monkeypatch.setenv(ONNX_CPU_ARENA_ENV, "0")
assert cpu_arena_enabled() is False
options = create_cpu_session_options(_FakeOrt)
assert options.enable_cpu_mem_arena is False
def test_arena_env_invalid_falls_back_to_platform_default(monkeypatch):
monkeypatch.setenv(ONNX_CPU_ARENA_ENV, "bananas")
monkeypatch.setattr(sys, "platform", "win32")
assert cpu_arena_enabled() is True
monkeypatch.setattr(sys, "platform", "linux")
assert cpu_arena_enabled() is False
def test_create_cpu_session_options_handles_older_session_options(monkeypatch):
monkeypatch.delenv(ONNX_CPU_ARENA_ENV, raising=False)
monkeypatch.setattr(sys, "platform", "linux")
options = create_cpu_session_options(_FakeOrtWithoutToggles)
assert options.intra_op_num_threads is None
assert options.inter_op_num_threads is None
def test_thread_spinning_disabled_by_default(monkeypatch):
# #2495: ORT thread pools spin-wait on all cores between inferences, so a
# long-lived proxy pegs every core while idle. Disable spinning by default.
monkeypatch.delenv(ONNX_ALLOW_SPINNING_ENV, raising=False)
monkeypatch.delenv(ONNX_CPU_ARENA_ENV, raising=False)
assert onnx_thread_spinning_enabled() is False
options = create_cpu_session_options(_FakeOrt)
assert options.config_entries.get("session.intra_op.allow_spinning") == "0"
assert options.config_entries.get("session.inter_op.allow_spinning") == "0"
def test_thread_spinning_env_can_reenable(monkeypatch):
monkeypatch.setenv(ONNX_ALLOW_SPINNING_ENV, "1")
monkeypatch.delenv(ONNX_CPU_ARENA_ENV, raising=False)
assert onnx_thread_spinning_enabled() is True
options = create_cpu_session_options(_FakeOrt)
assert "session.intra_op.allow_spinning" not in options.config_entries
assert "session.inter_op.allow_spinning" not in options.config_entries
def test_thread_spinning_env_explicit_off(monkeypatch):
monkeypatch.setenv(ONNX_ALLOW_SPINNING_ENV, "0")
assert onnx_thread_spinning_enabled() is False
options = create_cpu_session_options(_FakeOrt)
assert options.config_entries.get("session.intra_op.allow_spinning") == "0"
def test_spinning_disable_is_best_effort_on_older_ort(monkeypatch):
# An ORT build that rejects the config key must not break session creation.
monkeypatch.delenv(ONNX_ALLOW_SPINNING_ENV, raising=False)
monkeypatch.setattr(sys, "platform", "linux")
class _RejectingSessionOptions(_FakeSessionOptions):
def add_session_config_entry(self, key: str, value: str) -> None:
raise RuntimeError(f"unknown config key: {key}")
class _RejectingOrt:
SessionOptions = _RejectingSessionOptions
# Must not raise.
options = create_cpu_session_options(_RejectingOrt)
assert options.enable_cpu_mem_arena is False
def _write_fake_hf_cache(
root: str, repo_id: str, revision: str, *, no_exist_files: list[str]
) -> None:
"""Build a minimal on-disk HF hub cache layout for a single repo/revision.
Mirrors the real cache structure closely enough for
``huggingface_hub.try_to_load_from_cache`` to read it: a ``refs/<name>``
pointer file, a ``snapshots/<hash>`` directory, and a
``.no_exist/<hash>/<filename>`` marker per file whose absence is cached.
"""
from huggingface_hub.file_download import repo_folder_name
repo_folder = os.path.join(root, repo_folder_name(repo_id=repo_id, repo_type="model"))
os.makedirs(os.path.join(repo_folder, "refs"), exist_ok=True)
with open(os.path.join(repo_folder, "refs", revision), "w") as f:
f.write("abc123")
os.makedirs(os.path.join(repo_folder, "snapshots", "abc123"), exist_ok=True)
no_exist_dir = os.path.join(repo_folder, ".no_exist", "abc123")
os.makedirs(no_exist_dir, exist_ok=True)
for filename in no_exist_files:
open(os.path.join(no_exist_dir, filename), "w").close()
def test_hf_entry_known_absent_true_when_404_was_cached(tmp_path, monkeypatch):
from huggingface_hub import constants
_write_fake_hf_cache(str(tmp_path), "acme/widget", "main", no_exist_files=["merged.pt"])
monkeypatch.setattr(constants, "HF_HUB_CACHE", str(tmp_path))
monkeypatch.delenv("HEADROOM_HF_PIN", raising=False)
assert hf_entry_known_absent("acme/widget", "merged.pt") is True
def test_hf_entry_known_absent_false_when_never_checked(tmp_path, monkeypatch):
from huggingface_hub import constants
_write_fake_hf_cache(str(tmp_path), "acme/widget", "main", no_exist_files=[])
monkeypatch.setattr(constants, "HF_HUB_CACHE", str(tmp_path))
monkeypatch.delenv("HEADROOM_HF_PIN", raising=False)
assert hf_entry_known_absent("acme/widget", "merged.pt") is False
def test_hf_entry_known_absent_false_when_repo_not_cached_at_all(tmp_path, monkeypatch):
from huggingface_hub import constants
monkeypatch.setattr(constants, "HF_HUB_CACHE", str(tmp_path))
monkeypatch.delenv("HEADROOM_HF_PIN", raising=False)
assert hf_entry_known_absent("nobody/nothing", "merged.pt") is False