use std::sync::{Mutex, OnceLock, PoisonError};
use anyhow::{Context, Result, anyhow};
use xberg::embeddings::EMBEDDING_PRESETS;
use xberg::{EmbeddingConfig, EmbeddingModelType};
use crate::config::OnnxProvider;
pub fn acceleration(provider: OnnxProvider) -> Option<xberg::AccelerationConfig> {
use xberg::core::config::ExecutionProviderType as Provider;
let provider = match provider {
OnnxProvider::Auto => return None,
OnnxProvider::Cpu => Provider::Cpu,
OnnxProvider::CoreMl => Provider::CoreMl,
OnnxProvider::Cuda => Provider::Cuda,
OnnxProvider::TensorRt => Provider::TensorRt,
};
Some(xberg::AccelerationConfig { provider, device_id: 0 })
}
pub fn bound_ort_memory(intra_threads: usize) {
static APPLIED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
if APPLIED.swap(true, std::sync::atomic::Ordering::AcqRel) {
return;
}
xberg::set_ort_session_options(xberg::OrtSessionOptions {
memory_pattern: false,
cpu_arena: false,
max_threads: Some(intra_threads),
});
}
pub fn release_engines() -> usize {
xberg::clear_engine_caches()
}
static EMBED_PASSES_IN_FLIGHT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
static PASS_GATE: Mutex<()> = Mutex::new(());
#[must_use = "the engines are released when this guard drops"]
pub struct EmbedPass(());
pub fn begin_embed_pass() -> EmbedPass {
let _gate = PASS_GATE.lock().unwrap_or_else(PoisonError::into_inner);
EMBED_PASSES_IN_FLIGHT.fetch_add(1, std::sync::atomic::Ordering::AcqRel);
EmbedPass(())
}
impl Drop for EmbedPass {
fn drop(&mut self) {
let _gate = PASS_GATE.lock().unwrap_or_else(PoisonError::into_inner);
if EMBED_PASSES_IN_FLIGHT.fetch_sub(1, std::sync::atomic::Ordering::AcqRel) == 1 {
tracing::info!(
dropped = release_engines(),
"embedding pass finished; released resident embedding engines"
);
}
}
}
pub fn embed_passes_in_flight() -> usize {
EMBED_PASSES_IN_FLIGHT.load(std::sync::atomic::Ordering::Acquire)
}
static EMBED_POOL: OnceLock<rayon::ThreadPool> = OnceLock::new();
pub fn resolve_embed_threads(max_threads: usize) -> usize {
if max_threads == 0 {
std::cmp::max(2, rayon::current_num_threads() / 4)
} else {
max_threads
}
}
pub fn embed_pool(max_threads: usize) -> &'static rayon::ThreadPool {
EMBED_POOL.get_or_init(|| {
let n = resolve_embed_threads(max_threads);
rayon::ThreadPoolBuilder::new()
.num_threads(n)
.thread_name(|i| format!("bm-embed-{i}"))
.build()
.expect("failed to build embedding rayon pool")
})
}
#[derive(Clone)]
pub struct SharedEmbedder {
config: EmbeddingConfig,
dim: u16,
model_name: String,
max_embed_threads: usize,
}
impl SharedEmbedder {
pub fn load(preset: &str, max_embed_threads: usize, batch_size: usize) -> Result<Self> {
Self::load_with_provider(preset, max_embed_threads, batch_size, OnnxProvider::default())
}
pub fn load_with_provider(
preset: &str,
max_embed_threads: usize,
batch_size: usize,
provider: OnnxProvider,
) -> Result<Self> {
let meta = EMBEDDING_PRESETS.iter().find(|p| p.name == preset).ok_or_else(|| {
anyhow!(
"unknown embedding preset '{preset}'; \
available: fast, balanced, quality, multilingual"
)
})?;
let dim = u16::try_from(meta.dimensions)
.with_context(|| format!("preset '{preset}' dimension {} exceeds u16", meta.dimensions))?;
let config = EmbeddingConfig {
model: EmbeddingModelType::Preset {
name: preset.to_string(),
},
normalize: true,
batch_size,
show_download_progress: false,
cache_dir: None,
acceleration: acceleration(provider),
max_embed_duration_secs: Some(60),
max_sequence_length: None,
};
Ok(Self {
config,
dim,
model_name: preset.to_string(),
max_embed_threads,
})
}
pub fn dim(&self) -> u16 {
self.dim
}
pub fn model(&self) -> &str {
&self.model_name
}
pub fn embed(&self, text: &str) -> Result<Vec<f32>> {
if text.is_empty() {
return Err(anyhow!("embed: input text must not be empty"));
}
let (config, model, text) = (self.config.clone(), self.model_name.clone(), text.to_string());
self.run_on_embed_pool(move || {
let mut results = xberg::embeddings::embed_texts(&[text.as_str()], &config)
.with_context(|| format!("embed_texts(preset={model})"))?;
results
.pop()
.ok_or_else(|| anyhow!("embed_texts returned empty result"))
})?
}
#[cfg(any(feature = "code-search", feature = "documents"))]
pub fn embed_batch(&self, texts: &[&str]) -> Result<Vec<Vec<f32>>> {
if texts.is_empty() {
return Ok(Vec::new());
}
let (config, model) = (self.config.clone(), self.model_name.clone());
let owned: Vec<String> = texts.iter().map(|t| (*t).to_string()).collect();
self.run_on_embed_pool(move || {
let refs: Vec<&str> = owned.iter().map(String::as_str).collect();
xberg::embeddings::embed_texts(&refs, &config)
.with_context(|| format!("embed_texts(preset={model}, batch={})", refs.len()))
})?
}
fn run_on_embed_pool<T, F>(&self, work: F) -> Result<T>
where
T: Send + 'static,
F: FnOnce() -> T + Send + 'static,
{
let pool = embed_pool(self.max_embed_threads);
if pool.current_thread_index().is_some() {
return Ok(work());
}
let (tx, rx) = std::sync::mpsc::sync_channel(1);
pool.spawn(move || {
let _ = tx.send(std::panic::catch_unwind(std::panic::AssertUnwindSafe(work)));
});
match rx.recv() {
Ok(Ok(value)) => Ok(value),
Ok(Err(_)) | Err(_) => Err(anyhow!("embedding worker panicked")),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn embed_pass_guards_nest_and_count_down() {
let base = embed_passes_in_flight();
let first = begin_embed_pass();
let second = begin_embed_pass();
assert!(embed_passes_in_flight() >= base + 2);
drop(first);
assert!(
embed_passes_in_flight() > base,
"a pass is still running after the first ends"
);
drop(second);
}
#[test]
fn waiting_on_the_embed_pool_never_runs_foreign_work_on_the_waiting_worker() {
use std::sync::{Arc, Mutex, mpsc};
use std::time::Duration;
let embedder = SharedEmbedder::load("fast", 2, 8).expect("fast preset");
let scan_pool = rayon::ThreadPoolBuilder::new()
.num_threads(1)
.build()
.expect("scan pool");
let slot = Arc::new(Mutex::new(()));
let (done_tx, done_rx) = mpsc::channel();
let (first_in_tx, first_in_rx) = mpsc::channel();
let (holder_slot, holder_embedder, holder_done) = (Arc::clone(&slot), embedder.clone(), done_tx.clone());
scan_pool.spawn(move || {
let _held = holder_slot.lock().unwrap();
first_in_tx.send(()).ok();
std::thread::sleep(Duration::from_millis(50));
let answer = holder_embedder.run_on_embed_pool(|| 7).expect("embed pool result");
holder_done.send(("first", answer)).ok();
});
first_in_rx.recv().expect("first job started");
let second_slot = Arc::clone(&slot);
scan_pool.spawn(move || {
let _held = second_slot.lock().unwrap();
done_tx.send(("second", 0)).ok();
});
let mut finished = vec![
done_rx
.recv_timeout(Duration::from_secs(10))
.expect("deadlock: the waiting worker ran the second job inside the first"),
done_rx
.recv_timeout(Duration::from_secs(10))
.expect("second job never ran"),
];
finished.sort_unstable();
assert_eq!(finished, vec![("first", 7), ("second", 0)]);
}
#[test]
fn a_panicking_embed_job_is_an_error_not_a_hang() {
let embedder = SharedEmbedder::load("fast", 2, 8).expect("fast preset");
let result = embedder.run_on_embed_pool(|| -> u8 { panic!("boom") });
assert!(result.is_err());
}
#[test]
fn provider_setting_maps_onto_xberg_acceleration() {
use xberg::core::config::ExecutionProviderType as Provider;
assert!(
acceleration(OnnxProvider::Auto).is_none(),
"auto defers to xberg's platform default"
);
for (setting, expected) in [
(OnnxProvider::Cpu, Provider::Cpu),
(OnnxProvider::CoreMl, Provider::CoreMl),
(OnnxProvider::Cuda, Provider::Cuda),
(OnnxProvider::TensorRt, Provider::TensorRt),
] {
assert_eq!(acceleration(setting).expect("pinned provider").provider, expected);
}
assert_eq!(
OnnxProvider::default(),
OnnxProvider::Cpu,
"the bounded-memory provider is the default"
);
}
#[test]
fn bound_ort_memory_is_first_call_wins() {
bound_ort_memory(2);
bound_ort_memory(7);
}
#[test]
fn resolve_embed_threads_nonzero_passthrough() {
assert_eq!(resolve_embed_threads(4), 4);
assert_eq!(resolve_embed_threads(1), 1);
assert_eq!(resolve_embed_threads(16), 16);
}
#[test]
fn resolve_embed_threads_zero_gives_auto() {
let got = resolve_embed_threads(0);
let expected = std::cmp::max(2, rayon::current_num_threads() / 4);
assert_eq!(
got, expected,
"resolve_embed_threads(0) should yield max(2, cores/4) = {expected}"
);
assert!(got >= 2, "auto embed cap must be >= 2, got {got}");
}
}