Skip to content

Commit ec9b8fe

Browse files
Fix early check that prevents a compiled model from being overwritten (#26439)
### Description Fixes #26294 When using the old model compilation approach (session option configuration), ORT should verify that the generated output model does not already exist. Importantly, this check should be done _before_ calling an EP's compile() method. This PR fixes this check, which was unintentionally disabled by a [previous PR.](#25455). Note that this check also (correctly) happens _after_ calling the EP's compile() method, but it is better to catch it early if we can. ### Motivation and Context Fixes a regression in the older compilation workflow.
1 parent fc3bc09 commit ec9b8fe

File tree

3 files changed

+83
-15
lines changed

3 files changed

+83
-15
lines changed

onnxruntime/core/framework/ep_context_options.cc

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@ namespace epctx {
1515

1616
ModelGenOptions::ModelGenOptions() = default;
1717

18+
// This constructor is only used when using the older compilation approach that uses
19+
// session configuration options to enable and configure compilation (i.e., not OrtCompileApi).
20+
// This initializes ModelGenOptions from the session config options.
1821
ModelGenOptions::ModelGenOptions(const ConfigOptions& config_options) {
1922
enable = config_options.GetConfigOrDefault(kOrtSessionOptionEpContextEnable, "0") == "1";
2023

21-
std::string output_model_path = config_options.GetConfigOrDefault(kOrtSessionOptionEpContextFilePath, "");
22-
if (!output_model_path.empty()) {
23-
output_model_location = std::filesystem::path(output_model_path);
24-
} else {
25-
output_model_location = std::monostate{};
26-
}
24+
// Note: the older compilation approach only supports compiling to an output file.
25+
output_model_location = std::filesystem::path(
26+
config_options.GetConfigOrDefault(kOrtSessionOptionEpContextFilePath, ""));
2727

2828
std::string external_initializers_file_path = config_options.GetConfigOrDefault(
2929
kOrtSessionOptionsEpContextModelExternalInitializersFileName, "");

onnxruntime/core/session/utils.cc

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -134,18 +134,29 @@ static OrtStatus* CreateSessionAndLoadModelImpl(_In_ const OrtSessionOptions* op
134134
bool load_config_from_model =
135135
os_env.GetEnvironmentVar(inference_session_utils::kOrtLoadConfigFromModelEnvVar) == "1";
136136

137-
// If ep.context_enable is set, then ep.context_file_path is expected, otherwise ORT don't know where to generate the _ctx.onnx file
137+
// Check EPContext model generation options when the input model is loaded from memory (no input model path).
138138
if (options && model_path == nullptr) {
139139
epctx::ModelGenOptions ep_ctx_gen_options = options->value.GetEpContextGenerationOptions();
140140

141-
// This is checked by the OrtCompileApi's CompileModel() function, but we check again here in case
142-
// the user used the older SessionOptions' configuration entries to generate a compiled model.
143-
if (ep_ctx_gen_options.enable && !ep_ctx_gen_options.HasOutputModelLocation()) {
144-
return OrtApis::CreateStatus(ORT_FAIL,
145-
"Inference session was configured with EPContext model generation enabled but "
146-
"without a valid location (e.g., file or buffer) for the output model. "
147-
"Please specify a valid ep.context_file_path via SessionOption configs "
148-
"or use the OrtCompileApi to compile a model to a file or buffer.");
141+
if (ep_ctx_gen_options.enable) {
142+
auto* output_model_path = ep_ctx_gen_options.TryGetOutputModelPath();
143+
144+
// If the user does not provide an output model location, ORT normally generates an output model file path based
145+
// on the input model's path (i.e., replace ".onnx" with "_ctx.onnx"). However, because there is no input model
146+
// path, we require the application to explicitly set the output model's location.
147+
//
148+
// Note: This is checked by the OrtCompileApi's CompileModel() function, but we check again here in case
149+
// the user used the older SessionOptions' configuration entries to generate a compiled model.
150+
if (!ep_ctx_gen_options.HasOutputModelLocation() || // No output model location (file, buffer, etc.)
151+
(output_model_path != nullptr && output_model_path->empty()) // Has an output file, but it is empty.
152+
) {
153+
return OrtApis::CreateStatus(ORT_FAIL,
154+
"Inference session with a model loaded from bytes was configured with EPContext "
155+
"model generation enabled but without a valid location (e.g., file or buffer) "
156+
"for the output model. Please specify a valid ep.context_file_path via "
157+
"SessionOption configs or use the OrtCompileApi to compile a model to a "
158+
"file or buffer.");
159+
}
149160
}
150161
}
151162

onnxruntime/test/autoep/test_execution.cc

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <gtest/gtest.h>
1111

1212
#include "core/session/onnxruntime_cxx_api.h"
13+
#include "core/session/onnxruntime_session_options_config_keys.h"
1314

1415
#include "test/autoep/test_autoep_utils.h"
1516
#include "test/shared_lib/utils.h"
@@ -184,6 +185,62 @@ TEST(OrtEpLibrary, PluginEp_VirtGpu_GenEpContextModel) {
184185
ASSERT_TRUE(std::filesystem::exists(output_model_file));
185186
}
186187
}
188+
189+
// Uses the original compiling approach with session option configs (instead of explicit compile API).
190+
// Test that ORT does not overwrite an output model if it already exists.
191+
// Notably, this tests the case in which ORT automatically generates the output model name.
192+
TEST(OrtEpLibrary, PluginEp_GenEpContextModel_ErrorOutputModelExists_AutoGenOutputModelName) {
193+
const ORTCHAR_T* input_model_file = ORT_TSTR("testdata/mul_1.onnx");
194+
const ORTCHAR_T* expected_output_model_file = ORT_TSTR("testdata/mul_1_ctx.onnx");
195+
std::filesystem::remove(expected_output_model_file);
196+
197+
RegisteredEpDeviceUniquePtr example_ep;
198+
Utils::RegisterAndGetExampleEp(*ort_env, Utils::example_ep_info, example_ep);
199+
Ort::ConstEpDevice plugin_ep_device(example_ep.get());
200+
std::unordered_map<std::string, std::string> ep_options;
201+
202+
// Compile a model and let ORT set the output model name. This should succeed.
203+
{
204+
Ort::SessionOptions so;
205+
so.AddConfigEntry(kOrtSessionOptionEpContextEnable, "1");
206+
// Don't specify an output model path to let ORT automatically generate it!
207+
// so.AddConfigEntry(kOrtSessionOptionEpContextFilePath, "");
208+
209+
so.AppendExecutionProvider_V2(*ort_env, {plugin_ep_device}, ep_options);
210+
211+
Ort::Session session(*ort_env, input_model_file, so);
212+
ASSERT_TRUE(std::filesystem::exists(expected_output_model_file)); // check compiled model was generated.
213+
}
214+
215+
auto modify_time_1 = std::filesystem::last_write_time(expected_output_model_file);
216+
217+
// Try compiling the model again. ORT should return an error because the output model already exists.
218+
// Original compiled model should not be modified.
219+
{
220+
Ort::SessionOptions so;
221+
so.AddConfigEntry(kOrtSessionOptionEpContextEnable, "1");
222+
// Don't specify an output model path to let ORT automatically generate it!
223+
// so.AddConfigEntry(kOrtSessionOptionEpContextFilePath, "");
224+
225+
so.AppendExecutionProvider_V2(*ort_env, {plugin_ep_device}, ep_options);
226+
227+
try {
228+
Ort::Session session(*ort_env, input_model_file, so);
229+
FAIL(); // Should not get here!
230+
} catch (const Ort::Exception& excpt) {
231+
ASSERT_EQ(excpt.GetOrtErrorCode(), ORT_FAIL);
232+
ASSERT_THAT(excpt.what(),
233+
testing::HasSubstr("exists already. "
234+
"Please remove the EP context model if you want to re-generate it."));
235+
236+
ASSERT_TRUE(std::filesystem::exists(expected_output_model_file));
237+
auto modify_time_2 = std::filesystem::last_write_time(expected_output_model_file);
238+
ASSERT_EQ(modify_time_2, modify_time_1); // Check that file was not modified
239+
}
240+
}
241+
242+
std::filesystem::remove(expected_output_model_file);
243+
}
187244
} // namespace test
188245
} // namespace onnxruntime
189246

0 commit comments

Comments
 (0)