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