Skip to main content

ftts_artifacts/
safetensors.rs

1//! Hand-parsed safetensors reading into a byte-range index.
2//!
3//! The format is three parts: a little-endian `u64` header length, a JSON directory of that many
4//! bytes, then an opaque payload. We parse it ourselves rather than pulling a dependency because
5//! the checkpoint is the one input we accept from outside our own toolchain, and every field in it
6//! is attacker-reachable if a user points `ftts` at a hostile file. Every offset is bounds-checked
7//! against the payload before any read, and every declared shape is cross-checked against the byte
8//! span it claims (see [`SafetensorsIndex::parse`]).
9//!
10//! **BF16 stays resident.** The index is a map of byte ranges over a borrowed buffer; nothing is
11//! copied or widened at load. Widening to `f32` happens per element at the accessor
12//! ([`TensorView::get_f32`]) or per row ([`TensorView::copy_row_f32`]). Materializing a whole-model
13//! `f32` copy would more than double residency — the text embedding alone is ~622 MB in BF16 — for
14//! no benefit, since every consumer reads it a row or a tile at a time.
15
16use std::collections::BTreeMap;
17use std::fmt;
18
19use serde_json::Value;
20
21/// Largest JSON directory we will parse, as a guard against a hostile length prefix.
22///
23/// The real checkpoint's directory is a few hundred KiB; 64 MiB is far above any legitimate value
24/// while keeping a corrupt `u64` from provoking a huge allocation.
25const MAX_HEADER_BYTES: u64 = 64 * 1024 * 1024;
26
27/// Element types we accept from a checkpoint.
28///
29/// Deliberately narrow: the pinned Qwen3-TTS checkpoint is BF16 (talker) and F32 (speech
30/// tokenizer). An unknown dtype is a refusal, not a silent skip — a checkpoint carrying types we
31/// have never conformed is exactly the "wrong or stale weights" case the census exists to catch.
32#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
33pub enum Dtype {
34    /// bfloat16 — the talker checkpoint's storage type.
35    Bf16,
36    /// IEEE-754 binary32 — the speech tokenizer's storage type.
37    F32,
38}
39
40impl Dtype {
41    /// Bytes per element.
42    #[must_use]
43    pub const fn size(self) -> usize {
44        match self {
45            Self::Bf16 => 2,
46            Self::F32 => 4,
47        }
48    }
49
50    /// The safetensors spelling of this dtype.
51    #[must_use]
52    pub const fn as_str(self) -> &'static str {
53        match self {
54            Self::Bf16 => "BF16",
55            Self::F32 => "F32",
56        }
57    }
58
59    fn parse(raw: &str) -> Option<Self> {
60        match raw {
61            "BF16" => Some(Self::Bf16),
62            "F32" => Some(Self::F32),
63            _ => None,
64        }
65    }
66}
67
68impl fmt::Display for Dtype {
69    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70        f.write_str(self.as_str())
71    }
72}
73
74/// Everything the directory declares about one tensor, plus its resolved absolute byte span.
75#[derive(Clone, Debug, PartialEq, Eq)]
76pub struct TensorEntry {
77    /// Tensor name as it appears in the directory.
78    pub name: String,
79    /// Storage dtype.
80    pub dtype: Dtype,
81    /// Logical shape, outermost dimension first.
82    pub shape: Vec<usize>,
83    /// Absolute start offset into the whole file.
84    pub begin: usize,
85    /// Absolute end offset (exclusive) into the whole file.
86    pub end: usize,
87}
88
89impl TensorEntry {
90    /// Total element count.
91    #[must_use]
92    pub fn element_count(&self) -> usize {
93        self.shape.iter().product()
94    }
95
96    /// Byte length of the payload span.
97    #[must_use]
98    pub const fn byte_len(&self) -> usize {
99        self.end - self.begin
100    }
101
102    /// Elements per row, i.e. the product of every dimension after the first.
103    ///
104    /// Used by the cold-row text-embedding path, where a "row" is one vocabulary entry.
105    #[must_use]
106    pub fn row_len(&self) -> usize {
107        self.shape.iter().skip(1).product()
108    }
109}
110
111/// What went wrong reading a checkpoint.
112///
113/// Every variant names the offending tensor or offset. A checkpoint that fails to parse is a
114/// loud, specific refusal — never a partial load that surfaces later as garbage audio.
115#[derive(Clone, Debug, PartialEq, Eq)]
116pub enum WeightsError {
117    /// File is too short to contain even the 8-byte header length.
118    TooShortForHeader {
119        /// Bytes actually available.
120        len: usize,
121    },
122    /// The declared header length is implausible or does not fit in the file.
123    HeaderLengthOutOfRange {
124        /// Length the file declared.
125        declared: u64,
126        /// Bytes actually available.
127        available: usize,
128    },
129    /// The header is not valid JSON.
130    HeaderNotJson {
131        /// Parser message.
132        detail: String,
133    },
134    /// The header parsed but is not a JSON object.
135    HeaderNotObject,
136    /// A tensor's directory entry is malformed.
137    MalformedEntry {
138        /// Tensor name.
139        name: String,
140        /// What was wrong.
141        detail: String,
142    },
143    /// A tensor declares a dtype we do not accept.
144    UnsupportedDtype {
145        /// Tensor name.
146        name: String,
147        /// Raw dtype string from the directory.
148        raw: String,
149    },
150    /// A tensor's byte span lies outside the payload.
151    SpanOutOfBounds {
152        /// Tensor name.
153        name: String,
154        /// Declared start, relative to the payload.
155        begin: usize,
156        /// Declared end, relative to the payload.
157        end: usize,
158        /// Payload length.
159        payload_len: usize,
160    },
161    /// A tensor's declared shape does not match the size of its byte span.
162    ShapeSpanMismatch {
163        /// Tensor name.
164        name: String,
165        /// Declared shape.
166        shape: Vec<usize>,
167        /// Bytes the shape implies.
168        expected_bytes: usize,
169        /// Bytes the span actually covers.
170        actual_bytes: usize,
171    },
172    /// A shape's element count overflowed `usize`.
173    ShapeOverflow {
174        /// Tensor name.
175        name: String,
176        /// Declared shape.
177        shape: Vec<usize>,
178    },
179}
180
181impl fmt::Display for WeightsError {
182    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183        match self {
184            Self::TooShortForHeader { len } => {
185                write!(f, "not a safetensors file: {len} bytes, need at least 8")
186            }
187            Self::HeaderLengthOutOfRange {
188                declared,
189                available,
190            } => write!(
191                f,
192                "header length {declared} is out of range (file has {available} bytes, cap is \
193                 {MAX_HEADER_BYTES})"
194            ),
195            Self::HeaderNotJson { detail } => write!(f, "header is not valid JSON: {detail}"),
196            Self::HeaderNotObject => f.write_str("header JSON is not an object"),
197            Self::MalformedEntry { name, detail } => {
198                write!(f, "tensor `{name}`: {detail}")
199            }
200            Self::UnsupportedDtype { name, raw } => write!(
201                f,
202                "tensor `{name}`: unsupported dtype `{raw}` (accepted: BF16, F32)"
203            ),
204            Self::SpanOutOfBounds {
205                name,
206                begin,
207                end,
208                payload_len,
209            } => write!(
210                f,
211                "tensor `{name}`: byte span {begin}..{end} escapes the {payload_len}-byte payload"
212            ),
213            Self::ShapeSpanMismatch {
214                name,
215                shape,
216                expected_bytes,
217                actual_bytes,
218            } => write!(
219                f,
220                "tensor `{name}`: shape {shape:?} implies {expected_bytes} bytes but the span \
221                 covers {actual_bytes}"
222            ),
223            Self::ShapeOverflow { name, shape } => {
224                write!(f, "tensor `{name}`: shape {shape:?} overflows usize")
225            }
226        }
227    }
228}
229
230impl std::error::Error for WeightsError {}
231
232/// A parsed directory: names to byte ranges, over a buffer we do not own.
233///
234/// Construct with [`SafetensorsIndex::parse`], then pair it with the same buffer via
235/// [`SafetensorsIndex::view`] to read tensors.
236#[derive(Clone, Debug)]
237pub struct SafetensorsIndex {
238    entries: BTreeMap<String, TensorEntry>,
239    payload_begin: usize,
240}
241
242impl SafetensorsIndex {
243    /// Parse the header and resolve every tensor's absolute byte span.
244    ///
245    /// # Errors
246    ///
247    /// Returns a [`WeightsError`] naming the specific tensor or offset at fault. Nothing is read
248    /// from the payload here; this validates that every later read is in bounds by construction.
249    pub fn parse(bytes: &[u8]) -> Result<Self, WeightsError> {
250        let Some(len_prefix) = bytes.get(..8) else {
251            return Err(WeightsError::TooShortForHeader { len: bytes.len() });
252        };
253        // The prefix is exactly 8 bytes, so the conversion cannot fail.
254        let header_len = u64::from_le_bytes(
255            len_prefix
256                .try_into()
257                .expect("slice of 8 bytes converts to [u8; 8]"),
258        );
259
260        if header_len > MAX_HEADER_BYTES {
261            return Err(WeightsError::HeaderLengthOutOfRange {
262                declared: header_len,
263                available: bytes.len(),
264            });
265        }
266        // `header_len` is now <= 64 MiB, so this cast is lossless on every target we build for.
267        let header_len_usize =
268            usize::try_from(header_len).map_err(|_| WeightsError::HeaderLengthOutOfRange {
269                declared: header_len,
270                available: bytes.len(),
271            })?;
272        let payload_begin =
273            8usize
274                .checked_add(header_len_usize)
275                .ok_or(WeightsError::HeaderLengthOutOfRange {
276                    declared: header_len,
277                    available: bytes.len(),
278                })?;
279        let Some(header_bytes) = bytes.get(8..payload_begin) else {
280            return Err(WeightsError::HeaderLengthOutOfRange {
281                declared: header_len,
282                available: bytes.len(),
283            });
284        };
285
286        let parsed: Value =
287            serde_json::from_slice(header_bytes).map_err(|error| WeightsError::HeaderNotJson {
288                detail: error.to_string(),
289            })?;
290        let Value::Object(directory) = parsed else {
291            return Err(WeightsError::HeaderNotObject);
292        };
293
294        let payload_len = bytes.len() - payload_begin;
295        let mut entries = BTreeMap::new();
296        for (name, value) in directory {
297            // `__metadata__` is a free-form string map, not a tensor; skipping it is part of the
298            // format, not a leniency.
299            if name == "__metadata__" {
300                continue;
301            }
302            let entry = parse_entry(&name, &value, payload_begin, payload_len)?;
303            entries.insert(name, entry);
304        }
305
306        Ok(Self {
307            entries,
308            payload_begin,
309        })
310    }
311
312    /// Absolute offset where the payload begins.
313    #[must_use]
314    pub const fn payload_begin(&self) -> usize {
315        self.payload_begin
316    }
317
318    /// Number of tensors in the directory.
319    #[must_use]
320    pub fn len(&self) -> usize {
321        self.entries.len()
322    }
323
324    /// Whether the directory is empty.
325    #[must_use]
326    pub fn is_empty(&self) -> bool {
327        self.entries.is_empty()
328    }
329
330    /// Look up one tensor's entry.
331    #[must_use]
332    pub fn entry(&self, name: &str) -> Option<&TensorEntry> {
333        self.entries.get(name)
334    }
335
336    /// Every entry, in name order.
337    pub fn entries(&self) -> impl Iterator<Item = &TensorEntry> {
338        self.entries.values()
339    }
340
341    /// Tensor names, in sorted order.
342    pub fn names(&self) -> impl Iterator<Item = &str> {
343        self.entries.keys().map(String::as_str)
344    }
345
346    /// Total payload bytes claimed by all tensors.
347    ///
348    /// The census reports this so a wrong-checkpoint diagnosis can lead with the size mismatch.
349    #[must_use]
350    pub fn total_tensor_bytes(&self) -> usize {
351        self.entries.values().map(TensorEntry::byte_len).sum()
352    }
353
354    /// Borrow one tensor's bytes for reading.
355    ///
356    /// `bytes` must be the same buffer that was passed to [`SafetensorsIndex::parse`]; passing a
357    /// shorter one returns `None` rather than reading out of bounds.
358    #[must_use]
359    pub fn view<'a>(&self, name: &str, bytes: &'a [u8]) -> Option<TensorView<'a>> {
360        let entry = self.entries.get(name)?;
361        let raw = bytes.get(entry.begin..entry.end)?;
362        Some(TensorView {
363            dtype: entry.dtype,
364            shape: entry.shape.clone(),
365            raw,
366        })
367    }
368}
369
370fn parse_entry(
371    name: &str,
372    value: &Value,
373    payload_begin: usize,
374    payload_len: usize,
375) -> Result<TensorEntry, WeightsError> {
376    let object = value
377        .as_object()
378        .ok_or_else(|| WeightsError::MalformedEntry {
379            name: name.to_owned(),
380            detail: "entry is not a JSON object".to_owned(),
381        })?;
382
383    let raw_dtype = object.get("dtype").and_then(Value::as_str).ok_or_else(|| {
384        WeightsError::MalformedEntry {
385            name: name.to_owned(),
386            detail: "missing string field `dtype`".to_owned(),
387        }
388    })?;
389    let dtype = Dtype::parse(raw_dtype).ok_or_else(|| WeightsError::UnsupportedDtype {
390        name: name.to_owned(),
391        raw: raw_dtype.to_owned(),
392    })?;
393
394    let raw_shape = object
395        .get("shape")
396        .and_then(Value::as_array)
397        .ok_or_else(|| WeightsError::MalformedEntry {
398            name: name.to_owned(),
399            detail: "missing array field `shape`".to_owned(),
400        })?;
401    let mut shape = Vec::with_capacity(raw_shape.len());
402    for dim in raw_shape {
403        let dim = dim
404            .as_u64()
405            .and_then(|d| usize::try_from(d).ok())
406            .ok_or_else(|| WeightsError::MalformedEntry {
407                name: name.to_owned(),
408                detail: "shape contains a non-usize dimension".to_owned(),
409            })?;
410        shape.push(dim);
411    }
412
413    let offsets = object
414        .get("data_offsets")
415        .and_then(Value::as_array)
416        .ok_or_else(|| WeightsError::MalformedEntry {
417            name: name.to_owned(),
418            detail: "missing array field `data_offsets`".to_owned(),
419        })?;
420    if offsets.len() != 2 {
421        return Err(WeightsError::MalformedEntry {
422            name: name.to_owned(),
423            detail: format!("`data_offsets` has {} entries, expected 2", offsets.len()),
424        });
425    }
426    let mut bound = [0usize; 2];
427    for (slot, raw) in bound.iter_mut().zip(offsets) {
428        *slot = raw
429            .as_u64()
430            .and_then(|v| usize::try_from(v).ok())
431            .ok_or_else(|| WeightsError::MalformedEntry {
432                name: name.to_owned(),
433                detail: "`data_offsets` contains a non-usize value".to_owned(),
434            })?;
435    }
436    let [begin, end] = bound;
437
438    // Ordering first, so the subtraction below cannot wrap.
439    if begin > end || end > payload_len {
440        return Err(WeightsError::SpanOutOfBounds {
441            name: name.to_owned(),
442            begin,
443            end,
444            payload_len,
445        });
446    }
447
448    // A shape whose product overflows would otherwise wrap into a small, plausible-looking count.
449    let mut elements = 1usize;
450    for dim in &shape {
451        elements = elements
452            .checked_mul(*dim)
453            .ok_or_else(|| WeightsError::ShapeOverflow {
454                name: name.to_owned(),
455                shape: shape.clone(),
456            })?;
457    }
458    let expected_bytes =
459        elements
460            .checked_mul(dtype.size())
461            .ok_or_else(|| WeightsError::ShapeOverflow {
462                name: name.to_owned(),
463                shape: shape.clone(),
464            })?;
465    let actual_bytes = end - begin;
466    if expected_bytes != actual_bytes {
467        return Err(WeightsError::ShapeSpanMismatch {
468            name: name.to_owned(),
469            shape,
470            expected_bytes,
471            actual_bytes,
472        });
473    }
474
475    Ok(TensorEntry {
476        name: name.to_owned(),
477        dtype,
478        shape,
479        begin: payload_begin + begin,
480        end: payload_begin + end,
481    })
482}
483
484/// A borrowed window onto one tensor's bytes, widening to `f32` on read.
485///
486/// Holds no owned element storage: the BF16 (or F32) bytes stay exactly where they were mapped.
487#[derive(Clone, Copy, Debug)]
488pub struct TensorViewRef<'a> {
489    dtype: Dtype,
490    raw: &'a [u8],
491}
492
493/// A borrowed window onto one tensor, carrying its shape.
494#[derive(Clone, Debug)]
495pub struct TensorView<'a> {
496    dtype: Dtype,
497    shape: Vec<usize>,
498    raw: &'a [u8],
499}
500
501/// Widen one bfloat16, given as its raw bits, to `f32`.
502///
503/// BF16 is the top 16 bits of an `f32` with the same exponent layout, so widening is exact for
504/// every value including subnormals, infinities, and NaN payloads — a left shift, never a
505/// computation. This is the only conversion direction we need: nothing writes BF16.
506#[must_use]
507pub const fn bf16_bits_to_f32(bits: u16) -> f32 {
508    f32::from_bits((bits as u32) << 16)
509}
510
511impl<'a> TensorView<'a> {
512    /// Storage dtype.
513    #[must_use]
514    pub const fn dtype(&self) -> Dtype {
515        self.dtype
516    }
517
518    /// Logical shape.
519    #[must_use]
520    pub fn shape(&self) -> &[usize] {
521        &self.shape
522    }
523
524    /// Element count.
525    #[must_use]
526    pub fn len(&self) -> usize {
527        self.raw.len() / self.dtype.size()
528    }
529
530    /// Whether the tensor has no elements.
531    #[must_use]
532    pub fn is_empty(&self) -> bool {
533        self.len() == 0
534    }
535
536    /// Elements per row (product of all dimensions after the first).
537    #[must_use]
538    pub fn row_len(&self) -> usize {
539        self.shape.iter().skip(1).product()
540    }
541
542    /// Read one element, widened to `f32`.
543    ///
544    /// Returns `None` if `index` is past the end. This is the accessor-level widening the design
545    /// calls for: no whole-tensor `f32` buffer is ever built.
546    #[must_use]
547    pub fn get_f32(&self, index: usize) -> Option<f32> {
548        let size = self.dtype.size();
549        let start = index.checked_mul(size)?;
550        let chunk = self.raw.get(start..start.checked_add(size)?)?;
551        Some(match self.dtype {
552            Dtype::Bf16 => bf16_bits_to_f32(u16::from_le_bytes([chunk[0], chunk[1]])),
553            Dtype::F32 => f32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]),
554        })
555    }
556
557    /// Copy one row, widening to `f32`, into `out`.
558    ///
559    /// This is the cold-row path for the 151 936 × 2048 text embedding: prefill touches only the
560    /// rows its token ids name, so only those rows are ever fetched and widened. Nothing advises
561    /// the whole section resident.
562    ///
563    /// Returns `false` (writing nothing) if the row is out of range or `out` is the wrong length,
564    /// so a caller cannot silently consume a partially-filled buffer.
565    #[must_use]
566    pub fn copy_row_f32(&self, row: usize, out: &mut [f32]) -> bool {
567        let row_len = self.row_len();
568        if row_len == 0 || out.len() != row_len {
569            return false;
570        }
571        let Some(base) = row.checked_mul(row_len) else {
572            return false;
573        };
574        if base.checked_add(row_len).is_none_or(|end| end > self.len()) {
575            return false;
576        }
577        for (offset, slot) in out.iter_mut().enumerate() {
578            // Bounds were proven above, so this cannot miss.
579            match self.get_f32(base + offset) {
580                Some(value) => *slot = value,
581                None => return false,
582            }
583        }
584        true
585    }
586
587    /// Borrow the raw, un-widened bytes.
588    #[must_use]
589    pub const fn as_bytes(&self) -> &'a [u8] {
590        self.raw
591    }
592
593    /// A shape-less reference to the same bytes.
594    #[must_use]
595    pub const fn as_ref(&self) -> TensorViewRef<'a> {
596        TensorViewRef {
597            dtype: self.dtype,
598            raw: self.raw,
599        }
600    }
601}
602
603impl TensorViewRef<'_> {
604    /// Storage dtype.
605    #[must_use]
606    pub const fn dtype(&self) -> Dtype {
607        self.dtype
608    }
609
610    /// Element count.
611    #[must_use]
612    pub fn len(&self) -> usize {
613        self.raw.len() / self.dtype.size()
614    }
615
616    /// Whether the tensor has no elements.
617    #[must_use]
618    pub fn is_empty(&self) -> bool {
619        self.len() == 0
620    }
621}
622
623#[cfg(test)]
624mod tests {
625    use super::*;
626
627    /// Build a well-formed safetensors buffer from `(name, dtype, shape, payload)` parts.
628    fn build(parts: &[(&str, Dtype, &[usize], &[u8])]) -> Vec<u8> {
629        let mut directory = serde_json::Map::new();
630        let mut payload = Vec::new();
631        for (name, dtype, shape, bytes) in parts {
632            let begin = payload.len();
633            payload.extend_from_slice(bytes);
634            directory.insert(
635                (*name).to_owned(),
636                serde_json::json!({
637                    "dtype": dtype.as_str(),
638                    "shape": shape,
639                    "data_offsets": [begin, payload.len()],
640                }),
641            );
642        }
643        assemble(&Value::Object(directory), &payload)
644    }
645
646    fn assemble(header: &Value, payload: &[u8]) -> Vec<u8> {
647        let header_bytes = serde_json::to_vec(header).expect("header serializes");
648        let mut out = Vec::new();
649        out.extend_from_slice(&(header_bytes.len() as u64).to_le_bytes());
650        out.extend_from_slice(&header_bytes);
651        out.extend_from_slice(payload);
652        out
653    }
654
655    fn bf16_payload(values: &[u16]) -> Vec<u8> {
656        values.iter().flat_map(|v| v.to_le_bytes()).collect()
657    }
658
659    #[test]
660    fn parses_a_two_tensor_directory() {
661        let buffer = build(&[
662            ("a", Dtype::Bf16, &[2, 2], &bf16_payload(&[0, 1, 2, 3])),
663            ("b", Dtype::F32, &[2], &1.0f32.to_le_bytes().repeat(2)),
664        ]);
665        let index = SafetensorsIndex::parse(&buffer).expect("parses");
666
667        assert_eq!(index.len(), 2);
668        assert_eq!(index.names().collect::<Vec<_>>(), vec!["a", "b"]);
669        let a = index.entry("a").expect("entry a");
670        assert_eq!(a.dtype, Dtype::Bf16);
671        assert_eq!(a.shape, vec![2, 2]);
672        assert_eq!(a.element_count(), 4);
673        assert_eq!(a.byte_len(), 8);
674        assert_eq!(a.row_len(), 2);
675        assert_eq!(index.total_tensor_bytes(), 16);
676    }
677
678    #[test]
679    fn metadata_key_is_not_a_tensor() {
680        let mut directory = serde_json::Map::new();
681        directory.insert(
682            "__metadata__".to_owned(),
683            serde_json::json!({"format": "pt"}),
684        );
685        directory.insert(
686            "w".to_owned(),
687            serde_json::json!({"dtype": "F32", "shape": [1], "data_offsets": [0, 4]}),
688        );
689        let buffer = assemble(&Value::Object(directory), &1.0f32.to_le_bytes());
690        let index = SafetensorsIndex::parse(&buffer).expect("parses");
691        assert_eq!(index.len(), 1);
692        assert!(index.entry("__metadata__").is_none());
693    }
694
695    #[test]
696    fn widening_bf16_is_exact_for_representable_values() {
697        // Every BF16 bit pattern is the top half of an f32, so round-tripping an f32 whose low 16
698        // mantissa bits are zero must be lossless.
699        for bits in [0x0000u16, 0x3f80, 0xbf80, 0x7f80, 0xff80, 0x0001, 0x8000] {
700            let widened = bf16_bits_to_f32(bits);
701            assert_eq!(widened.to_bits() >> 16, u32::from(bits));
702            assert_eq!(widened.to_bits() & 0x0000_ffff, 0);
703        }
704        assert_eq!(bf16_bits_to_f32(0x3f80), 1.0);
705        assert_eq!(bf16_bits_to_f32(0xbf80), -1.0);
706        assert_eq!(bf16_bits_to_f32(0x0000), 0.0);
707        assert!(bf16_bits_to_f32(0x7f80).is_infinite());
708        assert!(bf16_bits_to_f32(0x7fc0).is_nan());
709    }
710
711    #[test]
712    fn bf16_widening_round_trips_every_bit_pattern() {
713        // Exhaustive over the whole 16-bit space: widening must never lose the pattern, and must
714        // preserve NaN-ness and sign rather than silently canonicalizing.
715        for bits in 0..=u16::MAX {
716            let widened = bf16_bits_to_f32(bits);
717            assert_eq!(
718                (widened.to_bits() >> 16) as u16,
719                bits,
720                "bit pattern {bits:#06x} did not survive widening"
721            );
722            let exponent = bits & 0x7f80;
723            let mantissa = bits & 0x007f;
724            if exponent == 0x7f80 && mantissa != 0 {
725                assert!(widened.is_nan(), "{bits:#06x} should widen to NaN");
726            } else {
727                assert!(!widened.is_nan(), "{bits:#06x} should not widen to NaN");
728            }
729        }
730    }
731
732    #[test]
733    fn reads_elements_and_rows_without_materializing() {
734        let payload = bf16_payload(&[0x3f80, 0xbf80, 0x4000, 0xc000]);
735        let buffer = build(&[("w", Dtype::Bf16, &[2, 2], &payload)]);
736        let index = SafetensorsIndex::parse(&buffer).expect("parses");
737        let view = index.view("w", &buffer).expect("view");
738
739        assert_eq!(view.len(), 4);
740        assert_eq!(view.row_len(), 2);
741        assert_eq!(view.get_f32(0), Some(1.0));
742        assert_eq!(view.get_f32(1), Some(-1.0));
743        assert_eq!(view.get_f32(3), Some(-2.0));
744        assert_eq!(view.get_f32(4), None);
745
746        let mut row = [0.0f32; 2];
747        assert!(view.copy_row_f32(1, &mut row));
748        assert_eq!(row, [2.0, -2.0]);
749
750        // Out-of-range row and wrong-sized buffer both refuse rather than partially fill.
751        assert!(!view.copy_row_f32(2, &mut row));
752        let mut wrong = [0.0f32; 3];
753        assert!(!view.copy_row_f32(0, &mut wrong));
754    }
755
756    #[test]
757    fn refuses_a_truncated_file() {
758        assert_eq!(
759            SafetensorsIndex::parse(&[0u8; 4]).expect_err("must refuse"),
760            WeightsError::TooShortForHeader { len: 4 }
761        );
762    }
763
764    #[test]
765    fn refuses_an_absurd_header_length() {
766        let mut buffer = u64::MAX.to_le_bytes().to_vec();
767        buffer.extend_from_slice(b"{}");
768        let error = SafetensorsIndex::parse(&buffer).expect_err("must refuse");
769        assert!(matches!(error, WeightsError::HeaderLengthOutOfRange { .. }));
770    }
771
772    #[test]
773    fn refuses_a_header_longer_than_the_file() {
774        let mut buffer = 4096u64.to_le_bytes().to_vec();
775        buffer.extend_from_slice(b"{}");
776        let error = SafetensorsIndex::parse(&buffer).expect_err("must refuse");
777        assert!(matches!(error, WeightsError::HeaderLengthOutOfRange { .. }));
778    }
779
780    #[test]
781    fn refuses_malformed_json_and_non_objects() {
782        let mut buffer = 5u64.to_le_bytes().to_vec();
783        buffer.extend_from_slice(b"{ not");
784        assert!(matches!(
785            SafetensorsIndex::parse(&buffer).expect_err("must refuse"),
786            WeightsError::HeaderNotJson { .. }
787        ));
788
789        let buffer = assemble(&serde_json::json!([1, 2]), &[]);
790        assert_eq!(
791            SafetensorsIndex::parse(&buffer).expect_err("must refuse"),
792            WeightsError::HeaderNotObject
793        );
794    }
795
796    #[test]
797    fn refuses_an_unsupported_dtype() {
798        let buffer = assemble(
799            &serde_json::json!({"w": {"dtype": "I64", "shape": [1], "data_offsets": [0, 8]}}),
800            &[0u8; 8],
801        );
802        assert_eq!(
803            SafetensorsIndex::parse(&buffer).expect_err("must refuse"),
804            WeightsError::UnsupportedDtype {
805                name: "w".to_owned(),
806                raw: "I64".to_owned(),
807            }
808        );
809    }
810
811    #[test]
812    fn refuses_a_span_past_the_payload() {
813        // The directory claims 64 bytes but only 8 follow the header.
814        let buffer = assemble(
815            &serde_json::json!({"w": {"dtype": "F32", "shape": [16], "data_offsets": [0, 64]}}),
816            &[0u8; 8],
817        );
818        let error = SafetensorsIndex::parse(&buffer).expect_err("must refuse");
819        assert!(matches!(error, WeightsError::SpanOutOfBounds { .. }));
820    }
821
822    #[test]
823    fn refuses_reversed_offsets() {
824        let buffer = assemble(
825            &serde_json::json!({"w": {"dtype": "F32", "shape": [1], "data_offsets": [8, 4]}}),
826            &[0u8; 8],
827        );
828        let error = SafetensorsIndex::parse(&buffer).expect_err("must refuse");
829        assert!(matches!(error, WeightsError::SpanOutOfBounds { .. }));
830    }
831
832    #[test]
833    fn refuses_a_shape_that_disagrees_with_its_span() {
834        // Shape says 4 F32 elements (16 bytes); the span covers 8.
835        let buffer = assemble(
836            &serde_json::json!({"w": {"dtype": "F32", "shape": [4], "data_offsets": [0, 8]}}),
837            &[0u8; 8],
838        );
839        let error = SafetensorsIndex::parse(&buffer).expect_err("must refuse");
840        assert!(
841            matches!(
842                error,
843                WeightsError::ShapeSpanMismatch {
844                    expected_bytes: 16,
845                    actual_bytes: 8,
846                    ..
847                }
848            ),
849            "wrong error: {error}"
850        );
851    }
852
853    #[test]
854    fn refuses_a_shape_that_overflows() {
855        let huge = usize::MAX;
856        let buffer = assemble(
857            &serde_json::json!({
858                "w": {"dtype": "F32", "shape": [huge, huge], "data_offsets": [0, 8]}
859            }),
860            &[0u8; 8],
861        );
862        let error = SafetensorsIndex::parse(&buffer).expect_err("must refuse");
863        assert!(matches!(error, WeightsError::ShapeOverflow { .. }));
864    }
865
866    #[test]
867    fn view_refuses_a_buffer_that_is_not_the_parsed_one() {
868        let buffer = build(&[("w", Dtype::F32, &[1], &1.0f32.to_le_bytes())]);
869        let index = SafetensorsIndex::parse(&buffer).expect("parses");
870        assert!(index.view("w", &buffer[..4]).is_none());
871        assert!(index.view("missing", &buffer).is_none());
872    }
873}
874
875/// A checkpoint held open as a read-only memory mapping, with its directory parsed.
876///
877/// This is the loading entry point the engine uses. It pairs the mapping with the index so callers
878/// cannot accidentally pass a different buffer to [`SafetensorsIndex::view`], and it keeps the
879/// zero-copy property end to end: the bytes are never read into an owned buffer, only addressed.
880///
881/// The `unsafe` needed to map a file lives in `ftts-kernels`; this crate stays `forbid(unsafe_code)`
882/// and only ever sees the `&[u8]` that mapping hands out.
883#[derive(Debug)]
884pub struct SafetensorsFile {
885    mapping: ftts_kernels::mmap::MappedFile,
886    index: SafetensorsIndex,
887}
888
889impl SafetensorsFile {
890    /// Map a checkpoint and parse its directory.
891    ///
892    /// # Errors
893    ///
894    /// Returns [`OpenError::Io`] if the file cannot be opened or mapped, or [`OpenError::Weights`]
895    /// if the directory is malformed — the latter naming the offending tensor.
896    pub fn open(path: impl AsRef<std::path::Path>) -> Result<Self, OpenError> {
897        let mapping = ftts_kernels::mmap::MappedFile::open(path).map_err(OpenError::Io)?;
898        let index = SafetensorsIndex::parse(mapping.as_slice()).map_err(OpenError::Weights)?;
899        Ok(Self { mapping, index })
900    }
901
902    /// Parse a checkpoint held wholly in memory, for targets with no filesystem (wasm32).
903    ///
904    /// # Errors
905    ///
906    /// [`OpenError::Weights`] when the directory is malformed, exactly as [`SafetensorsFile::open`].
907    #[cfg(not(unix))] // the owned-bytes MappedFile backing (and its from_bytes) exists off-unix
908    pub fn from_bytes(bytes: Vec<u8>) -> Result<Self, OpenError> {
909        let mapping = ftts_kernels::mmap::MappedFile::from_bytes(bytes);
910        let index = SafetensorsIndex::parse(mapping.as_slice()).map_err(OpenError::Weights)?;
911        Ok(Self { mapping, index })
912    }
913
914    /// Advise the kernel that access to this checkpoint is sparse and random.
915    ///
916    /// Appropriate for a checkpoint dominated by the cold text embedding, where prefill touches a
917    /// few hundred scattered rows out of 151 936 and read-ahead would fault in pages we never read.
918    pub fn advise_random(&self) {
919        self.mapping.advise_random();
920    }
921
922    /// The parsed directory.
923    #[must_use]
924    pub const fn index(&self) -> &SafetensorsIndex {
925        &self.index
926    }
927
928    /// Mapped size in bytes.
929    #[must_use]
930    pub const fn mapped_len(&self) -> usize {
931        self.mapping.len()
932    }
933
934    /// Borrow one tensor for reading.
935    #[must_use]
936    pub fn view(&self, name: &str) -> Option<TensorView<'_>> {
937        self.index.view(name, self.mapping.as_slice())
938    }
939}
940
941/// Why opening a checkpoint failed.
942#[derive(Debug)]
943pub enum OpenError {
944    /// The file could not be opened or mapped.
945    Io(std::io::Error),
946    /// The file mapped but its directory is not a valid safetensors header.
947    Weights(WeightsError),
948}
949
950impl fmt::Display for OpenError {
951    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
952        match self {
953            Self::Io(error) => write!(f, "cannot open checkpoint: {error}"),
954            Self::Weights(error) => write!(f, "{error}"),
955        }
956    }
957}
958
959impl std::error::Error for OpenError {
960    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
961        match self {
962            Self::Io(error) => Some(error),
963            Self::Weights(error) => Some(error),
964        }
965    }
966}