server: (router) do not evict busy models (#26567)

This commit is contained in:
Xuan-Son Nguyen
2026-08-07 14:39:59 +02:00
committed by GitHub
parent 217df17ac3
commit e1470ee6a2
2 changed files with 32 additions and 10 deletions
+23 -8
View File
@@ -721,7 +721,9 @@ void server_models::unload_lru() {
for (const auto & m : mapping) { for (const auto & m : mapping) {
if (m.second.meta.is_running()) { if (m.second.meta.is_running()) {
count_active++; count_active++;
if (m.second.meta.last_used < lru_last_used) { // do not evict busy one
bool is_model_idle = m.second.req_count == 0 && m.second.meta.is_ready_or_sleep();
if (is_model_idle && m.second.meta.last_used < lru_last_used) {
lru_model_name = m.first; lru_model_name = m.first;
lru_last_used = m.second.meta.last_used; lru_last_used = m.second.meta.last_used;
} }
@@ -739,6 +741,7 @@ void server_models::unload_lru() {
}); });
} }
} }
// TODO @ngxson : if no idle model is found, queue the load request
} }
void server_models::load(const std::string & name) { void server_models::load(const std::string & name) {
@@ -1180,9 +1183,12 @@ server_http_res_ptr server_models::proxy_request(const server_http_req & req, co
if (!meta->is_running()) { if (!meta->is_running()) {
throw std::invalid_argument("model name=" + name + " is not running"); throw std::invalid_argument("model name=" + name + " is not running");
} }
if (update_last_used) { {
std::unique_lock<std::mutex> lk(mutex); std::unique_lock<std::mutex> lk(mutex);
mapping[name].meta.last_used = ggml_time_ms(); if (update_last_used) {
mapping[name].meta.last_used = ggml_time_ms();
}
mapping[name].req_count++;
} }
SRV_INF("proxying request to model %s on port %d\n", name.c_str(), meta->port); SRV_INF("proxying request to model %s on port %d\n", name.c_str(), meta->port);
std::string proxy_path = req.path; std::string proxy_path = req.path;
@@ -1198,13 +1204,22 @@ server_http_res_ptr server_models::proxy_request(const server_http_req & req, co
req.headers, req.headers,
req.body, req.body,
req.files, req.files,
// a detached request belongs to a replay session that outlives the client socket: // a detached request belongs to a replay session
// it reaches the child even when the downstream died during the load wait, the detached
// session buffer is the recipient and DELETE remains the stop ? std::function<bool()>([]() { return false; })
detached ? std::function<bool()>([]() { return false; }) : req.should_stop, : req.should_stop,
base_params.timeout_read, base_params.timeout_read,
base_params.timeout_write base_params.timeout_write
); );
proxy->cleanup = [this, name]() {
std::unique_lock<std::mutex> lk(mutex);
auto it = mapping.find(name);
if (it != mapping.end() && it->second.req_count > 0) {
it->second.req_count--;
}
};
return proxy; return proxy;
} }
@@ -2064,7 +2079,7 @@ server_http_proxy::server_http_proxy(
cli->set_write_timeout(timeout_read, 0); // reversed for cli (client) vs srv (server) cli->set_write_timeout(timeout_read, 0); // reversed for cli (client) vs srv (server)
cli->set_read_timeout(timeout_write, 0); cli->set_read_timeout(timeout_write, 0);
this->status = 500; // to be overwritten upon response this->status = 500; // to be overwritten upon response
this->cleanup = [pipe]() { this->cleanup_pipes = [pipe]() {
pipe->close_read(); pipe->close_read();
pipe->close_write(); pipe->close_write();
}; };
+9 -2
View File
@@ -84,7 +84,6 @@ struct server_model_meta {
int exit_code = 0; // exit code of the model instance process (only valid if status == FAILED) int exit_code = 0; // exit code of the model instance process (only valid if status == FAILED)
int stop_timeout = 0; // seconds to wait before force-killing the model instance during shutdown int stop_timeout = 0; // seconds to wait before force-killing the model instance during shutdown
mtmd_caps multimodal; // multimodal capabilities mtmd_caps multimodal; // multimodal capabilities
// bool need_download = false; // whether the model needs to be downloaded before loading // TODO @ngxson: implement this
bool is_ready() const { bool is_ready() const {
return status == SERVER_MODEL_STATUS_LOADED; return status == SERVER_MODEL_STATUS_LOADED;
@@ -94,6 +93,10 @@ struct server_model_meta {
return status == SERVER_MODEL_STATUS_LOADED || status == SERVER_MODEL_STATUS_LOADING || status == SERVER_MODEL_STATUS_SLEEPING; return status == SERVER_MODEL_STATUS_LOADED || status == SERVER_MODEL_STATUS_LOADING || status == SERVER_MODEL_STATUS_SLEEPING;
} }
bool is_ready_or_sleep() const {
return status == SERVER_MODEL_STATUS_LOADED || status == SERVER_MODEL_STATUS_SLEEPING;
}
bool is_failed() const { bool is_failed() const {
return status == SERVER_MODEL_STATUS_UNLOADED && exit_code != 0; return status == SERVER_MODEL_STATUS_UNLOADED && exit_code != 0;
} }
@@ -113,6 +116,7 @@ private:
std::shared_ptr<server_subproc> subproc; // shared between main thread and monitoring thread std::shared_ptr<server_subproc> subproc; // shared between main thread and monitoring thread
std::thread th; std::thread th;
server_model_meta meta; server_model_meta meta;
int req_count = 0; // number of active proxy requests
}; };
std::mutex mutex; std::mutex mutex;
@@ -343,7 +347,6 @@ struct server_models_routes {
*/ */
struct server_http_proxy : server_http_res { struct server_http_proxy : server_http_res {
std::function<void()> cleanup = nullptr; std::function<void()> cleanup = nullptr;
public:
server_http_proxy(const std::string & method, server_http_proxy(const std::string & method,
const std::string & scheme, const std::string & scheme,
const std::string & host, const std::string & host,
@@ -357,11 +360,15 @@ public:
int32_t timeout_write int32_t timeout_write
); );
~server_http_proxy() { ~server_http_proxy() {
if (cleanup_pipes) {
cleanup_pipes();
}
if (cleanup) { if (cleanup) {
cleanup(); cleanup();
} }
} }
private: private:
std::function<void()> cleanup_pipes = nullptr;
std::thread thread; std::thread thread;
struct msg_t { struct msg_t {
std::map<std::string, std::string> headers; std::map<std::string, std::string> headers;