ember.cpp 0.1.0
C++ inference engine for Llama-family and paperkiln models (CPU, CUDA, WebAssembly)
Loading...
Searching...
No Matches
microtorch::chat Namespace Reference

Namespaces

namespace  detail
 

Classes

struct  Error
 
struct  Message
 
struct  ParsedReply
 
struct  Piece
 
struct  Resolution
 
struct  Template
 
struct  ToolCall
 

Typedefs

using json = nlohmann::ordered_json
 

Functions

Template v1 ()
 
Template v2 ()
 
Template legacy_word ()
 
Template chatml (const std::string &name, bool qwen25)
 
Template gemma3 ()
 
std::vector< Template > registry ()
 
Template get (const std::string &id)
 
template<class J >
std::vector< Message > messages_from_json (const J &a)
 
std::vector< Message > from_pairs (const std::vector< std::pair< std::string, std::string > > &turns, const std::string *user=nullptr, const std::string *system=nullptr)
 
size_t check_structure (const std::vector< Message > &m, bool prompt, bool tools_ok=false)
 
bool py_space (uint32_t c)
 
std::string strip_py_ws (const std::string &s)
 
std::string tool_json (const json &d)
 
std::vector< Piece > render_pieces (const std::vector< Message > &m, const Template &t, bool generation_prompt, int thinking=-1, const std::vector< json > &tools={})
 
std::string render (const std::vector< Message > &m, const Template &t, bool generation_prompt, int thinking=-1, const std::vector< json > &tools={})
 
std::vector< std::pair< size_t, size_t > > exchanges (const std::vector< Message > &m)
 
template<class Decode >
ParsedReply parse_reply (const std::vector< int > &ids, int call_id, Decode decode)
 
void check_control_tokens (const Template &t, const std::vector< std::string > &tokens, const std::vector< int32_t > &types)
 
std::string jinja (const Template &t)
 
std::string modelfile (const std::string &gguf_name, const Template &t)
 
Resolution resolve (const std::string &declared, const std::vector< std::string > &tokens, const std::string &model, const std::string &chat_template, const std::string &arch)
 

Variables

constexpr const char * kSystemModeV1 = "fold"
 
constexpr const char * kDefaultSftTurns = "all"
 
constexpr const char * kGGUFKey = "paperkiln.chat_template"
 
constexpr const char * kV1 = "paperkiln/1"
 
constexpr const char * kLegacyWord = "paperkiln/legacy-word"
 
constexpr const char * kV2 = "paperkiln/2"
 
const std::array< const char *, 4 > kV1Specials = {"<unk>", "<|endoftext|>", "<|user|>", "<|assistant|>"}
 
const std::array< const char *, 8 > kV2Control
 

Typedef Documentation

◆ json

using microtorch::chat::json = typedef nlohmann::ordered_json

Definition at line 71 of file chat_template.h.

Function Documentation

◆ chatml()

Template microtorch::chat::chatml ( const std::string &  name,
bool  qwen25 
)
inline

Definition at line 286 of file chat_template.h.

286 {
287 Template t;
288 t.name = name;
289 t.version = 1;
290 t.system_mode = "turn";
291 t.system_open = "<|im_start|>system\n";
292 t.system_close = "<|im_end|>\n";
293 t.user_open = "<|im_start|>user\n";
294 t.user_close = "<|im_end|>\n";
295 t.assistant_open = "<|im_start|>assistant\n";
296 t.assistant_close = "<|im_end|>\n";
297 t.generation_prompt = "<|im_start|>assistant\n";
298 t.stop_eos = {"<|im_end|>", "<|endoftext|>"};
299 t.reserved = {"<|im_start|>", "<|im_end|>", "<|endoftext|>"};
300 t.markers = {"<|im_start|>", "<|im_end|>"};
301 if (qwen25) {
302 t.has_default_system = true;
303 t.default_system = "You are Qwen, created by Alibaba Cloud. You are a helpful assistant.";
304 t.match = {"d5495a1e5db0611132a97e46a65dbb64a642a499421228b9c8b93229097fa9a4"};
305 t.architectures = {"qwen2"};
306 } else {
307 t.past_assistant = "strip_think";
308 t.has_nothink = true;
309 t.generation_prompt_nothink = "<|im_start|>assistant\n<think>\n\n</think>\n\n";
310 t.match = {"57f1fd00f0013a2be96aa79b857391f27e23df5b5f847072b524c897e24d0361"};
311 t.architectures = {"qwen3"};
312 }
313 return t;
314}
const char * name
size_t j

References j, microtorch::chat::Template::name, and name.

Referenced by registry().

◆ check_control_tokens()

void microtorch::chat::check_control_tokens ( const Template &  t,
const std::vector< std::string > &  tokens,
const std::vector< int32_t > &  types 
)
inline

Definition at line 720 of file chat_template.h.

721 {
722 if (!t.has_tool) return;
723 for (const auto& r : t.reserved) {
724 bool ok = false;
725 for (size_t i = 0; i < tokens.size() && !ok; ++i)
726 ok = tokens[i] == r && i < types.size() && types[i] == 3;
727 if (!ok) throw Error("chat template " + t.id() + " needs '" + r + "' as a control token of the vocabulary");
728 }
729}

References check_control_tokens(), and j.

Referenced by check_control_tokens(), and ember::make_chat_setup().

◆ check_structure()

size_t microtorch::chat::check_structure ( const std::vector< Message > &  m,
bool  prompt,
bool  tools_ok = false 
)
inline

Definition at line 443 of file chat_template.h.

443 {
444 if (m.empty()) throw Error("a conversation needs a non-empty messages list");
445 for (const auto& x : m) {
446 if (x.role == "tool" && !tools_ok) throw Error("role 'tool' is reserved: no template defines it yet");
447 if (x.role != "system" && x.role != "user" && x.role != "assistant" && x.role != "tool")
448 throw Error("unknown role '" + x.role + "'");
449 if (x.has_tool_calls && (!tools_ok || x.role != "assistant"))
450 throw Error("tool_calls belong on assistant messages of a template with tool turns");
451 if (x.has_tool_call_id && x.role != "tool") throw Error("tool_call_id belongs on tool messages");
452 }
453 const size_t first = m[0].role == "system" ? 1 : 0;
454 if (first == m.size()) throw Error("a system message needs a user message after it");
455 std::string want = "user";
456 size_t pending = 0, call = 0; // results still owed to the last calls; the next call they answer
457 const Message* caller = nullptr;
458 for (size_t i = first; i < m.size(); ++i) {
459 if (m[i].role == "system") throw Error("a system message is allowed only as the first message");
460 if (m[i].role != want) {
461 if (!tools_ok)
462 throw Error("message " + std::to_string(i) + " is " + m[i].role + ", expected " + want +
463 " (user and assistant alternate from the user)");
464 throw Error("message " + std::to_string(i) + " is " + m[i].role + ", expected " + want +
465 " (user, then assistant; an assistant message with k tool calls, then k tool messages, then "
466 "assistant)");
467 }
468 if (m[i].loss >= 0 && m[i].role != "assistant") throw Error("loss is a bool on assistant messages only");
469 if (m[i].role == "user") {
470 want = "assistant";
471 } else if (m[i].role == "assistant") {
472 pending = m[i].tool_calls.size();
473 call = 0;
474 caller = &m[i];
475 want = pending ? "tool" : "user";
476 } else {
477 const ToolCall& c = caller->tool_calls[call++];
478 --pending;
479 // A call without an id counts as id null (IR_SPEC 8.1.6): a null tool_call_id answers only such a call.
480 if (m[i].has_tool_call_id && m[i].tool_call_id_null && c.has_id)
481 throw Error("tool_call_id null is not the call's id '" + c.id + "' (tool messages answer the calls in "
482 "order; null answers a call without an id)");
483 if (m[i].has_tool_call_id && !m[i].tool_call_id_null && (!c.has_id || m[i].tool_call_id != c.id))
484 throw Error("tool_call_id '" + m[i].tool_call_id + "' is not the call's id (tool messages answer the "
485 "calls in order)");
486 want = pending ? "tool" : "assistant";
487 }
488 }
489 const std::string& last = m.back().role;
490 if (prompt && !(last == "user" || (last == "tool" && pending == 0)))
491 throw Error(std::string("a prompt conversation ends on a user message") +
492 (tools_ok ? " or on the last tool message of a call" : ""));
493 if (!prompt && last != "assistant") throw Error("a training conversation ends on an assistant message");
494 return first;
495}
float c

References c, and j.

◆ exchanges()

std::vector< std::pair< size_t, size_t > > microtorch::chat::exchanges ( const std::vector< Message > &  m)
inline

Definition at line 673 of file chat_template.h.

673 {
674 std::vector<std::pair<size_t, size_t>> out;
675 for (size_t i = 0; i < m.size(); ++i)
676 if (m[i].role == "user") {
677 if (!out.empty()) out.back().second = i;
678 out.push_back({i, m.size()});
679 }
680 return out;
681}
node out

References exchanges(), j, and out.

Referenced by ember::build_general_prompt(), exchanges(), and main().

◆ from_pairs()

std::vector< Message > microtorch::chat::from_pairs ( const std::vector< std::pair< std::string, std::string > > &  turns,
const std::string *  user = nullptr,
const std::string *  system = nullptr 
)
inline

Definition at line 429 of file chat_template.h.

430 {
431 std::vector<Message> m;
432 if (system) m.push_back({"system", *system});
433 for (const auto& t : turns) {
434 m.push_back({"user", t.first});
435 m.push_back({"assistant", t.second});
436 }
437 if (user) m.push_back({"user", *user});
438 return m;
439}

References j.

Referenced by main(), and ember::ChatFormat::prompt().

◆ gemma3()

Template microtorch::chat::gemma3 ( )
inline

Definition at line 316 of file chat_template.h.

316 {
317 Template t;
318 t.name = "gemma3";
319 t.version = 1;
320 t.bos = "<bos>";
321 t.content = "trim";
322 t.system_mode = "fold";
323 t.system_join = "\n\n";
324 t.user_open = "<start_of_turn>user\n";
325 t.user_close = "<end_of_turn>\n";
326 t.assistant_open = "<start_of_turn>model\n";
327 t.assistant_close = "<end_of_turn>\n";
328 t.generation_prompt = "<start_of_turn>model\n";
329 t.stop_eos = {"<end_of_turn>", "<eos>"};
330 t.reserved = {"<start_of_turn>", "<end_of_turn>", "<bos>", "<eos>"};
331 t.markers = {"<start_of_turn>", "<end_of_turn>"};
332 t.match = {"7de1c58e208eda46e9c7f86397df37ec49883aeece39fb961e0a6b24088dd3c4"};
333 t.architectures = {"gemma3"};
334 return t;
335}

References j, and microtorch::chat::Template::name.

Referenced by registry().

◆ get()

Template microtorch::chat::get ( const std::string &  id)
inline

Definition at line 341 of file chat_template.h.

341 {
342 std::string known;
343 for (const auto& t : registry()) {
344 if (t.id() == id) return t;
345 known += (known.empty() ? "" : ", ") + t.id();
346 }
347 throw Error("no chat template '" + id + "' (known: " + known + ")");
348}
std::vector< Template > registry()

References microtorch::chat::Template::id(), id, j, and registry().

◆ jinja()

std::string microtorch::chat::jinja ( const Template &  t)
inline

Definition at line 817 of file chat_template.h.

817 {
818 using detail::jstr;
819 detail::paperkiln_only(t, "jinja()");
820 if (t.content != "raw" || t.past_assistant != "keep" || t.has_default_system)
821 throw Error("jinja() covers raw content, kept replies and no default system");
822 if (t.has_tool) return detail::jinja_tools(t);
823 std::string j;
824 if (!t.bos.empty()) j += "{{- " + jstr(t.bos) + " -}}";
825 j += "{%- if messages and messages[0]['role'] == 'system' -%}"
826 "{%- set sys = messages[0]['content'] -%}{%- set msgs = messages[1:] -%}"
827 "{%- else -%}{%- set sys = none -%}{%- set msgs = messages -%}{%- endif -%}";
828 if (t.system_mode == "none")
829 j += "{%- if sys is not none -%}{{ raise_exception('this model\\'s template has no system role') }}"
830 "{%- endif -%}";
831 else if (t.system_mode == "turn")
832 j += "{%- if sys is not none -%}{{- " + jstr(t.system_open) + " + sys + " + jstr(t.system_close) +
833 " -}}{%- endif -%}";
834 j += "{%- for m in msgs -%}"
835 "{%- if (m['role'] == 'user') != (loop.index0 % 2 == 0) -%}"
836 "{{ raise_exception('user and assistant messages alternate from the user') }}{%- endif -%}"
837 "{%- if m['role'] == 'user' -%}{{- " + jstr(t.user_open) + " -}}";
838 if (t.system_mode == "fold")
839 j += "{%- if loop.first and sys is not none -%}{{- sys + " + jstr(t.system_join) + " -}}{%- endif -%}";
840 j += "{{- m['content'] + " + jstr(t.user_close) + " -}}"
841 "{%- else -%}{{- " + jstr(t.assistant_open) + " + m['content'] + " + jstr(t.assistant_close) + " -}}"
842 "{%- endif -%}{%- endfor -%}"
843 "{%- if add_generation_prompt -%}{{- " + jstr(t.generation_prompt) + " -}}{%- endif -%}";
844 return j;
845}

References j, and jinja().

Referenced by jinja().

◆ legacy_word()

Template microtorch::chat::legacy_word ( )
inline

Definition at line 270 of file chat_template.h.

270 {
271 Template t;
272 t.name = "paperkiln";
273 t.version = "legacy-word";
274 t.system_mode = kSystemModeV1;
275 t.system_join = "\n\n";
276 t.user_open = "user: ";
277 t.user_close = " ";
278 t.assistant_open = "assistant: ";
279 t.assistant_close = " ";
280 t.generation_prompt = "assistant:";
281 t.stop_turn = {"user:", "assistant:"};
282 return t;
283}
constexpr const char * kSystemModeV1

References j, kSystemModeV1, and microtorch::chat::Template::name.

Referenced by registry(), and resolve().

◆ messages_from_json()

template<class J >
std::vector< Message > microtorch::chat::messages_from_json ( const J &  a)

Definition at line 371 of file chat_template.h.

371 {
372 if (!a.is_array()) throw Error("a conversation needs a messages list");
373 std::vector<Message> out;
374 for (const auto& m : a) {
375 if (!m.is_object() || !m.contains("content") || !m["content"].is_string())
376 throw Error("every message needs a role and a string content");
377 Message x;
378 x.role = m.value("role", std::string());
379 x.content = m["content"].template get<std::string>();
380 if (m.contains("loss")) {
381 if (!m["loss"].is_boolean()) throw Error("loss is a bool on assistant messages only");
382 x.loss = m["loss"].template get<bool>() ? 1 : 0;
383 }
384 if (m.contains("tool_calls")) {
385 x.has_tool_calls = true;
386 const auto& tc = m["tool_calls"];
387 if (!tc.is_null()) {
388 if (!tc.is_array() || tc.empty()) throw Error("tool_calls is a non-empty list");
389 for (const auto& c : tc) {
390 if (!c.is_object() || !c.contains("function") || !c["function"].is_object())
391 throw Error("a tool call is {id?, type?: function, function: {name, arguments}}");
392 if (c.contains("type") && !(c["type"].is_string() && c["type"].template get<std::string>() == "function"))
393 throw Error("tool call type is not implemented (function)");
394 const auto& f = c["function"];
395 ToolCall k;
396 if (!f.contains("name") || !f["name"].is_string())
397 throw Error("a tool call's name is a non-empty string without a newline");
398 k.name = f["name"].template get<std::string>();
399 if (k.name.empty() || k.name.find('\n') != std::string::npos)
400 throw Error("a tool call's name is a non-empty string without a newline");
401 if (f.contains("arguments")) {
402 if (!f["arguments"].is_string()) throw Error("a tool call's arguments are a string (rendered verbatim)");
403 k.arguments = f["arguments"].template get<std::string>();
404 }
405 if (c.contains("id")) {
406 if (!c["id"].is_string()) throw Error("a tool call's id is a string");
407 k.has_id = true;
408 k.id = c["id"].template get<std::string>();
409 }
410 x.tool_calls.push_back(std::move(k));
411 }
412 }
413 }
414 if (m.contains("tool_call_id")) {
415 x.has_tool_call_id = true;
416 if (m["tool_call_id"].is_null())
417 x.tool_call_id_null = true;
418 else if (!m["tool_call_id"].is_string())
419 throw Error("tool_call_id is a string or null");
420 else
421 x.tool_call_id = m["tool_call_id"].template get<std::string>();
422 }
423 out.push_back(std::move(x));
424 }
425 return out;
426}
std::vector< E > a

References a, c, j, k, out, and microtorch::chat::Message::role.

Referenced by ember::detail::tool_turn_message().

◆ modelfile()

std::string microtorch::chat::modelfile ( const std::string &  gguf_name,
const Template &  t 
)
inline

Definition at line 847 of file chat_template.h.

847 {
848 using detail::go;
849 detail::paperkiln_only(t, "modelfile()");
850 if (t.has_tool) return detail::modelfile_tools(gguf_name, t);
851 // No action trims whitespace, so the record's text is emitted exactly; the fold goes to the first user
852 // message (Ollama keeps a system message in .Messages as well as in .System), as in tools/chat_template.py.
853 std::string msgs = "{{ $first := true }}{{ range .Messages }}{{ if eq .Role \"user\" }}" + go(t.user_open);
854 if (t.system_mode == "fold") msgs += "{{ if and $first $.System }}{{ $.System }}" + go(t.system_join) + "{{ end }}";
855 msgs += "{{ $first = false }}{{ .Content }}" + go(t.user_close) + "{{ else if eq .Role \"assistant\" }}" +
856 go(t.assistant_open) + "{{ .Content }}" + go(t.assistant_close) + "{{ end }}{{ end }}" + go(t.generation_prompt);
857 std::string head = go(t.bos);
858 if (t.system_mode == "turn")
859 head += "{{ if .System }}" + go(t.system_open) + "{{ .System }}" + go(t.system_close) + "{{ end }}";
860 std::string s = "# Ollama Modelfile for " + gguf_name + ": chat template " + t.id() +
861 " (LOCAL_LLM_STACK.md section 3, point 3).\nFROM ./" + gguf_name + "\nTEMPLATE \"\"\"" + head +
862 msgs + "\"\"\"\n";
863 std::vector<std::string> stops = t.stop_eos;
864 stops.insert(stops.end(), t.stop_turn.begin(), t.stop_turn.end());
865 for (const auto& x : stops) s += "PARAMETER stop " + json(x).dump() + "\n";
866 return s;
867}
nlohmann::ordered_json json

References j, and modelfile().

Referenced by modelfile().

◆ parse_reply()

template<class Decode >
ParsedReply microtorch::chat::parse_reply ( const std::vector< int > &  ids,
int  call_id,
Decode  decode 
)

Definition at line 695 of file chat_template.h.

695 {
696 std::vector<std::vector<int>> parts(1);
697 for (int x : ids) {
698 if (x == call_id) parts.emplace_back();
699 else parts.back().push_back(x);
700 }
701 ParsedReply r;
702 r.content = decode(parts[0]);
703 for (size_t k = 1; k < parts.size(); ++k) {
704 const std::string text = decode(parts[k]);
705 const size_t nl = text.find('\n');
706 if (nl == std::string::npos || nl == 0) return ParsedReply{false, {}, {}};
707 ToolCall c;
708 c.has_id = true;
709 c.id = "call_" + std::to_string(k - 1);
710 c.name = text.substr(0, nl);
711 c.arguments = text.substr(nl + 1);
712 r.tool_calls.push_back(std::move(c));
713 }
714 r.parsed = true;
715 return r;
716}
std::string text

References c, microtorch::chat::ParsedReply::content, microtorch::chat::ToolCall::has_id, j, k, parse_reply(), and text.

Referenced by parse_reply(), and ember::reply_of().

◆ py_space()

bool microtorch::chat::py_space ( uint32_t  c)
inline

Definition at line 504 of file chat_template.h.

504 {
505 return (c >= 0x09 && c <= 0x0d) || (c >= 0x1c && c <= 0x20) || c == 0x85 || c == 0xa0 || c == 0x1680 ||
506 (c >= 0x2000 && c <= 0x200a) || c == 0x2028 || c == 0x2029 || c == 0x202f || c == 0x205f ||
507 c == 0x3000;
508}

References c.

Referenced by strip_py_ws().

◆ registry()

std::vector< Template > microtorch::chat::registry ( )
inline

Definition at line 337 of file chat_template.h.

337 {
338 return {v1(), v2(), legacy_word(), chatml("chatml-qwen2.5", true), chatml("chatml-qwen3", false), gemma3()};
339}
Template chatml(const std::string &name, bool qwen25)
Template legacy_word()

References chatml(), gemma3(), legacy_word(), v1(), and v2().

Referenced by get(), and resolve().

◆ render()

std::string microtorch::chat::render ( const std::vector< Message > &  m,
const Template &  t,
bool  generation_prompt,
int  thinking = -1,
const std::vector< json > &  tools = {} 
)
inline

Definition at line 663 of file chat_template.h.

664 {}) {
665 std::string s;
666 for (const auto& p : render_pieces(m, t, generation_prompt, thinking, tools)) s += p.text;
667 return s;
668}
const float * p[kIrDepthSourcesMax]

References render().

Referenced by ember::build_general_prompt(), main(), ember::ChatFormat::prompt(), and render().

◆ render_pieces()

std::vector< Piece > microtorch::chat::render_pieces ( const std::vector< Message > &  m,
const Template &  t,
bool  generation_prompt,
int  thinking = -1,
const std::vector< json > &  tools = {} 
)
inline

Definition at line 578 of file chat_template.h.

579 {}) {
580 if (!tools.empty() && !t.has_tools) throw Error("this model's template has no tool definitions");
581 const size_t first = check_structure(m, generation_prompt, t.has_tool);
582 const bool trim = t.content == "trim";
583 auto check_reserved = [&](const std::string& s) {
584 for (const auto& r : t.reserved)
585 if (!r.empty() && s.find(r) != std::string::npos)
586 throw Error("content holds the template's reserved text '" + r + "'");
587 return s;
588 };
589 auto body = [&](std::string s) {
590 if (trim) s = strip_py_ws(s);
592 return s;
593 };
594 std::vector<Piece> out;
595 auto add = [&](const std::string& text, const std::string& role, const char* kind, int idx) {
596 if (!text.empty()) out.push_back({text, role, kind, idx});
597 };
598 add(t.bos, "", "bos", -1);
599 bool have_sys = first == 1;
600 std::string sys = have_sys ? m[0].content : std::string();
601 const int sys_idx = have_sys ? 0 : -1;
602 if (!have_sys && t.has_default_system && !t.default_system.empty()) {
603 have_sys = true;
604 sys = t.default_system;
605 }
606 std::string defs;
607 for (size_t k = 0; k < tools.size(); ++k) defs += (k ? t.tools_sep : "") + check_reserved(tool_json(tools[k]));
608 if (!tools.empty()) {
609 const std::string head = have_sys ? body(sys) : std::string();
610 sys = (head.empty() ? std::string() : head + t.tools_join) + defs;
611 have_sys = true;
612 }
613 bool fold = false;
614 std::string fold_text;
615 if (have_sys) {
616 if (t.system_mode == "none") throw Error("this model's template has no system role");
617 if (t.system_mode == "turn") {
618 add(t.system_open, "system", "open", sys_idx);
619 add(tools.empty() ? body(sys) : sys, "system", "content", sys_idx);
620 add(t.system_close, "system", "close", sys_idx);
621 } else {
622 check_reserved(sys);
623 fold = true;
624 fold_text = sys + t.system_join;
625 }
626 }
627 size_t last_user = first;
628 for (size_t i = first; i < m.size(); ++i)
629 if (m[i].role == "user") last_user = i;
630 for (size_t i = first; i < m.size(); ++i) {
631 const std::string& role = m[i].role;
632 const bool user = role == "user", tool = role == "tool";
633 std::string text = body(m[i].content);
634 if (user && fold) {
635 text = fold_text + text;
636 fold = false;
637 }
638 if (role == "assistant" && t.past_assistant == "strip_think" && i < last_user) {
639 const size_t p = text.rfind("</think>");
640 if (p != std::string::npos) {
641 text = text.substr(p + 8);
642 text.erase(0, text.find_first_not_of('\n') == std::string::npos ? text.size()
643 : text.find_first_not_of('\n'));
644 }
645 }
646 const int idx = static_cast<int>(i);
647 add(user ? t.user_open : tool ? t.tool_open : t.assistant_open, role, "open", idx);
648 add(text, role, "content", idx);
649 for (const auto& c : m[i].tool_calls) {
650 add(t.tool_call_open, role, "call_open", idx);
651 add(check_reserved(c.name) + t.tool_call_join + check_reserved(c.arguments), role, "call", idx);
652 }
653 add(user ? t.user_close : tool ? t.tool_close : t.assistant_close, role, "close", idx);
654 }
655 if (generation_prompt)
656 add(thinking == 0 && t.has_nothink ? t.generation_prompt_nothink : t.generation_prompt, "", "generation", -1);
657 else if (t.past_assistant == "strip_think")
658 throw Error("a strip_think template renders prompt conversations only (HF re-wraps the reasoning of "
659 "replies after the last user message; prompts never have one)");
660 return out;
661}
Kind kind
node add
std::string tool_json(const json &d)

Referenced by ember::build_general_prompt().

◆ resolve()

Resolution microtorch::chat::resolve ( const std::string &  declared,
const std::vector< std::string > &  tokens,
const std::string &  model,
const std::string &  chat_template,
const std::string &  arch 
)
inline

Definition at line 928 of file chat_template.h.

929 {
931 r.chat = true;
932 if (!declared.empty()) {
933 r.tmpl = Template::from_json(json::parse(declared));
934 r.why = "declared";
935 return r;
936 }
937 bool v1specials = tokens.size() >= 4;
938 for (size_t i = 0; v1specials && i < 4; ++i) v1specials = tokens[i] == kV1Specials[i];
939 if (v1specials) {
940 r.tmpl = v1();
941 r.why = "paperkiln specials at ids 0-3";
942 return r;
943 }
944 if (model == "word") {
945 r.tmpl = legacy_word();
946 r.why = "word-level vocabulary";
947 return r;
948 }
949 const auto reg = registry();
950 if (!chat_template.empty()) {
951 const std::string h = detail::sha256_hex(chat_template);
952 for (const auto& t : reg)
953 for (const auto& m : t.match)
954 if (m == h) {
955 r.tmpl = t;
956 r.why = "tokenizer.chat_template sha256 " + h.substr(0, 12);
957 return r;
958 }
959 }
960 for (const auto& t : reg) {
961 if (t.markers.empty()) continue;
962 bool arch_ok = false;
963 for (const auto& a : t.architectures) arch_ok = arch_ok || a == arch;
964 bool all = arch_ok;
965 for (const auto& mk : t.markers) {
966 bool found = false;
967 for (const auto& tok : tokens) found = found || tok == mk;
968 all = all && found;
969 }
970 if (all) {
971 r.tmpl = t;
972 r.why = "markers and architecture " + arch;
973 return r;
974 }
975 }
976 r.chat = false;
977 r.why = "no declared template, no paperkiln specials, and no known tokenizer.chat_template or markers: raw "
978 "completion only";
979 return r;
980}
Tok tok

References a, microtorch::chat::Resolution::chat, j, kV1Specials, legacy_word(), registry(), resolve(), tok, and v1().

Referenced by ember::make_chat_setup(), and resolve().

◆ strip_py_ws()

std::string microtorch::chat::strip_py_ws ( const std::string &  s)
inline

Definition at line 510 of file chat_template.h.

510 {
511 // Decode the code point starting at i; returns its length (0 when invalid).
512 auto decode = [&](size_t i, uint32_t& c) -> size_t {
513 const unsigned char b = static_cast<unsigned char>(s[i]);
514 size_t n = b < 0x80 ? 1 : (b >> 5) == 0x6 ? 2 : (b >> 4) == 0xe ? 3 : (b >> 3) == 0x1e ? 4 : 0;
515 if (n == 0 || i + n > s.size()) return 0;
516 c = n == 1 ? b : b & (0xff >> (n + 1));
517 for (size_t k = 1; k < n; ++k) {
518 const unsigned char x = static_cast<unsigned char>(s[i + k]);
519 if ((x >> 6) != 0x2) return 0;
520 c = (c << 6) | (x & 0x3f);
521 }
522 return n;
523 };
524 size_t b = 0;
525 while (b < s.size()) {
526 uint32_t c = 0;
527 const size_t n = decode(b, c);
528 if (n == 0 || !py_space(c)) break;
529 b += n;
530 }
531 size_t e = s.size();
532 while (e > b) {
533 size_t st = e - 1; // back up to the lead byte of the last code point
534 while (st > b && (static_cast<unsigned char>(s[st]) >> 6) == 0x2 && e - st < 4) --st;
535 uint32_t c = 0;
536 const size_t n = decode(st, c);
537 if (n != e - st || !py_space(c)) break;
538 e = st;
539 }
540 return s.substr(b, e - b);
541}
int n
bool py_space(uint32_t c)

References c, j, k, n, and py_space().

◆ tool_json()

std::string microtorch::chat::tool_json ( const json &  d)
inline

Definition at line 570 of file chat_template.h.

570 {
571 if (!d.is_object()) throw Error("a tool definition is a JSON object");
572 return detail::dump_py(d);
573}

References microtorch::chat::detail::dump_py().

◆ v1()

Template microtorch::chat::v1 ( )
inline

Definition at line 232 of file chat_template.h.

232 {
233 Template t;
234 t.name = "paperkiln";
235 t.version = 1;
236 t.system_mode = kSystemModeV1;
237 t.system_join = "\n\n";
238 t.user_open = "<|user|>";
239 t.assistant_open = "<|assistant|>";
240 t.assistant_close = "<|endoftext|>";
241 t.generation_prompt = "<|assistant|>";
242 t.stop_eos = {"<|endoftext|>"};
243 t.stop_turn = {"<|user|>", "<|assistant|>"};
244 t.reserved = {"<|endoftext|>", "<|user|>", "<|assistant|>"};
245 return t;
246}

References j, kSystemModeV1, and microtorch::chat::Template::name.

Referenced by registry(), resolve(), ember::ChatFormat::v1_markers(), and v2().

◆ v2()

Template microtorch::chat::v2 ( )
inline

Definition at line 249 of file chat_template.h.

249 {
250 Template t = v1();
251 t.version = 2;
252 t.system_mode = "turn";
253 t.system_join.clear();
254 t.system_open = "<|system|>";
255 t.has_tool = true;
256 t.tool_open = "<|tool|>";
257 t.tool_call_open = "<|tool_call|>";
258 t.tool_call_join = "\n";
259 t.has_tools = true;
260 t.tools_join = "\n\n";
261 t.tools_sep = "\n";
262 t.stop_turn = {"<|user|>", "<|assistant|>", "<|system|>", "<|tool|>"};
263 for (const char* c : kV2Control) t.reserved.push_back(c);
264 return t;
265}

References c, j, kV2Control, and v1().

Referenced by registry().

Variable Documentation

◆ kDefaultSftTurns

constexpr const char* microtorch::chat::kDefaultSftTurns = "all"
inlineconstexpr

Definition at line 74 of file chat_template.h.

◆ kGGUFKey

constexpr const char* microtorch::chat::kGGUFKey = "paperkiln.chat_template"
inlineconstexpr

Definition at line 75 of file chat_template.h.

◆ kLegacyWord

constexpr const char* microtorch::chat::kLegacyWord = "paperkiln/legacy-word"
inlineconstexpr

Definition at line 77 of file chat_template.h.

◆ kSystemModeV1

constexpr const char* microtorch::chat::kSystemModeV1 = "fold"
inlineconstexpr

Definition at line 73 of file chat_template.h.

Referenced by legacy_word(), and v1().

◆ kV1

constexpr const char* microtorch::chat::kV1 = "paperkiln/1"
inlineconstexpr

Definition at line 76 of file chat_template.h.

◆ kV1Specials

const std::array<const char*, 4> microtorch::chat::kV1Specials = {"<unk>", "<|endoftext|>", "<|user|>", "<|assistant|>"}
inline

Definition at line 79 of file chat_template.h.

79{"<unk>", "<|endoftext|>", "<|user|>", "<|assistant|>"};

Referenced by ember::has_chat_specials(), and resolve().

◆ kV2

constexpr const char* microtorch::chat::kV2 = "paperkiln/2"
inlineconstexpr

Definition at line 78 of file chat_template.h.

◆ kV2Control

const std::array<const char*, 8> microtorch::chat::kV2Control
inline
Initial value:
= {"<|system|>", "<|tool_call|>", "<|tool|>", "<|spare_3|>",
"<|spare_4|>", "<|spare_5|>", "<|spare_6|>", "<|spare_7|>"}

Definition at line 82 of file chat_template.h.

82 {"<|system|>", "<|tool_call|>", "<|tool|>", "<|spare_3|>",
83 "<|spare_4|>", "<|spare_5|>", "<|spare_6|>", "<|spare_7|>"};

Referenced by v2().