use iroh::EndpointId;
use std::collections::HashMap;
use std::path::Path;
use std::sync::{
atomic::{AtomicU64, Ordering},
Arc,
};
pub use crate::mesh::should_be_host_for_model;
pub fn total_model_bytes(model: &Path) -> u64 {
let name = model.to_string_lossy();
if let Some(pos) = name.find("-00001-of-") {
let of_pos = pos + 10;
if let Some(ext_pos) = name[of_pos..].find(".gguf") {
if let Ok(n_split) = name[of_pos..of_pos + ext_pos].parse::<u32>() {
let prefix = &name[..pos + 1];
let suffix = &name[of_pos + ext_pos..];
let mut total: u64 = 0;
for i in 1..=n_split {
let split_name = format!("{}{:05}-of-{:05}{}", prefix, i, n_split, suffix);
total += std::fs::metadata(&split_name).map(|m| m.len()).unwrap_or(0);
}
return total;
}
}
}
std::fs::metadata(model).map(|m| m.len()).unwrap_or(0)
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum InferenceTarget {
None,
Local(u16),
Remote(EndpointId),
MoeLocal(u16),
MoeRemote(EndpointId),
}
#[derive(Clone, Debug, Default)]
pub struct MoeState {
pub nodes: Vec<InferenceTarget>,
pub fallbacks: Vec<InferenceTarget>,
}
#[derive(Clone, Debug, Default)]
pub struct ModelTargets {
pub targets: HashMap<String, Vec<InferenceTarget>>,
pub moe: Option<MoeState>,
counter: Arc<AtomicU64>,
}
#[derive(Clone, Debug)]
pub struct LocalProcessInfo {
pub backend: String,
pub pid: u32,
pub port: u16,
pub context_length: u32,
}
impl ModelTargets {
pub fn get(&self, model: &str) -> InferenceTarget {
match self.targets.get(model) {
Some(targets) if !targets.is_empty() => {
let idx = self.counter.fetch_add(1, Ordering::Relaxed) as usize % targets.len();
targets[idx].clone()
}
_ => InferenceTarget::None,
}
}
pub fn candidates(&self, model: &str) -> Vec<InferenceTarget> {
self.targets.get(model).cloned().unwrap_or_default()
}
pub fn pick_from(&self, candidates: &[InferenceTarget]) -> InferenceTarget {
if candidates.is_empty() {
InferenceTarget::None
} else {
let idx = self.counter.fetch_add(1, Ordering::Relaxed) as usize % candidates.len();
candidates[idx].clone()
}
}
pub fn pick_sticky_from(candidates: &[InferenceTarget], sticky_key: u64) -> InferenceTarget {
if candidates.is_empty() {
InferenceTarget::None
} else {
let idx = sticky_key as usize % candidates.len();
candidates[idx].clone()
}
}
pub fn get_moe_target(&self, session_hint: &str) -> Option<InferenceTarget> {
let moe = self.moe.as_ref()?;
if moe.nodes.is_empty() {
return None;
}
let hash = session_hint
.bytes()
.fold(0u64, |acc, b| acc.wrapping_mul(31).wrapping_add(b as u64));
let idx = (hash as usize) % moe.nodes.len();
Some(moe.nodes[idx].clone())
}
pub fn get_moe_failover_targets(&self, session_hint: &str) -> Vec<InferenceTarget> {
let Some(primary) = self.get_moe_target(session_hint) else {
return Vec::new();
};
let mut ordered = vec![primary.clone()];
if let Some(moe) = self.moe.as_ref() {
for fallback in &moe.fallbacks {
if fallback != &primary {
ordered.push(fallback.clone());
}
}
}
ordered
}
}