diff --git a/include/onnxruntime/ep/adapter/tensor_helper.h b/include/onnxruntime/ep/adapter/tensor_helper.h index c0979618b0f64..ec7faf43cb253 100644 --- a/include/onnxruntime/ep/adapter/tensor_helper.h +++ b/include/onnxruntime/ep/adapter/tensor_helper.h @@ -37,7 +37,7 @@ inline onnxruntime::Tensor CreateTensorFromApiValue(OrtValue* ort_value) { memory_info.GetAllocatorType(), OrtDevice{ static_cast(memory_info.GetDeviceType()), - static_cast(memory_info.GetMemoryType()), + static_cast(memory_info.GetDeviceMemoryType()), static_cast(memory_info.GetVendorId()), static_cast(memory_info.GetDeviceId()), diff --git a/onnxruntime/test/providers/webgpu/plugin/test_webgpu_allocators.cc b/onnxruntime/test/providers/webgpu/plugin/test_webgpu_allocators.cc index 107446a80b07f..14a974acf7662 100644 --- a/onnxruntime/test/providers/webgpu/plugin/test_webgpu_allocators.cc +++ b/onnxruntime/test/providers/webgpu/plugin/test_webgpu_allocators.cc @@ -8,6 +8,8 @@ #include #include #include +#include +#include #include #include #include @@ -17,6 +19,7 @@ #include #include +#include #include "core/framework/allocator.h" #include "core/graph/constants.h" @@ -196,6 +199,115 @@ class WebGpuPluginSharedAllocatorTest : public ::testing::Test { std::vector ep_devices_; }; +TEST_F(WebGpuPluginSharedAllocatorTest, InferenceWithCpuTensorMemoryTypes) { + const auto ep_device = EpDevice(); +#if !defined(ORT_UNIT_TEST_HAS_WEBGPU_STATIC_PLUGIN_EP) + const auto metadata = ep_device.EpMetadata().GetKeyValuePairs(); + ASSERT_NE(metadata.find(kOrtEpDevice_EpMetadataKey_LibraryPath), metadata.end()) + << "This regression must run against the plugin, not built-in WebGPU."; +#endif + + // X is a CPU-side Pad parameter and a GPU-side Mul operand. A GPU-only consumer + // would let the framework copy X before Run, bypassing MemcpyFromHost's adapter. + ONNX_NAMESPACE::ModelProto model; + model.set_ir_version(ONNX_NAMESPACE::Version::IR_VERSION); + model.add_opset_import()->set_version(18); + auto* graph = model.mutable_graph(); + graph->set_name("webgpu_cpu_tensor_memory_types"); + const auto add_value = [&](const char* name, bool is_input, std::initializer_list dims) { + auto* value = is_input ? graph->add_input() : graph->add_output(); + value->set_name(name); + auto* type = value->mutable_type()->mutable_tensor_type(); + type->set_elem_type(ONNX_NAMESPACE::TensorProto_DataType_FLOAT); + auto* shape = type->mutable_shape(); + for (const auto dim : dims) { + shape->add_dim()->set_dim_value(dim); + } + }; + add_value("X", true, {}); + add_value("Y", false, {}); + add_value("P", false, {3}); + auto* pad_data = graph->add_initializer(); + pad_data->set_name("pad_data"); + pad_data->set_data_type(ONNX_NAMESPACE::TensorProto_DataType_FLOAT); + pad_data->add_dims(1); + pad_data->add_float_data(3.0f); + auto* pads = graph->add_initializer(); + pads->set_name("pads"); + pads->set_data_type(ONNX_NAMESPACE::TensorProto_DataType_INT64); + pads->add_dims(2); + pads->add_int64_data(1); + pads->add_int64_data(1); + auto* mul = graph->add_node(); + mul->set_op_type("Mul"); + mul->add_input("X"); + mul->add_input("X"); + mul->add_output("Y"); + auto* pad = graph->add_node(); + pad->set_op_type("Pad"); + pad->add_input("pad_data"); + pad->add_input("pads"); + pad->add_input("X"); + pad->add_output("P"); + + Ort::SessionOptions options; + options.SetGraphOptimizationLevel(GraphOptimizationLevel::ORT_DISABLE_ALL); + options.AddConfigEntry(kOrtSessionOptionsDisableCPUEPFallback, "1"); + options.EnableProfiling(ORT_TSTR("webgpu_tensor_memory_types")); + options.AppendExecutionProvider_V2(Env(), {ep_device}, Ort::KeyValuePairs{}); + const auto model_bytes = model.SerializeAsString(); + Ort::Session session(Env(), model_bytes.data(), model_bytes.size(), options); + + float input_data = 2.0f; + constexpr std::array expected_padding{2, 3, 2}; + const char* input_names[] = {"X"}; + const char* output_names[] = {"Y", "P"}; + + // C#'s default CPU tensor descriptor uses OrtMemTypeCPUOutput (-1). Device + // memory type HOST_ACCESSIBLE (5) is independent of these OrtMemType values. + const std::array memory_infos{ + Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeDefault), + Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPUInput), + Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPUOutput), + Ort::MemoryInfo("Cpu", OrtMemoryInfoDeviceType_CPU, 0, 0, + OrtDeviceMemoryType_HOST_ACCESSIBLE, 0, OrtDeviceAllocator)}; + for (const auto& memory_info : memory_infos) { + SCOPED_TRACE(::testing::Message() << memory_info.GetMemoryType() << "/" << memory_info.GetDeviceMemoryType()); + auto input = Ort::Value::CreateTensor( + memory_info, &input_data, 1, nullptr, 0); + auto outputs = session.Run(Ort::RunOptions{nullptr}, input_names, &input, 1, output_names, 2); + ASSERT_EQ(outputs.size(), 2u); + ASSERT_EQ(outputs[0].GetTensorTypeAndShapeInfo().GetElementCount(), 1u); + EXPECT_FLOAT_EQ(outputs[0].GetTensorData()[0], 4.0f); + ASSERT_EQ(outputs[1].GetTensorTypeAndShapeInfo().GetElementCount(), expected_padding.size()); + const auto* actual_padding = outputs[1].GetTensorData(); + for (size_t i = 0; i < expected_padding.size(); ++i) { + EXPECT_FLOAT_EQ(actual_padding[i], expected_padding[i]); + } + } + + Ort::AllocatorWithDefaultOptions allocator; + const auto profile_path = session.EndProfilingAllocated(allocator); + nlohmann::json profile; + { + std::ifstream file(profile_path.get()); + ASSERT_TRUE(file.is_open()); + file >> profile; + } + std::filesystem::remove(profile_path.get()); + size_t copies_from_host = 0; + for (const auto& event : profile) { + if (event.value("cat", "") == "Node" && event.contains("args")) { + const auto& args = event["args"]; + if (args.value("op_name", "") == "MemcpyFromHost") { + EXPECT_EQ(args.value("provider", ""), kWebGpuExecutionProvider); + ++copies_from_host; + } + } + } + EXPECT_EQ(copies_from_host, memory_infos.size()); +} + #if !defined(USE_EXTERNAL_DAWN) #if !defined(BUILD_DAWN_SHARED_LIBRARY) namespace { diff --git a/plugin-ep-webgpu/csharp/test/WebGpuEpNuGetTest/Program.cs b/plugin-ep-webgpu/csharp/test/WebGpuEpNuGetTest/Program.cs index 2001a99be5c43..7e74903ea41bd 100644 --- a/plugin-ep-webgpu/csharp/test/WebGpuEpNuGetTest/Program.cs +++ b/plugin-ep-webgpu/csharp/test/WebGpuEpNuGetTest/Program.cs @@ -20,11 +20,16 @@ static int Main() try { - // Find the OrtEpDevice for the WebGPU EP + // macOS also has built-in WebGPU. Select the library registered above. OrtEpDevice? epDevice = null; + var pathComparison = OperatingSystem.IsWindows() + ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; foreach (var d in env.GetEpDevices()) { - if (string.Equals(epName, d.EpName, StringComparison.Ordinal)) + using var metadata = d.EpMetadata; + if (string.Equals(epName, d.EpName, StringComparison.Ordinal) && + metadata.Entries.TryGetValue("library_path", out var libraryPath) && + string.Equals(Path.GetFullPath(epLibPath), Path.GetFullPath(libraryPath), pathComparison)) { epDevice = d; break; @@ -33,7 +38,7 @@ static int Main() if (epDevice == null) { - Console.Error.WriteLine($"ERROR: Unable to find OrtEpDevice with name '{epName}'"); + Console.Error.WriteLine($"ERROR: Unable to find OrtEpDevice for '{epName}' from '{epLibPath}'"); return 1; } Console.WriteLine($"Found OrtEpDevice for EP: {epName}");