Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
2578d1f
Zero-copy I/O for plugin EPs with HOST_ACCESSIBLE memory
ericcraw Apr 9, 2026
204dcd7
First pass addressing review comments
ericcraw Apr 17, 2026
40b78f5
Simplify CanSourceSatisfyTarget and add test cases
ericcraw Apr 23, 2026
5a83360
Add OrtEp::GetMemoryInfoByMemType for plugin EP memory placement
ericcraw Apr 30, 2026
34421c2
Merge remote-tracking branch 'upstream/main' into host-accessible-all…
ericcraw Apr 30, 2026
e19213b
Fix existing tests
ericcraw Apr 30, 2026
24256a4
Add additional tests for GetMemoryInfoByMemType
ericcraw Apr 30, 2026
627377e
Merge remote-tracking branch 'upstream/main' into host-accessible-all…
ericcraw May 6, 2026
dc9fa91
Bump since api version to 1.27 for GetMemoryInfoByMemType
ericcraw May 6, 2026
c60c3ee
Merge remote-tracking branch 'upstream/main' into host-accessible-all…
ericcraw May 11, 2026
363d98c
Remove OrtEp::GetMemoryInfoByMemType
ericcraw May 11, 2026
8316547
Add OrtEp::GetDefaultMemoryDevice
ericcraw May 11, 2026
154969b
Apply suggestions from code review
ericcraw May 14, 2026
096b76a
simplify GetDefaultMemoryDevice_StatusErrorThrows
ericcraw May 14, 2026
6aa5b94
Add note to GetDefaultMemoryDevice comment header.
ericcraw May 26, 2026
b65cf33
Change default device fallback behavior for plugin eps.
ericcraw May 27, 2026
ee5c5aa
Merge remote-tracking branch 'upstream/main' into host-accessible-all…
ericcraw May 28, 2026
4a4d361
Update InferOrtDeviceFromDeviceMemoryInfo to reflect new fallback beh…
ericcraw May 29, 2026
7219dbe
Address review comments
ericcraw May 29, 2026
5313639
Slightly relax assert to account for control flow cases
ericcraw Jun 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion onnxruntime/core/framework/allocation_planner.cc
Original file line number Diff line number Diff line change
Expand Up @@ -918,7 +918,7 @@ class PlannerImpl {
// We only do it for CPU based EPs. We are not likely to encounter
// non CPU devices here since they are already taken care of by using MemCpy nodes earlier.
// However, we still ignore them.
Comment thread
ericcraw marked this conversation as resolved.
Outdated
if (output_device.Type() == OrtDevice::CPU) {
if (output_device.UsesCpuMemory()) {
Comment thread
ericcraw marked this conversation as resolved.
const auto& output_name = node_output->Name();
const auto consumers = graph_viewer_.GetConsumerNodes(output_name);
for (const auto* consumer : consumers) {
Expand Down
84 changes: 56 additions & 28 deletions onnxruntime/core/framework/utils.cc
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,34 @@
return provider.GetDevice().Type() == OrtDevice::CPU;
}

// Returns true if no data transfer is needed between the two devices.
Comment thread
ericcraw marked this conversation as resolved.
Outdated
// HOST_ACCESSIBLE memory is a superset — accessible by both host and device — so it can satisfy
// DEFAULT memory requirements on the same physical device without a copy.
static bool DevicesAreMemoryCompatible(const OrtDevice& a, const OrtDevice& b) {
Comment thread
ericcraw marked this conversation as resolved.
Outdated
const bool a_is_cpu_mem = a.UsesCpuMemory();
const bool b_is_cpu_mem = b.UsesCpuMemory();

// Both CPU-accessible: compatible unless both are HOST_ACCESSIBLE on different physical devices.
if (a_is_cpu_mem && b_is_cpu_mem) {
if (a.Type() == OrtDevice::CPU || b.Type() == OrtDevice::CPU) {
return true;
}
Comment thread
ericcraw marked this conversation as resolved.
return a.Type() == b.Type() &&
a.Vendor() == b.Vendor() &&
a.Id() == b.Id();
}

// HOST_ACCESSIBLE <-> DEFAULT: compatible only on the same physical device.
if ((a_is_cpu_mem != b_is_cpu_mem) &&
a.Type() == b.Type() &&
a.Vendor() == b.Vendor() &&
a.Id() == b.Id()) {
return true;
}
Comment thread
ericcraw marked this conversation as resolved.
Outdated

return false;
Comment thread
yuslepukhin marked this conversation as resolved.
}

bool IsMemcpyNode(const Node& node) {
return node.Domain() == kOnnxDomain &&
(node.OpType() == "MemcpyFromHost" || node.OpType() == "MemcpyToHost");
Expand Down Expand Up @@ -117,6 +145,24 @@
return required_provider_type;
}

// Populate device_fetches for the output-copy path.
// Reuses a pre-allocated user buffer when the memory is compatible (same device or HOST_ACCESSIBLE
// <-> DEFAULT on the same physical device); otherwise inserts an empty placeholder.
static void PopulateDeviceFetches(gsl::span<const MLValueCopyInfo> fetch_copy_info,
Comment thread
ericcraw marked this conversation as resolved.
const std::vector<OrtValue>& fetches,
std::vector<OrtValue>& device_fetches) {
device_fetches.reserve(fetches.size());
for (size_t i = 0; i < fetches.size(); ++i) {
const auto& src = fetch_copy_info[i].source_device;
const auto& tgt = fetch_copy_info[i].target_device;
if ((src == tgt || DevicesAreMemoryCompatible(src, tgt)) && fetches[i].IsAllocated()) {
device_fetches.push_back(fetches[i]);
} else {
device_fetches.push_back({});
}
}
}

// Copy MLValue. Uses DataTransferManager for device copy if necessary. If copy_tensor_pairs/copy_sparse_pairs is provided,
// src/dst pairs that need a device copy are added to copy_pairs so copying can be batches by the DataTransferManager
// implementation for performance reasons.
Expand All @@ -132,8 +178,10 @@
std::vector<IDataTransfer::SrcDstPair>* copy_tensor_pairs = nullptr)
#endif
{
// same device so direct copy
if (copy_info.source_device == copy_info.target_device) {
// No data transfer needed if devices are the same or memory-compatible
// (e.g. HOST_ACCESSIBLE <-> DEFAULT on the same physical device).
if (copy_info.source_device == copy_info.target_device ||
DevicesAreMemoryCompatible(copy_info.source_device, copy_info.target_device)) {
target_mlvalue = source_mlvalue;
return Status::OK();
}
Expand Down Expand Up @@ -324,7 +372,8 @@
for (size_t i = 0, end = feed_locations.size(); i < end; ++i) {
copy_info[i].source_device = feed_locations[i];

if (copy_info[i].source_device != copy_info[i].target_device) {
if (copy_info[i].source_device != copy_info[i].target_device &&
!DevicesAreMemoryCompatible(copy_info[i].source_device, copy_info[i].target_device)) {
copy_needed = true;
}
}
Expand All @@ -345,7 +394,8 @@
copy_info[i].target_device = *alloc_info;
}

if (copy_info[i].source_device != copy_info[i].target_device) {
if (copy_info[i].source_device != copy_info[i].target_device &&
!DevicesAreMemoryCompatible(copy_info[i].source_device, copy_info[i].target_device)) {
copy_needed = true;
}
}
Expand Down Expand Up @@ -652,22 +702,11 @@
feeds_to_use = device_feeds;
}

auto num_outputs = fetches.size();

Check warning on line 705 in onnxruntime/core/framework/utils.cc

View workflow job for this annotation

GitHub Actions / build_x64_release

'num_outputs': local variable is initialized but not referenced

Check failure on line 705 in onnxruntime/core/framework/utils.cc

View workflow job for this annotation

GitHub Actions / build_x64_release

the following warning is treated as an error

Check warning on line 705 in onnxruntime/core/framework/utils.cc

View workflow job for this annotation

GitHub Actions / build_x64_release_ep_generic_interface

'num_outputs': local variable is initialized but not referenced

Check failure on line 705 in onnxruntime/core/framework/utils.cc

View workflow job for this annotation

GitHub Actions / build_x64_release_ep_generic_interface

the following warning is treated as an error

Check warning on line 705 in onnxruntime/core/framework/utils.cc

View workflow job for this annotation

GitHub Actions / build_x64_release_vitisai

'num_outputs': local variable is initialized but not referenced

Check failure on line 705 in onnxruntime/core/framework/utils.cc

View workflow job for this annotation

GitHub Actions / build_x64_release_vitisai

the following warning is treated as an error

Check warning on line 705 in onnxruntime/core/framework/utils.cc

View workflow job for this annotation

GitHub Actions / build_x64_debug

'num_outputs': local variable is initialized but not referenced

Check failure on line 705 in onnxruntime/core/framework/utils.cc

View workflow job for this annotation

GitHub Actions / build_x64_debug

the following warning is treated as an error

Check warning on line 705 in onnxruntime/core/framework/utils.cc

View workflow job for this annotation

GitHub Actions / build_x64_release_xnnpack

'num_outputs': local variable is initialized but not referenced

Check failure on line 705 in onnxruntime/core/framework/utils.cc

View workflow job for this annotation

GitHub Actions / build_x64_release_xnnpack

the following warning is treated as an error

Check warning on line 705 in onnxruntime/core/framework/utils.cc

View workflow job for this annotation

GitHub Actions / build_x86_release

'num_outputs': local variable is initialized but not referenced

Check warning on line 705 in onnxruntime/core/framework/utils.cc

View workflow job for this annotation

GitHub Actions / Windows GPU DML CI Pipeline

'num_outputs': local variable is initialized but not referenced

Check failure on line 705 in onnxruntime/core/framework/utils.cc

View workflow job for this annotation

GitHub Actions / Windows GPU DML CI Pipeline

the following warning is treated as an error

Check warning on line 705 in onnxruntime/core/framework/utils.cc

View workflow job for this annotation

GitHub Actions / Windows GPU TensorRT CI Pipeline

'num_outputs': local variable is initialized but not referenced

Check failure on line 705 in onnxruntime/core/framework/utils.cc

View workflow job for this annotation

GitHub Actions / Windows GPU TensorRT CI Pipeline

the following warning is treated as an error

Check warning on line 705 in onnxruntime/core/framework/utils.cc

View workflow job for this annotation

GitHub Actions / Windows GPU CUDA CI Pipeline

'num_outputs': local variable is initialized but not referenced

Check failure on line 705 in onnxruntime/core/framework/utils.cc

View workflow job for this annotation

GitHub Actions / Windows GPU CUDA CI Pipeline

the following warning is treated as an error

Check failure on line 705 in onnxruntime/core/framework/utils.cc

View workflow job for this annotation

GitHub Actions / webgpu_minimal_build_edge_build_x64_RelWithDebInfo

the following warning is treated as an error

Check failure on line 705 in onnxruntime/core/framework/utils.cc

View workflow job for this annotation

GitHub Actions / webgpu_build_x64_RelWithDebInfo (novcpkg, dynamic)

the following warning is treated as an error

Check warning on line 705 in onnxruntime/core/framework/utils.cc

View workflow job for this annotation

GitHub Actions / webgpu_build_x64_RelWithDebInfo (vcpkg, dynamic)

'num_outputs': local variable is initialized but not referenced

Check failure on line 705 in onnxruntime/core/framework/utils.cc

View workflow job for this annotation

GitHub Actions / webgpu_build_x64_RelWithDebInfo (vcpkg, dynamic)

the following warning is treated as an error

Check failure on line 705 in onnxruntime/core/framework/utils.cc

View workflow job for this annotation

GitHub Actions / webgpu_build_x64_RelWithDebInfo (novcpkg, static)

the following warning is treated as an error

Check warning on line 705 in onnxruntime/core/framework/utils.cc

View workflow job for this annotation

GitHub Actions / webgpu_build_x64_RelWithDebInfo (vcpkg, static)

'num_outputs': local variable is initialized but not referenced

Check failure on line 705 in onnxruntime/core/framework/utils.cc

View workflow job for this annotation

GitHub Actions / webgpu_build_x64_RelWithDebInfo (vcpkg, static)

the following warning is treated as an error
const auto& fetch_copy_info = feeds_fetches_manager.GetFetchesDeviceCopyInfo();

if (device_copy_checks.output_copy_needed == DeviceCopyCheck::Copy) {
// need intermediate fetches. use pre-allocated fetches where possible.
device_fetches.reserve(num_outputs);

for (size_t i = 0; i < num_outputs; ++i) {
if (fetch_copy_info[i].source_device == fetch_copy_info[i].target_device && fetches[i].IsAllocated()) {
device_fetches.push_back(fetches[i]);
} else {
// use temporary value
device_fetches.push_back({});
}
}

PopulateDeviceFetches(fetch_copy_info, fetches, device_fetches);
p_fetches = &device_fetches;
}

Expand Down Expand Up @@ -812,18 +851,7 @@
const auto& fetch_copy_info = feeds_fetches_manager.GetFetchesDeviceCopyInfo();

if (device_copy_checks.output_copy_needed == DeviceCopyCheck::Copy) {
// need intermediate fetches. use pre-allocated fetches where possible.
device_fetches.reserve(num_outputs);

for (size_t i = 0; i < num_outputs; ++i) {
if (fetch_copy_info[i].source_device == fetch_copy_info[i].target_device && fetches[i].IsAllocated()) {
device_fetches.push_back(fetches[i]);
} else {
// use temporary value
device_fetches.push_back({});
}
}

PopulateDeviceFetches(fetch_copy_info, fetches, device_fetches);
p_fetches = &device_fetches;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,18 @@ PluginExecutionProvider::PluginExecutionProvider(UniqueOrtEp ep, const OrtSessio
}
}

OrtDevice PluginExecutionProvider::GetOrtDeviceByMemType(OrtMemType mem_type) const {
if (mem_type == OrtMemTypeCPUInput || mem_type == OrtMemTypeCPUOutput) {
// Use the host-accessible allocator device if one was registered by the plugin.
// This avoids unnecessary copies between CPU and HOST_ACCESSIBLE memory.
if (!ep_devices_.empty() && ep_devices_[0]->host_accessible_memory_info != nullptr) {
Comment thread
ericcraw marked this conversation as resolved.
Outdated
return ep_devices_[0]->host_accessible_memory_info->device;
}
Comment thread
ericcraw marked this conversation as resolved.
Outdated
return OrtDevice();
}
return GetDevice();
}
Comment thread
ericcraw marked this conversation as resolved.
Outdated

PluginExecutionProvider::~PluginExecutionProvider() {
if (ort_ep_ && !api_node_compute_infos_.empty() && ort_ep_->ReleaseNodeComputeInfos != nullptr) {
ort_ep_->ReleaseNodeComputeInfos(ort_ep_.get(), api_node_compute_infos_.data(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ class PluginExecutionProvider : public IExecutionProvider {
common::Status ReplayGraph(int graph_annotation_id) override;
OrtGraphCaptureNodeAssignmentPolicy GetGraphCaptureNodeAssignmentPolicy() const override;

OrtDevice GetOrtDeviceByMemType(OrtMemType mem_type) const override;

Comment thread
yuslepukhin marked this conversation as resolved.
Outdated
private:
const logging::Logger& GetEpLoggerOrDefault() const;

Expand Down
Loading