server: real-time model load progress tracking via /models/sse (#24828)

* server: real-time model load progress tracking via /models/sse

* update docs

* add mutex for notify_to_router

* correct docs
This commit is contained in:
Xuan-Son Nguyen
2026-06-21 11:58:14 +02:00
committed by GitHub
parent 8a118ee86c
commit d6d899580d
4 changed files with 106 additions and 7 deletions
+56 -1
View File
@@ -833,6 +833,8 @@ private:
bool sleeping = false;
int64_t t_last_load_progress_ms = 0;
void destroy() {
spec.reset();
ctx_dft.reset();
@@ -863,6 +865,30 @@ private:
sleeping = new_state;
}
static bool load_progress_callback(float progress, void * user_data) {
auto * ctx = static_cast<server_context_impl *>(user_data);
GGML_ASSERT(ctx);
// always emit the first and final sample; throttle the rest to one per 200ms
{
auto & t_last = ctx->t_last_load_progress_ms;
const int64_t t_now = ggml_time_ms();
const bool first = t_last == 0;
const bool done = progress >= 1.0f;
const bool throttled = !first && !done && (t_now - t_last) < 200;
if (throttled) {
return true;
}
t_last = t_now;
}
if (ctx->callback_state) {
ctx->callback_state(SERVER_STATE_LOADING, {
{"stage", "text_model"},
{"value", progress},
});
}
return true;
}
// load the model and initialize llama_context
// this may also be called to resume from sleeping state
bool load_model(common_params & params) {
@@ -916,6 +942,10 @@ private:
// optionally reserve VRAM for the draft / MTP context before fitting the target model
if (params_base.fit_params) {
if (callback_state) {
callback_state(SERVER_STATE_LOADING, {{"stage", "fit_params"}});
}
const bool spec_mtp = std::find(params_base.speculative.types.begin(),
params_base.speculative.types.end(),
COMMON_SPECULATIVE_TYPE_DRAFT_MTP) != params_base.speculative.types.end();
@@ -991,6 +1021,13 @@ private:
}
}
// attach a progress callback
{
t_last_load_progress_ms = 0;
params_base.load_progress_callback = load_progress_callback;
params_base.load_progress_callback_user_data = this;
}
llama_init = common_init_from_params(params_base);
model_tgt = llama_init->model();
@@ -1008,6 +1045,10 @@ private:
add_bos_token = llama_vocab_get_add_bos(vocab);
if (params_base.speculative.has_dft()) {
if (callback_state) {
callback_state(SERVER_STATE_LOADING, {{"stage", "spec_model"}});
}
// TODO speculative: move to common/speculative.cpp?
const auto & params_spec = params_base.speculative.draft;
@@ -1079,6 +1120,10 @@ private:
}
if (has_mmproj) {
if (callback_state) {
callback_state(SERVER_STATE_LOADING, {{"stage", "mmproj_model"}});
}
if (!is_resume) {
mtmd_helper_log_set(common_log_default_callback, nullptr);
}
@@ -1259,6 +1304,10 @@ private:
return init();
}
if (callback_state) {
callback_state(SERVER_STATE_READY, {});
}
return true;
}
@@ -1335,6 +1384,9 @@ private:
const bool enable_thinking = params_base.enable_reasoning != 0 && template_supports_thinking;
SRV_INF("%s: chat template, thinking = %d\n", __func__, enable_thinking);
// IMPORTANT: chat_params is reused across sleeping / resuming states,
// never store llama_context/llama_model pointers in chat_params,
// as they may be invalidated after sleeping
chat_params = {
/* use_jinja */ params_base.use_jinja,
/* prefill_assistant */ params_base.prefill_assistant,
@@ -3734,7 +3786,10 @@ struct server_res_generator : server_http_res {
void server_context::set_state_callback(server_state_callback_t callback) {
impl->callback_state = std::move(callback);
impl->queue_tasks.on_sleeping_state([this](bool sleeping) {
impl->callback_state(sleeping ? SERVER_STATE_SLEEPING : SERVER_STATE_READY, {});
if (sleeping) {
impl->callback_state(SERVER_STATE_SLEEPING, {});
}
// for sleeping == false, event is emitted by load_model()
});
}