From e7306a087a4b52cac0fb55e529164e48cd6726f7 Mon Sep 17 00:00:00 2001
From: "Plamen K. Kosseff" <333840+blackd@users.noreply.github.com>
Date: Wed, 23 Sep 2026 13:16:31 +0300
Subject: [PATCH] feat(audio-cpp): AUDIOCPP_DEFAULT_BACKEND fallback for models
without a backend option (#12133)
Models whose options carry no explicit backend: open their session on the
CPU backend even in accelerator images. The gallery entries carry
backend:best since #11892; this covers hand-written model configurations
the same way, per deployment: the environment variable supplies the
fallback, an explicit backend: option always wins (merged beside the
existing threads and maingpu fallbacks), and validation reuses the
option parser.
Assisted-by: Claude:claude-fable-5
Signed-off-by: Plamen K. Kosseff
---
backend/cpp/audio-cpp/grpc-server.cpp | 19 +++++++++++++++++++
backend/cpp/audio-cpp/model_options.cpp | 1 +
backend/cpp/audio-cpp/model_options.h | 4 ++++
backend/cpp/audio-cpp/model_options_test.cpp | 5 +++++
backend/cpp/audio-cpp/run.sh | 5 +++++
docs/content/features/audio-cpp.md | 6 +++++-
6 files changed, 39 insertions(+), 1 deletion(-)
diff --git a/backend/cpp/audio-cpp/grpc-server.cpp b/backend/cpp/audio-cpp/grpc-server.cpp
index 53fa173c5..228eebf9d 100644
--- a/backend/cpp/audio-cpp/grpc-server.cpp
+++ b/backend/cpp/audio-cpp/grpc-server.cpp
@@ -596,6 +596,25 @@ public:
if (!parsed.options.device_set && !request->maingpu().empty()) {
parsed.options.device = parse_device_index(request->maingpu());
}
+ // AUDIOCPP_DEFAULT_BACKEND is the deployment's backend fallback:
+ // an accelerator image sets it (typically to "best") so models
+ // without an explicit backend: option use the compiled
+ // accelerator instead of the CPU default — gallery entries carry
+ // backend:best, hand-written model configs get the same fix
+ // here. An explicit backend: option wins, matching threads and
+ // maingpu above. Validation reuses the option parser itself.
+ if (!parsed.options.backend_set) {
+ const char * env = std::getenv("AUDIOCPP_DEFAULT_BACKEND");
+ if (env != nullptr && *env != '\0') {
+ auto fallback = audiocpp_backend::parse_model_options(
+ {std::string("backend:") + env});
+ if (!fallback.error.empty()) {
+ throw audiocpp_backend::ConfigError(
+ "audio-cpp: AUDIOCPP_DEFAULT_BACKEND: " + fallback.error);
+ }
+ parsed.options.backend = fallback.options.backend;
+ }
+ }
const std::string path = audiocpp_backend::resolve_model_path(
request->modelpath(), request->modelfile(), request->model());
diff --git a/backend/cpp/audio-cpp/model_options.cpp b/backend/cpp/audio-cpp/model_options.cpp
index 7c5f8d8fd..859ca0ff9 100644
--- a/backend/cpp/audio-cpp/model_options.cpp
+++ b/backend/cpp/audio-cpp/model_options.cpp
@@ -120,6 +120,7 @@ ParsedOptions parse_model_options(const std::vector &entries) {
return parsed;
}
parsed.options.backend = value;
+ parsed.options.backend_set = true;
} else if (key == "model_spec_override") {
parsed.options.model_spec_override = value;
} else if (key == "device") {
diff --git a/backend/cpp/audio-cpp/model_options.h b/backend/cpp/audio-cpp/model_options.h
index 0a5079e97..566ec2901 100644
--- a/backend/cpp/audio-cpp/model_options.h
+++ b/backend/cpp/audio-cpp/model_options.h
@@ -19,6 +19,10 @@ struct ModelOptions {
std::string task;
// ggml backend: cpu, cuda, hip (or rocm), vulkan, metal, best.
std::string backend = "cpu";
+ // True once a `backend:` entry has been seen: "cpu" is both the default
+ // and a legitimate explicit choice, so the value alone cannot tell them
+ // apart, and a caller merging in its own fallback needs the difference.
+ bool backend_set = false;
int device = 0;
// True once a `device:` entry has been seen. 0 is both the default and a
// legitimate device index, so the value alone cannot tell an explicit
diff --git a/backend/cpp/audio-cpp/model_options_test.cpp b/backend/cpp/audio-cpp/model_options_test.cpp
index 57abc567f..5874cfe5f 100644
--- a/backend/cpp/audio-cpp/model_options_test.cpp
+++ b/backend/cpp/audio-cpp/model_options_test.cpp
@@ -42,6 +42,10 @@ static void test_defaults() {
check(r.options.family.empty(), "family defaults to empty");
check(r.options.task.empty(), "task defaults to empty");
check(r.options.backend == "cpu", "backend defaults to cpu");
+ // backend_set separates the "cpu" default from an explicit backend:cpu —
+ // grpc-server merges the AUDIOCPP_DEFAULT_BACKEND fallback only when the
+ // model's options chose nothing.
+ check(!r.options.backend_set, "backend_set defaults to false");
check(r.options.device == 0, "device defaults to 0");
check(r.options.threads == 0, "threads defaults to 0");
check(r.options.busy_timeout_ms == 0, "busy_timeout_ms defaults to 0");
@@ -68,6 +72,7 @@ static void test_scalar_options() {
check(r.options.family == "qwen3_tts", "family parsed");
check(r.options.task == "tts", "task parsed");
check(r.options.backend == "cuda", "backend parsed");
+ check(r.options.backend_set, "backend_set records the explicit option");
check(r.options.device == 1, "device parsed");
check(r.options.threads == 8, "threads parsed");
check(r.options.busy_timeout_ms == 30000, "busy_timeout_ms parsed");
diff --git a/backend/cpp/audio-cpp/run.sh b/backend/cpp/audio-cpp/run.sh
index 2ecd56761..5da5d980d 100755
--- a/backend/cpp/audio-cpp/run.sh
+++ b/backend/cpp/audio-cpp/run.sh
@@ -10,6 +10,11 @@ set -e
CURDIR=$(dirname "$(realpath "$0")")
+# The image is built for one accelerator; models whose options carry no
+# explicit backend: should use it rather than the wrapper's CPU default.
+# An explicit backend: option and a caller's own environment both win.
+export AUDIOCPP_DEFAULT_BACKEND="${AUDIOCPP_DEFAULT_BACKEND:-best}"
+
if [ "$(uname -s)" = "Darwin" ]; then
export DYLD_LIBRARY_PATH="$CURDIR/lib:$CURDIR:$DYLD_LIBRARY_PATH"
exec "$CURDIR/grpc-server" "$@"
diff --git a/docs/content/features/audio-cpp.md b/docs/content/features/audio-cpp.md
index 2adfa47d7..4bb569c9a 100644
--- a/docs/content/features/audio-cpp.md
+++ b/docs/content/features/audio-cpp.md
@@ -31,7 +31,11 @@ in the model YAML, or select it explicitly in the import form.
The bundled audio-cpp gallery entries set `backend:best` in `options` to select
an available compute backend, with CPU as the fallback. To force CPU execution,
replace that option with `backend:cpu`. Model configurations that omit this
-option still default to CPU.
+option still default to CPU — unless the `AUDIOCPP_DEFAULT_BACKEND`
+environment variable is set on the backend process, which supplies the
+fallback for exactly those models (an explicit `backend:` option always
+wins). Set it to `best` in a deployment to give hand-written model
+configurations the same accelerator selection the gallery entries get.
### Sortformer installation