liblloyal 1.0.0
Branched Inference for llama.cpp
Loading...
Searching...
No Matches
embedding.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 "decode.hpp"
9#include <algorithm>
10#include <cmath>
11#include <cstdint>
12#include <llama/llama.h>
13#include <span>
14#include <stdexcept>
15#include <vector>
16
43
44// ===== NORMALIZATION MODES =====
45
49enum class Normalize : int32_t {
50 None = 0, // No normalization (raw embeddings)
51 L2 = 1, // L2 normalization (unit length, required for cosine similarity)
52};
53
54// ===== MODEL CAPABILITY CHECKS =====
55
64inline int32_t dimension(const llama_model *model) {
65 if (!model) {
66 LLOYAL_LOG_DEBUG("[embedding::dimension] ERROR: model is null");
67 return 0;
68 }
69
70 return llama_model_n_embd_out(model);
71}
72
82inline bool has_embeddings(const llama_model *model) {
83 return dimension(model) > 0;
84}
85
86// ===== CONTEXT CAPABILITY CHECKS =====
87
97inline bool has_pooling(llama_context *ctx) {
98 if (!ctx) {
99 LLOYAL_LOG_DEBUG("[embedding::has_pooling] ERROR: ctx is null");
100 return false;
101 }
102
103 return llama_pooling_type(ctx) != LLAMA_POOLING_TYPE_NONE;
104}
105
118inline int32_t pooling_type(llama_context *ctx) {
119 if (!ctx) {
120 LLOYAL_LOG_DEBUG("[embedding::pooling_type] ERROR: ctx is null");
121 return LLAMA_POOLING_TYPE_NONE;
122 }
123
124 return llama_pooling_type(ctx);
125}
126
127// ===== INTERNAL HELPERS =====
128
129namespace detail {
130
137inline void apply_l2_normalize(std::vector<float> &vec) {
138 if (vec.empty())
139 return;
140
141 float norm_sq = 0.0f;
142 for (float v : vec) {
143 norm_sq += v * v;
144 }
145
146 float norm = std::sqrt(norm_sq);
147 if (norm > 1e-8f) { // Avoid division by zero
148 for (float &v : vec) {
149 v /= norm;
150 }
151 } else {
153 "[embedding::detail::apply_l2_normalize] WARNING: near-zero norm");
154 }
155}
156
157} // namespace detail
158
159// ===== ENCODING (FORWARD PASS FOR EMBEDDINGS) =====
160
186inline void encode(llama_context *ctx, decode::SegmentSource &source) {
187 if (!ctx) {
188 throw std::runtime_error("embedding::encode - NULL context");
189 }
190 if (source.cells() == 0) {
191 throw std::runtime_error("embedding::encode - empty input");
192 }
193 if (!decode::reading_of(ctx).whole()) {
194 throw std::runtime_error(
195 "embedding::encode - this context keeps memory, attends causally and "
196 "does not pool, so it would read an input in pieces; give it a pooling "
197 "type, or use an embedding model");
198 }
199 decode::Scratch scratch;
200 std::vector<llama_pos> positions;
201 decode::segments(ctx, 0, 0, source, static_cast<int32_t>(llama_n_batch(ctx)),
202 scratch, positions, [](const decode::LandedSegment &) {});
203}
204
227inline void encode(llama_context *ctx, const llama_token *tokens,
228 int32_t n_tokens, int32_t n_batch) {
229 if (!tokens || n_tokens <= 0) {
230 throw std::runtime_error("embedding::encode - Invalid token array");
231 }
232 if (n_tokens > n_batch) {
233 throw std::runtime_error(
234 "embedding::encode - token count exceeds batch size (truncation not "
235 "supported, increase n_batch or reduce input length)");
236 }
237 decode::TokenSource source(std::span<const llama_token>(tokens, static_cast<size_t>(n_tokens)));
238 encode(ctx, source);
239}
240
244inline void encode(llama_context *ctx, const std::vector<llama_token> &tokens,
245 int32_t n_batch) {
246 encode(ctx, tokens.data(), static_cast<int32_t>(tokens.size()), n_batch);
247}
248
249// ===== EMBEDDING EXTRACTION =====
250
266inline std::vector<float> get(llama_context *ctx,
267 Normalize normalize = Normalize::L2) {
268 if (!ctx) {
269 LLOYAL_LOG_DEBUG("[embedding::get] ERROR: ctx is null");
270 throw std::invalid_argument("embedding::get: ctx is null");
271 }
272
273 // Get model to determine embedding dimension
274 const llama_model *model = llama_get_model(ctx);
275 if (!model) {
276 LLOYAL_LOG_DEBUG("[embedding::get] ERROR: failed to get model from context");
277 throw std::runtime_error("embedding::get: failed to get model");
278 }
279
280 // Warn if pooling not enabled (embeddings may be invalid)
281 if (!has_pooling(ctx)) {
283 "[embedding::get] WARNING: pooling not enabled, embeddings may be "
284 "invalid. Create context with pooling_type != NONE");
285 }
286
287 // Get embeddings pointer from llama.cpp
288 // For pooled embeddings, use sequence-specific API (sequence 0)
289 const float *embd_ptr = nullptr;
290 if (has_pooling(ctx)) {
291 embd_ptr = llama_get_embeddings_seq(ctx, 0);
292 LLOYAL_LOG_DEBUG("[embedding::get] Using llama_get_embeddings_seq for pooled "
293 "embeddings");
294 } else {
295 embd_ptr = llama_get_embeddings(ctx);
296 LLOYAL_LOG_DEBUG("[embedding::get] Using llama_get_embeddings (no pooling)");
297 }
298
299 if (!embd_ptr) {
300 LLOYAL_LOG_DEBUG("[embedding::get] ERROR: embeddings pointer is null. "
301 "Ensure context was created with embeddings=true and "
302 "tokens were encoded with logits=true for all tokens.");
303 throw std::runtime_error(
304 "embedding::get: embeddings unavailable (ensure embeddings=true in "
305 "context params and use encode_for_embeddings())");
306 }
307
308 // Copy to vector
309 int32_t n_embd = dimension(model);
310 std::vector<float> embeddings(embd_ptr, embd_ptr + n_embd);
311
312 // Apply normalization
313 if (normalize == Normalize::L2) {
314 detail::apply_l2_normalize(embeddings);
315 }
316
317 LLOYAL_LOG_DEBUG("[embedding::get] Extracted embeddings (dim=%d, normalize=%d)",
318 n_embd, static_cast<int>(normalize));
319
320 return embeddings;
321}
322
336inline std::vector<float> get_seq(llama_context *ctx, llama_seq_id seq,
337 Normalize normalize = Normalize::L2) {
338 if (!ctx) {
339 LLOYAL_LOG_DEBUG("[embedding::get_seq] ERROR: ctx is null");
340 throw std::invalid_argument("embedding::get_seq: ctx is null");
341 }
342
343 const llama_model *model = llama_get_model(ctx);
344 if (!model) {
345 LLOYAL_LOG_DEBUG("[embedding::get_seq] ERROR: failed to get model");
346 throw std::runtime_error("embedding::get_seq: failed to get model");
347 }
348
349 if (!has_pooling(ctx)) {
350 LLOYAL_LOG_DEBUG("[embedding::get_seq] WARNING: pooling not enabled");
351 }
352
353 // Try sequence-specific API
354 const float *embd_ptr = llama_get_embeddings_seq(ctx, seq);
355
356 // Fallback to global embeddings for seq=0
357 if (!embd_ptr) {
358 if (seq == 0) {
359 LLOYAL_LOG_DEBUG("[embedding::get_seq] Falling back to get() for seq=0");
360 return get(ctx, normalize);
361 }
362 LLOYAL_LOG_DEBUG("[embedding::get_seq] ERROR: embeddings unavailable for "
363 "seq=%d",
364 seq);
365 throw std::runtime_error("embedding::get_seq: embeddings unavailable");
366 }
367
368 int32_t n_embd = dimension(model);
369 std::vector<float> embeddings(embd_ptr, embd_ptr + n_embd);
370
371 if (normalize == Normalize::L2) {
372 detail::apply_l2_normalize(embeddings);
373 }
374
375 LLOYAL_LOG_DEBUG("[embedding::get_seq] Extracted embeddings for seq=%d "
376 "(dim=%d)",
377 seq, n_embd);
378
379 return embeddings;
380}
381
395inline std::vector<float> get_ith(llama_context *ctx, int32_t idx,
396 Normalize normalize = Normalize::L2) {
397 if (!ctx) {
398 LLOYAL_LOG_DEBUG("[embedding::get_ith] ERROR: ctx is null");
399 throw std::invalid_argument("embedding::get_ith: ctx is null");
400 }
401
402 const llama_model *model = llama_get_model(ctx);
403 if (!model) {
404 LLOYAL_LOG_DEBUG("[embedding::get_ith] ERROR: failed to get model");
405 throw std::runtime_error("embedding::get_ith: failed to get model");
406 }
407
408 const float *embd_ptr = llama_get_embeddings_ith(ctx, idx);
409 if (!embd_ptr) {
410 LLOYAL_LOG_DEBUG("[embedding::get_ith] ERROR: embeddings unavailable for "
411 "idx=%d",
412 idx);
413 throw std::runtime_error("embedding::get_ith: embeddings unavailable");
414 }
415
416 int32_t n_embd = dimension(model);
417 std::vector<float> embeddings(embd_ptr, embd_ptr + n_embd);
418
419 if (normalize == Normalize::L2) {
420 detail::apply_l2_normalize(embeddings);
421 }
422
423 LLOYAL_LOG_DEBUG("[embedding::get_ith] Extracted embeddings for idx=%d "
424 "(dim=%d)",
425 idx, n_embd);
426
427 return embeddings;
428}
429
430// ===== SIMILARITY =====
431
446inline float cosine_similarity(const std::vector<float> &a,
447 const std::vector<float> &b) {
448 if (a.size() != b.size()) {
449 LLOYAL_LOG_DEBUG("[embedding::cosine_similarity] ERROR: dimension mismatch "
450 "(%zu vs %zu)",
451 a.size(), b.size());
452 throw std::invalid_argument(
453 "embedding::cosine_similarity: dimension mismatch");
454 }
455
456 if (a.empty()) {
457 return 0.0f;
458 }
459
460 // For L2-normalized vectors, cosine similarity = dot product
461 float dot = 0.0f;
462 for (size_t i = 0; i < a.size(); ++i) {
463 dot += a[i] * b[i];
464 }
465
466 return dot;
467}
468
469} // namespace lloyal::embedding
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
#define LLOYAL_LOG_DEBUG(...)
liblloyal - Common definitions and logging
Definition common.hpp:48
Batch Decoding Operations.
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
void apply_l2_normalize(std::vector< float > &vec)
Apply L2 normalization to embedding vector (in-place)
std::vector< float > get_seq(llama_context *ctx, llama_seq_id seq, Normalize normalize=Normalize::L2)
Get embeddings for specific sequence.
float cosine_similarity(const std::vector< float > &a, const std::vector< float > &b)
Compute cosine similarity between two embedding vectors.
std::vector< float > get(llama_context *ctx, Normalize normalize=Normalize::L2)
Get embeddings for last decoded batch.
Normalize
Normalization modes for embedding vectors.
Definition embedding.hpp:49
int32_t pooling_type(llama_context *ctx)
Get pooling type for context.
std::vector< float > get_ith(llama_context *ctx, int32_t idx, Normalize normalize=Normalize::L2)
Get embeddings for specific token index in last batch.
void encode(llama_context *ctx, decode::SegmentSource &source)
Encode a whole input for embeddings: text, media, or both, as one stream.
bool has_embeddings(const llama_model *model)
Check if model supports embeddings.
Definition embedding.hpp:82
bool has_pooling(llama_context *ctx)
Check if context has pooling enabled.
Definition embedding.hpp:97
int32_t dimension(const llama_model *model)
Get embedding dimension for model: the width the model outputs, which a model with its own output hea...
Definition embedding.hpp:64
A segment of a stream once it is in the KV: what moved, for the caller's books.
Definition decode.hpp:909
Reusable scratch buffers for multi-sequence batch construction.
Definition decode.hpp:406
Supplies the segment sequence for one branch's prefill.
Definition decode.hpp:682
virtual size_t cells() const =0
KV cells this whole sequence will consume, known BEFORE anything decodes.