use std::fs::File;
use std::io;
use std::os::unix::fs::FileExt;
use std::path::{Path, PathBuf};
use super::variants::{Variant, VARIANT};
#[derive(Debug, thiserror::Error)]
pub enum ExpertIoError {
#[error("layer_idx {layer} out of range (must be < {num_layers})")]
BadLayerIdx { layer: i32, num_layers: usize },
#[error("expert_idx {expert} out of range (must be < {num_experts})")]
BadExpertIdx { expert: i32, num_experts: usize },
#[error("layer {layer} file not opened (probably missing on disk)")]
LayerFileMissing { layer: usize },
#[error(
"expert blob read short: expected {expected} bytes at \
layer={layer} expert={expert}, got {actual}"
)]
ShortRead {
layer: usize,
expert: usize,
expected: usize,
actual: usize,
},
#[error("out buffer must be EXPERT_SIZE={expected} bytes, got {actual}")]
BadOutLen { expected: usize, actual: usize },
#[error("I/O error reading layer {layer}: {source}")]
Io {
layer: usize,
#[source]
source: io::Error,
},
}
pub struct ExpertFiles {
layers: Vec<Option<File>>,
expert_size: usize,
experts_dir: PathBuf,
}
impl ExpertFiles {
pub fn open(experts_dir: &Path) -> Result<Self, ExpertIoError> {
let v: Variant = VARIANT;
let subdir = experts_dir.join("packed_experts");
let mut layers = Vec::with_capacity(v.num_layers);
for i in 0..v.num_layers {
let path = subdir.join(format!("layer_{i:02}.bin"));
match File::open(&path) {
Ok(f) => layers.push(Some(f)),
Err(e) if e.kind() == io::ErrorKind::NotFound => {
layers.push(None);
}
Err(e) => {
return Err(ExpertIoError::Io {
layer: i,
source: e,
});
}
}
}
Ok(Self {
layers,
expert_size: v.expert_size_4bit(),
experts_dir: experts_dir.to_path_buf(),
})
}
pub fn read_expert(
&self,
layer_idx: usize,
expert_idx: usize,
out: &mut [u8],
) -> Result<(), ExpertIoError> {
let v: Variant = VARIANT;
if layer_idx >= v.num_layers {
return Err(ExpertIoError::BadLayerIdx {
layer: layer_idx as i32,
num_layers: v.num_layers,
});
}
if expert_idx >= v.num_experts {
return Err(ExpertIoError::BadExpertIdx {
expert: expert_idx as i32,
num_experts: v.num_experts,
});
}
if out.len() != self.expert_size {
return Err(ExpertIoError::BadOutLen {
expected: self.expert_size,
actual: out.len(),
});
}
let Some(file) = self.layers[layer_idx].as_ref() else {
return Err(ExpertIoError::LayerFileMissing { layer: layer_idx });
};
let off = (expert_idx as u64) * (self.expert_size as u64);
let n = file.read_at(out, off).map_err(|e| ExpertIoError::Io {
layer: layer_idx,
source: e,
})?;
if n != self.expert_size {
return Err(ExpertIoError::ShortRead {
layer: layer_idx,
expert: expert_idx,
expected: self.expert_size,
actual: n,
});
}
Ok(())
}
pub fn num_layers(&self) -> usize {
self.layers.len()
}
pub fn has_layer(&self, layer_idx: usize) -> bool {
self.layers
.get(layer_idx)
.map(Option::is_some)
.unwrap_or(false)
}
}
impl std::fmt::Debug for ExpertFiles {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let opened = self.layers.iter().filter(|s| s.is_some()).count();
f.debug_struct("ExpertFiles")
.field("experts_dir", &self.experts_dir)
.field("num_layers", &self.layers.len())
.field("opened", &opened)
.field("expert_size", &self.expert_size)
.finish()
}
}