1use std::collections::HashMap;
24use std::sync::Arc;
25
26use anyhow::{Result, bail, ensure};
27
28use super::expert_prefetch::{ReaderPool, Ticket};
29use super::expert_stream::{ExpertSource, SlotLayout};
30use crate::gpu::{DevicePtr, GpuBackend};
31
32pub struct PinnedArena {
34 host: *mut u8,
35 dev: DevicePtr,
36 bytes: usize,
37}
38
39unsafe impl Send for PinnedArena {}
41unsafe impl Sync for PinnedArena {}
42
43impl PinnedArena {
44 pub fn alloc(gpu: &dyn GpuBackend, bytes: usize) -> Result<Self> {
47 let host = gpu.alloc_host_pinned(bytes)?;
48 let dev = match gpu.host_ptr_to_device(host) {
49 Ok(d) => d,
50 Err(e) => {
51 let _ = gpu.free_host_pinned(host, bytes);
52 return Err(e);
53 }
54 };
55 Ok(PinnedArena { host, dev, bytes })
56 }
57
58 pub fn host(&self) -> *mut u8 {
59 self.host
60 }
61
62 pub fn dev(&self) -> DevicePtr {
63 self.dev
64 }
65
66 pub fn bytes(&self) -> usize {
67 self.bytes
68 }
69
70 pub fn free(self, gpu: &dyn GpuBackend) -> Result<()> {
71 gpu.free_host_pinned(self.host, self.bytes)
72 }
73}
74
75#[derive(Clone, Copy, Debug, PartialEq, Eq)]
77pub struct ExpertSlot {
78 pub gate: DevicePtr,
79 pub up: DevicePtr,
80 pub down: DevicePtr,
81 pub host: *const u8,
83}
84
85#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
86pub struct LruStats {
87 pub hits: u64,
88 pub misses: u64,
89 pub evictions: u64,
90 pub bytes_read: u64,
91 pub waited: u64,
94 pub prefetched: u64,
96 pub prefetch_unused: u64,
97}
98
99pub(super) const NONE: u32 = u32::MAX;
100
101pub(super) struct SlotMeta {
102 pub(super) key: Option<(u32, u32)>,
103 pub(super) prev: u32,
104 pub(super) next: u32,
105 pub(super) epoch: u64,
107 pub(super) ticket: Option<Arc<Ticket>>,
109 pub(super) speculative: bool,
111}
112
113#[derive(Clone, Copy)]
116pub(super) struct SlotPtr(pub(super) *mut u8);
117
118impl SlotPtr {
119 pub(super) fn get(self) -> *mut u8 {
122 self.0
123 }
124}
125unsafe impl Send for SlotPtr {}
129unsafe impl Sync for SlotPtr {}
130
131type MissRange = (u32, (u32, u32), SlotPtr, usize, usize);
135
136pub struct ExpertLru {
137 host: *mut u8,
138 pub(super) dev: u64,
139 layout: SlotLayout,
140 pub(super) n_slots: usize,
141 pub(super) meta: Vec<SlotMeta>,
142 pub(super) map: HashMap<(u32, u32), u32>,
143 pub(super) head: u32,
145 pub(super) tail: u32,
147 pub(super) epoch: u64,
148 pub(super) stats: LruStats,
149 pub(super) pool: Option<(Arc<dyn ExpertSource + Send + Sync>, ReaderPool)>,
153 pub(super) in_flight: Vec<u32>,
155 pub(super) evict_pct: usize,
157 pub(super) rng: u64,
158 pub(super) trace: Option<std::io::BufWriter<std::fs::File>>,
160 pub(super) t0: std::time::Instant,
161 pub(super) staging: Option<super::expert_arena::Staging>,
164 pub(super) slot_changes: Vec<(u32, u32, i32)>,
167}
168
169unsafe impl Send for ExpertLru {}
172
173impl ExpertLru {
174 pub fn new(host: *mut u8, dev: DevicePtr, bytes: usize, layout: SlotLayout) -> Result<Self> {
177 ensure!(layout.bytes > 0, "expert slot layout has zero bytes");
178 let n_slots = bytes / layout.bytes;
179 ensure!(
180 n_slots >= 1,
181 "arena of {bytes} bytes holds no {}-byte expert slot",
182 layout.bytes
183 );
184 ensure!(n_slots < NONE as usize, "too many slots");
185 let mut meta = Vec::with_capacity(n_slots);
186 for i in 0..n_slots as u32 {
187 meta.push(SlotMeta {
188 key: None,
189 prev: if i == 0 { NONE } else { i - 1 },
190 next: if i as usize + 1 == n_slots {
191 NONE
192 } else {
193 i + 1
194 },
195 epoch: 0,
196 ticket: None,
197 speculative: false,
198 });
199 }
200 Ok(ExpertLru {
201 host,
202 dev: dev.0,
203 layout,
204 n_slots,
205 meta,
206 map: HashMap::new(),
207 head: 0,
208 tail: n_slots as u32 - 1,
209 epoch: 1,
210 stats: LruStats::default(),
211 pool: None,
212 in_flight: Vec::new(),
213 evict_pct: std::env::var("ATLAS_DS41_EVICT_RANDOM_PCT")
214 .ok()
215 .and_then(|v| v.parse().ok())
216 .filter(|p| *p <= 100)
217 .unwrap_or(8),
218 rng: 0x9E37_79B9_7F4A_7C15,
219 trace: None,
220 t0: std::time::Instant::now(),
221 staging: None,
222 slot_changes: Vec::new(),
223 })
224 }
225
226 pub fn n_slots(&self) -> usize {
227 self.n_slots
228 }
229
230 pub fn layout(&self) -> SlotLayout {
231 self.layout
232 }
233
234 pub fn resident(&self) -> usize {
236 self.map.len()
237 }
238
239 pub fn contains(&self, layer: u32, expert: u32) -> bool {
240 self.map.contains_key(&(layer, expert))
241 }
242
243 pub fn stats(&self) -> LruStats {
244 self.stats
245 }
246
247 pub fn reset_stats(&mut self) {
248 self.stats = LruStats::default();
249 }
250
251 pub fn begin_token(&mut self) {
254 self.epoch += 1;
255 if self.pool.is_some() {
256 self.reap();
257 }
258 }
259
260 pub fn slot(&self, i: u32) -> ExpertSlot {
261 let base = i as usize * self.layout.bytes;
262 let l = self.layout;
263 ExpertSlot {
264 gate: DevicePtr(self.dev + (base + l.gate_off) as u64),
265 up: DevicePtr(self.dev + (base + l.up_off) as u64),
266 down: DevicePtr(self.dev + (base + l.down_off) as u64),
267 host: if self.host.is_null() {
270 std::ptr::null()
271 } else {
272 unsafe { self.host.add(base) as *const u8 }
273 },
274 }
275 }
276
277 pub(super) fn slot_ptr(&self, i: u32) -> SlotPtr {
278 SlotPtr(unsafe { self.host.add(i as usize * self.layout.bytes) })
280 }
281
282 pub(super) fn unlink(&mut self, i: u32) {
283 let (p, n) = (self.meta[i as usize].prev, self.meta[i as usize].next);
284 if p == NONE {
285 self.head = n;
286 } else {
287 self.meta[p as usize].next = n;
288 }
289 if n == NONE {
290 self.tail = p;
291 } else {
292 self.meta[n as usize].prev = p;
293 }
294 }
295
296 fn push_front(&mut self, i: u32) {
297 let m = &mut self.meta[i as usize];
298 m.prev = NONE;
299 m.next = self.head;
300 if self.head != NONE {
301 self.meta[self.head as usize].prev = i;
302 }
303 self.head = i;
304 if self.tail == NONE {
305 self.tail = i;
306 }
307 }
308
309 pub(super) fn touch(&mut self, i: u32) {
310 self.unlink(i);
311 self.push_front(i);
312 self.meta[i as usize].epoch = self.epoch;
313 }
314
315 pub(super) fn assign(&mut self, key: (u32, u32)) -> Result<u32> {
317 let i = self.take_victim()?;
318 self.meta[i as usize].key = Some(key);
319 self.map.insert(key, i);
320 self.slot_changes.push((key.0, key.1, i as i32));
321 self.touch(i);
322 Ok(i)
323 }
324
325 pub(super) fn unmap(&mut self, i: u32) {
326 if let Some(k) = self.meta[i as usize].key.take() {
327 self.map.remove(&k);
328 self.slot_changes.push((k.0, k.1, -1));
329 }
330 self.meta[i as usize].epoch = 0;
331 self.meta[i as usize].ticket = None;
332 self.meta[i as usize].speculative = false;
333 self.unlink(i);
334 let m = &mut self.meta[i as usize];
336 m.next = NONE;
337 m.prev = self.tail;
338 if self.tail != NONE {
339 self.meta[self.tail as usize].next = i;
340 }
341 self.tail = i;
342 if self.head == NONE {
343 self.head = i;
344 }
345 }
346
347 pub fn fetch<S: ExpertSource + ?Sized>(
349 &mut self,
350 src: &S,
351 layer: u32,
352 expert: u32,
353 ) -> Result<(ExpertSlot, bool)> {
354 ensure!(
355 self.staging.is_none(),
356 "device expert cache: use fetch_many_on"
357 );
358 let key = (layer, expert);
359 if let Some(&i) = self.map.get(&key) {
360 self.touch(i);
361 self.stats.hits += 1;
362 return Ok((self.slot(i), true));
363 }
364 let i = self.assign(key)?;
365 let p = self.slot_ptr(i);
366 let dst = unsafe { std::slice::from_raw_parts_mut(p.0, self.layout.bytes) };
368 if let Err(e) = src.read_expert(layer, expert, dst) {
369 self.unmap(i);
370 return Err(e);
371 }
372 self.stats.misses += 1;
373 self.stats.bytes_read += self.layout.bytes as u64;
374 Ok((self.slot(i), false))
375 }
376
377 pub fn fetch_many<S: ExpertSource + ?Sized>(
381 &mut self,
382 src: &S,
383 keys: &[(u32, u32)],
384 threads: usize,
385 ) -> Result<Vec<ExpertSlot>> {
386 ensure!(
387 self.staging.is_none(),
388 "device expert cache: use fetch_many_on"
389 );
390 if self.pool.is_some() {
391 return self.fetch_many_prefetching(keys, &[]);
392 }
393 let mut out = Vec::with_capacity(keys.len());
394 let mut misses: Vec<(u32, (u32, u32))> = Vec::new();
395 let evictions0 = self.stats.evictions;
396 for &key in keys {
397 if let Some(&i) = self.map.get(&key) {
398 self.touch(i);
399 self.stats.hits += 1;
400 out.push(i);
401 continue;
402 }
403 let i = self.assign(key)?;
404 misses.push((i, key));
405 out.push(i);
406 }
407 let bytes = self.layout.bytes;
408 let mut failures: Vec<(u32, anyhow::Error)> = Vec::new();
409 if threads <= 1 {
410 for &(i, (l, e)) in &misses {
411 let p = self.slot_ptr(i);
412 let dst = unsafe { std::slice::from_raw_parts_mut(p.0, bytes) };
414 if let Err(err) = src.read_expert(l, e, dst) {
415 failures.push((i, err));
416 }
417 }
418 } else if !misses.is_empty() {
419 let parts = (threads / misses.len())
424 .clamp(1, threads)
425 .min((bytes / (1 << 20)).max(1));
426 let per_part = bytes.div_ceil(parts);
427 let work: Vec<MissRange> = misses
428 .iter()
429 .flat_map(|&(i, k)| {
430 let p = self.slot_ptr(i);
431 (0..parts).map(move |j| {
432 let off = j * per_part;
433 (i, k, p, off, per_part.min(bytes - off))
434 })
435 })
436 .filter(|w| w.4 > 0)
437 .collect();
438 let per = work.len().div_ceil(threads.min(work.len()));
439 let results: Vec<Vec<(u32, anyhow::Error)>> = std::thread::scope(|s| {
440 let handles: Vec<_> = work
441 .chunks(per)
442 .map(|chunk| {
443 s.spawn(move || {
444 let mut errs = Vec::new();
445 for (i, (l, e), p, off, len) in chunk {
446 let dst =
449 unsafe { std::slice::from_raw_parts_mut(p.0.add(*off), *len) };
450 if let Err(err) = src.read_expert_range(*l, *e, *off, dst) {
451 errs.push((*i, err));
452 }
453 }
454 errs
455 })
456 })
457 .collect();
458 handles
459 .into_iter()
460 .map(|h| h.join().expect("expert reader thread panicked"))
461 .collect()
462 });
463 let mut seen = std::collections::HashSet::new();
466 failures = results
467 .into_iter()
468 .flatten()
469 .filter(|(i, _)| seen.insert(*i))
470 .collect();
471 }
472 let n_ok = misses.len() - failures.len();
473 self.stats.misses += n_ok as u64;
474 self.stats.bytes_read += (n_ok * bytes) as u64;
475 if self.trace.is_some() {
476 let ev = self.stats.evictions - evictions0;
477 self.trace_line(keys, misses.len(), ev, 0);
478 }
479 if let Some((_, first)) = failures.first() {
480 let msg = format!(
481 "{} of {} expert reads failed; first: {first:#}",
482 failures.len(),
483 misses.len()
484 );
485 for (i, _) in &failures {
486 self.unmap(*i);
487 }
488 bail!(msg);
489 }
490 Ok(out.into_iter().map(|i| self.slot(i)).collect())
491 }
492}