Skip to content

Commit c69a428

Browse files
author
Chris Warren-Smith
committed
LLAMA: nitro - handling for KV cache exhaustion
1 parent 2433863 commit c69a428

4 files changed

Lines changed: 76 additions & 36 deletions

File tree

llama/llama-sb.cpp

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ bool Llama::add_message(LlamaIter &iter, const string &role, const string &conte
231231
int32_t n = 0;
232232

233233
if (_template.empty()) {
234-
_last_error = "No chat template available";
234+
set_last_error("No chat template available");
235235
return false;
236236
}
237237

@@ -250,7 +250,7 @@ bool Llama::add_message(LlamaIter &iter, const string &role, const string &conte
250250
bool add_ass = (role == "user" || role == "tool" || role == "tool_result");
251251
n = llama_chat_apply_template(_template.c_str(), &message, 1, add_ass, buf.data(), buf_size);
252252
if (n < 0) {
253-
_last_error = "No chat template no supported";
253+
set_last_error("No chat template no supported");
254254
return false;
255255
} else if (n > (int32_t)buf.size()) {
256256
buf.resize(n);
@@ -297,7 +297,7 @@ bool Llama::add_message(LlamaIter &iter, const string &role, const string &conte
297297

298298
llama_batch decoder_batch = llama_batch_get_one(&decoder_start_token_id, 1);
299299
if (llama_decode(_ctx, decoder_batch)) {
300-
_last_error = "Failed to evaluate decoder start token";
300+
set_last_error("Failed to evaluate decoder start token");
301301
return false;
302302
}
303303
}
@@ -310,7 +310,7 @@ bool Llama::add_message(LlamaIter &iter, const string &role, const string &conte
310310

311311
string Llama::next(LlamaIter &iter) {
312312
if (!iter._has_next) {
313-
_last_error = "Iteration beyond end of stream";
313+
set_last_error("Iteration beyond end of stream");
314314
return "";
315315
}
316316

@@ -328,7 +328,7 @@ string Llama::next(LlamaIter &iter) {
328328
// prepare the next batch with the sampled token
329329
llama_batch batch = llama_batch_get_one(&tok, 1);
330330
if (llama_decode(_ctx, batch)) {
331-
_last_error = "Failed to evaluate token during generation";
331+
set_last_error("Failed to evaluate token during generation");
332332
return "";
333333
}
334334

@@ -359,7 +359,7 @@ string Llama::all(LlamaIter &iter) {
359359
// decode the token
360360
llama_batch batch = llama_batch_get_one(&tok, 1);
361361
if (llama_decode(_ctx, batch)) {
362-
_last_error = "Failed to evaluate token during generation";
362+
set_last_error("Failed to evaluate token during generation");
363363
break;
364364
}
365365
}
@@ -444,7 +444,7 @@ bool Llama::embed_text(const std::string &text, std::vector<float> &out, int emb
444444
int n_ctx = llama_n_ctx(_ctx);
445445
int n = tokens.size();
446446
if (n > n_ctx) {
447-
_last_error = std::format("warning: chunk truncated {} -> {} tokens ", n, n_ctx);
447+
set_last_error(std::format("warning: chunk truncated {} -> {} tokens ", n, n_ctx));
448448
n = n_ctx;
449449
tokens.resize(n);
450450
}
@@ -461,7 +461,7 @@ bool Llama::embed_text(const std::string &text, std::vector<float> &out, int emb
461461
}
462462

463463
if (!emb) {
464-
_last_error = "no embedding returned\n";
464+
set_last_error("no embedding returned");
465465
return false;
466466
}
467467

@@ -488,6 +488,25 @@ bool Llama::batch_decode_tokens(vector<llama_token> &tokens) {
488488
size_t batch_size = std::min((size_t)n_batch, tokens.size() - i);
489489
llama_batch batch = llama_batch_get_one(tokens.data() + i, batch_size);
490490
int result = llama_decode(_ctx, batch);
491+
if (result == 1) {
492+
// KV full or fragmented mid-batch - evict oldest tokens and retry
493+
if (!make_space_for_tokens(n_batch)) {
494+
set_decode_error(result, i, tokens.size());
495+
return false;
496+
}
497+
result = llama_decode(_ctx, batch);
498+
if (result == 1) {
499+
// Eviction reported enough logical space but decode still failed -
500+
// this is fragmentation, not a real space shortage. No defrag API
501+
// is available, so fall back to a full non-system flush, which
502+
// guarantees one contiguous block.
503+
if (!full_flush_except_system()) {
504+
set_decode_error(result, i, tokens.size());
505+
return false;
506+
}
507+
result = llama_decode(_ctx, batch);
508+
}
509+
}
491510
if (result != 0) {
492511
set_decode_error(result, i, tokens.size());
493512
return false;
@@ -504,7 +523,7 @@ bool Llama::configure_sampler() {
504523
if (!_grammar_src.empty()) {
505524
llama_sampler *grammar = llama_sampler_init_grammar(_vocab, _grammar_src.c_str(), _grammar_root.c_str());
506525
if (!grammar) {
507-
_last_error = "failed to initialize grammar sampler";
526+
set_last_error("failed to initialize grammar sampler");
508527
return false;
509528
}
510529
llama_sampler_chain_add(chain, grammar);
@@ -535,6 +554,21 @@ bool Llama::configure_sampler() {
535554
return true;
536555
}
537556

557+
bool Llama::full_flush_except_system() {
558+
llama_memory_t mem = llama_get_memory(_ctx);
559+
llama_pos pos_min = llama_memory_seq_pos_min(mem, 0);
560+
if (pos_min < 0) {
561+
return true; // already empty
562+
}
563+
llama_pos flush_start = pos_min + _n_system_tokens;
564+
bool ok = llama_memory_seq_rm(mem, 0, flush_start, -1);
565+
if (!ok) {
566+
set_last_error("Failed to flush memory past system tokens");
567+
return false;
568+
}
569+
return true;
570+
}
571+
538572
// Makes space in the context for n_tokens by removing old tokens if necessary
539573
// Returns true if successful, false if impossible to make space
540574
//
@@ -549,7 +583,7 @@ bool Llama::configure_sampler() {
549583
bool Llama::make_space_for_tokens(int n_tokens) {
550584
int n_ctx = llama_n_ctx(_ctx);
551585
if (n_tokens > n_ctx) {
552-
_last_error = "Too many tokens, increase context size (n_ctx)";
586+
set_last_error("Too many tokens, increase context size (n_ctx)");
553587
return false;
554588
}
555589

@@ -579,11 +613,11 @@ bool Llama::make_space_for_tokens(int n_tokens) {
579613
// Can't remove more than we have (minus _n_system_tokens)
580614
int removable = current_used - _n_system_tokens;
581615
if (tokens_to_remove > removable) {
582-
_last_error = "Can't make enough space while keeping num_system_tokens tokens";
616+
set_last_error("Can't make enough space while keeping num_system_tokens tokens");
583617
return false;
584618
}
585619
if (!_can_shift) {
586-
_last_error = "Memory type doesn't support shifting, can't evict mid-sequence";
620+
set_last_error("Memory type doesn't support shifting, can't evict mid-sequence");
587621
return false;
588622
}
589623

@@ -595,6 +629,7 @@ bool Llama::make_space_for_tokens(int n_tokens) {
595629
// Shift remaining tokens down
596630
llama_memory_seq_add(mem, 0, remove_start + tokens_to_remove, -1, -tokens_to_remove);
597631

632+
set_last_error(std::format("made space for {} tokens", n_tokens));
598633
return true;
599634
}
600635

@@ -603,13 +638,13 @@ vector<llama_token> Llama::tokenize(const string &prompt) {
603638

604639
int n_prompt = -llama_tokenize(_vocab, prompt.c_str(), prompt.size(), nullptr, 0, true, true);
605640
if (n_prompt <= 0) {
606-
_last_error = "Failed to tokenize prompt";
641+
set_last_error("Failed to tokenize prompt");
607642
} else {
608643
result.reserve(n_prompt);
609644
result.resize(n_prompt);
610645
if (llama_tokenize(_vocab, prompt.c_str(), prompt.size(),
611646
result.data(), n_prompt, true, true) < 0) {
612-
_last_error = "Failed to tokenize prompt";
647+
set_last_error("Failed to tokenize prompt");
613648
}
614649
}
615650
return result;
@@ -658,7 +693,7 @@ string Llama::token_to_string(LlamaIter &iter, llama_token tok) {
658693
return result;
659694
}
660695

661-
void Llama::set_last_error(const char *message) {
696+
void Llama::set_last_error(const string &message) {
662697
if (!_last_error.empty()) {
663698
if (_last_error.back() == '\n') {
664699
_last_error.pop_back();
@@ -679,10 +714,10 @@ void Llama::set_decode_error(int32_t error, int index, int num_tokens) {
679714
int space_needed = num_tokens;
680715
int space_available = n_ctx - current_used;
681716
_n_system_tokens;
682-
_last_error = std::format("KV exhausted. Reduce batch or context sizes. batchNo:{} requested:{} available:{}",
683-
index, space_needed, space_available);
717+
set_last_error(std::format("KV exhausted. Reduce batch or context sizes. batchNo:{} requested:{} available:{}",
718+
index, space_needed, space_available));
684719
} else {
685720
auto message = error == 2 ? "abort" : error == -1 ? "invalid" : "fatal";
686-
_last_error = std::format("Failed to decode batch. batchNo:{} error:'{}'", index, message);
721+
set_last_error(std::format("Failed to decode batch. batchNo:{} error:'{}'", index, message));
687722
}
688723
}

llama/llama-sb.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,11 @@ struct Llama {
119119
bool batch_decode_tokens(vector<llama_token> &tokens);
120120
bool configure_sampler();
121121
void dirty() {_sampler_dirty = true; }
122+
bool full_flush_except_system();
122123
bool make_space_for_tokens(int n_tokens);
123124
vector<llama_token> tokenize(const string &prompt);
124125
string token_to_string(LlamaIter &iter, llama_token tok);
125-
void set_last_error(const char *message);
126+
void set_last_error(const string &message);
126127
void set_decode_error(int32_t error, int index, int num_tokens);
127128

128129
llama_model *_model;

llama/llama.cpp

llama/nitro.cpp

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -429,8 +429,8 @@ static const std::vector<std::string> CODE_EXTENSIONS = {
429429
//
430430
static std::string settings_path() {
431431
// Attempt to read settings from the current working directory first
432-
if (fs::exists("nitro.settings.json")) {
433-
return "nitro.settings.json";
432+
if (fs::exists("nitro.config.json")) {
433+
return "nitro.config.json";
434434
}
435435
const char *home = getenv("HOME");
436436
std::string base = home ? std::string(home) : ".";
@@ -2041,6 +2041,9 @@ std::string AgentState::process_tool(const std::string &cmd, const NitroConfig &
20412041
std::string rest = cmd.substr(sp1 + 1);
20422042
rest.erase(0, rest.find_first_not_of(" \t"));
20432043
auto sp2 = rest.find(' ');
2044+
if (sp2 == std::string::npos) {
2045+
sp2 = rest.find('\n');
2046+
}
20442047
if (sp2 == std::string::npos) {
20452048
arg1 = rest;
20462049
} else {
@@ -2251,9 +2254,9 @@ bool AgentState::run_turn(const std::string &user_message, const NitroConfig &cf
22512254
};
22522255

22532256
auto start_think = [&](const std::string &tag) -> void {
2254-
if (think_mode == t_init) {
2257+
if (think_mode != t_think) {
22552258
auto pos = buffer.find(tag);
2256-
if (pos != std::string::npos) {
2259+
if (pos == 0) {
22572260
think_mode = t_think;
22582261
// display prededing text
22592262
buffer = buffer.substr(0, pos);
@@ -2264,7 +2267,7 @@ bool AgentState::run_turn(const std::string &user_message, const NitroConfig &cf
22642267
auto end_think = [&](const std::string &tag) -> void {
22652268
if (think_mode == t_think) {
22662269
auto pos = buffer.find(tag);
2267-
if (pos != std::string::npos) {
2270+
if (pos == 0) {
22682271
think_mode = t_thunk;
22692272
// display remaining text
22702273
buffer = buffer.substr(pos + tag.length());
@@ -2312,19 +2315,20 @@ bool AgentState::run_turn(const std::string &user_message, const NitroConfig &cf
23122315
} else {
23132316
buffer += tok;
23142317
}
2315-
if (think_mode == t_init) {
2316-
start_think("<think>");
2317-
start_think("<|think|>");
2318-
start_think("<think|>");
2319-
start_think("<|channel>thought");
2320-
}
2318+
2319+
start_think("<think>");
2320+
start_think("<|think|>");
2321+
start_think("<think|>");
2322+
start_think("<|channel>thought");
2323+
2324+
end_think("</think>");
2325+
end_think("</|think|>");
2326+
end_think("</|think>");
2327+
end_think("<think|>");
2328+
end_think("<channel|>");
2329+
23212330
if (think_mode == t_think) {
23222331
tui.tick_spinner();
2323-
end_think("</think>");
2324-
end_think("</|think|>");
2325-
end_think("</|think>");
2326-
end_think("<think|>");
2327-
end_think("<channel|>");
23282332
}
23292333
auto tool_start = buffer.find("TOOL:");
23302334
if (tool_start == 0) {

0 commit comments

Comments
 (0)