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