Skip to content

Commit 2930feb

Browse files
adrianlizarragayuslepukhinskottmckay
authored andcommitted
[EP ABI] API to get external initializer info + lazy load external OrtValues (microsoft#25482)
### Description - Adds APIs to get information (file path, file offset, byte size) for initializers with data in external files. This allows EPs to do their own custom memory-mapping of initializer data. By default, EPs that don't have specific requirements can still use `ValueInfo_GetInitializerValue` to get an `OrtValue` with memory-mapped initializer data. - Updates `OrtGraph` to only load `OrtValue` for external initializers on demand. This prevents having to memory map all external initializers before the first call to `OrtEp::GetCapability`. Follow up to microsoft#25320 New API functions: | Function | Summary| |-----------|--------------| | `ValueInfo_GetExternalInitializerInfo` | Get `OrtExternalInitializerInfo` from `OrtValueInfo` (or `NULL`). Must be released with `ReleaseExternalInitializerInfo`| | `ReleaseExternalInitializerInfo` | Releases the `OrtExternalInitializerInfo` instance | | `ExternalInitializerInfo_GetFilePath` | Returns the relative path to the file that stores the initializer's data | | `ExternalInitializerInfo_GetFileOffset` | Returns the byte offset within the file where the initializer's data is stored | | `ExternalInitializerInfo_GetByteSize` | Returns the size in bytes of the initializer's data within the file | ### Motivation and Context <!-- - Why is this change required? What problem does it solve? - If it fixes an open issue, please link to the issue here. --> --------- Co-authored-by: Dmitri Smirnov <[email protected]> Co-authored-by: Scott McKay <[email protected]>
1 parent 9ed0d91 commit 2930feb

File tree

14 files changed

+436
-29
lines changed

14 files changed

+436
-29
lines changed

include/onnxruntime/core/graph/graph.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
struct OrtGraph;
4545

4646
namespace onnxruntime {
47+
class ExternalDataInfo;
4748
class Graph;
4849
struct IndexedSubGraph;
4950
class Model;
@@ -788,6 +789,27 @@ class Graph { // NOLINT(clang-analyzer-optin.performance.Padding): preserve exi
788789
*/
789790
bool GetOrtValueInitializer(const std::string& name, OrtValue& value, bool check_outer_scope = false) const;
790791

792+
/// <summary>
793+
/// Loads an initializer with data in an external file into an OrtValue. Does NOT cache the OrtValue
794+
/// in this Graph.
795+
/// </summary>
796+
/// <param name="name">The name of the initializer.</param>
797+
/// <param name="value">Output parameter set to the loaded OrtValue. Set to an existing OrtValue if
798+
/// it is already loaded.</param>
799+
/// <returns>A status indicating an error or success. An error occurs if `name` is not an initializer
800+
/// with external data.</returns>
801+
Status LoadExternalInitializerAsOrtValue(const std::string& name, OrtValue& value) const;
802+
803+
/// <summary>
804+
/// Gets information (external filepath, file offset, num bytes) for an initializer with data in an external file.
805+
/// </summary>
806+
/// <param name="name">The initializer's name.</param>
807+
/// <param name="ext_info">Output parameter set to the location information of the external data.</param>
808+
/// <param name="check_outer_scope">Set to true if parent graphs should be checked.</param>
809+
/// <returns>True if `name` refers to an initializer with data in an external file. Otherwise, returns false</returns>
810+
bool GetExternalInitializerInfo(const std::string& name, std::unique_ptr<ExternalDataInfo>& ext_info,
811+
bool check_outer_scope = false) const;
812+
791813
/** Gets all the initializer tensors in this Graph. */
792814
const InitializedTensorSet& GetAllInitializedTensors() const noexcept { return name_to_initial_tensor_; }
793815

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4+
// DO NOT include ORT header files as this is meant to be a header-only utility that can be copied
5+
// to other projects.
6+
47
/*
58
SUMMARY:
69
Utilities to serialize an OrtGraph into an ONNX GraphProto or ModelProto. Can be used by execution provider
@@ -494,11 +497,14 @@ Ort::Status OrtGraphToProto(const OrtGraph& ort_graph,
494497
auto* ext_data_entries = tensor_proto->mutable_external_data();
495498
onnx::StringStringEntryProto* location_entry = ext_data_entries->Add();
496499
onnx::StringStringEntryProto* offset_entry = ext_data_entries->Add();
500+
onnx::StringStringEntryProto* length_entry = ext_data_entries->Add();
497501

498502
location_entry->set_key("location");
499503
location_entry->set_value(ext_location);
500504
offset_entry->set_key("offset");
501505
offset_entry->set_value(std::to_string(ext_offset));
506+
length_entry->set_key("length");
507+
length_entry->set_value(std::to_string(data_bytes));
502508
} else {
503509
// User wants to store data inline the TensorProto's raw_data
504510
tensor_proto->set_data_location(onnx::TensorProto_DataLocation_DEFAULT);

include/onnxruntime/core/session/onnxruntime_c_api.h

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,7 @@ ORT_RUNTIME_CLASS(HardwareDevice);
324324
ORT_RUNTIME_CLASS(EpDevice);
325325
ORT_RUNTIME_CLASS(KeyValuePairs);
326326
ORT_RUNTIME_CLASS(SyncStream); // Opaque class to create an onnxruntime::Stream.
327+
ORT_RUNTIME_CLASS(ExternalInitializerInfo);
327328

328329
#ifdef _MSC_VER
329330
typedef _Return_type_success_(return == 0) OrtStatus* OrtStatusPtr;
@@ -5484,10 +5485,13 @@ struct OrtApi {
54845485
*
54855486
* Supports initializers defined in an outer scope (i.e., a parent graph).
54865487
*
5488+
* Supports initializers stored in an external file. For external initializers, ORT memory maps
5489+
* the initializer data on the first call to this function. If caller needs custom memory mapping,
5490+
* use ValueInfo_GetExternalInitializerInfo to get the location of the initializer data.
5491+
*
54875492
* \param[in] value_info The OrtValueInfo instance.
5488-
* \param[out] initializer_value Output parameter set to the initializer value or NULL. The OrtValue data pointer
5489-
* (obtained via GetTensorData) is stable during the lifetime of the OrtSession
5490-
* that owns the OrtGraph.
5493+
* \param[out] initializer_value Output parameter set to the initializer value or NULL. Do not cache the OrtValue
5494+
* as it is released when the owning OrtGraph is released.
54915495
*
54925496
* \snippet{doc} snippets.dox OrtStatus Return Value
54935497
*
@@ -5496,6 +5500,24 @@ struct OrtApi {
54965500
ORT_API2_STATUS(ValueInfo_GetInitializerValue, _In_ const OrtValueInfo* value_info,
54975501
_Outptr_ const OrtValue** initializer_value);
54985502

5503+
/** \brief Get information about an external initializer (e.g., filepath, file offset, byte size).
5504+
*
5505+
* Sets the output parameter `info` to NULL if the given OrtValueInfo does not represent an initializer
5506+
* with external data. In this case, a NULL status (non-error) is returned.
5507+
*
5508+
* \param[in] value_info The OrtValueInfo instance.
5509+
* \param[out] info Output parameter set to an OrtExternalInitializerInfo instance that can be used to query
5510+
* file path, file offset, etc. ORT sets this to NULL if the OrtValueInfo does not represent
5511+
* an external initializer.
5512+
* Must release with ReleaseExternalInitializerInfo.
5513+
*
5514+
* \snippet{doc} snippets.dox OrtStatus Return Value
5515+
*
5516+
* \since Version 1.23.
5517+
*/
5518+
ORT_API2_STATUS(ValueInfo_GetExternalInitializerInfo, _In_ const OrtValueInfo* value_info,
5519+
_Outptr_result_maybenull_ OrtExternalInitializerInfo** info);
5520+
54995521
/** \brief Returns a boolean indicating if the given value is a required graph input.
55005522
*
55015523
* For ONNX IR version < 4, all graph inputs without a matching initializer are required.
@@ -6085,6 +6107,50 @@ struct OrtApi {
60856107

60866108
/// @}
60876109

6110+
/// \name OrtExternalInitializerInfo
6111+
/// @{
6112+
6113+
/** \brief Release an OrtExternalInitializerInfo instance.
6114+
*
6115+
* \param[in] input OrtExternalInitializerInfo instance to be released.
6116+
*
6117+
* \since Version 1.23.
6118+
*/
6119+
ORT_CLASS_RELEASE(ExternalInitializerInfo);
6120+
6121+
/** \brief Get the relative path to the file that stores the initializer's data.
6122+
*
6123+
* \note The path is relative to the filesystem directory where the ONNX model was stored.
6124+
* Caller can use Graph_GetModelPath to get the model's full path and construct the absolute path to the
6125+
* external initializer file if necessary.
6126+
*
6127+
* \param[in] info The OrtExternalInitializerInfo instance.
6128+
* \return The relative path to the file that stores the initializer's data. Do NOT free this pointer.
6129+
*
6130+
* \since Version 1.23.
6131+
*/
6132+
ORT_API_T(const ORTCHAR_T*, ExternalInitializerInfo_GetFilePath, _In_ const OrtExternalInitializerInfo* info);
6133+
6134+
/** \brief Get the byte offset within the file where the initializer's data is stored.
6135+
*
6136+
* \param[in] info The OrtExternalInitializerInfo instance.
6137+
* \return The byte offset where the initializer's data is stored within the file.
6138+
*
6139+
* \since Version 1.23.
6140+
*/
6141+
ORT_API_T(int64_t, ExternalInitializerInfo_GetFileOffset, _In_ const OrtExternalInitializerInfo* info);
6142+
6143+
/** \brief Get the size in bytes of the initializer's data within the file.
6144+
*
6145+
* \param[in] info The OrtExternalInitializerInfo instance.
6146+
* \return The size in bytes of the initializer's data within the file.
6147+
*
6148+
* \since Version 1.23.
6149+
*/
6150+
ORT_API_T(size_t, ExternalInitializerInfo_GetByteSize, _In_ const OrtExternalInitializerInfo* info);
6151+
6152+
/// @}
6153+
60886154
/// \name OrtRunOptions
60896155
/// @{
60906156

onnxruntime/core/framework/tensorprotoutils.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,10 @@ bool HasExternalDataInMemory(const ONNX_NAMESPACE::TensorProto& ten_proto) {
252252
return false; // No external data in memory
253253
}
254254

255+
bool HasExternalDataInFile(const ONNX_NAMESPACE::TensorProto& tensor_proto) {
256+
return HasExternalData(tensor_proto) && !HasExternalDataInMemory(tensor_proto);
257+
}
258+
255259
Status TensorProtoWithExternalDataToTensorProto(
256260
const ONNX_NAMESPACE::TensorProto& ten_proto,
257261
const std::filesystem::path& model_path,

onnxruntime/core/framework/tensorprotoutils.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,13 @@ inline bool HasName(const ONNX_NAMESPACE::TypeProto_Opaque& op_proto) {
502502
/// <returns>true if ten_proto has external data and it is in memory</returns>
503503
[[nodiscard]] bool HasExternalDataInMemory(const ONNX_NAMESPACE::TensorProto& tensor_proto);
504504

505+
/// <summary>
506+
/// Check if the given tensor proto has external data store in a file (not in memory).
507+
/// </summary>
508+
/// <param name="tensor_proto"></param>
509+
/// <returns></returns>
510+
[[nodiscard]] bool HasExternalDataInFile(const ONNX_NAMESPACE::TensorProto& tensor_proto);
511+
505512
/// <summary>
506513
/// This function converts TensorProto with external data to TensorProto with inline data.
507514
/// </summary>

onnxruntime/core/graph/abi_graph_types.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <string>
88
#include <vector>
99
#include "core/common/inlined_containers_fwd.h"
10+
#include "core/framework/tensor_external_data_info.h"
1011
#include "core/framework/onnxruntime_typeinfo.h"
1112
#include "core/graph/onnx_protobuf.h"
1213

@@ -29,6 +30,9 @@ enum class OrtGraphIrApi {
2930
kEpApi,
3031
};
3132

33+
// Alias OrtExternalInitializerInfo to the internal type.
34+
struct OrtExternalInitializerInfo : onnxruntime::ExternalDataInfo {};
35+
3236
/// <summary>
3337
/// Public type that represents an ONNX value info.
3438
/// </summary>
@@ -94,6 +98,17 @@ struct OrtValueInfo {
9498
/// <returns>A status indicating success or an error.</returns>
9599
virtual onnxruntime::Status GetInitializerValue(const OrtValue*& value) const = 0;
96100

101+
/// <summary>
102+
/// Get information (file path, file offset, byte size) if this OrtValueInfo represents an initializer with
103+
/// data in an external file.
104+
/// </summary>
105+
/// <param name="ext_info">Output parameter set to the external initializer info or NULL if this is not an external
106+
/// initializer.</param>
107+
/// <returns>A status indicating an error or success. Calling this function on an OrtValueInfo that does not represent
108+
/// an external initializer is NOT an error.</returns>
109+
virtual onnxruntime::Status GetExternalInitializerInfo(
110+
std::unique_ptr<onnxruntime::ExternalDataInfo>& ext_info) const = 0;
111+
97112
/// <summary>
98113
/// Determine if the value is a required graph input.
99114
/// </summary>

onnxruntime/core/graph/ep_api_types.cc

Lines changed: 61 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <vector>
1616

1717
#include "core/framework/allocator.h"
18+
#include "core/framework/tensor_external_data_info.h"
1819
#include "core/framework/tensorprotoutils.h"
1920
#include "core/framework/onnxruntime_typeinfo.h"
2021
#include "core/graph/graph_viewer.h"
@@ -452,11 +453,29 @@ Status EpValueInfo::GetInitializerValue(const OrtValue*& result) const {
452453

453454
// This gets an initializer value defined in this graph or in a parent graph (as long as the value
454455
// is used in this graph).
455-
result = graph_->GetInitializerValue(name_);
456+
ORT_RETURN_IF_ERROR(graph_->GetInitializerValue(name_, result));
456457
ORT_RETURN_IF(result == nullptr, "Unable to find initializer value named '", name_, "'.");
457458
return Status::OK();
458459
}
459460

461+
Status EpValueInfo::GetExternalInitializerInfo(std::unique_ptr<ExternalDataInfo>& result) const {
462+
if (!IsFlagSet(kIsConstantInitializer) && !IsFlagSet(kIsOptionalGraphInput)) {
463+
result = nullptr;
464+
return Status::OK();
465+
}
466+
467+
ORT_RETURN_IF(graph_ == nullptr, "Unable to get external initializer information for value named '",
468+
name_, "': parent graph is NULL");
469+
470+
const onnxruntime::Graph& graph = graph_->GetGraphViewer().GetGraph();
471+
472+
if (!graph.GetExternalInitializerInfo(name_, result, /*check_outer_scope*/ true)) {
473+
result = nullptr;
474+
}
475+
476+
return Status::OK();
477+
}
478+
460479
Status EpValueInfo::IsRequiredGraphInput(bool& is_required_graph_input) const {
461480
is_required_graph_input = IsFlagSet(Flags::kIsRequiredGraphInput);
462481
return Status::OK();
@@ -593,15 +612,18 @@ Status EpGraph::CreateImpl(std::unique_ptr<EpGraph> ep_graph, const GraphViewer&
593612
initializer_value_infos.push_back(value_info);
594613

595614
// Initialize OrtValue for the initializer.
615+
// Note: using std::unique_ptr<OrtValue> because we return a OrtValue* to the user and we want it to be stable.
596616
auto initializer_value = std::make_unique<OrtValue>();
597617
bool graph_has_ortvalue = graph_viewer.GetGraph().GetOrtValueInitializer(initializer_name, *initializer_value,
598618
/*check_outer_scope*/ false);
599619

600620
if (!graph_has_ortvalue) {
601-
// onnxruntime::Graph does not have an OrtValue for this initializer, so create one from the TensorProto.
602-
// This should only happen for small initializers that are needed for ONNX shape inferencing.
603-
ORT_RETURN_IF_ERROR(utils::TensorProtoToOrtValue(Env::Default(), graph_viewer.ModelPath(), *tensor_proto,
604-
initializer_allocator, *initializer_value));
621+
// Copy to OrtValue if not external. This should only happen for small initializers.
622+
// Do nothing for external initializers, as we will load/mmap into an OrtValue on demand.
623+
if (!utils::HasExternalDataInFile(*tensor_proto)) {
624+
ORT_RETURN_IF_ERROR(utils::TensorProtoToOrtValue(Env::Default(), graph_viewer.ModelPath(), *tensor_proto,
625+
initializer_allocator, *initializer_value));
626+
}
605627
}
606628

607629
initializer_values.emplace(value_info->GetName(), std::move(initializer_value));
@@ -650,8 +672,10 @@ Status EpGraph::CreateImpl(std::unique_ptr<EpGraph> ep_graph, const GraphViewer&
650672
}
651673

652674
EpValueInfo* outer_value_info = value_info_iter->second.get();
653-
bool is_constant = false;
675+
676+
// Note: using std::unique_ptr<OrtValue> because we return a OrtValue* to the user and we want it to be stable.
654677
auto outer_initializer_value = std::make_unique<OrtValue>();
678+
bool is_constant = false;
655679
const ONNX_NAMESPACE::TensorProto* outer_initializer = parent_graph->GetInitializer(implicit_name,
656680
*outer_initializer_value,
657681
is_constant,
@@ -665,11 +689,13 @@ Status EpGraph::CreateImpl(std::unique_ptr<EpGraph> ep_graph, const GraphViewer&
665689
// Add the OrtValue if this is an initializer.
666690
if (outer_initializer != nullptr) {
667691
if (!outer_initializer_value->IsAllocated()) {
668-
// onnxruntime::Graph does not have an OrtValue for this initializer, so create one from the TensorProto.
669-
// This should only happen for small initializers that are needed for ONNX shape inferencing.
670-
ORT_RETURN_IF_ERROR(utils::TensorProtoToOrtValue(Env::Default(), parent_graph->ModelPath(),
671-
*outer_initializer, initializer_allocator,
672-
*outer_initializer_value));
692+
// Copy to OrtValue if not external. This should only happen for small initializers.
693+
// Do nothing for external initializers. Will load/mmap into an OrtValue on demand.
694+
if (!utils::HasExternalDataInFile(*outer_initializer)) {
695+
ORT_RETURN_IF_ERROR(utils::TensorProtoToOrtValue(Env::Default(), parent_graph->ModelPath(),
696+
*outer_initializer, initializer_allocator,
697+
*outer_initializer_value));
698+
}
673699
}
674700
outer_scope_initializer_values.emplace(outer_value_info->GetName(), std::move(outer_initializer_value));
675701
}
@@ -808,20 +834,40 @@ const EpNode* EpGraph::GetNode(NodeIndex node_index) const {
808834
return index_to_ep_node_.GetEpNode(node_index);
809835
}
810836

811-
const OrtValue* EpGraph::GetInitializerValue(std::string_view name) const {
837+
Status EpGraph::GetInitializerValue(std::string_view name, const OrtValue*& result) const {
838+
auto ensure_ort_value_loaded = [&](const std::unique_ptr<OrtValue>& ort_value) -> Status {
839+
if (!ort_value->IsAllocated()) {
840+
// Lazy load the OrtValue. This happens for external initializers.
841+
const Graph& graph = graph_viewer_.GetGraph();
842+
ORT_RETURN_IF_ERROR(graph.LoadExternalInitializerAsOrtValue(std::string(name),
843+
const_cast<OrtValue&>(*ort_value)));
844+
}
845+
846+
return Status::OK();
847+
};
848+
812849
// Check for initializer value in the graph's scope.
813850
if (auto iter = initializer_values_.find(name);
814851
iter != initializer_values_.end()) {
815-
return iter->second.get();
852+
const std::unique_ptr<OrtValue>& ort_value = iter->second;
853+
ORT_RETURN_IF_ERROR(ensure_ort_value_loaded(ort_value));
854+
855+
result = ort_value.get();
856+
return Status::OK();
816857
}
817858

818859
// Check for the initializer value in an outer scope.
819860
// Only finds a value if the outer initializer value is used within this graph.
820861
if (auto iter = outer_scope_initializer_values_.find(name);
821862
iter != outer_scope_initializer_values_.end()) {
822-
return iter->second.get();
863+
const std::unique_ptr<OrtValue>& ort_value = iter->second;
864+
ORT_RETURN_IF_ERROR(ensure_ort_value_loaded(ort_value));
865+
866+
result = ort_value.get();
867+
return Status::OK();
823868
}
824869

825-
return nullptr;
870+
result = nullptr;
871+
return Status::OK();
826872
}
827873
} // namespace onnxruntime

onnxruntime/core/graph/ep_api_types.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ struct EpValueInfo : public OrtValueInfo {
6565
// represent an initializer (either constant or non-constant).
6666
Status GetInitializerValue(const OrtValue*& value) const override;
6767

68+
// Gets external initializer information (file path, file offset, byte size) if this is an external initializer.
69+
// Otherwise, sets the output parameter `ext_info` to nullptr (not an error).
70+
Status GetExternalInitializerInfo(std::unique_ptr<onnxruntime::ExternalDataInfo>& ext_info) const override;
71+
6872
// Check if this value is a required graph input.
6973
Status IsRequiredGraphInput(bool& is_required_graph_input) const override;
7074

@@ -351,7 +355,7 @@ struct EpGraph : public OrtGraph {
351355
// Considers both constant and non-constant initializers.
352356
// Supports initializers defined in an outer scope as long as that initializer is used
353357
// within this graph.
354-
const OrtValue* GetInitializerValue(std::string_view name) const;
358+
Status GetInitializerValue(std::string_view name, const OrtValue*& value) const;
355359

356360
private:
357361
/// <summary>

0 commit comments

Comments
 (0)