use std::collections::{BTreeMap, BTreeSet};
use std::path::Path;
use nuvec_core::{Array, DType, Error, MappedFile, Result};
use crate::json::{self, Json};
fn invalid(msg: impl Into<String>) -> Error {
Error::InvalidArgument(format!("safetensors: {}", msg.into()))
}
#[derive(Debug, Clone, Default)]
pub struct SafeTensors {
pub tensors: BTreeMap<String, Array>,
pub metadata: BTreeMap<String, String>,
}
fn dtype_of(name: &str) -> Option<DType> {
Some(match name {
"BOOL" => DType::Bool,
"U8" => DType::U8,
"U16" => DType::U16,
"U32" => DType::U32,
"U64" => DType::U64,
"I8" => DType::I8,
"I16" => DType::I16,
"I32" => DType::I32,
"I64" => DType::I64,
"F16" => DType::F16,
"BF16" => DType::BF16,
"F32" => DType::F32,
"F64" => DType::F64,
"C64" => DType::Complex64,
"F8_E4M3" | "F8_E8M0" => DType::U8,
_ => return None,
})
}
fn name_of(dtype: DType) -> &'static str {
match dtype {
DType::Bool => "BOOL",
DType::U8 => "U8",
DType::U16 => "U16",
DType::U32 => "U32",
DType::U64 => "U64",
DType::I8 => "I8",
DType::I16 => "I16",
DType::I32 => "I32",
DType::I64 => "I64",
DType::F16 => "F16",
DType::BF16 => "BF16",
DType::F32 => "F32",
DType::F64 => "F64",
_ => "C64",
}
}
fn usize_of(v: &Json, what: &str) -> Result<usize> {
match v {
Json::Int(n) if *n >= 0 => Ok(*n as usize),
_ => Err(invalid(format!("{what} must be a non-negative integer"))),
}
}
const MAX_HEADER: usize = 100_000_000;
const TENSOR_ALIGN: usize = 256;
struct Header {
metadata: BTreeMap<String, String>,
ranges: Vec<(usize, usize, String, DType, Vec<usize>)>,
start: usize,
}
impl Header {
fn aligned(&self) -> bool {
self.ranges
.iter()
.all(|(begin, _, _, dtype, _)| (self.start + begin).is_multiple_of(dtype.size()))
}
fn plan(&self, keep: &dyn Fn(&str) -> bool) -> ReadPlan {
let mut plan = ReadPlan::default();
let mut file_end = None;
for (index, (begin, end, name, _, _)) in self.ranges.iter().enumerate() {
if !keep(name) {
continue;
}
let at = (self.start + begin) as u64;
let n = end - begin;
let offset = if file_end == Some(at) && plan.len.is_multiple_of(TENSOR_ALIGN) {
let last = plan.ranges.last_mut().expect("an open range");
last.2 += n;
plan.len
} else {
let offset = plan.len.next_multiple_of(TENSOR_ALIGN);
plan.ranges.push((at, offset, n));
offset
};
plan.tensors.push((index, offset));
plan.len = offset + n;
file_end = Some(at + n as u64);
}
plan
}
}
impl Header {
fn runs(&self, file_len: usize, page: u64, keep: &dyn Fn(&str) -> bool) -> Vec<(u64, u64)> {
let up = |x: u64| x.div_ceil(page) * page;
if self.ranges.iter().all(|(_, _, name, _, _)| keep(name)) {
return vec![(0, up(file_len as u64))];
}
let mut runs: Vec<(u64, u64)> = Vec::new();
for (begin, end, name, _, _) in &self.ranges {
if begin == end || !keep(name) {
continue;
}
let lo = (self.start + begin) as u64 / page * page;
let hi = up((self.start + end) as u64);
match runs.last_mut() {
Some((at, len)) if lo <= *at + *len => *len = hi.max(*at + *len) - *at,
_ => runs.push((lo, hi - lo)),
}
}
runs
}
}
#[derive(Default)]
struct ReadPlan {
ranges: Vec<(u64, usize, usize)>,
tensors: Vec<(usize, usize)>,
len: usize,
}
fn header_end(size: [u8; 8], file_len: usize) -> Result<usize> {
usize::try_from(u64::from_le_bytes(size))
.ok()
.filter(|&n| n <= MAX_HEADER)
.and_then(|n| n.checked_add(8))
.filter(|&end| end <= file_len)
.ok_or_else(|| invalid("header larger than the file or 100 MB"))
}
fn parse_header(json_bytes: &[u8], start: usize, file_len: usize) -> Result<Header> {
let Json::Object(entries) = json::parse(json_bytes)? else {
return Err(invalid("the header must be a JSON object"));
};
let mut metadata = BTreeMap::new();
let mut ranges = Vec::new();
let mut names = BTreeSet::new();
for (name, entry) in entries {
if name == "__metadata__" {
match entry {
Json::Null => {}
Json::Object(fields) => {
for (k, v) in fields {
let Json::Str(s) = v else {
return Err(invalid("metadata values must be strings"));
};
metadata.insert(k, s);
}
}
_ => return Err(invalid("`__metadata__` must be an object")),
}
continue;
}
if !names.insert(name.clone()) {
return Err(invalid(format!("duplicate tensor `{name}`")));
}
let Json::Object(fields) = entry else {
return Err(invalid(format!("entry `{name}` must be an object")));
};
let field = |key: &str| fields.iter().find(|(k, _)| k == key).map(|(_, v)| v);
let dtype = match field("dtype") {
Some(Json::Str(d)) => {
dtype_of(d).ok_or_else(|| invalid(format!("`{name}`: unsupported dtype {d}")))?
}
_ => return Err(invalid(format!("`{name}`: missing dtype"))),
};
let shape = match field("shape") {
Some(Json::Array(dims)) => dims
.iter()
.map(|d| usize_of(d, "a dimension"))
.collect::<Result<Vec<_>>>()?,
_ => return Err(invalid(format!("`{name}`: missing shape"))),
};
let (begin, end) = match field("data_offsets") {
Some(Json::Array(o)) if o.len() == 2 => {
(usize_of(&o[0], "an offset")?, usize_of(&o[1], "an offset")?)
}
_ => return Err(invalid(format!("`{name}`: missing data_offsets"))),
};
let expected = shape
.iter()
.try_fold(dtype.size(), |n, &d| n.checked_mul(d))
.ok_or_else(|| invalid(format!("`{name}`: size overflows")))?;
if end.checked_sub(begin) != Some(expected) {
return Err(invalid(format!(
"`{name}`: data_offsets [{begin}, {end}] do not hold {expected} bytes"
)));
}
ranges.push((begin, end, name, dtype, shape));
}
ranges.sort_by_key(|r| (r.0, r.1));
let mut at = 0;
for (begin, end, name, ..) in &ranges {
if *begin != at {
return Err(invalid(format!(
"`{name}`: data_offsets overlap or leave a gap"
)));
}
at = *end;
}
if start.checked_add(at) != Some(file_len) {
return Err(invalid(
"the tensors do not cover the data to the end of the file",
));
}
Ok(Header {
metadata,
ranges,
start,
})
}
fn read_header(path: &Path) -> Result<(Header, usize)> {
use std::io::Read;
let io = |e: std::io::Error| Error::Io(format!("{}: {e}", path.display()));
let mut file = std::fs::File::open(path).map_err(io)?;
let file_len = usize::try_from(file.metadata().map_err(io)?.len())
.map_err(|_| Error::Io(format!("{}: too large", path.display())))?;
let mut size = [0u8; 8];
file.read_exact(&mut size).map_err(|_| {
invalid(format!(
"{}: file shorter than its header size",
path.display()
))
})?;
let start = header_end(size, file_len).map_err(|e| named(path, e))?;
let mut json_bytes = vec![0u8; start - 8];
file.read_exact(&mut json_bytes).map_err(io)?;
let header = parse_header(&json_bytes, start, file_len).map_err(|e| named(path, e))?;
Ok((header, file_len))
}
fn named(path: &Path, e: Error) -> Error {
match e {
Error::InvalidArgument(m) => Error::InvalidArgument(format!("{}: {m}", path.display())),
Error::Io(m) => Error::Io(format!("{}: {m}", path.display())),
e => e,
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Footprint {
pub mapped: u64,
pub copied: u64,
pub transient: u64,
}
impl Footprint {
fn of(header: &Header, file_len: usize, keep: &dyn Fn(&str) -> bool) -> Self {
if header.aligned() {
let page = nuvec_core::MappedRanges::page_size() as u64;
let mapping = header.runs(file_len, page, keep).iter().map(|r| r.1).sum();
Footprint {
mapped: mapping,
copied: 0,
transient: mapping,
}
} else {
let copied = nuvec_core::MappedFile::copy_size(header.plan(keep).len as u64);
Footprint {
mapped: 0,
copied,
transient: copied,
}
}
}
}
pub fn safetensors_footprint(path: impl AsRef<Path>) -> Result<Footprint> {
safetensors_footprint_filtered(path, |_| true)
}
pub fn safetensors_footprint_filtered(
path: impl AsRef<Path>,
keep: impl Fn(&str) -> bool,
) -> Result<Footprint> {
let keep = |name: &str| !name.starts_with(PAD) && keep(name);
let path = path.as_ref();
let files = if path.is_dir() {
let mut files: Vec<_> = std::fs::read_dir(path)
.map_err(|e| Error::Io(format!("{}: {e}", path.display())))?
.filter_map(|e| e.ok().map(|e| e.path()))
.filter(|p| p.extension().is_some_and(|x| x == "safetensors"))
.collect();
files.sort();
files
} else {
vec![path.to_path_buf()]
};
let mut total = Footprint::default();
for file in &files {
let (header, len) = read_header(file)?;
let one = Footprint::of(&header, len, &keep);
total.mapped += one.mapped;
total.copied += one.copied;
}
total.transient = total.mapped + total.copied;
Ok(total)
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct TensorInfo {
pub name: String,
pub dtype: DType,
pub shape: Vec<usize>,
pub bytes: usize,
}
pub fn safetensors_tensors(path: impl AsRef<Path>) -> Result<Vec<TensorInfo>> {
let path = path.as_ref();
let (header, _) = read_header(path)?;
Ok(header
.ranges
.into_iter()
.filter(|(_, _, name, _, _)| !name.starts_with(PAD))
.map(|(begin, end, name, dtype, shape)| TensorInfo {
name,
dtype,
shape,
bytes: end - begin,
})
.collect())
}
pub fn load_safetensors(path: impl AsRef<Path>) -> Result<SafeTensors> {
load_safetensors_filtered(path, |_| true)
}
pub fn load_safetensors_filtered(
path: impl AsRef<Path>,
keep: impl Fn(&str) -> bool,
) -> Result<SafeTensors> {
let keep = |name: &str| !name.starts_with(PAD) && keep(name);
let path = path.as_ref();
let (header, len) = read_header(path)?;
let aligned = header.aligned();
let plan = (!aligned).then(|| header.plan(&keep));
let page = nuvec_core::MappedRanges::page_size() as u64;
let runs = header.runs(len, page, &keep);
let need = match &plan {
Some(plan) => nuvec_core::MappedFile::copy_size(plan.len as u64),
None => runs.iter().map(|r| r.1).sum(),
};
if let Some(room) = nuvec_core::MappedFile::headroom()
&& need > room
{
return Err(Error::Io(format!(
"{}: out of memory: loading needs {need} bytes (mapped or read), {room} \
are left under the memory limit",
path.display()
)));
}
let oom = |e| match e {
Error::OutOfMemory(n) => Error::Io(format!(
"{}: out of memory: {n} bytes pass the memory limit",
path.display()
)),
e => e,
};
let mut out = SafeTensors {
metadata: header.metadata,
..SafeTensors::default()
};
match plan {
Some(plan) => {
let data = MappedFile::read_ranges(path, &plan.ranges, plan.len).map_err(oom)?;
let mut ranges: Vec<_> = header.ranges.into_iter().map(Some).collect();
for (index, offset) in plan.tensors {
let (_, _, name, dtype, shape) = ranges[index].take().expect("kept once");
out.tensors.insert(name, data.array(offset, dtype, &shape)?);
}
}
None if runs != [(0, len.div_ceil(page as usize) as u64 * page)] => {
let ranges: Vec<(u64, u64)> = runs
.iter()
.map(|&(o, n)| (o, n.min(len as u64 - o)))
.collect();
let file = nuvec_core::MappedRanges::open(path, &ranges).map_err(oom)?;
for (begin, end, name, dtype, shape) in header.ranges {
if keep(&name) {
let tensor = if begin == end {
nuvec_core::ops::zeros(
shape.iter().map(|&d| d as i32).collect::<Vec<_>>(),
dtype,
)
} else {
file.array(header.start + begin, dtype, &shape)?
};
out.tensors.insert(name, tensor);
}
}
}
None => {
let file = MappedFile::open(path).map_err(oom)?;
if file.len() != len {
return Err(Error::Io(format!(
"{}: the file changed while it was loaded",
path.display()
)));
}
for (begin, _, name, dtype, shape) in header.ranges {
if keep(&name) {
let tensor = file.array(header.start + begin, dtype, &shape)?;
out.tensors.insert(name, tensor);
}
}
}
}
Ok(out)
}
pub(crate) fn array_bytes(a: &Array) -> Result<Vec<u8>> {
macro_rules! le {
($t:ty) => {
a.to_vec::<$t>()?
.iter()
.flat_map(|v| v.to_le_bytes())
.collect()
};
}
Ok(match a.dtype() {
DType::Bool => a.to_vec::<bool>()?.iter().map(|&b| u8::from(b)).collect(),
DType::U8 => a.to_vec::<u8>()?,
DType::U16 => le!(u16),
DType::U32 => le!(u32),
DType::U64 => le!(u64),
DType::I8 => le!(i8),
DType::I16 => le!(i16),
DType::I32 => le!(i32),
DType::I64 => le!(i64),
DType::F16 => le!(nuvec_core::f16),
DType::BF16 => le!(nuvec_core::bf16),
DType::F32 => le!(f32),
DType::F64 => le!(f64),
_ => a
.to_vec::<nuvec_core::Complex64>()?
.iter()
.flat_map(|z| [z.re.to_le_bytes(), z.im.to_le_bytes()].concat())
.collect(),
})
}
fn encode(tensors: &[(&str, &Array)], metadata: &[(&str, &str)]) -> Result<Vec<u8>> {
let mut sorted: Vec<&(&str, &Array)> = tensors.iter().collect();
sorted.sort_by(|(ln, la), (rn, ra)| ra.dtype().size().cmp(&la.dtype().size()).then(ln.cmp(rn)));
let mut header = Vec::new();
if !metadata.is_empty() {
let map: BTreeMap<String, String> = metadata
.iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect();
header.push(format!("\"__metadata__\":{}", json::object(&map)));
}
let mut data = Vec::new();
for (name, a) in sorted {
let bytes = array_bytes(a)?;
let shape: Vec<String> = a.shape().iter().map(i32::to_string).collect();
header.push(format!(
"{}:{{\"dtype\":\"{}\",\"shape\":[{}],\"data_offsets\":[{},{}]}}",
json::quote(name),
name_of(a.dtype()),
shape.join(","),
data.len(),
data.len() + bytes.len()
));
data.extend(bytes);
}
let mut header = format!("{{{}}}", header.join(",")).into_bytes();
header.resize(header.len().next_multiple_of(8), b' ');
let mut out = Vec::with_capacity(8 + header.len() + data.len());
out.extend((header.len() as u64).to_le_bytes());
out.extend(header);
out.extend(data);
Ok(out)
}
const PAD: &str = "__nuvec_pad_";
pub fn align_safetensors(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<()> {
use std::io::Write;
let (src, dst) = (src.as_ref(), dst.as_ref());
let (header, _) = read_header(src)?;
let mut entries = Vec::new();
if !header.metadata.is_empty() {
entries.push(format!(
"\"__metadata__\":{}",
json::object(&header.metadata)
));
}
let mut copies = Vec::new();
let mut at = 0usize;
for (i, (begin, end, name, dtype, shape)) in header.ranges.iter().enumerate() {
let pad = at.next_multiple_of(TENSOR_ALIGN) - at;
if pad > 0 {
entries.push(format!(
"\"{PAD}{i}\":{{\"dtype\":\"U8\",\"shape\":[{pad}],\"data_offsets\":[{at},{}]}}",
at + pad
));
at += pad;
}
let n = end - begin;
let dims: Vec<String> = shape.iter().map(usize::to_string).collect();
entries.push(format!(
"{}:{{\"dtype\":\"{}\",\"shape\":[{}],\"data_offsets\":[{at},{}]}}",
json::quote(name),
name_of(*dtype),
dims.join(","),
at + n
));
copies.push(((header.start + begin) as u64, n, at));
at += n;
}
let mut json = format!("{{{}}}", entries.join(",")).into_bytes();
json.resize((8 + json.len()).next_multiple_of(TENSOR_ALIGN) - 8, b' ');
fn io(p: &Path) -> impl Fn(std::io::Error) -> Error + '_ {
move |e| Error::Io(format!("{}: {e}", p.display()))
}
let tmp = dst.with_extension("safetensors.tmp");
let input = std::fs::File::open(src).map_err(io(src))?;
let mut out =
std::io::BufWriter::with_capacity(16 << 20, std::fs::File::create(&tmp).map_err(io(&tmp))?);
out.write_all(&(json.len() as u64).to_le_bytes())
.map_err(io(&tmp))?;
out.write_all(&json).map_err(io(&tmp))?;
let mut written = 0usize;
let mut chunk = vec![0u8; 16 << 20];
for (from, n, to) in copies {
out.write_all(&vec![0u8; to - written]).map_err(io(&tmp))?;
let mut done = 0;
while done < n {
let k = (n - done).min(chunk.len());
#[cfg(unix)]
std::os::unix::fs::FileExt::read_exact_at(&input, &mut chunk[..k], from + done as u64)
.map_err(io(src))?;
#[cfg(not(unix))]
{
use std::io::{Read, Seek, SeekFrom};
let mut f = &input;
f.seek(SeekFrom::Start(from + done as u64))
.map_err(io(src))?;
f.read_exact(&mut chunk[..k]).map_err(io(src))?;
}
out.write_all(&chunk[..k]).map_err(io(&tmp))?;
done += k;
}
written = to + n;
}
out.into_inner()
.map_err(|e| Error::Io(format!("{}: {e}", tmp.display())))?
.sync_all()
.map_err(io(&tmp))?;
std::fs::rename(&tmp, dst).map_err(io(dst))
}
pub fn load_safetensors_cached(
path: impl AsRef<Path>,
cache_dir: impl AsRef<Path>,
keep: impl Fn(&str) -> bool,
) -> Result<SafeTensors> {
use std::hash::{Hash, Hasher};
let (path, cache_dir) = (path.as_ref(), cache_dir.as_ref());
let (header, len) = read_header(path)?;
if header.aligned() {
return load_safetensors_filtered(path, keep);
}
let meta =
std::fs::metadata(path).map_err(|e| Error::Io(format!("{}: {e}", path.display())))?;
let mtime = meta
.modified()
.ok()
.and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
.map_or(0, |d| d.as_nanos());
let mut h = std::collections::hash_map::DefaultHasher::new();
std::fs::canonicalize(path)
.unwrap_or_else(|_| path.to_path_buf())
.hash(&mut h);
let stem = path
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("tensors");
let copy = cache_dir.join(format!(
"{stem}-{:016x}-{len}-{mtime}.safetensors",
h.finish()
));
if !copy.exists() {
let made = std::fs::create_dir_all(cache_dir)
.map_err(|e| Error::Io(format!("{}: {e}", cache_dir.display())))
.and_then(|()| align_safetensors(path, ©));
if let Err(e) = made {
static WARNED: std::sync::Once = std::sync::Once::new();
WARNED.call_once(|| {
eprintln!("nuvec: no aligned copy ({e}); reading {}", path.display())
});
return load_safetensors_filtered(path, keep);
}
}
load_safetensors_filtered(©, keep)
}
fn write_atomically(path: &Path, bytes: &[u8]) -> Result<()> {
let io = |e: std::io::Error| Error::Io(format!("{}: {e}", path.display()));
let mut tmp = path.as_os_str().to_owned();
tmp.push(".tmp");
let tmp = std::path::PathBuf::from(tmp);
if let Err(e) = std::fs::write(&tmp, bytes) {
let _ = std::fs::remove_file(&tmp);
return Err(io(e));
}
std::fs::rename(&tmp, path).map_err(|e| {
let _ = std::fs::remove_file(&tmp);
io(e)
})
}
pub fn save_safetensors(
path: impl AsRef<Path>,
tensors: &[(&str, &Array)],
metadata: &[(&str, &str)],
) -> Result<()> {
write_atomically(path.as_ref(), &encode(tensors, metadata)?)
}
#[must_use = "wait() returns the write's result; dropping the handle only waits"]
#[derive(Debug)]
pub struct SaveHandle {
thread: Option<std::thread::JoinHandle<Result<()>>>,
}
impl SaveHandle {
pub fn wait(mut self) -> Result<()> {
self.join()
}
fn join(&mut self) -> Result<()> {
match self.thread.take() {
Some(t) => t
.join()
.unwrap_or_else(|_| Err(Error::Io("the safetensors writer panicked".into()))),
None => Ok(()),
}
}
}
impl Drop for SaveHandle {
fn drop(&mut self) {
let _ = self.join();
}
}
pub fn save_safetensors_async(
path: impl AsRef<Path>,
tensors: &[(&str, &Array)],
metadata: &[(&str, &str)],
) -> SaveHandle {
let path = path.as_ref().to_path_buf();
let tensors: Vec<(String, Array)> = tensors
.iter()
.map(|(n, a)| ((*n).to_string(), (*a).clone()))
.collect();
let metadata: Vec<(String, String)> = metadata
.iter()
.map(|(k, v)| ((*k).to_string(), (*v).to_string()))
.collect();
let submitted = nuvec_core::async_eval(&tensors.iter().map(|(_, a)| a).collect::<Vec<_>>());
let thread = std::thread::spawn(move || {
submitted?;
let tensors: Vec<(&str, &Array)> = tensors.iter().map(|(n, a)| (n.as_str(), a)).collect();
let metadata: Vec<(&str, &str)> = metadata
.iter()
.map(|(k, v)| (k.as_str(), v.as_str()))
.collect();
write_atomically(&path, &encode(&tensors, &metadata)?)
});
SaveHandle {
thread: Some(thread),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn read_tensors_start_at_multiples_of_tensor_align() {
let json = br#"{"a":{"dtype":"U32","shape":[75],"data_offsets":[0,300]},
"b":{"dtype":"BF16","shape":[5],"data_offsets":[300,310]},
"skip":{"dtype":"U8","shape":[2],"data_offsets":[310,312]},
"c":{"dtype":"U32","shape":[64],"data_offsets":[312,568]},
"d":{"dtype":"U32","shape":[1],"data_offsets":[568,572]}}"#;
let start = 8 + json.len();
let header = parse_header(json, start, start + 572).unwrap();
let plan = header.plan(&|name| name != "skip");
let offsets: Vec<usize> = plan.tensors.iter().map(|&(_, at)| at).collect();
assert_eq!(offsets, [0, 512, 768, 1024]);
assert!(offsets.iter().all(|o| o.is_multiple_of(TENSOR_ALIGN)));
let s = start as u64;
assert_eq!(
plan.ranges,
[(s, 0, 300), (s + 300, 512, 10), (s + 312, 768, 260)]
);
assert_eq!(plan.len, 1028);
}
#[test]
fn filtered_aligned_loads_map_page_runs_of_the_kept_tensors() {
let json = format!(
"{:<376}",
r#"{"a":{"dtype":"F32","shape":[8],"data_offsets":[0,32]},"#.to_string()
+ r#""b":{"dtype":"F32","shape":[8],"data_offsets":[32,64]},"#
+ r#""big":{"dtype":"F32","shape":[64],"data_offsets":[64,320]},"#
+ r#""e":{"dtype":"F32","shape":[0],"data_offsets":[320,320]},"#
+ r#""z":{"dtype":"F32","shape":[8],"data_offsets":[320,352]}}"#
);
assert_eq!(json.len(), 376);
let start = 8 + json.len();
let len = start + 352;
let header = parse_header(json.as_bytes(), start, len).unwrap();
assert_eq!(header.runs(len, 64, &|_| true), [(0, 768)]);
let some = header.runs(len, 64, &|n| n != "big");
assert_eq!(some, [(384, 64), (704, 64)]);
assert_eq!(header.runs(len, 64, &|n| n != "z"), [(384, 320)]);
assert_eq!(header.runs(len, 64, &|n| n == "e"), []);
}
}