use std::fmt;
use crate::bucket::{Bucket, Buckets};
use crate::dtype::DType;
use crate::plan::Graph;
#[derive(Debug, Clone, Copy)]
pub struct HostTensor<'a> {
pub dtype: DType,
pub shape: &'a [usize],
pub bytes: &'a [u8],
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Batch<'a> {
pub ids: &'a [u32],
pub cu: &'a [u32],
pub markers: &'a [u32],
pub mcu: &'a [u32],
pub qtype: &'a [u8],
}
impl Batch<'_> {
#[must_use]
pub fn seqs(&self) -> usize {
self.cu.len().saturating_sub(1)
}
pub fn check(&self, vocab: usize, types: usize) -> Result<()> {
let bad = |m: String| Err(Error::Batch(m));
let s = self.seqs();
if self.cu.first() != Some(&0) || self.mcu.first() != Some(&0) {
return bad("cu and mcu must start at 0".into());
}
if self.mcu.len() != s + 1 || self.qtype.len() != s {
return bad(format!(
"{s} sequences but {} mcu and {} qtype",
self.mcu.len(),
self.qtype.len()
));
}
if self.cu[s] as usize != self.ids.len() || self.mcu[s] as usize != self.markers.len() {
return bad("cu and mcu must end at the totals".into());
}
for i in 0..s {
let (a, b) = (self.cu[i], self.cu[i + 1]);
if b < a || self.mcu[i + 1] < self.mcu[i] {
return bad(format!("sequence {i} ends before it starts"));
}
if usize::from(self.qtype[i]) >= types {
return bad(format!("sequence {i} has type {} of {types}", self.qtype[i]));
}
for &m in &self.markers[self.mcu[i] as usize..self.mcu[i + 1] as usize] {
if m >= b - a {
return bad(format!("marker {m} is past sequence {i} of {} tokens", b - a));
}
}
}
if let Some(&id) = self.ids.iter().find(|&&id| id as usize >= vocab) {
return bad(format!("token id {id} is past the vocabulary of {vocab}"));
}
Ok(())
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Outputs {
pub logits: Vec<f32>,
pub act: Vec<[f32; 2]>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Caps {
pub name: &'static str,
pub threads: usize,
pub graphs: bool,
pub unified_memory: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Error {
Batch(String),
NoBucket {
stage: String,
tokens: usize,
seqs: usize,
markers: usize,
},
Unsupported(String),
Device(String),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Batch(m) => write!(f, "bad batch: {m}"),
Error::NoBucket { stage, tokens, seqs, markers } => write!(
f,
"no {stage} bucket holds {tokens} tokens, {seqs} sequences and {markers} markers"
),
Error::Unsupported(m) => write!(f, "unsupported: {m}"),
Error::Device(m) => write!(f, "device: {m}"),
}
}
}
impl std::error::Error for Error {}
pub type Result<T> = std::result::Result<T, Error>;
pub trait Backend: Send + Sync {
type Weights: Send + Sync;
type Plan: Send;
fn caps(&self) -> Caps;
fn upload(&self, tensors: &[HostTensor<'_>], graph: &Graph) -> Result<Self::Weights>;
fn lower(&self, w: &Self::Weights, graph: &Graph, bucket: Bucket) -> Result<Self::Plan>;
fn run(&self, plan: &mut Self::Plan, batch: &Batch<'_>, out: &mut Outputs) -> Result<()>;
}
#[derive(Debug)]
pub struct Executor<B: Backend> {
backend: B,
weights: B::Weights,
graph: Graph,
stage: String,
buckets: Vec<Bucket>,
plans: Vec<(Bucket, B::Plan)>,
vocab: usize,
types: usize,
}
impl<B: Backend> Executor<B> {
pub fn new(
backend: B,
tensors: &[HostTensor<'_>],
graph: Graph,
buckets: &Buckets,
stage: &str,
vocab: usize,
types: usize,
) -> Result<Self> {
let weights = backend.upload(tensors, &graph)?;
Ok(Self {
backend,
weights,
graph,
stage: stage.to_string(),
buckets: buckets.stage(stage).to_vec(),
plans: Vec::new(),
vocab,
types,
})
}
pub fn backend(&self) -> &B {
&self.backend
}
pub fn warm(&self) -> impl Iterator<Item = Bucket> + '_ {
self.plans.iter().map(|p| p.0)
}
pub fn plans_mut(&mut self) -> impl Iterator<Item = &mut B::Plan> + '_ {
self.plans.iter_mut().map(|p| &mut p.1)
}
pub fn prepare(&mut self, bucket: Bucket) -> Result<usize> {
if let Some(i) = self.plans.iter().position(|p| p.0 == bucket) {
return Ok(i);
}
let plan = self.backend.lower(&self.weights, &self.graph, bucket)?;
self.plans.push((bucket, plan));
Ok(self.plans.len() - 1)
}
pub fn run(&mut self, batch: &Batch<'_>, out: &mut Outputs) -> Result<Bucket> {
batch.check(self.vocab, self.types)?;
let (t, s, m) = (batch.ids.len(), batch.seqs(), batch.markers.len());
let bucket = self.buckets.iter().copied().find(|b| b.holds(t, s, m)).ok_or_else(|| {
Error::NoBucket { stage: self.stage.clone(), tokens: t, seqs: s, markers: m }
})?;
let i = self.prepare(bucket)?;
self.backend.run(&mut self.plans[i].1, batch, out)?;
Ok(bucket)
}
}
#[derive(Debug, Clone, Default)]
pub struct BatchBuf {
ids: Vec<u32>,
cu: Vec<u32>,
markers: Vec<u32>,
mcu: Vec<u32>,
qtype: Vec<u8>,
}
impl BatchBuf {
pub fn clear(&mut self) {
for v in [&mut self.ids, &mut self.cu, &mut self.markers, &mut self.mcu] {
v.clear();
}
self.qtype.clear();
}
pub fn push(&mut self, ids: &[u32], markers: &[u32], qtype: u8) {
if self.cu.is_empty() {
self.cu.push(0);
self.mcu.push(0);
}
self.ids.extend_from_slice(ids);
self.markers.extend_from_slice(markers);
self.cu.push(u32::try_from(self.ids.len()).expect("under 2^32 tokens"));
self.mcu.push(u32::try_from(self.markers.len()).expect("under 2^32 markers"));
self.qtype.push(qtype);
}
#[must_use]
pub fn batch(&self) -> Batch<'_> {
const EMPTY: &[u32] = &[0];
let (cu, mcu) =
if self.cu.is_empty() { (EMPTY, EMPTY) } else { (&self.cu[..], &self.mcu[..]) };
Batch { ids: &self.ids, cu, markers: &self.markers, mcu, qtype: &self.qtype }
}
}