Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 3 additions & 1 deletion src/config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ namespace Generators {
std::string_view NormalizeProviderName(std::string_view name) {
std::string lower_name(name);
std::transform(lower_name.begin(), lower_name.end(), lower_name.begin(), [](unsigned char c) { return static_cast<unsigned char>(std::tolower(c)); });
if (lower_name == "qnn") {
if (lower_name == "cpu" || lower_name == "cpuexecutionprovider") {
return "CPU";
} else if (lower_name == "qnn") {
return "QNN";
} else if (lower_name == "webgpu") {
return "WebGPU";
Expand Down
17 changes: 17 additions & 0 deletions src/models/session_options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include "session_options.h"

#include <algorithm>
#include <functional>
#include <unordered_map>

Expand Down Expand Up @@ -145,9 +146,25 @@ DeviceInterface* SetProviderSessionOptions(OrtSessionOptions& session_options,
const Config&,
bool);

// CPU EP is always implicitly registered — no action needed.
// Throws if users attempt to set provider options (which would be silently lost).
static auto CPUAppendExecutionProvider = [](OrtSessionOptions&,
const Config::ProviderOptions& provider_options,
const Config&,
bool) -> DeviceInterface* {
if (!provider_options.options.empty()) {
throw std::runtime_error(
"CPU execution provider does not support provider options. "
"CPU is always available as the default fallback and does not need to be explicitly registered. "
"Remove the CPU provider entry and its options from your configuration.");
}
return nullptr;
};

// Dispatch table: maps provider name (as it appears in genai_config.json) to
// the corresponding provider-specific AppendExecutionProvider function.
static const std::unordered_map<std::string, AppendExecutionProviderFn> append_execution_provider{
{"CPU", CPUAppendExecutionProvider},
{"cuda", CUDAExecutionProvider::AppendExecutionProvider},
{"DML", DMLExecutionProvider::AppendExecutionProvider},
{"NvTensorRtRtx", NvTensorRtRtxExecutionProvider::AppendExecutionProvider},
Expand Down
25 changes: 25 additions & 0 deletions test/c_api_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,31 @@ TEST(CAPITests, Config) {
#endif
}

// Regression test: appending CPU provider should not throw.
// See https://github.com/microsoft/onnxruntime-genai/pull/2179
TEST(CAPITests, AppendCpuProvider) {
#if TEST_PHI2
auto config = OgaConfig::Create(PHI2_PATH);
config->ClearProviders();
config->AppendProvider("cpu");
auto model = OgaModel::Create(*config);
ASSERT_NE(model.get(), nullptr);

// Also test other case variants
auto config2 = OgaConfig::Create(PHI2_PATH);
config2->ClearProviders();
config2->AppendProvider("CPU");
auto model2 = OgaModel::Create(*config2);
ASSERT_NE(model2.get(), nullptr);

auto config3 = OgaConfig::Create(PHI2_PATH);
config3->ClearProviders();
config3->AppendProvider("CPUExecutionProvider");
auto model3 = OgaModel::Create(*config3);
ASSERT_NE(model3.get(), nullptr);
#endif
}

TEST(CAPITests, TokenizerCAPI) {
#if TEST_PHI2
auto config = OgaConfig::Create(PHI2_PATH);
Expand Down
Loading