use core::num::NonZeroU16;
use std::path::{Path, PathBuf};
use windit::plan::Span;
use self::{
alloc::with_capacity,
error::{AllocationSite, Result},
};
pub mod error;
mod alloc;
mod artifacts;
mod audio;
mod config;
mod contract;
mod embedding;
mod long_text;
mod map;
mod media;
mod options;
mod placement;
mod prewarm;
mod prompt;
mod table;
mod text;
mod tokenizer;
mod vision;
pub use artifacts::{
ArtifactLabel, ParseArtifactLabelError, Part,
compile::{CompileReport, PruneReport, compile_artifacts, prune},
};
pub use audio::{AudioEncoder, AudioPrecision};
pub use config::MAX_TOKENS;
pub use embedding::{EMBEDDING_DIM, Embedding, Matryoshka, Width};
pub use error::Error;
pub use long_text::WindowEmbedding;
pub use media::{AudioTokens, ImageTokens};
pub use options::{
AudioOptions, Batching, DEFAULT_AUDIO_BATCHING, DEFAULT_AUDIO_COREML, DEFAULT_AUDIO_PRECISION,
DEFAULT_BUDGET, DEFAULT_BUDGETS, DEFAULT_FOLD, DEFAULT_HOP_SAMPLES, DEFAULT_MIN_TAIL_SAMPLES,
DEFAULT_RESIZE, DEFAULT_SPLIT_SPECIAL_TOKENS, DEFAULT_TEXT_BATCHING, DEFAULT_TEXT_COREML,
DEFAULT_VISION_BATCHING, DEFAULT_VISION_COREML, DerivationTerms, EmbedderOptions, ImageOptions,
TextOptions,
};
pub use placement::{PACKED_SLOT_INVARIANT, PlacementTable, run_identity};
pub use prewarm::{PrewarmReport, Warmed};
pub use prompt::Prompt;
pub use text::{Framing, TextArtifacts, TextEmbedder, TextFunction, TextFunctions};
pub use tokenizer::{
SplitSpecialTokens,
bpe::{TokenId, TokenizerScratch},
};
pub use vision::{
ImageEncoder, PreparedImage, ResizeRoad, VisionArtifacts, VisionBudget, VisionBudgets,
};
pub use crate::embeddings::long_text::LongTextOptions;
pub use crate::embeddings::aggregate::{AggregatePolicyKind, EmaRenormalizedOptions};
pub use crate::embeddings::image::{ImageGeometryError, Rgb8Image};
pub use windit::{TailPolicy, plan::WindowOptions};
#[derive(Debug, Clone)]
pub struct AudioWindowEmbedding {
ordinal: usize,
samples: Span,
tokens: NonZeroU16,
embedding: Embedding,
}
impl AudioWindowEmbedding {
#[inline]
pub const fn ordinal(&self) -> usize {
self.ordinal
}
#[inline]
pub const fn samples(&self) -> Span {
self.samples
}
#[inline]
pub const fn tokens(&self) -> NonZeroU16 {
self.tokens
}
#[inline]
pub const fn embedding(&self) -> &Embedding {
&self.embedding
}
#[must_use]
#[inline]
pub fn into_embedding(self) -> Embedding {
self.embedding
}
}
#[derive(Debug)]
pub struct VisionPart {
artifacts: VisionArtifacts,
encoder: ImageEncoder,
prepared: PreparedImage,
}
impl VisionPart {
#[inline]
pub const fn artifacts(&self) -> &VisionArtifacts {
&self.artifacts
}
#[inline]
pub fn encoder_mut(&mut self) -> &mut ImageEncoder {
&mut self.encoder
}
#[must_use]
pub fn into_inner(self) -> (VisionArtifacts, ImageEncoder) {
(self.artifacts, self.encoder)
}
}
#[derive(Debug)]
pub struct Parts {
text: TextEmbedder,
audio: Option<AudioEncoder>,
vision: Option<VisionPart>,
}
impl Parts {
#[inline]
pub fn text_mut(&mut self) -> &mut TextEmbedder {
&mut self.text
}
#[inline]
pub fn audio_mut(&mut self) -> Option<&mut AudioEncoder> {
self.audio.as_mut()
}
#[inline]
pub fn vision_mut(&mut self) -> Option<&mut VisionPart> {
self.vision.as_mut()
}
#[must_use]
pub fn into_inner(self) -> (TextEmbedder, Option<AudioEncoder>, Option<VisionPart>) {
(self.text, self.audio, self.vision)
}
}
#[derive(Debug)]
pub struct Embedder {
parts: Parts,
}
impl Embedder {
pub fn load(root: impl AsRef<Path>, options: &EmbedderOptions) -> Result<Self> {
let root = root.as_ref();
let dir = |own: Option<&Path>| -> PathBuf { own.unwrap_or(root).to_path_buf() };
options.validate()?;
let text_options = options.text();
let artifacts = TextArtifacts::open(dir(text_options.dir()))?;
let text = TextEmbedder::new(&artifacts, text_options)?;
let audio = match options.audio() {
Some(audio) => Some(AudioEncoder::new(dir(audio.dir()), audio)?),
None => None,
};
let vision = match options.vision() {
Some(vision) => {
let artifacts = VisionArtifacts::open(dir(vision.dir()))?;
let encoder = ImageEncoder::new(&artifacts, vision)?;
Some(VisionPart {
artifacts,
encoder,
prepared: PreparedImage::new(),
})
}
None => None,
};
Ok(Self {
parts: Parts {
text,
audio,
vision,
},
})
}
pub fn prewarm(&mut self) -> Result<PrewarmReport> {
let mut report = self.parts.text.prewarm()?;
if let Some(audio) = self.parts.audio.as_mut() {
report.extend(audio.prewarm()?);
}
if let Some(vision) = self.parts.vision.as_mut() {
report.extend(vision.encoder.prewarm()?);
}
Ok(report)
}
pub fn embed_text(&mut self, text: &str, prompt: Prompt<'_>) -> Result<Embedding> {
self.parts.text.embed(text, prompt)
}
pub fn embed_text_long(&mut self, text: &str, prompt: Prompt<'_>) -> Result<Embedding> {
self.parts.text.embed_long(text, prompt)
}
pub fn embed_text_windows(
&mut self,
text: &str,
prompt: Prompt<'_>,
) -> Result<Vec<WindowEmbedding>> {
self.parts.text.embed_windows(text, prompt)
}
pub fn embed_audio(&mut self, samples: &[f32]) -> Result<Vec<AudioWindowEmbedding>> {
let audio = self
.parts
.audio
.as_mut()
.ok_or(Error::PartDisabled(Part::Audio))?;
let spans = audio.plan(samples.len())?;
let mut tokens: Vec<AudioTokens> = with_capacity(spans.len(), AllocationSite::WindowList)?;
for span in &spans {
tokens.push(audio.encode_window(&samples[span.start()..span.end()])?);
}
let batching = audio.options().batching();
let mut rows: Vec<&AudioTokens> = with_capacity(tokens.len(), AllocationSite::WindowList)?;
rows.extend(&tokens);
let mut vectors = Vec::new();
self
.parts
.text
.embed_audio_tokens_batch_with(&rows, batching, &mut vectors)?;
let mut windows: Vec<AudioWindowEmbedding> =
with_capacity(spans.len(), AllocationSite::WindowList)?;
for (ordinal, ((samples, tokens), embedding)) in
spans.into_iter().zip(&tokens).zip(vectors).enumerate()
{
windows.push(AudioWindowEmbedding {
ordinal,
samples,
tokens: tokens.valid(),
embedding,
});
}
Ok(windows)
}
pub fn embed_image(
&mut self,
image: Rgb8Image<'_>,
budget: Option<VisionBudget>,
) -> Result<Embedding> {
let vision = self
.parts
.vision
.as_mut()
.ok_or(Error::PartDisabled(Part::Vision))?;
let tokens = vision.encode(image, budget)?;
let batching = vision.encoder.options().batching();
let mut out = Vec::new();
self
.parts
.text
.embed_image_tokens_batch_with(&[&tokens], batching, &mut out)?;
Ok(out.pop().expect("one image, one vector"))
}
pub fn embed_images(
&mut self,
images: &[Rgb8Image<'_>],
budget: Option<VisionBudget>,
out: &mut Vec<Embedding>,
) -> Result<()> {
let vision = self
.parts
.vision
.as_mut()
.ok_or(Error::PartDisabled(Part::Vision))?;
let mut tokens: Vec<ImageTokens> = with_capacity(images.len(), AllocationSite::BatchOutput)?;
for image in images {
tokens.push(vision.encode(*image, budget)?);
}
let batching = vision.encoder.options().batching();
let mut rows: Vec<&ImageTokens> = with_capacity(tokens.len(), AllocationSite::BatchOutput)?;
rows.extend(&tokens);
self
.parts
.text
.embed_image_tokens_batch_with(&rows, batching, out)
}
pub fn derivation_terms(&self, part: Part) -> Option<DerivationTerms<'_>> {
match part {
Part::Text => Some(self.parts.text.derivation_terms()),
Part::Audio => self
.parts
.audio
.as_ref()
.map(AudioEncoder::derivation_terms),
Part::Vision => self
.parts
.vision
.as_ref()
.map(|vision| vision.encoder.derivation_terms()),
}
}
#[must_use]
pub fn into_parts(self) -> Parts {
self.parts
}
}
impl VisionPart {
fn encode(&mut self, image: Rgb8Image<'_>, budget: Option<VisionBudget>) -> Result<ImageTokens> {
self
.artifacts
.prepare_into(image, budget, self.encoder.options(), &mut self.prepared)?;
self.encoder.encode(&self.prepared)
}
}
#[cfg(test)]
mod tests;