use crate::{ComputeUnits, Input, Output, Prediction};
use std::path::{Path, PathBuf};
use std::time::Instant;
use crate::InferError;
type Result<T> = std::result::Result<T, InferError>;
fn err<T>(stage: &'static str, msg: impl Into<String>) -> Result<T> {
Err(ierr(stage, msg))
}
fn ierr(stage: &'static str, msg: impl Into<String>) -> InferError {
InferError {
stage,
message: msg.into(),
}
}
#[derive(Clone, Debug)]
pub struct ShardManifest {
pub raw: serde_json::Value,
pub layer_shards: Vec<(String, usize, usize)>,
pub head_shards: Vec<(String, i64, i64)>,
pub hidden_size: i64,
pub vocab_size: i64,
pub seq: i64,
pub embed: bool,
}
impl ShardManifest {
pub fn load(dir: &Path) -> Result<ShardManifest> {
let text = std::fs::read_to_string(dir.join("manifest.json"))
.map_err(|e| ierr("manifest", format!("{}: {e}", dir.display())))?;
Self::parse(&text)
}
pub fn parse(text: &str) -> Result<ShardManifest> {
let raw: serde_json::Value =
serde_json::from_str(text).map_err(|e| ierr("manifest", format!("json: {e}")))?;
if raw["kind"].as_str() != Some("milshards") {
return err("manifest", "kind is not \"milshards\"");
}
let members = |key: &str| -> Result<Vec<(String, i64, i64)>> {
raw[key]
.as_array()
.ok_or_else(|| ierr("manifest", format!("{key}: missing array")))?
.iter()
.map(|m| {
let file = m["file"]
.as_str()
.ok_or_else(|| ierr("manifest", format!("{key}: member w/o file")))?
.to_string();
let rng = m["layers"]
.as_array()
.or_else(|| m["rows"].as_array())
.ok_or_else(|| ierr("manifest", format!("{key}: member w/o range")))?;
Ok((
file,
rng[0].as_i64().unwrap_or(0),
rng[1].as_i64().unwrap_or(0),
))
})
.collect()
};
let layer_shards = members("layer_shards")?
.into_iter()
.map(|(f, a, b)| (f, a as usize, b as usize))
.collect();
let head_shards = members("head_shards")?;
let hidden_size = raw["hidden_size"].as_i64().unwrap_or(0);
let vocab_size = raw["vocab_size"].as_i64().unwrap_or(0);
let seq = raw["seq"].as_i64().unwrap_or(0);
let embed = raw["embed"].as_bool().unwrap_or(false);
Ok(ShardManifest {
raw,
layer_shards,
head_shards,
hidden_size,
vocab_size,
seq,
embed,
})
}
}
#[cfg(target_os = "macos")]
pub struct ShardedModel {
pub manifest: ShardManifest,
layers: Vec<(crate::Model, crate::State)>,
heads: Vec<crate::Model>,
pub compiled_dirs: Vec<PathBuf>,
}
#[cfg(target_os = "macos")]
impl ShardedModel {
pub fn load(dir: &Path, units: ComputeUnits, work_dir: &Path) -> Result<ShardedModel> {
let manifest = ShardManifest::load(dir)?;
std::fs::create_dir_all(work_dir)
.map_err(|e| ierr("compile", format!("{}: {e}", work_dir.display())))?;
let mut compiled_dirs = Vec::new();
let mut layers = Vec::new();
for (file, a, b) in &manifest.layer_shards {
let pkg = dir.join(file);
let cdir = work_dir.join(format!("layer_{a:02}-{b:02}"));
let c = mil_compile::compile(&pkg, &cdir)
.map_err(|e| ierr("compile", format!("{file}: {e}")))?;
let m = crate::Model::load(&c.path, units)
.map_err(|e| ierr("load", format!("{file}: {e}")))?;
let st = m
.new_state()
.map_err(|e| ierr("state", format!("{file}: {e}")))?;
layers.push((m, st));
compiled_dirs.push(cdir);
}
let mut heads = Vec::new();
for (file, lo, hi) in &manifest.head_shards {
let pkg = dir.join(file);
let cdir = work_dir.join(format!("head_v{lo}-{hi}"));
let c = mil_compile::compile(&pkg, &cdir)
.map_err(|e| ierr("compile", format!("{file}: {e}")))?;
let m = crate::Model::load(&c.path, units)
.map_err(|e| ierr("load", format!("{file}: {e}")))?;
heads.push(m);
compiled_dirs.push(cdir);
}
Ok(ShardedModel {
manifest,
layers,
heads,
compiled_dirs,
})
}
pub fn predict(&self, inputs: &[Input]) -> Result<Prediction> {
let t0 = Instant::now();
let mut xbuf: Vec<u8> = Vec::new();
let mut xshape: Vec<i64> = Vec::new();
let mut xcode: i64 = 65552;
for (i, (m, st)) in self.layers.iter().enumerate() {
let p = if i == 0 {
m.predict_with_state(Some(st), inputs)?
} else {
let mut feeds: Vec<Input> = inputs
.iter()
.filter(|f| f.name != "x" && f.name != "ids")
.cloned()
.collect();
feeds.insert(
0,
Input {
name: "x",
shape: &xshape,
data: &xbuf,
dtype: mil_spec::DType::Fp16,
},
);
m.predict_with_state(Some(st), &feeds)?
};
let o = &p.outputs[0];
xbuf = o.data.clone();
xshape = o.shape.clone();
xcode = o.dtype_code;
}
if self.layers.is_empty() {
let x = inputs
.iter()
.find(|f| f.name == "x")
.ok_or_else(|| ierr("predict", "no layer shards and no `x` input"))?;
xbuf = x.data.to_vec();
xshape = x.shape.to_vec();
}
let out = if self.heads.is_empty() {
Output {
name: "x".into(),
shape: xshape,
dtype_code: xcode,
data: xbuf,
}
} else {
let feed = [Input {
name: "x",
shape: &xshape,
data: &xbuf,
dtype: mil_spec::DType::Fp16,
}];
let mut logits = Vec::new();
let mut code = 65552;
for (i, h) in self.heads.iter().enumerate() {
let p = h.predict(&feed)?;
let o = &p.outputs[0];
code = o.dtype_code;
if i == 0 {
logits.reserve(o.data.len() * self.heads.len());
}
logits.extend_from_slice(&o.data);
}
let s = if xshape.len() >= 3 { xshape[2] } else { 1 };
Output {
name: "logits".into(),
shape: vec![1, self.manifest.vocab_size, s, 1],
dtype_code: code,
data: logits,
}
};
Ok(Prediction {
outputs: vec![out],
latency: t0.elapsed(),
})
}
}
#[cfg(not(target_os = "macos"))]
pub struct ShardedModel;
#[cfg(not(target_os = "macos"))]
impl ShardedModel {
pub fn load(_dir: &Path, _units: ComputeUnits, _work: &Path) -> Result<ShardedModel> {
err("load", "mil_infer requires macOS")
}
pub fn predict(&self, _inputs: &[Input]) -> Result<Prediction> {
err("predict", "mil_infer requires macOS")
}
}