Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
36ca1d6
Add EPContext data I/O callbacks
May 14, 2026
19b36c0
Add EPContext data callback test coverage
May 15, 2026
73b52ec
Document EPContext data callback contracts
May 16, 2026
7ee974e
Apply lint formatting fixes
May 19, 2026
18f515f
Fix EPContext config release API docs
May 19, 2026
c02af09
Refactor EPContext data callbacks for EP-owned I/O
May 22, 2026
34913c0
Add sample EPContext data fallback helpers
May 22, 2026
583dcbe
Add EPContext helper fallback coverage
Jun 2, 2026
9693266
Address lintrunner review suggestions
Jun 2, 2026
43deeb3
Address EPContext review comments
Jun 2, 2026
cc2e3d3
Address remaining EPContext review comments
Jun 2, 2026
af32616
Document EPContext API placement and sample path-safety
Jun 2, 2026
5aca0e1
Rename EPContext data callback typedefs
Jun 3, 2026
cfcb966
Address review: default example EP embed mode to non-embedded; standa…
Jun 3, 2026
9ae612b
Fix ModelPackageTest.CheckCompiledModelCompatibilityInfo: embed EPCon…
Jun 3, 2026
9e8a411
Rename OrtWriteFileDataFunc/OrtReadFileDataFunc to OrtWriteNamedBuffe…
Jun 3, 2026
617f418
Make EPContext data callback APIs experimental
Jun 10, 2026
b86f1e7
Harden EPContext helper path validation
Jun 11, 2026
da105b2
Address PR review feedback on EPContext callback APIs
Jun 12, 2026
689e3e5
Address EPContext callback review follow-ups
Jun 12, 2026
9e30393
Clarify EPContext external-data error message and callback validation
Jun 12, 2026
9c58baf
Remove premature EPContext external-data validation
Jun 12, 2026
c50584c
Address EPContext review feedback: docs, path symmetry, lifetime, style
Jun 16, 2026
2464603
Apply clang-format alignment to ExpectFailureOrtStatus calls
Jun 18, 2026
d64d708
Add Ort::Experimental::EpContextConfig RAII wrapper and use it in the…
Jun 18, 2026
57c262e
Replace reinterpret_cast EPContext config fake with a real config token
Jun 18, 2026
f610ce3
Split EpContextDataUtils tests into their own file
Jun 18, 2026
5bef55c
Address Copilot review: reset fake-config global, wrap long line, cla…
Jun 19, 2026
c8fef6d
Reject Windows rooted EPContext data names
Jun 19, 2026
d3576aa
Fix clang-format alignment in EpContextDataUtils _WIN32 test block
Jun 19, 2026
dcdb5f6
Address review: assert non-null status in test helper; verify Release…
Jun 19, 2026
25e9a5f
Address review: validate Win32 UTF-8 conversions and guard null write…
Jun 20, 2026
70d850b
Add null-status guard to ExpectOrtStatusError test helper for consist…
Jun 20, 2026
d54cf1c
Clarify GetEpContextConfig doc: handle is non-NULL on success, *confi…
Jun 22, 2026
9eaaf47
Merge branch 'main' into gokrishnan/EpContextHelperUtilities
Jun 22, 2026
773f2fe
Address review: harden untrusted EPContext path resolution and merge …
Jun 22, 2026
e870307
Address review: report UTF-8 path conversion errors via OrtStatus*
Jun 22, 2026
fa69850
Address review findings in EPContext data path helper
Jun 22, 2026
b7055f7
Add symlink escape coverage for EPContext data path resolver
Jun 22, 2026
b6203ac
Clarify EPContext write callback interaction with binary info
Jun 22, 2026
77ffd69
Fix EpContextDataUtils symlink-escape test: use absolute symlink target
Jun 23, 2026
f0843e3
Merge remote-tracking branch 'origin/main' into gokrishnan/EpContextH…
Jun 23, 2026
39ed361
Address EPContext helper review feedback
Jun 23, 2026
88e2e3a
Address latest EPContext review comments
Jun 24, 2026
7ab31a1
Address EPContext review comments (latest round)
Jun 25, 2026
6e82dca
Fix AndroidBinarySizeCheckJob_MinimalBaseline: bump threshold to 1438…
Copilot Jun 25, 2026
4fe683e
Restore <cstring> include for std::memset in ep_plugin_provider_test.cc
Jun 25, 2026
b2e31a2
example EP: use EXCEPTION_TO_RETURNED_STATUS macros in CreateEpImpl
Jun 25, 2026
e7f51d1
example EP helpers: reject model-derived EPContext names that designa…
Jun 25, 2026
a3cd1ba
Address EPContext review nits
Jun 25, 2026
99a3309
Merge remote-tracking branch 'origin/main' into gokrishnan/EpContextH…
Jun 26, 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
4 changes: 2 additions & 2 deletions .github/workflows/android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ jobs:
run: |
set -e -x
BINARY_SIZE_THRESHOLD_ARGS=""
echo "Binary size threshold in bytes: 1436672"
BINARY_SIZE_THRESHOLD_ARGS="--threshold_size_in_bytes 1436672"
echo "Binary size threshold in bytes: 1438720"
BINARY_SIZE_THRESHOLD_ARGS="--threshold_size_in_bytes 1438720"

# Ensure ANDROID_NDK_HOME is available and get its real path
if [ -z "$ANDROID_NDK_HOME" ]; then
Expand Down
2 changes: 2 additions & 0 deletions cmake/onnxruntime_unittests.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,7 @@ set (onnxruntime_shared_lib_test_SRC
${ONNXRUNTIME_SHARED_LIB_TEST_SRC_DIR}/custom_op_utils.cc
${ONNXRUNTIME_SHARED_LIB_TEST_SRC_DIR}/test_allocator.cc
${ONNXRUNTIME_SHARED_LIB_TEST_SRC_DIR}/test_data_copy.cc
${ONNXRUNTIME_SHARED_LIB_TEST_SRC_DIR}/test_ep_context_data_api.cc
${ONNXRUNTIME_SHARED_LIB_TEST_SRC_DIR}/test_experimental_api.cc
${ONNXRUNTIME_SHARED_LIB_TEST_SRC_DIR}/test_fixture.h
${ONNXRUNTIME_SHARED_LIB_TEST_SRC_DIR}/test_model_loading.cc
Expand Down Expand Up @@ -2174,6 +2175,7 @@ if (onnxruntime_BUILD_SHARED_LIB AND
#
file(GLOB onnxruntime_autoep_test_library_src "${TEST_SRC_DIR}/autoep/library/example_plugin_ep/*.h"
"${TEST_SRC_DIR}/autoep/library/example_plugin_ep/*.cc"
"${TEST_SRC_DIR}/autoep/library/ep_context_data_utils.h"
"${TEST_SRC_DIR}/autoep/library/plugin_ep_utils.h")
onnxruntime_add_shared_library_module(example_plugin_ep ${onnxruntime_autoep_test_library_src})
target_include_directories(example_plugin_ep PRIVATE ${REPO_ROOT}/include/onnxruntime/core/session)
Expand Down
62 changes: 62 additions & 0 deletions include/onnxruntime/core/session/onnxruntime_experimental_c_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,68 @@ ORT_RUNTIME_CLASS(ModelPackageOptions);
ORT_RUNTIME_CLASS(ModelPackageContext);
ORT_RUNTIME_CLASS(ModelPackageComponentContext);

// Opaque handle holding the EPContext callbacks and opaque state extracted from an OrtSessionOptions instance. Used by
// the experimental OrtEpApi_* EPContext data functions. Create via OrtEpApi_SessionOptions_GetEpContextConfig and
// release with OrtEpApi_ReleaseEpContextConfig.
ORT_RUNTIME_CLASS(EpContextConfig);

/** \brief Function called to write named binary data.
*
* This callback is currently used for EPContext binary data, but its contract is intentionally generic so future APIs
* can reuse it for other named data payloads. The callback is called synchronously by the component that receives it.
* ORT does not own or retain buffer after the callback returns. ORT does not serialize invocations made by different
* EP instances or worker threads.
*
* Each callback invocation represents one complete write operation for name. The callback signature does not
* provide an offset, sequence number, or final-chunk marker, so the component invoking the callback must define any
* chunked ordering and completion contract with the application. Current EPContext use should prefer a single callback
* invocation per EPContext binary unless chunking semantics are documented by the EP.
*
* The application's implementation can process the data in any way (e.g., encrypt and store, upload to cloud storage,
* or compress) before persisting it.
*
* \param[in] state Opaque pointer holding the user's state. ORT does not own or manage this pointer. The application
* must keep it valid for the duration required by the API that accepted the callback and must provide
* any synchronization required if it can be used concurrently.
* \param[in] name The file name or logical data identifier as a null-terminated UTF-8 string.
* \param[in] buffer The buffer containing data to write.
* \param[in] buffer_num_bytes The size of the buffer in bytes.
*
* \return OrtStatus* Write status. Return nullptr on success.
* On failure, use CreateStatus to provide error info with an appropriate OrtErrorCode
* (e.g., ORT_FAIL); ORT propagates the returned code. ORT will release the OrtStatus* if not null.
*/
typedef OrtStatus*(ORT_API_CALL* OrtWriteNamedBufferFunc)(_In_ void* state,
_In_ const char* name,
_In_ const void* buffer,
_In_ size_t buffer_num_bytes);

/** \brief Function called to read named binary data.
*
* This callback is currently used for EPContext binary data, but its contract is intentionally generic so future APIs
* can reuse it for other named data payloads. The application reads, processes (e.g., decrypts, decompresses,
* downloads), and returns the requested data. ORT provides an allocator so the application can allocate the output
* buffer directly. The callback is called synchronously by the component that receives it. ORT does not serialize
* invocations made by different EP instances or worker threads.
*
* \param[in] state Opaque pointer holding the user's state. ORT does not own or manage this pointer. The application
* must keep it valid for the duration required by the API that accepted the callback and must provide
* any synchronization required if it can be used concurrently.
* \param[in] name The file name or logical data identifier to read as a null-terminated UTF-8 string.
* \param[in] allocator ORT-provided allocator. The application must use this to allocate the output buffer.
* \param[out] buffer Set by the implementation to the allocated buffer containing the output data.
* \param[out] data_size Set by the implementation to the size of the output data in bytes.
*
* \return OrtStatus* Read status. Return nullptr on success.
* On failure, use CreateStatus to provide error info with an appropriate OrtErrorCode
* (e.g., ORT_FAIL); ORT propagates the returned code. ORT will release the OrtStatus* if not null.
*/
typedef OrtStatus*(ORT_API_CALL* OrtReadNamedBufferFunc)(_In_ void* state,
_In_ const char* name,
_In_ OrtAllocator* allocator,
_Outptr_ void** buffer,
_Out_ size_t* data_size);

Comment thread
GopalakrishnanN marked this conversation as resolved.
//
// C function pointer typedefs and name constants
//
Expand Down
114 changes: 114 additions & 0 deletions include/onnxruntime/core/session/onnxruntime_experimental_c_api.inc
Original file line number Diff line number Diff line change
Expand Up @@ -282,3 +282,117 @@ ORT_EXPERIMENTAL_API(28, OrtStatusPtr, OrtModelPackageApi_CreateSession,
_In_ OrtModelPackageComponentContext* context,
_In_opt_ const OrtSessionOptions* session_options,
_Outptr_ OrtSession** session)

/** \brief Registers a callback to provide EPContext binary data during session load.
*
* When loading a compiled model with external (non-embedded) EPContext binary data, an execution provider can
* retrieve this callback from OrtEpContextConfig and call it instead of reading the binary data from disk.
*
* Reading happens at session load, so this callback is configured on OrtSessionOptions. The corresponding write
* callback runs only at compile time and is configured on OrtModelCompilationOptions via
* OrtCompileApi_ModelCompilationOptions_SetEpContextDataWriteFunc.
*
* The state pointer is stored as-is and is not owned by ORT. It must remain valid while any session or EP created
* from these options may call the callback. If the same state may be used by multiple EPs or threads, the application
* is responsible for synchronization.
*
* \param[in] options The OrtSessionOptions instance.
* \param[in] read_func The OrtReadNamedBufferFunc callback. Pass NULL to clear a previously set callback (any
* previously set state is cleared as well).
* \param[in] state Opaque state passed to read_func. Can be NULL. Ignored when read_func is NULL.
*
* \snippet{doc} snippets.dox OrtStatus Return Value
*/
ORT_EXPERIMENTAL_API(28, OrtStatusPtr, OrtApi_SessionOptions_SetEpContextDataReadFunc,
_Inout_ OrtSessionOptions* options, _In_opt_ OrtReadNamedBufferFunc read_func, _In_opt_ void* state)

/** \brief Sets a callback for writing EPContext binary data during compilation.
*
* When EPContext embed mode is disabled, execution providers can retrieve this callback from OrtEpContextConfig and
* call it instead of writing EPContext binary data directly to disk.
*
* This callback may be used together with OrtCompileApi::ModelCompilationOptions_SetEpContextBinaryInformation. The
* binary information still describes the compiled model/output location that EPs may use to generate stable logical
* EPContext data names or as a file-fallback location. If this callback is configured, EPs should call it for
* EPContext binary data instead of writing that data to the fallback file path.
*
* Writing happens only at compile time, so this callback is configured on OrtModelCompilationOptions. The
* corresponding read callback runs at session load and is configured on OrtSessionOptions via
* OrtApi_SessionOptions_SetEpContextDataReadFunc.
*
* The state pointer is stored as-is and is not owned by ORT. It must remain valid for the duration of the compile
* operation that may call the callback. If the same state may be used by multiple EPs or threads, the application is
* responsible for synchronization.
*
* Like OrtApi_SessionOptions_SetEpContextDataReadFunc, passing a NULL write_func clears any previously set callback
* (any previously set state is cleared as well). Calling this multiple times overwrites the previously configured
* callback.
*
* \param[in] model_compile_options The OrtModelCompilationOptions instance.
* \param[in] write_func The OrtWriteNamedBufferFunc callback used to write EPContext bytes. Pass NULL to clear a
* previously set callback (any previously set state is cleared as well).
* \param[in] state Opaque state passed to write_func. Can be NULL. Ignored when write_func is NULL.
*
* \snippet{doc} snippets.dox OrtStatus Return Value
*/
ORT_EXPERIMENTAL_API(28, OrtStatusPtr, OrtCompileApi_ModelCompilationOptions_SetEpContextDataWriteFunc,
Comment thread
GopalakrishnanN marked this conversation as resolved.
_In_ OrtModelCompilationOptions* model_compile_options,
_In_opt_ OrtWriteNamedBufferFunc write_func, _In_opt_ void* state)

/** \brief Extracts the EPContext configuration (callbacks and state) from an OrtSessionOptions instance.
*
* The EP should call this during CreateEp() while session_options is still valid, and store the returned handle for
* use during Compile(). On success, `*config` is set to a non-NULL handle that must be released with
* OrtEpApi_ReleaseEpContextConfig. On failure, an error status is returned and `*config` is not modified.
*
* The returned handle owns only ORT's copy of callback function pointers and opaque state pointer values. It does not
* own the application-provided state. The application is responsible for keeping callback state valid and
* synchronized while an EP may call callbacks retrieved from this config.
*
* \param[in] session_options The OrtSessionOptions instance.
* \param[out] config The extracted OrtEpContextConfig.
*
* \snippet{doc} snippets.dox OrtStatus Return Value
*/
ORT_EXPERIMENTAL_API(28, OrtStatusPtr, OrtEpApi_SessionOptions_GetEpContextConfig,
_In_ const OrtSessionOptions* session_options, _Outptr_ OrtEpContextConfig** config)

/** \brief Release an OrtEpContextConfig instance.
*
* \param[in] config The OrtEpContextConfig instance to release. May be NULL.
*/
ORT_EXPERIMENTAL_API(28, void, OrtEpApi_ReleaseEpContextConfig, _Frees_ptr_opt_ OrtEpContextConfig* config)

/** \brief Get the application-provided EPContext data read callback.
*
* Returns the OrtReadNamedBufferFunc and opaque state pointer registered via
* OrtApi_SessionOptions_SetEpContextDataReadFunc. If no callback was registered, *read_func and *state are set to
* NULL. The EP is responsible for calling the callback when present and for using its own normal read path when no
* callback is present.
*
* \param[in] config The OrtEpContextConfig from OrtEpApi_SessionOptions_GetEpContextConfig.
* \param[out] read_func The registered read callback, or NULL if none was registered.
* \param[out] state Opaque state pointer passed to read_func, or NULL if none was registered.
*
* \snippet{doc} snippets.dox OrtStatus Return Value
*/
ORT_EXPERIMENTAL_API(28, OrtStatusPtr, OrtEpApi_EpContextConfig_GetEpContextDataReadFunc,
_In_ const OrtEpContextConfig* config, _Out_ OrtReadNamedBufferFunc* read_func,
_Out_ void** state)

/** \brief Get the application-provided EPContext data write callback.
*
* Returns the OrtWriteNamedBufferFunc and opaque state pointer registered via
* OrtCompileApi_ModelCompilationOptions_SetEpContextDataWriteFunc. If no callback was registered, *write_func and
* *state are set to NULL. The EP is responsible for calling the callback when present and for using its own normal
* write path when no callback is present.
*
* \param[in] config The OrtEpContextConfig from OrtEpApi_SessionOptions_GetEpContextConfig.
* \param[out] write_func The registered write callback, or NULL if none was registered.
* \param[out] state Opaque state pointer passed to write_func, or NULL if none was registered.
*
* \snippet{doc} snippets.dox OrtStatus Return Value
*/
ORT_EXPERIMENTAL_API(28, OrtStatusPtr, OrtEpApi_EpContextConfig_GetEpContextDataWriteFunc,
_In_ const OrtEpContextConfig* config, _Out_ OrtWriteNamedBufferFunc* write_func,
_Out_ void** state)
Original file line number Diff line number Diff line change
Expand Up @@ -108,5 +108,79 @@ namespace Experimental {
// C++ wrapper types or helpers go here in the `Ort::Experimental` namespace.
//

// Move-only RAII owner for an OrtEpContextConfig handle, which carries the EPContext read/write callbacks and opaque
// state extracted from an OrtSessionOptions instance. The handle is released via OrtEpApi_ReleaseEpContextConfig when
// the wrapper is destroyed.
//
// Typical EP usage: construct from the session options during CreateEp(), keep the wrapper for the EP's lifetime, and
// query the callbacks via GetReadFunc() / GetWriteFunc().
class EpContextConfig {
public:
explicit EpContextConfig(std::nullptr_t) noexcept {}
Comment thread
GopalakrishnanN marked this conversation as resolved.

explicit EpContextConfig(const SessionOptions& session_options) : EpContextConfig{session_options.GetConst()} {}

// Extracts the EPContext config from `session_options`. Throws Ort::Exception (ORT_NOT_IMPLEMENTED) if the
// experimental functions are not available in this build, or propagates any error from the extraction.
explicit EpContextConfig(ConstSessionOptions session_options) {
const OrtApi* api = &GetApi();
// Ensure the release function is available before creating a handle, so the handle can always be freed.
Get_OrtEpApi_ReleaseEpContextConfig_SinceV28_FnOrThrow(api);
auto* get_config = Get_OrtEpApi_SessionOptions_GetEpContextConfig_SinceV28_FnOrThrow(api);
ThrowOnError(get_config(static_cast<const OrtSessionOptions*>(session_options), &config_));
}

EpContextConfig(EpContextConfig&& other) noexcept : config_{other.config_} { other.config_ = nullptr; }

EpContextConfig& operator=(EpContextConfig&& other) noexcept {
if (this != &other) {
reset();
config_ = other.config_;
other.config_ = nullptr;
}
return *this;
}

EpContextConfig(const EpContextConfig&) = delete;
EpContextConfig& operator=(const EpContextConfig&) = delete;

~EpContextConfig() { reset(); }

OrtEpContextConfig* get() const noexcept { return config_; }
explicit operator bool() const noexcept { return config_ != nullptr; }

// Relinquishes ownership of the handle without releasing it.
OrtEpContextConfig* release() noexcept {
OrtEpContextConfig* released = config_;
config_ = nullptr;
return released;
}

// Releases any owned handle and resets to empty.
void reset() noexcept {
if (config_ != nullptr) {
if (auto* release_fn = Get_OrtEpApi_ReleaseEpContextConfig_SinceV28_Fn(&GetApi())) {
release_fn(config_);
}
config_ = nullptr;
}
}

// Returns the configured read callback and opaque state (both nullptr if none was set). Throws on failure.
void GetReadFunc(OrtReadNamedBufferFunc& read_func, void*& state) const {
auto* get_read_func = Get_OrtEpApi_EpContextConfig_GetEpContextDataReadFunc_SinceV28_FnOrThrow(&GetApi());
ThrowOnError(get_read_func(config_, &read_func, &state));
}

// Returns the configured write callback and opaque state (both nullptr if none was set). Throws on failure.
void GetWriteFunc(OrtWriteNamedBufferFunc& write_func, void*& state) const {
auto* get_write_func = Get_OrtEpApi_EpContextConfig_GetEpContextDataWriteFunc_SinceV28_FnOrThrow(&GetApi());
ThrowOnError(get_write_func(config_, &write_func, &state));
}

private:
OrtEpContextConfig* config_ = nullptr;
};

} // namespace Experimental
} // namespace Ort
4 changes: 4 additions & 0 deletions onnxruntime/core/framework/ep_context_options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ const BufferWriteFuncHolder* ModelGenOptions::TryGetOutputModelWriteFunc() const
return std::get_if<BufferWriteFuncHolder>(&output_model_location);
}

const EpContextDataWriteFuncHolder* ModelGenOptions::TryGetEpContextDataWriteFunc() const {
return ep_context_data_write_func.write_func != nullptr ? &ep_context_data_write_func : nullptr;
}

bool ModelGenOptions::AreInitializersEmbeddedInOutputModel() const {
return std::holds_alternative<std::monostate>(initializers_location);
}
Expand Down
14 changes: 14 additions & 0 deletions onnxruntime/core/framework/ep_context_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
#include <variant>
#include "core/framework/allocator.h"
#include "core/framework/config_options.h"
// Needed for OrtWriteNamedBufferFunc (used by EpContextDataWriteFuncHolder below). This include can be removed
// once the experimental EPContext data callback APIs are promoted to the stable C API.
#include "core/session/onnxruntime_experimental_c_api.h"
Comment thread
GopalakrishnanN marked this conversation as resolved.

namespace onnxruntime {
namespace epctx {
Expand All @@ -27,6 +30,14 @@ struct BufferWriteFuncHolder {
void* stream_state = nullptr; // Opaque pointer to user's stream state. Passed as first argument to write_func.
};

/// <summary>
/// Holds the opaque state and write function that EPs use to write EPContext binary data.
/// </summary>
struct EpContextDataWriteFuncHolder {
OrtWriteNamedBufferFunc write_func = nullptr;
void* state = nullptr;
};

/// <summary>
/// Holds path and size threshold used to write out initializers to an external file.
/// </summary>
Expand Down Expand Up @@ -84,10 +95,13 @@ struct ModelGenOptions {
InitializerHandler> // Custom function called for every initializer to determine location.
initializers_location = std::monostate{};

EpContextDataWriteFuncHolder ep_context_data_write_func = {};

bool HasOutputModelLocation() const;
const std::filesystem::path* TryGetOutputModelPath() const;
const BufferHolder* TryGetOutputModelBuffer() const;
const BufferWriteFuncHolder* TryGetOutputModelWriteFunc() const;
const EpContextDataWriteFuncHolder* TryGetEpContextDataWriteFunc() const;

bool AreInitializersEmbeddedInOutputModel() const;
const ExternalInitializerFileInfo* TryGetExternalInitializerFileInfo() const;
Expand Down
Loading
Loading