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}