liblloyal 1.0.0
Branched Inference for llama.cpp
Loading...
Searching...
No Matches
branch.hpp
Go to the documentation of this file.
1#pragma once
2
3// SPDX-License-Identifier: LicenseRef-FSL-1.1-Apache-2.0
4// Copyright 2026 Lloyal Labs
5
6
61#include "boundaries.hpp"
62#include "common.hpp"
63#include "decode.hpp"
64#include "grammar.hpp"
65#include "kv.hpp"
66#include "logits.hpp"
67#include "metrics.hpp"
68#include "sampler.hpp"
69
70#include <llama/llama.h>
71#include <algorithm> // std::remove
72#include <cassert> // assert
73#include <cmath> // std::exp, std::log, std::isinf, std::isfinite
74#include <cstdint>
75#include <cstring> // std::memcpy
76#include <ctime> // std::time
77#include <deque> // std::deque (pointer stability for BranchStore)
78#include <functional> // std::function
79#include <limits> // std::numeric_limits
80#include <mutex>
81#include <span> // std::span (C++20)
82#include <stdexcept> // std::runtime_error
83#include <string> // std::to_string
84#include <unordered_map>
85#include <utility> // std::pair, std::exchange
86#include <vector>
87
88namespace lloyal::branch {
89
90// ===== HANDLE TYPE =====
91
100using BranchHandle = uint32_t;
101
103constexpr llama_seq_id NO_LEASE = kv::NO_LEASE;
104constexpr int DEFAULT_N_BATCH = 512;
105constexpr uint32_t GEN_SHIFT = 16;
106constexpr uint32_t INDEX_MASK = 0xFFFF;
107
123 uint32_t n_ctx;
124 uint32_t cells_used;
125 uint32_t remaining;
126};
127
133inline uint16_t handle_index(BranchHandle h) {
134 return static_cast<uint16_t>(h & INDEX_MASK);
135}
136
142inline uint16_t handle_generation(BranchHandle h) {
143 return static_cast<uint16_t>(h >> GEN_SHIFT);
144}
145
152inline BranchHandle make_handle(uint16_t index, uint16_t generation) {
153 return (static_cast<uint32_t>(generation) << GEN_SHIFT) | index;
154}
155
156// ===== REGISTRY HANDLE TYPES =====
157
159using SamplerChainHandle = int32_t;
160
162using GrammarHandle = int32_t;
163
165using MetricsHandle = int32_t;
166
174 llama_sampler* chain = nullptr;
175 bool has_dist = false;
176
177 SamplerChainEntry() = default;
179
181 : chain(o.chain), has_dist(o.has_dist) { o.chain = nullptr; }
183 if (this != &o) {
185 chain = o.chain; has_dist = o.has_dist; o.chain = nullptr;
186 }
187 return *this;
188 }
191};
192
199 llama_sampler* sampler = nullptr;
200
201 GrammarEntry() = default;
203
204 GrammarEntry(GrammarEntry&& o) noexcept : sampler(o.sampler) { o.sampler = nullptr; }
206 if (this != &o) {
208 sampler = o.sampler; o.sampler = nullptr;
209 }
210 return *this;
211 }
212 GrammarEntry(const GrammarEntry&) = delete;
214};
215
225 float temperature = 0.8f;
226 int32_t top_k = 40;
227 float top_p = 0.95f;
228 float typical_p = 1.0f;
229 float min_p = 0.05f;
230 float penalty_repeat = 1.0f;
231 float penalty_freq = 0.0f;
232 float penalty_present = 0.0f;
233 int32_t penalty_last_n = 64;
234 uint32_t seed = 0;
235 bool operator==(const CachedSamplingParams&) const = default;
236};
237
249template <SamplingParamsLike P>
250inline CachedSamplingParams snapshot_params(const P& p, uint32_t absent_seed) {
251 using ::lloyal::detail::as_value;
253 as_value(p.temperature, 0.8f),
254 as_value(p.top_k, static_cast<int32_t>(40)),
255 as_value(p.top_p, 0.95f),
256 as_value(p.typical_p, 1.0f),
257 as_value(p.min_p, 0.05f),
258 as_value(p.penalty_repeat, 1.0f),
259 as_value(p.penalty_freq, 0.0f),
260 as_value(p.penalty_present, 0.0f),
261 as_value(p.penalty_last_n, static_cast<int32_t>(64)),
262 as_value(p.seed, absent_seed),
263 };
264}
265
267inline uint32_t clock_seed() { return static_cast<uint32_t>(std::time(nullptr)); }
268
269// ===== BRANCH STATE =====
270
294 llama_context* ctx = nullptr;
295 const llama_model* model = nullptr;
296
297 llama_seq_id seq_id = NO_LEASE;
298 llama_pos position = 0;
299 llama_pos fork_head = 0;
300
304 uint64_t revision = 0;
306 uint64_t fork_revision = 0;
310 bool poisoned = false;
311
312 void poison() {
313 poisoned = true;
314 ++revision;
315 }
316
323 uint32_t img_slack_own = 0;
324 uint32_t img_slack_total = 0;
325
332
334
336
337 std::vector<llama_logit_bias> logit_bias;
338 std::function<void(llama_token_data_array&)> steer_fn;
339
341
342 llama_token last_token = -1;
343 std::vector<llama_token_data> last_candidates;
344
345 std::vector<float> logits_snapshot;
346 bool has_logits = false;
347
354 void capture_logits(int32_t index) {
355 const float* raw = logits::get(ctx, index); // throws if ctx is null or no logits at index
356 if (n_vocab <= 0 || logits_snapshot.size() < static_cast<size_t>(n_vocab)) {
357 throw std::runtime_error("branch: cannot capture logits, branch has no vocab (n_vocab=" +
358 std::to_string(n_vocab) + ")");
359 }
360 std::memcpy(logits_snapshot.data(), raw, static_cast<size_t>(n_vocab) * sizeof(float));
361 has_logits = true;
362 }
363
372 std::vector<llama_token_data> candidates_buffer;
373
375 int n_vocab = 0;
376
377 uint16_t generation = 0;
378 bool in_use = false;
379
380 // Topology — maintained by fork/prune/pruneSubtree
382 std::vector<BranchHandle> children;
383};
384
385// ===== BATCHED DECODE ITEM TYPES =====
386
395 llama_token token;
396};
397
413 std::span<const llama_token> tokens;
414};
415
420 int64_t cells = 0;
421 llama_pos advance = 0;
422};
423
436inline void require_distinct_handles(std::span<const BranchHandle> handles, const char* who) {
437 std::unordered_map<BranchHandle, size_t> first;
438 first.reserve(handles.size());
439 for (size_t i = 0; i < handles.size(); ++i) {
440 if (handles[i] == INVALID_HANDLE) continue;
441 const auto [it, inserted] = first.emplace(handles[i], i);
442 if (!inserted) {
443 throw std::runtime_error(std::string(who) + " - duplicate handle at indices " +
444 std::to_string(it->second) + " and " + std::to_string(i) +
445 " (sequential calls required for multiple runs per branch)");
446 }
447 }
448}
449
450// ===== BRANCH STORE (HANDLE TABLE) =====
451
491public:
496 explicit BranchStore(size_t initial_capacity = 16) {
497 // Ensure minimum capacity of 2 (slot 0 reserved + at least 1 usable)
498 if (initial_capacity < 2) {
499 initial_capacity = 2;
500 }
501 slots_.resize(initial_capacity);
502 // Slot 0 is reserved (handle 0 = invalid)
503 slots_[0].in_use = true;
504 slots_[0].generation = 0xFFFF; // Never valid
505
506 // Initialize freelist with remaining slots
507 // NOTE: Use i-- > 1 pattern to avoid size_t underflow
508 for (size_t i = initial_capacity; i-- > 1; ) {
509 freelist_.push_back(static_cast<uint16_t>(i));
510 }
511 }
512
516 if (tenancy_.ctx != nullptr) {
517 LLOYAL_LOG_DEBUG("[BranchStore] WARNING: not drained before destruction");
518 }
519 for (size_t i = 1; i < slots_.size(); ++i) {
520 if (slots_[i].in_use) {
521 free_branch_resources(slots_[i]);
522 }
523 }
524 }
525
527 struct Allocation { BranchHandle handle; llama_seq_id seq_id; };
528
538 if (tenancy_.ctx == nullptr) return {INVALID_HANDLE, NO_LEASE}; // drained
539 llama_seq_id seq = kv::tenancy::acquire(tenancy_);
540 if (seq < 0) return {INVALID_HANDLE, NO_LEASE};
541 BranchHandle handle = allocate_slot();
542 if (handle == INVALID_HANDLE) {
543 // Seq was never used — bookkeeping-only return, no KV calls
544 kv::tenancy::release(tenancy_, seq);
545 return {INVALID_HANDLE, NO_LEASE};
546 }
547 // Stamp seq_id on slot so release() can evict properly
548 BranchState* st = get(handle);
549 st->seq_id = seq;
550 return {handle, seq};
551 }
552
561 void release(BranchHandle handle) {
562 if (handle == INVALID_HANDLE) return;
563 BranchState* st = get(handle);
564 if (!st) return;
565 // Eager edge cleanup: remove from parent's children
566 if (st->parent != INVALID_HANDLE) {
567 BranchState* p = get(st->parent);
568 if (p) {
569 auto& c = p->children;
570 c.erase(std::remove(c.begin(), c.end(), handle), c.end());
571 }
572 }
573 // Subtract unique cells owned by this branch (above fork_head).
574 // Position-delta alone undercounts embedding-row prefills (cells grow by
575 // n_tokens while position advances by n_pos) — add the branch's own
576 // slack so the pressure gauge recovers exactly what was decoded.
577 {
578 uint32_t unique = st->img_slack_own;
579 if (st->position > st->fork_head) {
580 unique += static_cast<uint32_t>(st->position - st->fork_head);
581 }
582 if (unique > 0) {
583 cells_used_ = (unique <= cells_used_) ? cells_used_ - unique : 0;
584 }
585 }
586 // Evict lease (KV strip + bookkeeping)
587 if (st->seq_id != NO_LEASE)
588 kv::tenancy::evict(tenancy_, st->seq_id);
589 free_branch_resources(*st);
590 reset_slot(*st);
591 freelist_.push_back(handle_index(handle));
592
593 // All branches released → KV cache empty, reset pressure counter
594 if (freelist_.size() == slots_.size() - 1) {
595 cells_used_ = 0;
596 }
597 }
598
599 // ===== TENANCY LIFECYCLE =====
600
605 void init_tenancy(llama_context* ctx) {
606 tenancy_ = kv::tenancy::init(ctx, llama_n_seq_max(ctx));
607 cells_used_ = 0;
608 }
609
618 void drain() {
619 if (tenancy_.ctx == nullptr) return; // idempotent
620 kv::tenancy::evict_all(tenancy_);
621 for (size_t i = 1; i < slots_.size(); ++i) {
622 if (slots_[i].in_use) {
623 free_branch_resources(slots_[i]);
624 reset_slot(slots_[i]);
625 }
626 }
627 tenancy_.ctx = nullptr; // marks as drained
628 cells_used_ = 0;
629 }
630
641 BranchState* w = get(winner);
642 if (!w) throw std::runtime_error("retainOnly: invalid winner handle");
643 if (w->seq_id == NO_LEASE) throw std::runtime_error("retainOnly: winner has no lease");
644 kv::tenancy::retain(tenancy_, w->seq_id); // nuclear KV pass
645 // Collect losers first — don't mutate while iterating
646 std::vector<BranchHandle> losers;
647 for (size_t i = 1; i < slots_.size(); ++i) {
648 if (!slots_[i].in_use) continue;
649 BranchHandle h = make_handle(static_cast<uint16_t>(i), slots_[i].generation);
650 if (h == winner) continue;
651 losers.push_back(h);
652 }
653 for (auto h : losers)
654 release_slot_only(h); // CPU only, KV already stripped
656 w->fork_head = 0;
657 w->children.clear();
658 // The winner absorbs the whole prefix (fork_head = 0), so its "own"
659 // slack becomes the inherited total, and the pressure counter is the
660 // prefix's real cell count: position-delta + embedding-row slack.
662 cells_used_ = static_cast<uint32_t>(w->position) + w->img_slack_total;
663 ++w->revision;
664 }
665
698 BranchState* a = get(parent);
699 if (!a) throw std::runtime_error("fast_forward: invalid parent handle");
700 BranchState* p = get(child);
701 if (!p) throw std::runtime_error("fast_forward: invalid child handle");
702 if (p->parent != parent) {
703 throw std::runtime_error("fast_forward: the child was not forked from this parent");
704 }
705 if (a->seq_id == NO_LEASE || p->seq_id == NO_LEASE) {
706 throw std::runtime_error("fast_forward: a branch has no lease");
707 }
708 if (!p->children.empty()) {
709 throw std::runtime_error("fast_forward: the child has children of its own");
710 }
711 if (p->poisoned) {
712 throw std::runtime_error("fast_forward: the child is poisoned by a failed decode; prune it");
713 }
714 if (p->fork_head != a->position) {
715 throw std::runtime_error("fast_forward: the parent has decoded since the fork");
716 }
717 if (p->fork_revision != a->revision) {
718 throw std::runtime_error("fast_forward: the parent has changed since the fork");
719 }
720
721 std::swap(a->seq_id, p->seq_id);
722 std::swap(a->logits_snapshot, p->logits_snapshot);
723 std::swap(a->has_logits, p->has_logits);
724 std::swap(a->sampler_chain, p->sampler_chain);
725 std::swap(a->cached_params, p->cached_params);
726 std::swap(a->grammar, p->grammar);
728 std::swap(a->last_token, p->last_token);
729 std::swap(a->last_candidates, p->last_candidates);
730 std::swap(a->metrics, p->metrics);
731 std::swap(a->logit_bias, p->logit_bias);
732 a->position = p->position;
735 ++a->revision;
736
737 // The child's range is the parent's now, so its release subtracts nothing;
738 // it evicts the sequence and frees the resources the parent held before.
739 p->position = p->fork_head;
740 p->img_slack_own = 0;
741 release(child);
742 }
743
744 // ===== TOPOLOGY QUERIES =====
745
750 size_t available() const { return kv::tenancy::available(tenancy_); }
751
760 if (!tenancy_.ctx) return {0, 0, 0};
761 uint32_t n_ctx = static_cast<uint32_t>(llama_n_ctx(tenancy_.ctx));
762 uint32_t remaining = (n_ctx > cells_used_) ? n_ctx - cells_used_ : 0;
763 return { n_ctx, cells_used_, remaining };
764 }
765
767 void add_cells_used(uint32_t n) { cells_used_ += n; }
768
775 const BranchState* st = get(h);
776 return st ? st->parent : INVALID_HANDLE;
777 }
778
784 llama_pos fork_head(BranchHandle h) const {
785 const BranchState* st = get(h);
786 return st ? st->fork_head : 0;
787 }
788
794 const std::vector<BranchHandle>& children(BranchHandle h) const {
795 static const std::vector<BranchHandle> empty;
796 const BranchState* st = get(h);
797 return st ? st->children : empty;
798 }
799
805 bool isLeaf(BranchHandle h) const {
806 const BranchState* st = get(h);
807 return st ? st->children.empty() : true;
808 }
809
815 bool isActive(BranchHandle h) const {
816 const BranchState* st = get(h);
817 return st ? (st->seq_id != NO_LEASE) : false;
818 }
819
830 if (handle == INVALID_HANDLE) return nullptr;
831
832 uint16_t index = handle_index(handle);
833 uint16_t gen = handle_generation(handle);
834
835 // Slot 0 is reserved and never valid for external use
836 if (index == 0) return nullptr;
837
838 if (index >= slots_.size()) return nullptr;
839
840 BranchState& slot = slots_[index];
841 if (!slot.in_use || slot.generation != gen) {
842 return nullptr;
843 }
844
845 return &slot;
846 }
847
849 const BranchState* get(BranchHandle handle) const {
850 return const_cast<BranchStore*>(this)->get(handle);
851 }
852
853 // ===== SAMPLER CHAIN REGISTRY =====
854
860 template <SamplingParamsLike P>
861 SamplerChainHandle create_sampler(const P& params, int32_t n_vocab) {
862 SamplerChainHandle h = next_sampler_handle_++;
863 SamplerChainEntry entry;
864 entry.chain = sampler::create_chain(params, n_vocab);
865 float temperature = ::lloyal::detail::as_value(params.temperature, 0.8f);
866 entry.has_dist = (temperature > 0.0f);
867 sampler_chains_.emplace(h, std::move(entry));
868 return h;
869 }
870
877 if (h == 0) return 0;
878 auto it = sampler_chains_.find(h);
879 if (it == sampler_chains_.end()) return 0;
880 SamplerChainHandle nh = next_sampler_handle_++;
881 SamplerChainEntry entry;
882 entry.chain = sampler::clone_chain(it->second.chain);
883 entry.has_dist = it->second.has_dist;
884 sampler_chains_.emplace(nh, std::move(entry));
885 return nh;
886 }
887
893 if (h != 0) sampler_chains_.erase(h);
894 }
895
901 llama_sampler* get_sampler_chain(SamplerChainHandle h) const {
902 if (h == 0) return nullptr;
903 auto it = sampler_chains_.find(h);
904 return it != sampler_chains_.end() ? it->second.chain : nullptr;
905 }
906
913 if (h == 0) return false;
914 auto it = sampler_chains_.find(h);
915 return it != sampler_chains_.end() ? it->second.has_dist : false;
916 }
917
918 // ===== GRAMMAR REGISTRY =====
919
927 GrammarHandle create_grammar(const llama_model* model,
928 const char* grammar_str,
929 const char* root = "root") {
930 GrammarHandle h = next_grammar_handle_++;
931 GrammarEntry entry;
932 entry.sampler = grammar::init_sampler(model, grammar_str, root);
933 grammars_.emplace(h, std::move(entry));
934 return h;
935 }
936
947 const llama_model* model,
948 const char* grammar_str,
949 const std::vector<std::string>& trigger_patterns,
950 const std::vector<llama_token>& trigger_tokens,
951 const char* root = "root") {
952 GrammarHandle h = next_grammar_handle_++;
953 GrammarEntry entry;
955 model, grammar_str, trigger_patterns, trigger_tokens, root);
956 if (!entry.sampler) return 0;
957 grammars_.emplace(h, std::move(entry));
958 return h;
959 }
960
967 if (h == 0) return 0;
968 auto it = grammars_.find(h);
969 if (it == grammars_.end()) return 0;
970 GrammarHandle nh = next_grammar_handle_++;
971 GrammarEntry entry;
972 entry.sampler = grammar::clone_sampler(it->second.sampler);
973 grammars_.emplace(nh, std::move(entry));
974 return nh;
975 }
976
982 if (h != 0) grammars_.erase(h);
983 }
984
990 llama_sampler* get_grammar_sampler(GrammarHandle h) const {
991 if (h == 0) return nullptr;
992 auto it = grammars_.find(h);
993 return it != grammars_.end() ? it->second.sampler : nullptr;
994 }
995
996 // ===== METRICS REGISTRY =====
997
1003 MetricsHandle h = next_metrics_handle_++;
1004 metrics_registry_[h] = metrics::BranchMetricsState{};
1005 return h;
1006 }
1007
1014 if (h == 0) return 0;
1015 auto it = metrics_registry_.find(h);
1016 if (it == metrics_registry_.end()) return 0;
1017 MetricsHandle nh = next_metrics_handle_++;
1018 metrics_registry_[nh] = it->second;
1019 return nh;
1020 }
1021
1027 if (h != 0) metrics_registry_.erase(h);
1028 }
1029
1035 void add_model_surprisal(MetricsHandle h, float surprisal) {
1036 if (h == 0) return;
1037 auto it = metrics_registry_.find(h);
1038 if (it == metrics_registry_.end()) return;
1039 if (!std::isfinite(surprisal)) return;
1040 it->second.model.nll_sum_nats += std::max(0.0f, surprisal);
1041 it->second.model.count++;
1042 }
1043
1049 void add_sampling_surprisal(MetricsHandle h, float surprisal) {
1050 if (h == 0) return;
1051 auto it = metrics_registry_.find(h);
1052 if (it == metrics_registry_.end()) return;
1053 if (!std::isfinite(surprisal)) return;
1054 it->second.sampling.nll_sum_nats += std::max(0.0f, surprisal);
1055 it->second.sampling.count++;
1056 }
1057
1064 if (h == 0) return std::numeric_limits<float>::infinity();
1065 auto it = metrics_registry_.find(h);
1066 if (it == metrics_registry_.end() || it->second.model.count == 0)
1067 return std::numeric_limits<float>::infinity();
1068 return std::exp(it->second.model.nll_sum_nats /
1069 static_cast<float>(it->second.model.count));
1070 }
1071
1078 if (h == 0) return std::numeric_limits<float>::infinity();
1079 auto it = metrics_registry_.find(h);
1080 if (it == metrics_registry_.end() || it->second.sampling.count == 0)
1081 return std::numeric_limits<float>::infinity();
1082 return std::exp(it->second.sampling.nll_sum_nats /
1083 static_cast<float>(it->second.sampling.count));
1084 }
1085
1086 // ===== BATCHED DECODE =====
1087
1110 void decode_each(std::span<const DecodeEachItem> items) {
1111 if (items.empty()) return;
1112
1113 const int32_t n = static_cast<int32_t>(items.size());
1114
1115 // Validate handles and collect states
1116 std::vector<BranchState*> states(n);
1117 for (int32_t i = 0; i < n; ++i) {
1118 states[i] = get(items[i].handle);
1119 if (!states[i]) {
1120 throw std::runtime_error("BranchStore::decode_each - invalid handle at index " + std::to_string(i));
1121 }
1122 if (i > 0 && states[i]->ctx != states[0]->ctx) {
1123 throw std::runtime_error("BranchStore::decode_each - all branches must share the same context");
1124 }
1125 }
1126
1127 {
1128 std::vector<BranchHandle> handles(n);
1129 for (int32_t i = 0; i < n; ++i) handles[i] = items[i].handle;
1130 require_distinct_handles(handles, "BranchStore::decode_each");
1131 }
1132
1133 // Build EachItem array from branch states
1134 std::vector<decode::EachItem> decode_items(n);
1135 for (int32_t i = 0; i < n; ++i) {
1136 decode_items[i].token = items[i].token;
1137 decode_items[i].pos = states[i]->position;
1138 decode_items[i].seq_id = states[i]->seq_id;
1139 decode_items[i].output_logits = true;
1140 }
1141
1142 // Single GPU dispatch
1143 if (const int32_t rc = decode::each(states[0]->ctx, decode_items.data(), n, scratch_); rc != 0) {
1144 if (!decode::leaves_branch_intact(rc, /*partial*/ false)) {
1145 for (auto* st : states) st->poison();
1146 }
1147 throw decode::DecodeError(rc, /*partial*/ false, "BranchStore::decode_each - llama_decode failed");
1148 }
1149
1150 // Capture logits and update positions
1151 for (int32_t i = 0; i < n; ++i) {
1152 states[i]->capture_logits(i);
1153 states[i]->position += 1;
1154 ++states[i]->revision;
1155 }
1156 cells_used_ += static_cast<uint32_t>(items.size());
1157 }
1158
1189 void decode_scatter(std::span<const DecodeScatterItem> items) {
1190 if (items.empty()) return;
1191
1192 const int32_t n = static_cast<int32_t>(items.size());
1193
1194 // Validate handles and collect states
1195 std::vector<BranchState*> states(n);
1196 for (int32_t i = 0; i < n; ++i) {
1197 states[i] = get(items[i].handle);
1198 if (!states[i]) {
1199 throw std::runtime_error("BranchStore::decode_scatter - invalid handle at index " + std::to_string(i));
1200 }
1201 if (i > 0 && states[i]->ctx != states[0]->ctx) {
1202 throw std::runtime_error("BranchStore::decode_scatter - all branches must share the same context");
1203 }
1204 }
1205 {
1206 // Only spans that DECODE can collide: an empty item is skipped by
1207 // bin_pack and occupies no cells, so it is INVALID_HANDLE to the rule.
1208 std::vector<BranchHandle> material(n);
1209 for (int32_t i = 0; i < n; ++i) {
1210 material[i] = items[i].tokens.empty() ? INVALID_HANDLE : items[i].handle;
1211 }
1212 require_distinct_handles(material, "BranchStore::decode_scatter");
1213 }
1214
1215 llama_context* ctx = states[0]->ctx;
1216 const int32_t batch_limit = static_cast<int32_t>(llama_n_batch(ctx));
1217
1218 // Build flat token spans — bin_pack skips empties internally
1219 std::vector<std::span<const llama_token>> spans(n);
1220 for (int32_t i = 0; i < n; ++i) {
1221 spans[i] = items[i].tokens;
1222 }
1223
1224 auto chunks = decode::bin_pack(spans.data(), n, batch_limit);
1225
1226 int32_t landed = 0; // chunks whose llama_decode returned 0
1227 for (const auto& chunk : chunks) {
1228 if (chunk.oversized) {
1229 int32_t idx = chunk.indices[0];
1230 int32_t tc = static_cast<int32_t>(items[idx].tokens.size());
1231
1232 int32_t committed = 0;
1233 if (const int32_t rc = decode::many(ctx, items[idx].tokens.data(), tc,
1234 states[idx]->position, batch_limit,
1235 states[idx]->seq_id, &committed); rc != 0) {
1236 if (!decode::leaves_branch_intact(rc, committed > 0)) states[idx]->poison();
1237 throw decode::DecodeError(rc, landed > 0 || committed > 0,
1238 "BranchStore::decode_scatter - decode::many failed for oversized item " + std::to_string(idx));
1239 }
1240
1241 land_tokens(*states[idx], tc, -1);
1242 ++landed;
1243 continue;
1244 }
1245
1246 // Normal chunk — build ScatterItems and dispatch
1247 std::vector<decode::ScatterItem> scatter_items(chunk.indices.size());
1248 for (size_t k = 0; k < chunk.indices.size(); ++k) {
1249 int32_t idx = chunk.indices[k];
1250 scatter_items[k].tokens = items[idx].tokens;
1251 scatter_items[k].start_pos = states[idx]->position;
1252 scatter_items[k].seq_id = states[idx]->seq_id;
1253 scatter_items[k].output_logits = true;
1254 }
1255
1256 if (const int32_t rc = decode::scatter(ctx, scatter_items.data(),
1257 static_cast<int32_t>(scatter_items.size()),
1258 scratch_); rc != 0) {
1259 // Each branch in the failing chunk lost only this call: intact iff it restored.
1260 if (!decode::leaves_branch_intact(rc, /*partial*/ false)) {
1261 for (const int32_t idx : chunk.indices) states[idx]->poison();
1262 }
1263 throw decode::DecodeError(rc, landed > 0, "BranchStore::decode_scatter - decode::scatter failed");
1264 }
1265
1266 // Capture logits and move the books for each item in the chunk
1267 int32_t cursor = 0;
1268 for (size_t k = 0; k < scatter_items.size(); ++k) {
1269 int32_t idx = chunk.indices[k];
1270 int32_t item_n = static_cast<int32_t>(scatter_items[k].tokens.size());
1271
1272 land_tokens(*states[idx], item_n, cursor + item_n - 1);
1273 cursor += item_n;
1274 }
1275 ++landed;
1276 }
1277 }
1278
1341 const float* rows, int32_t n_tokens, int32_t n_embd_inp,
1342 llama_pos n_pos,
1343 const llama_pos* pos, int32_t n_pos_per_embd,
1344 bool non_causal, bool want_logits) {
1345 BranchState* state = get(handle);
1346 if (!state) {
1347 throw std::runtime_error("BranchStore::decode_embd - invalid handle");
1348 }
1349 if (n_pos <= 0 || n_pos > n_tokens) {
1350 throw std::runtime_error("BranchStore::decode_embd - invalid n_pos");
1351 }
1352
1353 decode::EmbdItem item;
1354 item.rows = rows;
1355 item.n_rows = n_tokens;
1356 item.n_embd_inp = n_embd_inp;
1357 item.pos = pos;
1358 item.n_pos_per_embd = n_pos_per_embd;
1359 item.seq_id = state->seq_id;
1360 item.non_causal = non_causal;
1361 item.output_logits = want_logits;
1362
1363 // NOT atomic, and deliberately not made so. decode::embd chunks by
1364 // n_batch, so a failure on a later chunk leaves EARLIER chunks committed
1365 // while the counters below never move. Rolling those rows back is not
1366 // available: seq_rm can only rewind a recurrent carrier where the model
1367 // keeps per-token snapshots, and Gated DeltaNet reports rs_seq = 0, so a
1368 // rollback there silently restores attention and not the carrier.
1369 //
1370 // The branch is therefore poisoned on failure — prune it and replay onto
1371 // a fresh one, which is the portable correction regardless of layer type.
1372 // The error's `partial` says whether any chunk landed (see DecodeError).
1373 // Its own accounting stays CONSISTENT for that prune: neither position
1374 // nor cells_used_ moved, so release() subtracts what the branch legitimately
1375 // owned and whole-sequence eviction reclaims the orphaned rows.
1376 //
1377 // Admission is the better place to spend effort: SegmentSource::cells()
1378 // reports this prefill's cost before anything decodes, so a caller can
1379 // refuse rather than half-commit.
1380 int32_t committed = 0;
1381 if (const int32_t rc = decode::embd(state->ctx, item, state->n_batch, scratch_, &committed); rc != 0) {
1382 if (!decode::leaves_branch_intact(rc, committed > 0)) state->poison();
1383 throw decode::DecodeError(rc, committed > 0,
1384 "BranchStore::decode_embd - llama_decode failed; this branch is "
1385 "poisoned, prune it and replay onto a fresh one");
1386 }
1387
1388 land_rows(*state, n_tokens, n_pos, want_logits);
1389 }
1390
1431 const std::function<void(const decode::SegmentProgress&)>& on_progress = {}) {
1432 BranchState* state = get(handle);
1433 if (!state) {
1434 throw std::runtime_error("BranchStore::decode_segments - invalid handle");
1435 }
1436
1437 const llama_pos start_pos = state->position;
1438 DecodeSegmentsResult result;
1439
1440 std::vector<size_t> landed_by_input;
1441 const auto report = [&](const decode::LandedSegment& seg) {
1442 const int32_t input = source.input_of(seg.index);
1443 if (!on_progress || input < 0) return;
1444 if (landed_by_input.size() <= static_cast<size_t>(input)) landed_by_input.resize(input + 1, 0);
1445 landed_by_input[input] += static_cast<size_t>(seg.cells);
1446 on_progress(decode::SegmentProgress{input, landed_by_input[input], source.total_of(input)});
1447 };
1448
1449 try {
1450 decode::segments(state->ctx, state->seq_id, start_pos, source, state->n_batch,
1451 scratch_, segment_pos_, [&](const decode::LandedSegment& seg) {
1452 if (seg.kind == decode::Segment::Kind::Text) {
1453 land_tokens(*state, seg.cells, -1);
1454 } else {
1455 land_rows(*state, seg.cells, seg.n_pos, seg.has_logits);
1456 }
1457 result.cells += seg.cells;
1458 report(seg);
1459 });
1460 } catch (const decode::DecodeError& e) {
1461 // One operation to the caller. The rail marks its own failures partial
1462 // when an earlier segment landed or a chunk of this one did; a decode
1463 // error from the source or from on_progress cannot know what landed. The
1464 // branch's position does: every segment is material, so a moved position
1465 // is proof the prefill is part-way through.
1466 const bool moved = state->position != start_pos;
1467 if (moved || !decode::leaves_branch_intact(e.rc, e.partial)) state->poison();
1468 if (e.partial || !moved) throw;
1469 throw e.as_partial();
1470 } catch (...) {
1471 // The source threw after a segment landed: the branch is part-way through.
1472 if (state->position != start_pos) state->poison();
1473 throw;
1474 }
1475
1476 result.advance = state->position - start_pos;
1477 return result;
1478 }
1479
1505 BranchHandle dst_handle,
1506 std::span<const BranchHandle> expert_handles,
1507 float alpha) {
1508 BranchState* dst = get(dst_handle);
1509 if (!dst) {
1510 throw std::runtime_error("BranchStore::merge_logits - invalid dst handle");
1511 }
1512 if (!dst->has_logits) {
1513 throw std::runtime_error("BranchStore::merge_logits - dst has no captured logits");
1514 }
1515 if (dst->n_vocab <= 0) {
1516 throw std::runtime_error("BranchStore::merge_logits - dst n_vocab invalid");
1517 }
1518
1519 const int32_t n_vocab = dst->n_vocab;
1520
1521 // Every expert is checked before dst is written, so a refusal changes nothing.
1522 std::vector<const float*> sources;
1523 sources.reserve(expert_handles.size());
1524 for (size_t i = 0; i < expert_handles.size(); ++i) {
1525 BranchState* src = get(expert_handles[i]);
1526 if (!src) {
1527 throw std::runtime_error(
1528 "BranchStore::merge_logits - invalid expert handle at index " +
1529 std::to_string(i));
1530 }
1531 if (!src->has_logits) {
1532 throw std::runtime_error(
1533 "BranchStore::merge_logits - expert has no captured logits at index " +
1534 std::to_string(i));
1535 }
1536 if (src->n_vocab != n_vocab) {
1537 throw std::runtime_error(
1538 "BranchStore::merge_logits - n_vocab mismatch at index " +
1539 std::to_string(i));
1540 }
1541 sources.push_back(src->logits_snapshot.data());
1542 }
1543 if (sources.empty()) return; // nothing merged, nothing changed
1544
1545 float* dst_logits = dst->logits_snapshot.data();
1546 for (const float* src_logits : sources) {
1547 for (int32_t t = 0; t < n_vocab; ++t) {
1548 dst_logits[t] += alpha * src_logits[t];
1549 }
1550 }
1551 ++dst->revision;
1552 }
1553
1554private:
1557 void land_tokens(BranchState& state, int32_t n_tokens, int32_t logits_index) {
1558 state.capture_logits(logits_index);
1559 state.position += n_tokens;
1560 ++state.revision;
1561 cells_used_ += static_cast<uint32_t>(n_tokens);
1562 }
1563
1566 void land_rows(BranchState& state, int32_t n_rows, llama_pos n_pos, bool with_logits) {
1567 state.position += n_pos;
1568 cells_used_ += static_cast<uint32_t>(n_rows);
1569 const uint32_t slack = static_cast<uint32_t>(n_rows - n_pos);
1570 state.img_slack_own += slack;
1571 state.img_slack_total += slack;
1572 ++state.revision;
1573
1574 if (with_logits) {
1575 state.capture_logits(-1);
1576 } else {
1577 // The position advanced but no logits were computed for it. Leaving the
1578 // previous snapshot in place would let sample() read logits belonging to
1579 // an EARLIER position — wrong, and silently so. The token rails always
1580 // recapture, so rows are the one place that can strand them.
1581 state.has_logits = false;
1582 }
1583 }
1584
1586 void free_branch_resources(BranchState& slot) {
1587 if (slot.sampler_chain != 0) {
1588 free_sampler(slot.sampler_chain);
1589 slot.sampler_chain = 0;
1590 }
1591 if (slot.grammar != 0) {
1592 free_grammar(slot.grammar);
1593 slot.grammar = 0;
1594 }
1595 if (slot.boundary_tracker) {
1596 delete slot.boundary_tracker;
1597 slot.boundary_tracker = nullptr;
1598 }
1599 if (slot.metrics != 0) {
1600 free_metrics(slot.metrics);
1601 slot.metrics = 0;
1602 }
1603 }
1604
1606 void reset_slot(BranchState& slot) {
1607 slot.in_use = false;
1608 slot.generation = static_cast<uint16_t>(slot.generation + 1); // Prevent ABA
1609 slot.ctx = nullptr;
1610 slot.model = nullptr;
1611 slot.seq_id = NO_LEASE;
1612 slot.position = 0;
1613 slot.fork_head = 0;
1614 slot.revision = 0;
1615 slot.fork_revision = 0;
1616 slot.poisoned = false;
1617 slot.img_slack_own = 0;
1618 slot.img_slack_total = 0;
1619 slot.sampler_chain = 0;
1620 slot.grammar = 0;
1621 slot.grammar_sample_first = false;
1622 slot.metrics = 0;
1623 slot.cached_params = CachedSamplingParams{};
1624 slot.last_token = -1;
1625 slot.last_candidates.clear();
1626 slot.logits_snapshot.clear();
1627 slot.has_logits = false;
1628 slot.logit_bias.clear();
1629 slot.steer_fn = nullptr;
1630 slot.candidates_buffer.clear();
1631 slot.n_batch = DEFAULT_N_BATCH;
1632 slot.n_vocab = 0;
1633 slot.parent = INVALID_HANDLE;
1634 slot.children.clear();
1635 }
1636
1638 BranchHandle allocate_slot() {
1639 if (freelist_.empty()) {
1640 size_t old_size = slots_.size();
1641 size_t new_size = old_size * 2;
1642 if (new_size > INDEX_MASK) {
1643 new_size = INDEX_MASK + 1;
1644 }
1645 if (old_size >= new_size) {
1646 LLOYAL_LOG_DEBUG("[branch::allocate_slot] Store full, cannot allocate");
1647 return INVALID_HANDLE;
1648 }
1649 slots_.resize(new_size);
1650 for (size_t i = new_size; i-- > old_size; ) {
1651 freelist_.push_back(static_cast<uint16_t>(i));
1652 }
1653 }
1654 uint16_t index = freelist_.back();
1655 freelist_.pop_back();
1656 BranchState& slot = slots_[index];
1657 slot.in_use = true;
1658 return make_handle(index, slot.generation);
1659 }
1660
1663 void release_slot_only(BranchHandle handle) {
1664 if (handle == INVALID_HANDLE) return;
1665 BranchState* st = get(handle);
1666 if (!st) return;
1667 // Eager edge cleanup
1668 if (st->parent != INVALID_HANDLE) {
1669 BranchState* p = get(st->parent);
1670 if (p) {
1671 auto& c = p->children;
1672 c.erase(std::remove(c.begin(), c.end(), handle), c.end());
1673 }
1674 }
1675 free_branch_resources(*st);
1676 reset_slot(*st);
1677 freelist_.push_back(handle_index(handle));
1678 }
1679
1682 std::deque<BranchState> slots_;
1683 std::vector<uint16_t> freelist_;
1684
1689 kv::tenancy::State tenancy_;
1690
1694 uint32_t cells_used_ = 0;
1695
1698 decode::Scratch scratch_;
1699
1702 std::vector<llama_pos> segment_pos_;
1703
1704 // ===== Handle registries (instance-scoped, not global static) =====
1705
1706 std::unordered_map<SamplerChainHandle, SamplerChainEntry> sampler_chains_;
1707 SamplerChainHandle next_sampler_handle_ = 1;
1708
1709 std::unordered_map<GrammarHandle, GrammarEntry> grammars_;
1710 GrammarHandle next_grammar_handle_ = 1;
1711
1712 std::unordered_map<MetricsHandle, metrics::BranchMetricsState> metrics_registry_;
1713 MetricsHandle next_metrics_handle_ = 1;
1714};
1715
1716
1717// ===== BRANCH API =====
1718
1739template <SamplingParamsLike P>
1741 llama_context* ctx,
1742 const llama_model* model,
1743 BranchStore& s,
1744 llama_pos start_pos,
1745 const P& params,
1746 int n_batch = DEFAULT_N_BATCH,
1747 const char* grammar_str = nullptr,
1748 boundaries::BoundaryTracker* boundary_tracker = nullptr) {
1749 if (!ctx || !model) {
1750 LLOYAL_LOG_DEBUG("[branch::create] NULL ctx or model");
1751 return INVALID_HANDLE;
1752 }
1753
1754 // The invariant every logits capture stands on is established here, at
1755 // birth: a live branch has somewhere to put its logits. Decided BEFORE
1756 // anything is allocated, so a refusal owns nothing — no slot, no lease,
1757 // and no range for release() to subtract from the store's books.
1758 const llama_vocab* vocab = llama_model_get_vocab(model);
1759 const int n_vocab = llama_vocab_n_tokens(vocab);
1760 if (n_vocab <= 0) {
1761 throw std::runtime_error("branch::create - model has no vocab (n_vocab=" +
1762 std::to_string(n_vocab) + "); nothing to sample");
1763 }
1764
1765 auto [handle, seq_id] = s.allocate();
1766 if (handle == INVALID_HANDLE) {
1767 return INVALID_HANDLE;
1768 }
1769
1770 BranchState* state = s.get(handle);
1771 if (!state) {
1772 s.release(handle);
1773 return INVALID_HANDLE;
1774 }
1775
1776 state->ctx = ctx;
1777 state->model = model;
1778 // seq_id already stamped by allocate()
1779 state->position = start_pos;
1780 state->n_batch = n_batch;
1781
1782 state->n_vocab = n_vocab;
1783 state->logits_snapshot.resize(state->n_vocab);
1784 state->has_logits = false;
1785 state->candidates_buffer.resize(state->n_vocab);
1786
1787 // The chain is built from the resolved record, so the cache says exactly
1788 // what the chain draws with — a seedless branch's clock seed included.
1789 state->cached_params = snapshot_params(params, clock_seed());
1790 state->sampler_chain = s.create_sampler(state->cached_params, n_vocab);
1791
1792 if (grammar_str && grammar_str[0] != '\0') {
1793 state->grammar = s.create_grammar(model, grammar_str);
1794 }
1795
1796 state->boundary_tracker = boundary_tracker;
1797 state->metrics = s.create_metrics();
1798
1799 LLOYAL_LOG_DEBUG("[branch::create] Created branch handle=%u seq=%d pos=%d",
1800 handle, seq_id, start_pos);
1801
1802 return handle;
1803}
1804
1805
1814struct ForkOpts {
1820 bool clone_logits = true;
1821};
1822
1845inline BranchHandle fork(BranchHandle source, BranchStore& s, ForkOpts opts = {}) {
1846 BranchState* src = s.get(source);
1847 if (!src) {
1848 LLOYAL_LOG_DEBUG("[branch::fork] Invalid source handle");
1849 return INVALID_HANDLE;
1850 }
1851
1852 auto [new_handle, new_seq_id] = s.allocate();
1853 if (new_handle == INVALID_HANDLE) {
1854 return INVALID_HANDLE;
1855 }
1856
1857 BranchState* dst = s.get(new_handle);
1858 if (!dst) {
1859 s.release(new_handle);
1860 return INVALID_HANDLE;
1861 }
1862
1863 // Copy basic state
1864 dst->ctx = src->ctx;
1865 dst->model = src->model;
1866 dst->seq_id = new_seq_id;
1867 dst->position = src->position;
1868 dst->fork_head = src->position;
1869 dst->fork_revision = src->revision;
1870 dst->poisoned = src->poisoned;
1871 // Inherit the prefix's embedding-row slack; the child owns none yet.
1872 dst->img_slack_total = src->img_slack_total;
1873 dst->img_slack_own = 0;
1874 dst->n_batch = src->n_batch;
1875 dst->n_vocab = src->n_vocab;
1876
1877#ifndef NDEBUG
1878 assert(kv::pos_max(src->ctx, new_seq_id) < 0 && "tenancy: acquired seq must be clean");
1879 assert(dst->parent == INVALID_HANDLE && dst->children.empty() && "fresh slot must have no topology");
1880#endif
1881
1882 // Fork KV cache
1883 kv::seq_cp(src->ctx, src->seq_id, new_seq_id);
1884
1885 // Record topology
1886 dst->parent = source;
1887 src->children.push_back(new_handle);
1888
1889 // Clone sampler chain
1890 if (src->sampler_chain != 0) {
1891 dst->sampler_chain = s.clone_sampler(src->sampler_chain);
1892 }
1893 dst->cached_params = src->cached_params;
1894
1895 if (src->grammar != 0) {
1896 dst->grammar = s.clone_grammar(src->grammar);
1897 }
1898 dst->grammar_sample_first = src->grammar_sample_first;
1899
1900 if (src->boundary_tracker) {
1901 dst->boundary_tracker = src->boundary_tracker->clone().release();
1902 }
1903
1904 if (src->metrics != 0) {
1905 dst->metrics = s.clone_metrics(src->metrics);
1906 }
1907
1908 dst->last_token = src->last_token;
1909 dst->last_candidates = src->last_candidates;
1910
1911 // Logits snapshot: default preserves fork's "sample same distribution" contract.
1912 // opts.clone_logits=false skips the ~600KB memcpy from src for prefill-overwrite
1913 // consumers (rerank leaves, embedding probes). Buffer is still kept sized to
1914 // n_vocab so subsequent decode_scatter/prefill can write into it.
1915 if (opts.clone_logits) {
1916 dst->logits_snapshot = src->logits_snapshot;
1917 dst->has_logits = src->has_logits;
1918 } else {
1919 if (static_cast<int32_t>(dst->logits_snapshot.size()) != dst->n_vocab) {
1920 dst->logits_snapshot.resize(dst->n_vocab);
1921 }
1922 dst->has_logits = false;
1923 }
1924 dst->logit_bias = src->logit_bias;
1925
1926 dst->candidates_buffer.resize(dst->n_vocab);
1927
1928 LLOYAL_LOG_DEBUG("[branch::fork] Forked handle=%u -> handle=%u seq=%d->%d",
1929 source, new_handle, src->seq_id, new_seq_id);
1930
1931 return new_handle;
1932}
1933
1954inline void set_logit_bias(
1955 BranchHandle handle,
1956 const llama_logit_bias* biases,
1957 size_t n_biases,
1958 BranchStore& s) {
1959
1960
1961 BranchState* state = s.get(handle);
1962 if (!state) {
1963 throw std::runtime_error("set_logit_bias: invalid branch handle");
1964 }
1965
1966 // Replace existing biases with new set
1967 state->logit_bias.assign(biases, biases + n_biases);
1968 ++state->revision;
1969
1970 LLOYAL_LOG_DEBUG("[branch::set_logit_bias] Set %zu biases on handle=%u",
1971 n_biases, handle);
1972}
1973
1982 BranchHandle handle,
1983 BranchStore& s) {
1984
1985
1986 BranchState* state = s.get(handle);
1987 if (!state) {
1988 throw std::runtime_error("clear_logit_bias: invalid branch handle");
1989 }
1990
1991 state->logit_bias.clear();
1992 ++state->revision;
1993
1994 LLOYAL_LOG_DEBUG("[branch::clear_logit_bias] Cleared biases on handle=%u", handle);
1995}
1996
2023inline void set_steer(
2024 BranchHandle handle,
2025 std::function<void(llama_token_data_array&)> steer_fn,
2026 BranchStore& s) {
2027
2028
2029 BranchState* state = s.get(handle);
2030 if (!state) {
2031 throw std::runtime_error("set_steer: invalid branch handle");
2032 }
2033
2034 state->steer_fn = std::move(steer_fn);
2035
2036 LLOYAL_LOG_DEBUG("[branch::set_steer] Set steer callback on handle=%u", handle);
2037}
2038
2046inline void clear_steer(
2047 BranchHandle handle,
2048 BranchStore& s) {
2049
2050
2051 BranchState* state = s.get(handle);
2052 if (!state) {
2053 throw std::runtime_error("clear_steer: invalid branch handle");
2054 }
2055
2056 state->steer_fn = nullptr;
2057
2058 LLOYAL_LOG_DEBUG("[branch::clear_steer] Cleared steer callback on handle=%u", handle);
2059}
2060
2079template <SamplingParamsLike P>
2080inline void set_sampler_params(BranchHandle handle, const P& params, BranchStore& s) {
2081 BranchState* state = s.get(handle);
2082 if (!state) {
2083 throw std::runtime_error("set_sampler_params: invalid branch handle");
2084 }
2085
2086 CachedSamplingParams new_params = snapshot_params(params, state->cached_params.seed);
2087 if (new_params == state->cached_params && state->sampler_chain != 0) {
2088 return; // Memoized — no rebuild needed
2089 }
2090
2091 // Free old chain
2092 if (state->sampler_chain != 0) {
2093 s.free_sampler(state->sampler_chain);
2094 }
2095
2096 // Create new chain, from the resolved record so the chain draws with the seed the cache says.
2097 state->sampler_chain = s.create_sampler(new_params, state->n_vocab);
2098 state->cached_params = new_params;
2099 ++state->revision;
2100
2101 LLOYAL_LOG_DEBUG("[branch::set_sampler_params] Rebuilt chain on handle=%u temp=%.3f",
2102 handle, new_params.temperature);
2103}
2104
2117inline void set_grammar(
2118 BranchHandle handle,
2119 const llama_model* model,
2120 const char* grammar_str,
2121 BranchStore& s) {
2122 BranchState* state = s.get(handle);
2123 if (!state) {
2124 throw std::runtime_error("set_grammar: invalid branch handle");
2125 }
2126
2127 // Free old grammar
2128 if (state->grammar != 0) {
2129 s.free_grammar(state->grammar);
2130 state->grammar = 0;
2131 }
2132 state->grammar_sample_first = false;
2133
2134 // Create new grammar if provided
2135 if (grammar_str && grammar_str[0] != '\0') {
2136 state->grammar = s.create_grammar(model, grammar_str);
2137 }
2138 ++state->revision;
2139
2140 LLOYAL_LOG_DEBUG("[branch::set_grammar] %s grammar on handle=%u",
2141 state->grammar != 0 ? "Set" : "Cleared", handle);
2142}
2143
2159 BranchHandle handle,
2160 const llama_model* model,
2161 const char* grammar_str,
2162 const std::vector<std::string>& trigger_patterns,
2163 const std::vector<llama_token>& trigger_tokens,
2164 BranchStore& s) {
2165 BranchState* state = s.get(handle);
2166 if (!state) {
2167 throw std::runtime_error("set_grammar_lazy: invalid branch handle");
2168 }
2169
2170 if (state->grammar != 0) {
2171 s.free_grammar(state->grammar);
2172 state->grammar = 0;
2173 }
2174 state->grammar_sample_first = false;
2175
2176 if (grammar_str && grammar_str[0] != '\0') {
2177 state->grammar = s.create_grammar_lazy(
2178 model, grammar_str, trigger_patterns, trigger_tokens);
2179 }
2180 ++state->revision;
2181
2182 LLOYAL_LOG_DEBUG("[branch::set_grammar_lazy] %s grammar on handle=%u",
2183 state->grammar != 0 ? "Set" : "Cleared", handle);
2184}
2185
2200inline void set_grammar_order(BranchHandle handle, bool sample_first, BranchStore& s) {
2201 BranchState* state = s.get(handle);
2202 if (!state) {
2203 throw std::runtime_error("set_grammar_order: invalid branch handle");
2204 }
2205 if (state->grammar_sample_first != sample_first) {
2206 state->grammar_sample_first = sample_first;
2207 ++state->revision;
2208 }
2209}
2210
2221inline void prune(BranchHandle handle, BranchStore& s) {
2222 BranchState* state = s.get(handle);
2223 if (!state) return;
2224 if (!state->children.empty())
2225 throw std::runtime_error("prune: RESTRICT — branch has children. Use pruneSubtree() for CASCADE.");
2226 s.release(handle);
2227}
2228
2239 std::vector<BranchHandle> stack{h}, post_order;
2240 while (!stack.empty()) {
2241 BranchHandle cur = stack.back(); stack.pop_back();
2242 post_order.push_back(cur);
2243 BranchState* st = s.get(cur);
2244 if (st) for (auto child : st->children) stack.push_back(child);
2245 }
2246 for (auto it = post_order.rbegin(); it != post_order.rend(); ++it)
2247 prune(*it, s);
2248}
2249
2262inline void reseed_sampler(BranchHandle handle, uint32_t seed, BranchStore& s) {
2263 BranchState* state = s.get(handle);
2264 if (!state) {
2265 throw std::runtime_error("reseed_sampler: invalid branch handle");
2266 }
2267 if (!s.sampler_has_dist(state->sampler_chain)) return;
2268 sampler::reseed_chain(s.get_sampler_chain(state->sampler_chain), seed);
2269 state->cached_params.seed = seed;
2270 ++state->revision;
2271}
2272
2276inline void fast_forward(BranchHandle parent, BranchHandle child, BranchStore& s) {
2277 s.fast_forward(parent, child);
2278}
2279
2284inline bool is_poisoned(BranchHandle handle, BranchStore& s) {
2285 const BranchState* state = s.get(handle);
2286 return state && state->poisoned;
2287}
2288
2309
2310
2311 BranchState* state = s.get(handle);
2312 if (!state) {
2313 throw std::runtime_error("force_snapshot_logits: invalid branch handle");
2314 }
2315
2316 state->capture_logits(-1);
2317 ++state->revision;
2318}
2319
2336inline void prefill(
2337 BranchHandle handle,
2338 const llama_token* tokens,
2339 size_t n_tokens,
2340 BranchStore& s) {
2341
2342
2343 BranchState* state = s.get(handle);
2344 if (!state) {
2345 throw std::runtime_error("prefill: invalid branch handle");
2346 }
2347
2348 // Pass raw pointer directly - no vector copy needed
2349 int32_t committed = 0;
2350 if (const int32_t rc = decode::many(state->ctx, tokens, static_cast<int32_t>(n_tokens),
2351 state->position, state->n_batch, state->seq_id,
2352 &committed); rc != 0) {
2353 if (!decode::leaves_branch_intact(rc, committed > 0)) state->poison();
2354 throw decode::DecodeError(rc, committed > 0, "prefill: llama_decode failed");
2355 }
2356
2357 state->position += static_cast<llama_pos>(n_tokens);
2358 s.add_cells_used(static_cast<uint32_t>(n_tokens));
2359 state->capture_logits(-1);
2360 ++state->revision;
2361}
2362
2375inline void step(
2376 BranchHandle handle,
2377 llama_token token,
2378 BranchStore& s) {
2379
2380
2381 BranchState* state = s.get(handle);
2382 if (!state) {
2383 throw std::runtime_error("step: invalid branch handle");
2384 }
2385
2386 if (const int32_t rc = decode::one(state->ctx, token, state->position, state->seq_id, true); rc != 0) {
2387 if (!decode::leaves_branch_intact(rc, /*partial*/ false)) state->poison();
2388 throw decode::DecodeError(rc, /*partial*/ false, "step: llama_decode failed");
2389 }
2390 state->position += 1;
2391 s.add_cells_used(1);
2392 state->capture_logits(-1);
2393 ++state->revision;
2394}
2395
2406inline const float* get_logits(BranchHandle handle, BranchStore& s) {
2407
2408
2409 const BranchState* state = s.get(handle);
2410 // Must check has_logits, not just empty() - buffer is pre-allocated in create()
2411 if (!state || !state->has_logits) {
2412 return nullptr;
2413 }
2414
2415 return state->logits_snapshot.data();
2416}
2417
2436inline void set_logits(BranchHandle handle, std::span<const float> data, BranchStore& s) {
2437 BranchState* state = s.get(handle);
2438 if (!state) {
2439 throw std::runtime_error("set_logits: invalid handle");
2440 }
2441 if (state->n_vocab <= 0) {
2442 throw std::runtime_error("set_logits: invalid vocab size");
2443 }
2444 if (static_cast<int32_t>(data.size()) != state->n_vocab) {
2445 throw std::runtime_error(
2446 "set_logits: data length (" + std::to_string(data.size()) +
2447 ") does not match branch n_vocab (" + std::to_string(state->n_vocab) + ")");
2448 }
2449
2450 std::memcpy(state->logits_snapshot.data(), data.data(),
2451 state->n_vocab * sizeof(float));
2452 state->has_logits = true;
2453 ++state->revision;
2454}
2455
2476inline void get_logits_at(BranchHandle handle,
2477 std::span<const int32_t> indices,
2478 std::span<float> out,
2479 BranchStore& s) {
2480 if (indices.size() != out.size()) {
2481 throw std::runtime_error("get_logits_at: indices/out size mismatch");
2482 }
2483 const BranchState* state = s.get(handle);
2484 if (!state) {
2485 throw std::runtime_error("get_logits_at: invalid handle");
2486 }
2487 if (!state->has_logits) {
2488 throw std::runtime_error("get_logits_at: no captured logits for handle");
2489 }
2490 const int32_t n_vocab = state->n_vocab;
2491 const float* logits = state->logits_snapshot.data();
2492 for (size_t i = 0; i < indices.size(); ++i) {
2493 const int32_t idx = indices[i];
2494 // Bounds check: indices arrive from JS Int32Array via N-API — must validate
2495 // against n_vocab to prevent native OOB reads driven by JS-controlled offsets.
2496 if (idx < 0 || idx >= n_vocab) {
2497 throw std::runtime_error("get_logits_at: index out of range");
2498 }
2499 out[i] = logits[idx];
2500 }
2501}
2502
2519inline llama_token sample(BranchHandle handle, BranchStore& s) {
2520
2521
2522 BranchState* state = s.get(handle);
2523 llama_sampler* chain = state ? s.get_sampler_chain(state->sampler_chain) : nullptr;
2524 if (!state || !chain) {
2525 return -1;
2526 }
2527
2528 // Must have logits captured before sampling
2529 if (!state->has_logits) {
2530 LLOYAL_LOG_DEBUG("[branch::sample] No logits captured - call prefill()/step() first");
2531 return -1;
2532 }
2533
2534 llama_sampler* gram = s.get_grammar_sampler(state->grammar);
2535
2536 // The candidate array is rebuilt from the branch's own logits snapshot for every pass, so a pass that
2537 // decided nothing (a refused pick) costs one more fill, never another decode.
2538 auto fill = [&]() -> llama_token_data_array {
2539 for (int i = 0; i < state->n_vocab; i++) {
2540 state->candidates_buffer[i] = llama_token_data{
2541 static_cast<llama_token>(i),
2542 state->logits_snapshot[i],
2543 0.0f};
2544 }
2545 return llama_token_data_array{
2546 state->candidates_buffer.data(),
2547 static_cast<size_t>(state->n_vocab),
2548 -1,
2549 false};
2550 };
2551
2552 // One pass of the modifiers after the (optional) grammar mask: Logit Bias → Steer → Sampler Chain.
2553 auto shape = [&](llama_token_data_array& cur_p, bool mask_with_grammar) -> llama_token {
2554 if (mask_with_grammar && gram) {
2555 grammar::apply(gram, &cur_p);
2556 }
2557
2558 // Logit bias — O(n_biases) via direct index (candidates are in token-ID order; grammar::apply
2559 // preserves order)
2560 if (!state->logit_bias.empty()) {
2561 for (const auto& bias : state->logit_bias) {
2562 if (bias.token >= 0 && bias.token < state->n_vocab) {
2563 cur_p.data[bias.token].logit += bias.bias;
2564 }
2565 }
2566 }
2567
2568 if (state->steer_fn) {
2569 try {
2570 state->steer_fn(cur_p);
2571 } catch (const std::exception& e) {
2572 LLOYAL_LOG_DEBUG("[branch::sample] Steer exception: %s", e.what());
2573 // Continue sampling without steer on exception
2574 }
2575 }
2576
2577 sampler::apply(chain, &cur_p);
2578 return cur_p.selected == -1 ? -1 : cur_p.data[cur_p.selected].id;
2579 };
2580
2581 // Does the grammar admit this one token? One-element apply: the grammar decodes one piece instead of
2582 // the whole vocabulary, and apply() reads the parser state without advancing it.
2583 auto admits = [&](llama_token token) -> bool {
2584 llama_token_data single{token, 0.0f, 0.0f};
2585 llama_token_data_array one{&single, 1, -1, false};
2586 grammar::apply(gram, &one);
2587 return one.data[0].logit != -INFINITY;
2588 };
2589
2590 llama_token_data_array cur_p = fill();
2591 const bool sample_first = gram && state->grammar_sample_first;
2592 llama_token token = shape(cur_p, /*mask_with_grammar=*/!sample_first);
2593
2594 if (sample_first && token != -1 && !admits(token)) {
2595 // Refused: take the exact order for this token.
2596 cur_p = fill();
2597 token = shape(cur_p, /*mask_with_grammar=*/true);
2598 }
2599 ++state->revision; // the chain ran: its PRNG may have advanced
2600
2601 if (token == -1) {
2602 return -1;
2603 }
2604
2605 // Capture filtered candidates for sampling metrics: the distribution the token was actually drawn from.
2606 state->last_token = token;
2607 state->last_candidates.clear();
2608 state->last_candidates.reserve(cur_p.size);
2609
2610 for (size_t i = 0; i < cur_p.size; i++) {
2611 state->last_candidates.push_back(cur_p.data[i]);
2612 }
2613
2614 return token;
2615}
2616
2632inline void accept_token(
2633 BranchHandle handle,
2634 llama_token token,
2635 BranchStore& s) {
2636
2637
2638 BranchState* state = s.get(handle);
2639 if (!state) return;
2640 ++state->revision;
2641
2642 // Accept in grammar (via anti-corruption layer)
2643 llama_sampler* gram = s.get_grammar_sampler(state->grammar);
2644 if (gram) {
2645 grammar::accept(gram, token);
2646 }
2647
2648 // Accept in sampler chain for penalty tracking (via anti-corruption layer)
2649 llama_sampler* chain = s.get_sampler_chain(state->sampler_chain);
2650 if (chain) {
2651 sampler::accept(chain, token);
2652 }
2653
2654 // Update model-level perplexity (from raw logits)
2655 // Guard on has_logits to avoid computing surprisal from zero-filled buffer
2656 if (state->metrics != 0 && state->has_logits) {
2657 float ms = metrics::model_surprisal(
2658 state->logits_snapshot.data(), state->n_vocab, token);
2659 s.add_model_surprisal(state->metrics, ms);
2660 }
2661
2662 // Update sampling-level perplexity (from filtered candidates)
2663 if (state->metrics != 0 && !state->last_candidates.empty() &&
2664 token == state->last_token) {
2665 // Extract filtered logits and IDs from candidates
2666 std::vector<float> candidate_logits;
2667 std::vector<int32_t> candidate_ids;
2668 candidate_logits.reserve(state->last_candidates.size());
2669 candidate_ids.reserve(state->last_candidates.size());
2670
2671 for (const auto& cand : state->last_candidates) {
2672 candidate_logits.push_back(cand.logit);
2673 candidate_ids.push_back(cand.id);
2674 }
2675
2676 // Compute sampling-level surprisal
2677 float ss = metrics::sampling_surprisal(
2678 candidate_logits.data(),
2679 candidate_ids.data(),
2680 static_cast<int>(candidate_logits.size()),
2681 token
2682 );
2683 s.add_sampling_surprisal(state->metrics, ss);
2684 }
2685}
2686
2701inline void apply_grammar(
2702 BranchHandle handle,
2703 float* logits,
2704 int n_vocab,
2705 BranchStore& s) {
2706
2707
2708 BranchState* state = s.get(handle);
2709 llama_sampler* gram = state ? s.get_grammar_sampler(state->grammar) : nullptr;
2710 if (!state || !gram) return;
2711
2712 // Use pre-allocated candidates buffer if size matches, otherwise allocate
2713 std::vector<llama_token_data>* candidates_ptr;
2714 std::vector<llama_token_data> temp_buffer;
2715
2716 if (n_vocab == state->n_vocab && !state->candidates_buffer.empty()) {
2717 candidates_ptr = &state->candidates_buffer;
2718 } else {
2719 temp_buffer.resize(n_vocab);
2720 candidates_ptr = &temp_buffer;
2721 }
2722
2723 auto& candidates = *candidates_ptr;
2724 for (int i = 0; i < n_vocab; i++) {
2725 candidates[i] = llama_token_data{
2726 static_cast<llama_token>(i), logits[i], 0.0f};
2727 }
2728
2729 llama_token_data_array cur_p = {
2730 candidates.data(),
2731 static_cast<size_t>(n_vocab),
2732 -1,
2733 false};
2734
2735 grammar::apply(gram, &cur_p);
2736
2737 // Copy masked logits back
2738 for (int i = 0; i < n_vocab; i++) {
2739 logits[i] = candidates[i].logit;
2740 }
2741}
2742
2757inline std::vector<std::pair<llama_token, float>> get_legal_priors(
2758 BranchHandle handle,
2759 BranchStore& s) {
2760
2761
2762 BranchState* state = s.get(handle);
2763 if (!state || !state->has_logits) {
2764 return {};
2765 }
2766
2767 // Reuse pre-allocated candidates buffer
2768 for (int i = 0; i < state->n_vocab; i++) {
2769 state->candidates_buffer[i] = llama_token_data{
2770 static_cast<llama_token>(i),
2771 state->logits_snapshot[i],
2772 0.0f};
2773 }
2774
2775 llama_token_data_array cur_p = {
2776 state->candidates_buffer.data(),
2777 static_cast<size_t>(state->n_vocab),
2778 -1,
2779 false};
2780
2781 // Apply grammar to mask illegal tokens
2782 llama_sampler* gram = s.get_grammar_sampler(state->grammar);
2783 if (gram) {
2784 grammar::apply(gram, &cur_p);
2785 }
2786
2787 // Collect legal candidates (logit is finite after grammar masking)
2788 // Grammar masking sets illegal tokens to -INFINITY
2789 std::vector<std::pair<llama_token, float>> legal_priors;
2790 float max_logit = -std::numeric_limits<float>::infinity();
2791
2792 for (size_t i = 0; i < cur_p.size; i++) {
2793 if (std::isfinite(cur_p.data[i].logit)) { // Not masked
2794 legal_priors.emplace_back(cur_p.data[i].id, cur_p.data[i].logit);
2795 if (cur_p.data[i].logit > max_logit) {
2796 max_logit = cur_p.data[i].logit;
2797 }
2798 }
2799 }
2800
2801 if (legal_priors.empty()) {
2802 return {};
2803 }
2804
2805 // Compute softmax over legal moves only (numerically stable)
2806 float sum_exp = 0.0f;
2807 for (auto& [token, logit] : legal_priors) {
2808 float exp_val = std::exp(logit - max_logit);
2809 logit = exp_val; // Temporarily store exp value
2810 sum_exp += exp_val;
2811 }
2812
2813 // Normalize to probabilities
2814 for (auto& [token, prob] : legal_priors) {
2815 prob /= sum_exp;
2816 }
2817
2818 return legal_priors;
2819}
2820
2837
2838
2839 BranchState* state = s.get(handle);
2840 if (!state || !state->has_logits) {
2841 return -std::numeric_limits<float>::infinity();
2842 }
2843
2844 // Reuse pre-allocated candidates buffer
2845 for (int i = 0; i < state->n_vocab; i++) {
2846 state->candidates_buffer[i] = llama_token_data{
2847 static_cast<llama_token>(i),
2848 state->logits_snapshot[i],
2849 0.0f};
2850 }
2851
2852 llama_token_data_array cur_p = {
2853 state->candidates_buffer.data(),
2854 static_cast<size_t>(state->n_vocab),
2855 -1,
2856 false};
2857
2858 // Apply grammar to mask illegal tokens
2859 llama_sampler* gram = s.get_grammar_sampler(state->grammar);
2860 if (gram) {
2861 grammar::apply(gram, &cur_p);
2862 }
2863
2864 // Numerically stable logsumexp over legal tokens (finite logits only)
2865 float max_logit = -std::numeric_limits<float>::infinity();
2866 for (size_t i = 0; i < cur_p.size; i++) {
2867 if (std::isfinite(cur_p.data[i].logit) && cur_p.data[i].logit > max_logit) {
2868 max_logit = cur_p.data[i].logit;
2869 }
2870 }
2871
2872 if (!std::isfinite(max_logit)) {
2873 return -std::numeric_limits<float>::infinity(); // No legal tokens
2874 }
2875
2876 float sum_exp = 0.0f;
2877 for (size_t i = 0; i < cur_p.size; i++) {
2878 if (std::isfinite(cur_p.data[i].logit)) {
2879 sum_exp += std::exp(cur_p.data[i].logit - max_logit);
2880 }
2881 }
2882
2883 return max_logit + std::log(sum_exp);
2884}
2885
2897inline bool is_token_legal(
2898 BranchHandle handle,
2899 llama_token token,
2900 BranchStore& s) {
2901
2902
2903 BranchState* state = s.get(handle);
2904 if (!state || token < 0 || token >= state->n_vocab) {
2905 return false;
2906 }
2907
2908 // No grammar = all tokens legal
2909 llama_sampler* gram = s.get_grammar_sampler(state->grammar);
2910 if (!gram) {
2911 return true;
2912 }
2913
2914 // Build 1-element candidate array (stack allocated, no heap)
2915 llama_token_data single_candidate = {
2916 token,
2917 state->has_logits ? state->logits_snapshot[token] : 0.0f,
2918 0.0f
2919 };
2920
2921 llama_token_data_array cur_p = {
2922 &single_candidate,
2923 1,
2924 -1,
2925 false
2926 };
2927
2928 // Apply grammar - will set logit to -INFINITY if illegal
2929 grammar::apply(gram, &cur_p);
2930
2931 return std::isfinite(single_candidate.logit);
2932}
2933
2949 BranchHandle handle,
2950 llama_token token,
2951 float logsumexp,
2952 BranchStore& s) {
2953
2954
2955 BranchState* state = s.get(handle);
2956 if (!state || !state->has_logits || token < 0 || token >= state->n_vocab) {
2957 return 0.0f;
2958 }
2959
2960 float logit = state->logits_snapshot[token];
2961 return std::exp(logit - logsumexp);
2962}
2963
2979inline float get_token_prior(
2980 BranchHandle handle,
2981 llama_token token,
2982 float logsumexp,
2983 BranchStore& s) {
2984 if (!is_token_legal(handle, token, s)) {
2985 return 0.0f;
2986 }
2987 return get_token_prior_assume_legal(handle, token, logsumexp, s);
2988}
2989
2990// ===== STATE ACCESSORS =====
2991
2992
2999inline llama_pos get_position(BranchHandle handle, BranchStore& s) {
3000
3001 const BranchState* state = s.get(handle);
3002 return state ? state->position : -1;
3003}
3004
3011inline llama_pos get_fork_head(BranchHandle handle, BranchStore& s) {
3012 const BranchState* state = s.get(handle);
3013 return state ? state->fork_head : 0;
3014}
3015
3027inline float get_perplexity(BranchHandle handle, BranchStore& s) {
3028
3029 const BranchState* state = s.get(handle);
3030 if (!state || state->metrics == 0) {
3031 return std::numeric_limits<float>::infinity();
3032 }
3033 return s.get_model_ppl(state->metrics);
3034}
3035
3048
3049 const BranchState* state = s.get(handle);
3050 if (!state || state->metrics == 0) {
3051 return std::numeric_limits<float>::infinity();
3052 }
3053 return s.get_sampling_ppl(state->metrics);
3054}
3055
3067
3068 const BranchState* state = s.get(handle);
3069
3070 if (!state || state->last_candidates.empty() || state->last_token < 0) {
3071 return 0.0f;
3072 }
3073
3074 // Extract candidates
3075 std::vector<float> candidate_logits;
3076 std::vector<int32_t> candidate_ids;
3077 candidate_logits.reserve(state->last_candidates.size());
3078 candidate_ids.reserve(state->last_candidates.size());
3079
3080 for (const auto& cand : state->last_candidates) {
3081 candidate_logits.push_back(cand.logit);
3082 candidate_ids.push_back(cand.id);
3083 }
3084
3085 // Compute surprisal from filtered distribution
3086 float surprisal = metrics::sampling_surprisal(
3087 candidate_logits.data(),
3088 candidate_ids.data(),
3089 static_cast<int>(candidate_logits.size()),
3090 state->last_token
3091 );
3092
3093 // Convert to probability: P = exp(-surprisal)
3094 return std::exp(-surprisal);
3095}
3096
3103inline int get_n_vocab(BranchHandle handle, BranchStore& s) {
3104
3105 const BranchState* state = s.get(handle);
3106 return state ? state->n_vocab : 0;
3107}
3108
3109
3110// ===== RAII WRAPPER =====
3111
3129class Branch {
3130public:
3131 Branch() : store_(nullptr), handle_(INVALID_HANDLE) {}
3132
3134 : store_(store), handle_(handle) {}
3135
3138 if (handle_ != INVALID_HANDLE && store_) {
3139 branch::pruneSubtree(handle_, *store_);
3140 }
3141 }
3142
3143 Branch(Branch&& other) noexcept
3144 : store_(other.store_), handle_(other.handle_) {
3145 other.handle_ = INVALID_HANDLE;
3146 }
3147
3148 Branch& operator=(Branch&& other) noexcept {
3149 if (this != &other) {
3150 if (handle_ != INVALID_HANDLE && store_) {
3151 branch::pruneSubtree(handle_, *store_);
3152 }
3153 store_ = other.store_;
3154 handle_ = other.handle_;
3155 other.handle_ = INVALID_HANDLE;
3156 }
3157 return *this;
3158 }
3159
3160 Branch(const Branch&) = delete;
3161 Branch& operator=(const Branch&) = delete;
3162
3164 template <SamplingParamsLike P>
3166 llama_context* ctx,
3167 const llama_model* model,
3168 BranchStore& store,
3169 llama_pos start_pos,
3170 const P& params,
3171 int n_batch = DEFAULT_N_BATCH,
3172 const char* grammar_str = nullptr,
3173 boundaries::BoundaryTracker* boundary_tracker = nullptr) {
3174 BranchHandle h = branch::create(ctx, model, store, start_pos, params, n_batch, grammar_str, boundary_tracker);
3175 return Branch(&store, h);
3176 }
3177
3180 BranchHandle h = branch::fork(handle_, *store_);
3181 return Branch(store_, h);
3182 }
3183
3185 void prune() {
3186 branch::prune(handle_, *store_);
3187 handle_ = INVALID_HANDLE;
3188 }
3189
3192 branch::pruneSubtree(handle_, *store_);
3193 handle_ = INVALID_HANDLE;
3194 }
3195
3199 branch::force_snapshot_logits(handle_, *store_);
3200 }
3201
3205 void prefill(const llama_token* tokens, size_t n) {
3206 branch::prefill(handle_, tokens, n, *store_);
3207 }
3208
3211 void step(llama_token token) {
3212 branch::step(handle_, token, *store_);
3213 }
3214
3217 const float* logits() const {
3218 return branch::get_logits(handle_, *store_);
3219 }
3220
3224 llama_token sample() {
3225 return branch::sample(handle_, *store_);
3226 }
3227
3230 void accept(llama_token token) {
3231 branch::accept_token(handle_, token, *store_);
3232 }
3233
3235 bool is_eog(llama_token token) const {
3236 const BranchState* st = store_ ? store_->get(handle_) : nullptr;
3237 return st && st->model ? tokenizer::is_eog(st->model, token) : false;
3238 }
3239
3242 template <SamplingParamsLike P>
3243 void setSamplerParams(const P& params) {
3244 branch::set_sampler_params(handle_, params, *store_);
3245 }
3246
3249 void setGrammar(const char* grammar_str) {
3250 const BranchState* st = store_ ? store_->get(handle_) : nullptr;
3251 branch::set_grammar(handle_, st ? st->model : nullptr, grammar_str, *store_);
3252 }
3253
3254 // ===== ACCESSORS =====
3255
3257 llama_pos position() const { return branch::get_position(handle_, *store_); }
3259 llama_pos forkHead() const { return branch::get_fork_head(handle_, *store_); }
3261 float perplexity() const { return branch::get_perplexity(handle_, *store_); }
3263 int n_vocab() const { return branch::get_n_vocab(handle_, *store_); }
3265 bool valid() const { return handle_ != INVALID_HANDLE; }
3267 BranchHandle handle() const { return handle_; }
3268
3269 // ===== TOPOLOGY =====
3270
3272 BranchHandle parentHandle() const { return store_ ? store_->parent(handle_) : INVALID_HANDLE; }
3274 const std::vector<BranchHandle>& childHandles() const {
3275 static const std::vector<BranchHandle> empty;
3276 return store_ ? store_->children(handle_) : empty;
3277 }
3279 bool isLeaf() const { return store_ ? store_->isLeaf(handle_) : true; }
3281 bool isActive() const { return store_ ? store_->isActive(handle_) : false; }
3282
3283private:
3284 BranchStore* store_;
3285 BranchHandle handle_;
3286};
3287
3288} // namespace lloyal::branch
Stub BoundaryTracker - does nothing.
Handle table and batched decode orchestrator for branch management.
Definition branch.hpp:490
DecodeSegmentsResult decode_segments(BranchHandle handle, decode::SegmentSource &source, const std::function< void(const decode::SegmentProgress &)> &on_progress={})
Prefill a heterogeneous segment sequence into ONE branch.
Definition branch.hpp:1430
GrammarHandle create_grammar_lazy(const llama_model *model, const char *grammar_str, const std::vector< std::string > &trigger_patterns, const std::vector< llama_token > &trigger_tokens, const char *root="root")
Create a lazy grammar (unconstrained until trigger fires)
Definition branch.hpp:946
float get_sampling_ppl(MetricsHandle h) const
Get sampling-level perplexity from a metrics tracker.
Definition branch.hpp:1077
bool isActive(BranchHandle h) const
Test whether a branch holds a KV lease.
Definition branch.hpp:815
void free_grammar(GrammarHandle h)
Free a grammar.
Definition branch.hpp:981
GrammarHandle create_grammar(const llama_model *model, const char *grammar_str, const char *root="root")
Create a grammar sampler and register it.
Definition branch.hpp:927
MetricsHandle clone_metrics(MetricsHandle h)
Clone a metrics tracker (for fork)
Definition branch.hpp:1013
size_t available() const
Number of vacant seq_ids available for acquisition.
Definition branch.hpp:750
void release(BranchHandle handle)
Release a branch slot + evict its KV lease.
Definition branch.hpp:561
void drain()
Explicit teardown — evict all leases while context is alive.
Definition branch.hpp:618
float get_model_ppl(MetricsHandle h) const
Get model-level perplexity from a metrics tracker.
Definition branch.hpp:1063
void decode_embd(BranchHandle handle, const float *rows, int32_t n_tokens, int32_t n_embd_inp, llama_pos n_pos, const llama_pos *pos, int32_t n_pos_per_embd, bool non_causal, bool want_logits)
Decode embedding rows into ONE branch's KV (multimodal ingress)
Definition branch.hpp:1340
bool isLeaf(BranchHandle h) const
Test whether a branch is a leaf (no children)
Definition branch.hpp:805
void decode_each(std::span< const DecodeEachItem > items)
Decode one token per branch in a single GPU dispatch.
Definition branch.hpp:1110
void fast_forward(BranchHandle parent, BranchHandle child)
Fast-forward parent to child: the child's state becomes the parent's.
Definition branch.hpp:697
bool sampler_has_dist(SamplerChainHandle h) const
Check if a sampler chain ends with dist (stochastic) or greedy.
Definition branch.hpp:912
MetricsHandle create_metrics()
Create a metrics tracker and register it.
Definition branch.hpp:1002
~BranchStore()
Destructor — frees CPU resources.
Definition branch.hpp:515
SamplerChainHandle clone_sampler(SamplerChainHandle h)
Clone a sampler chain (for fork)
Definition branch.hpp:876
llama_pos fork_head(BranchHandle h) const
Get a branch's fork head (parent position at fork time)
Definition branch.hpp:784
Allocation allocate()
Allocate a branch slot + KV lease atomically.
Definition branch.hpp:537
void add_cells_used(uint32_t n)
Increment cells_used counter (for standalone prefill/step outside BranchStore methods)
Definition branch.hpp:767
const BranchState * get(BranchHandle handle) const
Look up branch state by handle.
Definition branch.hpp:849
void free_sampler(SamplerChainHandle h)
Free a sampler chain.
Definition branch.hpp:892
void retainOnly(BranchHandle winner)
Keep only the winner — nuclear KV + CPU cleanup.
Definition branch.hpp:640
KvPressure kv_pressure() const
KV cache pressure snapshot — O(1), no tree walking.
Definition branch.hpp:759
SamplerChainHandle create_sampler(const P &params, int32_t n_vocab)
Create a sampler chain and register it.
Definition branch.hpp:861
llama_sampler * get_grammar_sampler(GrammarHandle h) const
Dereference a grammar handle (non-owning)
Definition branch.hpp:990
void add_sampling_surprisal(MetricsHandle h, float surprisal)
Add sampling-level surprisal to a metrics tracker.
Definition branch.hpp:1049
void init_tenancy(llama_context *ctx)
Initialize KV tenancy after context creation.
Definition branch.hpp:605
void merge_logits(BranchHandle dst_handle, std::span< const BranchHandle > expert_handles, float alpha)
Merge experts' logits_snapshot into dst's logits_snapshot.
Definition branch.hpp:1504
GrammarHandle clone_grammar(GrammarHandle h)
Clone a grammar (for fork)
Definition branch.hpp:966
void decode_scatter(std::span< const DecodeScatterItem > items)
Decode variable token counts per branch with auto-chunking.
Definition branch.hpp:1189
BranchStore(size_t initial_capacity=16)
Construct a branch store with initial slot capacity.
Definition branch.hpp:496
void free_metrics(MetricsHandle h)
Free a metrics tracker.
Definition branch.hpp:1026
const std::vector< BranchHandle > & children(BranchHandle h) const
Get a branch's child handles.
Definition branch.hpp:794
BranchState * get(BranchHandle handle)
Look up branch state by handle.
Definition branch.hpp:829
BranchHandle parent(BranchHandle h) const
Get a branch's parent handle.
Definition branch.hpp:774
void add_model_surprisal(MetricsHandle h, float surprisal)
Add model-level surprisal to a metrics tracker.
Definition branch.hpp:1035
llama_sampler * get_sampler_chain(SamplerChainHandle h) const
Dereference a sampler chain handle (non-owning)
Definition branch.hpp:901
~Branch()
Destructor — CASCADE prunes entire subtree.
Definition branch.hpp:3137
void accept(llama_token token)
Accept a token — advance grammar, penalty window, and metrics.
Definition branch.hpp:3230
Branch & operator=(const Branch &)=delete
int n_vocab() const
Vocabulary size.
Definition branch.hpp:3263
void setGrammar(const char *grammar_str)
Replace grammar constraint (nullptr/empty to remove)
Definition branch.hpp:3249
const std::vector< BranchHandle > & childHandles() const
Child branch handles (empty if leaf)
Definition branch.hpp:3274
void setSamplerParams(const P &params)
Replace sampler chain with new parameters (memoized)
Definition branch.hpp:3243
BranchHandle handle() const
Underlying opaque handle (for interop with free functions)
Definition branch.hpp:3267
const float * logits() const
Get the branch's captured logits snapshot.
Definition branch.hpp:3217
bool is_eog(llama_token token) const
Check if a token is end-of-generation for this branch's model.
Definition branch.hpp:3235
BranchHandle parentHandle() const
Parent branch handle, or INVALID_HANDLE if root.
Definition branch.hpp:3272
Branch(BranchStore *store, BranchHandle handle)
Definition branch.hpp:3133
llama_pos position() const
Current decode position (token count)
Definition branch.hpp:3257
llama_token sample()
Sample a token from captured logits.
Definition branch.hpp:3224
void step(llama_token token)
Decode one token and capture logits (generation step)
Definition branch.hpp:3211
void pruneSubtree()
CASCADE prune — removes entire subtree.
Definition branch.hpp:3191
float perplexity() const
Model-level perplexity (from raw logits, pre-filter)
Definition branch.hpp:3261
Branch(const Branch &)=delete
bool isActive() const
True if this branch holds a KV lease.
Definition branch.hpp:3281
llama_pos forkHead() const
Parent's position at fork time (0 for root branches)
Definition branch.hpp:3259
void force_snapshot_logits()
Force-copy shared logits buffer into this branch's snapshot.
Definition branch.hpp:3198
bool isLeaf() const
True if this branch has no children.
Definition branch.hpp:3279
void prefill(const llama_token *tokens, size_t n)
Decode multiple tokens and capture logits atomically (prompt injection)
Definition branch.hpp:3205
bool valid() const
True if this Branch holds a valid handle.
Definition branch.hpp:3265
void prune()
RESTRICT prune (throws if children exist)
Definition branch.hpp:3185
Branch fork()
Fork: allocates slot + lease, records topology edge.
Definition branch.hpp:3179
Branch & operator=(Branch &&other) noexcept
Definition branch.hpp:3148
static Branch create(llama_context *ctx, const llama_model *model, BranchStore &store, llama_pos start_pos, const P &params, int n_batch=DEFAULT_N_BATCH, const char *grammar_str=nullptr, boundaries::BoundaryTracker *boundary_tracker=nullptr)
Factory: allocates slot + lease from store.
Definition branch.hpp:3165
Branch(Branch &&other) noexcept
Definition branch.hpp:3143
#define LLOYAL_LOG_DEBUG(...)
liblloyal - Common definitions and logging
Definition common.hpp:48
Batch Decoding Operations.
Grammar-Constrained Sampling.
constexpr llama_seq_id NO_LEASE
Sentinel value indicating a branch has no KV residency.
Definition kv.hpp:234
KV Cache Physics.
Zero-copy logits access with clear lifetime semantics.
Distribution Metrics for Test-Time Alignment.
BranchHandle fork(BranchHandle source, BranchStore &s, ForkOpts opts={})
Fork a branch into a new independent sequence.
Definition branch.hpp:1845
void prefill(BranchHandle handle, const llama_token *tokens, size_t n_tokens, BranchStore &s)
Decode multiple tokens and capture logits atomically (prompt prefill)
Definition branch.hpp:2336
void set_logit_bias(BranchHandle handle, const llama_logit_bias *biases, size_t n_biases, BranchStore &s)
Definition branch.hpp:1954
float get_perplexity(BranchHandle handle, BranchStore &s)
Get model-level perplexity (from raw logits)
Definition branch.hpp:3027
void apply_grammar(BranchHandle handle, float *logits, int n_vocab, BranchStore &s)
Apply grammar constraints to an external logits buffer.
Definition branch.hpp:2701
int32_t GrammarHandle
Handle to a grammar sampler in BranchStore's registry (0 = invalid/none)
Definition branch.hpp:162
uint32_t BranchHandle
Opaque handle to a branch slot.
Definition branch.hpp:100
void step(BranchHandle handle, llama_token token, BranchStore &s)
Decode a single token and capture logits (generation step)
Definition branch.hpp:2375
constexpr int DEFAULT_N_BATCH
Default batch size for decode operations.
Definition branch.hpp:104
void set_grammar_lazy(BranchHandle handle, const llama_model *model, const char *grammar_str, const std::vector< std::string > &trigger_patterns, const std::vector< llama_token > &trigger_tokens, BranchStore &s)
Set lazy grammar on a branch (unconstrained until trigger fires)
Definition branch.hpp:2158
void set_logits(BranchHandle handle, std::span< const float > data, BranchStore &s)
Overwrite a branch's logits_snapshot with caller-provided values.
Definition branch.hpp:2436
void set_steer(BranchHandle handle, std::function< void(llama_token_data_array &)> steer_fn, BranchStore &s)
Definition branch.hpp:2023
void fast_forward(BranchHandle parent, BranchHandle child, BranchStore &s)
Fast-forward parent to child; see BranchStore::fast_forward()
Definition branch.hpp:2276
constexpr uint32_t GEN_SHIFT
Bit shift for generation field.
Definition branch.hpp:105
void accept_token(BranchHandle handle, llama_token token, BranchStore &s)
Accept a sampled token, advancing grammar and sampler state.
Definition branch.hpp:2632
void force_snapshot_logits(BranchHandle handle, BranchStore &s)
Force-copy the shared llama.cpp logits buffer into this branch's private snapshot.
Definition branch.hpp:2308
llama_pos get_fork_head(BranchHandle handle, BranchStore &s)
Get the branch's fork head (parent position at fork time)
Definition branch.hpp:3011
float get_sampling_perplexity(BranchHandle handle, BranchStore &s)
Get sampling-level perplexity (from filtered distribution)
Definition branch.hpp:3047
float get_legal_logsumexp(BranchHandle handle, BranchStore &s)
Compute log-sum-exp over grammar-legal logits.
Definition branch.hpp:2836
void clear_logit_bias(BranchHandle handle, BranchStore &s)
Clear all logit biases from a branch.
Definition branch.hpp:1981
uint16_t handle_generation(BranchHandle h)
Extract generation counter from a branch handle.
Definition branch.hpp:142
uint32_t clock_seed()
A fresh seed for a chain created without one.
Definition branch.hpp:267
const float * get_logits(BranchHandle handle, BranchStore &s)
Get the branch's captured logits snapshot.
Definition branch.hpp:2406
void set_sampler_params(BranchHandle handle, const P &params, BranchStore &s)
Replace a branch's sampler chain with new parameters.
Definition branch.hpp:2080
void prune(BranchHandle handle, BranchStore &s)
Prune a leaf branch (RESTRICT — throws if children exist)
Definition branch.hpp:2221
BranchHandle create(llama_context *ctx, const llama_model *model, BranchStore &s, llama_pos start_pos, const P &params, int n_batch=DEFAULT_N_BATCH, const char *grammar_str=nullptr, boundaries::BoundaryTracker *boundary_tracker=nullptr)
Create a new branch with sampler chain, optional grammar, and metrics.
Definition branch.hpp:1740
void require_distinct_handles(std::span< const BranchHandle > handles, const char *who)
A handle may appear at most once per batched call: each item's start position is read from its branch...
Definition branch.hpp:436
constexpr uint32_t INDEX_MASK
Mask for slot index field.
Definition branch.hpp:106
int get_n_vocab(BranchHandle handle, BranchStore &s)
Get the branch's vocabulary size.
Definition branch.hpp:3103
bool is_poisoned(BranchHandle handle, BranchStore &s)
Whether a failed decode left this branch's KV ahead of its books.
Definition branch.hpp:2284
CachedSamplingParams snapshot_params(const P &p, uint32_t absent_seed)
Resolve sampling params into the record a chain is built from.
Definition branch.hpp:250
llama_token sample(BranchHandle handle, BranchStore &s)
Sample a token from the branch's captured logits.
Definition branch.hpp:2519
void pruneSubtree(BranchHandle h, BranchStore &s)
Prune a branch and all descendants (CASCADE — iterative post-order)
Definition branch.hpp:2238
constexpr BranchHandle INVALID_HANDLE
Null handle sentinel.
Definition branch.hpp:102
float get_token_prior(BranchHandle handle, llama_token token, float logsumexp, BranchStore &s)
Compute prior probability for a token, checking grammar legality first.
Definition branch.hpp:2979
constexpr llama_seq_id NO_LEASE
Branch has no KV residency.
Definition branch.hpp:103
int32_t MetricsHandle
Handle to a metrics tracker in BranchStore's registry (0 = invalid/none)
Definition branch.hpp:165
llama_pos get_position(BranchHandle handle, BranchStore &s)
Get the branch's current decode position.
Definition branch.hpp:2999
void set_grammar(BranchHandle handle, const llama_model *model, const char *grammar_str, BranchStore &s)
Replace a branch's grammar constraint.
Definition branch.hpp:2117
void clear_steer(BranchHandle handle, BranchStore &s)
Clear the steer callback from a branch.
Definition branch.hpp:2046
float get_token_prior_assume_legal(BranchHandle handle, llama_token token, float logsumexp, BranchStore &s)
Compute prior probability for a token known to be grammar-legal.
Definition branch.hpp:2948
void set_grammar_order(BranchHandle handle, bool sample_first, BranchStore &s)
Choose how sample() consults the attached grammar.
Definition branch.hpp:2200
BranchHandle make_handle(uint16_t index, uint16_t generation)
Construct a branch handle from index and generation.
Definition branch.hpp:152
uint16_t handle_index(BranchHandle h)
Extract slot index from a branch handle.
Definition branch.hpp:133
int32_t SamplerChainHandle
Handle to a sampler chain in BranchStore's registry (0 = invalid/none)
Definition branch.hpp:159
void reseed_sampler(BranchHandle handle, uint32_t seed, BranchStore &s)
Reseed a branch's sampling PRNG, the way forks are made to diverge.
Definition branch.hpp:2262
std::vector< std::pair< llama_token, float > > get_legal_priors(BranchHandle handle, BranchStore &s)
Get grammar-legal tokens with renormalized probabilities.
Definition branch.hpp:2757
bool is_token_legal(BranchHandle handle, llama_token token, BranchStore &s)
Check if a token is legal under grammar constraints.
Definition branch.hpp:2897
float get_last_sampling_prior(BranchHandle handle, BranchStore &s)
Get the last sampled token's prior from the filtered distribution.
Definition branch.hpp:3066
void get_logits_at(BranchHandle handle, std::span< const int32_t > indices, std::span< float > out, BranchStore &s)
Read selected logit values from a branch's snapshot.
Definition branch.hpp:2476
void segments(llama_context *ctx, llama_seq_id seq, llama_pos base, SegmentSource &source, int32_t rows_n_batch, Scratch &scratch, std::vector< llama_pos > &positions, OnLanded &&landed)
Place a source's segments into one sequence, in order.
Definition decode.hpp:1170
std::vector< PackedChunk > bin_pack(const std::span< const llama_token > *items, int32_t n, int32_t n_batch)
Greedy first-fit bin-packing of token spans into n_batch-sized chunks.
Definition decode.hpp:1215
int embd(llama_context *ctx, const EmbdItem &item, int32_t n_batch, Scratch &scratch, int32_t *n_committed=nullptr)
Decode pre-computed embedding rows into one sequence's KV cache.
Definition decode.hpp:794
bool leaves_branch_intact(int32_t rc, bool partial)
llama_decode failure carrying the raw return code
Definition decode.hpp:152
int scatter(llama_context *ctx, const ScatterItem *items, int32_t n, Scratch &scratch)
Decode multiple tokens per sequence in a single llama_decode() call.
Definition decode.hpp:547
int each(llama_context *ctx, const EachItem *items, int32_t n, Scratch &scratch)
Decode one token per sequence in a single llama_decode() call.
Definition decode.hpp:489
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.
Definition decode.hpp:240
constexpr T as_value(const X &x, T def)
Extract value from either T or std::optional<T> with fallback.
Definition sampler.hpp:51
std::vector< float > get(llama_context *ctx, Normalize normalize=Normalize::L2)
Get embeddings for last decoded batch.
void free_sampler(llama_sampler *smpl)
Free a grammar sampler.
Definition grammar.hpp:236
llama_sampler * clone_sampler(llama_sampler *smpl)
Clone a grammar sampler (for fork/branching).
Definition grammar.hpp:214
llama_sampler * init_sampler(const llama_model *model, const std::string &grammar_str, const std::string &root_rule="root")
Initialize a grammar sampler from GBNF grammar string.
Definition grammar.hpp:108
llama_sampler * init_lazy_sampler(const llama_model *model, const std::string &grammar_str, const std::vector< std::string > &trigger_patterns, const std::vector< llama_token > &trigger_tokens, const std::string &root_rule="root")
Initialize a lazy grammar sampler from GBNF grammar string.
Definition grammar.hpp:159
void evict_all(State &s)
Evict every leased seq_id.
Definition kv.hpp:378
llama_seq_id acquire(State &s)
Acquire a seq_id from the vacant pool.
Definition kv.hpp:283
size_t available(const State &s)
Number of vacant seq_ids available for acquisition.
Definition kv.hpp:392
void evict(State &s, llama_seq_id seq)
Evict a seq_id — strip all KV tags then release.
Definition kv.hpp:324
void retain(State &s, llama_seq_id keep)
Nuclear retain — keep one seq, rebuild vacancy from scratch.
Definition kv.hpp:347
State init(llama_context *ctx, llama_seq_id n_seq_max)
Initialize tenancy with all seq_ids vacant.
Definition kv.hpp:261
void release(State &s, llama_seq_id seq)
Release a seq_id back to vacant — bookkeeping only, no KV calls.
Definition kv.hpp:302
float * get(llama_context *ctx, int32_t index=-1)
Definition logits.hpp:79
llama_sampler * clone_chain(llama_sampler *chain)
Clone a sampler chain.
Definition sampler.hpp:533
llama_sampler * create_chain(const P &params, int32_t n_vocab)
Create a persistent sampler chain from parameters.
Definition sampler.hpp:467
void free_chain(llama_sampler *chain)
Free a sampler chain.
Definition sampler.hpp:574
Token Sampling Operations.
Consolidated mutable state for a single branch.
Definition branch.hpp:293
bool in_use
True when slot is allocated to an active branch.
Definition branch.hpp:378
const llama_model * model
Llama model (not owned, must outlive branch)
Definition branch.hpp:295
uint64_t fork_revision
The parent's revision at fork time (0 for root branches).
Definition branch.hpp:306
bool has_logits
True once logits_snapshot holds a live distribution: capture_logits(), set_logits(),...
Definition branch.hpp:346
std::function< void(llama_token_data_array &)> steer_fn
Dynamic logit callback, NOT cloned on fork.
Definition branch.hpp:338
int n_batch
Batch size for decode operations.
Definition branch.hpp:374
bool grammar_sample_first
Grammar order for sample(): false masks the whole vocabulary before the chain (exact under truncation...
Definition branch.hpp:331
llama_seq_id seq_id
KV cache sequence identifier (NO_LEASE when inactive)
Definition branch.hpp:297
llama_pos position
Current decode position in the sequence.
Definition branch.hpp:298
std::vector< llama_logit_bias > logit_bias
Static token biases, cloned on fork.
Definition branch.hpp:337
std::vector< llama_token_data > last_candidates
Filtered candidates from last sample()
Definition branch.hpp:343
std::vector< float > logits_snapshot
Captured logit distribution (n_vocab floats)
Definition branch.hpp:345
std::vector< llama_token_data > candidates_buffer
Reusable scratch buffer for sampling (avoids O(n_vocab) allocs per sample call).
Definition branch.hpp:372
void capture_logits(int32_t index)
Copy the logits at batch row index into this branch's private snapshot.
Definition branch.hpp:354
uint32_t img_slack_own
Embedding-row slack: cells beyond position-delta from decode_embd() (an M-RoPE image adds nx*ny cells...
Definition branch.hpp:323
boundaries::BoundaryTracker * boundary_tracker
Token boundary detector (owned, optional)
Definition branch.hpp:335
SamplerChainHandle sampler_chain
Handle into BranchStore's sampler registry.
Definition branch.hpp:326
llama_context * ctx
Llama context (not owned, must outlive branch)
Definition branch.hpp:294
uint64_t revision
Counts every change to the state fast_forward() moves: decodes, logits, sampling, grammar and bias.
Definition branch.hpp:304
BranchHandle parent
Parent branch (INVALID_HANDLE if root)
Definition branch.hpp:381
int n_vocab
Vocabulary size (cached for buffer pre-allocation)
Definition branch.hpp:375
GrammarHandle grammar
Handle into BranchStore's grammar registry.
Definition branch.hpp:327
std::vector< BranchHandle > children
Child branches forked from this one.
Definition branch.hpp:382
llama_token last_token
Last token returned by sample()
Definition branch.hpp:342
bool poisoned
A decode failed and left the KV ahead of these books (see decode::leaves_branch_intact).
Definition branch.hpp:310
MetricsHandle metrics
Handle into BranchStore's metrics registry.
Definition branch.hpp:340
llama_pos fork_head
Parent's position at fork time (0 for root branches)
Definition branch.hpp:299
CachedSamplingParams cached_params
Params used to create current chain (for memoization)
Definition branch.hpp:333
uint16_t generation
Slot generation counter (for ABA prevention)
Definition branch.hpp:377
Result of allocate(): a slot handle + its leased seq_id.
Definition branch.hpp:527
The resolved sampling params a branch's chain was built from.
Definition branch.hpp:224
bool operator==(const CachedSamplingParams &) const =default
Item for decode_each: one token per branch.
Definition branch.hpp:393
Item for decode_scatter: variable tokens per branch.
Definition branch.hpp:411
std::span< const llama_token > tokens
Definition branch.hpp:413
What a heterogeneous prefill consumed.
Definition branch.hpp:419
Options controlling what state fork() clones from source to child.
Definition branch.hpp:1814
RAII entry for a grammar sampler in the registry.
Definition branch.hpp:198
GrammarEntry(GrammarEntry &&o) noexcept
Definition branch.hpp:204
GrammarEntry & operator=(const GrammarEntry &)=delete
GrammarEntry & operator=(GrammarEntry &&o) noexcept
Definition branch.hpp:205
GrammarEntry(const GrammarEntry &)=delete
llama_sampler * sampler
Definition branch.hpp:199
Snapshot of KV cache pressure from BranchStore.
Definition branch.hpp:122
uint32_t remaining
n_ctx - cells_used (clamped to 0)
Definition branch.hpp:125
uint32_t n_ctx
Total KV capacity.
Definition branch.hpp:123
uint32_t cells_used
Cells allocated since last reset.
Definition branch.hpp:124
RAII entry for a sampler chain in the registry.
Definition branch.hpp:173
SamplerChainEntry(const SamplerChainEntry &)=delete
SamplerChainEntry & operator=(SamplerChainEntry &&o) noexcept
Definition branch.hpp:182
SamplerChainEntry(SamplerChainEntry &&o) noexcept
Definition branch.hpp:180
bool has_dist
True if chain ends with dist (temp > 0), false if greedy.
Definition branch.hpp:175
SamplerChainEntry & operator=(const SamplerChainEntry &)=delete
Input item for decode::embd — embedding rows for one sequence.
Definition decode.hpp:612
int32_t n_embd_inp
Row width — llama_model_n_embd_inp(model), the INPUT dim (not n_embd)
Definition decode.hpp:617
const float * rows
n_rows x n_embd_inp floats, caller-owned, valid for the call's duration
Definition decode.hpp:614
bool output_logits
When true, compute logits for the LAST row of the last sub-chunk.
Definition decode.hpp:631
const llama_pos * pos
Section-major positions: n_rows * n_pos_per_embd entries, laid out [s0...][s1...]....
Definition decode.hpp:621
bool non_causal
Bracket the whole decode in non-causal attention (Gemma-class projectors; requires n_ubatch >= n_rows...
Definition decode.hpp:627
int32_t n_pos_per_embd
1 (plain positions) or 4 (M-RoPE)
Definition decode.hpp:623
A segment of a stream once it is in the KV: what moved, for the caller's books.
Definition decode.hpp:909
How far one input of a stream has landed: a reading of progress.
Definition decode.hpp:759
Supplies the segment sequence for one branch's prefill.
Definition decode.hpp:682
virtual size_t total_of(int32_t) const
KV cells input input takes, summed over its segments: what progress on it counts towards.
Definition decode.hpp:733
virtual int32_t input_of(size_t) const
The input segment i belongs to (an image, a video, a recording), or -1 for prompt text that belongs t...
Definition decode.hpp:729
llama_context * ctx
Context for KV operations (nullptr after drain)
Definition kv.hpp:245
Unified model + sampling perplexity tracker.
Definition metrics.hpp:100