Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
32 changes: 18 additions & 14 deletions include/onnxruntime/core/session/onnxruntime_cxx_inline.h
Original file line number Diff line number Diff line change
Expand Up @@ -1582,11 +1582,13 @@ inline std::vector<ConstMemoryInfo> ConstSessionImpl<T>::GetMemoryInfoForInputs(

auto num_inputs = GetInputCount();
std::vector<ConstMemoryInfo> mem_infos;
mem_infos.resize(num_inputs);
if (num_inputs > 0) {
mem_infos.resize(num_inputs);

ThrowOnError(GetApi().SessionGetMemoryInfoForInputs(this->p_,
reinterpret_cast<const OrtMemoryInfo**>(mem_infos.data()),
num_inputs));
ThrowOnError(GetApi().SessionGetMemoryInfoForInputs(this->p_,
reinterpret_cast<const OrtMemoryInfo**>(mem_infos.data()),
num_inputs));
}

return mem_infos;
}
Expand All @@ -1598,11 +1600,13 @@ inline std::vector<ConstMemoryInfo> ConstSessionImpl<T>::GetMemoryInfoForOutputs

auto num_outputs = GetOutputCount();
std::vector<ConstMemoryInfo> mem_infos;
mem_infos.resize(num_outputs);
if (num_outputs > 0) {
mem_infos.resize(num_outputs);

ThrowOnError(GetApi().SessionGetMemoryInfoForOutputs(this->p_,
reinterpret_cast<const OrtMemoryInfo**>(mem_infos.data()),
num_outputs));
ThrowOnError(GetApi().SessionGetMemoryInfoForOutputs(this->p_,
Comment thread
yuslepukhin marked this conversation as resolved.
reinterpret_cast<const OrtMemoryInfo**>(mem_infos.data()),
num_outputs));
}
return mem_infos;
}

Expand Down Expand Up @@ -1631,12 +1635,12 @@ template <typename T>
inline std::vector<ConstEpDevice> ConstSessionImpl<T>::GetEpDeviceForInputs() const {
auto num_inputs = GetInputCount();
std::vector<ConstEpDevice> input_devices;
input_devices.resize(num_inputs);

ThrowOnError(GetApi().SessionGetEpDeviceForInputs(this->p_,
reinterpret_cast<const OrtEpDevice**>(input_devices.data()),
num_inputs));

if (num_inputs > 0) {
input_devices.resize(num_inputs);
ThrowOnError(GetApi().SessionGetEpDeviceForInputs(this->p_,
reinterpret_cast<const OrtEpDevice**>(input_devices.data()),
num_inputs));
}
return input_devices;
}

Expand Down
73 changes: 57 additions & 16 deletions onnxruntime/core/session/inference_session.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3383,17 +3383,54 @@ common::Status InferenceSession::GetInputOutputMemoryInfo(SessionInputOutputType

for (const auto* def : def_list) {
InlinedVector<SessionState::NodeInfo> node_info_vec;
Status status;
if (type == SessionInputOutputType::kOutput) {
ORT_RETURN_IF_ERROR(session_state_->GetOutputNodeInfo(def->Name(), node_info_vec));
status = session_state_->GetOutputNodeInfo(def->Name(), node_info_vec);
} else {
ORT_RETURN_IF_ERROR(session_state_->GetInputNodeInfo(def->Name(), node_info_vec));
status = session_state_->GetInputNodeInfo(def->Name(), node_info_vec);
}

// all entries are for the same OrtDevice so use the first one.
// we need to get an OrtMemoryInfo* that will remain valid, so we get the allocator for the OrtDevice
// from the session state and use its OrtMemoryInfo.
auto allocator = session_state_->GetAllocator(*node_info_vec.front().device);
memory_info.push_back(&allocator->Info());
if (!status.IsOK()) {
if (type == SessionInputOutputType::kInput) {
return status;
}

// Check first if this output is produced by an input that directly
// propagates to output with the same name.
status = session_state_->GetInputNodeInfo(def->Name(), node_info_vec);
if (status.IsOK()) {
Comment thread
yuslepukhin marked this conversation as resolved.
auto allocator = session_state_->GetAllocator(*node_info_vec.front().device);
Comment thread
yuslepukhin marked this conversation as resolved.
memory_info.push_back(&allocator->Info());
} else {
// Check if this output is produced by a constant initializer
// Pick the MemoryInfo from the initializer's OrtValue
const auto& ort_value_map = session_state_->GetOrtValueNameIdxMap();

OrtValueIndex ort_value_index;
status = ort_value_map.GetIdx(def->Name(), ort_value_index);
if (!status.IsOK()) {
return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL,
"Failed to find node output or a constant initializer producing output: ",
def->Name(), ".");
Comment thread
yuslepukhin marked this conversation as resolved.
}

const auto& idx_to_ort_value = session_state_->GetInitializedTensors();
auto it = idx_to_ort_value.find(ort_value_index);
if (it == idx_to_ort_value.end()) {
return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL,
"Failed to find node output or a constant initializer producing output: ",
def->Name(), ".");
}
Comment thread
yuslepukhin marked this conversation as resolved.
const auto& tensor = it->second.Get<Tensor>();
memory_info.push_back(&tensor.Location());
}
} else {
// all entries are for the same OrtDevice so use the first one.
// we need to get an OrtMemoryInfo* that will remain valid, so we get the allocator for the OrtDevice
// from the session state and use its OrtMemoryInfo.
auto allocator = session_state_->GetAllocator(*node_info_vec.front().device);
memory_info.push_back(&allocator->Info());
}
}

return Status::OK();
Expand Down Expand Up @@ -3422,15 +3459,19 @@ common::Status InferenceSession::GetEpDeviceForInputs(InlinedVector<const OrtEpD
for (const auto* def : def_list) {
InlinedVector<SessionState::NodeInfo> node_info_vec;
ORT_RETURN_IF_ERROR(session_state_->GetInputNodeInfo(def->Name(), node_info_vec));

// if we have a lot of inputs or there are a lot of execution providers it may be worth creating a map
// instead of doing a linear search each time.
const auto& ep_name = node_info_vec.front().p_node->GetExecutionProviderType();
auto it = std::find_if(available_eps.begin(), available_eps.end(), [&ep_name](const OrtEpDevice* entry) {
return entry->ep_name == ep_name;
});

ep_devices.push_back(it != available_eps.end() ? *it : nullptr);
assert(!node_info_vec.empty());
// If we have an input that is not consumed by any node,
// including nodes in subgraphs, then we return nullptr.
const auto* p_node = node_info_vec.front().p_node;
if (p_node != nullptr) {
const auto ep_name = p_node->GetExecutionProviderType();
auto it = std::find_if(available_eps.begin(), available_eps.end(), [&ep_name](const OrtEpDevice* entry) {
return entry->ep_name == ep_name;
});
ep_devices.push_back(it != available_eps.end() ? *it : nullptr);
} else {
ep_devices.push_back(nullptr);
}
}

return Status::OK();
Expand Down
37 changes: 37 additions & 0 deletions onnxruntime/test/shared_lib/test_inference.cc
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,35 @@ INSTANTIATE_TEST_SUITE_P(CApiTestWithProviders,
CApiTestWithProvider,
::testing::Values(0, 1, 2, 3, 4));

TEST(CApiTest, TestInputPassThroughToOutput) {
const ORTCHAR_T* model_uri = TSTR("testdata/input_propagated_to_output.onnx");
Ort::Session session(*ort_env, model_uri, Ort::SessionOptions{});
auto inputs_meminfos = session.GetMemoryInfoForInputs();
ASSERT_EQ(1U, inputs_meminfos.size());
auto inputs_epdevices = session.GetEpDeviceForInputs();
ASSERT_EQ(1U, inputs_epdevices.size());
auto outputs_meminfos = session.GetMemoryInfoForOutputs();
ASSERT_EQ(7U, outputs_meminfos.size());
}

TEST(CApiTest, TestDanglingInput) {
// Here we test an issue with segments_ids that is an input not consumed by anything
// This kind of model is unlikely to be used in practice but we want to make sure it works
const ORTCHAR_T* model_uri = TSTR("testdata/test_dangling_input_segment_ids.onnx");
Ort::Session session(*ort_env, model_uri, Ort::SessionOptions{});
auto inputs_meminfos = session.GetMemoryInfoForInputs();
ASSERT_EQ(2U, inputs_meminfos.size());
auto outputs_meminfos = session.GetMemoryInfoForOutputs();
ASSERT_EQ(2U, outputs_meminfos.size());
auto inputs_epdevices = session.GetEpDeviceForInputs();
ASSERT_EQ(2U, inputs_epdevices.size());
// One of the devices returning is null since the input is not consumed
// there is not a device for it.
const bool null_present = std::any_of(inputs_epdevices.begin(), inputs_epdevices.end(),
[](const auto& device) { return device == nullptr; });
ASSERT_TRUE(null_present);
}

#if !defined(DISABLE_SPARSE_TENSORS)
TEST(CApiTest, SparseOutputModel) {
std::vector<int64_t> dense_shape{3, 3};
Expand All @@ -505,7 +534,15 @@ TEST(CApiTest, SparseOutputModel) {
std::vector<Ort::Value> ort_inputs;
std::vector<const char*> input_names;
const char* const output_names[] = {"values"};
// This model produces a sparse output from a constant sparse initializer
Ort::Session session(*ort_env, SPARSE_OUTPUT_MODEL_URI, Ort::SessionOptions{});
auto inputs_meminfos = session.GetMemoryInfoForInputs();
ASSERT_TRUE(inputs_meminfos.empty());
auto outputs_meminfos = session.GetMemoryInfoForOutputs();
ASSERT_EQ(1U, outputs_meminfos.size());
auto inputs_epdevices = session.GetEpDeviceForInputs();
ASSERT_TRUE(inputs_epdevices.empty());

auto ort_outputs = session.Run(Ort::RunOptions{}, input_names.data(), ort_inputs.data(), ort_inputs.size(),
output_names, 1);
ASSERT_EQ(ort_outputs.size(), 1U);
Expand Down
Comment thread
yuslepukhin marked this conversation as resolved.
Binary file not shown.
Binary file not shown.
Loading