37#include <speculative.h>
39#include <mtmd-helper.h>
41#include <nlohmann/json.hpp>
83 return text.size() >=
suffix.size()
96 const std::string& text,
100 if (!stop.empty() &&
ends_with(text, stop)) {
114 logger->info(
"Sampler: temp={:.2f}, top_k={}, top_p={:.2f}, "
115 "repeat_penalty={:.2f}, thinking={}",
128 std::chrono::steady_clock::time_point start_time)
133 if (result.token_count > 0 && result.generation_time_ms > 0.0) {
134 result.throughput_tok_s =
135 static_cast<double>(result.token_count)
136 / result.generation_time_ms * 1000.0;
141 {result.token_count, result.finish_reason,
142 result.generation_time_ms, result.throughput_tok_s, 0, 0}));
143 logger->info(
"Content:\n{}", result.content);
167 const std::string& generated,
int n_generated,
168 const GenerationParams&
params,
169 std::chrono::steady_clock::time_point
t0,
186 result.prefill_tokens = prefill_tokens;
187 if (n_generated >=
params.max_tokens
188 && result.finish_reason.empty()) {
189 result.finish_reason =
"length";
209 result.token_count = n_generated;
226 std::chrono::steady_clock::time_point
t0)
230 r.error_message =
"Sampler factory not initialized";
231 r.finish_reason =
"error";
246 static const std::pair<const char*, ggml_type>
kTable[] = {
253 for (
const auto& [name, type] :
kTable) {
254 if (
s == name) {
return type; }
256 logger->warn(
"Unknown cache_type '{}' — defaulting to f16",
s);
271 static const std::pair<const char*, llama_split_mode>
kTable[] = {
276 for (
const auto& [name, mode] :
kTable) {
277 if (
s == name) {
return mode; }
279 logger->warn(
"Unknown split_mode '{}' — defaulting to layer",
s);
356 m.n_gpu_layers =
cfg.gpu_layers;
362 m.main_gpu =
cfg.main_gpu;
365 m.tensor_buft_overrides =
moe.data();
389 const std::string key = std::string(
arch) +
".expert_count";
390 char value[32] = {0};
395 return std::atoi(
value);
425 logger->info(
"Model loaded (CPU): {} tokens in vocab, recurrent={}",
487 if (
cfg.n_ubatch > 0) {
490 c.n_threads =
cfg.n_threads > 0
493 c.flash_attn_type =
cfg.flash_attn
500 c.offload_kqv =
cfg.offload_kqv;
503 c.rope_freq_base =
cfg.rope_freq_base;
504 c.rope_freq_scale =
cfg.rope_freq_scale;
513 c.kv_unified =
pool.kv_unified;
598 last_error_ =
"Failed to load model into target residency "
601 +
") — check llama_ggml.log in the engine's log_dir "
602 "for the underlying llama.cpp/CUDA error";
608 logger->info(
"Model loaded into target residency: gpu_layers={}, "
609 "{} tokens in vocab, recurrent={}",
648 logger->info(
"[expert-offload] EXPERIMENTAL: experts of the "
649 "first {} of {} layers -> host; attention, KV, "
650 "router, dense/shared FFN and norms follow "
651 "gpu_layers={}. Confirm placement in "
652 "llama_ggml.log ('load_tensors:' buffer sizes).",
694 if (!
config().tensor_split.empty()) {
696 logger->warn(
"tensor_split not yet implemented, ignoring");
717 last_error_ =
"Failed to reload model with GPU layers "
719 +
", gpu_layers=" + std::to_string(
config().gpu_layers)
720 +
") — check llama_ggml.log in the engine's log_dir "
721 "for the underlying llama.cpp/CUDA error";
744 logger->info(
"Context created: n_ctx={}, n_batch={}, "
745 "flash_attn={}, type_k={}, type_v={}",
754 logger->info(
"Prompt cache initialized: max_bytes={}",
773 if (
config().mmproj_path.empty()) {
786 logger->error(
"mtmd_init_from_file failed for {} — "
787 "continuing in text-only mode",
788 config().mmproj_path.string());
793 logger->info(
"mmproj loaded from {} — vision={}",
869 if (
ctx_ ==
nullptr) {
870 last_error_ =
"MTP setup requires an ACTIVE target context";
876 last_error_ =
"MTP requires speculative.draft.path (the head GGUF); "
894 logger->info(
"MTP head ready: {} (n_max={}, ctx_other=target, "
976 logger->error(
"Failed to reload CPU model during deactivate "
977 "(path={}); backend left unloaded until next activate",
1051 std::unique_ptr<SamplerFactory> factory)
1140 return {
ids.begin(),
ids.end()};
1168 return static_cast<int>(
tokens.size());
1179 const std::string& text)
const {
1214 for (
int i = 0;
i < n_tokens;
i++) {
1220 throw std::runtime_error(
"llama_decode failed at logprob pos");
1222 if (
i < n_tokens - 1) {
1318 const std::vector<Message>& messages) {
1319 std::vector<llama_chat_message>
chat_msgs;
1321 for (
const auto&
msg : messages) {
1341 const std::vector<Message>& messages) {
1342 std::vector<common_chat_msg> out;
1343 out.reserve(messages.size());
1344 for (
const auto&
msg : messages) {
1347 cm.content =
msg.content;
1348 out.push_back(std::move(
cm));
1369 std::vector<common_chat_tool> out;
1371 auto arr = nlohmann::json::parse(
tools_json,
nullptr,
false);
1372 if (!arr.is_array()) {
return out; }
1373 for (
const auto&
t : arr) {
1375 ct.name =
t.value(
"name",
"");
1376 ct.description =
t.value(
"description",
"");
1377 if (
t.contains(
"inputSchema")) {
1378 ct.parameters =
t[
"inputSchema"].dump();
1380 if (!
ct.name.empty()) { out.push_back(std::move(
ct)); }
1401 tc.arguments_json =
cc.arguments;
1402 auto j = nlohmann::json::parse(
cc.arguments,
nullptr,
false);
1403 if (
j.is_object()) {
1404 for (
auto it =
j.begin();
it !=
j.end(); ++
it) {
1405 tc.arguments[
it.key()] =
1406 it->is_string() ?
it->get<std::string>() :
it->dump();
1439 const std::vector<Message>& messages,
1441 const std::vector<common_chat_tool>& tools,
1442 bool require_tool_call) {
1443 if (model ==
nullptr) {
return std::nullopt; }
1445 std::optional<common_chat_params> out;
1449 inputs.add_generation_prompt =
true;
1453 if (!tools.empty()) {
1458 inputs.tool_choice = require_tool_call
1464 }
catch (
const std::exception&
e) {
1465 logger->warn(
"jinja chat template apply failed ({}); "
1466 "falling back to low-level template",
e.what());
1480 const std::vector<Message>& messages) {
1482 for (
const auto&
msg : messages) {
1509 const std::vector<Message>& messages,
1532 const std::vector<Message>& messages,
1555 logger->info(
"Active tools staged for common_chat render: {} bytes",
1581 const std::vector<Message>& messages,
1607 logger->info(
"render_with_tools: format={}, {} tool(s), captured "
1608 "parser ({} bytes), grammar ({} bytes, lazy={})",
1618 "require_tool_call is set for this tier but the chat template "
1619 "derived NO tool-call grammar from {} staged tool(s) "
1620 "(format={}). The turn will decode unconstrained and may end "
1621 "in prose with no tool call. This template may not support "
1622 "tool_choice=REQUIRED.",
1633 if (!
params.enable_thinking) {
1635 "Falling back to the low-level chat template, which cannot "
1636 "honor enable_thinking:false — this tier WILL still emit "
1637 "reasoning, and a generation that is entirely reasoning is "
1638 "stripped to empty content. The jinja render was unavailable "
1639 "for this turn (no tools staged, or the template could not be "
1706 logger->info(
"Sequential tier: tool-call close marker injected "
1707 "post-render (gh#105) — hard-stop at first tool call");
1738 static const std::string
kOpen =
"<|channel>";
1739 static const std::string
kClose =
"<channel|>";
1743 while ((pos = content.find(
kOpen)) != std::string::npos) {
1748 (
end == std::string::npos) ? content.size() :
end +
kClose.size();
1752 (
end == std::string::npos) ? content.size() :
end;
1755 content.erase(pos,
span_end - pos);
1758 std::size_t
nb = content.find_first_not_of(
" \t\r\n");
1759 content.erase(0,
nb == std::string::npos ? content.size() :
nb);
1762 logger->warn(
"strip_thinking_channels: a <|channel> reasoning block "
1763 "was never closed, so the strip removed the whole "
1764 "generation and content is empty. Not a parse error. The "
1765 "orchestrator reports whether this was a token budget or "
1766 "the model ending its own turn — only it holds "
1767 "finish_reason. gh#137.");
1790 const std::string&
raw)
const
1810 for (
const auto&
tc :
msg.tool_calls) {
1816 }
catch (
const std::exception&
e) {
1817 logger->warn(
"common_chat_parse failed ({}); raw kept as content",
1837 const std::vector<Message>& messages)
const
1845 logger->error(
"llama_chat_apply_template failed (size query)");
1849 std::vector<char>
buf(
static_cast<size_t>(
n + 1));
1854 logger->error(
"llama_chat_apply_template failed (render)");
1858 return std::string(
buf.data(),
static_cast<size_t>(
written));
1920 logger->error(
"Prefill decode failed (slot={})",
active_slot_);
1944 std::string& generated,
1945 std::function<
void(std::string_view)>& on_token,
1946 const std::vector<std::string>& stop)
1958 on_token(std::string_view(
piece));
1977 return ok ?
"continue" :
"error";
1995 const std::vector<llama_token>&
tokens,
1997 std::function<
void(std::string_view)> on_token,
1998 std::atomic<bool>*
cancel)
2043 std::function<
void(std::string_view)> on_token,
2044 std::atomic<bool>*
cancel)
2047 std::string generated;
2048 int n_generated = 0;
2051 while (n_generated <
params.max_tokens) {
2059 auto status =
step_token(sampler, generated, on_token, stop);
2060 if (status ==
"continue") {
2063 result.
finish_reason = (status ==
"error") ?
"error" :
"stop";
2064 if (status ==
"error") {
2092 result.
content = entropic::mcp::sanitize_utf8(generated);
2107 e.error_message =
msg;
2108 e.finish_reason =
"error";
2122 b.seq_id[
k][0] =
seq;
2133 std::vector<BatchSeq>&
seqs,
2134 const std::vector<GenerationParams>&
params) {
2142 for (std::size_t
i = 0;
i <
seqs.size(); ++
i) {
2145 if (
ls ==
nullptr) {
return false; }
2152 for (std::size_t
i = 0;
i <
seqs.size() &&
i <
plan.seq_ids.size(); ++
i) {
2164 std::vector<BatchSeq>&
seqs,
const std::vector<llama_token>&
seq0,
2166 std::vector<llama_token>
prefix(
2175 for (std::size_t
i = 1;
i <
seqs.size(); ++
i) {
2179 for (
auto&
s :
seqs) {
s.pos =
static_cast<int>(
shared); }
2192 std::vector<BatchSeq>&
seqs,
2193 const std::vector<std::vector<llama_token>>&
toks,
2199 for (
const auto&
t :
toks) {
2200 total +=
static_cast<int>(
t.size() - std::min(
shared,
t.size()));
2205 for (std::size_t
i = 0;
i <
seqs.size(); ++
i) {
2206 int len =
static_cast<int>(
toks[
i].size());
2210 if (
p ==
len - 1) {
seqs[
i].logits_idx =
k; }
2228 for (
auto&
s :
seqs) {
2229 if (!
s.active) {
continue; }
2239 s.out.push_back(
tok);
2241 if (
s.n_gen >=
s.max_tokens) {
s.active =
false;
s.finish =
"length"; }
2260 if (
cancel.load(std::memory_order_acquire)) {
break; }
2263 for (
auto&
s :
seqs) {
2264 if (!
s.active) {
continue; }
2270 if (
k == 0) {
break; }
2284 std::vector<BatchSeq>&
seqs) {
2285 std::vector<GenerationResult> out;
2286 out.reserve(
seqs.size());
2287 for (
auto&
s :
seqs) {
2290 r.token_count =
s.n_gen;
2291 r.finish_reason =
s.finish;
2292 out.push_back(std::move(
r));
2307 for (
auto&
s :
seqs) {
2308 if (
s.seq_id == 0) {
continue; }
2326 const std::vector<std::vector<llama_token>>&
toks,
2327 const std::vector<GenerationParams>&
params,
2329 std::atomic<bool>&
cancel)
2331 const std::size_t
n =
toks.size();
2332 std::vector<BatchSeq>
seqs(
n);
2335 return std::vector<GenerationResult>(
2355 : std::vector<GenerationResult>(
2358 logger->info(
"gh#98 batch: requests={} prefix.tokens_shared={} "
2359 "prefix.tokens_saved={} total_prefill_tokens={} gen_decodes={}",
2383 const std::vector<std::vector<Message>>&
requests,
2384 const std::vector<GenerationParams>&
params,
2385 std::atomic<bool>&
cancel)
2388 std::vector<std::vector<llama_token>>
toks(
n);
2389 for (std::size_t
i = 0;
i <
n; ++
i) {
2414 const std::vector<Message>& messages)
2416 for (
const auto&
msg : messages) {
2417 if (
msg.role ==
"system") {
2452 const int total =
static_cast<int>(
tokens.size());
2471 tokens[
static_cast<std::size_t
>(
2483 logger->error(
"Decode chunk failed (slot={}, start={}, off={}, "
2531 const std::vector<llama_token>&
tokens)
2551 logger->warn(
"KV state restore failed, falling back to full prefill");
2596 const std::vector<Message>& messages,
2600 for (
const auto&
msg : messages) {
2601 if (
msg.role ==
"system") {
2636 const std::vector<llama_token>&
tokens,
2648 std::vector<llama_token>
prefix(
2671 auto arr = nlohmann::json::parse(
tools_json,
nullptr,
false);
2672 return arr.is_array() ?
static_cast<int>(arr.size()) : 0;
2692 const std::vector<llama_token>&
tokens,
2693 const std::string& system_prompt)
2707 static_cast<int>(
tokenize(system_prompt,
false).size());
2729 r.error_message =
"Prefill decode failed";
2755 const std::vector<llama_token>&
tokens,
2756 const std::string& system_prompt,
2757 const std::vector<Message>& messages,
2783 if (
ctx_ ==
nullptr) {
2784 logger->error(
"Prefill requested with no context (not ACTIVE)");
2787 auto t_pre = entropic::log::now();
2814 logger->info(
"Prefill (gh#96): {} tokens / {:.1f} ms decoded this turn",
2860 long pos_max =
static_cast<long>(
2876 logger->info(
"Warm-keep: reused {} resident tokens, decoded {} "
2914 if (
ctx_ !=
nullptr) {
2918 logger->info(
"Session '{}' KV dropped from slot {} (context restored)",
2953 const std::vector<llama_token>&
tokens,
2954 const std::string& system_prompt,
2955 const std::vector<Message>& messages,
2960 && !system_prompt.empty();
2967 system_prompt,
config().path.string());
2972 logger->info(
"Prompt cache HIT: {} bytes, {} prefix tokens",
2978 logger->warn(
"Cache restore failed, falling back to full prefill");
2980 logger->info(
"Prompt cache MISS: processing full prompt");
2996bool any_image_in(
const std::vector<Message>& messages) {
2997 for (
const auto&
m : messages) {
3016 const std::vector<Message>& messages) {
3017 std::vector<Message> out = messages;
3018 for (
auto&
m : out) {
3019 if (
m.content_parts.empty()) {
continue; }
3021 m.content_parts.clear();
3044 const std::vector<Message>& messages,
3047 std::vector<Message> out;
3048 out.reserve(messages.size());
3050 for (
const auto&
m : messages) {
3053 if (
m.content_parts.empty()) {
3054 copy.content =
m.content;
3055 out.push_back(std::move(
copy));
3059 for (
const auto&
p :
m.content_parts) {
3065 if (!
p.image_path.empty()) {
3072 ctx,
p.image_path.c_str(),
false,
3075 if (
bm ==
nullptr) {
return {}; }
3080 out.push_back(std::move(
copy));
3098 const std::string& prompt,
3099 const std::vector<::mtmd_bitmap*>&
bitmaps,
3106 std::vector<const ::mtmd_bitmap*>
bm_cptrs(
3112 err_msg =
"mtmd_tokenize failed (rc="
3113 + std::to_string(
tok_rc) +
")";
3123 err_msg =
"mtmd_helper_eval_chunks failed (rc="
3124 + std::to_string(
eval_rc) +
")";
3127 logger->info(
"Multimodal prefill complete: n_past={}",
new_n_past);
3149 std::function<
void(std::string_view token)> on_token,
3150 std::atomic<bool>*
cancel,
3151 const std::chrono::steady_clock::time_point&
t0)
3163 std::string generated;
3164 int n_generated = 0;
3166 while (n_generated <
params.max_tokens) {
3168 &&
cancel->load(std::memory_order_acquire)) {
3174 *sampler, generated, on_token, stop);
3175 if (status ==
"continue") { ++n_generated;
continue; }
3176 result.
finish_reason = (status ==
"error") ?
"error" :
"stop";
3177 if (status ==
"error") {
3203 const std::vector<Message>& messages,
3205 std::function<
void(std::string_view token)> on_token,
3206 std::atomic<bool>*
cancel)
3208 auto t0 = entropic::log::now();
3210 std::vector<::mtmd_bitmap*>
bitmaps;
3218 "mtmd_helper_bitmap_init_from_file failed";
3222 logger->info(
"Multimodal generate: {} images, prompt={} chars, max_tokens={}",
3272 logger->info(
"Session pool: evicted '{}' from slot {} for '{}'",
3293 const std::vector<Message>& messages,
3305 logger->warn(
"Image content present but model has no vision "
3306 "capability — stripping image parts");
3316 const std::vector<Message>& messages,
3319 auto t0 = entropic::log::now();
3324 logger->info(
"Generate: {} input tokens, max_tokens={}",
3337 std::string generated;
3338 int n_generated = 0;
3339 std::function<
void(std::string_view)>
no_cb =
nullptr;
3342 while (n_generated <
params.max_tokens) {
3344 *sampler, generated,
no_cb, stop);
3345 if (status ==
"continue") { ++n_generated; }
3348 (status ==
"error") ?
"error" :
"stop";
3349 if (status ==
"error") {
3372 const std::vector<Message>& messages,
3374 std::atomic<bool>&
cancel)
3382 logger->warn(
"Image content present but model has no vision "
3383 "capability — stripping image parts");
3407 const std::vector<Message>& messages,
3409 std::atomic<bool>&
cancel)
3411 auto t0 = entropic::log::now();
3416 logger->info(
"Generate (cancellable): {} input tokens, max_tokens={}",
3428 std::string generated;
3429 int n_generated = 0;
3430 std::function<
void(std::string_view)>
no_cb =
nullptr;
3433 while (n_generated <
params.max_tokens) {
3434 if (
cancel.load(std::memory_order_acquire)) {
3440 *sampler, generated,
no_cb, stop);
3441 if (status ==
"continue") { ++n_generated; }
3444 (status ==
"error") ?
"error" :
"stop";
3445 if (status ==
"error") {
3468 const std::vector<Message>& messages,
3470 std::function<
void(std::string_view token)> on_token,
3471 std::atomic<bool>&
cancel)
3480 logger->warn(
"Image content present but model has no vision "
3481 "capability — stripping image parts");
3498 const std::vector<Message>& messages,
3500 std::function<
void(std::string_view token)> on_token,
3501 std::atomic<bool>&
cancel)
3503 auto t0 = entropic::log::now();
3507 logger->info(
"Stream: {} input tokens, max_tokens={}",
3518 std::string generated;
3519 int n_generated = 0;
3521 while (n_generated <
params.max_tokens) {
3522 if (
cancel.load(std::memory_order_acquire)) {
3528 *sampler, generated, on_token, stop);
3529 if (status ==
"continue") { ++n_generated; }
3532 (status ==
"error") ?
"error" :
"stop";
3533 if (status ==
"error") {
3557 const std::vector<Message>& ,
3559 std::function<
void(std::string_view)> ,
3560 std::atomic<bool>& )
3565 "LlamaCppBackend speculative requires an explicit draft "
3566 "backend handle — orchestrator dispatches via "
3567 "generate_speculative_with_draft";
3607 "Both a request grammar and a tool-call grammar are active. These "
3608 "constrain decoding to different languages and cannot compose; the "
3609 "request grammar is being used and the staged tools will NOT be "
3610 "structurally enforced. Drop one: clear params.grammar to let the "
3611 "tool schemas constrain, or unstage tools to use your grammar.");
3643 "Tool-call grammar applied: {} bytes, lazy={}, prefill={} bytes",
3651 "Tool-call grammar ({} bytes) NOT applied — an explicit request "
3652 "grammar takes precedence. Tool calls are not structurally "
3653 "enforced this turn.",
3685 const GenerationParams&
params,
3695 cps.penalty_repeat =
params.repeat_penalty;
3701 cps.penalty_freq =
params.frequency_penalty;
3702 cps.penalty_present =
params.presence_penalty;
3727 if (
params.min_p > 0.0f) {
3730 if (
params.temperature > 0.0f) {
3734 cps.dry_multiplier = 0.0f;
3735 cps.top_n_sigma = -1.0f;
3758 if (
total <= 0) {
return true; }
3786 logger->error(
"Speculative decode failed ({}): {}",
3789 r.error_code =
code;
3790 r.error_message = std::move(
msg);
3791 r.finish_reason =
"error";
3810 bool batch_initialized =
false;
3814 std::vector<llama_token> prompt_tgt;
3815 std::vector<llama_token> draft;
3816 std::string generated;
3818 int n_generated = 0;
3827 bool has_eos =
false;
3828 std::string finish_reason;
3830 std::string error_message;
3839 bool use_ckpt_tgt =
false;
3840 bool use_ckpt_dft =
false;
3853 if (state.batch_initialized) {
3867 state.n_past, {state.seq_id},
true);
3868 int pos = state.n_past + 1;
3871 {state.seq_id},
true);
3887 logger->error(
"Speculative target decode failed: rc={}, "
3888 "n_past={}, draft_size={}",
3889 rc_tgt, state.n_past, state.draft.size());
3891 state.error_message =
"target llama_decode failed";
3892 state.finish_reason =
"error";
3897 logger->error(
"Speculative draft decode failed: rc={}, "
3898 "n_past={}, draft_size={}",
3899 rc_dft, state.n_past, state.draft.size());
3901 state.error_message =
"draft llama_decode failed";
3902 state.finish_reason =
"error";
3916 state.spec, state.seq_id);
3919 dp.pos0 = state.n_past;
3920 dp.id_last = state.id_last;
3921 dp.prompt = &state.prompt_tgt;
3922 dp.result = &state.draft;
3924 return static_cast<int>(state.draft.size());
3943 std::function<
void(std::string_view)>& on_token,
3944 std::atomic<bool>&
cancel)
3947 state.prompt_tgt.push_back(state.id_last);
3949 state.n_generated++;
3951 state.has_eos =
true;
3952 state.finish_reason =
"stop";
3955 const std::string
piece =
3957 state.generated +=
piece;
3958 if (on_token) { on_token(
piece); }
3964 state.finish_reason =
"stop";
3966 }
else if (
cancel.load(std::memory_order_acquire)) {
3968 state.finish_reason =
"cancelled";
3970 }
else if (state.n_generated >= max_tokens) {
3971 state.finish_reason =
"length";
3990 state.ckpt.update_pos(
3991 static_cast<int64_t>(state.prompt_tgt.size()),
3996 if (state.use_ckpt_dft) {
3997 state.ckpt.update_dft(state.ctx_dft, state.seq_id,
4010 if (state.use_ckpt_tgt && !state.draft.empty()) {
4011 state.ckpt.update_tgt(state.ctx_tgt, state.seq_id,
4026 if (state.use_ckpt_dft) {
4027 state.ckpt.load_dft(state.ctx_dft, state.seq_id,
flags);
4030 state.seq_id, state.ckpt.pos_max + 1, -1);
4044 std::vector<llama_token>&
ids) {
4047 state.draft = std::move(
ids);
4048 state.ckpt.load_tgt(state.ctx_tgt, state.seq_id,
flags);
4050 state.seq_id, state.ckpt.pos_max + 1, -1);
4051 state.ckpt.load_dft(state.ctx_dft, state.seq_id,
flags);
4053 state.seq_id, state.ckpt.pos_max + 1, -1);
4054 state.prompt_tgt.resize(
static_cast<size_t>(state.ckpt.n_tokens));
4055 state.n_past =
static_cast<int>(state.prompt_tgt.size());
4087 state.seq_id, state.n_past, -1);
4089 state.seq_id, state.n_past, -1);
4100 const std::vector<llama_token>&
ids,
4102 std::function<
void(std::string_view)>& on_token,
4103 std::atomic<bool>&
cancel) {
4105 for (
auto id :
ids) {
4108 if (!
signal.empty()) { stop =
true;
break; }
4136 if (!state.draft.empty()) {
4137 return static_cast<int>(state.draft.size());
4155 std::function<
void(std::string_view)>& on_token,
4156 std::atomic<bool>&
cancel)
4163 if (state.use_ckpt_tgt) {
4167 state.smpl, state.ctx_tgt, state.draft);
4173 if (state.use_ckpt_tgt
4174 &&
static_cast<int>(
ids.size()) - 1
4175 <
static_cast<int>(state.draft.size())) {
4189 state.n_past +=
static_cast<int>(
ids.size());
4193 state.draft.clear();
4222 logger->info(
"Speculative seq_rm capability: target={}, draft={} "
4225 err =
"speculative requires ACTIVE target + draft";
4228 err =
"speculative refused: architecture (target is "
4229 "recurrent or hybrid; see proposal Implementation "
4235 err =
"speculative kernel requires at least FULL seq_rm "
4236 "(target/draft reported NO seq_rm at all)";
4280 if (!state.smpl) {
return "common_sampler_init failed"; }
4294 state.smpl =
nullptr;
4295 return "common_speculative_init failed";
4300 state.batch_initialized =
true;
4318 const std::vector<llama_token>&
tokens,
4323 state.id_last =
tokens.back();
4324 state.prompt_tgt.assign(
tokens.begin(),
tokens.end() - 1);
4325 state.n_past =
static_cast<int>(
tokens.size()) - 1;
4332 return "speculative prefill failed";
4347 std::function<
void(std::string_view)>& on_token,
4348 std::atomic<bool>&
cancel) {
4349 while (state.n_generated < max_tokens) {
4350 if (
cancel.load(std::memory_order_acquire)) {
4352 state.finish_reason =
"cancelled";
4360 if (state.finish_reason.empty()) {
4361 state.finish_reason = (state.n_generated >= max_tokens)
4362 ?
"length" :
"stop";
4379 std::chrono::steady_clock::time_point
t0) {
4398 result.
content = entropic::mcp::sanitize_utf8(state.generated);
4412 entropic::log::elapsed_ms(
t0, entropic::log::now());
4430 state.n_drafted, state.n_accepted}));
4434 if (state.n_drafted > 0) {
4436 static_cast<float>(state.n_accepted)
4437 /
static_cast<float>(state.n_drafted);
4438 logger->info(
"Speculative: generated={}, drafted={}, "
4439 "accepted={}, accept_rate={:.3f}",
4440 state.n_generated, state.n_drafted,
4490 std::function<
void(std::string_view)>& on_token,
4493 std::chrono::steady_clock::time_point
t0,
4497 state.ctx_tgt = ctx_tgt;
4498 state.ctx_dft = ctx_dft;
4519 const std::vector<Message>& messages,
4521 std::function<
void(std::string_view)> on_token,
4522 std::atomic<bool>&
cancel,
4527 auto t0 = entropic::log::now();
4534 (
pre_err.find(
"requires ACTIVE") != std::string::npos)
4543 "speculative prompt must have at least 2 tokens");
4545 logger->info(
"Speculative: {} input tokens, max_tokens={}, "
4586 dp.pos0 = state.n_past;
4587 dp.id_last = state.id_last;
4588 dp.prompt = &state.prompt_tgt;
4589 dp.result = &state.draft;
4591 return static_cast<int>(state.draft.size());
4607 if (
llama_decode(state.ctx_tgt, state.batch_tgt) != 0) {
4609 state.error_message =
"MTP target decode failed";
4610 state.finish_reason =
"error";
4615 state.error_message =
"common_speculative_process failed";
4616 state.finish_reason =
"error";
4630 int max_tokens, std::function<
void(std::string_view)>& on_token,
4631 std::atomic<bool>&
cancel) {
4635 state.smpl, state.ctx_tgt, state.draft);
4650 state.n_past +=
static_cast<int>(
ids.size());
4653 state.draft.clear();
4670 off +
j, {state.seq_id},
false);
4672 if (
llama_decode(state.ctx_tgt, state.batch_tgt) != 0) {
return false; }
4684 int total =
static_cast<int>(state.prompt_tgt.size());
4685 if (
total == 0) {
return true; }
4695 state.n_prefilled +=
chunk;
4719 if (!state.smpl) {
return "common_sampler_init failed"; }
4723 sp.draft.ctx_tgt = state.ctx_tgt;
4724 sp.draft.ctx_dft = state.ctx_dft;
4728 state.smpl =
nullptr;
4729 return "common_speculative_init (MTP) failed";
4732 state.batch_initialized =
true;
4747 const std::vector<llama_token>&
tokens,
4751 state.id_last =
tokens.back();
4752 state.prompt_tgt.assign(
tokens.begin(),
tokens.end() - 1);
4753 state.n_past =
static_cast<int>(
tokens.size()) - 1;
4778 if (!
err.empty()) {
return err; }
4780 return "MTP prefill/process failed";
4793 int max_tokens, std::function<
void(std::string_view)>& on_token,
4794 std::atomic<bool>&
cancel) {
4795 while (state.n_generated < max_tokens) {
4796 if (
cancel.load(std::memory_order_acquire)) {
4798 state.finish_reason =
"cancelled";
4806 if (state.finish_reason.empty()) {
4807 state.finish_reason = (state.n_generated >= max_tokens)
4808 ?
"length" :
"stop";
4819 const std::vector<llama_token>&
tokens,
const GenerationParams&
params,
4820 std::function<
void(std::string_view)>& on_token,
4822 const std::vector<std::string>& stop,
4823 std::chrono::steady_clock::time_point
t0,
4826 SpeculativeRunState state;
4827 state.ctx_tgt = ctx_tgt;
4828 state.ctx_dft = ctx_dft;
4874 const std::function<
void(std::string_view)>& on_token,
4879 static_cast<bool>(on_token));
4882 "MTP requires an ACTIVE target");
4883 }
else if (!reason.empty()) {
4899 "speculative.n_draft+1 ("
4902 +
"); reduce n_draft or raise n_batch");
4928 const std::vector<llama_token>&
tokens)
const {
4933 const long pos_max =
static_cast<long>(
4937 return static_cast<int>(
4962 const std::vector<Message>& messages,
4964 std::function<
void(std::string_view)> on_token,
4965 std::atomic<bool>&
cancel,
4969 auto t0 = entropic::log::now();
4979 "MTP prompt must have at least 2 tokens");
4986 logger->info(
"MTP: {} input tokens, reusing {}, max_tokens={}, n_max={}",
5017 const std::string& prompt,
5020 auto t0 = entropic::log::now();
5024 logger->info(
"Complete: {} input tokens, max_tokens={}",
5062 int idx =
static_cast<int>(
cap);
5071 static constexpr bool always[] = {
5072 false,
false,
true,
true,
true,
true,
5073 false,
true,
true,
false,
false,
true,
5112#if defined(ENTROPIC_BACKEND_CUDA)
5113 bi.compute_device =
"cuda";
5114#elif defined(ENTROPIC_BACKEND_VULKAN)
5115 bi.compute_device =
"vulkan";
5117 bi.compute_device =
"cpu";
5119 bi.model_format =
"gguf";
5123 bi.max_context_length =
5129 char desc[256] = {};
5144 if (
ctx_ ==
nullptr) {
5173 int seq_id, std::vector<uint8_t>&
buffer)
const {
5174 if (
ctx_ ==
nullptr) {
return false; }
5177 if (
sz == 0) {
return false; }
5200 int seq_id,
const std::vector<uint8_t>&
buffer) {
5201 if (
ctx_ ==
nullptr ||
buffer.empty()) {
return false; }
ChatAdapter concrete base class.
bool cancelled
The run was interrupted and the group was killed (gh#172)
gh#158: which sequences a same-prefix batch may touch, purely.
gh#98 (v2.8.0) same-prefix batch-generation decision logic.
Owning, null-terminated tensor-buffer override list for expert offload.
virtual std::vector< GenerationResult > do_generate_batch(const std::vector< std::vector< Message > > &requests, const std::vector< GenerationParams > ¶ms, std::atomic< bool > &cancel)
Subclass same-prefix batch generation (gh#98, v2.8.0).
std::string last_error_
Last error message for diagnostics.
bool is_active() const
True when state is ACTIVE.
ModelState state() const
Current lifecycle state (lock-free read).
const ModelConfig & config() const
Stored model config.
int context_length() const
Model's context window size.
std::atomic< ModelState > state_
State transition slot accessible to subclasses for test-only injection.
LlamaCppBackend — common llama.cpp patterns (15% layer).
bool parse_params_valid_
True once a tooled render snapshotted.
int last_gen_decode_calls_
gh#98: batched-decode step count of last batch
bool load_gpu_model()
Load the GGUF model onto the GPU (do_activate step 1).
bool do_load(const ModelConfig &config) override
Load model into CPU RAM (COLD → WARM).
bool do_supports(BackendCapability cap) const override
Declare llama.cpp backend capabilities.
std::vector< GenerationResult > build_batch_results(std::vector< BatchSeq > &seqs)
Detokenize each sequence into a GenerationResult.
std::vector< std::string > effective_stop(const GenerationParams ¶ms) const
params.stop + the sequential tool-call close marker, if applicable.
SessionResidency< llama_token > residency_
gh#144 (v2.12.0): per-sequence warm-keep residency.
int kv_full_clear_count_
gh#158: whole-KV-cache clears since activation.
double last_prefill_ms_
gh#96: prefill wall-clock ms of last generate()
int last_input_tokens_
gh#97: tokenized prompt size of last generate()
GenerationResult decode_loop(const std::vector< llama_token > &tokens, const GenerationParams ¶ms, std::function< void(std::string_view)> on_token, std::atomic< bool > *cancel)
Core decode loop — shared by generate and streaming.
bool is_recurrent_
True if loaded model is recurrent (GDN/Mamba/RWKV).
static std::uint64_t model_file_loads()
Whole-file loads of a tier model in this process (gh#148).
bool try_warm_reuse(const std::vector< llama_token > &tokens)
gh#96 (v2.7.5): try incremental prefill against resident KV.
LogprobResult do_evaluate_logprobs(const int32_t *tokens, int n_tokens) override
Evaluate per-token log-probabilities via sequential decode.
std::string do_backend_name() const override
Return backend name.
bool is_hybrid_
gh#97: attention + recurrent/SSM memory
bool refuse_over_context(const std::vector< llama_token > &tokens, const std::string &system_prompt)
Refuse this turn when its prompt cannot fit the tier context.
bool do_save_state(int seq_id, std::vector< uint8_t > &buffer) const override
Capture a sequence's KV cache into a byte buffer.
std::string render_prompt(const std::vector< Message > &messages, const GenerationParams ¶ms)
Generation render seam: common_chat-with-tools or legacy (gh#87).
std::unique_ptr< PromptCache > prompt_cache_
KV prefix cache (v1.8.3)
void teardown_mtp_draft()
Free the MTP head context + model (gh#106 lifecycle).
int active_slot_
Sequence slot the current generation is bound to (gh#144).
std::string parse_generation_prompt_
Last TOOLED render's gen prompt.
std::vector< GenerationResult > run_batched_decode(const std::vector< std::vector< llama_token > > &toks, const std::vector< GenerationParams > ¶ms, std::size_t shared, std::atomic< bool > &cancel)
Run the gh#98 multi-seq batched decode (v2.8.0).
GenerationResult do_generate(const std::vector< Message > &messages, const GenerationParams ¶ms) override
Generate a complete response using chat template.
void reload_model_cpu_only()
Reload the model CPU-only for the WARM state (do_deactivate tail).
bool finish_activation()
Context + sampler + mmproj, shared by both activation paths.
std::string render_with_tools(const std::vector< Message > &messages, const GenerationParams ¶ms)
Render messages through common_chat WITH the active tools.
void sample_batch_active(std::vector< BatchSeq > &seqs)
Sample+accept+classify each still-active sequence.
bool common_chat_parse_reliable() const
True iff common_chat parsing is reliable for the last render (gh#87).
std::string active_tools_json_
MCP tool defs for next render.
GenerationResult do_complete(const std::string &prompt, const GenerationParams ¶ms) override
Raw text completion without chat template.
int last_prefill_tokens_
gh#96: prompt tokens decoded by last generate()
std::vector< llama_token > tokenize(const std::string &text, bool add_special) const
Tokenize text using model vocabulary.
bool create_inference_context()
Create the llama context + prompt cache (do_activate step 2).
const llama_vocab * vocab_
Vocabulary (from model_)
std::string tool_call_close_marker() const override
Tool-call close marker for the captured chat format (gh#103).
void invalidate_all_resident_kv()
Drop EVERY slot's warm-keep record (gh#144, v2.12.0).
bool have_chat_params_
True once a tool render captured params.
int compute_prefix_token_count(const std::vector< Message > &messages, const GenerationParams ¶ms)
Compute token count of system messages only.
static void reset_model_file_loads()
Reset the whole-file load counter (gh#148, test surface).
std::unique_ptr< SamplerFactory > sampler_factory_
Factory used by the decode loop to build per-generation samplers.
void release_temp_seqs(std::vector< BatchSeq > &seqs)
Release every batch sequence's temp seq_id (seq 0 excluded).
std::string detokenize(llama_token token) const
Detokenize a single token.
void bind_model_handles()
Bind vocab, tokenizer and architecture flags to model_.
void set_active_tools(const std::string &tools_json)
Stage tool definitions for the next common_chat render (gh#87).
void init_mmproj_if_configured()
Initialize the libmtmd context if mmproj is configured.
int last_chat_format_
Captured common_chat_format.
GenerationResult generate_speculative_with_draft(const std::vector< Message > &messages, const GenerationParams ¶ms, std::function< void(std::string_view token)> on_token, std::atomic< bool > &cancel, LlamaCppBackend &draft, int n_draft_max, const std::string &draft_path)
Speculative-decoding kernel with explicit draft backend.
llama_context * ctx_
Inference context (ACTIVE)
bool run_prefill(const std::vector< llama_token > &tokens)
Run batched prefill on input tokens.
GenerationResult run_sampling_loop(const GenerationParams ¶ms, std::function< void(std::string_view token)> on_token, std::atomic< bool > *cancel, const std::chrono::steady_clock::time_point &t0)
Sample tokens until stop / max_tokens / cancel.
llama_seq_id next_temp_seq_id_
gh#98: monotonic high-water for NEW temp seq_ids (the old 1 + size() handed out duplicates when the p...
void bind_session_slot(const GenerationParams ¶ms)
Bind this generation to its session's sequence slot (gh#144).
std::string last_generation_prompt_
Captured generation_prompt.
GenerationResult mtp_guard(const GenerationParams ¶ms, const std::function< void(std::string_view)> &on_token, const std::string &head_path, int n_max)
Validate MTP run preconditions (gh#108, fail-fast/fail-loud).
bool restore_cached_prefix(const CacheEntry *cached, const std::vector< llama_token > &tokens)
Restore KV state from cache and decode remaining tokens.
void save_prefix_to_cache(const CacheKey &key, int prefix_tokens)
Capture seq 0 KV state and store under the given key.
std::vector< int32_t > tokenize_text(const std::string &text) const override
Tokenize text to token IDs using model vocabulary.
int mtp_n_max_
MTP draft window (n_max) of the live head.
bool is_recurrent() const
Check if loaded model is recurrent.
std::string step_token(Sampler &sampler, std::string &generated, std::function< void(std::string_view)> &on_token, const std::vector< std::string > &stop)
Generate one token and append to output.
GenerationResult generate_after_prefill(Sampler &sampler, const GenerationParams ¶ms, std::function< void(std::string_view)> on_token, std::atomic< bool > *cancel)
The post-prefill sampling loop (extracted from decode_loop).
entropic_error_t mtmd_prefill(const std::string &prompt, const std::vector<::mtmd_bitmap * > &bitmaps, std::string &err_msg)
Run mtmd_tokenize + mtmd_helper_eval_chunks on a prompt.
void run_batch_gen_loop(std::vector< BatchSeq > &seqs, int max_steps, std::atomic< bool > &cancel)
Decode all sequences together until each finishes.
bool run_prefill_cached(const std::vector< llama_token > &tokens, const std::string &system_prompt, const std::vector< Message > &messages, const GenerationParams ¶ms)
Run prefill with prompt cache integration.
int mtp_reuse_cut(const std::vector< llama_token > &tokens) const
How much of an MTP prompt is already resident (gh#144).
std::string mtp_head_path_
Path the live mtp_draft_ctx_ was built from.
GenerationResult do_generate_text_only(const std::vector< Message > &messages, const GenerationParams ¶ms)
Text-only batch generation (extracted from do_generate).
bool do_restore_state(int seq_id, const std::vector< uint8_t > &buffer) override
Restore a sequence's KV cache from a byte buffer.
std::string apply_chat_template(const std::vector< Message > &messages, const GenerationParams ¶ms) const
Apply chat template to messages.
CommonChatResult parse_response(const std::string &raw) const
Parse a raw model emission via the last captured render params.
bool expert_offload_admits(const ModelConfig &config)
Refuse a loaded model that cannot honour cpu_moe_layers.
bool prefill_batch_suffixes(std::vector< BatchSeq > &seqs, const std::vector< std::vector< llama_token > > &toks, std::size_t shared)
Prefill each request's suffix; set per-seq logits_idx.
std::unique_ptr< Tokenizer > tokenizer_
Tokenizer used by tokenize_text / do_count_tokens / internal tokenize/detokenize.
GenerationResult do_generate_streaming(const std::vector< Message > &messages, const GenerationParams ¶ms, std::function< void(std::string_view token)> on_token, std::atomic< bool > &cancel) override
Streaming generation with per-token callback.
bool has_vision_
Cached mtmd_support_vision(mtmd_ctx_) result.
GenerationResult prefill_error() const
Build the error result for a prefill that did not run.
void inject_tokenizer_for_test(std::unique_ptr< Tokenizer > tokenizer)
Inject a tokenizer for unit testing (v2.3.10).
bool decode_tokens_from(const std::vector< llama_token > &tokens, int start_offset)
Decode tokens starting at a given offset.
bool prefill_dispatch(const std::vector< llama_token > &tokens, const std::string &system_prompt, const std::vector< Message > &messages, const GenerationParams ¶ms)
Cache-aware prefill dispatch (gh#96 v2.7.5: extracted body of run_prefill_cached so the wrapper owns ...
void release_temp_seq_id(llama_seq_id seq_id)
Release a temporary sequence ID back to the pool.
std::unique_ptr< Sampler > create_sampler(const GenerationParams ¶ms) const
Build a Sampler for one generation from params.
int do_count_tokens(const std::string &text) const override
Count tokens in text.
::mtmd_context * mtmd_ctx_
libmtmd context, or nullptr if no mmproj loaded.
bool require_tool_call_
gh#134 (v2.10.4): when true the render asks llama.cpp for COMMON_CHAT_TOOL_CHOICE_REQUIRED,...
std::string tool_grammar_
Render-derived tool-call GBNF.
std::string last_parser_
Captured serialized PEG arena.
void forget_session_kv(const std::string &session_key) override
Drop one session's resident KV and release its slot (gh#165).
GenerationResult generate_multimodal(const std::vector< Message > &messages, const GenerationParams ¶ms, std::function< void(std::string_view token)> on_token, std::atomic< bool > *cancel)
Multimodal generation core (v1.9.11 Phases 5–7).
std::mutex seq_id_mutex_
Guards temp seq_id pool (v1.9.10)
bool do_clear_state(int seq_id) override
Clear KV cache or recurrent hidden state.
bool prefill_shared_and_fanout(std::vector< BatchSeq > &seqs, const std::vector< llama_token > &seq0, std::size_t shared)
Prefill shared prefix into seq 0 + seq_cp fan-out.
std::string apply_chat_template_lowlevel(const std::vector< Message > &messages) const
Low-level GGUF template path (gh#86 fallback, v2.6.1).
static float extract_token_logprob(const float *logits, int32_t next_token, int n_vocab)
Extract log-probability for a token from logits.
void do_deactivate() override
Deactivate: free context, reload model CPU-only.
GenerationResult generate_mtp(const std::vector< Message > &messages, const GenerationParams ¶ms, std::function< void(std::string_view token)> on_token, std::atomic< bool > &cancel, const std::string &head_path, int n_max)
Speculative generation via a target-owned MTP head (gh#106).
bool tool_grammar_lazy_
Arm on trigger vs bind eagerly.
BackendInfo do_info() const override
Populate backend metadata from llama.cpp model.
std::vector< GenerationResult > do_generate_batch(const std::vector< std::vector< Message > > &requests, const std::vector< GenerationParams > ¶ms, std::atomic< bool > &cancel) override
Same-prefix batch generation (gh#98, v2.8.0).
std::string parse_parser_
Last TOOLED render's PEG arena.
bool do_activate() override
Activate model on GPU (WARM → ACTIVE).
bool prepare_batch_seqs(std::vector< BatchSeq > &seqs, const std::vector< GenerationParams > ¶ms)
Build per-request sampler chains + seq ids.
bool build_mtp_head(const std::string &head_path)
Load the MTP head GGUF + create its shared-KV context (gh#106).
bool prefill_and_cache_prefix(const std::vector< llama_token > &tokens, int prefix_tokens, const CacheKey &key)
Two-pass prefill: prefix-only prefill → save → rest.
std::mutex mtp_mutex_
gh#108: serialises MTP head setup/teardown vs in-flight generate_mtp (no deactivate-during-generate U...
llama_seq_id allocate_temp_seq_id()
Allocate a temporary sequence ID for evaluation.
bool decode_tokens_into_slot(const std::vector< llama_token > &tokens, int start_offset, int slot)
Decode a token run into an explicit sequence slot (gh#144).
PromptCacheConfig prompt_cache_config_
Cache config (v1.8.3)
int parse_chat_format_
Last TOOLED render's format.
llama_model * mtp_draft_model_
MTP head GGUF (separate, trunk-sharing)
void do_unload() override
Full unload — free all resources, clear prompt cache.
llama_model * model_
Loaded model (WARM+)
std::string prefill_refusal_
v2.13.0: why the last prefill refused to run ("" = it ran).
~LlamaCppBackend() override
Free llama.cpp + mtmd resources on destruction.
void invalidate_resident_kv()
gh#96 (v2.7.5): drop the warm-keep resident-KV record.
std::vector< llama_seq_id > free_seq_ids_
Available temporary seq_ids (v1.9.10)
GenerationResult do_generate_speculative(const std::vector< Message > &messages, const GenerationParams ¶ms, std::function< void(std::string_view token)> on_token, std::atomic< bool > &cancel) override
Speculative streaming via the abstract InferenceBackend interface (kept as NOT_SUPPORTED — see kernel...
GenerationResult do_generate_streaming_text_only(const std::vector< Message > &messages, const GenerationParams ¶ms, std::function< void(std::string_view token)> on_token, std::atomic< bool > &cancel)
Text-only streaming generation (extracted from streaming).
bool do_load_active(const ModelConfig &config) override
COLD → ACTIVE in ONE whole-file read (gh#148).
static std::string extract_system_prompt(const std::vector< Message > &messages)
Extract the system prompt from messages.
llama_context * mtp_draft_ctx_
MTP context (ctx_type=MTP, ctx_other=ctx_)
void inject_sampler_factory_for_test(std::unique_ptr< SamplerFactory > factory)
Inject a SamplerFactory for unit testing (v2.3.10).
bool setup_mtp_draft(const std::string &head_path, int n_max)
Lazily build the MTP head context against the live ctx_ (gh#106).
Sampler adapter that wraps a llama_sampler* chain.
llama_sampler * native_chain() const
Expose the underlying chain for legacy call sites that have not yet been ported to the Sampler API.
static CacheKey make_key(std::string_view prompt_text, std::string_view model_path)
Compute a cache key from prompt text and model path.
Pure-virtual per-generation sampler used by the decode loop.
virtual int32_t sample()=0
Sample one token from the current decode position.
Per-session llama sequence slots and their resident tokens.
const std::vector< Tok > & resident(int slot) const
Tokens recorded as resident in a slot.
int slots() const
Number of sequence slots in the pool.
void invalidate_all()
Forget every slot's residency.
void invalidate(int slot)
Forget a slot's residency (its KV was cleared or invalidated).
int forget_session(const std::string &key)
Drop one SESSION's residency and release its slot (gh#165).
int acquire(const std::string &key, std::string *evicted)
Assign a slot to this session, evicting the least-recently used one when the pool is full.
void set_resident(int slot, std::vector< Tok > tokens)
Record what a slot now holds after a decode.
v2.13.0 prompt-vs-context admission arithmetic for one turn.
int * cancel
Consumer's cancel flag, nullable.
ENTROPIC_EXPORT const char * entropic_error_name(entropic_error_t code)
Get the human-readable name for an error code.
entropic_error_t
Error codes returned by all C API functions.
@ ENTROPIC_ERROR_CANCELLED
Operation cancelled via cancel token.
@ ENTROPIC_ERROR_IMAGE_LOAD_FAILED
Image file could not be read or decoded (v1.9.11)
@ ENTROPIC_ERROR_EVAL_CONTEXT_FULL
Sequence exceeds model context window (v1.9.10)
@ ENTROPIC_ERROR_SPECULATIVE_INCOMPATIBLE_CONFIG
MTP/speculative enabled but the request can't run correctly (temp>0, grammar, tools,...
@ ENTROPIC_ERROR_NOT_SUPPORTED
Capability not supported by this backend (v1.9.13)
@ ENTROPIC_ERROR_GENERATE_FAILED
Generation failed (context overflow, model error)
@ ENTROPIC_ERROR_INVALID_STATE
Operation not valid in current state (e.g., generate before activate)
@ ENTROPIC_ERROR_LOAD_FAILED
Model load failed (corrupt file, OOM, unsupported format)
EXPERIMENTAL (v2.13.0): the llama.cpp binding for expert offload — an owning, null-terminated llama_m...
std::string err
size_exceeded, when set
One post-generation summary line, formatted identically on every decode path.
Which grammar source constrains a decode (gh#134).
LlamaCppBackend — llama.cpp C API integration.
Concrete llama.cpp Sampler + SamplerFactory (v2.3.10 seam impl).
Concrete llama.cpp tokenizer (v2.3.10 seam impl).
spdlog initialization and logger access.
auto now()
Get current time for timing measurements.
ENTROPIC_EXPORT std::shared_ptr< spdlog::logger > get(const std::string &name)
Get or create a named logger.
double elapsed_ms(std::chrono::steady_clock::time_point start, std::chrono::steady_clock::time_point end)
Compute elapsed milliseconds between two time points.
Pure envelope check for the MTP speculative path (gh#108).
Activate model on GPU (WARM → ACTIVE).
@ IMAGE
Image content (local path or data URI)
static GenerationResult spec_run_from_tokens(llama_context *ctx_tgt, llama_context *ctx_dft, llama_model *model_tgt, const std::vector< llama_token > &tokens, const GenerationParams ¶ms, std::function< void(std::string_view)> &on_token, std::atomic< bool > &cancel, int n_draft_max, const std::string &draft_path, std::chrono::steady_clock::time_point t0, const std::string &tool_grammar, bool tool_grammar_lazy, const std::string &generation_prompt)
Public entry point for the speculative-decoding kernel.
BackendCapability
Capabilities that an inference backend may or may not support.
@ SPECULATIVE_DECODING
Speculative decoding compatibility.
@ HIDDEN_STATE
Recurrent hidden state management (save/load/reset)
@ VISION
Vision / multimodal input (v1.9.11)
@ KV_CACHE
KV cache state management (save/load/clear)
@ AUDIO
Audio input via mtmd audio projector (gh#53, v2.3.0)
@ _COUNT
Sentinel — must be last. Used for iteration/array sizing.
void append_sequential_stop(GenerationParams ¶ms, const std::string &marker)
Append a tool-call close marker to params.stop for sequential mode.
void coerce_string_typed_args(std::vector< ToolCall > &calls, const std::string &tools_json)
gh#90: coerce numeric scalars back to strings for string-typed tool parameters.
bool batch_is_viable(std::size_t n, int n_parallel, std::size_t shared, bool hybrid, std::size_t total_suffix, int n_batch)
Decide whether the same-prefix batch fast-path is safe + worthwhile.
bool has_images(const std::vector< ContentPart > &parts)
Check if content parts contain any image parts.
static int effective_n_draft(int n_max)
Draft-window size setup_mtp_draft will actually use (gh#106).
static bool spec_decode_both(SpeculativeRunState &state)
Decode the speculative batch on both contexts.
@ tokens
Gate on generated tokens since the last tool call.
@ off
Disabled (default) — no thinking-budget gating.
std::string extract_text(const std::vector< ContentPart > &parts)
Extract concatenated text from content parts.
std::string close_marker_for_format(common_chat_format fmt)
Map a resolved common_chat format to its single-tool-call close marker.
constexpr int kNoSessionSlot
Sentinel for "no slot assigned".
static void spec_ckpt_save_tgt(SpeculativeRunState &state)
Snapshot target state right before the target decode of the speculative batch (when use_ckpt_tgt + no...
@ ok
Tool dispatched, returned non-empty content.
std::size_t batch_shared_prefix_len(const std::vector< std::vector< Tok > > &seqs)
Longest shared token prefix across N request sequences (gh#98).
@ optional
optional: every escape asks the host's path approver
static void spec_trim_rejected_drafts(SpeculativeRunState &state)
Clear any stale KV positions left by rejected draft tokens.
static bool spec_commit_accepted(SpeculativeRunState &state, const std::vector< llama_token > &ids, const llama_vocab *vocab, int max_tokens, std::function< void(std::string_view)> &on_token, std::atomic< bool > &cancel)
Walk accepted ids, emit tokens via callback, update state.
static std::vector< common_chat_tool > mcp_tools_to_common_chat(const std::string &tools_json)
Convert entropic MCP tool JSON to common_chat_tool defs (gh#87).
static std::string spec_emit_token(SpeculativeRunState &state, llama_token id, const llama_vocab *vocab, int max_tokens, std::function< void(std::string_view)> &on_token, std::atomic< bool > &cancel)
Emit on_token for one accepted id, updating state and returning a stop signal when terminating condit...
PoolGeometry derive_pool_geometry(const ModelConfig &cfg)
Derive the context geometry a tier's config implies.
std::size_t warm_keep_cut(const std::vector< Tok > &resident, const std::vector< Tok > &incoming, long kv_pos_max)
Decide how many resident-KV tokens warm-keep may reuse this turn.
static void spec_rollback_partial(SpeculativeRunState &state, common_sampler *smpl_save, std::vector< llama_token > &ids)
Partial-acceptance rollback: restore both contexts and the sampler to their pre-draft state,...
static void fill_batch_cell(llama_batch &b, int k, llama_token tok, llama_pos pos, llama_seq_id seq, bool want_logits)
Fill one cell of a multi-seq llama_batch (defined below).
static std::string spec_check_preconditions(bool target_active, bool draft_active, llama_context *ctx_tgt, llama_context *ctx_dft)
Validate speculative preconditions and reject NO-seq_rm.
std::string context_overflow_message(const ContextFit &f)
The refusal an operator reads, carrying every number they need.
bool grammar_sources_collide(const std::string &request_grammar, const std::string &tool_grammar)
Whether both sources are active — a config error worth reporting.
@ tool_call
Render-derived (COMMON_GRAMMAR_TYPE_TOOL_CALLS, needs prefill)
@ count
Sentinel — MUST remain last.
@ WARM
mmap'd + mlock'd in RAM
@ COLD
On disk only, no RAM consumed.
BatchKvPlan plan_batch_kv(std::size_t n, int session_slots, const std::vector< int > &temp_ids)
Plan the sequence ids a same-prefix batch may use.
void strip_thinking_channels(std::string &content, std::string *reasoning_out)
Strip Gemma 4 QAT reasoning channels (<|channel>…<channel|>) from content, accumulating the stripped ...
std::string format_generation_summary(const GenerationSummary &s)
Format the one-line generation summary.
static std::string concat_messages_fallback(const std::vector< Message > &messages)
Plain "role: content" join used when templating fails.
static std::optional< common_chat_params > render_common_chat(llama_model *model, const std::vector< Message > &messages, const GenerationParams ¶ms, const std::vector< common_chat_tool > &tools, bool require_tool_call)
Shared common_chat render core for both template paths (gh#87).
static int staged_tool_count(const std::string &tools_json)
Number of definitions in a staged MCP tool-list JSON array.
static std::string spec_init_run(SpeculativeRunState &state, llama_model *model_tgt, const std::vector< llama_token > &tokens, const GenerationParams ¶ms, int n_draft_max, const std::string &draft_path, const std::string &tool_grammar, bool tool_grammar_lazy, const std::string &generation_prompt)
Initialize speculative run state (prefill + sampler + decoder).
static int spec_run_draft(SpeculativeRunState &state)
Trigger draft generation via common_speculative_draft.
static void spec_run_loop(SpeculativeRunState &state, const llama_vocab *vocab, int max_tokens, std::function< void(std::string_view)> &on_token, std::atomic< bool > &cancel)
Run the accept-round loop until completion / EOS / cancel.
bool is_request_grammar(GrammarSource source)
Whether a source is applied as a request-side grammar.
GrammarSource resolve_grammar_source(const std::string &request_grammar, const std::string &tool_grammar, bool request_from_tier=false)
Resolve which grammar wins.
std::string mtp_unsupported_reason(float temperature, bool has_grammar, bool streaming)
Reason MTP cannot run for a request, or "" when the envelope is safe.
static std::vector< common_chat_msg > to_common_chat(const std::vector< Message > &messages)
Convert engine messages to common_chat_msg (gh#86, v2.6.1).
static ToolCall to_entropic_tool_call(const common_chat_tool_call &cc)
Map a common_chat_tool_call to entropic's ToolCall (gh#87).
static std::vector< llama_chat_message > to_llama_chat(const std::vector< Message > &messages)
Convert engine messages to llama_chat_message views.
static std::string spec_init_sampler_and_decoder(SpeculativeRunState &state, llama_model *model_tgt, const GenerationParams ¶ms, int n_draft_max, const std::string &draft_path, const std::string &tool_grammar, bool tool_grammar_lazy, const std::string &generation_prompt)
Initialize the kernel state: clear KV, prefill, sampler, speculative context, batch,...
static GenerationResult spec_finalize(SpeculativeRunState &state, std::chrono::steady_clock::time_point t0)
Speculative kernel against an explicit draft backend.
static bool spec_accept_round(SpeculativeRunState &state, const llama_vocab *vocab, int max_tokens, std::function< void(std::string_view)> &on_token, std::atomic< bool > &cancel)
Run one speculative accept round; return false to stop.
static GenerationResult batch_error_result(const std::string &msg)
Build a single error GenerationResult (gh#98 batch failures).
static void spec_build_batch(SpeculativeRunState &state)
Build the target batch [id_last, draft0, ..., draftN-1].
static int spec_prepare_draft(SpeculativeRunState &state)
Drive one accept round: optional draft generation, decode on both contexts, sample-and-accept,...
static void spec_ckpt_save_dft(SpeculativeRunState &state)
Drive one accept round: draft → decode → sample-and-accept → emit tokens.
static void spec_cleanup(SpeculativeRunState &state)
Free everything allocated by the kernel.
bool context_fit_overflows(const ContextFit &f)
Whether this turn's prompt cannot fit the tier's context at all.
static void spec_ckpt_restore_dft(SpeculativeRunState &state)
Restore the draft's pre-draft state so the upcoming target-batch decode on the draft re-fills cleanly...
std::string expert_offload_load_refusal(int cpu_moe_layers, int n_layer, int n_expert)
Post-load refusal, against the numbers only the loaded model knows.
gh#144: derive a tier's KV geometry from max_sessions, purely.
Backend metadata for introspection.
std::string name
Backend identifier (e.g. "llama.cpp", "axcl")
Single cached KV state snapshot.
64-bit hash used as cache lookup key.
One turn's irreducible prompt, measured against the tier budget.
int prompt_tokens
Rendered + tokenized prompt for this turn.
Generation parameters for a single inference call.
std::vector< std::string > stop
Stop sequences.
Result of a single generation call.
entropic_error_t error_code
Error code (ENTROPIC_OK if no error)
double generation_time_ms
Wall-clock generation time.
int n_drafted
Tokens proposed by the draft/MTP head across all rounds.
double throughput_tok_s
Measured throughput for this generation (tok/s).
std::string finish_reason
Finish reason: "stop", "length", "error".
std::string content
Generated text (cleaned by adapter)
int prefill_tokens
Prompt tokens actually pushed through llama_decode during this run's prefill (gh#144,...
int n_accepted
Draft tokens the target accepted (≤ n_drafted).
std::string error_message
Error description (empty if no error)
int token_count
Generated token count.
Result of a common_chat parse: native tool calls + split content.
std::vector< ToolCall > tool_calls
Extracted native tool calls.
std::string content
Content with calls + reasoning removed.
std::string reasoning_content
Extracted reasoning/thought block.
Per-token log-probability evaluation result.
std::vector< float > logprobs
Log-prob for each token transition (N-1 values)
int n_logprobs
Number of logprob values (n_tokens - 1)
int n_tokens
Number of input tokens.
float total_logprob
Sum of all logprob values.
float perplexity
exp(-mean(logprobs)) — lower = less surprising
std::vector< int32_t > tokens
Input tokens echoed back for verification.
Model configuration for a single tier.
std::filesystem::path mmproj_path
Vision projector GGUF path.
int gpu_layers
GPU offload layers (-1 = all)
int context_length
Context window size (512–131072)
std::filesystem::path path
Resolved model file path.
int cpu_moe_layers
EXPERIMENTAL: layers whose ROUTED-EXPERT tensors go to the host while everything else follows gpu_lay...
bool flash_attn
Enable flash attention.
bool use_mlock
Lock model in system RAM.
int n_seq_max
cparams.n_seq_max
size_t max_bytes
Maximum cache RAM (512 MB default)
bool log_hits
Log cache hit/miss at INFO level.
bool enabled
Master switch (false = no caching)
bool warm_keep
gh#96 (v2.7.5): keep the prior turn's KV resident and re-decode only the appended delta (warm-keep / ...
Bundles per-kernel-run mutable state to keep the loop body focused on its responsibility (knots: cogn...
std::vector< std::string > stop
gh#108: stop seqs (effective_stop); empty for gh#36
int n_prefilled
gh#144 (v2.12.0): prompt tokens actually pushed through llama_decode during this run's prefill.
UTF-8 validation + replacement at every system boundary where bytes change ownership.
ENTROPIC_EXPORT std::string sanitize_utf8(std::string_view input)
Replace invalid UTF-8 byte sequences with U+FFFD.
gh#96 (v2.7.5) warm-keep / incremental-prefill decision logic.