11#include <llama/llama.h>
90#if INTPTR_MAX == INT64_MAX
91static_assert(
sizeof(llama_batch) == 56,
"llama_batch changed shape: audit Scratch::as_batch and as_embd_batch");
93namespace shape_guard {
95 [[maybe_unused]]
auto [n_tokens, token,
embd, pos, n_seq_id, seq_id, logits] = llama_batch{};
103inline void batch_clear(llama_batch& batch) { batch.n_tokens = 0; }
104inline void batch_add(llama_batch& batch, llama_token
id, llama_pos pos,
105 llama_seq_id seq_id,
bool logits) {
106 const int32_t i = batch.n_tokens;
109 batch.n_seq_id[i] = 1;
110 batch.seq_id[i][0] = seq_id;
111 batch.logits[i] = logits ? int8_t{1} : int8_t{0};
153 return (rc == 1 || rc == -1) && !partial;
160 : std::runtime_error(msg +
" (rc=" + std::to_string(rc_) +
161 (partial_ ?
", partial)" :
")")),
181 const int32_t limit =
static_cast<int32_t
>(llama_n_batch(ctx));
182 if (n_tokens > limit) {
183 throw std::runtime_error(std::string(who) +
" - a batch of " + std::to_string(n_tokens) +
184 " tokens exceeds the context's n_batch (" + std::to_string(limit) +
")");
240[[nodiscard]]
inline int many(llama_context *ctx,
const llama_token *tokens,
241 int32_t n_tokens, int32_t n_past, int32_t n_batch,
242 llama_seq_id seq_id = 0,
243 int32_t* n_committed =
nullptr) {
245 "[decode::many] Processing %d tokens at position %d", n_tokens,
250 throw std::runtime_error(
"decode::many - NULL context");
253 if (!tokens || n_tokens <= 0) {
255 throw std::runtime_error(
"decode::many - Invalid token array");
259 throw std::runtime_error(
"decode::many - n_batch must be positive");
264 struct ThreadLocalBatch {
266 int32_t capacity = 0;
268 void ensure(int32_t n) {
269 if (n <= capacity)
return;
270 if (capacity > 0) llama_batch_free(batch);
271 batch = llama_batch_init(n, 0, 1);
275 ~ThreadLocalBatch() {
276 if (capacity > 0) llama_batch_free(batch);
279 thread_local ThreadLocalBatch tl;
281 llama_batch& batch = tl.batch;
284 int32_t processed = 0;
285 while (processed < n_tokens) {
286 const int32_t n_eval = std::min(n_tokens - processed, n_batch);
291 const bool is_last_chunk = (processed + n_eval >= n_tokens);
292 for (int32_t i = 0; i < n_eval; ++i) {
293 const int32_t pos = n_past + i;
294 const bool want_logits = is_last_chunk && (i == n_eval - 1);
300 const int rc = llama_decode(ctx, batch);
303 "[decode::many] ERROR: llama_decode failed at position %d (rc=%d)",
305 if (n_committed) *n_committed = processed;
313 processed, n_tokens);
317 if (n_committed) *n_committed = n_tokens;
322[[nodiscard]]
inline int many(llama_context *ctx,
323 const std::vector<llama_token> &tokens,
324 int32_t n_past, int32_t n_batch,
325 llama_seq_id seq_id = 0) {
326 return many(ctx, tokens.data(),
static_cast<int32_t
>(tokens.size()), n_past,
354[[nodiscard]]
inline int one(llama_context *ctx, llama_token tok, llama_pos pos,
355 llama_seq_id seq_id = 0,
bool want_logits =
true) {
357 throw std::runtime_error(
"decode::one - NULL context");
360 struct ThreadLocalBatch {
361 llama_batch batch = llama_batch_init(1, 0, 1);
362 ~ThreadLocalBatch() { llama_batch_free(batch); }
364 thread_local ThreadLocalBatch tl;
369 return llama_decode(ctx, tl.batch);
418 void resize(int32_t n, int32_t n_pos_per_embd = 1) {
420 pos_.resize(
static_cast<size_t>(n) * n_pos_per_embd);
431 batch.n_tokens = n_tokens;
433 batch.embd =
nullptr;
434 batch.pos =
pos_.data();
448 batch.n_tokens = n_tokens;
449 batch.token =
nullptr;
450 batch.embd =
const_cast<float*
>(rows);
451 batch.pos =
pos_.data();
463 if (
const llama_model* m = llama_get_model(ctx)) {
464 const int32_t expected = llama_model_n_embd_inp(m);
465 if (expected > 0 && n_embd_inp != expected) {
466 throw std::runtime_error(std::string(who) +
" - n_embd_inp " + std::to_string(n_embd_inp) +
467 " does not match the model's input width " + std::to_string(expected));
489[[nodiscard]]
inline int each(llama_context* ctx,
494 throw std::runtime_error(
"decode::each - NULL context");
497 throw std::runtime_error(
"decode::each - negative item count");
499 if (n == 0)
return 0;
504 for (int32_t i = 0; i < n; ++i) {
506 scratch.
pos_[i] = items[i].
pos;
513 llama_batch batch = scratch.
as_batch(n);
515 LLOYAL_LOG_DEBUG(
"[decode::each] Submitting %d tokens across %d sequences", n, n);
517 return llama_decode(ctx, batch);
521[[nodiscard]]
inline int each(llama_context* ctx,
522 const std::vector<EachItem>& items,
524 return each(ctx, items.data(),
static_cast<int32_t
>(items.size()), scratch);
547[[nodiscard]]
inline int scatter(llama_context* ctx,
552 throw std::runtime_error(
"decode::scatter - NULL context");
555 throw std::runtime_error(
"decode::scatter - negative item count");
559 for (int32_t i = 0; i < n; ++i) {
560 total +=
static_cast<int32_t
>(items[i].
tokens.size());
562 if (total == 0)
return 0;
568 for (int32_t i = 0; i < n; ++i) {
569 const auto& item = items[i];
570 const llama_pos base_pos = item.
start_pos;
571 const int32_t item_n =
static_cast<int32_t
>(item.tokens.size());
573 for (int32_t j = 0; j < item_n; ++j) {
574 scratch.
tokens_[cursor] = item.tokens[j];
575 scratch.
pos_[cursor] = base_pos + j;
580 const bool want_logits =
581 item.output_logits ? (j == item_n - 1) :
false;
582 scratch.
logits_[cursor] = want_logits ? int8_t{1} : int8_t{0};
588 llama_batch batch = scratch.
as_batch(total);
590 LLOYAL_LOG_DEBUG(
"[decode::scatter] Submitting %d total tokens across %d sequences", total, n);
592 return llama_decode(ctx, batch);
596[[nodiscard]]
inline int scatter(llama_context* ctx,
597 const std::vector<ScatterItem>& items,
599 return scatter(ctx, items.data(),
static_cast<int32_t
>(items.size()), scratch);
621 const llama_pos*
pos =
nullptr;
725 virtual void positions(
size_t i, llama_pos base, llama_pos* out) = 0;
729 virtual int32_t
input_of(
size_t )
const {
return -1; }
733 virtual size_t total_of(int32_t )
const {
return 0; }
742 explicit TokenSource(std::span<const llama_token> tokens) : tokens_(tokens) {}
743 size_t size()
override {
return 1; }
744 size_t cells()
const override {
return tokens_.size(); }
750 void positions(
size_t, llama_pos, llama_pos*)
override {}
753 std::span<const llama_token> tokens_;
794[[nodiscard]]
inline int embd(llama_context* ctx,
798 int32_t* n_committed =
nullptr) {
800 throw std::runtime_error(
"decode::embd - NULL context");
803 throw std::runtime_error(
"decode::embd - invalid rows");
806 throw std::runtime_error(
"decode::embd - invalid positions");
809 throw std::runtime_error(
"decode::embd - n_batch must be positive");
819 const int32_t n = item.
n_rows;
830 const int32_t n_ubatch =
static_cast<int32_t
>(llama_n_ubatch(ctx));
831 if (n > n_batch || n > n_ubatch) {
832 throw std::runtime_error(
833 "decode::embd - non-causal block of " + std::to_string(n) +
834 " rows exceeds n_batch (" + std::to_string(n_batch) +
835 ") or n_ubatch (" + std::to_string(n_ubatch) +
836 "); a bidirectional image must decode in a single dispatch");
848 llama_context* ctx =
nullptr;
850 bool engaged =
false;
851 ~CausalGuard() {
if (engaged) llama_set_causal_attn(ctx,
true); }
854 if (item.
non_causal) llama_set_causal_attn(ctx,
false);
856 int32_t processed = 0;
857 while (processed < n) {
858 const int32_t n_view = std::min(n - processed, n_batch);
859 scratch.
resize(n_view, nppe);
863 for (int32_t s = 0; s < nppe; ++s) {
864 const llama_pos* src = item.
pos +
static_cast<size_t>(s) * n + processed;
865 std::copy(src, src + n_view,
866 scratch.
pos_.begin() +
static_cast<size_t>(s) * n_view);
869 for (int32_t i = 0; i < n_view; ++i) {
875 const bool is_last_view = (processed + n_view >= n);
877 scratch.
logits_[n_view - 1] = 1;
881 n_view, item.
rows +
static_cast<size_t>(processed) * item.
n_embd_inp);
884 processed + n_view, n, item.
seq_id);
886 const int rc = llama_decode(ctx, batch);
889 if (n_committed) *n_committed = processed;
897 if (n_committed) *n_committed = n;
949 return StreamReading{llama_get_memory(ctx) !=
nullptr, llama_get_causal_attn(ctx),
950 llama_pooling_type(ctx) != LLAMA_POOLING_TYPE_NONE};
964 throw std::runtime_error(
"decode::segments - empty TEXT segment at " + std::to_string(i) +
965 "; a source must not yield empty segments");
970 throw std::runtime_error(
"decode::segments - empty embedding segment at " + std::to_string(i));
975 throw std::runtime_error(
"decode::segments - segment " + std::to_string(i) +
" has n_pos_per_embd " +
979 throw std::runtime_error(
"decode::segments - segment " + std::to_string(i) +
" has non-positive n_embd_inp");
982 throw std::runtime_error(
"decode::segments - segment " + std::to_string(i) +
" has n_pos " +
983 std::to_string(seg.
n_pos) +
" outside (0, n_rows=" + std::to_string(seg.
n_rows) +
"]");
989template <
typename OnLanded>
991 int32_t rows_n_batch,
Scratch& scratch, std::vector<llama_pos>& positions,
993 const size_t n = source.
size();
994 const int32_t text_n_batch =
static_cast<int32_t
>(llama_n_batch(ctx));
996 bool any_landed =
false;
998 const auto failed = [&](
int rc, int32_t committed,
const std::string& what) {
999 return DecodeError(rc, any_landed || committed > 0,
"decode::segments - " + what);
1002 for (
size_t i = 0; i < n; ++i) {
1004 const bool is_last = (i + 1 == n);
1008 const int32_t n_tokens =
static_cast<int32_t
>(seg.
tokens.size());
1009 int32_t committed = 0;
1010 if (
const int rc =
many(ctx, seg.
tokens.data(), n_tokens, at, text_n_batch, seq, &committed); rc != 0) {
1011 throw failed(rc, committed,
"TEXT segment " + std::to_string(i) +
" failed");
1021 source.
positions(i, at, positions.data());
1027 item.
pos = positions.data();
1032 int32_t committed = 0;
1033 if (
const int rc =
embd(ctx, item, rows_n_batch, scratch, &committed); rc != 0) {
1034 throw failed(rc, committed,
"EMBD segment " + std::to_string(i) +
" failed");
1045template <
typename OnLanded>
1047 SegmentSource& source, std::vector<llama_pos>& positions, OnLanded&& landed) {
1052 const size_t n_fit = std::min(llama_n_batch(ctx), llama_n_ubatch(ctx));
1053 const auto too_large = [&](
size_t n_entries) {
1054 return std::runtime_error(
"decode::segments - a stream of " + std::to_string(n_entries) +
1055 " entries must go in one dispatch on this context, which holds " +
1056 std::to_string(n_fit));
1058 if (source.
cells() > n_fit)
throw too_large(source.
cells());
1060 std::unique_ptr<llama_batch_ext,
decltype(&llama_batch_ext_free)> batch(llama_batch_ext_init(ctx),
1061 llama_batch_ext_free);
1063 throw std::runtime_error(
"decode::segments - llama_batch_ext_init failed");
1067 const auto entry = [&](int32_t idx,
const llama_pos* pos) {
1068 if (idx >= 0 &&
static_cast<size_t>(idx) >= n_fit)
throw too_large(
static_cast<size_t>(idx) + 1);
1069 if (idx < 0 || !llama_batch_ext_set_pos(batch.get(), idx, pos) ||
1070 !llama_batch_ext_set_output_embd(batch.get(), idx,
true)) {
1071 throw std::runtime_error(
"decode::segments - the batch refused an entry (" + std::to_string(idx) +
")");
1076 const size_t n = source.
size();
1077 std::vector<LandedSegment> placed;
1079 llama_pos at = base;
1082 for (
size_t i = 0; i < n; ++i) {
1087 for (
size_t k = 0; k < seg.
tokens.size(); ++k) {
1088 const llama_pos pos = at +
static_cast<llama_pos
>(k);
1089 last = entry(llama_batch_ext_add_token(batch.get(), seq, seg.
tokens[k]), &pos);
1091 const int32_t n_tokens =
static_cast<int32_t
>(seg.
tokens.size());
1099 throw std::runtime_error(
"decode::segments - segment " + std::to_string(i) +
1100 " is bidirectional, and this context attends causally");
1103 source.
positions(i, at, positions.data());
1105 std::array<llama_pos, 4> pos{};
1106 for (int32_t r = 0; r < seg.
n_rows; ++r) {
1108 pos[k] = positions[
static_cast<size_t>(k) * seg.
n_rows + r];
1110 const llama_embd row{seg.
rows +
static_cast<size_t>(r) * seg.
n_embd_inp, 1,
1112 last = entry(llama_batch_ext_add_embd(batch.get(), seq, row), pos.data());
1118 if (last < 0)
return;
1120 llama_batch_ext_set_output_logits(batch.get(), last,
true);
1121 placed.back().has_logits =
true;
1124 if (
const int rc = llama_process(ctx, LLAMA_PROCESS_TYPE_DECODE, batch.get()); rc != 0) {
1125 throw DecodeError(rc,
false,
"decode::segments - the stream's one dispatch failed");
1127 for (
const auto& seg : placed) landed(seg);
1169template <
typename OnLanded>
1170inline void segments(llama_context* ctx, llama_seq_id seq, llama_pos base,
1172 Scratch& scratch, std::vector<llama_pos>& positions,
1173 OnLanded&& landed) {
1175 throw std::runtime_error(
"decode::segments - NULL context");
1216 const std::span<const llama_token>* items,
1220 std::vector<PackedChunk> chunks;
1221 int32_t chunk_total = 0;
1223 for (int32_t i = 0; i < n; ++i) {
1224 int32_t tc =
static_cast<int32_t
>(items[i].size());
1225 if (tc == 0)
continue;
1228 chunks.push_back({{i},
true});
1232 if (chunks.empty() || chunks.back().oversized ||
1233 chunk_total + tc > n_batch) {
1234 chunks.push_back({{i},
false});
1237 chunks.back().indices.push_back(i);
A token span as a stream of one TEXT segment, so text is placed by the rail like any other input.
size_t size() override
Number of segments in this prefill.
void positions(size_t, llama_pos, llama_pos *) override
Fill positions for an EMBD segment, given the absolute base the store chose.
Segment at(size_t) override
Segment i. Invalidates any previously returned segment.
size_t cells() const override
KV cells this whole sequence will consume, known BEFORE anything decodes.
TokenSource(std::span< const llama_token > tokens)
#define LLOYAL_LOG_DEBUG(...)
liblloyal - Common definitions and logging
void segments_each(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)
Each segment its own dispatch: the shape of a prefill into a sequence that keeps what it was given.
void batch_clear(llama_batch &batch)
The two helpers llama.cpp's common library used to provide for a llama_batch_init()-allocated batch: ...
void segments_whole(llama_context *ctx, const StreamReading &reading, llama_seq_id seq, llama_pos base, SegmentSource &source, std::vector< llama_pos > &positions, OnLanded &&landed)
The whole stream in one dispatch: one llama_batch_ext, tokens and rows as entries in order,...
void require_segment(const Segment &seg, size_t i)
A segment the source yielded, checked before anything is sized from it or handed back to the source: ...
void batch_add(llama_batch &batch, llama_token id, llama_pos pos, llama_seq_id seq_id, bool logits)
void llama_batch_has_seven_members()
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.
StreamReading reading_of(llama_context *ctx)
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.
int one(llama_context *ctx, llama_token tok, llama_pos pos, llama_seq_id seq_id=0, bool want_logits=true)
Decode a single token into the KV cache.
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.
void require_row_width(llama_context *ctx, int32_t n_embd_inp, const char *who)
Rows must be exactly the model's input width: a wrong-but-positive width starts later rows mid-row an...
bool leaves_branch_intact(int32_t rc, bool partial)
llama_decode failure carrying the raw return code
int scatter(llama_context *ctx, const ScatterItem *items, int32_t n, Scratch &scratch)
Decode multiple tokens per sequence in a single llama_decode() call.
int each(llama_context *ctx, const EachItem *items, int32_t n, Scratch &scratch)
Decode one token per sequence in a single llama_decode() call.
void require_fits_batch(llama_context *ctx, int32_t n_tokens, const char *who)
The context's batch is a hard limit: the pinned llama.cpp asserts n_tokens <= n_batch inside llama_de...
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.
DecodeError(int32_t rc_, bool partial_, const std::string &msg)
DecodeError as_partial() const
The same failure as an ENCLOSING operation reports it once work before the failing call has landed — ...
Input item for decode::each — one token for one sequence.
llama_token token
Token to decode.
bool output_logits
Whether to compute logits after this token.
llama_seq_id seq_id
Target sequence ID.
llama_pos pos
KV cache position for this token.
Input item for decode::embd — embedding rows for one sequence.
int32_t n_embd_inp
Row width — llama_model_n_embd_inp(model), the INPUT dim (not n_embd)
const float * rows
n_rows x n_embd_inp floats, caller-owned, valid for the call's duration
bool output_logits
When true, compute logits for the LAST row of the last sub-chunk.
const llama_pos * pos
Section-major positions: n_rows * n_pos_per_embd entries, laid out [s0...][s1...]....
bool non_causal
Bracket the whole decode in non-causal attention (Gemma-class projectors; requires n_ubatch >= n_rows...
int32_t n_pos_per_embd
1 (plain positions) or 4 (M-RoPE)
A segment of a stream once it is in the KV: what moved, for the caller's books.
int32_t cells
KV cells it took: tokens for TEXT, rows for EMBD.
bool has_logits
Whether its last entry computed logits.
llama_pos n_pos
Position it advanced: the token count for TEXT, n_pos for EMBD.
size_t index
Its index in the source.
A chunk of item indices produced by bin_pack()
bool oversized
True → single item exceeding n_batch.
std::vector< int32_t > indices
Indices into the original items array.
Input item for decode::scatter — multiple tokens for one sequence.
llama_pos start_pos
KV cache position for first token.
bool output_logits
When true, compute logits for last token in this run.
std::span< const llama_token > tokens
Token array (non-owning view)
llama_seq_id seq_id
Target sequence ID.
Reusable scratch buffers for multi-sequence batch construction.
std::vector< llama_seq_id > seq_id_single_
std::vector< llama_token > tokens_
std::vector< int32_t > n_seq_id_
void resize(int32_t n, int32_t n_pos_per_embd=1)
std::vector< int8_t > logits_
std::vector< llama_seq_id * > seq_id_ptrs_
llama_batch as_batch(int32_t n_tokens)
ABI-sensitive: writes llama_batch fields directly (no common_batch_* wrapper exists for external-buff...
llama_batch as_embd_batch(int32_t n_tokens, const float *rows)
The embedding-rail twin of as_batch(): embd points at CALLER-owned rows (never copied — that is the p...
std::vector< llama_pos > pos_
How far one input of a stream has landed: a reading of progress.
int32_t input
The input, as SegmentSource::input_of names it.
size_t total
Its cells in all, as SegmentSource::total_of quotes them.
size_t landed
Its cells in the KV so far.
Supplies the segment sequence for one branch's prefill.
virtual size_t total_of(int32_t) const
KV cells input input takes, summed over its segments: what progress on it counts towards.
virtual ~SegmentSource()=default
virtual size_t size()=0
Number of segments in this prefill.
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...
virtual size_t cells() const =0
KV cells this whole sequence will consume, known BEFORE anything decodes.
virtual Segment at(size_t i)=0
Segment i. Invalidates any previously returned segment.
virtual void positions(size_t i, llama_pos base, llama_pos *out)=0
Fill positions for an EMBD segment, given the absolute base the store chose.
One run of input in a heterogeneous prefill.
const float * rows
Embd: n_rows x n_embd_inp floats.
int32_t n_pos_per_embd
1 (plain positions) or 4 (M-RoPE)
bool non_causal
Bracket this segment's decode in non-causal attention.
llama_pos n_pos
Position advance this segment costs (may be < n_rows under M-RoPE)
std::span< const llama_token > tokens
Text: ready token ids (never re-tokenized by the store)
How a context reads what is placed into it: the one read of its traits that placement decides from.
bool whole() const
With no memory, attention that is not causal, or pooling, a dispatch sees only its own entries,...
bool takes_bidirectional() const
Whether a bidirectional block can ride inside one mixed batch.
bool keeps_memory
It keeps what it was given (a KV cache), so a later dispatch attends to an earlier one.
bool pooled
It pools a sequence into one output.
bool causal
Its attention is causal.