Spaces:
Paused
Paused
File size: 5,965 Bytes
92eac91 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 | //! Memory-mapped safetensors access. Weights are used exactly as stored (f16); nothing is copied
//! or up-cast except the small vectors (norms, biases) the kernels want as f32.
use super::kernels::{f16_slice_to_f32, f16_to_f32};
use anyhow::{bail, Context, Result};
use memmap2::Mmap;
use serde_json::Value;
use std::{collections::HashMap, fs::File, path::Path, sync::Arc};
struct Info {
dtype: String,
shape: Vec<usize>,
start: usize,
end: usize,
}
pub struct Weights {
mmap: Arc<Mmap>,
base: usize,
index: HashMap<String, Info>,
}
/// A row-major `[n, k]` f16 matrix living inside the mapped checkpoint.
pub struct F16Mat {
mmap: Arc<Mmap>,
off: usize,
pub n: usize,
pub k: usize,
}
impl F16Mat {
pub fn data(&self) -> &[u16] {
// SAFETY: `off` is 2-byte aligned (checked at construction) inside a live mapping that this
// struct keeps alive, and `n * k * 2` bytes were bounds-checked against the tensor extent.
unsafe {
std::slice::from_raw_parts(
self.mmap.as_ptr().add(self.off) as *const u16,
self.n * self.k,
)
}
}
pub fn row(&self, r: usize) -> &[u16] {
&self.data()[r * self.k..(r + 1) * self.k]
}
}
impl Weights {
pub fn open(path: &Path) -> Result<Self> {
let file = File::open(path).with_context(|| format!("opening {}", path.display()))?;
// SAFETY: the checkpoint is treated as read-only for the life of the process.
let mmap =
unsafe { Mmap::map(&file) }.with_context(|| format!("mapping {}", path.display()))?;
if mmap.len() < 8 {
bail!("{} is too small to be a safetensors file", path.display());
}
let hlen = u64::from_le_bytes(mmap[..8].try_into().unwrap()) as usize;
let base = 8 + hlen;
if base > mmap.len() {
bail!("corrupt safetensors header length in {}", path.display());
}
let header: serde_json::Map<String, Value> =
serde_json::from_slice(&mmap[8..base]).context("parsing safetensors header")?;
let mut index = HashMap::new();
for (name, v) in header {
if name == "__metadata__" {
continue;
}
let dtype = v["dtype"]
.as_str()
.context("tensor without dtype")?
.to_string();
let shape = v["shape"]
.as_array()
.context("tensor without shape")?
.iter()
.map(|x| x.as_u64().unwrap_or(0) as usize)
.collect();
let off = v["data_offsets"]
.as_array()
.context("tensor without data_offsets")?;
let (start, end) = (
off.first()
.and_then(Value::as_u64)
.context("bad data_offsets")? as usize,
off.get(1)
.and_then(Value::as_u64)
.context("bad data_offsets")? as usize,
);
if base + end > mmap.len() || start > end {
bail!("tensor {name} points outside the file");
}
index.insert(
name,
Info {
dtype,
shape,
start,
end,
},
);
}
let mmap = Arc::new(mmap);
// Ask the kernel to start paging the weights in; the warm-up pass finishes the job.
#[cfg(unix)]
let _ = mmap.advise(memmap2::Advice::WillNeed);
Ok(Self { mmap, base, index })
}
pub fn has(&self, name: &str) -> bool {
self.index.contains_key(name)
}
fn info(&self, name: &str) -> Result<&Info> {
self.index
.get(name)
.with_context(|| format!("checkpoint has no tensor `{name}`"))
}
/// A 2-D f16 matrix `[n, k]`. `k` must be a multiple of 8 (the kernels' vector width).
pub fn mat(&self, name: &str, n: usize, k: usize) -> Result<F16Mat> {
let i = self.info(name)?;
if i.dtype != "F16" {
bail!(
"tensor `{name}` is {} but the fast engine needs F16",
i.dtype
);
}
if i.shape != [n, k] {
bail!(
"tensor `{name}` has shape {:?}, expected [{n}, {k}]",
i.shape
);
}
let off = self.base + i.start;
if off % 2 != 0 || i.end - i.start != n * k * 2 {
bail!("tensor `{name}` is misaligned or has an unexpected size");
}
Ok(F16Mat {
mmap: Arc::clone(&self.mmap),
off,
n,
k,
})
}
/// A 1-D tensor of `len` elements, widened to f32 (F16 and F32 checkpoints both work).
pub fn vec(&self, name: &str, len: usize) -> Result<Vec<f32>> {
let i = self.info(name)?;
if i.shape != [len] {
bail!("tensor `{name}` has shape {:?}, expected [{len}]", i.shape);
}
let bytes = &self.mmap[self.base + i.start..self.base + i.end];
match i.dtype.as_str() {
"F16" => Ok(f16_slice_to_f32(
&bytes
.chunks_exact(2)
.map(|b| u16::from_le_bytes([b[0], b[1]]))
.collect::<Vec<_>>(),
)),
"F32" => Ok(bytes
.chunks_exact(4)
.map(|b| f32::from_le_bytes([b[0], b[1], b[2], b[3]]))
.collect()),
other => bail!("tensor `{name}` has unsupported dtype {other}"),
}
}
/// One row of a 2-D f16 tensor widened to f32.
pub fn row_f32(&self, name: &str, rows: usize, cols: usize, row: usize) -> Result<Vec<f32>> {
let m = self.mat(name, rows, cols)?;
Ok(m.row(row).iter().map(|&h| f16_to_f32(h)).collect())
}
}
|