Skip to content

Commit fa9ffc1

Browse files
mudler's LocalAI [bot]mudlerlocalai-org-maint-bot
authored
chore: ⬆️ Update ggml-org/llama.cpp to f280b26983ad0fdb705a0d9ebf0503e76f2899b0 (mudler#11646)
* ⬆️ Update ggml-org/llama.cpp Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * fix(llama-cpp): adapt to the common JSON API The llama.cpp bump replaces its nlohmann JSON alias with common_json. Update the gRPC adapter for the new exception, iterator, conversion, and container APIs. Assisted-by: Codex:gpt-5.6 [systematic-debugging] * fix(turboquant): adapt the JSON exception type The shared gRPC source now follows the upstream common_json API. The TurboQuant fork still exposes nlohmann JSON and cannot compile the new exception type. Translate that exception in the fork-specific source patch so both llama.cpp variants compile from the shared adapter. Assisted-by: Codex:gpt-5.6 [systematic-debugging] * fix(bonsai): adapt the JSON exception type The shared gRPC source uses upstream's common_json wrapper. The Bonsai fork still exposes nlohmann JSON and cannot compile that exception type.\n\nTranslate the exception in the fork-specific preparation step and verify that repeated preparation stays idempotent.\n\nAssisted-by: Codex:gpt-5.6 [systematic-debugging] * fix(llama-cpp): let prepare register gRPC The score patch duplicated the gRPC CMake registration that prepare.sh already owns. Its stale context rejects the current upstream tools file on Darwin before compilation starts. Assisted-by: Codex:gpt-5 --------- 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>
1 parent ccb9a0a commit fa9ffc1

8 files changed

Lines changed: 97 additions & 44 deletions

File tree

backend/cpp/bonsai/Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ define bonsai-build
4141
# and are applied by apply-patches.sh below.
4242
rm -rf $(CURRENT_MAKEFILE_DIR)/../bonsai-$(1)-build/patches
4343
$(MAKE) -C $(CURRENT_MAKEFILE_DIR)/../bonsai-$(1)-build purge
44+
bash $(CURRENT_MAKEFILE_DIR)/patch-grpc-server.sh $(CURRENT_MAKEFILE_DIR)/../bonsai-$(1)-build/grpc-server.cpp
4445
bash $(LLAMA_CPP_DIR)/disable-score-task.sh $(CURRENT_MAKEFILE_DIR)/../bonsai-$(1)-build/grpc-server.cpp
4546
bash $(LLAMA_CPP_DIR)/disable-tts-task.sh $(CURRENT_MAKEFILE_DIR)/../bonsai-$(1)-build/grpc-server.cpp
4647
$(info $(GREEN)I bonsai build info:$(1)$(RESET))
@@ -79,6 +80,7 @@ bonsai-cpu-all:
7980
# and are applied by apply-patches.sh below.
8081
rm -rf $(CURRENT_MAKEFILE_DIR)/../bonsai-cpu-all-build/patches
8182
$(MAKE) -C $(CURRENT_MAKEFILE_DIR)/../bonsai-cpu-all-build purge
83+
bash $(CURRENT_MAKEFILE_DIR)/patch-grpc-server.sh $(CURRENT_MAKEFILE_DIR)/../bonsai-cpu-all-build/grpc-server.cpp
8284
bash $(LLAMA_CPP_DIR)/disable-score-task.sh $(CURRENT_MAKEFILE_DIR)/../bonsai-cpu-all-build/grpc-server.cpp
8385
bash $(LLAMA_CPP_DIR)/disable-tts-task.sh $(CURRENT_MAKEFILE_DIR)/../bonsai-cpu-all-build/grpc-server.cpp
8486
$(info $(GREEN)I bonsai build info:cpu-all-variants$(RESET))
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/bin/bash
2+
# Adapt the shared llama.cpp gRPC source to the older JSON API in Bonsai.
3+
4+
set -euo pipefail
5+
6+
if [[ $# -ne 1 ]]; then
7+
echo "usage: $0 <grpc-server.cpp>" >&2
8+
exit 2
9+
fi
10+
11+
SRC=$1
12+
if [[ ! -f "$SRC" ]]; then
13+
echo "grpc-server.cpp not found at $SRC" >&2
14+
exit 2
15+
fi
16+
17+
if grep -q 'common_json_error' "$SRC"; then
18+
echo "==> patching $SRC to use the Bonsai JSON exception type"
19+
awk '{ gsub(/common_json_error/, "json::parse_error"); print }' "$SRC" > "$SRC.tmp"
20+
mv "$SRC.tmp" "$SRC"
21+
echo "==> Bonsai JSON exception patch OK"
22+
else
23+
echo "==> $SRC already uses a Bonsai-compatible JSON exception type, skipping"
24+
fi

backend/cpp/llama-cpp/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11

2-
LLAMA_VERSION?=d59d455fd8ea09e5a2e87ce2a9d668267ffb5ccd
2+
LLAMA_VERSION?=f280b26983ad0fdb705a0d9ebf0503e76f2899b0
33
LLAMA_REPO?=https://github.com/ggerganov/llama.cpp
44

55
CMAKE_ARGS?=

backend/cpp/llama-cpp/grpc-server.cpp

Lines changed: 26 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ json parse_options(bool streaming, const backend::PredictOptions* predict, const
294294
} else {
295295
SRV_WRN("[TOOLS DEBUG] parse_options: Parsed tools JSON is not an array: %s\n", tools_json.dump().c_str());
296296
}
297-
} catch (const json::parse_error& e) {
297+
} catch (const common_json_error& e) {
298298
SRV_WRN("Failed to parse tools JSON from proto: %s\n", e.what());
299299
SRV_WRN("[TOOLS DEBUG] parse_options: Tools string that failed to parse: %s\n", predict->tools().c_str());
300300
}
@@ -324,7 +324,7 @@ json parse_options(bool streaming, const backend::PredictOptions* predict, const
324324
SRV_DBG("[TOOLS DEBUG] Received tool_choice object from Go layer: %s\n", tool_choice_json.dump().c_str());
325325
}
326326
SRV_INF("Extracted tool_choice from proto: %s\n", predict->toolchoice().c_str());
327-
} catch (const json::parse_error& e) {
327+
} catch (const common_json_error& e) {
328328
// If parsing fails, treat as string
329329
data["tool_choice"] = predict->toolchoice();
330330
SRV_INF("Extracted tool_choice as string: %s\n", predict->toolchoice().c_str());
@@ -353,7 +353,7 @@ json parse_options(bool streaming, const backend::PredictOptions* predict, const
353353
// Add to data - llama.cpp server expects it as an object (map)
354354
data["logit_bias"] = logit_bias_json;
355355
SRV_INF("Using logit_bias: %s\n", predict->logitbias().c_str());
356-
} catch (const json::parse_error& e) {
356+
} catch (const common_json_error& e) {
357357
SRV_ERR("Failed to parse logit_bias JSON from proto: %s\n", e.what());
358358
}
359359
}
@@ -398,7 +398,10 @@ json parse_options(bool streaming, const backend::PredictOptions* predict, const
398398
});
399399
}
400400

401-
data["stop"] = predict->stopprompts();
401+
data["stop"] = json::array();
402+
for (const auto & stop : predict->stopprompts()) {
403+
data["stop"].push_back(stop);
404+
}
402405
// data["n_probs"] = predict->nprobs();
403406
//TODO: images,
404407

@@ -1795,7 +1798,7 @@ class BackendServiceImpl final : public backend::Backend::Service {
17951798
for (int j = 0; j < request->audios_size(); j++) rin.audios.push_back(request->audios(j));
17961799
for (int j = 0; j < request->videos_size(); j++) rin.videos.push_back(request->videos(j));
17971800
}
1798-
messages_json.push_back(llama_grpc::build_reconstructed_message(rin));
1801+
messages_json.push_back(json::parse(llama_grpc::build_reconstructed_message(rin).dump()));
17991802
}
18001803

18011804
// Final safety check: Ensure no message has null content (Jinja templates require strings)
@@ -1988,7 +1991,7 @@ class BackendServiceImpl final : public backend::Backend::Service {
19881991
if (!body_json.contains("chat_template_kwargs")) {
19891992
body_json["chat_template_kwargs"] = json::object();
19901993
}
1991-
for (auto& el : ctk.items()) {
1994+
for (auto el : ctk.items()) {
19921995
body_json["chat_template_kwargs"][el.key()] = el.value();
19931996
}
19941997
}
@@ -2074,30 +2077,27 @@ class BackendServiceImpl final : public backend::Backend::Service {
20742077
// If not using chat templates, extract files from image_data/audio_data fields
20752078
// (If using chat templates, files were already extracted by oaicompat_chat_params_parse)
20762079
if (!request->usetokenizertemplate() || request->messages_size() == 0 || ctx_server.impl->chat_params.tmpls == nullptr) {
2077-
const auto &images_data = data.find("image_data");
2078-
if (images_data != data.end() && images_data->is_array())
2080+
if (data.contains("image_data") && data.at("image_data").is_array())
20792081
{
2080-
for (const auto &img : *images_data)
2082+
for (const auto &img : data.at("image_data"))
20812083
{
20822084
auto decoded_data = base64_decode(img["data"].get<std::string>());
20832085
files.push_back(decoded_data);
20842086
}
20852087
}
20862088

2087-
const auto &audio_data = data.find("audio_data");
2088-
if (audio_data != data.end() && audio_data->is_array())
2089+
if (data.contains("audio_data") && data.at("audio_data").is_array())
20892090
{
2090-
for (const auto &audio : *audio_data)
2091+
for (const auto &audio : data.at("audio_data"))
20912092
{
20922093
auto decoded_data = base64_decode(audio["data"].get<std::string>());
20932094
files.push_back(decoded_data);
20942095
}
20952096
}
20962097

2097-
const auto &video_data = data.find("video_data");
2098-
if (video_data != data.end() && video_data->is_array())
2098+
if (data.contains("video_data") && data.at("video_data").is_array())
20992099
{
2100-
for (const auto &video : *video_data)
2100+
for (const auto &video : data.at("video_data"))
21012101
{
21022102
auto decoded_data = base64_decode(video["data"].get<std::string>());
21032103
files.push_back(decoded_data);
@@ -2370,7 +2370,7 @@ class BackendServiceImpl final : public backend::Backend::Service {
23702370
for (int j = 0; j < request->audios_size(); j++) rin.audios.push_back(request->audios(j));
23712371
for (int j = 0; j < request->videos_size(); j++) rin.videos.push_back(request->videos(j));
23722372
}
2373-
messages_json.push_back(llama_grpc::build_reconstructed_message(rin));
2373+
messages_json.push_back(json::parse(llama_grpc::build_reconstructed_message(rin).dump()));
23742374
}
23752375

23762376
// Final safety check: Ensure no message has null content (Jinja templates require strings)
@@ -2563,7 +2563,7 @@ class BackendServiceImpl final : public backend::Backend::Service {
25632563
if (!body_json.contains("chat_template_kwargs")) {
25642564
body_json["chat_template_kwargs"] = json::object();
25652565
}
2566-
for (auto& el : ctk.items()) {
2566+
for (auto el : ctk.items()) {
25672567
body_json["chat_template_kwargs"][el.key()] = el.value();
25682568
}
25692569
}
@@ -2649,32 +2649,29 @@ class BackendServiceImpl final : public backend::Backend::Service {
26492649
// If not using chat templates, extract files from image_data/audio_data fields
26502650
// (If using chat templates, files were already extracted by oaicompat_chat_params_parse)
26512651
if (!request->usetokenizertemplate() || request->messages_size() == 0 || ctx_server.impl->chat_params.tmpls == nullptr) {
2652-
const auto &images_data = data.find("image_data");
2653-
if (images_data != data.end() && images_data->is_array())
2652+
if (data.contains("image_data") && data.at("image_data").is_array())
26542653
{
2655-
std::cout << "[PREDICT] Processing " << images_data->size() << " images" << std::endl;
2656-
for (const auto &img : *images_data)
2654+
std::cout << "[PREDICT] Processing " << data.at("image_data").size() << " images" << std::endl;
2655+
for (const auto &img : data.at("image_data"))
26572656
{
26582657
std::cout << "[PREDICT] Processing image" << std::endl;
26592658
auto decoded_data = base64_decode(img["data"].get<std::string>());
26602659
files.push_back(decoded_data);
26612660
}
26622661
}
26632662

2664-
const auto &audio_data = data.find("audio_data");
2665-
if (audio_data != data.end() && audio_data->is_array())
2663+
if (data.contains("audio_data") && data.at("audio_data").is_array())
26662664
{
2667-
for (const auto &audio : *audio_data)
2665+
for (const auto &audio : data.at("audio_data"))
26682666
{
26692667
auto decoded_data = base64_decode(audio["data"].get<std::string>());
26702668
files.push_back(decoded_data);
26712669
}
26722670
}
26732671

2674-
const auto &video_data = data.find("video_data");
2675-
if (video_data != data.end() && video_data->is_array())
2672+
if (data.contains("video_data") && data.at("video_data").is_array())
26762673
{
2677-
for (const auto &video : *video_data)
2674+
for (const auto &video : data.at("video_data"))
26782675
{
26792676
auto decoded_data = base64_decode(video["data"].get<std::string>());
26802677
files.push_back(decoded_data);
@@ -3005,7 +3002,7 @@ class BackendServiceImpl final : public backend::Backend::Service {
30053002
}
30063003

30073004
// Collect responses
3008-
json responses = json::array();
3005+
std::vector<json> responses;
30093006
for (auto & res : all_results.results) {
30103007
GGML_ASSERT(dynamic_cast<server_task_result_rerank*>(res.get()) != nullptr);
30113008
responses.push_back(res->to_json());
@@ -3018,7 +3015,7 @@ class BackendServiceImpl final : public backend::Backend::Service {
30183015
// Crop results by request.top_n if specified
30193016
int top_n = request->top_n();
30203017
if (top_n > 0 && top_n < static_cast<int>(responses.size())) {
3021-
responses = json(responses.begin(), responses.begin() + top_n);
3018+
responses.resize(top_n);
30223019
}
30233020
// Set usage information
30243021
backend::Usage* usage = rerankResult->mutable_usage();

backend/cpp/llama-cpp/message_content.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,15 @@ inline nlohmann::ordered_json normalize_message_content(const std::string& role,
5252
// (#7528). A multimodal user message legitimately carries a typed-part array
5353
// ({type:text}, {type:image_url}, ...), which must be left intact. Shared by the
5454
// streaming and non-streaming paths so this invariant cannot drift between them.
55-
inline void normalize_template_message(nlohmann::ordered_json& msg) {
55+
template <typename Json>
56+
inline void normalize_template_message(Json& msg) {
5657
if (!msg.contains("content")) {
5758
msg["content"] = ""; // templates expect the field to exist
5859
return;
5960
}
60-
nlohmann::ordered_json& content = msg["content"];
61+
auto& content = msg["content"];
6162
const std::string role = (msg.contains("role") && msg["role"].is_string())
62-
? msg["role"].get<std::string>()
63+
? msg["role"].template get<std::string>()
6364
: std::string();
6465
if (content.is_null()) {
6566
content = ""; // #7324: null would crash content[:N] slicing

backend/cpp/llama-cpp/patches/0001-add-server-task-type-score.patch

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,9 @@ Subject: [PATCH 1/2] score-patch
66
---
77
common/common.cpp | 6 +-
88
common/common.h | 3 +
9-
tools/CMakeLists.txt | 1 +
109
tools/server/server-context.cpp | 358 +++++++++++++++++++++++++++++++-
1110
tools/server/server-task.h | 47 +++++
12-
5 files changed, 406 insertions(+), 9 deletions(-)
11+
4 files changed, 405 insertions(+), 9 deletions(-)
1312

1413
diff --git a/common/common.cpp b/common/common.cpp
1514
index 2e3f14c..0cec0dc 100644
@@ -42,15 +41,6 @@ index 878534d..4001df2 100644
4241
int32_t n_sequences = 1; // number of sequences to decode
4342
int32_t n_outputs_max = 0; // max outputs in a batch (0 = n_batch)
4443
int32_t n_outputs_max_per_seq = 1; // max outputs per sequence
45-
diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt
46-
index 780df32..1d2fe8f 100644
47-
--- a/tools/CMakeLists.txt
48-
+++ b/tools/CMakeLists.txt
49-
@@ -41,3 +41,4 @@ else()
50-
add_subdirectory(fit-params)
51-
add_subdirectory(results)
52-
endif()
53-
+add_subdirectory(grpc-server)
5444
diff --git a/tools/server/server-context.cpp b/tools/server/server-context.cpp
5545
index 3b5f6a1..d0e18e6 100644
5646
--- a/tools/server/server-context.cpp

backend/cpp/turboquant/patch-grpc-server.sh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
# so the grpc-server option parser skips the two references to
99
# common_params::checkpoint_min_step (the default and the option handler).
1010
# That field does not exist in the fork yet; drop this once it does.
11+
# 3. Use nlohmann's parse_error type in JSON catch clauses because the fork
12+
# predates upstream's common_json_error wrapper.
1113
#
1214
# The fork used to lag upstream on the whole common_params_speculative refactor
1315
# (ggml-org/llama.cpp#22397/#22838/#22964), the model_tgt rename (#22838) and
@@ -100,4 +102,16 @@ else
100102
echo "==> LOCALAI_TURBOQUANT_NO_CHECKPOINT_MIN_STEP define OK"
101103
fi
102104

105+
# 3. The shared source follows current upstream and catches common_json_error.
106+
# TurboQuant still exposes nlohmann::json directly, so its equivalent parse
107+
# failures use json::parse_error instead.
108+
if grep -q 'common_json_error' "$SRC"; then
109+
echo "==> patching $SRC to use the TurboQuant JSON exception type"
110+
awk '{ gsub(/common_json_error/, "json::parse_error"); print }' "$SRC" > "$SRC.tmp"
111+
mv "$SRC.tmp" "$SRC"
112+
echo "==> TurboQuant JSON exception patch OK"
113+
else
114+
echo "==> $SRC already uses a TurboQuant-compatible JSON exception type, skipping"
115+
fi
116+
103117
echo "==> all patches applied"
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#!/bin/bash
2+
set -euo pipefail
3+
4+
ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)
5+
PATCHER="$ROOT/backend/cpp/bonsai/patch-grpc-server.sh"
6+
WORK=$(mktemp -d)
7+
trap 'rm -rf "$WORK"' EXIT
8+
9+
cat > "$WORK/grpc-server.cpp" <<'EOF'
10+
try {
11+
json::parse("{");
12+
} catch (const common_json_error& e) {
13+
}
14+
EOF
15+
16+
bash "$PATCHER" "$WORK/grpc-server.cpp"
17+
grep -q 'catch (const json::parse_error& e)' "$WORK/grpc-server.cpp"
18+
! grep -q 'common_json_error' "$WORK/grpc-server.cpp"
19+
20+
# A repeated preparation pass must not change the generated source.
21+
cp "$WORK/grpc-server.cpp" "$WORK/once.cpp"
22+
bash "$PATCHER" "$WORK/grpc-server.cpp"
23+
cmp "$WORK/once.cpp" "$WORK/grpc-server.cpp"
24+
25+
echo "PASS: Bonsai uses its fork-compatible JSON exception"

0 commit comments

Comments
 (0)