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}