use std::ptr::NonNull;
use std::sync::Arc;
use offgrid_sys as sys;
use serde::{Deserialize, Serialize};
use crate::batch::Batch;
use crate::chat::default_threads;
use crate::{Error, Model, Result};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct EmbedParams {
pub n_ctx: u32,
pub max_batch_inputs: u32,
pub normalize: bool,
pub n_threads: Option<i32>,
}
impl Default for EmbedParams {
fn default() -> Self {
Self { n_ctx: 0, max_batch_inputs: 16, normalize: true, n_threads: None }
}
}
pub struct Embedder {
model: Arc<Model>,
ctx: NonNull<sys::llama_context>,
batch: Batch,
n_batch: usize,
max_inputs: usize,
normalize: bool,
dim: usize,
}
unsafe impl Send for Embedder {}
impl Drop for Embedder {
fn drop(&mut self) {
let _guard = crate::backend::setup_lock();
unsafe { sys::llama_free(self.ctx.as_ptr()) };
}
}
impl Embedder {
pub fn new(model: Arc<Model>, params: &EmbedParams) -> Result<Self> {
let n_ctx = match params.n_ctx {
0 => model.info().n_ctx_train.clamp(512, 8192),
n => n,
};
let max_inputs = params.max_batch_inputs.max(1);
let threads = params.n_threads.unwrap_or_else(default_threads);
let arch = model.meta("general.architecture").unwrap_or_default();
let causal = model.meta(&format!("{arch}.attention.causal")).is_none_or(|v| v != "false");
let last_pooling = model.meta(&format!("{arch}.pooling_type")).as_deref() == Some("3");
let mut cp = unsafe { sys::llama_context_default_params() };
cp.n_ctx = n_ctx;
cp.n_batch = n_ctx;
cp.n_ubatch = if causal && last_pooling { n_ctx.min(512) } else { n_ctx };
cp.n_seq_max = max_inputs;
cp.kv_unified = true;
cp.n_threads = threads;
cp.n_threads_batch = threads;
cp.embeddings = true;
cp.no_perf = true;
let ctx = {
let _guard = crate::backend::setup_lock();
unsafe { sys::llama_init_from_model(model.as_ptr(), cp) }
};
let ctx = NonNull::new(ctx).ok_or(Error::ContextCreate)?;
let this_pooling = unsafe { sys::llama_pooling_type(ctx.as_ptr()) };
if this_pooling == sys::llama_pooling_type_LLAMA_POOLING_TYPE_NONE
|| this_pooling == sys::llama_pooling_type_LLAMA_POOLING_TYPE_RANK
{
let _guard = crate::backend::setup_lock();
unsafe { sys::llama_free(ctx.as_ptr()) };
return Err(Error::NoEmbeddings);
}
let n_batch = unsafe { sys::llama_n_batch(ctx.as_ptr()) } as usize;
let max_inputs = unsafe { sys::llama_n_seq_max(ctx.as_ptr()) } as usize;
let m = model.as_ptr();
let dim = match unsafe { sys::llama_model_n_embd_out(m) } {
d if d > 0 => d as usize,
_ => (unsafe { sys::llama_model_n_embd(m) }) as usize,
};
Ok(Self { model, ctx, batch: Batch::new(n_batch, 1), n_batch, max_inputs, normalize: params.normalize, dim })
}
pub fn dim(&self) -> usize {
self.dim
}
pub fn model(&self) -> &Arc<Model> {
&self.model
}
pub fn embed_one(&mut self, text: &str) -> Result<Vec<f32>> {
Ok(self.embed(&[text])?.pop().expect("one input"))
}
pub fn embed<S: AsRef<str>>(&mut self, inputs: &[S]) -> Result<Vec<Vec<f32>>> {
let tokenized = inputs
.iter()
.map(|s| {
let t = self.model.tokenize(s.as_ref(), true, false)?;
if t.is_empty() {
return Err(Error::InvalidArgument("empty input".into()));
}
if t.len() > self.n_batch {
return Err(Error::ContextOverflow { needed: t.len(), n_ctx: self.n_batch });
}
Ok(t)
})
.collect::<Result<Vec<_>>>()?;
let mut out = Vec::with_capacity(inputs.len());
let mut start = 0;
while start < tokenized.len() {
let mut end = start;
let mut total = 0;
while end < tokenized.len() && end - start < self.max_inputs && total + tokenized[end].len() <= self.n_batch
{
total += tokenized[end].len();
end += 1;
}
self.run_batch(&tokenized[start..end], &mut out)?;
start = end;
}
Ok(out)
}
fn run_batch(&mut self, group: &[Vec<sys::llama_token>], out: &mut Vec<Vec<f32>>) -> Result<()> {
let ctx = self.ctx.as_ptr();
let mem = unsafe { sys::llama_get_memory(ctx) };
if !mem.is_null() {
unsafe { sys::llama_memory_clear(mem, true) };
}
self.batch.clear();
for (seq, tokens) in group.iter().enumerate() {
for (pos, &t) in tokens.iter().enumerate() {
self.batch.add(t, pos as i32, &[seq as i32], true);
}
}
let m = self.model.as_ptr();
let rc = unsafe {
if sys::llama_model_has_encoder(m) && !sys::llama_model_has_decoder(m) {
sys::llama_encode(ctx, self.batch.raw())
} else {
sys::llama_decode(ctx, self.batch.raw())
}
};
if rc != 0 {
return Err(Error::Decode(rc));
}
for seq in 0..group.len() {
let ptr = unsafe { sys::llama_get_embeddings_seq(ctx, seq as i32) };
if ptr.is_null() {
return Err(Error::NoEmbeddings);
}
let mut v = unsafe { std::slice::from_raw_parts(ptr, self.dim) }.to_vec();
if self.normalize {
let norm = v.iter().map(|x| x * x).sum::<f32>().sqrt();
if norm > 0.0 {
v.iter_mut().for_each(|x| *x /= norm);
}
}
out.push(v);
}
Ok(())
}
}