use anyhow::Context;
use std::path::{Path, PathBuf};
const REFERENCE_SUFFIX: &str = ".llama.f32";
const FRINK_SUFFIX: &str = ".frink.f32";
const TOKENS_SUFFIX: &str = ".tokens.txt";
fn reference_path(prefix: &str, i: usize) -> PathBuf {
if i == 0 {
PathBuf::from(format!("{prefix}{REFERENCE_SUFFIX}"))
} else {
PathBuf::from(format!("{prefix}.llama{}.f32", i + 1))
}
}
pub fn write(
prefix: &str,
tokens: &[u32],
references: &[&[f32]],
frink: &[f32],
) -> anyhow::Result<Vec<PathBuf>> {
let mut paths = Vec::with_capacity(references.len() + 2);
for (i, reference) in references.iter().enumerate() {
let p = reference_path(prefix, i);
write_f32(&p, reference)?;
paths.push(p);
}
let fx_path = PathBuf::from(format!("{prefix}{FRINK_SUFFIX}"));
write_f32(&fx_path, frink)?;
paths.push(fx_path);
let tok_path = PathBuf::from(format!("{prefix}{TOKENS_SUFFIX}"));
let ids = tokens
.iter()
.map(|t| t.to_string())
.collect::<Vec<_>>()
.join(" ");
std::fs::write(&tok_path, format!("{ids}\n"))
.with_context(|| format!("writing {}", tok_path.display()))?;
paths.push(tok_path);
Ok(paths)
}
fn write_f32(path: &Path, values: &[f32]) -> anyhow::Result<()> {
let mut bytes = Vec::with_capacity(values.len() * 4);
for v in values {
bytes.extend_from_slice(&v.to_le_bytes());
}
std::fs::write(path, &bytes).with_context(|| format!("writing {}", path.display()))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_dumped_vector_reads_back_bit_for_bit() {
let dir = std::env::temp_dir().join(format!("frink-dump-test-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let prefix = dir.join("case").to_string_lossy().into_owned();
let reference = vec![
0.1f32,
-0.0,
f32::MIN_POSITIVE,
3.402_823_5e38,
-1.234_567_8e-9,
];
let frink = vec![0.2f32, 1.0, -5.5, 0.0, 7.7];
let paths = write(&prefix, &[1, 2, 3], &[&reference], &frink).unwrap();
assert_eq!(paths.len(), 3);
let back = std::fs::read(&paths[0]).unwrap();
let read: Vec<f32> = back
.as_chunks::<4>()
.0
.iter()
.map(|c| f32::from_le_bytes(*c))
.collect();
assert_eq!(read.len(), reference.len());
for (a, b) in read.iter().zip(&reference) {
assert_eq!(
a.to_bits(),
b.to_bits(),
"dumped {b} came back as {a}: the dump is not bit-exact"
);
}
let ids = std::fs::read_to_string(&paths[2]).unwrap();
assert_eq!(ids.trim(), "1 2 3");
for p in &paths {
let _ = std::fs::remove_file(p);
}
let _ = std::fs::remove_dir(&dir);
}
#[test]
fn no_two_dumped_vectors_share_a_file_name() {
assert_ne!(REFERENCE_SUFFIX, FRINK_SUFFIX);
assert_ne!(REFERENCE_SUFFIX, TOKENS_SUFFIX);
assert_ne!(FRINK_SUFFIX, TOKENS_SUFFIX);
let dir = std::env::temp_dir().join(format!("frink-dump-names-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let prefix = dir.join("case").to_string_lossy().into_owned();
let refs: Vec<Vec<f32>> = (0..3).map(|i| vec![i as f32, 1.0]).collect();
let borrowed: Vec<&[f32]> = refs.iter().map(Vec::as_slice).collect();
let paths = write(&prefix, &[7], &borrowed, &[9.0f32, 1.0]).unwrap();
assert_eq!(paths.len(), 5, "3 references + frink + the token ids");
let unique: std::collections::HashSet<_> = paths.iter().collect();
assert_eq!(
unique.len(),
paths.len(),
"two dumps share a name: {paths:?}"
);
for (i, r) in refs.iter().enumerate() {
let back = std::fs::read(&paths[i]).unwrap();
assert_eq!(
f32::from_le_bytes(back[..4].try_into().unwrap()),
r[0],
"reference [{i}] landed in {} with someone else's bytes",
paths[i].display()
);
}
assert!(paths[0].to_string_lossy().ends_with(REFERENCE_SUFFIX));
for p in &paths {
let _ = std::fs::remove_file(p);
}
let _ = std::fs::remove_dir(&dir);
}
}