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    /// Wraps an in-memory buffer as a mapping, for targets with no filesystem (wasm32).
129    ///
130    /// Only the owned-bytes backing can host this, so it exists exactly where that backing is
131    /// compiled in; the native mmap variant keeps its single `open` entry point.
132    #[cfg(not(all(feature = "native-mmap", unix)))]
133    #[must_use]
134    pub fn from_bytes(bytes: Vec<u8>) -> Self {
135        Self { bytes }
136    }
137
138    #[cfg(all(feature = "native-mmap", unix))]
139    fn open_native(path: &Path) -> io::Result<Self> {
140        let file = File::open(path)?;
141        let len = file.metadata()?.len();
142
143        if len == 0 {
144            return Ok(Self {
145                ptr: std::ptr::NonNull::<u8>::dangling().as_ptr(),
146                len: 0,
147            });
148        }
149
150        let len = usize::try_from(len).map_err(|_| {
151            io::Error::new(
152                io::ErrorKind::InvalidData,
153                "checkpoint is larger than this platform's address space",
154            )
155        })?;
156
157        // SAFETY: `file` is an open, readable descriptor that outlives this call. We request a
158        // read-only private mapping of `len` bytes at an address of the kernel's choosing, with
159        // offset 0 — `len` came from `fstat` on this same descriptor, so it is a valid extent.
160        // `mmap` returns `MAP_FAILED` rather than a null pointer on error, which is checked below;
161        // on success the returned range is valid for reads of `len` bytes until `munmap`.
162        let ptr = unsafe {
163            libc::mmap(
164                std::ptr::null_mut(),
165                len,
166                libc::PROT_READ,
167                libc::MAP_PRIVATE,
168                file.as_raw_fd(),
169                0,
170            )
171        };
172
173        if ptr == libc::MAP_FAILED {
174            return Err(io::Error::last_os_error());
175        }
176
177        // The mapping is independent of the descriptor: closing `file` here (by dropping it at the
178        // end of scope) does not unmap.
179        Ok(Self {
180            ptr: ptr.cast::<u8>().cast_const(),
181            len,
182        })
183    }
184
185    /// Apply `advice` to one validated byte range.
186    ///
187    /// The caller supplies artifact-relative offsets. This method bounds-checks them before the
188    /// audited native call and aligns only the address down to the host page boundary, as required
189    /// by `madvise`; the supplied byte length still limits the advised range.
190    ///
191    /// # Errors
192    ///
193    /// Returns `InvalidInput` for a range outside this mapping, or the native `madvise` error.
194    pub fn advise(
195        &self,
196        offset: u64,
197        length: u64,
198        advice: MemoryAdvice,
199    ) -> io::Result<MemoryAdviceOutcome> {
200        let (offset, length) = self.validated_range(offset, length)?;
201        if length == 0 {
202            return Ok(MemoryAdviceOutcome::SkippedEmpty);
203        }
204
205        #[cfg(all(feature = "native-mmap", unix))]
206        {
207            self.advise_native(offset, length, advice)
208        }
209
210        #[cfg(not(all(feature = "native-mmap", unix)))]
211        {
212            let _ = (offset, advice);
213            Ok(MemoryAdviceOutcome::Unsupported)
214        }
215    }
216
217    /// Counts resident pages in one validated byte range when the platform exposes `mincore`.
218    ///
219    /// This does not fault pages in or evict them. It is an OQ-18 measurement hook used to make the
220    /// cold-embedding policy observable; unsupported targets return [`MemoryResidency::Unsupported`]
221    /// instead of claiming equivalent platform behavior without an audited implementation.
222    ///
223    /// # Errors
224    ///
225    /// Returns `InvalidInput` for a range outside this mapping, or the native `mincore` error.
226    pub fn resident_pages(&self, offset: u64, length: u64) -> io::Result<MemoryResidency> {
227        let (offset, length) = self.validated_range(offset, length)?;
228        #[cfg(all(feature = "native-mmap", unix))]
229        {
230            if length == 0 {
231                return Ok(MemoryResidency::Measured {
232                    resident_pages: 0,
233                    total_pages: 0,
234                });
235            }
236            self.resident_pages_native(offset, length)
237        }
238
239        #[cfg(not(all(feature = "native-mmap", unix)))]
240        {
241            let _ = (offset, length);
242            Ok(MemoryResidency::Unsupported)
243        }
244    }
245
246    fn validated_range(&self, offset: u64, length: u64) -> io::Result<(usize, usize)> {
247        let offset = usize::try_from(offset).map_err(|_| invalid_range_error())?;
248        let length = usize::try_from(length).map_err(|_| invalid_range_error())?;
249        let end = offset.checked_add(length).ok_or_else(invalid_range_error)?;
250        if end > self.len() {
251            return Err(invalid_range_error());
252        }
253        Ok((offset, length))
254    }
255
256    #[cfg(all(feature = "native-mmap", unix))]
257    fn advise_native(
258        &self,
259        offset: usize,
260        length: usize,
261        advice: MemoryAdvice,
262    ) -> io::Result<MemoryAdviceOutcome> {
263        let page_size = page_size()?;
264        let aligned_offset = offset - (offset % page_size);
265        let advised_length = offset
266            .checked_add(length)
267            .and_then(|end| end.checked_sub(aligned_offset))
268            .ok_or_else(invalid_range_error)?;
269        let native_advice = match advice {
270            MemoryAdvice::WillNeed => libc::MADV_WILLNEED,
271            MemoryAdvice::Random => libc::MADV_RANDOM,
272        };
273
274        // SAFETY: `self.ptr`/`self.len` describe our own live mapping, which is exactly the extent
275        // `madvise` expects. `aligned_offset` is rounded down to the actual runtime page size and
276        // `advised_length` ends no later than `self.len`, so the advised range lies inside the
277        // mapping. Both available advice values only influence page-cache behavior; neither can
278        // mutate or invalidate the bytes exposed by this read-only private mapping.
279        let result = unsafe {
280            libc::madvise(
281                self.ptr
282                    .add(aligned_offset)
283                    .cast_mut()
284                    .cast::<libc::c_void>(),
285                advised_length,
286                native_advice,
287            )
288        };
289        if result == -1 {
290            return Err(io::Error::last_os_error());
291        }
292        Ok(MemoryAdviceOutcome::Applied)
293    }
294
295    #[cfg(all(feature = "native-mmap", unix))]
296    fn resident_pages_native(&self, offset: usize, length: usize) -> io::Result<MemoryResidency> {
297        let page_size = page_size()?;
298        let aligned_offset = offset - (offset % page_size);
299        let observed_length = offset
300            .checked_add(length)
301            .and_then(|end| end.checked_sub(aligned_offset))
302            .ok_or_else(invalid_range_error)?;
303        let total_pages = observed_length
304            .checked_add(page_size - 1)
305            .and_then(|bytes| bytes.checked_div(page_size))
306            .ok_or_else(invalid_range_error)?;
307        let mut residency = vec![0_u8; total_pages];
308
309        // SAFETY: `self.ptr`/`self.len` describe our own live read-only mapping. `aligned_offset`
310        // is page-aligned and inside that mapping, `observed_length` ends no later than `self.len`,
311        // and `residency` owns exactly one output byte for each page the kernel may report. `mincore`
312        // observes page-cache state only; it cannot mutate or invalidate the mapping.
313        let result = unsafe {
314            libc::mincore(
315                self.ptr
316                    .add(aligned_offset)
317                    .cast_mut()
318                    .cast::<libc::c_void>(),
319                observed_length,
320                residency.as_mut_ptr().cast(),
321            )
322        };
323        if result == -1 {
324            return Err(io::Error::last_os_error());
325        }
326
327        Ok(MemoryResidency::Measured {
328            resident_pages: residency.iter().filter(|state| **state & 1 != 0).count(),
329            total_pages,
330        })
331    }
332
333    /// Advise the kernel that the whole file is accessed sparsely and randomly.
334    ///
335    /// Retained for the safetensors loader, whose upstream file has no `.fttsq` section directory.
336    /// Its best-effort semantics match the historical API: advice failures do not reject a valid
337    /// checkpoint because they affect performance only.
338    pub fn advise_random(&self) {
339        let _ = self.advise(0, self.len() as u64, MemoryAdvice::Random);
340    }
341
342    /// The mapped bytes.
343    #[must_use]
344    pub fn as_slice(&self) -> &[u8] {
345        #[cfg(all(feature = "native-mmap", unix))]
346        {
347            if self.len == 0 {
348                return &[];
349            }
350            // SAFETY: `ptr` addresses `len` initialized, readable bytes for as long as `self` lives
351            // (the mapping is only released in `Drop`), and the returned borrow cannot outlive `self`.
352            // The mapping is read-only and private, so no other handle can mutate it through us.
353            unsafe { std::slice::from_raw_parts(self.ptr, self.len) }
354        }
355
356        #[cfg(not(all(feature = "native-mmap", unix)))]
357        {
358            &self.bytes
359        }
360    }
361
362    /// Mapped length in bytes.
363    #[must_use]
364    pub const fn len(&self) -> usize {
365        #[cfg(all(feature = "native-mmap", unix))]
366        {
367            self.len
368        }
369
370        #[cfg(not(all(feature = "native-mmap", unix)))]
371        {
372            self.bytes.len()
373        }
374    }
375
376    /// Whether the mapping is empty.
377    #[must_use]
378    pub const fn is_empty(&self) -> bool {
379        self.len() == 0
380    }
381}
382
383impl Deref for MappedFile {
384    type Target = [u8];
385
386    fn deref(&self) -> &[u8] {
387        self.as_slice()
388    }
389}
390
391impl AsRef<[u8]> for MappedFile {
392    fn as_ref(&self) -> &[u8] {
393        self.as_slice()
394    }
395}
396
397#[cfg(all(feature = "native-mmap", unix))]
398impl Drop for MappedFile {
399    fn drop(&mut self) {
400        if self.len == 0 {
401            return;
402        }
403        // SAFETY: `ptr`/`len` are exactly the values returned by our own successful `mmap`, and
404        // `Drop` runs once, so the mapping is released exactly once. No slice handed out by
405        // `as_slice` can still be alive here: each borrows `self`.
406        unsafe {
407            libc::munmap(self.ptr as *mut libc::c_void, self.len);
408        }
409    }
410}
411
412fn invalid_range_error() -> io::Error {
413    io::Error::new(
414        io::ErrorKind::InvalidInput,
415        "memory-advice range lies outside the mapped artifact",
416    )
417}
418
419#[cfg(all(feature = "native-mmap", unix))]
420fn page_size() -> io::Result<usize> {
421    // SAFETY: `sysconf(_SC_PAGESIZE)` has no pointer arguments and does not mutate process state;
422    // it returns the runtime page size needed solely to satisfy `madvise`'s address-alignment rule.
423    let raw = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
424    if raw <= 0 {
425        return Err(io::Error::last_os_error());
426    }
427    usize::try_from(raw).map_err(|_| io::Error::other("page size does not fit usize"))
428}
429
430#[cfg(test)]
431mod tests {
432    use super::*;
433    use std::fs::File;
434    use std::io::Write as _;
435
436    fn temp_path(tag: &str) -> std::path::PathBuf {
437        let mut path = std::env::temp_dir();
438        path.push(format!("ftts-mmap-{tag}-{}.bin", std::process::id()));
439        path
440    }
441
442    #[test]
443    fn maps_file_contents() {
444        let path = temp_path("contents");
445        let payload: Vec<u8> = (0u8..=255).cycle().take(9000).collect();
446        File::create(&path)
447            .and_then(|mut f| f.write_all(&payload))
448            .expect("write temp file");
449
450        let mapped = MappedFile::open(&path).expect("maps");
451        assert_eq!(mapped.len(), payload.len());
452        assert!(!mapped.is_empty());
453        assert_eq!(mapped.as_slice(), payload.as_slice());
454        // Deref and AsRef expose the same bytes.
455        assert_eq!(&mapped[..4], &payload[..4]);
456        assert_eq!(AsRef::<[u8]>::as_ref(&mapped).len(), payload.len());
457        assert!(matches!(
458            mapped
459                .advise(1, 32, MemoryAdvice::Random)
460                .expect("in-range advice"),
461            MemoryAdviceOutcome::Applied | MemoryAdviceOutcome::Unsupported
462        ));
463        match mapped
464            .resident_pages(1, 32)
465            .expect("in-range residency observation")
466        {
467            MemoryResidency::Measured {
468                resident_pages,
469                total_pages,
470            } => assert!(resident_pages <= total_pages),
471            MemoryResidency::Unsupported => {}
472        }
473        mapped.advise_random();
474        assert_eq!(mapped[8999], payload[8999]);
475
476        drop(mapped);
477        let _ = std::fs::remove_file(&path);
478    }
479
480    #[test]
481    fn empty_file_maps_to_empty_slice() {
482        let path = temp_path("empty");
483        File::create(&path).expect("create temp file");
484
485        let mapped = MappedFile::open(&path).expect("maps");
486        assert!(mapped.is_empty());
487        assert_eq!(mapped.len(), 0);
488        assert_eq!(mapped.as_slice(), &[] as &[u8]);
489        mapped.advise_random();
490
491        drop(mapped);
492        let _ = std::fs::remove_file(&path);
493    }
494
495    #[test]
496    fn missing_file_is_an_error_not_a_panic() {
497        let path = temp_path("definitely-absent-xyz");
498        let _ = std::fs::remove_file(&path);
499        assert!(MappedFile::open(&path).is_err());
500    }
501
502    #[test]
503    fn advice_refuses_a_range_outside_the_mapping() {
504        let path = temp_path("range");
505        std::fs::write(&path, [1_u8; 32]).expect("write temp file");
506        let mapped = MappedFile::open(&path).expect("maps");
507
508        let error = mapped
509            .advise(31, 2, MemoryAdvice::WillNeed)
510            .expect_err("range crossing EOF must be refused");
511        assert_eq!(error.kind(), io::ErrorKind::InvalidInput);
512
513        drop(mapped);
514        let _ = std::fs::remove_file(&path);
515    }
516}