fix: skip Magika backend on x86 CPUs without AVX2 (#1162)

## Description

Adds a narrow runtime AVX2 guard before initializing the Magika/ONNX
Runtime detector on x86/x86_64. On x86/x86_64 CPUs without AVX2,
Headroom falls back to existing non-Magika detection tiers instead of
crashing during ONNX Runtime initialization. AVX2-capable systems retain
existing behavior.

Refs #1005

## 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)
- [x] Documentation update
- [ ] Performance improvement
- [ ] Code refactoring (no functional changes)

## Changes Made

- Adds a Magika/ONNX Runtime CPU support guard before `Session::new()`.
- Returns a normal Magika init error on x86/x86_64 hosts without AVX2,
allowing the existing detection chain to fall through to non-Magika
tiers.
- Keeps AVX2-capable x86/x86_64 behavior unchanged.
- Does not apply the x86-specific AVX2 gate on non-x86 targets.
- Adds CPU-aware Rust tests and a short troubleshooting note.

## Testing

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

### Test Output

```text
$ cargo test -p headroom-core --lib --locked
833 passed; 0 failed; 1 ignored

$ cargo test --workspace --locked
passed

$ cargo clippy -p headroom-core --locked -- -D warnings
clean
```

## Real Behavior Proof

- Environment: x86_64 Linux host with AVX but no AVX2 (Intel Xeon
E5-2697 v2 on Proxmox), local build from this branch.
- Exact command / steps: `python -X faulthandler -c 'from headroom._core
import detect_content_type; print(detect_content_type("hello world"))'`
- Observed result: before — process exited with `Fatal Python error:
Illegal instruction`; after — command completed successfully returning
`DetectionResult(content_type="text", ...)`, and full `cargo test -p
headroom-core --lib --locked` passed with 833/0/1.
- Not tested: generic no-AVX CPUs, alternate ONNX Runtime builds,
non-x86 platforms.

## 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
- [x] 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

## Screenshots (if applicable)

N/A

## Additional Notes

This partially addresses #1005 by handling one concrete native crash
class: the Magika detector initializes ONNX Runtime through ort/ort-sys,
whose precompiled runtime can contain AVX2-family instructions. On
AVX-only x86_64 hosts, that initialization can SIGILL before Headroom
can fall back.

Scope:

- This does not introduce generic no-AVX wheels.
- This does not redesign Rust-core packaging.
- This does not disable the Rust core globally.
- This only prevents the Magika/ONNX detector tier from loading on
x86/x86_64 CPUs where AVX2 is unavailable.
- Non-Magika detection tiers continue to run.
- On non-x86 targets, this x86-specific AVX2 gate is not applied.

Changelog omitted: small native detector fallback fix with no public API
change.

Co-authored-by: AI Agent <ai-agent@homelab.internal>
This commit is contained in:
DM 2026-06-30 13:34:17 -05:00 committed by GitHub
parent abab3ccbfc
commit 64783d8824
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 147 additions and 19 deletions

View file

@ -28,6 +28,14 @@
//! issue. Loud-on-error stays at the [`magika_detect`] entry point for
//! callers who care; the chain swallows the err with a log line.
//!
//! # CPU compatibility
//!
//! Precompiled ONNX Runtime binaries from `ort-sys` may contain AVX2-family
//! instructions. On x86/x86_64 CPUs where AVX2 is unavailable, the magika
//! session init returns an init error before touching ONNX. The chain
//! handles this identically to any other tier-1 error — logs it and falls
//! through to Tier 2 / Tier 3.
//!
//! # SearchResults / BuildOutput
//!
//! The retired regex detector recognized grep-style search output
@ -84,6 +92,7 @@ pub fn detect(content: &str) -> ContentType {
#[cfg(test)]
mod tests {
use super::*;
use crate::transforms::magika_detector::magika_onnx_runtime_supported_by_cpu;
#[test]
fn empty_input_short_circuits_to_plain_text() {
@ -93,19 +102,38 @@ mod tests {
#[test]
fn json_array_routes_via_tier_1() {
let payload = r#"[{"id": 1}, {"id": 2}, {"id": 3}]"#;
assert_eq!(detect(payload), ContentType::JsonArray);
if !magika_onnx_runtime_supported_by_cpu() {
// On no-AVX2 hosts, magika returns Err and the chain
// falls through to Tier 2 (unidiff — no match for JSON)
// then Tier 3 (PlainText).
assert_eq!(detect(payload), ContentType::PlainText);
} else {
assert_eq!(detect(payload), ContentType::JsonArray);
}
}
#[test]
fn source_code_routes_via_tier_1() {
let py = "def hello():\n print('world')\n\nclass Foo:\n pass\n";
assert_eq!(detect(py), ContentType::SourceCode);
if !magika_onnx_runtime_supported_by_cpu() {
// Magika fallthrough — unidiff won't catch Python source,
// so the chain lands on PlainText.
assert_eq!(detect(py), ContentType::PlainText);
} else {
assert_eq!(detect(py), ContentType::SourceCode);
}
}
#[test]
fn html_routes_via_tier_1() {
let html = "<!DOCTYPE html><html><body><h1>x</h1></body></html>";
assert_eq!(detect(html), ContentType::Html);
if !magika_onnx_runtime_supported_by_cpu() {
// Magika fallthrough — unidiff won't catch HTML,
// so the chain lands on PlainText.
assert_eq!(detect(html), ContentType::PlainText);
} else {
assert_eq!(detect(html), ContentType::Html);
}
}
#[test]
@ -118,7 +146,8 @@ mod tests {
+ print(\"new\")\n";
// Either magika tags it `diff` (Tier 1 hit) or magika
// mis-classifies as text and unidiff catches it (Tier 2).
// Both paths produce GitDiff.
// On no-AVX2 hosts, magika is unavailable so Tier 2 still
// catches the diff. Both paths produce GitDiff.
assert_eq!(detect(diff), ContentType::GitDiff);
}
@ -127,7 +156,7 @@ mod tests {
// Magika often mis-classifies naked hunks (no `diff --git`
// wrapper) because the visible bytes look like ordinary
// patch lines mixed with code. Tier 2 (unidiff parser)
// catches these.
// catches these — even when magika is unavailable.
let diff = "--- a/foo.py\n\
+++ b/foo.py\n\
@@ -1,2 +1,2 @@\n\
@ -195,7 +224,13 @@ mod tests {
// YAML lives in magika's `code` group; the chain returns it
// as SourceCode so the router picks the code-aware compressor.
let yaml = "name: my-app\nversion: 1.0\ndependencies:\n - foo\n";
assert_eq!(detect(yaml), ContentType::SourceCode);
if !magika_onnx_runtime_supported_by_cpu() {
// Magika fallthrough — unidiff won't catch YAML,
// so the chain lands on PlainText.
assert_eq!(detect(yaml), ContentType::PlainText);
} else {
assert_eq!(detect(yaml), ContentType::SourceCode);
}
}
#[test]
@ -205,13 +240,21 @@ mod tests {
impl Counter {\n \
pub fn new() -> Self { Self { counts: HashMap::new() } }\n\
}\n";
assert_eq!(detect(rs), ContentType::SourceCode);
if !magika_onnx_runtime_supported_by_cpu() {
// Magika fallthrough — unidiff won't catch Rust source,
// so the chain lands on PlainText.
assert_eq!(detect(rs), ContentType::PlainText);
} else {
assert_eq!(detect(rs), ContentType::SourceCode);
}
}
#[test]
fn chain_is_deterministic_across_repeated_calls() {
// Magika returns the same label for identical input on
// repeated calls; the chain wraps that determinism.
// On no-AVX2 hosts, the chain always falls through to
// PlainText — which is equally deterministic.
let payload = r#"{"users": [{"id": 1}, {"id": 2}]}"#;
let a = detect(payload);
let b = detect(payload);

View file

@ -28,6 +28,12 @@
//! - **No router rewiring here.** PR3 lands the detector + tests
//! only. PR5 flips the ContentRouter to call us instead of the
//! regex-based [`crate::transforms::content_detector`].
//!
//! - **CPU compatibility.** The precompiled ONNX Runtime binary shipped by
//! `ort-sys` may contain AVX2-family instructions on x86/x86_64. Where
//! AVX2 is unavailable on those targets, the session init returns an
//! error early instead of crashing with SIGILL; the detection chain then
//! falls through to Tier 2 and Tier 3 normally.
use std::sync::mpsc;
use std::sync::{Mutex, OnceLock};
@ -39,6 +45,25 @@ use tracing;
use crate::transforms::content_detector::ContentType;
/// Check whether the CPU can run the precompiled ONNX Runtime binary
/// that magika depends on.
///
/// On x86/x86_64 without AVX2, the `onnxruntime` shared library shipped
/// by `ort-sys` can contain AVX2-family instructions that will SIGILL.
/// We detect this up front so the magika session init can fail gracefully
/// instead of crashing.
///
/// On non-x86 targets, this x86-specific AVX2 gate is not applied.
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub(crate) fn magika_onnx_runtime_supported_by_cpu() -> bool {
std::is_x86_feature_detected!("avx2")
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
pub(crate) fn magika_onnx_runtime_supported_by_cpu() -> bool {
true
}
/// Errors from the magika detector. Wraps the underlying `magika::Error`
/// so callers can match on whether init or inference broke without
/// pulling magika types into their imports.
@ -102,6 +127,17 @@ fn magika_init_timeout() -> Duration {
fn session() -> &'static Mutex<Result<Session, String>> {
MAGIKA_SESSION.get_or_init(|| {
// Early-out if the CPU can't run the precompiled ONNX Runtime.
// Without this check the `onnxruntime` shared library will crash
// with SIGILL on x86 CPUs lacking AVX2.
if !magika_onnx_runtime_supported_by_cpu() {
return Mutex::new(Err(
"Magika ONNX Runtime backend requires AVX2 on this platform; \
falling back to non-Magika detection"
.to_string(),
));
}
let timeout = magika_init_timeout();
let (tx, rx) = mpsc::channel();
// Run the (potentially hanging) ONNX init on a side thread so we
@ -234,9 +270,26 @@ mod tests {
use super::*;
fn assert_detect(content: &str, expected: ContentType, hint: &str) {
match magika_detect(content) {
Ok(got) => assert_eq!(got, expected, "{hint}: expected {expected:?}, got {got:?}"),
Err(e) => panic!("{hint}: detection failed: {e}"),
if !magika_onnx_runtime_supported_by_cpu() {
// On x86 hosts without AVX2 the magika session returns Err
// before any ONNX init — assert graceful degradation
// rather than panicking.
match magika_detect(content) {
Err(MagikaDetectorError::Init(msg)) => {
assert!(
msg.contains("AVX2"),
"{hint}: expected AVX2 error, got: {msg}"
);
}
other => panic!("{hint}: on no-AVX2 host expected Init(AVX2) error, got {other:?}"),
}
} else {
match magika_detect(content) {
Ok(got) => {
assert_eq!(got, expected, "{hint}: expected {expected:?}, got {got:?}")
}
Err(e) => panic!("{hint}: detection failed: {e}"),
}
}
}
@ -368,15 +421,32 @@ index abc123..def456 100644
#[test]
fn singleton_session_is_reused_across_calls() {
// Two back-to-back calls should both succeed without re-initing
// the model (no panic, no error). We can't directly observe
// the singleton hit-rate without instrumenting the test, but
// wall-clock asymmetry between the first and second call is
// strong evidence (cold ~50 ms, warm <1 ms). For the unit
// suite, just prove neither call errors.
magika_detect("hello world").unwrap();
magika_detect("def f(): pass").unwrap();
magika_detect(r#"{"a":1}"#).unwrap();
// Two back-to-back calls should reuse the same session
// (or same cached error). On AVX2 hosts the session is
// Ok and repeated calls succeed; on no-AVX2 hosts the
// session is Err and repeated calls return the same Err.
if !magika_onnx_runtime_supported_by_cpu() {
// On no-AVX2 the singleton caches the init error;
// repeated calls all return the same Init error.
let r1 = magika_detect("hello world");
let r2 = magika_detect("def f(): pass");
let r3 = magika_detect(r#"{"a":1}"#);
for r in [&r1, &r2, &r3] {
match r {
Err(MagikaDetectorError::Init(msg)) => {
assert!(msg.contains("AVX2"), "expected AVX2 error, got: {msg}");
}
other => panic!("on no-AVX2 host expected Init(AVX2) error, got {other:?}"),
}
}
} else {
// On AVX2 hosts the session loads once and all calls
// succeed. Wall-clock asymmetry (cold ~50 ms, warm
// <1 ms) confirms reuse.
magika_detect("hello world").unwrap();
magika_detect("def f(): pass").unwrap();
magika_detect(r#"{"a":1}"#).unwrap();
}
}
#[test]

View file

@ -441,6 +441,21 @@ print(f"Compressed content: {result.messages[0]['content'][:200]}...")
---
### "Native detector crashes with illegal instruction"
On some older or virtualized x86_64 CPUs, AVX2 may be unavailable. The
Magika/ONNX Runtime detector can require AVX2 through its precompiled runtime
binary. Headroom skips that detector tier on x86/x86_64 hosts without AVX2 and
falls back to non-Magika detection tiers instead of crashing.
If native startup still fails on an older CPU, set:
```bash
export HEADROOM_REQUIRE_RUST_CORE=false
```
---
## Error Reference
| Exception | Meaning | Solution |