liblloyal 1.0.0
Branched Inference for llama.cpp
Loading...
Searching...
No Matches
decode.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
7#include "common.hpp"
8#include <algorithm>
9#include <array>
10#include <cstdint>
11#include <llama/llama.h>
12#include <memory>
13#include <span>
14#include <stdexcept>
15#include <string>
16#include <vector>
17
85namespace lloyal::decode {
86
87// Scratch::as_batch and as_embd_batch write llama_batch field by field; a new
88// upstream field would arrive as zero without a word. Fail the build instead:
89// the size on 64-bit, and the member count, which padding cannot hide.
90#if INTPTR_MAX == INT64_MAX
91static_assert(sizeof(llama_batch) == 56, "llama_batch changed shape: audit Scratch::as_batch and as_embd_batch");
92#endif
93namespace shape_guard {
95 [[maybe_unused]] auto [n_tokens, token, embd, pos, n_seq_id, seq_id, logits] = llama_batch{};
96}
97} // namespace shape_guard
98
99namespace detail {
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;
107 batch.token[i] = id;
108 batch.pos[i] = pos;
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};
112 batch.n_tokens++;
113}
114} // namespace detail
115
152inline bool leaves_branch_intact(int32_t rc, bool partial) {
153 return (rc == 1 || rc == -1) && !partial;
154}
155
156struct DecodeError : std::runtime_error {
157 int32_t rc;
159 DecodeError(int32_t rc_, bool partial_, const std::string& msg)
160 : std::runtime_error(msg + " (rc=" + std::to_string(rc_) +
161 (partial_ ? ", partial)" : ")")),
162 rc(rc_), partial(partial_), msg_(msg) {}
163
166 DecodeError as_partial() const { return DecodeError(rc, true, msg_); }
167
168private:
169 std::string msg_;
170};
171
180inline void require_fits_batch(llama_context* ctx, int32_t n_tokens, const char* who) {
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) + ")");
185 }
186}
187
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,
246 n_past);
247
248 if (!ctx) {
249 LLOYAL_LOG_DEBUG("[decode::many] ERROR: NULL context");
250 throw std::runtime_error("decode::many - NULL context");
251 }
252
253 if (!tokens || n_tokens <= 0) {
254 LLOYAL_LOG_DEBUG("[decode::many] ERROR: Invalid token array");
255 throw std::runtime_error("decode::many - Invalid token array");
256 }
257
258 if (n_batch <= 0) {
259 throw std::runtime_error("decode::many - n_batch must be positive");
260 }
261 require_fits_batch(ctx, std::min(n_batch, n_tokens), "decode::many");
262
263 // Thread-local batch avoids per-call allocation. Grows if needed, never shrinks.
264 struct ThreadLocalBatch {
265 llama_batch batch{};
266 int32_t capacity = 0;
267
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);
272 capacity = n;
273 }
274
275 ~ThreadLocalBatch() {
276 if (capacity > 0) llama_batch_free(batch);
277 }
278 };
279 thread_local ThreadLocalBatch tl;
280 tl.ensure(n_batch);
281 llama_batch& batch = tl.batch;
282
283 // Process tokens in chunks
284 int32_t processed = 0;
285 while (processed < n_tokens) {
286 const int32_t n_eval = std::min(n_tokens - processed, n_batch);
287
288 detail::batch_clear(batch);
289
290 // Add tokens one by one, mark logits=true only on the final chunk's last token
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);
295
296 detail::batch_add(batch, tokens[processed + i], pos, seq_id, want_logits);
297 }
298
299 // Decode chunk (updates KV cache)
300 const int rc = llama_decode(ctx, batch);
301 if (rc != 0) {
303 "[decode::many] ERROR: llama_decode failed at position %d (rc=%d)",
304 n_past, rc);
305 if (n_committed) *n_committed = processed;
306 return rc;
307 }
308
309 n_past += n_eval;
310 processed += n_eval;
311
312 LLOYAL_LOG_DEBUG("[decode::many] Processed %d/%d tokens",
313 processed, n_tokens);
314 }
315
316 LLOYAL_LOG_DEBUG("[decode::many] Decode complete");
317 if (n_committed) *n_committed = n_tokens;
318 return 0;
319}
320
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,
327 n_batch, seq_id);
328}
329
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) {
356 if (!ctx) {
357 throw std::runtime_error("decode::one - NULL context");
358 }
359
360 struct ThreadLocalBatch {
361 llama_batch batch = llama_batch_init(1, 0, 1);
362 ~ThreadLocalBatch() { llama_batch_free(batch); }
363 };
364 thread_local ThreadLocalBatch tl;
365
366 detail::batch_clear(tl.batch);
367 detail::batch_add(tl.batch, tok, pos, seq_id, want_logits);
368
369 return llama_decode(ctx, tl.batch);
370}
371
372// ============================================================================
373// Multi-Sequence Decode
374// ============================================================================
375
379struct EachItem {
380 llama_token token;
381 llama_pos pos;
382 llama_seq_id seq_id;
383 bool output_logits = false;
384};
385
394 std::span<const llama_token> tokens;
395 llama_pos start_pos;
396 llama_seq_id seq_id;
397 bool output_logits = false;
398};
399
406struct Scratch {
407 std::vector<llama_token> tokens_;
408 std::vector<llama_pos> pos_;
409 std::vector<int32_t> n_seq_id_;
410 std::vector<llama_seq_id> seq_id_single_;
411 std::vector<llama_seq_id*> seq_id_ptrs_;
412 std::vector<int8_t> logits_;
413
418 void resize(int32_t n, int32_t n_pos_per_embd = 1) {
419 tokens_.resize(n);
420 pos_.resize(static_cast<size_t>(n) * n_pos_per_embd);
421 n_seq_id_.resize(n);
422 seq_id_single_.resize(n);
423 seq_id_ptrs_.resize(n);
424 logits_.resize(n);
425 }
426
429 llama_batch as_batch(int32_t n_tokens) {
430 llama_batch batch{};
431 batch.n_tokens = n_tokens;
432 batch.token = tokens_.data();
433 batch.embd = nullptr;
434 batch.pos = pos_.data();
435 batch.n_seq_id = n_seq_id_.data();
436 batch.seq_id = seq_id_ptrs_.data();
437 batch.logits = logits_.data();
438 return batch;
439 }
440
446 llama_batch as_embd_batch(int32_t n_tokens, const float* rows) {
447 llama_batch batch{};
448 batch.n_tokens = n_tokens;
449 batch.token = nullptr;
450 batch.embd = const_cast<float*>(rows);
451 batch.pos = pos_.data();
452 batch.n_seq_id = n_seq_id_.data();
453 batch.seq_id = seq_id_ptrs_.data();
454 batch.logits = logits_.data();
455 return batch;
456 }
457};
458
462inline void require_row_width(llama_context* ctx, int32_t n_embd_inp, const char* who) {
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));
468 }
469 }
470}
471
489[[nodiscard]] inline int each(llama_context* ctx,
490 const EachItem* items,
491 int32_t n,
492 Scratch& scratch) {
493 if (!ctx) {
494 throw std::runtime_error("decode::each - NULL context");
495 }
496 if (n < 0) {
497 throw std::runtime_error("decode::each - negative item count");
498 }
499 if (n == 0) return 0;
500 require_fits_batch(ctx, n, "decode::each");
501
502 scratch.resize(n);
503
504 for (int32_t i = 0; i < n; ++i) {
505 scratch.tokens_[i] = items[i].token;
506 scratch.pos_[i] = items[i].pos;
507 scratch.n_seq_id_[i] = 1;
508 scratch.seq_id_single_[i] = items[i].seq_id;
509 scratch.seq_id_ptrs_[i] = &scratch.seq_id_single_[i];
510 scratch.logits_[i] = items[i].output_logits ? int8_t{1} : int8_t{0};
511 }
512
513 llama_batch batch = scratch.as_batch(n);
514
515 LLOYAL_LOG_DEBUG("[decode::each] Submitting %d tokens across %d sequences", n, n);
516
517 return llama_decode(ctx, batch);
518}
519
521[[nodiscard]] inline int each(llama_context* ctx,
522 const std::vector<EachItem>& items,
523 Scratch& scratch) {
524 return each(ctx, items.data(), static_cast<int32_t>(items.size()), scratch);
525}
526
547[[nodiscard]] inline int scatter(llama_context* ctx,
548 const ScatterItem* items,
549 int32_t n,
550 Scratch& scratch) {
551 if (!ctx) {
552 throw std::runtime_error("decode::scatter - NULL context");
553 }
554 if (n < 0) {
555 throw std::runtime_error("decode::scatter - negative item count");
556 }
557
558 int32_t total = 0;
559 for (int32_t i = 0; i < n; ++i) {
560 total += static_cast<int32_t>(items[i].tokens.size());
561 }
562 if (total == 0) return 0;
563 require_fits_batch(ctx, total, "decode::scatter");
564
565 scratch.resize(total);
566
567 int32_t cursor = 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());
572
573 for (int32_t j = 0; j < item_n; ++j) {
574 scratch.tokens_[cursor] = item.tokens[j];
575 scratch.pos_[cursor] = base_pos + j;
576 scratch.n_seq_id_[cursor] = 1;
577 scratch.seq_id_single_[cursor] = item.seq_id;
578 scratch.seq_id_ptrs_[cursor] = &scratch.seq_id_single_[cursor];
579
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};
583
584 ++cursor;
585 }
586 }
587
588 llama_batch batch = scratch.as_batch(total);
589
590 LLOYAL_LOG_DEBUG("[decode::scatter] Submitting %d total tokens across %d sequences", total, n);
591
592 return llama_decode(ctx, batch);
593}
594
596[[nodiscard]] inline int scatter(llama_context* ctx,
597 const std::vector<ScatterItem>& items,
598 Scratch& scratch) {
599 return scatter(ctx, items.data(), static_cast<int32_t>(items.size()), scratch);
600}
601
602// ============================================================================
603// Embedding-Row Decode (multimodal ingress)
604// ============================================================================
605
612struct EmbdItem {
614 const float* rows = nullptr;
615 int32_t n_rows = 0;
617 int32_t n_embd_inp = 0;
621 const llama_pos* pos = nullptr;
623 int32_t n_pos_per_embd = 1;
624 llama_seq_id seq_id = 0;
627 bool non_causal = false;
631 bool output_logits = false;
632};
633
646struct Segment {
647 enum class Kind { Text, Embd };
649
651 std::span<const llama_token> tokens;
652
655 const float* rows = nullptr;
663 bool non_causal = false;
664};
665
683 virtual ~SegmentSource() = default;
684
686 virtual size_t size() = 0;
687
711 virtual size_t cells() const = 0;
712
714 virtual Segment at(size_t i) = 0;
715
725 virtual void positions(size_t i, llama_pos base, llama_pos* out) = 0;
726
729 virtual int32_t input_of(size_t /*i*/) const { return -1; }
730
733 virtual size_t total_of(int32_t /*input*/) const { return 0; }
734};
735
740class TokenSource final : public SegmentSource {
741public:
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(); }
745 Segment at(size_t) override {
746 Segment seg;
747 seg.tokens = tokens_;
748 return seg;
749 }
750 void positions(size_t, llama_pos, llama_pos*) override {}
751
752private:
753 std::span<const llama_token> tokens_;
754};
755
761 int32_t input = -1;
763 size_t landed = 0;
765 size_t total = 0;
766};
767
794[[nodiscard]] inline int embd(llama_context* ctx,
795 const EmbdItem& item,
796 int32_t n_batch,
797 Scratch& scratch,
798 int32_t* n_committed = nullptr) {
799 if (!ctx) {
800 throw std::runtime_error("decode::embd - NULL context");
801 }
802 if (!item.rows || item.n_rows <= 0 || item.n_embd_inp <= 0) {
803 throw std::runtime_error("decode::embd - invalid rows");
804 }
805 if (!item.pos || (item.n_pos_per_embd != 1 && item.n_pos_per_embd != 4)) {
806 throw std::runtime_error("decode::embd - invalid positions");
807 }
808 if (n_batch <= 0) {
809 throw std::runtime_error("decode::embd - n_batch must be positive");
810 }
811 // The row width must match the RESIDENT model, not merely be positive.
812 // llama_batch carries no width metadata: llama_decode consumes rows at the
813 // model's own input width while the chunk loop below strides by this one.
814 // A wrong-but-positive width therefore starts later chunks mid-row and reads
815 // past the caller's allocation — corrupt vision state, or an out-of-bounds
816 // native read, with nothing to signal it.
817 require_row_width(ctx, item.n_embd_inp, "decode::embd");
818
819 const int32_t n = item.n_rows;
820 const int32_t nppe = item.n_pos_per_embd;
821 require_fits_batch(ctx, std::min(n_batch, n), "decode::embd");
822
823 // A non-causal block is bidirectional: every row must be able to attend to
824 // every other row, which only holds if they share one forward pass. The
825 // chunk loop below would split an oversized block across separate
826 // llama_decode calls, and rows in an earlier call cannot see later ones —
827 // the block silently stops being bidirectional and the vision state is
828 // wrong with no error anywhere. Refuse the configuration instead.
829 if (item.non_causal) {
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");
837 }
838 }
839
847 struct CausalGuard {
848 llama_context* ctx = nullptr;
850 bool engaged = false;
851 ~CausalGuard() { if (engaged) llama_set_causal_attn(ctx, true); }
852 } causal_guard{ctx, item.non_causal};
853
854 if (item.non_causal) llama_set_causal_attn(ctx, false);
855
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);
860
861 // Section-major re-pack for this view: source section s spans
862 // [s*n + processed, +n_view); destination is contiguous per section.
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);
867 }
868
869 for (int32_t i = 0; i < n_view; ++i) {
870 scratch.n_seq_id_[i] = 1;
871 scratch.seq_id_single_[i] = item.seq_id;
872 scratch.seq_id_ptrs_[i] = &scratch.seq_id_single_[i];
873 scratch.logits_[i] = 0;
874 }
875 const bool is_last_view = (processed + n_view >= n);
876 if (item.output_logits && is_last_view) {
877 scratch.logits_[n_view - 1] = 1;
878 }
879
880 llama_batch batch = scratch.as_embd_batch(
881 n_view, item.rows + static_cast<size_t>(processed) * item.n_embd_inp);
882
883 LLOYAL_LOG_DEBUG("[decode::embd] Submitting %d/%d rows (seq %d)",
884 processed + n_view, n, item.seq_id);
885
886 const int rc = llama_decode(ctx, batch);
887 if (rc != 0) {
888 LLOYAL_LOG_DEBUG("[decode::embd] ERROR: llama_decode failed (rc=%d)", rc);
889 if (n_committed) *n_committed = processed;
890 return rc;
891 }
892
893 processed += n_view;
894 }
895
896 LLOYAL_LOG_DEBUG("[decode::embd] Decode complete (%d rows)", n);
897 if (n_committed) *n_committed = n;
898 return 0;
899}
900
901// ============================================================================
902// The segments rail
903// ============================================================================
904
922
929 bool keeps_memory = true;
931 bool causal = true;
933 bool pooled = false;
934
939 bool whole() const { return !keeps_memory || !causal || pooled; }
940
945 bool takes_bidirectional() const { return !keeps_memory || !causal; }
946};
947
948inline StreamReading reading_of(llama_context* ctx) {
949 return StreamReading{llama_get_memory(ctx) != nullptr, llama_get_causal_attn(ctx),
950 llama_pooling_type(ctx) != LLAMA_POOLING_TYPE_NONE};
951}
952
953namespace detail {
954
958inline void require_segment(const Segment& seg, size_t i) {
959 if (seg.kind == Segment::Kind::Text) {
960 // Terminality comes from the index, so an empty trailing segment would make
961 // the real final one non-terminal: [EMBD, empty TEXT] would decode the image
962 // without logits and hand back nothing to sample.
963 if (seg.tokens.empty()) {
964 throw std::runtime_error("decode::segments - empty TEXT segment at " + std::to_string(i) +
965 "; a source must not yield empty segments");
966 }
967 return;
968 }
969 if (!seg.rows || seg.n_rows <= 0) {
970 throw std::runtime_error("decode::segments - empty embedding segment at " + std::to_string(i));
971 }
972 // n_pos_per_embd of -1 wraps a size_t multiply into an enormous allocation,
973 // and 0 hands positions() a zero-length buffer to write into.
974 if (seg.n_pos_per_embd != 1 && seg.n_pos_per_embd != 4) {
975 throw std::runtime_error("decode::segments - segment " + std::to_string(i) + " has n_pos_per_embd " +
976 std::to_string(seg.n_pos_per_embd) + " (expected 1 or 4)");
977 }
978 if (seg.n_embd_inp <= 0) {
979 throw std::runtime_error("decode::segments - segment " + std::to_string(i) + " has non-positive n_embd_inp");
980 }
981 if (seg.n_pos <= 0 || seg.n_pos > seg.n_rows) {
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) + "]");
984 }
985}
986
989template <typename OnLanded>
990void segments_each(llama_context* ctx, llama_seq_id seq, llama_pos base, SegmentSource& source,
991 int32_t rows_n_batch, Scratch& scratch, std::vector<llama_pos>& positions,
992 OnLanded&& landed) {
993 const size_t n = source.size();
994 const int32_t text_n_batch = static_cast<int32_t>(llama_n_batch(ctx));
995 llama_pos at = base;
996 bool any_landed = false;
997
998 const auto failed = [&](int rc, int32_t committed, const std::string& what) {
999 return DecodeError(rc, any_landed || committed > 0, "decode::segments - " + what);
1000 };
1001
1002 for (size_t i = 0; i < n; ++i) {
1003 const Segment seg = source.at(i);
1004 const bool is_last = (i + 1 == n);
1005 require_segment(seg, i);
1006
1007 if (seg.kind == Segment::Kind::Text) {
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");
1012 }
1013 at += n_tokens;
1014 any_landed = true;
1015 landed(LandedSegment{i, Segment::Kind::Text, n_tokens, n_tokens, true});
1016 continue;
1017 }
1018
1019 // The base is handed to the source, which applies its own model's rules.
1020 positions.assign(static_cast<size_t>(seg.n_rows) * seg.n_pos_per_embd, 0);
1021 source.positions(i, at, positions.data());
1022
1023 EmbdItem item;
1024 item.rows = seg.rows;
1025 item.n_rows = seg.n_rows;
1026 item.n_embd_inp = seg.n_embd_inp;
1027 item.pos = positions.data();
1028 item.n_pos_per_embd = seg.n_pos_per_embd;
1029 item.seq_id = seq;
1030 item.non_causal = seg.non_causal;
1031 item.output_logits = is_last;
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");
1035 }
1036 at += seg.n_pos;
1037 any_landed = true;
1038 landed(LandedSegment{i, Segment::Kind::Embd, seg.n_rows, seg.n_pos, is_last});
1039 }
1040}
1041
1045template <typename OnLanded>
1046void segments_whole(llama_context* ctx, const StreamReading& reading, llama_seq_id seq, llama_pos base,
1047 SegmentSource& source, std::vector<llama_pos>& positions, OnLanded&& landed) {
1048 // An oversized dispatch on this context aborts the process inside llama.cpp
1049 // rather than returning an error. The source's quote refuses one early, before
1050 // anything is encoded; the entries actually built are counted against the same
1051 // bound below, since the source is untrusted.
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));
1057 };
1058 if (source.cells() > n_fit) throw too_large(source.cells());
1059
1060 std::unique_ptr<llama_batch_ext, decltype(&llama_batch_ext_free)> batch(llama_batch_ext_init(ctx),
1061 llama_batch_ext_free);
1062 if (!batch) {
1063 throw std::runtime_error("decode::segments - llama_batch_ext_init failed");
1064 }
1065 // A stream placed whole is read whole: every entry's output is on, pooled or
1066 // per entry, as llama.cpp's encoder computes them for such a context anyway.
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) + ")");
1072 }
1073 return idx;
1074 };
1075
1076 const size_t n = source.size();
1077 std::vector<LandedSegment> placed;
1078 placed.reserve(n);
1079 llama_pos at = base;
1080 int32_t last = -1;
1081
1082 for (size_t i = 0; i < n; ++i) {
1083 const Segment seg = source.at(i);
1084 require_segment(seg, i);
1085
1086 if (seg.kind == Segment::Kind::Text) {
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);
1090 }
1091 const int32_t n_tokens = static_cast<int32_t>(seg.tokens.size());
1092 placed.push_back(LandedSegment{i, Segment::Kind::Text, n_tokens, n_tokens, false});
1093 at += n_tokens;
1094 continue;
1095 }
1096
1097 require_row_width(ctx, seg.n_embd_inp, "decode::segments");
1098 if (seg.non_causal && !reading.takes_bidirectional()) {
1099 throw std::runtime_error("decode::segments - segment " + std::to_string(i) +
1100 " is bidirectional, and this context attends causally");
1101 }
1102 positions.assign(static_cast<size_t>(seg.n_rows) * seg.n_pos_per_embd, 0);
1103 source.positions(i, at, positions.data());
1104 // positions() is section-major; an entry takes its own [t, y, x, z].
1105 std::array<llama_pos, 4> pos{};
1106 for (int32_t r = 0; r < seg.n_rows; ++r) {
1107 for (int32_t k = 0; k < seg.n_pos_per_embd; ++k) {
1108 pos[k] = positions[static_cast<size_t>(k) * seg.n_rows + r];
1109 }
1110 const llama_embd row{seg.rows + static_cast<size_t>(r) * seg.n_embd_inp, 1,
1111 static_cast<size_t>(seg.n_embd_inp)};
1112 last = entry(llama_batch_ext_add_embd(batch.get(), seq, row), pos.data());
1113 }
1114 placed.push_back(LandedSegment{i, Segment::Kind::Embd, seg.n_rows, seg.n_pos, false});
1115 at += seg.n_pos;
1116 }
1117
1118 if (last < 0) return;
1119 // The stream's last entry carries its logits, as a per-segment placement's does.
1120 llama_batch_ext_set_output_logits(batch.get(), last, true);
1121 placed.back().has_logits = true;
1122
1123 // A model that cannot mix tokens and rows in one batch answers -1 here.
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");
1126 }
1127 for (const auto& seg : placed) landed(seg);
1128}
1129
1130} // namespace detail
1131
1132
1169template <typename OnLanded>
1170inline void segments(llama_context* ctx, llama_seq_id seq, llama_pos base,
1171 SegmentSource& source, int32_t rows_n_batch,
1172 Scratch& scratch, std::vector<llama_pos>& positions,
1173 OnLanded&& landed) {
1174 if (!ctx) {
1175 throw std::runtime_error("decode::segments - NULL context");
1176 }
1177 if (const StreamReading reading = reading_of(ctx); reading.whole()) {
1178 detail::segments_whole(ctx, reading, seq, base, source, positions, landed);
1179 return;
1180 }
1181 detail::segments_each(ctx, seq, base, source, rows_n_batch, scratch, positions, landed);
1182}
1183
1184// ============================================================================
1185// Bin-Packing Utility
1186// ============================================================================
1187
1196 std::vector<int32_t> indices;
1197 bool oversized = false;
1198};
1199
1215inline std::vector<PackedChunk> bin_pack(
1216 const std::span<const llama_token>* items,
1217 int32_t n,
1218 int32_t n_batch) {
1219
1220 std::vector<PackedChunk> chunks;
1221 int32_t chunk_total = 0;
1222
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;
1226
1227 if (tc > n_batch) {
1228 chunks.push_back({{i}, true});
1229 continue;
1230 }
1231
1232 if (chunks.empty() || chunks.back().oversized ||
1233 chunk_total + tc > n_batch) {
1234 chunks.push_back({{i}, false});
1235 chunk_total = tc;
1236 } else {
1237 chunks.back().indices.push_back(i);
1238 chunk_total += tc;
1239 }
1240 }
1241
1242 return chunks;
1243}
1244
1245} // namespace lloyal::decode
A token span as a stream of one TEXT segment, so text is placed by the rail like any other input.
Definition decode.hpp:740
size_t size() override
Number of segments in this prefill.
Definition decode.hpp:743
void positions(size_t, llama_pos, llama_pos *) override
Fill positions for an EMBD segment, given the absolute base the store chose.
Definition decode.hpp:750
Segment at(size_t) override
Segment i. Invalidates any previously returned segment.
Definition decode.hpp:745
size_t cells() const override
KV cells this whole sequence will consume, known BEFORE anything decodes.
Definition decode.hpp:744
TokenSource(std::span< const llama_token > tokens)
Definition decode.hpp:742
#define LLOYAL_LOG_DEBUG(...)
liblloyal - Common definitions and logging
Definition common.hpp:48
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.
Definition decode.hpp:990
void batch_clear(llama_batch &batch)
The two helpers llama.cpp's common library used to provide for a llama_batch_init()-allocated batch: ...
Definition decode.hpp:103
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,...
Definition decode.hpp:1046
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: ...
Definition decode.hpp:958
void batch_add(llama_batch &batch, llama_token id, llama_pos pos, llama_seq_id seq_id, bool logits)
Definition decode.hpp:104
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
StreamReading reading_of(llama_context *ctx)
Definition decode.hpp:948
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 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.
Definition decode.hpp:354
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
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...
Definition decode.hpp:462
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
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...
Definition decode.hpp:180
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
DecodeError(int32_t rc_, bool partial_, const std::string &msg)
Definition decode.hpp:159
DecodeError as_partial() const
The same failure as an ENCLOSING operation reports it once work before the failing call has landed — ...
Definition decode.hpp:166
Input item for decode::each — one token for one sequence.
Definition decode.hpp:379
llama_token token
Token to decode.
Definition decode.hpp:380
bool output_logits
Whether to compute logits after this token.
Definition decode.hpp:383
llama_seq_id seq_id
Target sequence ID.
Definition decode.hpp:382
llama_pos pos
KV cache position for this token.
Definition decode.hpp:381
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
int32_t cells
KV cells it took: tokens for TEXT, rows for EMBD.
Definition decode.hpp:914
bool has_logits
Whether its last entry computed logits.
Definition decode.hpp:920
llama_pos n_pos
Position it advanced: the token count for TEXT, n_pos for EMBD.
Definition decode.hpp:916
size_t index
Its index in the source.
Definition decode.hpp:911
A chunk of item indices produced by bin_pack()
Definition decode.hpp:1195
bool oversized
True → single item exceeding n_batch.
Definition decode.hpp:1197
std::vector< int32_t > indices
Indices into the original items array.
Definition decode.hpp:1196
Input item for decode::scatter — multiple tokens for one sequence.
Definition decode.hpp:393
llama_pos start_pos
KV cache position for first token.
Definition decode.hpp:395
bool output_logits
When true, compute logits for last token in this run.
Definition decode.hpp:397
std::span< const llama_token > tokens
Token array (non-owning view)
Definition decode.hpp:394
llama_seq_id seq_id
Target sequence ID.
Definition decode.hpp:396
Reusable scratch buffers for multi-sequence batch construction.
Definition decode.hpp:406
std::vector< llama_seq_id > seq_id_single_
Definition decode.hpp:410
std::vector< llama_token > tokens_
Definition decode.hpp:407
std::vector< int32_t > n_seq_id_
Definition decode.hpp:409
void resize(int32_t n, int32_t n_pos_per_embd=1)
Definition decode.hpp:418
std::vector< int8_t > logits_
Definition decode.hpp:412
std::vector< llama_seq_id * > seq_id_ptrs_
Definition decode.hpp:411
llama_batch as_batch(int32_t n_tokens)
ABI-sensitive: writes llama_batch fields directly (no common_batch_* wrapper exists for external-buff...
Definition decode.hpp:429
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...
Definition decode.hpp:446
std::vector< llama_pos > pos_
Definition decode.hpp:408
How far one input of a stream has landed: a reading of progress.
Definition decode.hpp:759
int32_t input
The input, as SegmentSource::input_of names it.
Definition decode.hpp:761
size_t total
Its cells in all, as SegmentSource::total_of quotes them.
Definition decode.hpp:765
size_t landed
Its cells in the KV so far.
Definition decode.hpp:763
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 ~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...
Definition decode.hpp:729
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.
Definition decode.hpp:646
const float * rows
Embd: n_rows x n_embd_inp floats.
Definition decode.hpp:655
int32_t n_pos_per_embd
1 (plain positions) or 4 (M-RoPE)
Definition decode.hpp:661
bool non_causal
Bracket this segment's decode in non-causal attention.
Definition decode.hpp:663
llama_pos n_pos
Position advance this segment costs (may be < n_rows under M-RoPE)
Definition decode.hpp:659
std::span< const llama_token > tokens
Text: ready token ids (never re-tokenized by the store)
Definition decode.hpp:651
How a context reads what is placed into it: the one read of its traits that placement decides from.
Definition decode.hpp:927
bool whole() const
With no memory, attention that is not causal, or pooling, a dispatch sees only its own entries,...
Definition decode.hpp:939
bool takes_bidirectional() const
Whether a bidirectional block can ride inside one mixed batch.
Definition decode.hpp:945
bool keeps_memory
It keeps what it was given (a KV cache), so a later dispatch attends to an earlier one.
Definition decode.hpp:929
bool pooled
It pools a sequence into one output.
Definition decode.hpp:933
bool causal
Its attention is causal.
Definition decode.hpp:931