33#include <llama/llama.h>
45inline ggml_type
from_str(
const std::string& s) {
46 if (s ==
"f32")
return GGML_TYPE_F32;
47 if (s ==
"f16")
return GGML_TYPE_F16;
48 if (s ==
"bf16")
return GGML_TYPE_BF16;
49 if (s ==
"q8_0")
return GGML_TYPE_Q8_0;
50 if (s ==
"q4_0")
return GGML_TYPE_Q4_0;
51 if (s ==
"q4_1")
return GGML_TYPE_Q4_1;
52 if (s ==
"iq4_nl")
return GGML_TYPE_IQ4_NL;
53 if (s ==
"q5_0")
return GGML_TYPE_Q5_0;
54 if (s ==
"q5_1")
return GGML_TYPE_Q5_1;
55 return GGML_TYPE_COUNT;
98inline bool remove_range(llama_context *ctx, llama_seq_id seq, llama_pos p0,
105 llama_memory_t mem = llama_get_memory(ctx);
106 bool success = llama_memory_seq_rm(mem, seq, p0, p1);
109 LLOYAL_LOG_DEBUG(
"[kv::remove_range] FAILED: seq=%d, p0=%d, p1=%d", seq, p0,
112 "remove_range called BEFORE next llama_decode()");
131inline llama_pos
pos_max(llama_context *ctx, llama_seq_id seq) {
137 llama_memory_t mem = llama_get_memory(ctx);
138 llama_pos max_pos = llama_memory_seq_pos_max(mem, seq);
165inline void seq_cp(llama_context *ctx, llama_seq_id src, llama_seq_id dst,
166 llama_pos p0 = 0, llama_pos p1 = -1) {
172 llama_memory_t mem = llama_get_memory(ctx);
173 llama_memory_seq_cp(mem, src, dst, p0, p1);
175 LLOYAL_LOG_DEBUG(
"[kv::seq_cp] Copied seq %d → %d [%d, %d)", src, dst, p0, p1);
189inline void seq_keep(llama_context *ctx, llama_seq_id seq) {
195 llama_memory_t mem = llama_get_memory(ctx);
196 llama_memory_seq_keep(mem, seq);
225static_assert(std::is_signed_v<llama_seq_id>,
226 "llama_seq_id must be signed for NO_LEASE sentinel");
234constexpr llama_seq_id
NO_LEASE =
static_cast<llama_seq_id
>(-1);
245 llama_context*
ctx =
nullptr;
261inline State init(llama_context* ctx, llama_seq_id n_seq_max) {
265 s.
leased.resize(
static_cast<size_t>(n_seq_max), 0);
266 s.
vacant.reserve(
static_cast<size_t>(n_seq_max));
267 for (llama_seq_id i = n_seq_max; i-- > 0; ) {
285 llama_seq_id seq = s.
vacant.back();
287 s.
leased[
static_cast<size_t>(seq)] = 1;
303 assert(seq >= 0 && seq < s.
n_seq_max &&
"release: seq out of range");
304 assert(s.
leased[
static_cast<size_t>(seq)] &&
"release: seq not leased");
305 s.
leased[
static_cast<size_t>(seq)] = 0;
325 assert(seq >= 0 && seq < s.
n_seq_max &&
"evict: seq out of range");
326 assert(s.
leased[
static_cast<size_t>(seq)] &&
"evict: seq not leased");
348 assert(keep >= 0 && keep < s.
n_seq_max &&
"retain: keep seq out of range");
349 assert(s.
leased[
static_cast<size_t>(keep)] &&
"retain: keep seq not leased");
352 for (llama_seq_id i = 0; i < s.
n_seq_max; ++i) {
353 if (i != keep) assert(
pos_max(s.
ctx, i) < 0 &&
"retain: seq_keep left dirty tags");
358 for (llama_seq_id i = s.
n_seq_max; i-- > 0; ) {
360 s.
leased[
static_cast<size_t>(i)] = 1;
362 s.
leased[
static_cast<size_t>(i)] = 0;
379 for (llama_seq_id i = 0; i < s.
n_seq_max; ++i) {
380 if (s.
leased[
static_cast<size_t>(i)]) {
415inline size_t state_size(llama_context *ctx, llama_seq_id seq) {
421 llama_memory_t mem = llama_get_memory(ctx);
422 llama_pos max_pos = llama_memory_seq_pos_max(mem, seq);
424 LLOYAL_LOG_DEBUG(
"[kv::state_size] WARNING: KV cache is empty (max_pos=%d) "
430 size_t size = llama_state_seq_get_size(ctx, seq);
434 "[kv::state_size] Per-sequence size query failed for seq=%d", seq);
436 "[kv::state_size] Attempting global state size (fallback)");
437 size = llama_state_get_size(ctx);
444 "size queries failed");
448 "[kv::state_size] Per-sequence size for seq=%d: %zu bytes (%.1f MB)",
449 seq, size, size / 1024.0 / 1024.0);
470inline size_t state_save(llama_context *ctx, llama_seq_id seq, uint8_t *dst,
472 if (!ctx || !dst || size == 0) {
474 "[kv::state_save] ERROR: invalid parameters (ctx=%p, dst=%p, size=%zu)",
479 llama_memory_t mem = llama_get_memory(ctx);
480 llama_pos max_pos = llama_memory_seq_pos_max(mem, seq);
482 LLOYAL_LOG_DEBUG(
"[kv::state_save] WARNING: KV cache is empty (max_pos=%d) "
488 size_t written = llama_state_seq_get_data(ctx, dst, size, seq);
492 "(possible KV fragmentation)",
495 "[kv::state_save] Attempting global state save (fallback)");
496 written = llama_state_get_data(ctx, dst, size);
500 "[kv::state_save] Global fallback succeeded: %zu bytes (%.1f MB)",
501 written, written / 1024.0 / 1024.0);
504 "[kv::state_save] ERROR: Both per-sequence and global save failed");
508 "[kv::state_save] Per-sequence saved %zu bytes (%.1f MB) for seq=%d",
509 written, written / 1024.0 / 1024.0, seq);
531inline size_t state_load(llama_context *ctx, llama_seq_id seq,
532 const uint8_t *src,
size_t size) {
533 if (!ctx || !src || size == 0) {
535 "[kv::state_load] ERROR: invalid parameters (ctx=%p, src=%p, size=%zu)",
540 llama_memory_t mem = llama_get_memory(ctx);
541 llama_pos max_pos = llama_memory_seq_pos_max(mem, seq);
543 LLOYAL_LOG_DEBUG(
"[kv::state_load] WARNING: KV cache is empty (max_pos=%d) "
544 "- loading may crash on recurrent models",
548 size_t read = llama_state_seq_set_data(ctx, src, size, seq);
552 "(possible fragmentation)",
555 "[kv::state_load] Attempting global state restore (fallback)");
556 read = llama_state_set_data(ctx, src, size);
560 "[kv::state_load] Global fallback succeeded: %zu bytes (%.1f MB)",
561 read, read / 1024.0 / 1024.0);
568 "[kv::state_load] Per-sequence loaded %zu bytes (%.1f MB) for seq=%d",
569 read, read / 1024.0 / 1024.0, seq);
592 size_t size = llama_state_get_size(ctx);
594 size / 1024.0 / 1024.0);
609 if (!ctx || !dst || size == 0) {
614 size_t written = llama_state_get_data(ctx, dst, size);
616 written, written / 1024.0 / 1024.0);
632 if (!ctx || !src || size == 0) {
637 size_t read = llama_state_set_data(ctx, src, size);
639 read / 1024.0 / 1024.0);
657 "[kv::build_info] ============================================");
659 "[kv::build_info] llama.cpp KV Sequence Operations Configuration");
661 "[kv::build_info] ============================================");
664 "[kv::build_info] Current MVP: n_seq_max=1 (single sequence only)");
667 llama_pos max_pos =
pos_max(ctx, 0);
676 if (snapshot_size > 0) {
678 "[kv::build_info] Estimated snapshot size: %zu bytes (%.1f MB)",
679 snapshot_size, snapshot_size / 1024.0 / 1024.0);
684 "[kv::build_info] Fragmentation fallback: per-sequence → global state");
686 "[kv::build_info] Critical: Call remove_range() BEFORE llama_decode()");
688 "[kv::build_info] ============================================");
712 throw std::runtime_error(
"kv::clear_all - NULL context");
716 llama_memory_clear(llama_get_memory(ctx),
true);
737 throw std::runtime_error(
"kv::clear_metadata - NULL context");
741 llama_memory_clear(llama_get_memory(ctx),
false);
780 const std::vector<llama_token> &original_sinks,
781 const std::vector<llama_token> &tail,
785 throw std::runtime_error(
"kv::clear_and_reseed - NULL context");
788 if (original_sinks.empty() && tail.empty()) {
789 LLOYAL_LOG_DEBUG(
"[kv::clear_and_reseed] ERROR: both sinks and tail are empty");
790 throw std::runtime_error(
"kv::clear_and_reseed - no tokens to reseed");
793 LLOYAL_LOG_DEBUG(
"[kv::clear_and_reseed] Starting reseed: %zu sinks + %zu tail = %zu total",
794 original_sinks.size(), tail.size(), original_sinks.size() + tail.size());
797 llama_memory_t mem = llama_get_memory(ctx);
800 llama_pos max_pos_before = llama_memory_seq_pos_max(mem, 0);
801 LLOYAL_LOG_DEBUG(
"[kv::clear_and_reseed] Before clear: KV cache max_pos=%d", max_pos_before);
804 llama_memory_clear(mem,
true);
806 llama_pos max_pos_after_clear = llama_memory_seq_pos_max(mem, 0);
807 if (max_pos_after_clear != -1) {
808 LLOYAL_LOG_DEBUG(
"[kv::clear_and_reseed] WARNING: KV cache not empty after clear (max_pos=%d)",
809 max_pos_after_clear);
813 if (!original_sinks.empty()) {
814 LLOYAL_LOG_DEBUG(
"[kv::clear_and_reseed] Re-decoding %zu sinks at position 0", original_sinks.size());
816 throw std::runtime_error(
"kv::clear_and_reseed - llama_decode failed on sinks");
822 int32_t tail_start_pos =
static_cast<int32_t
>(original_sinks.size());
823 LLOYAL_LOG_DEBUG(
"[kv::clear_and_reseed] Re-decoding %zu tail tokens at position %d",
824 tail.size(), tail_start_pos);
826 throw std::runtime_error(
"kv::clear_and_reseed - llama_decode failed on tail");
831 llama_pos max_pos_after = llama_memory_seq_pos_max(mem, 0);
832 int32_t expected_pos =
static_cast<int32_t
>(original_sinks.size() + tail.size()) - 1;
834 LLOYAL_LOG_DEBUG(
"[kv::clear_and_reseed] After reseed: KV cache max_pos=%d (expected %d)",
835 max_pos_after, expected_pos);
837 if (max_pos_after != expected_pos) {
838 LLOYAL_LOG_DEBUG(
"[kv::clear_and_reseed] WARNING: Unexpected final position (got %d, expected %d)",
839 max_pos_after, expected_pos);
878inline size_t write_file(llama_context *ctx, llama_seq_id seq,
879 const std::string &filepath,
880 const std::vector<llama_token> &tokens) {
886 if (filepath.empty()) {
892 llama_memory_t mem = llama_get_memory(ctx);
893 llama_pos max_pos = llama_memory_seq_pos_max(mem, seq);
896 "[kv::write_file] WARNING: KV cache is empty - skipping write");
902 size_t bytes = llama_state_seq_save_file(ctx, filepath.c_str(), seq,
903 tokens.data(), tokens.size());
908 filepath.c_str(), bytes, bytes / 1024.0 / 1024.0,
938 const std::string &filepath) {
940 throw std::runtime_error(
"[kv::read_file] null context");
943 if (filepath.empty()) {
944 throw std::runtime_error(
"[kv::read_file] empty filepath");
948 const uint32_t n_ctx = llama_n_ctx(ctx);
950 std::vector<llama_token> tokens;
951 tokens.resize(n_ctx);
953 size_t token_count = 0;
956 llama_state_seq_load_file(ctx, filepath.c_str(), seq, tokens.data(),
957 tokens.size(), &token_count);
960 throw std::runtime_error(
"[kv::read_file] failed to load from " +
964 tokens.resize(token_count);
966 LLOYAL_LOG_DEBUG(
"[kv::read_file] Loaded %s: %zu bytes (%.1f MB), %zu tokens",
967 filepath.c_str(), bytes, bytes / 1024.0 / 1024.0,
970 return FileData{std::move(tokens), bytes};
#define LLOYAL_LOG_DEBUG(...)
liblloyal - Common definitions and logging
Batch Decoding Operations.
constexpr llama_seq_id NO_LEASE
Sentinel value indicating a branch has no KV residency.
int many(llama_context *ctx, const llama_token *tokens, int32_t n_tokens, int32_t n_past, int32_t n_batch, llama_seq_id seq_id=0, int32_t *n_committed=nullptr)
Decode multiple tokens into the KV cache with auto-chunking.
ggml_type from_str(const std::string &s)
Map string name to ggml_type enum (matches llama.cpp CLI -ctk/-ctv flags).
void evict_all(State &s)
Evict every leased seq_id.
llama_seq_id acquire(State &s)
Acquire a seq_id from the vacant pool.
size_t available(const State &s)
Number of vacant seq_ids available for acquisition.
void evict(State &s, llama_seq_id seq)
Evict a seq_id — strip all KV tags then release.
void retain(State &s, llama_seq_id keep)
Nuclear retain — keep one seq, rebuild vacancy from scratch.
State init(llama_context *ctx, llama_seq_id n_seq_max)
Initialize tenancy with all seq_ids vacant.
void release(State &s, llama_seq_id seq)
Release a seq_id back to vacant — bookkeeping only, no KV calls.
void seq_keep(llama_context *ctx, llama_seq_id seq)
Keep only one sequence, removing all others.
FileData read_file(llama_context *ctx, llama_seq_id seq, const std::string &filepath)
void log_build_info(llama_context *ctx)
Log KV cache build info and current state.
size_t state_size(llama_context *ctx, llama_seq_id seq)
Get size needed to serialize sequence state.
void seq_cp(llama_context *ctx, llama_seq_id src, llama_seq_id dst, llama_pos p0=0, llama_pos p1=-1)
Copy KV cache from one sequence to another.
void clear_and_reseed(llama_context *ctx, const std::vector< llama_token > &original_sinks, const std::vector< llama_token > &tail, int32_t n_batch)
size_t state_save(llama_context *ctx, llama_seq_id seq, uint8_t *dst, size_t size)
Save sequence state to buffer.
void clear_metadata(llama_context *ctx)
Clear KV cache metadata only (fast reset)
size_t global_state_load(llama_context *ctx, const uint8_t *src, size_t size)
Restore global state from buffer.
size_t state_load(llama_context *ctx, llama_seq_id seq, const uint8_t *src, size_t size)
Restore sequence state from buffer.
llama_pos pos_max(llama_context *ctx, llama_seq_id seq)
Get maximum position in KV cache sequence.
void clear_all(llama_context *ctx)
Clear all KV cache (complete reset)
size_t global_state_size(llama_context *ctx)
Get size needed to serialize global state.
bool remove_range(llama_context *ctx, llama_seq_id seq, llama_pos p0, llama_pos p1)
Remove token range from KV cache sequence.
size_t write_file(llama_context *ctx, llama_seq_id seq, const std::string &filepath, const std::vector< llama_token > &tokens)
Write KV state to file with self-describing format.
size_t global_state_save(llama_context *ctx, uint8_t *dst, size_t size)
Save global state to buffer.
Data structure returned by read_file.
std::vector< llama_token > tokens
Tokens restored from file.
size_t bytes_read
Total bytes read from file.
Tenancy state — tracks seq_id vacancy and leases.
llama_context * ctx
Context for KV operations (nullptr after drain)
std::vector< llama_seq_id > vacant
Available seq_ids (LIFO stack)
std::vector< uint8_t > leased
Bitmap: leased[seq] = 1 if issued.
llama_seq_id n_seq_max
Total seq_id capacity (from llama_n_seq_max)