Skip to content

Commit addf1d8

Browse files
committed
Merge commit '190c4838bd8bfff218a32c73dad7c9b4d5c444f1' into concedo_experimental
# Conflicts: # .github/workflows/build.yml # .github/workflows/release.yml # .gitignore # CMakeLists.txt # CODEOWNERS # common/CMakeLists.txt # ggml/CMakeLists.txt # ggml/src/ggml-webgpu/CMakeLists.txt # ggml/src/ggml-webgpu/ggml-webgpu.cpp # tests/.gitignore # tests/CMakeLists.txt # tests/test-backend-ops.cpp
2 parents 510508e + 190c483 commit addf1d8

23 files changed

Lines changed: 2814 additions & 42 deletions

common/arg.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include <thread> // for hardware_concurrency
3333
#include <vector>
3434

35+
#ifndef __EMSCRIPTEN__
3536
#ifdef __linux__
3637
#include <linux/limits.h>
3738
#elif defined(_WIN32)
@@ -43,6 +44,8 @@
4344
#else
4445
#include <sys/syslimits.h>
4546
#endif
47+
#endif
48+
4649
#define LLAMA_MAX_URL_LENGTH 2084 // Maximum URL Length in Chrome: 2083
4750

4851
using json = nlohmann::ordered_json;

common/chat-parser.cpp

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#include "chat-parser.h"
2+
#include "chat-peg-parser.h"
23
#include "common.h"
34
#include "log.h"
5+
#include "peg-parser.h"
46
#include "regex-partial.h"
57

68
#include <algorithm>
@@ -1483,6 +1485,11 @@ static void common_chat_parse(common_chat_msg_parser & builder) {
14831485
}
14841486

14851487
common_chat_msg common_chat_parse(const std::string & input, bool is_partial, const common_chat_syntax & syntax) {
1488+
if (syntax.format == COMMON_CHAT_FORMAT_PEG_SIMPLE ||
1489+
syntax.format == COMMON_CHAT_FORMAT_PEG_NATIVE ||
1490+
syntax.format == COMMON_CHAT_FORMAT_PEG_CONSTRUCTED) {
1491+
return common_chat_peg_parse(syntax.parser, input, is_partial, syntax);
1492+
}
14861493
common_chat_msg_parser builder(input, is_partial, syntax);
14871494
try {
14881495
common_chat_parse(builder);
@@ -1500,3 +1507,36 @@ common_chat_msg common_chat_parse(const std::string & input, bool is_partial, co
15001507
}
15011508
return msg;
15021509
}
1510+
1511+
common_chat_msg common_chat_peg_parse(const common_peg_arena & parser, const std::string & input, bool is_partial, const common_chat_syntax & syntax) {
1512+
if (parser.empty()) {
1513+
throw std::runtime_error("Failed to parse due to missing parser definition.");
1514+
}
1515+
1516+
LOG_DBG("Parsing input with format %s: %s\n", common_chat_format_name(syntax.format), input.c_str());
1517+
1518+
common_peg_parse_context ctx(input, is_partial);
1519+
auto result = parser.parse(ctx);
1520+
if (result.fail()) {
1521+
throw std::runtime_error(std::string("Failed to parse input at pos ") + std::to_string(result.end));
1522+
}
1523+
1524+
common_chat_msg msg;
1525+
msg.role = "assistant";
1526+
1527+
if (syntax.format == COMMON_CHAT_FORMAT_PEG_NATIVE) {
1528+
auto mapper = common_chat_peg_native_mapper(msg);
1529+
mapper.from_ast(ctx.ast, result);
1530+
} else if (syntax.format == COMMON_CHAT_FORMAT_PEG_CONSTRUCTED) {
1531+
auto mapper = common_chat_peg_constructed_mapper(msg);
1532+
mapper.from_ast(ctx.ast, result);
1533+
} else {
1534+
// Generic mapper
1535+
auto mapper = common_chat_peg_mapper(msg);
1536+
mapper.from_ast(ctx.ast, result);
1537+
}
1538+
if (!is_partial) {
1539+
LOG_DBG("Parsed message: %s\n", common_chat_msgs_to_json_oaicompat<json>({msg}).at(0).dump().c_str());
1540+
}
1541+
return msg;
1542+
}

common/chat-peg-parser.cpp

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
#include "chat-peg-parser.h"
2+
3+
#include <nlohmann/json.hpp>
4+
5+
// using json = nlohmann::json;
6+
7+
static std::string_view trim_trailing_space(std::string_view sv) {
8+
while (!sv.empty() && std::isspace(static_cast<unsigned char>(sv.back()))) {
9+
sv.remove_suffix(1);
10+
}
11+
return sv;
12+
}
13+
14+
void common_chat_peg_mapper::from_ast(const common_peg_ast_arena & arena, const common_peg_parse_result & result) {
15+
arena.visit(result, [this](const common_peg_ast_node & node) {
16+
map(node);
17+
});
18+
}
19+
20+
void common_chat_peg_mapper::map(const common_peg_ast_node & node) {
21+
bool is_reasoning = node.tag == common_chat_peg_builder::REASONING;
22+
bool is_content = node.tag == common_chat_peg_builder::CONTENT;
23+
24+
if (is_reasoning) {
25+
result.reasoning_content = std::string(trim_trailing_space(node.text));
26+
}
27+
28+
if (is_content) {
29+
result.content = std::string(trim_trailing_space(node.text));
30+
}
31+
}
32+
33+
void common_chat_peg_native_mapper::map(const common_peg_ast_node & node) {
34+
common_chat_peg_mapper::map(node);
35+
36+
bool is_tool_open = node.tag == common_chat_peg_native_builder::TOOL_OPEN;
37+
bool is_tool_name = node.tag == common_chat_peg_native_builder::TOOL_NAME;
38+
bool is_tool_id = node.tag == common_chat_peg_native_builder::TOOL_ID;
39+
bool is_tool_args = node.tag == common_chat_peg_native_builder::TOOL_ARGS;
40+
41+
if (is_tool_open) {
42+
result.tool_calls.emplace_back();
43+
current_tool = &result.tool_calls.back();
44+
}
45+
46+
if (is_tool_id && current_tool) {
47+
current_tool->id = std::string(trim_trailing_space(node.text));
48+
}
49+
50+
if (is_tool_name && current_tool) {
51+
current_tool->name = std::string(trim_trailing_space(node.text));
52+
}
53+
54+
if (is_tool_args && current_tool) {
55+
current_tool->arguments = std::string(trim_trailing_space(node.text));
56+
}
57+
}
58+
59+
void common_chat_peg_constructed_mapper::map(const common_peg_ast_node & node) {
60+
common_chat_peg_mapper::map(node);
61+
62+
bool is_tool_open = node.tag == common_chat_peg_constructed_builder::TOOL_OPEN;
63+
bool is_tool_name = node.tag == common_chat_peg_constructed_builder::TOOL_NAME;
64+
bool is_tool_close = node.tag == common_chat_peg_constructed_builder::TOOL_CLOSE;
65+
bool is_arg_open = node.tag == common_chat_peg_constructed_builder::TOOL_ARG_OPEN;
66+
bool is_arg_close = node.tag == common_chat_peg_constructed_builder::TOOL_ARG_CLOSE;
67+
bool is_arg_name = node.tag == common_chat_peg_constructed_builder::TOOL_ARG_NAME;
68+
bool is_arg_string = node.tag == common_chat_peg_constructed_builder::TOOL_ARG_STRING_VALUE;
69+
bool is_arg_json = node.tag == common_chat_peg_constructed_builder::TOOL_ARG_JSON_VALUE;
70+
71+
if (is_tool_open) {
72+
result.tool_calls.emplace_back();
73+
current_tool = &result.tool_calls.back();
74+
arg_count = 0;
75+
}
76+
77+
if (is_tool_name) {
78+
current_tool->name = std::string(node.text);
79+
current_tool->arguments = "{";
80+
}
81+
82+
if (is_arg_open) {
83+
needs_closing_quote = false;
84+
}
85+
86+
if (is_arg_name && current_tool) {
87+
if (arg_count > 0) {
88+
current_tool->arguments += ",";
89+
}
90+
current_tool->arguments += json(trim_trailing_space(node.text)).dump() + ":";
91+
++arg_count;
92+
}
93+
94+
if (is_arg_string && current_tool) {
95+
// Serialize to JSON, but exclude the end quote
96+
std::string dumped = json(node.text).dump();
97+
current_tool->arguments += dumped.substr(0, dumped.size() - 1);
98+
needs_closing_quote = true;
99+
}
100+
101+
if (is_arg_close && current_tool) {
102+
if (needs_closing_quote) {
103+
current_tool->arguments += "\"";
104+
}
105+
}
106+
107+
if (is_arg_json && current_tool) {
108+
current_tool->arguments += std::string(trim_trailing_space(node.text));
109+
}
110+
111+
if (is_tool_close && current_tool) {
112+
current_tool->arguments += "}";
113+
}
114+
}

common/chat-peg-parser.h

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
#pragma once
2+
3+
#include "chat.h"
4+
#include "peg-parser.h"
5+
6+
class common_chat_peg_builder : public common_peg_parser_builder {
7+
public:
8+
static constexpr const char * REASONING_BLOCK = "reasoning-block";
9+
static constexpr const char * REASONING = "reasoning";
10+
static constexpr const char * CONTENT = "content";
11+
12+
common_peg_parser reasoning_block(const common_peg_parser & p) { return tag(REASONING_BLOCK, p); }
13+
common_peg_parser reasoning(const common_peg_parser & p) { return tag(REASONING, p); }
14+
common_peg_parser content(const common_peg_parser & p) { return tag(CONTENT, p); }
15+
};
16+
17+
inline common_peg_arena build_chat_peg_parser(const std::function<common_peg_parser(common_chat_peg_builder & builder)> & fn) {
18+
common_chat_peg_builder builder;
19+
builder.set_root(fn(builder));
20+
return builder.build();
21+
}
22+
23+
class common_chat_peg_mapper {
24+
public:
25+
common_chat_msg & result;
26+
27+
common_chat_peg_mapper(common_chat_msg & msg) : result(msg) {}
28+
29+
virtual void from_ast(const common_peg_ast_arena & arena, const common_peg_parse_result & result);
30+
virtual void map(const common_peg_ast_node & node);
31+
};
32+
33+
class common_chat_peg_native_builder : public common_chat_peg_builder {
34+
public:
35+
static constexpr const char * TOOL = "tool";
36+
static constexpr const char * TOOL_OPEN = "tool-open";
37+
static constexpr const char * TOOL_CLOSE = "tool-close";
38+
static constexpr const char * TOOL_ID = "tool-id";
39+
static constexpr const char * TOOL_NAME = "tool-name";
40+
static constexpr const char * TOOL_ARGS = "tool-args";
41+
42+
common_peg_parser tool(const common_peg_parser & p) { return tag(TOOL, p); }
43+
common_peg_parser tool_open(const common_peg_parser & p) { return atomic(tag(TOOL_OPEN, p)); }
44+
common_peg_parser tool_close(const common_peg_parser & p) { return atomic(tag(TOOL_CLOSE, p)); }
45+
common_peg_parser tool_id(const common_peg_parser & p) { return atomic(tag(TOOL_ID, p)); }
46+
common_peg_parser tool_name(const common_peg_parser & p) { return atomic(tag(TOOL_NAME, p)); }
47+
common_peg_parser tool_args(const common_peg_parser & p) { return tag(TOOL_ARGS, p); }
48+
};
49+
50+
class common_chat_peg_native_mapper : public common_chat_peg_mapper {
51+
common_chat_tool_call * current_tool;
52+
53+
public:
54+
common_chat_peg_native_mapper(common_chat_msg & msg) : common_chat_peg_mapper(msg) {}
55+
56+
void map(const common_peg_ast_node & node) override;
57+
};
58+
59+
inline common_peg_arena build_chat_peg_native_parser(const std::function<common_peg_parser(common_chat_peg_native_builder & builder)> & fn) {
60+
common_chat_peg_native_builder builder;
61+
builder.set_root(fn(builder));
62+
return builder.build();
63+
}
64+
65+
class common_chat_peg_constructed_builder : public common_chat_peg_builder {
66+
public:
67+
static constexpr const char * TOOL = "tool";
68+
static constexpr const char * TOOL_OPEN = "tool-open";
69+
static constexpr const char * TOOL_CLOSE = "tool-close";
70+
static constexpr const char * TOOL_NAME = "tool-name";
71+
static constexpr const char * TOOL_ARG = "tool-arg";
72+
static constexpr const char * TOOL_ARG_OPEN = "tool-arg-open";
73+
static constexpr const char * TOOL_ARG_CLOSE = "tool-arg-close";
74+
static constexpr const char * TOOL_ARG_NAME = "tool-arg-name";
75+
static constexpr const char * TOOL_ARG_STRING_VALUE = "tool-arg-string-value";
76+
static constexpr const char * TOOL_ARG_JSON_VALUE = "tool-arg-json-value";
77+
78+
common_peg_parser tool(const common_peg_parser & p) { return tag(TOOL, p); }
79+
common_peg_parser tool_open(const common_peg_parser & p) { return atomic(tag(TOOL_OPEN, p)); }
80+
common_peg_parser tool_close(const common_peg_parser & p) { return atomic(tag(TOOL_CLOSE, p)); }
81+
common_peg_parser tool_name(const common_peg_parser & p) { return atomic(tag(TOOL_NAME, p)); }
82+
common_peg_parser tool_arg(const common_peg_parser & p) { return tag(TOOL_ARG, p); }
83+
common_peg_parser tool_arg_open(const common_peg_parser & p) { return atomic(tag(TOOL_ARG_OPEN, p)); }
84+
common_peg_parser tool_arg_close(const common_peg_parser & p) { return atomic(tag(TOOL_ARG_CLOSE, p)); }
85+
common_peg_parser tool_arg_name(const common_peg_parser & p) { return atomic(tag(TOOL_ARG_NAME, p)); }
86+
common_peg_parser tool_arg_string_value(const common_peg_parser & p) { return tag(TOOL_ARG_STRING_VALUE, p); }
87+
common_peg_parser tool_arg_json_value(const common_peg_parser & p) { return tag(TOOL_ARG_JSON_VALUE, p); }
88+
};
89+
90+
class common_chat_peg_constructed_mapper : public common_chat_peg_mapper {
91+
common_chat_tool_call * current_tool;
92+
int arg_count = 0;
93+
bool needs_closing_quote = false;
94+
95+
public:
96+
common_chat_peg_constructed_mapper(common_chat_msg & msg) : common_chat_peg_mapper(msg) {}
97+
98+
void map(const common_peg_ast_node & node) override;
99+
};
100+
101+
inline common_peg_arena build_chat_peg_constructed_parser(const std::function<common_peg_parser(common_chat_peg_constructed_builder & builder)> & fn) {
102+
common_chat_peg_constructed_builder builder;
103+
builder.set_root(fn(builder));
104+
return builder.build();
105+
}

common/chat.cpp

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -85,29 +85,36 @@ json common_chat_msg::to_json_oaicompat() const
8585
return message;
8686
}
8787

88-
std::vector<common_chat_msg_diff> common_chat_msg_diff::compute_diffs(const common_chat_msg & previous_msg, const common_chat_msg & new_msg) {
88+
std::vector<common_chat_msg_diff> common_chat_msg_diff::compute_diffs(const common_chat_msg & msg_prv, const common_chat_msg & msg_new) {
8989
std::vector<common_chat_msg_diff> diffs;
90-
if (previous_msg.reasoning_content != new_msg.reasoning_content) {
90+
if (msg_new.tool_calls.size() > msg_prv.tool_calls.size()) {
91+
diffs.reserve(msg_new.tool_calls.size() - msg_prv.tool_calls.size() + 3);
92+
} else {
93+
diffs.reserve(3);
94+
}
95+
96+
// TODO: these can become expensive for long messages - how to optimize?
97+
if (msg_prv.reasoning_content != msg_new.reasoning_content) {
9198
auto & diff = diffs.emplace_back();
92-
diff.reasoning_content_delta = string_diff(previous_msg.reasoning_content, new_msg.reasoning_content);
99+
diff.reasoning_content_delta = string_diff(msg_prv.reasoning_content, msg_new.reasoning_content);
93100
}
94-
if (previous_msg.content != new_msg.content) {
101+
if (msg_prv.content != msg_new.content) {
95102
auto & diff = diffs.emplace_back();
96-
diff.content_delta = string_diff(previous_msg.content, new_msg.content);
103+
diff.content_delta = string_diff(msg_prv.content, msg_new.content);
97104
}
98105

99-
if (new_msg.tool_calls.size() < previous_msg.tool_calls.size()) {
106+
if (msg_new.tool_calls.size() < msg_prv.tool_calls.size()) {
100107
throw std::runtime_error("Invalid diff: now finding less tool calls!");
101108
}
102109

103-
if (!previous_msg.tool_calls.empty()) {
104-
auto idx = previous_msg.tool_calls.size() - 1;
105-
const auto & pref = previous_msg.tool_calls[idx];
106-
const auto & newf = new_msg.tool_calls[idx];
110+
if (!msg_prv.tool_calls.empty()) {
111+
const auto idx = msg_prv.tool_calls.size() - 1;
112+
const auto & pref = msg_prv.tool_calls[idx];
113+
const auto & newf = msg_new.tool_calls[idx];
107114
if (pref.name != newf.name) {
108115
throw std::runtime_error("Invalid diff: tool call mismatch!");
109116
}
110-
auto args_diff = string_diff(pref.arguments, newf.arguments);
117+
const auto args_diff = string_diff(pref.arguments, newf.arguments);
111118
if (!args_diff.empty() || pref.id != newf.id) {
112119
auto & diff = diffs.emplace_back();
113120
diff.tool_call_index = idx;
@@ -118,11 +125,12 @@ std::vector<common_chat_msg_diff> common_chat_msg_diff::compute_diffs(const comm
118125
diff.tool_call_delta.arguments = args_diff;
119126
}
120127
}
121-
for (size_t idx = previous_msg.tool_calls.size(); idx < new_msg.tool_calls.size(); ++idx) {
128+
for (size_t idx = msg_prv.tool_calls.size(); idx < msg_new.tool_calls.size(); ++idx) {
122129
auto & diff = diffs.emplace_back();
123130
diff.tool_call_index = idx;
124-
diff.tool_call_delta = new_msg.tool_calls[idx];
131+
diff.tool_call_delta = msg_new.tool_calls[idx];
125132
}
133+
126134
return diffs;
127135
}
128136

@@ -434,6 +442,9 @@ template <> json common_chat_msg_diff_to_json_oaicompat(const common_chat_msg_di
434442
}
435443

436444
#include "chat-parser.cpp"
445+
#include "common/unicode.cpp"
446+
#include "peg-parser.cpp"
447+
#include "chat-peg-parser.cpp"
437448

438449
bool common_chat_verify_template(const std::string & tmpl, bool use_jinja) {
439450
if (use_jinja) {
@@ -651,6 +662,9 @@ const char * common_chat_format_name(common_chat_format format) {
651662
case COMMON_CHAT_FORMAT_QWEN3_CODER_XML: return "Qwen3 Coder";
652663
case COMMON_CHAT_FORMAT_APRIEL_1_5: return "Apriel 1.5";
653664
case COMMON_CHAT_FORMAT_XIAOMI_MIMO: return "Xiaomi MiMo";
665+
case COMMON_CHAT_FORMAT_PEG_SIMPLE: return "peg-simple";
666+
case COMMON_CHAT_FORMAT_PEG_NATIVE: return "peg-native";
667+
case COMMON_CHAT_FORMAT_PEG_CONSTRUCTED: return "peg-constructed";
654668
default:
655669
throw std::runtime_error("Unknown chat format");
656670
}

0 commit comments

Comments
 (0)