Merge branch 'upstream' into concedo_experimental

# Conflicts:
#	.devops/nix/package.nix
#	.github/workflows/server.yml
#	docs/development/HOWTO-add-model.md
#	ggml/src/ggml-hexagon/ggml-hexagon.cpp
#	ggml/src/ggml-hexagon/htp/CMakeLists.txt
#	ggml/src/ggml-hexagon/htp/argsort-ops.c
#	ggml/src/ggml-hexagon/htp/concat-ops.c
#	ggml/src/ggml-hexagon/htp/flash-attn-ops.c
#	ggml/src/ggml-hexagon/htp/gated-delta-net-ops.c
#	ggml/src/ggml-hexagon/htp/hmx-flash-attn-ops.c
#	ggml/src/ggml-hexagon/htp/hmx-matmul-ops.c
#	ggml/src/ggml-hexagon/htp/hmx-ops.h
#	ggml/src/ggml-hexagon/htp/htp-ctx.h
#	ggml/src/ggml-hexagon/htp/hvx-utils.h
#	ggml/src/ggml-hexagon/htp/main.c
#	ggml/src/ggml-hexagon/htp/matmul-ops.c
#	ggml/src/ggml-hexagon/htp/pad-ops.c
#	ggml/src/ggml-hexagon/htp/unary-ops.c
#	ggml/src/ggml-opencl/CMakeLists.txt
#	ggml/src/ggml-opencl/ggml-opencl.cpp
#	ggml/src/ggml-opencl/kernels/cvt.cl
#	ggml/src/ggml-webgpu/wgsl-shaders/cpy.wgsl
#	scripts/sync_vendor.py
#	src/llama-context.cpp
#	tests/test-backend-sampler.cpp
#	tools/server/README.md
#	tools/ui/tests/stories/ChatScreenForm.a11y.stories.svelte
This commit is contained in:
Concedo
2026-06-02 19:28:39 +08:00
78 changed files with 1801 additions and 477 deletions
+1
View File
@@ -1132,6 +1132,7 @@ json oaicompat_chat_params_parse(
llama_params["reasoning_budget_start_tag"] = chat_params.thinking_start_tag;
llama_params["reasoning_budget_end_tag"] = chat_params.thinking_end_tag;
llama_params["reasoning_budget_message"] = opt.reasoning_budget_message;
llama_params["reasoning_control"] = json_value(body, "reasoning_control", false);
}
}
+105 -4
View File
@@ -37,6 +37,21 @@ using json = nlohmann::ordered_json;
constexpr int HTTP_POLLING_SECONDS = 1;
static uint32_t server_n_outputs_max(const common_params & params) {
const uint32_t n_batch = params.n_batch;
if (params.embedding ||
(params.pooling_type != LLAMA_POOLING_TYPE_UNSPECIFIED && params.pooling_type != LLAMA_POOLING_TYPE_NONE)) {
return n_batch;
}
const uint32_t n_outputs_per_seq = 1 + common_speculative_n_max(&params.speculative);
const uint64_t n_outputs = (uint64_t) params.n_parallel * n_outputs_per_seq;
return std::max<uint32_t>(1, std::min<uint64_t>(n_batch, n_outputs));
}
// state diagram: https://github.com/ggml-org/llama.cpp/pull/9283
enum slot_state {
SLOT_STATE_IDLE,
@@ -753,6 +768,7 @@ private:
SRV_INF("loading model '%s'\n", params.model.path.c_str());
params_base = params;
params_base.n_outputs_max = server_n_outputs_max(params_base);
std::string & mmproj_path = params_base.mmproj.path;
bool has_mmproj = !mmproj_path.empty();
@@ -818,6 +834,8 @@ private:
measure_model_bytes = false;
}
params_dft.n_outputs_max = params_base.n_parallel;
auto mparams_dft = common_model_params_to_llama(params_dft);
auto cparams_dft = common_context_params_to_llama(params_dft);
if (spec_mtp) {
@@ -941,10 +959,11 @@ private:
params_base.model.path.c_str());
auto cparams_mtp = common_context_params_to_llama(params_base);
cparams_mtp.ctx_type = LLAMA_CONTEXT_TYPE_MTP;
cparams_mtp.type_k = params_base.speculative.draft.cache_type_k;
cparams_mtp.type_v = params_base.speculative.draft.cache_type_v;
cparams_mtp.n_rs_seq = 0;
cparams_mtp.ctx_type = LLAMA_CONTEXT_TYPE_MTP;
cparams_mtp.type_k = params_base.speculative.draft.cache_type_k;
cparams_mtp.type_v = params_base.speculative.draft.cache_type_v;
cparams_mtp.n_rs_seq = 0;
cparams_mtp.n_outputs_max = params_base.n_parallel;
ctx_dft.reset(llama_init_from_model(model_tgt, cparams_mtp));
if (ctx_dft == nullptr) {
@@ -1244,6 +1263,20 @@ private:
return nullptr;
}
server_slot * get_slot_by_cmpl_id(const std::string & cmpl_id) {
if (cmpl_id.empty()) {
return nullptr;
}
for (server_slot & slot : slots) {
if (slot.is_processing() && slot.task && slot.task->params.oaicompat_cmpl_id == cmpl_id) {
return &slot;
}
}
return nullptr;
}
server_slot * get_available_slot(const server_task & task) {
server_slot * ret = nullptr;
@@ -2095,6 +2128,37 @@ private:
}
}
} break;
case SERVER_TASK_TYPE_CONTROL:
{
auto res = std::make_unique<server_task_result_control>();
res->id = task.id;
server_slot * slot = get_slot_by_cmpl_id(task.params.control_cmpl_id);
if (slot == nullptr) {
res->success = false;
res->message = "no active completion for this id";
queue_results.send(std::move(res));
break;
}
if (task.params.control_action == "reasoning_end") {
// the budget sampler only exists when reasoning control was armed
if (!slot->task->params.sampling.reasoning_control) {
res->success = false;
res->message = "reasoning control not enabled for this completion";
queue_results.send(std::move(res));
break;
}
// act on the live slot mid generation, never defer
common_sampler_reasoning_budget_force(slot->smpl.get());
res->success = true;
} else {
res->success = false;
res->message = "unknown control action";
}
queue_results.send(std::move(res));
} break;
case SERVER_TASK_TYPE_NEXT_RESPONSE:
{
// do nothing
@@ -4247,6 +4311,43 @@ void server_routes::init_routes() {
TASK_RESPONSE_TYPE_OAI_CHAT);
};
this->post_control = [this](const server_http_req & req) {
auto res = create_response();
const json body = json::parse(req.body);
const std::string cmpl_id = json_value(body, "id", std::string());
const std::string action = json_value(body, "action", std::string());
if (cmpl_id.empty()) {
res->error(format_error_response("missing completion id", ERROR_TYPE_INVALID_REQUEST));
return res;
}
if (action != "reasoning_end") {
res->error(format_error_response("unknown control action", ERROR_TYPE_INVALID_REQUEST));
return res;
}
auto & rd = res->rd;
{
server_task task(SERVER_TASK_TYPE_CONTROL);
task.id = rd.get_new_id();
task.params.control_cmpl_id = cmpl_id;
task.params.control_action = action;
rd.post_task(std::move(task));
}
auto result = rd.next(req.should_stop);
if (!result) {
GGML_ASSERT(req.should_stop());
return res;
}
if (result->is_error()) {
res->error(result->to_json());
return res;
}
res->ok(result->to_json());
return res;
};
this->post_responses_oai = [this](const server_http_req & req) {
auto res = create_response();
std::vector<raw_buffer> files;
+1
View File
@@ -110,6 +110,7 @@ struct server_routes {
server_http_context::handler_t post_completions;
server_http_context::handler_t post_completions_oai;
server_http_context::handler_t post_chat_completions;
server_http_context::handler_t post_control;
server_http_context::handler_t post_responses_oai;
server_http_context::handler_t post_transcriptions_oai;
server_http_context::handler_t post_anthropic_messages;
+1
View File
@@ -499,6 +499,7 @@ task_params server_task::params_from_json_cmpl(
const auto end_tag = json_value(data, "reasoning_budget_end_tag", std::string());
const auto message = json_value(data, "reasoning_budget_message", std::string());
params.sampling.reasoning_budget_tokens = budget;
params.sampling.reasoning_control = json_value(data, "reasoning_control", false);
if (!start_tag.empty()) {
params.sampling.reasoning_budget_start = common_tokenize(vocab, start_tag, false, true);
+18
View File
@@ -19,6 +19,7 @@ enum server_task_type {
SERVER_TASK_TYPE_RERANK,
SERVER_TASK_TYPE_INFILL,
SERVER_TASK_TYPE_CANCEL,
SERVER_TASK_TYPE_CONTROL,
SERVER_TASK_TYPE_NEXT_RESPONSE,
SERVER_TASK_TYPE_METRICS,
SERVER_TASK_TYPE_SLOT_SAVE,
@@ -84,6 +85,10 @@ struct task_params {
std::string oaicompat_model;
std::string oaicompat_cmpl_id;
// realtime control (SERVER_TASK_TYPE_CONTROL)
std::string control_action;
std::string control_cmpl_id;
// per-request parameters for chat parsing
common_chat_parser_params chat_parser_params;
@@ -551,6 +556,19 @@ struct server_task_result_slot_erase : server_task_result {
virtual json to_json() override;
};
struct server_task_result_control : server_task_result {
bool success = false;
std::string message; // optional detail when success is false
virtual json to_json() override {
json out = json { { "success", success } };
if (!message.empty()) {
out["message"] = message;
}
return out;
}
};
struct server_task_result_get_lora : server_task_result {
struct lora {
common_adapter_lora_info info;
+2
View File
@@ -149,6 +149,7 @@ int llama_server(int argc, char ** argv) {
routes.post_completions = models_routes->proxy_post;
routes.post_completions_oai = models_routes->proxy_post;
routes.post_chat_completions = models_routes->proxy_post;
routes.post_control = models_routes->proxy_post;
routes.post_responses_oai = models_routes->proxy_post;
routes.post_transcriptions_oai = models_routes->proxy_post;
routes.post_anthropic_messages = models_routes->proxy_post;
@@ -185,6 +186,7 @@ int llama_server(int argc, char ** argv) {
ctx_http.post("/v1/completions", ex_wrapper(routes.post_completions_oai));
ctx_http.post("/chat/completions", ex_wrapper(routes.post_chat_completions));
ctx_http.post("/v1/chat/completions", ex_wrapper(routes.post_chat_completions));
ctx_http.post("/v1/chat/completions/control", ex_wrapper(routes.post_control));
ctx_http.post("/v1/responses", ex_wrapper(routes.post_responses_oai));
ctx_http.post("/responses", ex_wrapper(routes.post_responses_oai));
ctx_http.post("/v1/audio/transcriptions", ex_wrapper(routes.post_transcriptions_oai));