Skip to content

[WebGPU] Accelerated model loading using Win32 and D3D12 - #33055

Merged
Sushanth Rajasankar (sushraja-msft) merged 46 commits into
mainfrom
user/sushraja/d3d12_file_loading
Oct 9, 2026
Merged

Sushanth Rajasankar (sushraja-msft) merged 46 commits into
mainfrom
user/sushraja/d3d12_file_loading

Conversation

@sushraja-msft

@sushraja-msft Sushanth Rajasankar (sushraja-msft) commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Description

Large ONNX models often store weights in external files. The existing native WebGPU loading path materializes each weight in tensor-sized, CPU-accessible memory before uploading it to the GPU. For large tensors and multi-gigabyte models, these allocations increase CPU memory pressure, address-space activity, and session creation time.

This change adds an accelerated Windows loading path that streams external weights through a bounded pool of twelve reusable 16 MiB Direct3D 12 (D3D12) upload buffers. File reads and GPU copies operate in chunks, so staging memory remains bounded instead of scaling with individual tensor sizes. The loader copies each chunk into its final GPU-local tensor resource, which WebGPU then imports into Dawn.

External weight file
  -> mapped D3D12 upload buffer
  -> D3D12 copy queue
  -> GPU-local tensor resource
  -> Dawn shared-buffer import

File reads, GPU copies, and upload-buffer reuse overlap through Windows events and D3D12 fences. Weight payloads are not materialized in application CPU memory, and the application does not perform an additional CPU payload copy.

Design

The reusable loader, onnxruntime::windows::d3d12::D3D12FileBufferLoader, is part of the shared Windows platform layer. It does not depend on Dawn, WebGPU, ONNX Runtime tensors, or execution-provider interfaces.

The WebGPU integration:

  • Selects the requested physical adapter through DirectX Graphics Infrastructure (DXGI).
  • Pins Dawn and the loading device to the same adapter by locally unique identifier (LUID).
  • Prepares external tensor ranges and invokes the shared loader.
  • Imports completed D3D12 resources into Dawn.
  • Retains the D3D12 resources and heaps for the required lifetime.

The shared loader provides:

  • Overlapped, unbuffered Win32 file reads.
  • Twelve persistent 16 MiB mapped upload buffers.
  • Event-driven read completion and D3D12 copy-queue uploads.
  • Overlap between destination allocation and the initial read wave.
  • Placed resources packed into heaps targeted at no more than 512 MiB.
  • Committed resources for tensors that are too large for those heaps.
  • Support for multiple external-data files and non-contiguous ranges.
  • GPU-side handling of aligned tensor padding.
  • Bounds validation, cancellation, error propagation, and explicit resource ownership.

Provider option

Enable the feature with:

{
  "weightLoadAcceleration": "preferred"
}

Supported values:

  • off: Use the existing initializer loading path.
  • preferred: Use accelerated loading when available; otherwise, fall back to the existing path.
  • required: Require accelerated loading and return the underlying error if initialization fails.

Cancellation is always propagated and never triggers fallback.

Performance

Measurements used a decoder-only model with:

  • 454 external tensors.
  • 1,291,602,752 bytes of weight data.
  • 452 of 453 adjacent tensor boundaries contiguous.
  • 3,072 non-tensor bytes within the covered file range.

Test system:

  • Intel Core Ultra 9 285K.
  • Intel Graphics (0x8086:0x7D67).
  • NVIDIA GeForce RTX 4070 (0x10DE:0x2709).
  • SK hynix PC801 NVMe solid-state drive.

ONNX Runtime session creation

Nine fresh onnxruntime_perf_test processes were run for each mode and GPU preference.

GPU preference GPU off median preferred median Improvement
High performance NVIDIA GeForce RTX 4070 1.562 s 0.715 s 846 ms / 54.2%
Low power Intel Graphics 1.420 s 0.669 s 751 ms / 52.9%

Observed ranges, including the first process:

GPU off range preferred range
NVIDIA 1.534-2.826 s 0.665-0.840 s
Intel 1.400-2.430 s 0.605-0.783 s

The first off process on each GPU was a cold-start outlier. The required mode uses the same loading path as preferred; only its fallback policy differs.

Standalone comparison with DirectStorage

A 15-repetition comparison alternated the path order, excluded the first run of each path from the steady-state median, and verified every destination byte.

GPU DirectStorage This implementation Difference
NVIDIA 225.097 ms 187.070 ms 38.028 ms / 16.9% faster
Intel 262.044 ms 241.538 ms 20.506 ms / 7.8% faster

Diagnostics indicate that the new path is storage-bound:

GPU Raw unbuffered read GPU copy only Approximate rates
NVIDIA 182.050 ms 52.087 ms 7.10 GB/s read, 24.80 GB/s copy
Intel 210.477 ms 68.945 ms 6.14 GB/s read, 18.73 GB/s copy

Byte-for-byte verification passed for all 1,291,602,752 payload bytes on both loading paths and both GPUs.

Validation

  • Static, shared, and external Dawn provider and test targets build successfully.
  • External-data-loader lifecycle and manager tests pass.
  • WebGPU context and provider tests pass with static and shared Dawn configurations.
  • Device-precondition tests skip where expected.

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.

Copilot review overview

🟡 Changes recommended

Adapter selection and reused-context behavior can prevent the requested acceleration, and critical execution paths lack reliable regression coverage.

Review effort: Balanced
Findings: 2 Medium severity · 2 Low severity

Open (4)
What changed in this PR

Adds Windows D3D12-accelerated external-weight loading to the native WebGPU EP.

Changes:

  • Adds a reusable Win32/D3D12 file-to-GPU loader.
  • Extends external-loader lifecycle and WebGPU provider options.
  • Adds build integration and regression coverage.
File Description
onnxruntime/​test/​webgpu/​external_dawn/​main.cc Enables preferred acceleration in external-Dawn testing.
onnxruntime/​test/​webgpu/​delay_load/​main.cc Exercises the new provider option.
onnxruntime/​test/​providers/​webgpu/​webgpu_context_test.cc Adds WebGPU acceleration tests.
onnxruntime/​test/​framework/​external_data_loader_test.cc Tests loader batch lifecycle.
onnxruntime/​core/​providers/​webgpu/​webgpu_provider_options.h Defines acceleration modes and option keys.
onnxruntime/​core/​providers/​webgpu/​webgpu_provider_factory.cc Parses and propagates the option.
onnxruntime/​core/​providers/​webgpu/​webgpu_execution_provider.h Adds accelerated-loader state.
onnxruntime/​core/​providers/​webgpu/​webgpu_execution_provider.cc Creates the loader and allocator.
onnxruntime/​core/​providers/​webgpu/​webgpu_context.h Adds D3D12 context state.
onnxruntime/​core/​providers/​webgpu/​webgpu_context.cc Implements adapter selection and synchronization.
onnxruntime/​core/​providers/​webgpu/​external_data_loader.h Updates the loader interface.
onnxruntime/​core/​providers/​webgpu/​external_data_loader.cc Adapts the standard WebGPU loader.
onnxruntime/​core/​providers/​webgpu/​d3d12_external_data_loader.h Declares the accelerated adapter.
onnxruntime/​core/​providers/​webgpu/​d3d12_external_data_loader.cc Imports D3D12 resources into Dawn.
onnxruntime/​core/​providers/​js/​external_data_loader.h Updates the JS loader signature.
onnxruntime/​core/​providers/​js/​external_data_loader.cc Adapts JS loading.
onnxruntime/​core/​providers/​cuda/​cuda_external_data_loader.h Updates the CUDA loader signature.
onnxruntime/​core/​providers/​cuda/​cuda_external_data_loader.cc Adapts CUDA loading.
onnxruntime/​core/​platform/​windows/​d3d12_file_loader/​d3d12_file_buffer_loader.h Declares the reusable file loader.
onnxruntime/​core/​platform/​windows/​d3d12_file_loader/​d3d12_file_buffer_loader.cc Implements asynchronous file-to-GPU loading.
onnxruntime/​core/​framework/​tensorprotoutils.h Declares preparation/loading helpers.
onnxruntime/​core/​framework/​tensorprotoutils.cc Resolves and prepares external tensor ranges.
onnxruntime/​core/​framework/​session_state_utils.cc Integrates batched loading into initialization.
onnxruntime/​core/​framework/​external_data_loader.h Extends the loader lifecycle API.
onnxruntime/​core/​framework/​external_data_loader_manager.h Declares lifecycle coordination.
onnxruntime/​core/​framework/​external_data_loader_manager.cc Coordinates loaders and fallback.
js/​node/​test/​api/​simple-api-tests.ts Tests Node option forwarding.
js/​node/​src/​session_options_helper.cc Accepts the Node provider option.
js/​common/​lib/​inference-session.ts Documents the TypeScript option.
cmake/​onnxruntime_providers_webgpu.cmake Links D3D12 loading into WebGPU.
cmake/​onnxruntime_d3d12_file_loader.cmake Defines the loader target.
cmake/​CMakeLists.txt Adds the build option.
.github/​workflows/​windows_webgpu.yml Enables the feature in Windows CI.

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread onnxruntime/core/providers/webgpu/webgpu_context.cc Outdated
Comment thread onnxruntime/core/providers/webgpu/webgpu_context.cc
Comment thread onnxruntime/test/providers/webgpu/webgpu_context_test.cc

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.

Comment thread onnxruntime/core/providers/webgpu/webgpu_context.cc Outdated
Comment thread onnxruntime/core/providers/webgpu/webgpu_context.cc
Comment thread js/node/test/api/simple-api-tests.ts
Comment thread onnxruntime/core/platform/windows/d3d12_file_loader/d3d12_file_buffer_loader.cc Outdated
Comment thread onnxruntime/core/providers/webgpu/webgpu_provider_factory.cc Outdated

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.

Comment thread onnxruntime/core/providers/webgpu/webgpu_execution_provider.cc
Comment thread onnxruntime/core/providers/webgpu/d3d12_external_data_loader.cc Outdated
Comment thread onnxruntime/core/providers/webgpu/webgpu_context.cc Outdated

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.

Comment thread onnxruntime/core/platform/windows/d3d12_file_loader/d3d12_file_buffer_loader.cc Outdated
Comment thread onnxruntime/core/providers/webgpu/d3d12_external_data_loader.cc

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.

Copilot review overview

🟡 Changes recommended

Imported buffers expose the wrong size, and device-free tests are order-dependent.

Review effort: Balanced
Findings: 1 High severity

Open (1)
Resolved since last review (2)
Previously missed (1)

In code that hasn't changed since last review

Medium severity Compile-only tests reuse a device-bearing global context

onnxruntime/​test/​providers/​webgpu/​webgpu_context_test.cc:130

Setting compile_only here does not guarantee a device-free context because default context 0 is process-global and initialized via call_once; earlier tests may already have created its device. Consequently these three tests do not reliably exercise the advertised no-device paths, and the required test can observe a non-null loader. Follow the established CompileOnlyContextDoesNotCreateDevice pattern and run each case through RunWithFreshDefaultContext(..., /*compile_only_parent=*/true).

Comment thread onnxruntime/core/providers/webgpu/d3d12_external_data_loader.cc Outdated

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.

Copilot review overview

🔵 Needs a closer look

Reused external-Dawn contexts cannot enable acceleration after initially being created with the option off.

Review effort: Balanced
Findings: None

Resolved since last review (1)
Previously missed (1)

In code that hasn't changed since last review

Medium severity External-Dawn contexts cannot enable acceleration after initial off

onnxruntime/​core/​providers/​webgpu/​webgpu_context.cc:465

In an external-Dawn build, a live default context that was first created with acceleration off can never enable it in a later session. The one-time initialization skips D3D12-device preselection when the first mode is off, and this recovery path is excluded under USE_EXTERNAL_DAWN, so WeightLoadingD3D12Device() remains null and a later required session fails (while the new “enabled after initial off” test merely skips on that failure). Please provide a matching D3D12 device for reused external-Dawn contexts, or reject/document that configuration and make the test assert the intended behavior rather than skipping it.

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.

Copilot review overview

🔵 Needs a closer look

The Node.js portability assertion mishandles expected no-GPU errors, and key D3D12 allocation branches lack focused coverage.

Review effort: Balanced
Findings: None

Previously missed (2)

In code that hasn't changed since last review

Medium severity Allow acceleration-specific errors in portability test

js/​node/​test/​api/​simple-api-tests.ts:115

On Windows builds with D3D12 file loading but no usable GPU, off fails with Dawn's adapter error, while required can fail earlier with the expected acceleration-specific DXGI/D3D12 error. Because unavailableProviderError is then set, this branch incorrectly requires those distinct valid errors to be identical, so the portability test fails on the exact device-unavailable configuration it intends to tolerate. Allow required to report either the baseline provider error or an acceleration-specific error.

Low severity Test heap rollover and committed-resource loading paths

onnxruntime/​core/​platform/​windows/​d3d12_file_loader/​d3d12_file_buffer_loader.cc:461

The only direct loader test uses the default 512 MiB heap limit, so it exercises a placed resource in one heap but never this committed-resource branch or the rollover to a second heap. These are advertised loading paths with distinct ownership/allocation behavior. Add a focused test using a small max_destination_heap_size (for example 64 KiB) and small ranges that force both heap rollover and a committed destination, then verify every output byte.

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.

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Copilot review overview

Review effort: Lite
Findings: 2 Low severity

Open (2)

Comment thread onnxruntime/core/providers/webgpu/webgpu_context.h Outdated

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.

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Copilot review overview

Review effort: Lite
Findings: 2 Medium severity

Open (2)
Resolved since last review (2)

Comment thread onnxruntime/core/framework/session_state_utils.cc Outdated
Comment thread onnxruntime/core/framework/session_state_utils.cc

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.

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Copilot review overview

Review effort: Lite
Findings: 3 Medium severity

Open (3)
Resolved since last review (2)

Comment thread onnxruntime/core/framework/session_state_utils.cc
Comment thread onnxruntime/core/framework/session_state_utils.cc
Comment thread onnxruntime/core/providers/webgpu/d3d12_external_data_loader.cc Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Mark the allocator parameter unused when D3D12 file loading is disabled so warning-as-error builds compile cleanly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use explicit source, read-plan, staging, and destination names. Store destination resources directly in BufferCollection. Detach destination resources when GPU completion cannot be tracked, and reject further loads.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Make the allocator-aware LoadTensor overload available to WebAssembly loaders while restricting batch preparation hooks to D3D12 file loading.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Explain why copy encoding shares the recording lock with public allocator operations.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove the test that shares one unsupported low-level command recording state across threads. Retain the independent data transfer coverage that matches the supported ownership model.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Propagate the WebGPU context ID through the accelerated allocator and external loader so their memory identity matches the current main branch interfaces.

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

@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.

Comment thread onnxruntime/core/providers/webgpu/webgpu_execution_provider.cc Outdated
Comment thread onnxruntime/core/providers/webgpu/webgpu_provider_factory.cc Outdated
Preserve uncaptured error and device-loss state for checked completion, and apply the required C++ formatting.

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

@qjia7 Jiajia Qin (qjia7) 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.

LGTM

Route memory-backed external data through ordinary loaders while excluding tensor-creating file loaders such as D3D12 acceleration.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@sushraja-msft
Sushanth Rajasankar (sushraja-msft) merged commit f289d7a into main Oct 9, 2026
105 of 108 checks passed
@sushraja-msft
Sushanth Rajasankar (sushraja-msft) deleted the user/sushraja/d3d12_file_loading branch October 9, 2026 08:25
Gopalakrishnan Nallasamy (GopalakrishnanN) added a commit to GopalakrishnanN/onnxruntime that referenced this pull request Oct 9, 2026
…gistration

Both WebGPU compile variants reject ModelPath at the D3D12 candidate-registration call added by upstream microsoft#33055. Integrate the exact already-tested upstream base 0066c38, align the helper declaration and implementation with ModelPath, and explicitly project Path() only for existing pathname parsing. Retain the ModelPath object for file validation rather than introducing an implicit conversion that loses its captured-directory context. Add a feature-guarded candidate-registration regression. Full production caller/helper and regression translation units compile with ENABLE_D3D12_FILE_LOADING and MSVC /W4 /WX.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

4 participants