From 90703c69e5814507cf57221d8dfa920a89f2ce49 Mon Sep 17 00:00:00 2001 From: bong-water-water-bong Date: Fri, 25 Sep 2026 07:22:17 -0300 Subject: [PATCH] 1bit serve: forward llama-server's other routes (responses, tokenize, slots, props) Lemonade's llamacpp backend, which the onebit recipe inherits, forwards /v1/responses, /tokenize and /slots to the backend; 1bit serve answered 404. On vulkan, hrx and rocm they now go to the llama-server behind the model; npu, zinc and mlx answer 501. Lemonade's LLM suite through onebit on Strix Halo: Vulkan 19 -> 23/31, HRX 19 -> 23/31. Co-Authored-By: Claude Opus 5.5 --- app/serve.cpp | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++ docs/serve.md | 7 +++++++ 2 files changed, 60 insertions(+) diff --git a/app/serve.cpp b/app/serve.cpp index 8fd914d2c..6710ab39c 100644 --- a/app/serve.cpp +++ b/app/serve.cpp @@ -340,6 +340,29 @@ void forward(int port, std::shared_ptr held, int draft_max, const std: }); } +// llama-server's own routes that are not chat or completions (/slots, /props, /metrics): +// method, query string and body pass through unchanged; the reply comes back as it is. +void relay(int port, const httplib::Request& req, httplib::Response& res) { + std::string path = req.path; + if (!req.params.empty()) { + std::string query; + for (const auto& [k, v] : req.params) + query += (query.empty() ? "?" : "&") + httplib::encode_query_component(k) + "=" + httplib::encode_query_component(v); + path += query; + } + httplib::Client c("127.0.0.1", port); + c.set_read_timeout(3600); + const std::string type = req.get_header_value("Content-Type").empty() ? "application/json" : req.get_header_value("Content-Type"); + auto r = req.method == "GET" ? c.Get(path) : c.Post(path, req.body, type); + if (!r) { + res.status = 502; + res.set_content(R"({"error":{"message":"backend did not answer"}})", "application/json"); + return; + } + res.status = r->status; + res.set_content(r->body, r->get_header_value("Content-Type").empty() ? "application/json" : r->get_header_value("Content-Type")); +} + struct Launch { std::string device; int port = 0; @@ -503,6 +526,36 @@ int serve_child(const Options& o) { }; srv.Post("/v1/chat/completions", post); srv.Post("/v1/completions", post); + // The rest of llama-server's API, for the devices that run llama-server (Lemonade's + // llamacpp backend, which the onebit recipe inherits, forwards these to us). They go to + // the first backend; the NPU, ZINC and MLX answer 501. + const bool llama = device == "vulkan" || device == "hrx" || device == "rocm" || device == "vulkan+rocm"; + auto unsupported = [&](const httplib::Request& q, httplib::Response& r) { + r.status = 501; + r.set_content(json{{"error", {{"message", q.path + " is not available on --device " + device}, + {"type", "not_implemented"}}}}.dump(), "application/json"); + }; + auto first = [&](const httplib::Request& q, httplib::Response& r) { + if (!ready) { + r.status = 503; + r.set_content(R"({"error":{"message":"model is loading"}})", "application/json"); + return; + } + relay(backends[0].port, q, r); + }; + auto first_json = [&](const httplib::Request& q, httplib::Response& r) { + if (!ready) { + r.status = 503; + r.set_content(R"({"error":{"message":"model is loading"}})", "application/json"); + return; + } + forward(backends[0].port, nullptr, -1, id, drop_model, set_model, q, r); + }; + for (const char* path : {"/v1/responses", "/tokenize", "/detokenize", "/apply-template"}) + srv.Post(path, llama ? httplib::Server::Handler(first_json) : httplib::Server::Handler(unsupported)); + for (const char* path : {"/slots", "/props", "/metrics"}) + srv.Get(path, llama ? httplib::Server::Handler(first) : httplib::Server::Handler(unsupported)); + srv.Post(R"(/slots/(\d+))", llama ? httplib::Server::Handler(first) : httplib::Server::Handler(unsupported)); // RAG: embeddings and rerank go to their own backends, the reply names the model served auto rag_route = [&](size_t i, const std::string& model) { return [&, i, model](const httplib::Request& q, httplib::Response& r) { diff --git a/docs/serve.md b/docs/serve.md index 7691980c1..fc448f91a 100644 --- a/docs/serve.md +++ b/docs/serve.md @@ -42,6 +42,13 @@ One model per process: | `POST /v1/completions` | | | `POST /v1/embeddings` | with `--embed` (RAG) | | `POST /v1/rerank` | with `--rerank` (RAG) | +| `POST /v1/responses` | OpenAI Responses API, streamed or not (llama-server devices) | +| `POST /tokenize`, `/detokenize`, `/apply-template` | llama-server devices | +| `GET /slots`, `POST /slots/?action=…`, `GET /props`, `GET /metrics` | llama-server devices; slot save and restore answer 501 (llama-server runs without `--slot-save-path`) | + +"llama-server devices" means `vulkan`, `hrx` and `rocm`. These routes go to the llama-server +behind the model, which is how Lemonade's llamacpp backend reaches them (the `onebit` recipe +inherits it). On `npu`, `zinc` and `mlx` they answer 501. ## Where the model runs