Repository navigation
[WebGPU] Accelerated model loading using Win32 and D3D12 - #32444
Sushanth Rajasankar (sushraja-msft) wants to merge 34 commits into
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Build-definition propagation, required-pipelined semantics, cleanup, empty tensors, and coverage have unresolved issues.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Adds experimental DirectStorage-based external-weight loading for native Windows WebGPU.
Changes:
- Adds batched disk-to-GPU loading and Dawn resource import.
- Extends external-data loader lifecycle and asynchronous WebGPU initialization.
- Adds configuration, packaging, benchmark support, and tests.
File summaries
| File | Description |
|---|---|
tools/nuget/generate_nuspec_for_native_nuget.py |
Packages DirectStorage runtimes. |
onnxruntime/test/providers/webgpu/webgpu_context_test.cc |
Tests acceleration modes. |
onnxruntime/test/perftest/ort_test_session.cc |
Passes WebGPU benchmark options. |
onnxruntime/test/framework/external_data_loader_test.cc |
Tests loader batch lifecycle. |
onnxruntime/core/session/inference_session.h |
Tracks preload state. |
onnxruntime/core/session/inference_session.cc |
Starts external-data preloading. |
onnxruntime/core/providers/webgpu/webgpu_provider_options.h |
Defines acceleration policies. |
onnxruntime/core/providers/webgpu/webgpu_provider_factory.cc |
Parses the new option. |
onnxruntime/core/providers/webgpu/webgpu_execution_provider.h |
Adds DirectStorage state. |
onnxruntime/core/providers/webgpu/webgpu_execution_provider.cc |
Integrates loader and allocator. |
onnxruntime/core/providers/webgpu/webgpu_context.h |
Adds asynchronous initialization state. |
onnxruntime/core/providers/webgpu/webgpu_context.cc |
Implements pipelined device initialization. |
onnxruntime/core/providers/webgpu/external_data_loader.h |
Updates loader signature. |
onnxruntime/core/providers/webgpu/external_data_loader.cc |
Adapts WebAssembly loader. |
onnxruntime/core/providers/webgpu/direct_storage_external_data_loader.h |
Declares DirectStorage loading APIs. |
onnxruntime/core/providers/webgpu/direct_storage_external_data_loader.cc |
Implements batched loading and import. |
onnxruntime/core/providers/webgpu/data_transfer.h |
Defers buffer-manager lookup. |
onnxruntime/core/providers/webgpu/data_transfer.cc |
Uses deferred lookup. |
onnxruntime/core/providers/webgpu/allocator.h |
Defers UMA detection. |
onnxruntime/core/providers/webgpu/allocator.cc |
Initializes allocation mode lazily. |
onnxruntime/core/providers/js/external_data_loader.h |
Updates JS loader signature. |
onnxruntime/core/providers/js/external_data_loader.cc |
Adapts JS loader implementation. |
onnxruntime/core/framework/tensorprotoutils.h |
Adds external-data preparation API. |
onnxruntime/core/framework/tensorprotoutils.cc |
Validates and prepares external tensors. |
onnxruntime/core/framework/session_state_utils.cc |
Batches initializer preparation/loading. |
onnxruntime/core/framework/external_data_loader.h |
Extends loader lifecycle interface. |
onnxruntime/core/framework/external_data_loader.cc |
Provides default lifecycle hooks. |
onnxruntime/core/framework/external_data_loader_manager.h |
Adds preload and batch management. |
onnxruntime/core/framework/external_data_loader_manager.cc |
Coordinates loader lifecycles. |
cmake/onnxruntime_providers_webgpu.cmake |
Configures DirectStorage dependencies. |
cmake/CMakeLists.txt |
Adds DirectStorage build options. |
Review details
- Files reviewed: 31/31 changed files
- Comments generated: 6
- Review effort level: Balanced
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
🟡 Changes recommended
Cancellation propagation, queue sizing, preload filtering, and initialization portability contain blocking defects.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 31/31 changed files
- Comments generated: 5
- Review effort level: Balanced
There was a problem hiding this comment.
🟡 Changes recommended
Cancellation can be lost, a lazy callback can outlive its provider, and the feature lacks an enabled CI build.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 31/31 changed files
- Comments generated: 3
- Review effort level: Balanced
7d5ba58 to
d4d4026
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Preloading breaks caller-supplied external initializer overrides, and packaged feature availability is inconsistent across Windows Python wheels.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 32/32 changed files
- Comments generated: 2
- Review effort level: Balanced
There was a problem hiding this comment.
🟡 Changes recommended
The Windows wheel omits required runtime DLLs, and unresolved loader compatibility, boolean normalization, and cancellation defects remain.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (4)
Previously missed (2) — in code that hasn't changed since the last review.
cmake/onnxruntime_providers_webgpu.cmake:16
- This gate still permits
onnxruntime_USE_EP_API_ADAPTERS, but the OrtEp adapter has no external-data-loader callback/bridge: the wrapped WebGPU provider'sGetExternalDataLoader()is never registered withInferenceSession. In that build, every acceleration mode is ineffective and evenrequiredsilently uses ordinary loading. Either implement an EP-API bridge or reject adapter/plugin builds here.
onnxruntime/core/providers/webgpu/direct_storage_external_data_loader.cc:728 - The zero-request preload fast path never evaluates
is_cancelled, so an all-empty external-initializer model can report successful preload even when cancellation is already requested. This contradicts the stated cancellation guarantee; continue initialization, but returnMODEL_LOAD_CANCELEDwhen the callback is true.
This issue also appears in the following locations of the same file:
- line 807
- line 860
onnxruntime/core/providers/webgpu/direct_storage_external_data_loader.cc:819
- The zero-request final batch likewise ignores the current cancellation callback. If there was no non-empty preload (for example, all external tensors are empty), cancellation requested after preparation is converted into success. Check
is_cancelledin this branch and returnMODEL_LOAD_CANCELEDthroughfail_or_fallback.
if (batch.request_count == 0) {
common::Status preload_status = common::Status::OK();
if (impl_->preload_future.valid()) {
preload_status = impl_->preload_future.get();
}
impl_->preload_batch.reset();
impl_->context.ContinueInitialize();
if (!preload_status.IsOK()) {
return fail_or_fallback(preload_status);
}
impl_->context.WaitForInitializeComplete();
batch.finalized = true;
return common::Status::OK();
onnxruntime/core/providers/webgpu/direct_storage_external_data_loader.cc:864
- After the DirectStorage transfer completes, cancellation is no longer polled while this potentially long Dawn initialization wait and the subsequent per-tensor import run. In pipelined mode, a cancellation arriving during that interval is therefore returned as a successful load. Recheck the callback after the wait (and during a large import loop) and propagate
MODEL_LOAD_CANCELEDviafail_or_fallback.
impl_->context.WaitForInitializeComplete();
const auto import_start = Clock::now();
const auto import_status = [&]() -> common::Status {
ORT_RETURN_IF_NOT(
- Files reviewed: 32/32 changed files
- Comments generated: 2
- Review effort level: Balanced
|
I'm also trying to improve the loading time on GPU, see #32437. I'm using pinned buffers. I was planning to use DirectStorage APIs next. |
nice, let me know if the config options can be used in CUDA as well or if you would want it named differently. the difference between preffered vs preffered-pipelined is just that device feature querying can be parallelized with the weight loads. So pipelining does just that. required more throws an error if anything fails with direct storage based weight loading instead of falling back which is what the prefered mode does. My change here also contains an extension point to |
9972253 to
f3e2d9f
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Asynchronous initialization has a data race, and aborting a failed load can still wait for the entire preload.
Get a fresh assessment by requesting another Copilot review.
Review details
- Files reviewed: 34/34 changed files
- Comments generated: 2
- Review effort level: Balanced (auto)
Note
Copilot is running an experiment and ran this review at Balanced.
4338fd7 to
4b87453
Compare
4b87453 to
1d82d54
Compare
Avoid reading the asynchronously initialized WebGPU device when checking robustness settings, and allow AbortLoad to cancel an in-flight DirectStorage preload before waiting for it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Rename the new external-data-loader cancellation identifiers so the repository misspell check accepts them. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Create external data loaders early only for WebGPU DirectStorage and keep the preloaded instance through session initialization. Update the lifetime test loader for the DirectStorage interface. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Preserve inline loader defaults for shared provider RTTI, update CUDA's Windows loader signature, and build DirectStorage with static Dawn. Include lintrunner formatting fixes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Match the validated maximum tensor length to the 16-byte resource alignment arithmetic so the rounding addition cannot wrap. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy DirectStorage runtime DLLs into the isolated test directory and load ONNX Runtime with its DLL directory in the dependency search path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Expose only the initialized tensor range through Dawn, cover an unaligned external tensor, and fetch DirectStorage through the standard dependency manifest while preserving the root override. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Package the DirectStorage runtimes for Java and Node, fall back from preferred pipelined adapter pinning, and exercise accelerated external initializers through a real inference session. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Reserve asynchronous Dawn handoff for pipelined acceleration and cover separated tensor ranges across multiple external-data files. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Delay the pipelined preload handoff until Dawn accepts the pinned adapter, and validate request splitting at and beyond the 64 MiB boundary. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Reject duplicate providers before loader side effects and initialize contexts inline unless a viable pipelined DirectStorage mode needs asynchronous handoff. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add the public five-value WebGPU option, forward it through the native Node binding, and cover option recognition across every policy value. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Preserve cancellation during preferred fallback, initialize and expose copy-aligned buffer padding, use exception-compatible ORT handlers, and apply the required Node test formatting. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use the execution provider command recording for allocator operations and defer imported-resource access release until referencing commands are submitted. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove newly registered loaders and data transfers when late provider setup fails, and exercise required-pipelined external initializer loading end to end. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Compile transactional provider-registration state only for Windows DirectStorage builds and restore the Android binary-size budget. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Clear asynchronous initialization thread identity on every exit and preserve the original non-DirectStorage post-load path to avoid cross-platform binary growth. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep generic external data loaders alive only for session initialization on every platform. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Rather than download the direct storage SDK, embed the headers directly in ORT to minimize external dependencies.
9f8cc2c to
f2994a3
Compare
| @@ -0,0 +1,1423 @@ | |||
| /*------------------------------------------------------------------------------------- | |||
| @@ -0,0 +1,492 @@ | |||
| /*------------------------------------------------------------------------------------- | |||
|
Superseded by #33055, which presents the final Win32 and D3D12 implementation as a clean change based on current main. |
Description
This change adds accelerated external-weight loading for the native Windows WebGPU execution provider using documented Win32 and D3D12 APIs.
External initializers are loaded through documented Win32 and D3D12 APIs:
This avoids materializing weight payloads in application CPU memory and performs no application CPU payload copy.
Implementation
The reusable implementation is in the shared Windows platform layer as
onnxruntime::windows::d3d12::D3D12FileBufferLoader. It has no dependency on Dawn, WebGPU, ORT tensors, or execution-provider interfaces. A separate WebGPU adapter prepares tensor ranges, invokes the shared loader, imports the D3D12 resources into Dawn, and owns their lifetime.The loader includes:
WebGPU selects the requested physical adapter through DXGI and pins Dawn to the same adapter by LUID so the loading device and WebGPU device always refer to the same GPU. Initialization is synchronous and uses one accelerated loading path.
Provider option
Supported values:
off: use the existing initializer path.preferred: attempt D3D12 accelerated loading and fall back to ordinary loading if it is unavailable.required: require D3D12 accelerated loading and report the underlying error.Cancellation is always propagated and is never converted into a successful fallback.
Motivation
Large models commonly store most parameters as external ONNX initializer data. The ordinary native WebGPU path materializes those bytes in CPU-accessible memory and then uploads each initializer to a Dawn buffer. For multi-gigabyte models this adds CPU memory pressure, address-space activity, and extra copying to session creation.
The new shared loader keeps payload bytes in a bounded D3D12 upload ring and transfers them into GPU-local tensor resources without requiring an additional storage runtime deployment. The shared placement also allows other Windows D3D12-based execution providers to reuse the loader.
Measurements
Measurements used an approximately 2-billion-parameter-class decoder-only model containing:
Test system:
0x8086:0x7D67).0x10DE:0x2709).ONNX Runtime session creation
Nine fresh
onnxruntime_perf_testprocesses were run for each mode and GPU preference:offmedianpreferredmedianObserved ranges, including the first process:
offrangepreferredrangeThe first
offprocess on each GPU was a cold-start outlier.requireduses the same data path aspreferredand differs only in fallback policy.Standalone loader versus DirectStorage
A standalone 15-repetition comparison used alternating path order, excluded each path's first run from the steady-state median, and verified every destination byte:
Isolated diagnostics show that the loader is storage-bound:
Full byte-for-byte verification passed for all 1,291,602,752 payload bytes on both paths and both GPUs.
Validation
git diff --checkpasses.