use std::fs::File;
use std::os::unix::fs::FileExt;
use std::path::{Path, PathBuf};
use crate::ane_pipeline::Pipeline;
use crate::e5rt::{DeviceMask, E5Error};
const MASKED: f32 = -10_000.0;
#[derive(Clone, Debug)]
pub struct Manifest {
pub hidden_size: usize,
pub vocab_size: usize,
pub seq_len: usize,
pub rope_dim: usize,
pub rope_freq_base: f64,
pub embedding: PathBuf,
pub layers: Vec<PathBuf>,
pub heads: Vec<PathBuf>,
}
impl Manifest {
pub fn load(dir: &Path) -> Result<Self, E5Error> {
let text = std::fs::read_to_string(dir.join("conversion_manifest.json"))?;
let v: serde_json::Value =
serde_json::from_str(&text).map_err(|e| E5Error::Parse(e.to_string()))?;
let model = &v["model"];
let num = |k: &str| -> Result<usize, E5Error> {
model[k]
.as_u64()
.map(|n| n as usize)
.ok_or_else(|| E5Error::Parse(format!("manifest missing model.{k}")))
};
let mil_of =
|dir: &Path, compiled: &str| dir.join(compiled.trim_end_matches('/')).join("model.mil");
let mut layers: Vec<PathBuf> = v["shards"]
.as_array()
.unwrap_or(&vec![])
.iter()
.filter_map(|s| s["compiled_path"].as_str())
.map(|c| mil_of(dir, c))
.collect();
layers.sort();
let mut head_specs: Vec<(u64, PathBuf)> = v["shared"]["lm_head_shards"]
.as_array()
.unwrap_or(&vec![])
.iter()
.filter_map(|h| {
Some((
h["vocab_start"].as_u64()?,
mil_of(dir, h["compiled_path"].as_str()?),
))
})
.collect();
head_specs.sort_by_key(|(start, _)| *start);
let embedding = dir.join(
v["shared"]["embedding"]["path"]
.as_str()
.unwrap_or("embedding.f16"),
);
for p in layers.iter().chain(head_specs.iter().map(|(_, p)| p)) {
if !p.exists() {
return Err(E5Error::Parse(format!(
"manifest references missing shard {}",
p.display()
)));
}
}
Ok(Manifest {
hidden_size: num("hidden_size")?,
vocab_size: num("vocab_size")?,
seq_len: num("seq_len")?,
rope_dim: num("rope_dim")?,
rope_freq_base: model["rope_freq_base"].as_f64().unwrap_or(1e6),
embedding,
layers,
heads: head_specs.into_iter().map(|(_, p)| p).collect(),
})
}
}
pub struct Decoder {
pipe: Pipeline,
manifest: Manifest,
embedding: File,
inv_freq: Vec<f64>,
position: usize,
attn: Vec<u16>,
attn_rows: usize,
kv_mask: Vec<u16>,
}
impl Decoder {
pub fn open_dir(dir: &Path, mask: DeviceMask) -> Result<Self, E5Error> {
let manifest = Manifest::load(dir)?;
let pipe = Pipeline::open(&manifest.layers, &manifest.heads, mask)?;
let embedding = File::open(&manifest.embedding)?;
let half = manifest.rope_dim / 2;
let inv_freq: Vec<f64> = (0..half)
.map(|i| manifest.rope_freq_base.powf(-(i as f64) / half as f64))
.collect();
let attn_rows =
pipe.shared_elems("attn_mask").unwrap_or(manifest.seq_len) / manifest.seq_len;
let mut d = Decoder {
pipe,
manifest,
embedding,
inv_freq,
position: 0,
attn: Vec::new(),
attn_rows,
kv_mask: Vec::new(),
};
d.attn = vec![half::f16::from_f32(MASKED).to_bits(); attn_rows * d.manifest.seq_len];
d.kv_mask = vec![0u16; d.manifest.seq_len];
d.reset()?;
Ok(d)
}
pub fn reset(&mut self) -> Result<(), E5Error> {
self.position = 0;
self.pipe.reset_state()?;
for v in &mut self.attn {
*v = half::f16::from_f32(MASKED).to_bits();
}
for v in &mut self.kv_mask {
*v = 0;
}
self.pipe.set_shared("attn_mask", &self.attn)?;
self.pipe.set_shared("kv_write_mask", &self.kv_mask)?;
Ok(())
}
pub fn position(&self) -> usize {
self.position
}
pub fn hidden_bytes(&self) -> usize {
self.manifest.hidden_size * 2
}
pub fn push(&mut self, token: u32, project: bool) -> Result<Option<u32>, E5Error> {
if token as usize >= self.manifest.vocab_size {
return Err(E5Error::Call("decoder: token out of vocab", -1));
}
let mut row = vec![0u8; self.manifest.hidden_size * 2];
self.embedding.read_exact_at(
&mut row,
token as u64 * self.manifest.hidden_size as u64 * 2,
)?;
self.push_row(&row, project)
}
pub fn push_hidden(&mut self, fp16_row: &[u8], project: bool) -> Result<Option<u32>, E5Error> {
if fp16_row.len() != self.manifest.hidden_size * 2 {
return Err(E5Error::Call("decoder: hidden row size mismatch", -1));
}
self.push_row(fp16_row, project)
}
pub fn rewind(&mut self, target: usize) -> Result<(), E5Error> {
if target > self.position {
return Err(E5Error::Call("decoder: rewind forward", -1));
}
let masked = half::f16::from_f32(MASKED).to_bits();
for row in 0..self.attn_rows {
for p in target..self.position {
self.attn[row * self.manifest.seq_len + p] = masked;
}
}
if self.position > 0 {
self.kv_mask[self.position - 1] = 0;
}
if target > 0 {
self.kv_mask[target - 1] = half::f16::from_f32(1.0).to_bits();
}
self.position = target;
self.pipe.set_shared("attn_mask", &self.attn)?;
self.pipe.set_shared("kv_write_mask", &self.kv_mask)?;
Ok(())
}
fn push_row(&mut self, row: &[u8], project: bool) -> Result<Option<u32>, E5Error> {
let p = self.position;
if p >= self.manifest.seq_len {
return Err(E5Error::Call("decoder: context horizon exhausted", -1));
}
let (slot, n) = self.pipe.embedding_slot()?;
if n as usize != self.manifest.hidden_size {
return Err(E5Error::Call("decoder: hidden/embedding mismatch", -1));
}
unsafe { std::ptr::copy_nonoverlapping(row.as_ptr(), slot as *mut u8, row.len()) };
let cos: Vec<u16> = self
.inv_freq
.iter()
.map(|f| half::f16::from_f64((p as f64 * f).cos()).to_bits())
.collect();
let sin: Vec<u16> = self
.inv_freq
.iter()
.map(|f| half::f16::from_f64((p as f64 * f).sin()).to_bits())
.collect();
self.pipe.set_shared("rope_cos", &cos)?;
self.pipe.set_shared("rope_sin", &sin)?;
let zero = 0u16;
for row in 0..self.attn_rows {
self.attn[row * self.manifest.seq_len + p] = zero;
}
if p > 0 {
self.kv_mask[p - 1] = 0;
}
self.kv_mask[p] = half::f16::from_f32(1.0).to_bits();
self.pipe.set_shared("attn_mask", &self.attn)?;
self.pipe.set_shared("kv_write_mask", &self.kv_mask)?;
self.pipe.step()?;
self.position += 1;
if !project {
return Ok(None);
}
let logits = self.pipe.logits();
if logits.is_empty() {
return Err(E5Error::Call("decoder: no head logits bound", -1));
}
let (best, _) = logits
.iter()
.enumerate()
.max_by(|(_, a), (_, b)| {
half::f16::from_bits(**a)
.to_f32()
.total_cmp(&half::f16::from_bits(**b).to_f32())
})
.ok_or(E5Error::Call("decoder: empty logits", -1))?;
Ok(Some(best as u32))
}
pub fn generate(&mut self, prompt: &[u32], max_new: usize) -> Result<Vec<u32>, E5Error> {
if prompt.is_empty() {
return Err(E5Error::Call("decoder: empty prompt", -1));
}
for &t in &prompt[..prompt.len() - 1] {
self.push(t, false)?;
}
let mut next = self.push(prompt[prompt.len() - 1], true)?;
let mut out = Vec::new();
for _ in 0..max_new {
let Some(t) = next else { break };
out.push(t);
next = self.push(t, true)?;
}
Ok(out)
}
}