use std::ffi::c_void;
use std::path::{Path, PathBuf};
use crate::e5rt::{self, Api, DeviceMask, E5Error, PortKind};
pub struct Pipeline {
api: Api,
stream: *mut c_void,
#[allow(dead_code)]
ops: Vec<*mut c_void>,
hidden: [*mut c_void; 2],
hidden_elems: u64,
shared_in: Vec<(String, *mut c_void, u64)>,
states: Vec<(usize, String, *mut c_void, u64)>,
logits: Vec<(String, *mut c_void, u64)>,
}
pub fn shard_paths(dir: &Path) -> Result<(Vec<PathBuf>, Vec<PathBuf>), E5Error> {
let mut layers = Vec::new();
let mut heads = Vec::new();
for e in std::fs::read_dir(dir)? {
let p = e?.path();
let name = p.file_name().and_then(|s| s.to_str()).unwrap_or("");
if p.extension().and_then(|s| s.to_str()) != Some("mlmodelc") {
continue;
}
let mil = p.join("model.mil");
if !mil.exists() {
continue;
}
if name.starts_with("head_") {
heads.push(mil);
} else if name.starts_with("layer_") {
layers.push(mil);
}
}
layers.sort();
heads.sort();
if layers.is_empty() {
return Err(E5Error::Call("no layer_*.mlmodelc shards found", -1));
}
Ok((layers, heads))
}
impl Pipeline {
pub fn open(body: &[PathBuf], heads: &[PathBuf], mask: DeviceMask) -> Result<Self, E5Error> {
let api = Api::open()?;
let stream = api.stream()?;
let mut ops = Vec::new();
let mut shared_in: Vec<(String, *mut c_void, u64)> = Vec::new();
let mut states = Vec::new();
let mut hidden: [*mut c_void; 2] = [std::ptr::null_mut(); 2];
let mut hidden_elems = 0u64;
for (i, mil) in body.iter().enumerate() {
let (op, _fns) = e5rt::Program::make_op_on(&api, mil, mask)?;
ops.push(op);
for name in api.port_names(op, PortKind::Out) {
let port = api.port(op, PortKind::Out, &name)?;
let elems = api.port_elems(port);
if hidden[0].is_null() {
hidden = [api.buf(elems * 2)?, api.buf(elems * 2)?];
hidden_elems = elems;
}
api.bind(port, hidden[i % 2])?;
}
for name in api.port_names(op, PortKind::In) {
let port = api.port(op, PortKind::In, &name)?;
let elems = api.port_elems(port);
if name == "x" {
api.bind(port, hidden[(i + 1) % 2])?;
continue;
}
match shared_in.iter().find(|(n, _, _)| n == &name) {
Some((_, buf, _)) => api.bind(port, *buf)?,
None => {
let buf = api.buf(elems * 2)?;
shared_in.push((name.clone(), buf, elems));
api.bind(port, buf)?;
}
}
}
for name in api.port_names(op, PortKind::InOut) {
let port = api.port(op, PortKind::InOut, &name)?;
let elems = api.port_elems(port);
let buf = api.buf(elems * 2)?;
states.push((i, name, buf, elems));
api.bind(port, buf)?;
}
}
let mut logits = Vec::new();
for mil in heads {
let (op, _fns) = e5rt::Program::make_op_on(&api, mil, mask)?;
ops.push(op);
for name in api.port_names(op, PortKind::In) {
let port = api.port(op, PortKind::In, &name)?;
api.bind(port, hidden[(body.len() - 1) % 2])?;
}
for name in api.port_names(op, PortKind::Out) {
let port = api.port(op, PortKind::Out, &name)?;
let elems = api.port_elems(port);
let buf = api.buf(elems * 2)?;
logits.push((name.clone(), buf, elems));
api.bind(port, buf)?;
}
}
for op in &ops {
api.encode(stream, *op)?;
}
Ok(Pipeline {
api,
stream,
ops,
hidden,
hidden_elems,
shared_in,
states,
logits,
})
}
pub fn open_dir(dir: &Path, mask: DeviceMask) -> Result<Self, E5Error> {
let (layers, heads) = shard_paths(dir)?;
Self::open(&layers, &heads, mask)
}
pub fn embedding_slot(&self) -> Result<(*mut u16, u64), E5Error> {
Ok((self.api.ptr(self.hidden[1])?, self.hidden_elems))
}
pub fn shared_elems(&self, name: &str) -> Option<usize> {
self.shared_in
.iter()
.find(|(n, _, _)| n == name)
.map(|(_, _, e)| *e as usize)
}
pub fn set_shared(&self, name: &str, data: &[u16]) -> Result<(), E5Error> {
let (_, buf, elems) = self
.shared_in
.iter()
.find(|(n, _, _)| n == name)
.ok_or(E5Error::Call("set_shared: no such input", -1))?;
if data.len() as u64 > *elems {
return Err(E5Error::Call("set_shared: overflow", -1));
}
unsafe { std::ptr::copy_nonoverlapping(data.as_ptr(), self.api.ptr(*buf)?, data.len()) };
Ok(())
}
pub fn reset_state(&self) -> Result<(), E5Error> {
for (_, _, buf, elems) in &self.states {
unsafe { std::ptr::write_bytes(self.api.ptr(*buf)?, 0, *elems as usize) };
}
Ok(())
}
pub fn step(&self) -> Result<(), E5Error> {
self.api.exec(self.stream)
}
pub fn logits(&self) -> Vec<u16> {
let mut out = Vec::new();
for (_, buf, elems) in &self.logits {
if let Ok(p) = self.api.ptr(*buf) {
out.extend(unsafe { std::slice::from_raw_parts(p, *elems as usize) });
}
}
out
}
pub fn last_hidden(&self, n_layers: usize) -> Option<Vec<u16>> {
if self.hidden[0].is_null() || n_layers == 0 {
return None;
}
let buf = self.hidden[(n_layers - 1) % 2];
let p = self.api.ptr(buf).ok()?;
Some(unsafe { std::slice::from_raw_parts(p, self.hidden_elems as usize).to_vec() })
}
pub fn state_f16(&self, op_idx: usize, name: &str) -> Option<Vec<u16>> {
let (_, _, buf, elems) = self
.states
.iter()
.find(|(i, n, _, _)| *i == op_idx && n == name)?;
let p = self.api.ptr(*buf).ok()?;
Some(unsafe { std::slice::from_raw_parts(p, *elems as usize).to_vec() })
}
pub fn state_names(&self) -> Vec<(usize, String)> {
self.states
.iter()
.map(|(i, n, _, _)| (*i, n.clone()))
.collect()
}
}
unsafe impl Send for Pipeline {}
unsafe impl Sync for Pipeline {}