From ad2be8a856af2c561d2795a582aec1fe29155832 Mon Sep 17 00:00:00 2001
From: "mudler's LocalAI [bot]"
<139863280+localai-bot@users.noreply.github.com>
Date: Sat, 1 Aug 2026 16:06:13 +0200
Subject: [PATCH] chore: :arrow_up: Update ggml-org/llama.cpp to
`876a4321163249c43ca4e986818fab5ab081f282` (#11177)
* :arrow_up: Update ggml-org/llama.cpp
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* fix(llama-cpp): drop merged MiniMax-M3 patch
The bumped llama.cpp revision includes the MiniMax-M3 parser and template detection, so the carried patch now rejects during backend preparation. Remove the obsolete patch while retaining the independent score-task patch.
Assisted-by: Codex:gpt-5 [systematic-debugging]
---------
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: mudler <2420543+mudler@users.noreply.github.com>
Co-authored-by: localai-org-maint-bot <306269227+localai-org-maint-bot@users.noreply.github.com>
---
backend/cpp/llama-cpp/Makefile | 2 +-
.../0001-add-minimax-m3-chat-parser.patch | 225 ------------------
2 files changed, 1 insertion(+), 226 deletions(-)
delete mode 100644 backend/cpp/llama-cpp/patches/0001-add-minimax-m3-chat-parser.patch
diff --git a/backend/cpp/llama-cpp/Makefile b/backend/cpp/llama-cpp/Makefile
index 1ab952209..f8b1f32c2 100644
--- a/backend/cpp/llama-cpp/Makefile
+++ b/backend/cpp/llama-cpp/Makefile
@@ -1,5 +1,5 @@
-LLAMA_VERSION?=1cbfd1988311775425d36c0ce066590f7d3049cf
+LLAMA_VERSION?=876a4321163249c43ca4e986818fab5ab081f282
LLAMA_REPO?=https://github.com/ggerganov/llama.cpp
CMAKE_ARGS?=
diff --git a/backend/cpp/llama-cpp/patches/0001-add-minimax-m3-chat-parser.patch b/backend/cpp/llama-cpp/patches/0001-add-minimax-m3-chat-parser.patch
deleted file mode 100644
index 2655102c7..000000000
--- a/backend/cpp/llama-cpp/patches/0001-add-minimax-m3-chat-parser.patch
+++ /dev/null
@@ -1,225 +0,0 @@
-# MiniMax-M3 chat-template parser, vendored from upstream llama.cpp PR #24523.
-#
-# Upstream has since merged the *model* half of #24523 (LLM_ARCH_MINIMAX_M3,
-# src/models/minimax-m3.cpp, the gguf-py constants and conversion/minimax.py), so
-# only the chat half is carried here: M3's namespace token "]<]minimax[>[" collides
-# with the autoparser's markup delimiters, so common/chat.cpp needs a dedicated
-# template detection + PEG parser that upstream does not have yet.
-#
-# Rebased against LLAMA_VERSION 0d47ea7427463093e69128bf2c2f9cd06b3ee5b3, which also
-# renamed common_chat_params::thinking_end_tag to thinking_end_tags (a vector).
-# LLAMA_VERSION is auto-bumped nightly; if a bump rejects this patch, re-vendor from
-# #24523 — or, once the chat half merges upstream, delete this file.
-# See https://github.com/mudler/LocalAI/issues/10820 and PR #10837.
-diff --git a/common/chat.cpp b/common/chat.cpp
-index 7a6e7238c..2dd015a2e 100644
---- a/common/chat.cpp
-+++ b/common/chat.cpp
-@@ -2121,6 +2121,191 @@ static common_chat_params common_chat_params_init_deepseek_v3_2(const common_cha
- return data;
- }
-
-+static common_chat_params common_chat_params_init_minimax_m3(const common_chat_template & tmpl,
-+ const autoparser::generation_params & inputs) {
-+ common_chat_params data;
-+
-+ data.prompt = common_chat_template_direct_apply_impl(tmpl, inputs);
-+ data.generation_prompt = common_chat_template_generation_prompt_impl(tmpl, inputs);
-+ data.format = COMMON_CHAT_FORMAT_PEG_NATIVE;
-+ data.supports_thinking = true;
-+ data.thinking_start_tag = "";
-+ data.thinking_end_tags = {""};
-+
-+ // M3 prefixes every tool tag with the namespace token "]<]minimax[>[";
-+ // params use the parameter name as the tag (...).
-+ const std::string NS = "]<]minimax[>[";
-+ const std::string THINK_START = "";
-+ const std::string THINK_END = "";
-+ const std::string FC_START = NS + "";
-+ const std::string FC_END = NS + "";
-+ const std::string INVOKE_END = NS + "";
-+
-+ data.preserved_tokens = {
-+ NS,
-+ "",
-+ "",
-+ THINK_START,
-+ THINK_END,
-+ };
-+
-+ auto has_tools = inputs.tools.is_array() && !inputs.tools.empty();
-+ auto has_response_format = !inputs.json_schema.is_null() && inputs.json_schema.is_object();
-+ auto extract_reasoning = inputs.reasoning_format != COMMON_REASONING_FORMAT_NONE;
-+ auto include_grammar = has_response_format || (has_tools && inputs.tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE);
-+
-+ const std::string GEN_PROMPT = data.generation_prompt;
-+
-+ if (inputs.has_continuation()) {
-+ const auto & msg = inputs.continue_msg;
-+
-+ data.generation_prompt = GEN_PROMPT + THINK_START + msg.reasoning_content;
-+ if (inputs.continue_final_message == COMMON_CHAT_CONTINUATION_CONTENT) {
-+ data.generation_prompt += THINK_END + msg.render_content();
-+ }
-+
-+ data.prompt += data.generation_prompt;
-+ }
-+
-+ auto parser = build_chat_peg_parser([&](common_chat_peg_builder & p) {
-+ auto generation_prompt = p.literal(GEN_PROMPT);
-+ auto end = p.end();
-+
-+ auto reasoning = p.eps();
-+ // M3 can emit a bare (no opener) after tool results; keep the opener optional.
-+ if (extract_reasoning && inputs.enable_thinking) {
-+ reasoning = p.optional(p.optional(p.literal(THINK_START)) + p.reasoning(p.until(THINK_END)) + THINK_END);
-+ } else if (extract_reasoning) {
-+ reasoning = p.optional(p.optional(p.literal(THINK_START)) + p.until(THINK_END) + p.literal(THINK_END));
-+ }
-+
-+ if (has_response_format) {
-+ auto response_format = p.rule("response-format",
-+ p.literal("```json") + p.space() +
-+ p.content(p.schema(p.json(), "response-format-schema", inputs.json_schema)) +
-+ p.space() + p.literal("```"));
-+ return generation_prompt + reasoning + response_format + end;
-+ }
-+
-+ if (!has_tools || inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_NONE) {
-+ return generation_prompt + reasoning + p.content(p.rest()) + end;
-+ }
-+
-+ auto tool_choice = p.choice();
-+ foreach_function(inputs.tools, [&](const json & tool) {
-+ const auto & function = tool.at("function");
-+ std::string name = function.at("name");
-+ auto params = function.contains("parameters") ? function.at("parameters") : json::object();
-+ const auto & props = params.contains("properties") ? params.at("properties") : json::object();
-+
-+ std::set required;
-+ if (params.contains("required")) {
-+ params.at("required").get_to(required);
-+ }
-+
-+ auto schema_info = common_schema_info();
-+ schema_info.resolve_refs(params);
-+
-+ std::vector required_parsers;
-+ std::vector optional_parsers;
-+ for (const auto & [param_name, param_schema] : props.items()) {
-+ bool is_required = required.find(param_name) != required.end();
-+ bool is_string = schema_info.resolves_to_string(param_schema);
-+
-+ const std::string p_close = NS + "" + param_name + ">";
-+
-+ auto arg = p.tool_arg(
-+ p.tool_arg_open(
-+ p.literal(NS + "<") +
-+ p.tool_arg_name(p.literal(param_name)) +
-+ p.literal(">")) +
-+ (is_string
-+ ? p.ac(p.tool_arg_string_value(p.until(p_close)) +
-+ p.tool_arg_close(p.literal(p_close)), p_close)
-+ : p.tool_arg_json_value(p.schema(p.json(),
-+ "tool-" + name + "-arg-" + param_name + "-schema",
-+ param_schema, false)) +
-+ p.tool_arg_close(p.literal(p_close))));
-+
-+ auto named_arg = p.rule("tool-" + name + "-arg-" + param_name, arg);
-+ if (is_required) {
-+ required_parsers.push_back(named_arg);
-+ } else {
-+ optional_parsers.push_back(named_arg);
-+ }
-+ }
-+
-+ common_peg_parser args_seq = p.eps();
-+ for (size_t i = 0; i < required_parsers.size(); i++) {
-+ if (i > 0) {
-+ args_seq = args_seq + p.space();
-+ }
-+ args_seq = args_seq + required_parsers[i];
-+ }
-+
-+ if (!optional_parsers.empty()) {
-+ common_peg_parser any_opt = p.choice();
-+ for (const auto & opt : optional_parsers) {
-+ any_opt |= opt;
-+ }
-+ args_seq = args_seq + p.repeat(p.space() + any_opt, 0, -1);
-+ }
-+
-+ common_peg_parser invoke_body = args_seq;
-+ auto func_parser = p.tool(
-+ p.tool_open(p.literal(NS + "")) +
-+ p.space() + invoke_body + p.space() +
-+ p.tool_close(p.literal(INVOKE_END)));
-+
-+ tool_choice |= p.rule("tool-" + name, func_parser);
-+ });
-+
-+ auto require_tools = inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED;
-+
-+ common_peg_parser tool_calls = p.eps();
-+ if (inputs.parallel_tool_calls) {
-+ tool_calls = p.trigger_rule("tool-call",
-+ p.literal(FC_START) + p.space() + tool_choice +
-+ p.zero_or_more(p.space() + tool_choice) + p.space() + p.literal(FC_END));
-+ } else {
-+ tool_calls = p.trigger_rule("tool-call",
-+ p.literal(FC_START) + p.space() + tool_choice + p.space() + p.literal(FC_END));
-+ }
-+
-+ if (!require_tools) {
-+ tool_calls = p.optional(tool_calls);
-+ }
-+
-+ auto content_before_tools = p.content(p.until(FC_START));
-+ return generation_prompt + reasoning + content_before_tools + tool_calls + end;
-+ });
-+
-+ data.parser = parser.save();
-+
-+ if (include_grammar) {
-+ data.grammar_lazy = !(has_response_format || (has_tools && inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED));
-+ data.grammar = build_grammar([&](const common_grammar_builder & builder) {
-+ foreach_function(inputs.tools, [&](const json & tool) {
-+ const auto & function = tool.at("function");
-+ auto schema = function.contains("parameters") ? function.at("parameters") : json::object();
-+ builder.resolve_refs(schema);
-+ });
-+ if (has_response_format) {
-+ auto schema = inputs.json_schema;
-+ builder.resolve_refs(schema);
-+ }
-+ parser.build_grammar(builder, data.grammar_lazy);
-+ });
-+
-+ data.grammar_triggers = {
-+ { COMMON_GRAMMAR_TRIGGER_TYPE_WORD, FC_START },
-+ };
-+ }
-+
-+ return data;
-+}
-+
- // Cohere2 MoE (a.k.a. "North Code") parser.
- //
- // The assistant turn is fully marker-wrapped:
-@@ -2707,6 +2892,15 @@ std::optional common_chat_try_specialized_template(
- return common_chat_params_init_gigachat_v3(tmpl, params);
- }
-
-+ // MiniMax-M3: the namespace token "]<]minimax[>[" collides with the autoparser's
-+ // markup delimiters, so detect the template and use a dedicated parser.
-+ if (src.find("]<]minimax[>[") != std::string::npos &&
-+ src.find("") != std::string::npos &&
-+ src.find("