1use anyhow::{Context, Result, bail, ensure};
31
32use super::expert_direct::{StagedRead, direct_plan, ring_slot_bytes};
33use super::expert_lru::{ExpertLru, ExpertSlot, PinnedArena, SlotPtr};
34use super::expert_stream::{DirectSeg, ExpertSource, SlotLayout, pread_at_least};
35use crate::gpu::{DevicePtr, GpuBackend};
36
37pub struct DeviceArena {
39 dev: DevicePtr,
40 bytes: usize,
41 ring: *mut u8,
42 ring_bytes: usize,
43 events: [u64; 2],
44}
45
46unsafe impl Send for DeviceArena {}
49unsafe impl Sync for DeviceArena {}
50
51pub struct Staging {
53 pub(super) ring: *mut u8,
54 pub(super) ring_slots: usize,
56 pub(super) slot_bytes: usize,
59 pub(super) events: [u64; 2],
61 pub(super) armed: [bool; 2],
63 pub(super) half: usize,
65}
66
67unsafe impl Send for Staging {}
69
70impl DeviceArena {
71 pub fn alloc(
74 gpu: &dyn GpuBackend,
75 bytes: usize,
76 layout: SlotLayout,
77 ring_slots: usize,
78 ) -> Result<(Self, ExpertLru)> {
79 let ring_slots = ring_slots.max(2).next_multiple_of(2);
80 let dev = gpu.alloc_arena(bytes)?;
81 let slot_bytes = ring_slot_bytes(&layout);
82 let ring_bytes = ring_slots * slot_bytes;
83 let ring = match gpu.alloc_host_pinned(ring_bytes) {
84 Ok(p) => p,
85 Err(e) => {
86 let _ = gpu.free_arena(dev);
87 return Err(e);
88 }
89 };
90 let events = [gpu.create_event()?, gpu.create_event()?];
91 let mut lru = ExpertLru::new(std::ptr::null_mut(), dev, bytes, layout)?;
92 lru.staging = Some(Staging {
93 ring,
94 ring_slots,
95 slot_bytes,
96 events,
97 armed: [false; 2],
98 half: 0,
99 });
100 Ok((
101 DeviceArena {
102 dev,
103 bytes,
104 ring,
105 ring_bytes,
106 events,
107 },
108 lru,
109 ))
110 }
111
112 pub fn dev(&self) -> DevicePtr {
113 self.dev
114 }
115
116 pub fn bytes(&self) -> usize {
117 self.bytes
118 }
119
120 pub fn free(self, gpu: &dyn GpuBackend) -> Result<()> {
121 for e in self.events {
122 gpu.destroy_event(e)?;
123 }
124 gpu.free_host_pinned(self.ring, self.ring_bytes)?;
125 gpu.free_arena(self.dev)
126 }
127}
128
129pub enum ExpertArena {
131 Pinned(PinnedArena),
133 Device(DeviceArena),
135}
136
137impl ExpertArena {
138 pub fn alloc(
141 gpu: &dyn GpuBackend,
142 bytes: usize,
143 layout: SlotLayout,
144 device: bool,
145 ring_slots: usize,
146 ) -> Result<(Self, ExpertLru)> {
147 if device {
148 let (a, lru) = DeviceArena::alloc(gpu, bytes, layout, ring_slots)?;
149 return Ok((ExpertArena::Device(a), lru));
150 }
151 let a = PinnedArena::alloc(gpu, bytes)?;
152 let lru = ExpertLru::new(a.host(), a.dev(), a.bytes(), layout)?;
153 Ok((ExpertArena::Pinned(a), lru))
154 }
155
156 pub fn bytes(&self) -> usize {
157 match self {
158 ExpertArena::Pinned(a) => a.bytes(),
159 ExpertArena::Device(a) => a.bytes(),
160 }
161 }
162
163 pub fn describe(&self) -> String {
165 let gib = self.bytes() as f64 / (1024.0 * 1024.0 * 1024.0);
166 match self {
167 ExpertArena::Pinned(_) => format!("{gib:.1} GiB page-locked, read in place"),
168 ExpertArena::Device(a) => format!(
169 "{gib:.1} GiB device memory behind a {:.0} MiB page-locked staging ring",
170 a.ring_bytes as f64 / 1048576.0
171 ),
172 }
173 }
174
175 pub fn free(self, gpu: &dyn GpuBackend) -> Result<()> {
176 match self {
177 ExpertArena::Pinned(a) => a.free(gpu),
178 ExpertArena::Device(a) => a.free(gpu),
179 }
180 }
181}
182
183impl ExpertLru {
186 pub fn arena_dev(&self) -> DevicePtr {
188 DevicePtr(self.dev)
189 }
190
191 pub fn drain_slot_changes(&mut self) -> Vec<(u32, u32, i32)> {
194 std::mem::take(&mut self.slot_changes)
195 }
196
197 pub fn is_device(&self) -> bool {
199 self.staging.is_some()
200 }
201
202 pub fn fetch_many_on<S: ExpertSource + ?Sized>(
207 &mut self,
208 gpu: &dyn GpuBackend,
209 stream: u64,
210 src: &S,
211 keys: &[(u32, u32)],
212 predicted: &[(u32, u32)],
213 threads: usize,
214 ) -> Result<Vec<ExpertSlot>> {
215 if self.staging.is_some() {
216 self.fetch_many_staged(gpu, stream, src, keys, threads)
217 } else if self.has_pool() {
218 self.fetch_many_prefetching(keys, predicted)
219 } else {
220 self.fetch_many(src, keys, threads)
221 }
222 }
223
224 fn fetch_many_staged<S: ExpertSource + ?Sized>(
229 &mut self,
230 gpu: &dyn GpuBackend,
231 stream: u64,
232 src: &S,
233 keys: &[(u32, u32)],
234 threads: usize,
235 ) -> Result<Vec<ExpertSlot>> {
236 let (ring, ring_slots, slot_bytes, events) = match &self.staging {
237 Some(s) => (s.ring, s.ring_slots, s.slot_bytes, s.events),
238 None => bail!("fetch_many_staged on a page-locked expert cache"),
239 };
240 let evictions0 = self.stats.evictions;
241 let mut out: Vec<u32> = Vec::with_capacity(keys.len());
242 let mut misses: Vec<(u32, (u32, u32))> = Vec::new();
243 for &key in keys {
244 if let Some(&i) = self.map.get(&key) {
245 self.touch(i);
246 self.stats.hits += 1;
247 out.push(i);
248 continue;
249 }
250 let i = self.assign(key)?;
251 misses.push((i, key));
252 out.push(i);
253 }
254 let layout = self.layout();
255 let bytes = layout.bytes;
256 let half_slots = ring_slots / 2;
257 let threads = threads.max(1);
258 let mut failures: Vec<(u32, anyhow::Error)> = Vec::new();
259 for batch in misses.chunks(half_slots) {
260 let h = {
261 let st = self.staging.as_mut().expect("staging");
262 let h = st.half;
263 st.half ^= 1;
264 h
265 };
266 if self.staging.as_ref().is_some_and(|s| s.armed[h]) {
269 gpu.event_synchronize(events[h])?;
270 self.staging.as_mut().expect("staging").armed[h] = false;
271 }
272 let half_base = SlotPtr(unsafe { ring.add(h * half_slots * slot_bytes) });
274 let parts = (threads / batch.len())
278 .clamp(1, threads)
279 .min((bytes / (1 << 20)).max(1));
280 let per_part = bytes.div_ceil(parts);
281 let mut work: Vec<StagedRead> = Vec::new();
282 let mut direct: Vec<Option<(Vec<DirectSeg>, Vec<usize>)>> =
284 Vec::with_capacity(batch.len());
285 for (j, &(i, (l, e))) in batch.iter().enumerate() {
286 let slot_ring_off = j * slot_bytes;
287 match src.direct_segments(l, e)? {
288 Some(segs) => {
289 let (reads, bases) = direct_plan(i, &segs, &layout, slot_ring_off, parts);
290 work.extend(reads);
291 direct.push(Some((segs, bases)));
292 }
293 None => {
294 for p in 0..parts {
295 let off = p * per_part;
296 let len = per_part.min(bytes.saturating_sub(off));
297 if len > 0 {
298 work.push(StagedRead::Buffered {
299 slot: i,
300 key: (l, e),
301 off,
302 len,
303 ring_off: slot_ring_off + off,
304 });
305 }
306 }
307 direct.push(None);
308 }
309 }
310 }
311 let per = work.len().div_ceil(threads.min(work.len()).max(1)).max(1);
312 let results: Vec<Vec<(u32, anyhow::Error)>> = std::thread::scope(|s| {
313 let handles: Vec<_> = work
314 .chunks(per)
315 .map(|chunk| {
316 s.spawn(move || {
317 let mut errs = Vec::new();
318 for r in chunk {
319 if let Err(err) = staged_read(src, half_base, r) {
320 errs.push((r.slot(), err));
321 }
322 }
323 errs
324 })
325 })
326 .collect();
327 handles
328 .into_iter()
329 .map(|h| h.join().expect("expert reader thread panicked"))
330 .collect()
331 });
332 let mut seen = std::collections::HashSet::new();
333 let failed_now: Vec<(u32, anyhow::Error)> = results
334 .into_iter()
335 .flatten()
336 .filter(|(i, _)| seen.insert(*i))
337 .collect();
338 for (j, &(i, _)) in batch.iter().enumerate() {
341 if seen.contains(&i) {
342 continue;
343 }
344 let slot_dev = self.slot(i).gate.0 - layout.gate_off as u64;
345 let copies: Vec<(usize, usize, usize)> = match &direct[j] {
347 Some((segs, bases)) => segs
348 .iter()
349 .zip(bases)
350 .map(|(seg, &base)| (base, seg.len, seg.slot_off))
351 .collect(),
352 None => vec![(j * slot_bytes, bytes, 0)],
353 };
354 for (ring_off, len, dev_off) in copies {
355 let src_bytes =
357 unsafe { std::slice::from_raw_parts(half_base.get().add(ring_off), len) };
358 gpu.copy_h2d_async_retained(
359 src_bytes,
360 DevicePtr(slot_dev + dev_off as u64),
361 stream,
362 )?;
363 }
364 }
365 gpu.record_event(events[h], stream)?;
366 self.staging.as_mut().expect("staging").armed[h] = true;
367 failures.extend(failed_now);
368 }
369 let n_ok = misses.len() - failures.len();
370 self.stats.misses += n_ok as u64;
371 self.stats.bytes_read += (n_ok * bytes) as u64;
372 if self.trace.is_some() {
373 let ev = self.stats.evictions - evictions0;
374 self.trace_line(keys, misses.len(), ev, 0);
375 }
376 if let Some((_, first)) = failures.first() {
377 let msg = format!(
378 "{} of {} expert reads failed; first: {first:#}",
379 failures.len(),
380 misses.len()
381 );
382 for (i, _) in &failures {
383 self.unmap(*i);
384 }
385 bail!(msg);
386 }
387 ensure!(out.len() == keys.len(), "staged fetch lost a key");
388 Ok(out.into_iter().map(|i| self.slot(i)).collect())
389 }
390}
391
392fn staged_read<S: ExpertSource + ?Sized>(
394 src: &S,
395 half_base: SlotPtr,
396 r: &StagedRead,
397) -> Result<()> {
398 match r {
399 StagedRead::Buffered {
400 key: (l, e),
401 off,
402 len,
403 ring_off,
404 ..
405 } => {
406 let dst =
409 unsafe { std::slice::from_raw_parts_mut(half_base.get().add(*ring_off), *len) };
410 src.read_expert_range(*l, *e, *off, dst)
411 }
412 StagedRead::Direct {
413 shard,
414 file_off,
415 len,
416 need,
417 ring_off,
418 ..
419 } => {
420 let dst =
423 unsafe { std::slice::from_raw_parts_mut(half_base.get().add(*ring_off), *len) };
424 let f = src
425 .direct_file(*shard)
426 .with_context(|| format!("shard {shard} has no direct descriptor"))?;
427 pread_at_least(f, *file_off, dst, *need)
428 .with_context(|| format!("direct read, shard {shard}"))
429 }
430 }
431}