Skip to content

Commit af205c2

Browse files
[EP ABI] Add documentation for OrtValue and ort_graph_to_proto util (microsoft#25411)
### Description - Adds documentation to state that the data pointer for an `OrtValue` owned by an `OrtGraph` is stable during the lifetime of the `OrtSession` that owns the `OrtGraph`. - Adds documentation to the ort_graph_to_proto.h utils to show how to create a `onnx::GraphProto` with external initializers that actually point to in-memory data (same approach used internally within ORT). ### Motivation and Context Clarification of usage of new graph apis.
1 parent 053ed28 commit af205c2

File tree

3 files changed

+113
-1
lines changed

3 files changed

+113
-1
lines changed

include/onnxruntime/core/providers/utils/ort_graph_to_proto.h

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,44 @@
7575
// graph_proto stores large initializers in an external file
7676
}
7777
```
78+
79+
EXAMPLE SNIPPET (external initializers that point to data in memory, not officially supported by ONNX spec):
80+
81+
This example stores initializers externally. However, instead of storing the initializers in a separate
82+
file, the onnx::TensorProto objects point directly to memory addresses. This requires setting the initializer's
83+
location to a special tag like "_MEM_ADDR_" (instead of a file path). The offset is set to the pointer to the
84+
initializer's data in memory (instead of an offset into a file).
85+
86+
Because this is not standard ONNX, such a onnx::GraphProto should not be saved as an ONNX file.
87+
However, it allows custom tools that operate directly on a onnx::GraphProto to get the initializer data
88+
if it has already been loaded into memory.
89+
90+
```C++
91+
#define ORT_EP_UTILS_ORT_GRAPH_TO_PROTO_IMPL
92+
#include "ort_graph_to_proto.h"
93+
94+
OrtStatus* ORT_API_CALL GetCapabilityImpl(OrtEp* this_ptr, const OrtGraph* ort_graph,
95+
OrtEpGraphSupportInfo* graph_support_info) {
96+
auto handle_initializer_data = [](const OrtValueInfo* value_info,
97+
const void* data, size_t bytes,
98+
bool& is_external, std::string& location,
99+
int64_t& offset) -> Ort::Status {
100+
(void)value_info;
101+
(void)bytes;
102+
103+
offset = reinterpret_cast<int64_t>(data);
104+
location = "_MEM_ADDR_"; // Some special location tag that indicates the offset is a pointer.
105+
is_external = true; // True if is external initializer
106+
return Ort::Status{nullptr};
107+
}
108+
109+
ONNX_NAMESPACE::GraphProto graph_proto;
110+
OrtEpUtils::OrtGraphToProto(*ort_graph, graph_proto, handle_initializer_data);
111+
112+
// graph_proto has initializers that look like they are stored in an external file,
113+
// but they are actually pointing to the data in memory.
114+
}
115+
```
78116
*/
79117

80118
#ifndef INCLUDE_ONNXRUNTIME_CORE_PROVIDERS_UTILS_ORT_GRAPH_TO_PROTO_H_

include/onnxruntime/core/session/onnxruntime_c_api.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5488,9 +5488,12 @@ struct OrtApi {
54885488
* Supports initializers defined in an outer scope (i.e., a parent graph).
54895489
*
54905490
* \param[in] value_info The OrtValueInfo instance.
5491-
* \param[out] initializer_value Output parameter set to the initializer value or NULL.
5491+
* \param[out] initializer_value Output parameter set to the initializer value or NULL. The OrtValue data pointer
5492+
* (obtained via GetTensorData) is stable during the lifetime of the OrtSession
5493+
* that owns the OrtGraph.
54925494
*
54935495
* \snippet{doc} snippets.dox OrtStatus Return Value
5496+
*
54945497
* \since Version 1.23.
54955498
*/
54965499
ORT_API2_STATUS(ValueInfo_GetInitializerValue, _In_ const OrtValueInfo* value_info,

onnxruntime/test/ep_graph/test_ep_graph.cc

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,77 @@ TEST(EpGraphTest, SerializeToProto_Mnist) {
178178
EXPECT_EQ(output_serialized, output_original);
179179
}
180180

181+
// Test serializing an OrtGraph (MNIST) to GraphProto. Initializers are configured as "external" but point to
182+
// existing data in memory (not standard ONNX).
183+
TEST(EpGraphTest, SerializeToProto_ExternalInitializersInMemory) {
184+
const ORTCHAR_T* original_model_path = ORT_TSTR("testdata/mnist.onnx");
185+
auto test_graph = TestGraph::Load(original_model_path);
186+
ASSERT_NE(test_graph, nullptr) << "Failed to load test model";
187+
188+
const OrtGraph& ort_graph = test_graph->GetOrtGraph();
189+
190+
auto handle_initializer_data = [](const OrtValueInfo* value_info,
191+
const void* data, size_t bytes,
192+
bool& is_external, std::string& location,
193+
int64_t& offset) -> Ort::Status {
194+
(void)value_info;
195+
(void)bytes;
196+
197+
offset = reinterpret_cast<int64_t>(data);
198+
location = "_MEM_ADDR_";
199+
is_external = true; // True if is external initializer.
200+
201+
return Ort::Status{nullptr};
202+
};
203+
204+
ONNX_NAMESPACE::GraphProto graph_proto;
205+
OrtEpUtils::OrtGraphToProto(ort_graph, graph_proto, handle_initializer_data);
206+
207+
// Verify that TensorProto objects within GraphProto point to memory owned by OrtValues in the OrtGraph.
208+
const OrtApi& ort_api = Ort::GetApi();
209+
210+
size_t api_num_initializers = 0;
211+
ASSERT_ORTSTATUS_OK(ort_api.Graph_GetNumInitializers(&ort_graph, &api_num_initializers));
212+
213+
std::vector<const OrtValueInfo*> api_initializers(api_num_initializers);
214+
ASSERT_ORTSTATUS_OK(ort_api.Graph_GetInitializers(&ort_graph, api_initializers.data(), api_initializers.size()));
215+
216+
const auto& tensor_protos = graph_proto.initializer();
217+
ASSERT_EQ(tensor_protos.size(), api_num_initializers);
218+
219+
std::unordered_map<std::string, const ONNX_NAMESPACE::TensorProto*> tensor_proto_map;
220+
for (const auto& tensor_proto : tensor_protos) {
221+
tensor_proto_map.emplace(tensor_proto.name(), &tensor_proto);
222+
}
223+
224+
for (size_t i = 0; i < api_num_initializers; ++i) {
225+
const OrtValue* ort_value = nullptr;
226+
const void* ort_value_data = nullptr;
227+
const char* value_name = nullptr;
228+
229+
ASSERT_ORTSTATUS_OK(ort_api.GetValueInfoName(api_initializers[i], &value_name));
230+
ASSERT_ORTSTATUS_OK(ort_api.ValueInfo_GetInitializerValue(api_initializers[i], &ort_value));
231+
ASSERT_ORTSTATUS_OK(ort_api.GetTensorData(ort_value, &ort_value_data));
232+
233+
auto iter = tensor_proto_map.find(value_name);
234+
ASSERT_NE(iter, tensor_proto_map.end());
235+
const ONNX_NAMESPACE::TensorProto* tensor_proto = iter->second;
236+
ONNX_NAMESPACE::TensorProto_DataLocation data_location = tensor_proto->data_location();
237+
ASSERT_EQ(data_location, ONNX_NAMESPACE::TensorProto_DataLocation_EXTERNAL);
238+
239+
const auto& ext_data_entries = tensor_proto->external_data();
240+
const ONNX_NAMESPACE::StringStringEntryProto& location_entry = ext_data_entries[0];
241+
const ONNX_NAMESPACE::StringStringEntryProto& offset_entry = ext_data_entries[1];
242+
243+
ASSERT_EQ(location_entry.key(), "location");
244+
ASSERT_EQ(location_entry.value(), "_MEM_ADDR_");
245+
ASSERT_EQ(offset_entry.key(), "offset");
246+
247+
long long offset_int = std::stoll(offset_entry.value());
248+
ASSERT_EQ(offset_int, reinterpret_cast<long long>(ort_value_data));
249+
}
250+
}
251+
181252
static void Run3LayerModel(const ORTCHAR_T* model_path, bool input_cond, std::vector<float>& output_data) {
182253
auto memory_info = Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU);
183254
Ort::SessionOptions sess_options;

0 commit comments

Comments
 (0)