metrale_storage/
expert_arena.rs1use anyhow::{Context, Result, bail};
15use std::ffi::c_void;
16
17use crate::cuda_min::PinnedBuffer;
18
19pub struct ExpertArena {
22 pinned: PinnedBuffer,
23 dev_base: u64,
26 num_slabs: u32,
27 slots_per_slab: u32,
28 record_stride: usize,
29}
30
31impl ExpertArena {
32 pub fn new(num_slabs: u32, slots_per_slab: u32, record_stride: usize) -> Result<Self> {
37 if num_slabs == 0 || slots_per_slab == 0 || record_stride == 0 {
38 bail!("ExpertArena: zero geometry ({num_slabs},{slots_per_slab},{record_stride})");
39 }
40 if !record_stride.is_multiple_of(4096) {
41 bail!("ExpertArena: record_stride {record_stride} must be a 4 KiB multiple (O_DIRECT)");
42 }
43 let total = (num_slabs as usize)
44 .checked_mul(slots_per_slab as usize)
45 .and_then(|v| v.checked_mul(record_stride))
46 .context("ExpertArena: size overflow")?;
47 let pinned = PinnedBuffer::new(total)?;
48 let dev_base = pinned.device_ptr()?;
49 let host_base = pinned.ptr as u64;
50 if dev_base != host_base {
51 bail!(
52 "ExpertArena: pinned host VA {host_base:#x} != device VA {dev_base:#x} \
53 — host is not unified-addressing (UMA zero-copy unavailable)"
54 );
55 }
56 Ok(Self {
57 pinned,
58 dev_base,
59 num_slabs,
60 slots_per_slab,
61 record_stride,
62 })
63 }
64
65 pub fn num_slabs(&self) -> u32 {
66 self.num_slabs
67 }
68 pub fn slots_per_slab(&self) -> u32 {
69 self.slots_per_slab
70 }
71 pub fn record_stride(&self) -> usize {
72 self.record_stride
73 }
74
75 fn linear_slot(&self, slab: u32, slot: u32) -> Result<usize> {
76 if slab >= self.num_slabs || slot >= self.slots_per_slab {
77 bail!(
78 "ExpertArena: slot ({slab},{slot}) out of range ({},{})",
79 self.num_slabs,
80 self.slots_per_slab
81 );
82 }
83 Ok((slab as usize) * (self.slots_per_slab as usize) + (slot as usize))
84 }
85
86 pub fn slot_host_ptr(&self, slab: u32, slot: u32) -> Result<*mut u8> {
88 let i = self.linear_slot(slab, slot)?;
89 Ok(unsafe { (self.pinned.ptr as *mut u8).add(i * self.record_stride) })
92 }
93
94 pub fn slot_dev_va(&self, slab: u32, slot: u32) -> Result<u64> {
96 let i = self.linear_slot(slab, slot)?;
97 Ok(self.dev_base + (i as u64) * (self.record_stride as u64))
98 }
99
100 pub fn base_ptr(&self) -> *mut c_void {
103 self.pinned.ptr
104 }
105 pub fn total_bytes(&self) -> usize {
106 self.pinned.bytes
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
117 #[ignore = "requires GPU"]
118 fn arena_slots_are_strided_and_same_va() {
119 let _ctx = crate::cuda_min::CudaCtx::new(0).unwrap();
120 let stride = 8192;
121 let arena = ExpertArena::new(2, 3, stride).unwrap();
122 assert_eq!(arena.total_bytes(), 2 * 3 * stride);
123 let a = arena.slot_dev_va(0, 0).unwrap();
124 let b = arena.slot_dev_va(0, 1).unwrap();
125 let c = arena.slot_dev_va(1, 0).unwrap();
126 assert_eq!(b - a, stride as u64);
127 assert_eq!(c - a, 3 * stride as u64);
128 assert_eq!(
129 arena.slot_host_ptr(1, 2).unwrap() as u64,
130 arena.slot_dev_va(1, 2).unwrap()
131 );
132 assert!(arena.slot_dev_va(2, 0).is_err());
133 }
134}