Skip to main content

ftts_kernels/
mmap.rs

1//! Audited OS-interface island: read-only memory mapping of a checkpoint file.
2//!
3//! This exists so weights can be *addressed* without being *read*. A 1.7 GB checkpoint loaded with
4//! `fs::read` costs 1.7 GB of resident anonymous memory before a single tensor is touched; mapped
5//! read-only, the same file costs address space, and only the pages actually dereferenced — the
6//! embedding rows a prompt names, the layers a frame walks — are ever faulted in. That difference
7//! is the whole point of the `.fttsq` access-class design, and it cannot be expressed in safe Rust.
8//!
9//! Scope of the island: `mmap`, `munmap`, `madvise`, `mincore`, and page-size discovery. No kernels,
10//! no arithmetic, no parsing. Everything above this file — the safetensors directory, the census,
11//! every accessor — is `forbid(unsafe_code)` and operates on the `&[u8]` this hands out.
12//!
13//! # The truncation hazard, stated plainly
14//!
15//! A file that is truncated by another process while mapped will fault with `SIGBUS` on access to
16//! the vanished pages. Rust cannot prevent this, and neither can any mmap wrapper — it is a property
17//! of the syscall. We accept it for the same reason every mmap-based loader does, under a narrow
18//! usage contract: the mapped file is a content-addressed model artifact that is written once and
19//! read many times, never appended to or truncated in place while an engine holds it. Callers that
20//! cannot honour that contract should read the file instead.
21
22use std::io;
23use std::ops::Deref;
24use std::path::Path;
25
26#[cfg(all(feature = "native-mmap", unix))]
27use std::fs::File;
28#[cfg(all(feature = "native-mmap", unix))]
29use std::os::fd::AsRawFd;
30
31/// A kernel page-cache hint for a byte range within a mapped artifact.
32///
33/// This deliberately represents only the two policy actions the `.fttsq` access classes need.
34/// Adding another OS-specific hint requires giving it a model-level meaning first; otherwise an
35/// advisory syscall becomes an unreviewable collection of performance folklore.
36#[derive(Clone, Copy, Debug, PartialEq, Eq)]
37pub enum MemoryAdvice {
38    /// The range is recurrent and should be faulted in before steady-state decoding.
39    WillNeed,
40    /// The range is sparse and row-granular, so read-ahead is counterproductive.
41    Random,
42}
43
44/// What became of one [`MemoryAdvice`] request.
45///
46/// Advice cannot change artifact bytes or correctness. Unsupported platforms retain the safe
47/// owned-byte fallback and report that they did not make an OS request instead of pretending a
48/// Windows `PrefetchVirtualMemory` policy was implemented and tested when it was not.
49#[derive(Clone, Copy, Debug, PartialEq, Eq)]
50pub enum MemoryAdviceOutcome {
51    /// The native `madvise` request succeeded.
52    Applied,
53    /// The mapping is empty, so there is no range for the kernel to advise.
54    SkippedEmpty,
55    /// This build or platform deliberately has no native advisory implementation.
56    Unsupported,
57}
58
59/// An observation of which pages in one mapped byte range are currently resident.
60///
61/// This is intentionally an observation rather than an eviction or a residency promise: the OS
62/// owns page-cache policy, so callers use it to record an access-class measurement for OQ-18, not
63/// to turn a performance hint into a correctness condition.
64#[derive(Clone, Copy, Debug, PartialEq, Eq)]
65pub enum MemoryResidency {
66    /// `mincore` counted resident pages in the requested range.
67    Measured {
68        /// Pages the OS currently reports resident.
69        resident_pages: usize,
70        /// Pages spanned by the requested byte range.
71        total_pages: usize,
72    },
73    /// This build deliberately has no native residency-query implementation.
74    Unsupported,
75}
76
77/// A read-only, private memory mapping of a whole file.
78///
79/// Derefs to `&[u8]`, so it drops straight into anything expecting a borrowed buffer — notably the
80/// safetensors index, which is a map of byte ranges over exactly such a slice.
81#[derive(Debug)]
82pub struct MappedFile {
83    #[cfg(all(feature = "native-mmap", unix))]
84    ptr: *const u8,
85    #[cfg(all(feature = "native-mmap", unix))]
86    len: usize,
87    #[cfg(not(all(feature = "native-mmap", unix)))]
88    bytes: Vec<u8>,
89}
90
91// SAFETY: the mapping is `PROT_READ` + `MAP_PRIVATE`, so the pointer addresses immutable memory for
92// the lifetime of the value and no interior mutability is reachable through it. `MappedFile` hands
93// out only shared slices, and `munmap` happens once in `Drop` on the owning thread. Sharing the
94// pointer across threads therefore exposes no data race.
95#[cfg(all(feature = "native-mmap", unix))]
96unsafe impl Send for MappedFile {}
97// SAFETY: as above — `&MappedFile` yields only `&[u8]` into a read-only mapping.
98#[cfg(all(feature = "native-mmap", unix))]
99unsafe impl Sync for MappedFile {}
100
101impl MappedFile {
102    /// Map `path` read-only for its entire length.
103    ///
104    /// An empty file maps to an empty slice without calling `mmap`, because `mmap` rejects a zero
105    /// length with `EINVAL` and an empty checkpoint is better rejected by the parser's own
106    /// "too short for a header" path than by an opaque errno.
107    ///
108    /// # Errors
109    ///
110    /// Propagates the underlying `open`, `fstat` or `mmap` failure.
111    pub fn open(path: impl AsRef<Path>) -> io::Result<Self> {
112        #[cfg(all(feature = "native-mmap", unix))]
113        {
114            Self::open_native(path.as_ref())
115        }
116
117        #[cfg(not(all(feature = "native-mmap", unix)))]
118        {
119            // This is the bit-identical scalar fallback for targets where the audited POSIX
120            // implementation is unavailable. It is deliberately safe and explicit about its
121            // footprint trade-off rather than relying on an untested platform FFI binding.
122            Ok(Self {
123                bytes: std::fs::read(path)?,
124            })
125        }
126    }
127
128    #[cfg(all(feature = "native-mmap", unix))]
129    fn open_native(path: &Path) -> io::Result<Self> {
130        let file = File::open(path)?;
131        let len = file.metadata()?.len();
132
133        if len == 0 {
134            return Ok(Self {
135                ptr: std::ptr::NonNull::<u8>::dangling().as_ptr(),
136                len: 0,
137            });
138        }
139
140        let len = usize::try_from(len).map_err(|_| {
141            io::Error::new(
142                io::ErrorKind::InvalidData,
143                "checkpoint is larger than this platform's address space",
144            )
145        })?;
146
147        // SAFETY: `file` is an open, readable descriptor that outlives this call. We request a
148        // read-only private mapping of `len` bytes at an address of the kernel's choosing, with
149        // offset 0 — `len` came from `fstat` on this same descriptor, so it is a valid extent.
150        // `mmap` returns `MAP_FAILED` rather than a null pointer on error, which is checked below;
151        // on success the returned range is valid for reads of `len` bytes until `munmap`.
152        let ptr = unsafe {
153            libc::mmap(
154                std::ptr::null_mut(),
155                len,
156                libc::PROT_READ,
157                libc::MAP_PRIVATE,
158                file.as_raw_fd(),
159                0,
160            )
161        };
162
163        if ptr == libc::MAP_FAILED {
164            return Err(io::Error::last_os_error());
165        }
166
167        // The mapping is independent of the descriptor: closing `file` here (by dropping it at the
168        // end of scope) does not unmap.
169        Ok(Self {
170            ptr: ptr.cast::<u8>().cast_const(),
171            len,
172        })
173    }
174
175    /// Apply `advice` to one validated byte range.
176    ///
177    /// The caller supplies artifact-relative offsets. This method bounds-checks them before the
178    /// audited native call and aligns only the address down to the host page boundary, as required
179    /// by `madvise`; the supplied byte length still limits the advised range.
180    ///
181    /// # Errors
182    ///
183    /// Returns `InvalidInput` for a range outside this mapping, or the native `madvise` error.
184    pub fn advise(
185        &self,
186        offset: u64,
187        length: u64,
188        advice: MemoryAdvice,
189    ) -> io::Result<MemoryAdviceOutcome> {
190        let (offset, length) = self.validated_range(offset, length)?;
191        if length == 0 {
192            return Ok(MemoryAdviceOutcome::SkippedEmpty);
193        }
194
195        #[cfg(all(feature = "native-mmap", unix))]
196        {
197            self.advise_native(offset, length, advice)
198        }
199
200        #[cfg(not(all(feature = "native-mmap", unix)))]
201        {
202            let _ = (offset, advice);
203            Ok(MemoryAdviceOutcome::Unsupported)
204        }
205    }
206
207    /// Counts resident pages in one validated byte range when the platform exposes `mincore`.
208    ///
209    /// This does not fault pages in or evict them. It is an OQ-18 measurement hook used to make the
210    /// cold-embedding policy observable; unsupported targets return [`MemoryResidency::Unsupported`]
211    /// instead of claiming equivalent platform behavior without an audited implementation.
212    ///
213    /// # Errors
214    ///
215    /// Returns `InvalidInput` for a range outside this mapping, or the native `mincore` error.
216    pub fn resident_pages(&self, offset: u64, length: u64) -> io::Result<MemoryResidency> {
217        let (offset, length) = self.validated_range(offset, length)?;
218        #[cfg(all(feature = "native-mmap", unix))]
219        {
220            if length == 0 {
221                return Ok(MemoryResidency::Measured {
222                    resident_pages: 0,
223                    total_pages: 0,
224                });
225            }
226            self.resident_pages_native(offset, length)
227        }
228
229        #[cfg(not(all(feature = "native-mmap", unix)))]
230        {
231            let _ = (offset, length);
232            Ok(MemoryResidency::Unsupported)
233        }
234    }
235
236    fn validated_range(&self, offset: u64, length: u64) -> io::Result<(usize, usize)> {
237        let offset = usize::try_from(offset).map_err(|_| invalid_range_error())?;
238        let length = usize::try_from(length).map_err(|_| invalid_range_error())?;
239        let end = offset.checked_add(length).ok_or_else(invalid_range_error)?;
240        if end > self.len() {
241            return Err(invalid_range_error());
242        }
243        Ok((offset, length))
244    }
245
246    #[cfg(all(feature = "native-mmap", unix))]
247    fn advise_native(
248        &self,
249        offset: usize,
250        length: usize,
251        advice: MemoryAdvice,
252    ) -> io::Result<MemoryAdviceOutcome> {
253        let page_size = page_size()?;
254        let aligned_offset = offset - (offset % page_size);
255        let advised_length = offset
256            .checked_add(length)
257            .and_then(|end| end.checked_sub(aligned_offset))
258            .ok_or_else(invalid_range_error)?;
259        let native_advice = match advice {
260            MemoryAdvice::WillNeed => libc::MADV_WILLNEED,
261            MemoryAdvice::Random => libc::MADV_RANDOM,
262        };
263
264        // SAFETY: `self.ptr`/`self.len` describe our own live mapping, which is exactly the extent
265        // `madvise` expects. `aligned_offset` is rounded down to the actual runtime page size and
266        // `advised_length` ends no later than `self.len`, so the advised range lies inside the
267        // mapping. Both available advice values only influence page-cache behavior; neither can
268        // mutate or invalidate the bytes exposed by this read-only private mapping.
269        let result = unsafe {
270            libc::madvise(
271                self.ptr
272                    .add(aligned_offset)
273                    .cast_mut()
274                    .cast::<libc::c_void>(),
275                advised_length,
276                native_advice,
277            )
278        };
279        if result == -1 {
280            return Err(io::Error::last_os_error());
281        }
282        Ok(MemoryAdviceOutcome::Applied)
283    }
284
285    #[cfg(all(feature = "native-mmap", unix))]
286    fn resident_pages_native(&self, offset: usize, length: usize) -> io::Result<MemoryResidency> {
287        let page_size = page_size()?;
288        let aligned_offset = offset - (offset % page_size);
289        let observed_length = offset
290            .checked_add(length)
291            .and_then(|end| end.checked_sub(aligned_offset))
292            .ok_or_else(invalid_range_error)?;
293        let total_pages = observed_length
294            .checked_add(page_size - 1)
295            .and_then(|bytes| bytes.checked_div(page_size))
296            .ok_or_else(invalid_range_error)?;
297        let mut residency = vec![0_u8; total_pages];
298
299        // SAFETY: `self.ptr`/`self.len` describe our own live read-only mapping. `aligned_offset`
300        // is page-aligned and inside that mapping, `observed_length` ends no later than `self.len`,
301        // and `residency` owns exactly one output byte for each page the kernel may report. `mincore`
302        // observes page-cache state only; it cannot mutate or invalidate the mapping.
303        let result = unsafe {
304            libc::mincore(
305                self.ptr
306                    .add(aligned_offset)
307                    .cast_mut()
308                    .cast::<libc::c_void>(),
309                observed_length,
310                residency.as_mut_ptr().cast(),
311            )
312        };
313        if result == -1 {
314            return Err(io::Error::last_os_error());
315        }
316
317        Ok(MemoryResidency::Measured {
318            resident_pages: residency.iter().filter(|state| **state & 1 != 0).count(),
319            total_pages,
320        })
321    }
322
323    /// Advise the kernel that the whole file is accessed sparsely and randomly.
324    ///
325    /// Retained for the safetensors loader, whose upstream file has no `.fttsq` section directory.
326    /// Its best-effort semantics match the historical API: advice failures do not reject a valid
327    /// checkpoint because they affect performance only.
328    pub fn advise_random(&self) {
329        let _ = self.advise(0, self.len() as u64, MemoryAdvice::Random);
330    }
331
332    /// The mapped bytes.
333    #[must_use]
334    pub fn as_slice(&self) -> &[u8] {
335        #[cfg(all(feature = "native-mmap", unix))]
336        {
337            if self.len == 0 {
338                return &[];
339            }
340            // SAFETY: `ptr` addresses `len` initialized, readable bytes for as long as `self` lives
341            // (the mapping is only released in `Drop`), and the returned borrow cannot outlive `self`.
342            // The mapping is read-only and private, so no other handle can mutate it through us.
343            unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
344        }
345
346        #[cfg(not(all(feature = "native-mmap", unix)))]
347        {
348            &self.bytes
349        }
350    }
351
352    /// Mapped length in bytes.
353    #[must_use]
354    pub const fn len(&self) -> usize {
355        #[cfg(all(feature = "native-mmap", unix))]
356        {
357            self.len
358        }
359
360        #[cfg(not(all(feature = "native-mmap", unix)))]
361        {
362            self.bytes.len()
363        }
364    }
365
366    /// Whether the mapping is empty.
367    #[must_use]
368    pub const fn is_empty(&self) -> bool {
369        self.len() == 0
370    }
371}
372
373impl Deref for MappedFile {
374    type Target = [u8];
375
376    fn deref(&self) -> &[u8] {
377        self.as_slice()
378    }
379}
380
381impl AsRef<[u8]> for MappedFile {
382    fn as_ref(&self) -> &[u8] {
383        self.as_slice()
384    }
385}
386
387#[cfg(all(feature = "native-mmap", unix))]
388impl Drop for MappedFile {
389    fn drop(&mut self) {
390        if self.len == 0 {
391            return;
392        }
393        // SAFETY: `ptr`/`len` are exactly the values returned by our own successful `mmap`, and
394        // `Drop` runs once, so the mapping is released exactly once. No slice handed out by
395        // `as_slice` can still be alive here: each borrows `self`.
396        unsafe {
397            libc::munmap(self.ptr as *mut libc::c_void, self.len);
398        }
399    }
400}
401
402fn invalid_range_error() -> io::Error {
403    io::Error::new(
404        io::ErrorKind::InvalidInput,
405        "memory-advice range lies outside the mapped artifact",
406    )
407}
408
409#[cfg(all(feature = "native-mmap", unix))]
410fn page_size() -> io::Result<usize> {
411    // SAFETY: `sysconf(_SC_PAGESIZE)` has no pointer arguments and does not mutate process state;
412    // it returns the runtime page size needed solely to satisfy `madvise`'s address-alignment rule.
413    let raw = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
414    if raw <= 0 {
415        return Err(io::Error::last_os_error());
416    }
417    usize::try_from(raw).map_err(|_| io::Error::other("page size does not fit usize"))
418}
419
420#[cfg(test)]
421mod tests {
422    use super::*;
423    use std::fs::File;
424    use std::io::Write as _;
425
426    fn temp_path(tag: &str) -> std::path::PathBuf {
427        let mut path = std::env::temp_dir();
428        path.push(format!("ftts-mmap-{tag}-{}.bin", std::process::id()));
429        path
430    }
431
432    #[test]
433    fn maps_file_contents() {
434        let path = temp_path("contents");
435        let payload: Vec<u8> = (0u8..=255).cycle().take(9000).collect();
436        File::create(&path)
437            .and_then(|mut f| f.write_all(&payload))
438            .expect("write temp file");
439
440        let mapped = MappedFile::open(&path).expect("maps");
441        assert_eq!(mapped.len(), payload.len());
442        assert!(!mapped.is_empty());
443        assert_eq!(mapped.as_slice(), payload.as_slice());
444        // Deref and AsRef expose the same bytes.
445        assert_eq!(&mapped[..4], &payload[..4]);
446        assert_eq!(AsRef::<[u8]>::as_ref(&mapped).len(), payload.len());
447        assert!(matches!(
448            mapped
449                .advise(1, 32, MemoryAdvice::Random)
450                .expect("in-range advice"),
451            MemoryAdviceOutcome::Applied | MemoryAdviceOutcome::Unsupported
452        ));
453        match mapped
454            .resident_pages(1, 32)
455            .expect("in-range residency observation")
456        {
457            MemoryResidency::Measured {
458                resident_pages,
459                total_pages,
460            } => assert!(resident_pages <= total_pages),
461            MemoryResidency::Unsupported => {}
462        }
463        mapped.advise_random();
464        assert_eq!(mapped[8999], payload[8999]);
465
466        drop(mapped);
467        let _ = std::fs::remove_file(&path);
468    }
469
470    #[test]
471    fn empty_file_maps_to_empty_slice() {
472        let path = temp_path("empty");
473        File::create(&path).expect("create temp file");
474
475        let mapped = MappedFile::open(&path).expect("maps");
476        assert!(mapped.is_empty());
477        assert_eq!(mapped.len(), 0);
478        assert_eq!(mapped.as_slice(), &[] as &[u8]);
479        mapped.advise_random();
480
481        drop(mapped);
482        let _ = std::fs::remove_file(&path);
483    }
484
485    #[test]
486    fn missing_file_is_an_error_not_a_panic() {
487        let path = temp_path("definitely-absent-xyz");
488        let _ = std::fs::remove_file(&path);
489        assert!(MappedFile::open(&path).is_err());
490    }
491
492    #[test]
493    fn advice_refuses_a_range_outside_the_mapping() {
494        let path = temp_path("range");
495        std::fs::write(&path, [1_u8; 32]).expect("write temp file");
496        let mapped = MappedFile::open(&path).expect("maps");
497
498        let error = mapped
499            .advise(31, 2, MemoryAdvice::WillNeed)
500            .expect_err("range crossing EOF must be refused");
501        assert_eq!(error.kind(), io::ErrorKind::InvalidInput);
502
503        drop(mapped);
504        let _ = std::fs::remove_file(&path);
505    }
506}