Skip to content

Commit 2834f07

Browse files
committed
Compose the server-side preemption pin with ggml-org#25731 (inkling) so both merge onto b10871
2 parents afd3248 + 946fc11 commit 2834f07

72 files changed

Lines changed: 4370 additions & 140 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

common/chat-peg-parser.cpp

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -630,7 +630,8 @@ common_peg_parser common_chat_peg_builder::build_json_tools_function_is_key(
630630
const std::string & args_key,
631631
const std::string & effective_args_key,
632632
const std::string & call_id_key,
633-
const std::string & gen_call_id_key) {
633+
const std::string & gen_call_id_key,
634+
bool require_object_args) {
634635

635636
auto tool_choices = choice();
636637

@@ -667,10 +668,10 @@ common_peg_parser common_chat_peg_builder::build_json_tools_function_is_key(
667668
// Arguments — either wrapped in args_key or parsed directly
668669
common_peg_parser args_parser = eps();
669670
if (args_key.empty()) {
670-
args_parser = tool_args(schema(json(), "tool-" + name + "-schema", params));
671+
args_parser = tool_args(schema(require_object_args ? json_object() : json(), "tool-" + name + "-schema", params));
671672
} else {
672673
args_parser = literal("\"" + effective_args_key + "\"") + space() + literal(":") + space() +
673-
tool_args(schema(json(), "tool-" + name + "-schema", params));
674+
tool_args(schema(require_object_args ? json_object() : json(), "tool-" + name + "-schema", params));
674675
}
675676
inner_fields.push_back(args_parser);
676677

@@ -709,7 +710,8 @@ common_peg_parser common_chat_peg_builder::build_json_tools_nested_keys(
709710
const std::string & effective_name_key,
710711
const std::string & effective_args_key,
711712
const std::string & call_id_key,
712-
const std::string & gen_call_id_key) {
713+
const std::string & gen_call_id_key,
714+
bool require_object_args) {
713715

714716
auto tool_choices = choice();
715717

@@ -731,7 +733,7 @@ common_peg_parser common_chat_peg_builder::build_json_tools_nested_keys(
731733
auto nested_name = literal("\"" + nested_name_field + "\"") + space() + literal(":") + space() +
732734
atomic(literal("\"") + tool_name(literal(name)) + literal("\""));
733735
auto nested_args = literal("\"" + nested_args_field + "\"") + space() + literal(":") + space() +
734-
tool_args(schema(json(), "tool-" + name + "-schema", params));
736+
tool_args(schema(require_object_args ? json_object() : json(), "tool-" + name + "-schema", params));
735737

736738
auto nested_object = literal("{") + space() +
737739
nested_name + space() + literal(",") + space() +
@@ -783,7 +785,8 @@ common_peg_parser common_chat_peg_builder::build_json_tools_flat_keys(
783785
const std::string & call_id_key,
784786
const std::string & gen_call_id_key,
785787
const std::vector<std::string> & parameters_order,
786-
bool accept_openai_wrapper) {
788+
bool accept_openai_wrapper,
789+
bool require_object_args) {
787790

788791
auto tool_choices = choice();
789792
auto name_key_parser = literal("\"" + effective_name_key + "\"");
@@ -800,7 +803,7 @@ common_peg_parser common_chat_peg_builder::build_json_tools_flat_keys(
800803
auto tool_name_ = name_key_parser + space() + literal(":") + space() +
801804
atomic(literal("\"") + tool_name(literal(name)) + literal("\""));
802805
auto tool_args_ = args_key_parser + space() + literal(":") + space() +
803-
tool_args(schema(json(), "tool-" + name + "-schema", params));
806+
tool_args(schema(require_object_args ? json_object() : json(), "tool-" + name + "-schema", params));
804807

805808
// Build ID parsers if keys are provided
806809
common_peg_parser id_parser = eps();
@@ -915,7 +918,8 @@ common_peg_parser common_chat_peg_builder::standard_json_tools(
915918
const std::string & call_id_key,
916919
const std::string & gen_call_id_key,
917920
const std::vector<std::string> & parameters_order,
918-
bool accept_openai_wrapper) {
921+
bool accept_openai_wrapper,
922+
bool require_object_args) {
919923
if (!tools.is_array() || tools.empty()) {
920924
return eps();
921925
}
@@ -926,14 +930,14 @@ common_peg_parser common_chat_peg_builder::standard_json_tools(
926930
// Dispatch to the appropriate builder based on the JSON layout mode
927931
common_peg_parser tool_choices = eps();
928932
if (function_is_key) {
929-
tool_choices = build_json_tools_function_is_key(tools, args_key, effective_args_key, call_id_key, gen_call_id_key);
933+
tool_choices = build_json_tools_function_is_key(tools, args_key, effective_args_key, call_id_key, gen_call_id_key, require_object_args);
930934
} else {
931935
auto name_spec = parse_key_spec(effective_name_key);
932936
auto args_spec = parse_key_spec(effective_args_key);
933937
if (!name_spec.first.empty() || !args_spec.first.empty()) {
934-
tool_choices = build_json_tools_nested_keys(tools, effective_name_key, effective_args_key, call_id_key, gen_call_id_key);
938+
tool_choices = build_json_tools_nested_keys(tools, effective_name_key, effective_args_key, call_id_key, gen_call_id_key, require_object_args);
935939
} else {
936-
tool_choices = build_json_tools_flat_keys(tools, effective_name_key, effective_args_key, call_id_key, gen_call_id_key, parameters_order, accept_openai_wrapper);
940+
tool_choices = build_json_tools_flat_keys(tools, effective_name_key, effective_args_key, call_id_key, gen_call_id_key, parameters_order, accept_openai_wrapper, require_object_args);
937941
}
938942
}
939943

common/chat-peg-parser.h

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,8 @@ class common_chat_peg_builder : public common_peg_parser_builder {
138138
const std::string & call_id_key = "",
139139
const std::string & gen_call_id_key = "",
140140
const std::vector<std::string> & parameters_order = {},
141-
bool accept_openai_wrapper = false);
141+
bool accept_openai_wrapper = false,
142+
bool require_object_args = false);
142143

143144
// Legacy-compatible helper for building XML/tagged style tool calls
144145
// Used by tests and manual parsers
@@ -162,21 +163,24 @@ class common_chat_peg_builder : public common_peg_parser_builder {
162163
const std::string & args_key,
163164
const std::string & effective_args_key,
164165
const std::string & call_id_key,
165-
const std::string & gen_call_id_key);
166+
const std::string & gen_call_id_key,
167+
bool require_object_args);
166168

167169
common_peg_parser build_json_tools_nested_keys(const common_json & tools,
168170
const std::string & effective_name_key,
169171
const std::string & effective_args_key,
170172
const std::string & call_id_key,
171-
const std::string & gen_call_id_key);
173+
const std::string & gen_call_id_key,
174+
bool require_object_args);
172175

173176
common_peg_parser build_json_tools_flat_keys(const common_json & tools,
174177
const std::string & effective_name_key,
175178
const std::string & effective_args_key,
176179
const std::string & call_id_key,
177180
const std::string & gen_call_id_key,
178181
const std::vector<std::string> & parameters_order,
179-
bool accept_openai_wrapper);
182+
bool accept_openai_wrapper,
183+
bool require_object_args);
180184
};
181185

182186
inline common_peg_arena build_chat_peg_parser(

common/chat.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1132,6 +1132,14 @@ std::optional<common_chat_params> common_chat_try_specialized_template(
11321132
return common_chat_params_init_cohere2moe(tmpl, params);
11331133
}
11341134

1135+
// Inkling / TML: this marker combination is unique to the template
1136+
if (src.find("<|content_thinking|>") != std::string::npos &&
1137+
src.find("<|content_text|>") != std::string::npos &&
1138+
src.find("<|message_model|>") != std::string::npos) {
1139+
LOG_DBG("Using specialized template: Inkling\n");
1140+
return common_chat_params_init_inkling(tmpl, params);
1141+
}
1142+
11351143
if (is_lfm2_template(src)) {
11361144
LOG_DBG("Using specialized template: LFM2\n");
11371145
return common_chat_params_init_lfm2(tmpl, params, /* tool_list_tokens = */ true);

common/parsers/inkling.cpp

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
#include "parsers.h"
2+
3+
// Inkling / TML typed-content-block parser: <|end_message|> separates blocks within a turn,
4+
// <|content_model_end_sampling|> is the sole end-of-generation token (mirrors sglang TmlDetector).
5+
common_chat_params common_chat_params_init_inkling(const common_chat_template & tmpl,
6+
const autoparser::generation_params & inputs) {
7+
common_chat_params data;
8+
9+
const std::string MSG_MODEL = "<|message_model|>";
10+
const std::string MSG_USER = "<|message_user|>";
11+
const std::string MSG_SYSTEM = "<|message_system|>";
12+
const std::string MSG_TOOL = "<|message_tool|>";
13+
const std::string THINK = "<|content_thinking|>";
14+
const std::string TEXT = "<|content_text|>";
15+
const std::string END_MESSAGE = "<|end_message|>";
16+
const std::string END_SAMPLING = "<|content_model_end_sampling|>";
17+
const std::string INVOKE_TOOL = "<|content_invoke_tool_json|>";
18+
19+
data.prompt = common_chat_template_direct_apply_impl(tmpl, inputs);
20+
data.generation_prompt = common_chat_template_generation_prompt_impl(tmpl, inputs);
21+
data.format = COMMON_CHAT_FORMAT_PEG_NATIVE;
22+
data.supports_thinking = true;
23+
data.thinking_start_tag = THINK;
24+
data.thinking_end_tags = {END_MESSAGE};
25+
data.preserved_tokens = {
26+
MSG_MODEL, MSG_USER, MSG_SYSTEM, MSG_TOOL,
27+
THINK, TEXT, END_MESSAGE, END_SAMPLING, INVOKE_TOOL,
28+
};
29+
30+
auto has_tools = inputs.tools.is_array() && !inputs.tools.empty();
31+
data.message_delimiters = {
32+
{ COMMON_CHAT_ROLE_ASSISTANT, MSG_MODEL },
33+
{ COMMON_CHAT_ROLE_USER, MSG_USER },
34+
{ COMMON_CHAT_ROLE_SYSTEM, MSG_SYSTEM },
35+
{ COMMON_CHAT_ROLE_TOOL, MSG_TOOL },
36+
};
37+
38+
auto extract_reasoning = inputs.reasoning_format != COMMON_REASONING_FORMAT_NONE;
39+
40+
if (inputs.has_continuation()) {
41+
const auto & msg = inputs.continue_msg;
42+
43+
data.generation_prompt = MSG_MODEL + THINK + msg.reasoning_content;
44+
if (inputs.continue_final_message == COMMON_CHAT_CONTINUATION_CONTENT) {
45+
data.generation_prompt += END_MESSAGE + TEXT + msg.render_content();
46+
}
47+
48+
data.prompt += data.generation_prompt;
49+
}
50+
51+
auto parser = build_chat_peg_parser([&](common_chat_peg_builder & p) {
52+
auto generation_prompt = p.literal(MSG_MODEL);
53+
auto end = p.end();
54+
55+
// thinking block; may also reappear mid-turn (after content), so it is both an optional
56+
// prefix and a choice inside the block loops. With reasoning_format=NONE keep it
57+
// (markers included) inline as content
58+
common_peg_parser reasoning_block = p.eps();
59+
if (extract_reasoning) {
60+
reasoning_block = p.literal(THINK) +
61+
p.reasoning(p.until_one_of({ END_MESSAGE, TEXT, END_SAMPLING })) +
62+
p.optional(p.literal(END_MESSAGE));
63+
} else {
64+
reasoning_block = p.content(p.literal(THINK) +
65+
p.until_one_of({ END_MESSAGE, TEXT, END_SAMPLING }) +
66+
p.optional(p.literal(END_MESSAGE)));
67+
}
68+
auto reasoning = p.optional(reasoning_block);
69+
70+
// TML re-emits <|message_model|> before each content block; a turn may contain several
71+
// text blocks (one per content part), so the block repeats and bodies concatenate.
72+
// THINK stops the content scan so a mid-turn thinking block is never leaked as text
73+
auto text_block = p.optional(p.literal(MSG_MODEL)) +
74+
p.optional(p.literal(TEXT)) +
75+
p.content(p.until_one_of({ THINK, END_MESSAGE, END_SAMPLING })) +
76+
p.optional(p.literal(END_MESSAGE));
77+
auto text_content = p.one_or_more(p.choice({ reasoning_block, text_block }));
78+
79+
if (!has_tools || inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_NONE) {
80+
return generation_prompt + reasoning + text_content +
81+
p.optional(p.literal(END_SAMPLING)) + end;
82+
}
83+
84+
// each call is its own block (role opener + bare name echo + JSON section);
85+
// force_tool_calls=true makes the JSON section required so a pure-text answer fails the
86+
// block cleanly; parallel calls are separate blocks, hence repeat + parallel=false
87+
auto tool_section = p.standard_json_tools(
88+
INVOKE_TOOL, END_MESSAGE, inputs.tools, /* parallel_tool_calls = */ false,
89+
/* force_tool_calls = */ true,
90+
/* name_key = */ "name",
91+
/* args_key = */ "args",
92+
/* array_wrapped = */ false,
93+
/* function_is_key = */ false,
94+
/* call_id_key = */ "",
95+
/* gen_call_id_key = */ "",
96+
/* parameters_order = */ {},
97+
/* accept_openai_wrapper = */ false,
98+
/* require_object_args = */ true);
99+
// the name-echo scan must stop at any block marker: a greedy until(INVOKE_TOOL) returns
100+
// NEED_MORE_INPUT mid-stream, which choice() treats as a match and shadows the text branch
101+
auto tool_block = p.optional(p.literal(MSG_MODEL)) +
102+
p.until_one_of({ INVOKE_TOOL, TEXT, THINK, END_MESSAGE, END_SAMPLING }) +
103+
tool_section;
104+
auto tool_calls = inputs.parallel_tool_calls ? p.one_or_more(tool_block) : tool_block;
105+
// turns may interleave narration, thinking and calls; parse block-by-block (tool block
106+
// first) since a whole-body choice would let the text branch swallow tool blocks into
107+
// visible content
108+
auto mixed_body = p.one_or_more(p.choice({ tool_block, reasoning_block, text_block }));
109+
auto body = inputs.tool_choice == COMMON_CHAT_TOOL_CHOICE_REQUIRED
110+
? tool_calls
111+
: mixed_body;
112+
113+
return generation_prompt + reasoning + body +
114+
p.optional(p.literal(END_SAMPLING)) + end;
115+
});
116+
117+
data.parser = parser.save();
118+
119+
return data;
120+
}

common/parsers/parsers.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ common_chat_params common_chat_params_init_gigachat_v3(const common_chat_templat
5959

6060
common_chat_params common_chat_params_init_gpt_oss(const common_chat_template & tmpl, const autoparser::generation_params & inputs);
6161

62+
common_chat_params common_chat_params_init_inkling(const common_chat_template & tmpl, const autoparser::generation_params & inputs);
63+
6264
common_chat_params common_chat_params_init_kimi_k2(const common_chat_template & tmpl, const autoparser::generation_params & inputs);
6365

6466
common_chat_params common_chat_params_init_kimi_k3(const common_chat_template & tmpl, const autoparser::generation_params & inputs);

common/parsers/sources.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ set(LLAMA_CHAT_PARSERS_SOURCES
99
${CMAKE_CURRENT_LIST_DIR}/gemma4.cpp
1010
${CMAKE_CURRENT_LIST_DIR}/gigachat-v3.cpp
1111
${CMAKE_CURRENT_LIST_DIR}/gpt-oss.cpp
12+
${CMAKE_CURRENT_LIST_DIR}/inkling.cpp
1213
${CMAKE_CURRENT_LIST_DIR}/kimi-k2.cpp
1314
${CMAKE_CURRENT_LIST_DIR}/kimi-k3.cpp
1415
${CMAKE_CURRENT_LIST_DIR}/lfm2.cpp

conversion/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@
126126
"HYV3ForCausalLM": "hunyuan",
127127
"HYV4ForCausalLM": "hy_v4",
128128
"IQuestCoderForCausalLM": "llama",
129+
"InklingForConditionalGeneration": "inkling",
129130
"InternLM2ForCausalLM": "internlm",
130131
"InternLM3ForCausalLM": "internlm",
131132
"JAISLMHeadModel": "jais",
@@ -308,6 +309,7 @@
308309
"GraniteSpeechPlusForConditionalGeneration": "granite",
309310
"HunYuanVLForConditionalGeneration": "hunyuan",
310311
"Idefics3ForConditionalGeneration": "smolvlm",
312+
"InklingForConditionalGeneration": "inkling",
311313
"InternVisionModel": "internvl",
312314
"JanusForConditionalGeneration": "januspro",
313315
"KimiK25ForConditionalGeneration": "kimivl",

0 commit comments

Comments
 (0)