Skip to content

[WebGPU] Plugin EP terminates the host process when a Memcpy input fails to convert to a Tensor #33557

Closed

Description

@KakaruHayate

Describe the issue

Using the WebGPU plugin EP from C#, selecting the second OrtEpDevice reported by GetEpDevices() and running any model aborts the host process during Run:

Thread 21 Crashed:: .NET TP Worker
 4  libc++abi                            __abort_message
 5  libc++abi                            demangling_terminate_handler()
 6  libobjc                              _objc_terminate()
 7  libc++abi                            std::__terminate(void (*)())
 8  libc++abi                            std::terminate()
 9  libonnxruntime_providers_webgpu.dylib  onnxruntime::ep::adapter::CreateTensorFromApiValue(OrtValue*) + 904
10  libonnxruntime_providers_webgpu.dylib  onnxruntime::ep::adapter::OpKernelContext::Input<onnxruntime::Tensor, void>(int) const + 188
11  libonnxruntime_providers_webgpu.dylib  onnxruntime::webgpu::Memcpy::Compute(onnxruntime::ep::adapter::OpKernelContext*) const + 40
12  libonnxruntime_providers_webgpu.dylib  onnxruntime::ep::adapter::KernelImpl::ComputeImpl(OrtKernelImpl*, OrtKernelContext*) + 80
13  libonnxruntime.dylib                   onnxruntime::PluginEpOpKernel::Compute(onnxruntime::OpKernelContext*) const + 28
14  libonnxruntime.dylib                   onnxruntime::ExecuteKernel(onnxruntime::StreamExecutionContext&, unsigned long, unsigned long, bool const&, onnxruntime::SessionScope&) + 360

Exception Type: EXC_CRASH (SIGABRT), Termination Reason: Namespace SIGNAL, Code 6, Application Specific Information: abort() called.

The same model, same process, device index 0 works; index 1 aborts every time. It is deterministic, not a race.

Environment

  • macOS (arm64), Apple Silicon. Crash report from macOS 26/27.
  • Microsoft.ML.OnnxRuntime 1.31.0 + Microsoft.ML.OnnxRuntime.EP.WebGpu 0.4.0 (onnxruntime_providers_webgpu.dylib reports 1.30.0.20260916.3.f2c39fe).
  • C# API. Session built with sessionOptions.AppendExecutionProvider(env, new[] { epDevice }, new Dictionary<string, string>()), then InferenceSession.Run.

What I think is wrong

adapter::KernelImpl::ComputeImpl in include/onnxruntime/ep/adapter/op_kernel.h is ORT_API_CALL ... noexcept and wraps the kernel call in ORT_TRY / ORT_CATCH(const std::exception&), so a std::exception thrown while converting an input should have become a Status rather than a terminate. Since the process died with std::__terminate, the throw either is not a std::exception, or it crosses a noexcept boundary before the handler can see it.

The throwing code path is Memcpy::Compute -> OpKernelContext::Input<Tensor>() -> CreateTensorFromApiValue(), and the only enforce in that helper is EP_ENFORCE(value.IsTensor(), "Only tensor OrtValue is supported.") (include/onnxruntime/ep/adapter/tensor_helper.h). So on this device the input OrtValue apparently fails that check (or the conversion fails right after it), and instead of a Status the host process is killed.

For a plugin EP this is a hard failure mode: host applications cannot catch it, cannot fall back to CPU, and get no message. Compare #32315, which fixed the same class of problem for the data transfer callback by converting throws into OrtStatus.

Possible duplicate device enumeration

GetEpDevices() returns two WebGpuExecutionProvider devices on this machine even though Apple Silicon exposes one GPU. On Windows I see the same shape with a single NVIDIA GPU listed twice: identical description string, different LUID / DxgiAdapterNumber.

If the second entry is not actually usable, the EP should either not report it or reject it with a Status - reporting it and then aborting when it is used means a host's device picker can kill the process.

Questions

  1. Is the extra OrtEpDevice supposed to be usable? If it exists for a reason (e.g. one entry per adapter view), what should a host use to tell them apart?
  2. Should ComputeImpl use a catch-all instead of ORT_CATCH(const std::exception&) so an adapter-level enforce can never terminate the host?
  3. Is this fixed in a plugin build newer than 0.4.0? I can retest with a specific version or a dev build.

I can provide the full crash report, and the device metadata from OrtEpDevice.HardwareDevice.Metadata for both entries, if that helps.

Activity

  1. added
    .NETPull requests that update .net code
    api:CSharpissues related to the C# API
    ep:WebGPUort-web webgpu provider
    platform:webissues related to ONNX Runtime web; typically submitted using template
    on Oct 23, 2026
  2. edgchen1 commented on Oct 23, 2026

    @edgchen1
    Contributor

    thanks for reporting the issue.

    I can provide the full crash report, and the device metadata from OrtEpDevice.HardwareDevice.Metadata for both entries, if that helps.

    this would be helpful. would be interested to see any additional info about the OrtEpDevices.

  3. edgchen1 commented on Oct 23, 2026

    @edgchen1
    Contributor

    to answer your questions:

    Is the extra OrtEpDevice supposed to be usable? If it exists for a reason (e.g. one entry per adapter view), what should a host use to tell them apart?

    there are a few kinds of OrtEpDevice added by the WebGPU OrtEpFactory. see GetSupportedDevicesImpl.

    the first kind is associated with an OrtHardwareDevice corresponding to a GPU device. any of these should be usable. note that selecting a particular device to use with WebGPU is done separately through provider options, somewhat unintuitively.

    the second kind is a fallback OrtEpDevice that is associated with the CPU device, enabled when the EP factory is configured to allow software adapters. this is currently configured by setting an environment variable, ORT_WEBGPU_EP_ALLOW_SOFTWARE_ADAPTER. it allows use of WebGPU EP with something like the SwiftShader software Vulkan implementation.

    the third kind is associated with a virtual device. these aren't usable for running inference. the OrtHardwareDevice should have a metadata entry indicating it is a virtual device.

    Should ComputeImpl use a catch-all instead of ORT_CATCH(const std::exception&) so an adapter-level enforce can never terminate the host?

    not sure yet. wouldn't think we should be throwing anything not derived from std::exception. EP_ENFORCE() throws std::runtime_error.

    Is this fixed in a plugin build newer than 0.4.0? I can retest with a specific version or a dev build.

    I'm not aware of a fix.

  4. KakaruHayate commented on Oct 25, 2026

    @KakaruHayate
    Author

    Thanks for the pointers - the three kinds explained a lot, and one of them let me rule something out. Here is the data you asked for.

    Device metadata, Windows x64

    Microsoft.ML.OnnxRuntime 1.31.0 + Microsoft.ML.OnnxRuntime.EP.WebGpu 0.4.0, OrtEnv.GetEpDevices():

    CPUExecutionProvider / CPU / Intel
      Description=Intel(R) Xeon(R) CPU E5-2675 v3 @ 1.80GHz
    
    WebGpuExecutionProvider / GPU / NVIDIA          <- index 0
      Description=NVIDIA GeForce RTX 2070
      LUID=95700; DxgiAdapterNumber=0; DxgiHighPerformanceIndex=0
      DxgiVideoMemory=8006 MB; Discrete=1
    
    WebGpuExecutionProvider / GPU / NVIDIA          <- index 1
      Description=NVIDIA GeForce RTX 2070
      LUID=141797; DxgiAdapterNumber=1; DxgiHighPerformanceIndex=2
      DxgiVideoMemory=8006 MB
    

    Neither entry carries is_virtual (allow_virtual_devices is off), and the fallback CPU-backed entry is absent (a real GPU device was added), so both are the "first kind" - GPU-associated devices.

    The difference between them is DxgiAdapterNumber 0 vs 1, DxgiHighPerformanceIndex 0 vs 2, and entry 1 has no Discrete metadata.

    Both entries are usable on Windows

    This is the part that made me doubt the "second device is broken" theory: on the same machine both index 0 and index 1 load the same 272 MB DiffSinger acoustic model without problems (8.0 s on index 1). So duplicate GPU-associated OrtEpDevices are not harmful per se.

    macOS is different

    On the Mac from the crash report (Apple Silicon, macOS 26/27) the picker also shows two entries, and there the second one aborts during Run (the Memcpy::Compute / CreateTensorFromApiValue / std::terminate stack from the report), while the first renders fine. Another user reports the opposite (only the second works), so which index is usable differs per machine.

    Questions

    1. Is one physical GPU expected to produce two GPU-associated OrtHardwareDevices with different LUIDs, the second one lacking Discrete? On Windows this host also has a virtual display driver installed, and OpenUtau now filters indirect display adapters on its side (fix: Exclude indirect display adapters openutau/OpenUtau#2549), but these two are reported as ordinary NVIDIA GPUs by ORT. If DxgiAdapterNumber=1 is a render-only view of the same physical adapter, should a host prefer/ignore one of them?

    2. On the exception path: EP_ENFORCE throwing std::runtime_error should be caught by ORT_CATCH(const std::exception&) in adapter::KernelImpl::ComputeImpl. Since the plugin build we ship still reaches std::__terminate, I suspect the throw crosses a noexcept boundary before reaching that handler. Is the WebGPU plugin library built with exceptions enabled? If it is, would a catch-all in ComputeImpl (instead of only std::exception) be acceptable as a belt-and-braces measure, so an adapter-level enforce can never take down the host?

    I have the full .ips crash report from the macOS machine (symbolicated stack, thread 21). I cannot attach files through the API, so tell me if you want it and I will ask the reporter to attach it here, or paste whichever sections are useful.

  5. gyagp commented on Oct 27, 2026

    @gyagp
    Contributor

    Kakaru (@KakaruHayate) Thanks for reporting this bug. I tried on the macOS, and hopefully #33634 can fix your issue.
    On my macOS, it doesn't report multiple devices. But please note device is a logical concept, and one physical adapter may map to multiple devices.
    Please also note WebGPU EP doesn't respect your selection from GetEpDevices() now. We'll better handle this part in the near future.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    .NETPull requests that update .net codeapi:CSharpissues related to the C# APIep:WebGPUort-web webgpu providerplatform:webissues related to ONNX Runtime web; typically submitted using template

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions