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())
    }
}