From e9f4eac0303a68bb6c043d3c6f0dcfe24acf9182 Mon Sep 17 00:00:00 2001 From: Warren B Date: Wed, 2 Sep 2026 22:57:27 +0100 Subject: [PATCH] server: validate the backend before binding the port MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The server backend defaults to CUDA and nothing verified it at startup, so on a machine where that backend is not registered the process bound its port and served the whole UI — health, model list, downloads — with an engine that could never run anything. /health answered {"status":"ok","backend":"cuda"} on an Apple-silicon host with no CUDA device, and the operator only learned the truth when the first model load or generate failed. main() now initialises the requested backend and device once, before constructing the server, and frees it again. A missing backend or device therefore aborts startup with the same message the engine would have raised later, at the point the operator can still change --backend or --device. The probe costs one backend init on a path that already loads Metal or CUDA libraries and does not touch the request path. --- app/server/main.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/app/server/main.cpp b/app/server/main.cpp index 1fec357ef..abffa9c4e 100644 --- a/app/server/main.cpp +++ b/app/server/main.cpp @@ -229,6 +229,21 @@ int main(int argc, char ** argv) { throw std::runtime_error("--min-free-memory-mb must be >= 0 (0 disables the memory guard)"); } + // Prove the requested backend and device exist before binding a port. + // The backend defaults to CUDA, and nothing else checks it until the + // first generate, so a machine without CUDA would otherwise serve the + // whole UI — health, model list, downloads — and only fail once the + // user pressed Run. Failing here reports the same message the engine + // would have raised, at the point the operator can still act on it. + { + engine::core::BackendConfig probe; + probe.type = config.backend; + probe.device = config.device; + probe.threads = config.threads; + ggml_backend_t backend = engine::core::init_backend(probe); + ggml_backend_free(backend); + } + const auto ui_resource_anchor = executable_directory(argc > 0 ? argv[0] : nullptr); minitts::server::ServerState state( config,