use std::collections::{BTreeMap, BTreeSet};
use std::path::{Path, PathBuf};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ShardName {
pub base: String,
pub index: usize,
pub total: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Member {
pub index: usize,
pub path: PathBuf,
pub bytes: i64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ShardGroup {
pub base: String,
pub total: usize,
pub members: Vec<Member>,
}
impl ShardGroup {
pub fn first_shard(&self) -> Option<&Path> {
self.members
.iter()
.find(|member| member.index == 1)
.map(|member| member.path.as_path())
}
pub fn footprint_bytes(&self) -> i64 {
self.members.iter().map(|member| member.bytes).sum()
}
pub fn complete(&self) -> bool {
self.members.len() == self.total
}
}
pub fn parse(filename: &str) -> Option<ShardName> {
let cut = filename.len().checked_sub(".gguf".len())?;
if !filename.get(cut..)?.eq_ignore_ascii_case(".gguf") {
return None;
}
let stem = &filename[..cut];
let of_position = stem.rfind("-of-")?;
let total_field = &stem[of_position + "-of-".len()..];
let total = five_digit_number(total_field)?;
if total == 0 {
return None;
}
let head = &stem[..of_position];
let dash_position = head.rfind('-')?;
let index_field = &head[dash_position + 1..];
let index = five_digit_number(index_field)?;
if index == 0 || index > total {
return None;
}
let base = &head[..dash_position];
if base.is_empty() {
return None;
}
Some(ShardName {
base: base.to_owned(),
index,
total,
})
}
pub fn shard_filename(base: &str, index: usize, total: usize) -> String {
format!("{base}-{index:05}-of-{total:05}.gguf")
}
pub fn group(files: &[(PathBuf, i64)]) -> (Vec<ShardGroup>, Vec<PathBuf>) {
let mut buckets: BTreeMap<(String, String, usize), Vec<Member>> = BTreeMap::new();
let mut loose: Vec<PathBuf> = Vec::new();
for (path, bytes) in files {
let shard = path
.file_name()
.and_then(|name| name.to_str())
.and_then(parse);
match shard {
Some(shard) => {
let directory = path
.parent()
.map(|dir| dir.to_string_lossy().into_owned())
.unwrap_or_default();
buckets
.entry((directory, shard.base, shard.total))
.or_default()
.push(Member {
index: shard.index,
path: path.clone(),
bytes: *bytes,
});
}
None => loose.push(path.clone()),
}
}
let groups = buckets
.into_iter()
.map(|((_, base, total), members)| {
let mut seen = BTreeSet::new();
let mut members: Vec<Member> = members
.into_iter()
.filter(|member| seen.insert(member.index))
.collect();
members.sort_by_key(|member| member.index);
ShardGroup {
base,
total,
members,
}
})
.collect();
(groups, loose)
}
fn five_digit_number(field: &str) -> Option<usize> {
if field.len() != 5 || !field.bytes().all(|byte| byte.is_ascii_digit()) {
return None;
}
field.parse().ok()
}