use anyhow::bail;
pub(crate) struct WeightCursor<'a> {
data: &'a [f32],
pos: usize,
pub layout: crate::loader::nam_json::WeightsLayout,
}
impl<'a> WeightCursor<'a> {
#[cold]
pub fn new(data: &'a [f32], layout: crate::loader::nam_json::WeightsLayout) -> Self {
Self {
data,
pos: 0,
layout,
}
}
pub fn is_interleaved4(&self) -> bool {
self.layout == crate::loader::nam_json::WeightsLayout::Interleaved4WaveNet
}
pub fn is_gate_major_lstm(&self) -> bool {
self.layout == crate::loader::nam_json::WeightsLayout::GateMajorLstm
}
pub(crate) fn read_slice(&mut self, len: usize) -> anyhow::Result<&'a [f32]> {
let end = self
.pos
.checked_add(len)
.filter(|&e| e <= self.data.len())
.ok_or_else(|| {
anyhow::anyhow!(
"Insufficient weights: required {} starting from position {}, available {}",
len,
self.pos,
self.data.len()
)
})?;
let slice = &self.data[self.pos..end];
self.pos = end;
Ok(slice)
}
pub(crate) fn read_f32(&mut self) -> anyhow::Result<f32> {
let s = self.read_slice(1)?;
Ok(s[0])
}
pub(crate) fn read_f32_finite(&mut self) -> anyhow::Result<f32> {
let val = self.read_f32()?;
if !val.is_finite() {
bail!(
"Non-finite f32 scalar at weight position {}: {:e}",
self.pos.wrapping_sub(1),
val
);
}
Ok(val)
}
pub(crate) fn verify_exhausted(&self) -> anyhow::Result<()> {
if self.pos != self.data.len() {
bail!(
"Model with inconsistent weights: consumed {}, total {}",
self.pos,
self.data.len()
);
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::loader::nam_json::WeightsLayout;
#[test]
fn read_slice_normal_reads_correct_range() {
let data = &[0.0f32, 1.0, 2.0, 3.0, 4.0];
let mut cursor = WeightCursor::new(data, WeightsLayout::Original);
let slice = cursor.read_slice(3).unwrap();
assert_eq!(slice, &[0.0, 1.0, 2.0]);
assert_eq!(cursor.pos, 3);
}
#[test]
fn read_slice_exceeding_data_len_returns_err() {
let data = &[0.0f32, 1.0, 2.0];
let mut cursor = WeightCursor::new(data, WeightsLayout::Original);
let result = cursor.read_slice(10);
assert!(result.is_err());
let err_msg = format!("{}", result.unwrap_err());
assert!(
err_msg.contains("Insufficient weights"),
"Expected 'Insufficient weights' in error message, got: {err_msg}"
);
}
#[test]
fn read_slice_usize_max_returns_err_no_panic() {
let data = &[0.0f32, 1.0, 2.0];
let mut cursor = WeightCursor::new(data, WeightsLayout::Original);
let result = cursor.read_slice(usize::MAX);
assert!(result.is_err());
}
#[test]
fn read_slice_exhausts_cursor_pos() {
let data = &[0.0f32, 1.0, 2.0, 3.0];
let mut cursor = WeightCursor::new(data, WeightsLayout::Original);
cursor.read_slice(2).unwrap();
cursor.read_slice(1).unwrap();
let slice = cursor.read_slice(1).unwrap();
assert_eq!(slice, &[3.0]);
assert_eq!(cursor.pos, 4);
}
#[test]
fn verify_exhausted_passes_when_all_read() {
let data = &[0.0f32, 1.0, 2.0];
let mut cursor = WeightCursor::new(data, WeightsLayout::Original);
cursor.read_slice(3).unwrap();
assert!(cursor.verify_exhausted().is_ok());
}
#[test]
fn verify_exhausted_fails_when_not_all_read() {
let data = &[0.0f32, 1.0, 2.0];
let mut cursor = WeightCursor::new(data, WeightsLayout::Original);
cursor.read_slice(1).unwrap();
let result = cursor.verify_exhausted();
assert!(result.is_err());
let err_msg = format!("{}", result.unwrap_err());
assert!(
err_msg.contains("inconsistent weights"),
"Expected 'inconsistent weights' in error message, got: {err_msg}"
);
}
#[test]
fn read_f32_advances_cursor() {
let data = &[7.0f32, 8.0, 9.0];
let mut cursor = WeightCursor::new(data, WeightsLayout::Original);
let val = cursor.read_f32().unwrap();
assert_eq!(val, 7.0);
assert_eq!(cursor.pos, 1);
}
#[test]
fn read_f32_finite_ok_for_finite_values() {
let data = &[42.0f32];
let mut cursor = WeightCursor::new(data, WeightsLayout::Original);
let val = cursor.read_f32_finite().unwrap();
assert_eq!(val, 42.0);
}
#[test]
fn read_f32_finite_err_for_nan() {
let data = &[f32::NAN];
let mut cursor = WeightCursor::new(data, WeightsLayout::Original);
let result = cursor.read_f32_finite();
assert!(result.is_err());
assert!(format!("{}", result.unwrap_err()).contains("Non-finite"));
}
#[test]
fn read_f32_finite_err_for_inf() {
let data = &[f32::INFINITY];
let mut cursor = WeightCursor::new(data, WeightsLayout::Original);
let result = cursor.read_f32_finite();
assert!(result.is_err());
}
#[test]
fn is_interleaved4_returns_true_for_correct_layout() {
let data = &[0.0f32];
let cursor = WeightCursor::new(data, WeightsLayout::Interleaved4WaveNet);
assert!(cursor.is_interleaved4());
}
#[test]
fn is_interleaved4_returns_false_for_original_layout() {
let data = &[0.0f32];
let cursor = WeightCursor::new(data, WeightsLayout::Original);
assert!(!cursor.is_interleaved4());
}
#[test]
fn is_gate_major_lstm_returns_true_for_correct_layout() {
let data = &[0.0f32];
let cursor = WeightCursor::new(data, WeightsLayout::GateMajorLstm);
assert!(cursor.is_gate_major_lstm());
}
#[test]
fn is_gate_major_lstm_returns_false_for_original_layout() {
let data = &[0.0f32];
let cursor = WeightCursor::new(data, WeightsLayout::Original);
assert!(!cursor.is_gate_major_lstm());
}
}