Skip to content

Support native file handles for external initializers - #33660

Open
Mustapha Jaber (mustjab) wants to merge 21 commits into
mainfrom
user/mustjab/upstream-linux-file-sources
Open

Mustapha Jaber (mustjab) wants to merge 21 commits into
mainfrom
user/mustjab/upstream-linux-file-sources

Conversation

@mustjab

@mustjab Mustapha Jaber (mustjab) commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

A sandboxed process may have permission to use model files without permission to open their filesystem paths. This change adds SessionOptionsSetExternalInitializerFileHandles and its C++ SessionOptions wrapper to the stable ONNX Runtime 1.32 API so a host can register named, pre-opened external initializer files before loading an ONNX graph.

ONNX Runtime duplicates native Windows file handles or read-only POSIX file descriptors and retains them across option clones and sessions. Initializer ranges are loaded or mapped from those handles without resolving or canonicalizing filesystem paths. On Windows WebGPU, the D3D12 accelerated external data loader reopens registered handles directly before falling back to CPU-mapped initializers. Missing sources and invalid ranges fail explicitly rather than falling back to disk access, and nested-graph validation inspects initializer metadata without copying mapped weight payloads.

The API accepts flat relative external-data names and supports graph initializers, including subgraphs. External sparse tensors, tensor attributes, model-local function data, and model packages are rejected. Minimal builds and unsupported platforms return ORT_NOT_IMPLEMENTED. Path-based loading, legacy ORT-format loading, and execution-provider selection are unchanged.

Validated on Linux and Windows. All 31 shared-library session-options and file-source tests pass in the full build and 12 capability tests pass in the minimal build, alongside end-to-end Linux CPU and Windows WebGPU D3D12 model-loading benchmarks.

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

External sparse tensors and model-local function constants can bypass descriptor-registry validation and trigger filesystem reads even when the registry is empty.

4 open issues
What changed in this PR

Adds an experimental Linux API for loading ONNX external initializers from pre-opened file descriptors, allowing hosts to authorize model assets before filesystem lockdown without changing the stable API table.

Changes:

  • Registers named, read-only descriptors with atomic replacement and ownership retained across option clones and sessions.
  • Maps registered initializer ranges into CPU tensors, copying small or unaligned data and rejecting unsupported loading modes.
  • Adds API validation and lifetime tests, minimal-build capability checks, and selected WebGPU adapter logging.
File Description
onnxruntime/​test/​shared_lib/​test_experimental_api.cc Tests registration, ownership, strict loading, and unsupported builds.
onnxruntime/​core/​session/​inference_session.cc Passes registered sources into model loading.
onnxruntime/​core/​session/​experimental_c_api.cc Implements descriptor registration and argument validation.
onnxruntime/​core/​providers/​webgpu/​webgpu_context.cc Logs the selected adapter at INFO level.
onnxruntime/​core/​platform/​posix/​env.cc Duplicates and validates read-only regular-file descriptors.
onnxruntime/​core/​platform/​env.h Adds the descriptor-duplication interface.
onnxruntime/​core/​graph/​model.h Carries file sources in model options.
onnxruntime/​core/​graph/​model.cc Validates external attributes and initiates source injection.
onnxruntime/​core/​graph/​graph.cc Maps initializer data and retains tensor backing storage.
onnxruntime/​core/​framework/​session_options.h Adds trailing, shared file-source state.
onnxruntime/​core/​framework/​session_options.cc Rejects mixing file sources with memory buffers.
include/​onnxruntime/​core/​session/​onnxruntime_experimental_c_api.inc Declares and documents the experimental API.
include/​onnxruntime/​core/​graph/​graph.h Declares graph-level file-source injection.

Review effort: Balanced

Comment thread onnxruntime/core/graph/model.cc Resolved
Comment thread onnxruntime/core/graph/model.cc Resolved
Comment thread onnxruntime/core/graph/graph.cc Resolved
Comment thread onnxruntime/core/graph/graph.cc Resolved
Register owned read-only descriptors through the experimental API, map bounded initializer ranges before graph resolution, and reject missing sources without reopening paths. Preserve existing loading when no source registry is supplied. Include selected-adapter diagnostics for hardware validation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Convert the external-data PathString before formatting diagnostics so the descriptor loader compiles with Windows wide-character paths. Assert that missing-source diagnostics retain the model's external-data filename.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Exercise inference parity after closing the original descriptor and releasing session options, preserve valid registrations after a failed replacement, reject path-only descriptors, and cover unsupported builds. Clarify empty-registry and unsupported-build semantics in the experimental API contract.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The descriptor API is unavailable in minimal builds, so do not retain its session/model state or descriptor-duplication virtual implementation there. Preserve the existing three-argument ModelOptions constructor and keep the experimental API's explicit unsupported status. Validate both the full runtime and a real no-operators, exceptions-disabled minimal shared library.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Omit the unavailable descriptor entry and its stub from minimal experimental lookup instead of retaining unsupported functionality in the minimal binary. Keep explicit NotImplemented status on unsupported non-minimal builds and test capability absence against a real minimal runtime. Full Linux descriptor inference is unchanged; do not relax the Android size budget.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Avoid MSVC C4127 when the descriptor initializer loader checks the host byte order. Preserve the same unsupported-endianness error without disabling warnings or changing initializer loading.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Append default-initialized file-source state after existing SessionOptions members instead of shifting positional aggregate fields. The unchanged training-runner initializer now compiles with missing-field warnings treated as errors. Minimal layouts remain unchanged and the full runtime retains descriptor ownership.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Extend named initializer sources to Windows, Linux, Android, macOS and iOS. Duplicate native Windows handles with read-only rights and reuse copy-on-write range mappings; retain CLOEXEC read-only descriptor ownership on POSIX. Run the existing API ownership and inference tests across supported platforms.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mustjab
Mustapha Jaber (mustjab) force-pushed the user/mustjab/upstream-linux-file-sources branch from 7bc1b7a to 20c10de Compare October 28, 2026 04:48
@mustjab Mustapha Jaber (mustjab) changed the title Add experimental Linux external initializer file-descriptor sources Support native file handles for external initializers Oct 28, 2026

@github-actions github-actions Bot 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.

You can commit the suggested changes from lintrunner.

Apply the single-space alignment requested by the existing clang-format CI check. No code tokens or runtime behavior change.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Create the writable Windows test file with a native delete-on-close handle in the per-user temporary directory, and use secure wide-character CRT opening for the read-only fixture. Avoid C4996 without suppressing warnings or weakening handle ownership and inference checks. All11 full and8 minimal API cases pass locally; Windows CI remains required.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Reject external sparse tensors and external function data before schema inference or Graph construction in both Model paths. Validate zero-byte offsets against source length. Add sparse/default-function/nested-graph regressions and256-byte aligned/unaligned initializer lifetime inference tests to the existing shared-library suite.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

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

Duplicate initializer names can bypass registry checks, allowing an external tensor’s location to reach filesystem validation despite an empty registry and violating the no-filesystem-fallback contract.

1 open issue
4 resolved since last review

Review effort: Balanced

Comment thread onnxruntime/core/graph/graph.cc Resolved
Validate initializer-name uniqueness before Graph maps can hide protobuf entries. Cover external/embedded duplicates in both orders with an empty registry, and document the strict contract. All16 official API cases and8 minimal capability checks pass locally.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

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.

🔵 Needs a closer look

BOOL initializers larger than 127 bytes bypass normalization, allowing noncanonical bytes to reach boolean consumers and produce inconsistent results.

No open issues 🎉

4 previously missed issues

In code that hasn't changed since last review

High severity Normalize large BOOL external initializers

onnxruntime/​core/​graph/​graph.cc:4502

BOOL initializers larger than 127 bytes retain their file bytes in a graph-owned OrtValue, which is reused or uploaded without the usual boolean normalization. Noncanonical bytes such as 0x02 and 0xFF can therefore reach boolean consumers and produce inconsistent results. Normalize the tensor before conversion and add a regression above the inline-size threshold.

Medium severity Subgraph inference still validates unavailable external paths

onnxruntime/​core/​graph/​graph.cc:4460

Subgraph injection updates copied node attributes but leaves the original NodeProto used by the first Resolve() unchanged. ONNX checking therefore still validates external initializer pathnames in If/Loop subgraphs, so a registered handle does not suffice when the original path is unavailable. Make checking consume an updated representation with mapped data safely inlined, and add a successful subgraph-inference regression with the weight path unavailable.

Medium severity Detect external initializer and Constant name collisions

onnxruntime/​core/​graph/​model.cc:90

An external initializer X plus an embedded Constant producing X passes this preflight, but Graph construction replaces the lookup entry with the Constant's initializer. File-source injection then misses the external tensor, and later validation accesses its pathname even with an empty registry. Include Constant outputs in the duplicate-name check and add this collision to the regression tests.

Medium severity Finalization drops external initializer source registry

onnxruntime/​core/​session/​inference_session.cc:1507

With ORT_LOAD_CONFIG_FROM_MODEL=1, FinalizeSessionOptions() replaces the caller's options and discards external_initializer_file_sources. This load path then skips preflight and injection and falls back to pathname loading, even when the caller installed an empty registry to prohibit fallback. Preserve the registry when finalizing model-supplied options, or reject this combination before external-data access.

1 resolved since last review

Review effort: Balanced

Normalize BOOL initializers in owned storage, check updated subgraphs with safely inlined data, reject Constant outputs hiding external initializers, and retain source capabilities when model metadata supplies session options. Each of the four new public API regressions failed against the previous runtime and passes with these fixes. Also verified 11 legacy external-data cases, minimal capability omission, and the 285-case GenAI native suite.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Preserve the existing checker representation for legacy ORT-format models. Only graphs initialized through registered file sources materialize updated subgraphs, avoiding invalid ONNX name checks on ORT-format Loop bodies while keeping unavailable-path file-source inference working.

Added public API tests first and confirmed both fail against the previous runtime with the exact Loop graph-name error. Cover path/buffer/no-copy/mmap inference and ONNX-to-ORT serialization/reload, verifying123-to125 outputs. After the fix,38 official API/ORT-format/legacy cases,8 checks against an actual minimal rebuild, and32 focused GenAI memory/file-source cases all pass without skips.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

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

Validation of file-source models with If/Loop subgraphs copies entire initializer payloads into temporary buffers, with nested control flow repeating the copies and risking memory exhaustion.

1 open issue

Review effort: Balanced

Comment thread onnxruntime/core/graph/graph.cc Resolved
Use a checker-only representation for file-source graphs. Rebind already-validated mapped initializer names as typed inputs in the temporary graph while preserving the live graph, inference buffers, ordinary initializer checks and legacy ORT behavior. Recurse through current If/Loop subgraphs without inlining mapped tensor payloads.

Add a fresh-process Linux RSS regression using32MiB external weights: the previous implementation grew by130MiB at depth1 and514MiB at depth3, exceeding a16MiB budget. The corrected checker stays below11MiB in repeated runs. Keep this unsanitized RSS budget separate from sanitizer shadow/quarantine accounting; portable nested If, Loop and malformed-graph tests remain enabled. All40 related official ORT cases pass three times, plus8 checks against a minimal rebuild and32 focused GenAI cases including13 native-handle cases.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

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.

🔵 Needs a closer look

Human assessment is needed because graph-list attributes can bypass registry-backed external-data handling, compounded by duplicate-attribute validation gaps and size-dependent initializer override behavior.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

…ndles

Retain registered RandomAccessFile provenance on Graph for aligned numeric external initializers and route eligible device-allocated weights through IExternalDataLoader before falling back to CPU-mapped OrtValues. Extend D3D12AcceleratedExternalDataLoader and D3D12FileBufferLoader to reopen pre-opened Windows file handles with overlapped unbuffered and tail-buffered flags without path lookup.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@mustjab
Mustapha Jaber (mustjab) force-pushed the user/mustjab/upstream-linux-file-sources branch from 628dedc to 810ea43 Compare October 30, 2026 23:22

@edgchen1 Edward Chen (edgchen1) 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.

Reviewed this head with bounded domain specialists, a separate integration reviewer, and independent verification. Posting seven verified findings: three medium correctness findings, two low internal/validation correctness findings, and two low missing-test findings. Existing matching-head Windows GPU and Linux CPU test logs were inspected; local validation was limited to static proofs and native Windows primitive probes, not ORT/GPU session execution.

🤖 Written by GitHub Copilot on behalf of Edward Chen (@edgchen1).

Comment thread onnxruntime/core/graph/graph.cc Resolved
Comment thread onnxruntime/core/graph/graph.cc Resolved
Comment thread onnxruntime/core/graph/model.cc Resolved
Expose OrtFileHandle and SessionOptionsSetExternalInitializerFileHandles directly on OrtApi and Ort::SessionOptionsImpl in ORT 1.32, remove the experimental C API lookup entries, and move the shared-library file-source tests to test_session_options.cc.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Convert logical file-source names to UTF-8 via PathToUTF8String in D3D12
  external data loader diagnostics while retaining filesystem::path only for
  normalized batch keys.
- Reject non-empty embedded TensorProto data fields on EXTERNAL initializers
  before replacing them in Graph::InjectExternalInitializersFromFileSources.
- Preserve ExternalInitializerFileSource provenance across Graph::InlineIfSubgraph,
  MemcpyTransformer initializer duplication (including outer-scope lookup with
  local-shadowing semantics), and graph_utils initializer copy helpers.
- Reject dense external initializers inside unsupported GRAPHS attributes and
  their nested GRAPH descendants during ValidateFileSourceModel prevalidation.
- Satisfy WindowsRandomAccessFile::Read via read-only mapped views for handles
  duplicated by DuplicateReadOnlyFileHandle so shared synchronous FILE_OBJECT
  cursors are never modified.
- Expand unit test coverage across test_session_options.cc,
  external_data_loader_test.cc, and webgpu_context_test.cc.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

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

Model-package loading fails to reject external-initializer registries, including empty ones, and materializing small external weights causes subsequent initializer overrides to fail.

2 open issues

Review effort: Balanced

Comment thread onnxruntime/core/graph/graph.cc Resolved
…zer overrides

- Reject non-null external_initializer_file_sources registries (including
  empty registries) in CreateModelPackageOptionsFromSessionOptions,
  ModelPackageOptions, OrtModelPackageAPI::CreateSession, and
  CreateSessionForModelPackage.
- Retain external provenance for small (<= 127B), BOOL, and unaligned
  file-source initializers so subsequent AddExternalInitializers overrides
  succeed while still rejecting non-external initializers and withholding
  file-source loader eligibility when unmapped.
- Add unit tests in test_session_options.cc covering model package rejection
  and AddExternalInitializers overrides for both small and large weights.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

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.

🔵 Needs a closer look

Human assessment is needed because an unpublished critical finding indicates that exception-dependent test code prevents full ORT_NO_EXCEPTIONS builds from compiling.

No open issues 🎉

2 resolved since last review

Review effort: Balanced

…ONS builds

Replace C++ try/catch/throw and iostream exception masks in
FileSourcesSessionOptionsTest with C API OrtStatus checks and ORT_ENFORCE so
full builds with onnxruntime_DISABLE_EXCEPTIONS=ON (-DORT_NO_EXCEPTIONS
-fno-exceptions) compile and run all file-source tests cleanly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

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

Handle-based loading linearly scans the initializer array for each replacement, causing quadratic work when many initializers are stored externally.

1 open issue

Review effort: Balanced

Comment thread onnxruntime/core/graph/graph.cc Resolved
Iterate directly over graph_proto_->mutable_initializer() in
Graph::InjectExternalInitializersFromFileSources and pass the mutable
TensorProto* to ReplaceInitializedTensorImpl so each replacement verifies map
consistency in O(1) without scanning mutable_initializers.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

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.

🔵 Needs a closer look

Human assessment is needed because a critical initializer-copying issue can break mixed-provider control-flow compilation, while invalid-name tests fail to detect name-validation regressions.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

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.

3 participants