server: (router) lazy-load startup_models after main setup (#27424)
* server: (router) lazy-load startup_models after main setup * only allow is_first_load to populate it * nits * nits 2
This commit is contained in:
@@ -1757,7 +1757,7 @@ The precedence rule for preset options is as follows:
|
|||||||
3. **Global options** defined in the preset file (`[*]`)
|
3. **Global options** defined in the preset file (`[*]`)
|
||||||
|
|
||||||
We also offer additional options that are exclusive to presets (these aren't treated as command-line arguments):
|
We also offer additional options that are exclusive to presets (these aren't treated as command-line arguments):
|
||||||
- `load-on-startup` (boolean): Controls whether the model loads automatically when the server starts
|
- `load-on-startup` (boolean): Controls whether the model loads automatically when the server starts. Only applies at startup: if the model list is reloaded later (for example after editing the preset file), a newly added model is listed but not loaded
|
||||||
- `stop-timeout` (int, seconds): After requested unload, wait for this many seconds before forcing termination (default: 10)
|
- `stop-timeout` (int, seconds): After requested unload, wait for this many seconds before forcing termination (default: 10)
|
||||||
- `dedup-cache-models` (boolean): When the preset uses `hf-repo` pointing to a model that is already downloaded, hide the corresponding cached model entry from `GET /models` (the preset entry remains visible). Set it in the `[*]` section to apply to all presets.
|
- `dedup-cache-models` (boolean): When the preset uses `hf-repo` pointing to a model that is already downloaded, hide the corresponding cached model entry from `GET /models` (the preset entry remains visible). Set it in the `[*]` section to apply to all presets.
|
||||||
|
|
||||||
|
|||||||
@@ -672,24 +672,26 @@ void server_models::load_models() {
|
|||||||
apply_hidden();
|
apply_hidden();
|
||||||
log_available_models();
|
log_available_models();
|
||||||
|
|
||||||
std::vector<std::string> models_to_load;
|
// skipped on reload, see startup_models
|
||||||
for (const auto & [name, inst] : mapping) {
|
if (startup_models.has_value()) {
|
||||||
std::string val;
|
std::vector<std::string> models_to_load;
|
||||||
if (inst.meta.preset.get_option(COMMON_ARG_PRESET_LOAD_ON_STARTUP, val) && common_arg_utils::is_truthy(val)) {
|
for (const auto & [name, inst] : mapping) {
|
||||||
models_to_load.push_back(name);
|
std::string val;
|
||||||
|
if (inst.meta.preset.get_option(COMMON_ARG_PRESET_LOAD_ON_STARTUP, val) && common_arg_utils::is_truthy(val)) {
|
||||||
|
models_to_load.push_back(name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
if ((int)models_to_load.size() > base_params.models_max) {
|
||||||
if ((int)models_to_load.size() > base_params.models_max) {
|
throw std::runtime_error(string_format(
|
||||||
throw std::runtime_error(string_format(
|
"number of models to load on startup (%zu) exceeds models_max (%d)",
|
||||||
"number of models to load on startup (%zu) exceeds models_max (%d)",
|
models_to_load.size(), base_params.models_max));
|
||||||
models_to_load.size(), base_params.models_max));
|
}
|
||||||
|
|
||||||
|
// to be lazy-loaded after main() setup phase is completed
|
||||||
|
startup_models = std::move(models_to_load);
|
||||||
}
|
}
|
||||||
|
|
||||||
lk.unlock();
|
lk.unlock();
|
||||||
for (const auto & name : models_to_load) {
|
|
||||||
SRV_INF("(startup) loading model %s\n", name.c_str());
|
|
||||||
load(name);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// RELOAD: diff the new preset list against the current mapping and reconcile
|
// RELOAD: diff the new preset list against the current mapping and reconcile
|
||||||
is_reloading = true;
|
is_reloading = true;
|
||||||
@@ -819,8 +821,8 @@ void server_models::load_models() {
|
|||||||
inst.meta.update_caps();
|
inst.meta.update_caps();
|
||||||
}
|
}
|
||||||
|
|
||||||
// add models that are new in this reload
|
// add models that are new in this reload, load-on-startup is not honored here since a
|
||||||
std::vector<std::string> newly_added;
|
// reload never spawns an instance
|
||||||
for (const auto & [name, preset] : final_presets) {
|
for (const auto & [name, preset] : final_presets) {
|
||||||
if (mapping.find(name) == mapping.end()) {
|
if (mapping.find(name) == mapping.end()) {
|
||||||
server_model_meta meta{
|
server_model_meta meta{
|
||||||
@@ -841,42 +843,40 @@ void server_models::load_models() {
|
|||||||
// /* need_download */ false,
|
// /* need_download */ false,
|
||||||
};
|
};
|
||||||
add_model(std::move(meta));
|
add_model(std::move(meta));
|
||||||
newly_added.push_back(name);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apply_stop_timeout();
|
apply_stop_timeout();
|
||||||
apply_hidden();
|
apply_hidden();
|
||||||
|
|
||||||
// clear reload flag before unlocking for autoload - load() blocks on !is_reloading,
|
// clear reload flag under the lock, this releases the load() calls waiting on !is_reloading
|
||||||
// so clearing it here (while still locked) prevents a deadlock in the autoload calls below
|
|
||||||
is_reloading = false;
|
is_reloading = false;
|
||||||
cv.notify_all();
|
cv.notify_all();
|
||||||
|
|
||||||
log_available_models();
|
log_available_models();
|
||||||
|
|
||||||
// collect autoload candidates while still under the lock
|
|
||||||
std::vector<std::string> to_autoload;
|
|
||||||
for (const auto & name : newly_added) {
|
|
||||||
auto it = mapping.find(name);
|
|
||||||
if (it != mapping.end()) {
|
|
||||||
std::string val;
|
|
||||||
if (it->second.meta.preset.get_option(COMMON_ARG_PRESET_LOAD_ON_STARTUP, val) && common_arg_utils::is_truthy(val)) {
|
|
||||||
to_autoload.push_back(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lk.unlock();
|
lk.unlock();
|
||||||
for (const auto & name : to_autoload) {
|
|
||||||
SRV_INF("(reload) loading new model %s\n", name.c_str());
|
|
||||||
load(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
notify_sse("models_reload", "*");
|
notify_sse("models_reload", "*");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void server_models::load_startup_models() {
|
||||||
|
std::vector<std::string> to_load;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lk(mutex);
|
||||||
|
if (!startup_models.has_value()) {
|
||||||
|
return; // already drained
|
||||||
|
}
|
||||||
|
to_load = std::move(*startup_models);
|
||||||
|
startup_models.reset();
|
||||||
|
}
|
||||||
|
for (const auto & name : to_load) {
|
||||||
|
SRV_INF("(startup) loading model %s\n", name.c_str());
|
||||||
|
load(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void server_models::update_meta(const std::string & name, const server_model_meta & meta) {
|
void server_models::update_meta(const std::string & name, const server_model_meta & meta) {
|
||||||
std::lock_guard<std::mutex> lk(mutex);
|
std::lock_guard<std::mutex> lk(mutex);
|
||||||
auto it = mapping.find(name);
|
auto it = mapping.find(name);
|
||||||
|
|||||||
@@ -136,6 +136,10 @@ private:
|
|||||||
// if true, the next get_meta() will trigger a reload of model list
|
// if true, the next get_meta() will trigger a reload of model list
|
||||||
bool need_reload = false;
|
bool need_reload = false;
|
||||||
|
|
||||||
|
// models marked with load-on-startup, unset once load_startup_models() drains it
|
||||||
|
// no value means the startup phase is over, so a reload must not queue anything
|
||||||
|
std::optional<std::vector<std::string>> startup_models{std::in_place};
|
||||||
|
|
||||||
// conv_id -> model name that currently serves its stream session, lets the resumable stream
|
// conv_id -> model name that currently serves its stream session, lets the resumable stream
|
||||||
// routes go straight to the owning child instead of polling every one. populated when
|
// routes go straight to the owning child instead of polling every one. populated when
|
||||||
// proxy_request forwards a POST carrying an X-Conversation-Id. best effort: a stale entry just
|
// proxy_request forwards a POST carrying an X-Conversation-Id. best effort: a stale entry just
|
||||||
@@ -231,6 +235,9 @@ public:
|
|||||||
// - if a model is not running, it will be added or updated according to the source
|
// - if a model is not running, it will be added or updated according to the source
|
||||||
void load_models();
|
void load_models();
|
||||||
|
|
||||||
|
// lazy-load startup_models, to be called after main() setup phase
|
||||||
|
void load_startup_models();
|
||||||
|
|
||||||
// check if a model instance exists (thread-safe)
|
// check if a model instance exists (thread-safe)
|
||||||
bool has_model(const std::string & name);
|
bool has_model(const std::string & name);
|
||||||
|
|
||||||
|
|||||||
@@ -424,6 +424,18 @@ int llama_server(common_params & params, int argc, char ** argv) {
|
|||||||
ctx_http.stop();
|
ctx_http.stop();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
models_routes->models.load_startup_models();
|
||||||
|
} catch (const std::exception & e) {
|
||||||
|
SRV_ERR("failed to load models on startup: %s\n", e.what());
|
||||||
|
ctx_http.stop();
|
||||||
|
if (ctx_http.thread.joinable()) {
|
||||||
|
ctx_http.thread.join();
|
||||||
|
}
|
||||||
|
clean_up();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// setup clean up function, to be called before exit
|
// setup clean up function, to be called before exit
|
||||||
clean_up = [&ctx_http, &ctx_server, &mcp_mgr]() {
|
clean_up = [&ctx_http, &ctx_server, &mcp_mgr]() {
|
||||||
|
|||||||
Reference in New Issue
Block a user