Skip to content

[XNNPACK] Keep cpuinfo initialized for the lifetime of the EP - #34228

Open
Nopthakorn Kutawan (im-noon) wants to merge 1 commit into
microsoft:mainfrom
im-noon:fix/xnnpack-keep-cpuinfo-initialized
Open

Nopthakorn Kutawan (im-noon) wants to merge 1 commit into
microsoft:mainfrom
im-noon:fix/xnnpack-keep-cpuinfo-initialized

Conversation

@im-noon

Copy link
Copy Markdown

Description

Create the CPUIDInfo singleton in the XNNPACK EP constructor so cpuinfo stays initialized for the lifetime of the
process.

Motivation and Context

XNNPACK calls cpuinfo_initialize() / cpuinfo_deinitialize() around its hardware config lookups, which run in every
operator's reshape/setup, and cpuinfo is reference counted. ORT normally keeps a reference through the CPUIDInfo
singleton, but on Apple and Android PosixEnv doesn't hold one and CPUIDInfo is created lazily (by graph optimizers,
the thread pool or some CPU kernels). In a single-threaded session where none of those has run, e.g. an ORT format
model in a minimal build whose nodes are all taken by XNNPACK, nothing holds a reference, so every XNNPACK operator
re-detects the CPU from scratch. On macOS/iOS that is dozens of sysctl calls per operator; sampling shows
__sysctl as the hottest function, and it costs about 35 us per XNNPACK op on an Apple M-series CPU.

Measured with onnxruntime_perf_test, extended minimal build (upstream main), macOS arm64, 1 thread, P50,
Fixed-style ORT format model made of a chain of 20 3x3 Conv nodes (32 channels):

Input XNNPACK, before XNNPACK, after CPU EP
1x3x14x14 1.46 ms 0.76 ms 0.95 ms
1x3x56x56 12.20 ms 11.42 ms 12.93 ms

Before the fix XNNPACK is slower than the CPU EP on the small model; after it, faster. Models where CPU kernels also
run (and create CPUIDInfo), and full builds that run the graph optimizers, are unaffected: torchvision
MobileNetV2/V3 and ResNet18 timings are unchanged within noise in both builds.

Testing: the XNNPACK unit tests in onnxruntime_provider_test (20 tests) and the Conv/Pool/MatMul/Gemm/Softmax/Resize
op tests (1798 tests) pass in a full build with XNNPACK; onnx_test_runner results on the ORT format test models are
unchanged in the minimal build.

🤖 Generated with Claude Code

XNNPACK calls cpuinfo_initialize()/cpuinfo_deinitialize() around its
hardware config lookups in every operator's reshape/setup, and cpuinfo is
reference counted. On Apple and Android, PosixEnv doesn't hold a cpuinfo
reference and the CPUIDInfo singleton is created lazily (by the NHWC
transformer, the thread pool or some CPU kernels). In a single-threaded
session where nothing has created it yet, e.g. an ORT format model in a
minimal build whose nodes are all taken by XNNPACK, every XNNPACK op
re-detects the CPU: dozens of sysctl calls on macOS/iOS, about 35 us per op
on an Apple M-series CPU.

A chain of 20 3x3 Conv nodes (32 channels, 14x14), Fixed-style ORT format,
minimal build, 1 thread: 1.46 ms before, 0.76 ms after (CPU EP: 0.95 ms).

Create the CPUIDInfo singleton in the XNNPACK EP constructor so cpuinfo
stays initialized.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings November 14, 2026 12:25
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

A provider-level regression test is missing: existing tests do not detect removal of the cpuinfo retention fix because they either pass without it or never construct the provider.

1 open issue
What changed in this PR

This PR avoids repeated CPU detection in XNNPACK by eagerly creating ONNX Runtime’s CPUIDInfo singleton. It targets inference overhead in single-threaded, XNNPACK-only sessions where the singleton might otherwise remain uninitialized.

Changes:

  • Initialize CPUIDInfo in the XNNPACK execution provider constructor to retain cpuinfo’s reference.
  • Document why lazy initialization can cause repeated hardware detection, especially in minimal builds on Apple platforms.
File Description
onnxruntime/​core/​providers/​xnnpack/​xnnpack_execution_provider.cc Includes the CPUIDInfo header, initializes the singleton, and explains the lifetime requirement.

Review effort: Balanced

// process lifetime, but it is created lazily (by graph optimizers, the thread pool or some CPU kernels), so a
// single-threaded session whose nodes are all taken by XNNPACK, e.g. an ORT format model in a minimal build, may
// never create it.
CPUIDInfo::GetCPUIDInfo();

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants