1use std::collections::HashMap;
45use std::io;
46use std::sync::atomic::{AtomicU64, Ordering};
47use std::sync::{Arc, Mutex};
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
51pub struct ExpertKey {
52 pub layer: u32,
53 pub expert: u32,
54}
55
56pub trait ExpertSource: Send + Sync {
59 fn expert_len(&self, key: ExpertKey) -> Option<usize>;
62
63 fn read_expert(&self, key: ExpertKey) -> io::Result<Vec<u8>>;
65}
66
67pub struct ExpertLease {
70 data: Arc<Vec<u8>>,
71}
72
73impl ExpertLease {
74 pub fn bytes(&self) -> &[u8] {
75 &self.data
76 }
77
78 pub fn shared_buf(&self) -> Arc<Vec<u8>> {
84 Arc::clone(&self.data)
85 }
86}
87
88#[derive(Debug, Clone, Copy, Default)]
91pub struct ExpertStoreStats {
92 pub hits: u64,
93 pub misses: u64,
94 pub evictions: u64,
95 pub pass_throughs: u64,
98 pub bytes_read: u64,
99 pub resident_bytes: u64,
100}
101
102struct Entry {
103 data: Arc<Vec<u8>>,
104 last_used: u64,
110}
111
112struct Inner {
113 entries: HashMap<ExpertKey, Entry>,
114 resident_bytes: usize,
115 clock: u64,
116}
117
118pub struct ExpertStore<S: ExpertSource> {
120 source: S,
121 budget_bytes: usize,
122 inner: Mutex<Inner>,
123 hits: AtomicU64,
124 misses: AtomicU64,
125 evictions: AtomicU64,
126 pass_throughs: AtomicU64,
127 bytes_read: AtomicU64,
128}
129
130impl<S: ExpertSource> ExpertStore<S> {
131 pub fn new(source: S, budget_bytes: usize) -> Self {
132 ExpertStore {
133 source,
134 budget_bytes,
135 inner: Mutex::new(Inner {
136 entries: HashMap::new(),
137 resident_bytes: 0,
138 clock: 0,
139 }),
140 hits: AtomicU64::new(0),
141 misses: AtomicU64::new(0),
142 evictions: AtomicU64::new(0),
143 pass_throughs: AtomicU64::new(0),
144 bytes_read: AtomicU64::new(0),
145 }
146 }
147
148 pub fn budget_bytes(&self) -> usize {
149 self.budget_bytes
150 }
151
152 pub fn stats(&self) -> ExpertStoreStats {
153 let resident = self
154 .inner
155 .lock()
156 .unwrap_or_else(|p| p.into_inner())
157 .resident_bytes as u64;
158 ExpertStoreStats {
159 hits: self.hits.load(Ordering::Relaxed),
160 misses: self.misses.load(Ordering::Relaxed),
161 evictions: self.evictions.load(Ordering::Relaxed),
162 pass_throughs: self.pass_throughs.load(Ordering::Relaxed),
163 bytes_read: self.bytes_read.load(Ordering::Relaxed),
164 resident_bytes: resident,
165 }
166 }
167
168 pub fn prefetch(&self, keys: &[ExpertKey]) {
174 for &key in keys {
175 let _ = self.acquire(key);
176 }
177 }
178
179 pub fn acquire(&self, key: ExpertKey) -> io::Result<ExpertLease> {
184 {
186 let mut inner = self.inner.lock().unwrap_or_else(|p| p.into_inner());
187 inner.clock += 1;
188 let clock = inner.clock;
189 if let Some(entry) = inner.entries.get_mut(&key) {
190 entry.last_used = clock;
191 self.hits.fetch_add(1, Ordering::Relaxed);
192 return Ok(ExpertLease {
193 data: Arc::clone(&entry.data),
194 });
195 }
196 }
197
198 self.misses.fetch_add(1, Ordering::Relaxed);
200 let data = self.source.read_expert(key)?;
201 self.bytes_read
202 .fetch_add(data.len() as u64, Ordering::Relaxed);
203 let size = data.len();
204 let data = Arc::new(data);
205
206 let mut inner = self.inner.lock().unwrap_or_else(|p| p.into_inner());
207 if let Some(entry) = inner.entries.get_mut(&key) {
213 return Ok(ExpertLease {
214 data: Arc::clone(&entry.data),
215 });
216 }
217
218 if size > self.budget_bytes {
219 self.pass_throughs.fetch_add(1, Ordering::Relaxed);
220 return Ok(ExpertLease { data });
221 }
222
223 while inner.resident_bytes + size > self.budget_bytes {
225 let victim = inner
226 .entries
227 .iter()
228 .filter(|(_, e)| Arc::strong_count(&e.data) == 1)
229 .min_by_key(|(_, e)| e.last_used)
230 .map(|(k, _)| *k);
231 match victim {
232 Some(k) => {
233 let e = inner.entries.remove(&k).expect("victim key just found");
234 inner.resident_bytes -= e.data.len();
235 self.evictions.fetch_add(1, Ordering::Relaxed);
236 }
237 None => {
238 self.pass_throughs.fetch_add(1, Ordering::Relaxed);
242 return Ok(ExpertLease { data });
243 }
244 }
245 }
246
247 inner.clock += 1;
248 let clock = inner.clock;
249 inner.resident_bytes += size;
250 inner.entries.insert(
251 key,
252 Entry {
253 data: Arc::clone(&data),
254 last_used: clock,
255 },
256 );
257 Ok(ExpertLease { data })
258 }
259}
260
261pub struct FileRangeSource {
269 file: std::fs::File,
270 ranges: HashMap<ExpertKey, (u64, usize)>,
271 #[cfg(not(unix))]
273 seek_lock: Mutex<()>,
274}
275
276impl FileRangeSource {
277 pub fn new(file: std::fs::File, ranges: HashMap<ExpertKey, (u64, usize)>) -> Self {
278 FileRangeSource {
279 file,
280 ranges,
281 #[cfg(not(unix))]
282 seek_lock: Mutex::new(()),
283 }
284 }
285}
286
287impl ExpertSource for FileRangeSource {
288 fn expert_len(&self, key: ExpertKey) -> Option<usize> {
289 self.ranges.get(&key).map(|&(_, len)| len)
290 }
291
292 fn read_expert(&self, key: ExpertKey) -> io::Result<Vec<u8>> {
293 let &(offset, len) = self
294 .ranges
295 .get(&key)
296 .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, format!("{key:?}")))?;
297 let mut buf = vec![0u8; len];
298 #[cfg(unix)]
299 {
300 use std::os::unix::fs::FileExt;
301 self.file.read_exact_at(&mut buf, offset)?;
302 }
303 #[cfg(not(unix))]
304 {
305 use std::io::{Read, Seek, SeekFrom};
306 let _guard = self.seek_lock.lock().unwrap_or_else(|p| p.into_inner());
307 let mut f = &self.file;
308 f.seek(SeekFrom::Start(offset))?;
309 f.read_exact(&mut buf)?;
310 }
311 Ok(buf)
312 }
313}
314
315#[cfg(test)]
316mod tests {
317 use super::*;
318
319 struct PatternSource {
323 len: usize,
324 n_layers: u32,
325 n_experts: u32,
326 }
327
328 fn expected_bytes(key: ExpertKey, len: usize) -> Vec<u8> {
329 (0..len)
330 .map(|i| (key.layer as usize * 31 + key.expert as usize * 7 + i * 13) as u8)
331 .collect()
332 }
333
334 impl ExpertSource for PatternSource {
335 fn expert_len(&self, key: ExpertKey) -> Option<usize> {
336 (key.layer < self.n_layers && key.expert < self.n_experts).then_some(self.len)
337 }
338 fn read_expert(&self, key: ExpertKey) -> io::Result<Vec<u8>> {
339 if self.expert_len(key).is_none() {
340 return Err(io::Error::new(io::ErrorKind::NotFound, "no such expert"));
341 }
342 Ok(expected_bytes(key, self.len))
343 }
344 }
345
346 fn key(layer: u32, expert: u32) -> ExpertKey {
347 ExpertKey { layer, expert }
348 }
349
350 fn store(len: usize, budget: usize) -> ExpertStore<PatternSource> {
351 ExpertStore::new(
352 PatternSource {
353 len,
354 n_layers: 8,
355 n_experts: 8,
356 },
357 budget,
358 )
359 }
360
361 #[test]
362 fn hits_misses_and_lru_eviction_order() {
363 let s = store(100, 250); assert_eq!(
365 s.acquire(key(0, 0)).unwrap().bytes(),
366 expected_bytes(key(0, 0), 100)
367 );
368 assert_eq!(
369 s.acquire(key(0, 1)).unwrap().bytes(),
370 expected_bytes(key(0, 1), 100)
371 );
372 s.acquire(key(0, 0)).unwrap();
374 s.acquire(key(0, 2)).unwrap();
376 let before = s.stats();
377 s.acquire(key(0, 0)).unwrap(); let after = s.stats();
379 assert_eq!(after.hits, before.hits + 1);
380 assert_eq!(after.misses, before.misses);
381 assert_eq!(after.evictions, 1);
382 assert!(after.resident_bytes <= 250);
383 s.acquire(key(0, 1)).unwrap();
385 assert_eq!(s.stats().misses, after.misses + 1);
386 }
387
388 #[test]
389 fn a_live_lease_pins_its_entry_against_eviction() {
390 let s = store(100, 250); let pinned = s.acquire(key(1, 0)).unwrap();
392 for e in 1..6 {
396 s.acquire(key(1, e)).unwrap();
397 }
398 assert_eq!(pinned.bytes(), expected_bytes(key(1, 0), 100));
399 let hits_before = s.stats().hits;
401 let again = s.acquire(key(1, 0)).unwrap();
402 assert_eq!(s.stats().hits, hits_before + 1);
403 assert_eq!(again.bytes(), expected_bytes(key(1, 0), 100));
404
405 drop(pinned);
408 drop(again);
409 for e in 1..6 {
410 s.acquire(key(2, e)).unwrap();
411 }
412 let misses_before = s.stats().misses;
413 s.acquire(key(1, 0)).unwrap();
414 assert_eq!(
415 s.stats().misses,
416 misses_before + 1,
417 "unpinned entry was evictable"
418 );
419 }
420
421 #[test]
425 fn budget_smaller_than_one_expert_degrades_to_pass_through_not_failure() {
426 let s = store(100, 50);
427 for e in 0..4 {
428 let lease = s.acquire(key(0, e)).unwrap();
429 assert_eq!(lease.bytes(), expected_bytes(key(0, e), 100));
430 }
431 let st = s.stats();
432 assert_eq!(st.pass_throughs, 4);
433 assert_eq!(st.resident_bytes, 0);
434 assert_eq!(st.evictions, 0);
435 }
436
437 #[test]
440 fn fully_pinned_cache_serves_new_experts_uncached() {
441 let s = store(100, 200);
442 let _a = s.acquire(key(0, 0)).unwrap();
443 let _b = s.acquire(key(0, 1)).unwrap();
444 let c = s.acquire(key(0, 2)).unwrap();
445 assert_eq!(c.bytes(), expected_bytes(key(0, 2), 100));
446 assert_eq!(s.stats().pass_throughs, 1);
447 assert_eq!(s.stats().evictions, 0);
448 let hits_before = s.stats().hits;
450 s.acquire(key(0, 0)).unwrap();
451 s.acquire(key(0, 1)).unwrap();
452 assert_eq!(s.stats().hits, hits_before + 2);
453 }
454
455 #[test]
456 fn missing_expert_is_a_clean_error() {
457 let s = store(100, 1000);
458 assert!(s.acquire(key(99, 0)).is_err());
459 }
460
461 #[test]
466 fn concurrent_acquires_under_eviction_pressure_never_yield_wrong_bytes() {
467 let s = Arc::new(store(64, 200)); let mut handles = Vec::new();
469 for t in 0..8u32 {
470 let s = Arc::clone(&s);
471 handles.push(std::thread::spawn(move || {
472 let mut state = t.wrapping_mul(2654435761).wrapping_add(12345);
473 for _ in 0..500 {
474 state = state.wrapping_mul(1664525).wrapping_add(1013904223);
475 let k = key((state >> 8) % 4, (state >> 16) % 4);
476 let lease = s.acquire(k).expect("in-range key must read");
477 assert_eq!(
478 lease.bytes(),
479 expected_bytes(k, 64),
480 "wrong bytes for {k:?} -- slot reuse corruption"
481 );
482 }
483 }));
484 }
485 for h in handles {
486 h.join().unwrap();
487 }
488 let st = s.stats();
489 assert_eq!(
490 st.hits + st.misses,
491 8 * 500,
492 "every acquire is a hit or a miss"
493 );
494 assert!(st.resident_bytes <= 200, "budget held under concurrency");
495 }
496
497 #[test]
503 fn weight_matrix_over_a_lease_computes_identically_and_extends_the_pin() {
504 use crate::weight_matrix::{QuantKind, WeightBytes, WeightMatrix};
505
506 struct QuantSource {
508 rows: Vec<u8>,
509 }
510 impl ExpertSource for QuantSource {
511 fn expert_len(&self, _k: ExpertKey) -> Option<usize> {
512 Some(self.rows.len())
513 }
514 fn read_expert(&self, _k: ExpertKey) -> io::Result<Vec<u8>> {
515 Ok(self.rows.clone())
516 }
517 }
518
519 let cols = 64;
520 let rows = 2;
521 let values: Vec<f32> = (0..rows * cols)
522 .map(|i| ((i as f32) * 0.11).sin())
523 .collect();
524 let mut packed = Vec::new();
525 for r in 0..rows {
526 packed.extend(ferrox_quant::quantize_q8_0(
527 &values[r * cols..(r + 1) * cols],
528 ));
529 }
530 let store = ExpertStore::new(
531 QuantSource {
532 rows: packed.clone(),
533 },
534 packed.len(),
535 );
536
537 let lease = store.acquire(key(0, 0)).unwrap();
538 let matrix = WeightMatrix::Quantized {
539 data: WeightBytes::Shared {
540 buf: lease.shared_buf(),
541 range: 0..packed.len(),
542 },
543 rows,
544 cols,
545 kind: QuantKind::Q8_0,
546 };
547 drop(lease); let owned = WeightMatrix::Quantized {
550 data: WeightBytes::Owned(packed),
551 rows,
552 cols,
553 kind: QuantKind::Q8_0,
554 };
555 let x: Vec<f32> = (0..cols).map(|i| ((i as f32) * 0.07).cos()).collect();
556 assert_eq!(
557 matrix.apply(&x),
558 owned.apply(&x),
559 "lease-backed == owned, bit for bit"
560 );
561
562 let hits_before = store.stats().hits;
565 store.acquire(key(0, 0)).unwrap();
566 assert_eq!(store.stats().hits, hits_before + 1);
567 }
568
569 #[test]
573 fn file_range_source_reads_correct_ranges_through_the_store() {
574 let dir = std::env::temp_dir();
575 let path = dir.join(format!(
576 "ferrox_expert_store_test_{}.bin",
577 std::process::id()
578 ));
579 let mut file_bytes = Vec::new();
580 let mut ranges = HashMap::new();
581 for l in 0..3u32 {
582 for e in 0..4u32 {
583 let content = expected_bytes(key(l, e), 96);
584 ranges.insert(key(l, e), (file_bytes.len() as u64, content.len()));
585 file_bytes.extend_from_slice(&content);
586 }
587 }
588 std::fs::write(&path, &file_bytes).unwrap();
589 let source = FileRangeSource::new(std::fs::File::open(&path).unwrap(), ranges);
590 let store = Arc::new(ExpertStore::new(source, 300)); let mut handles = Vec::new();
593 for t in 0..4u32 {
594 let store = Arc::clone(&store);
595 handles.push(std::thread::spawn(move || {
596 for i in 0..200u32 {
597 let k = key((t + i) % 3, i % 4);
598 let lease = store.acquire(k).unwrap();
599 assert_eq!(lease.bytes(), expected_bytes(k, 96));
600 }
601 }));
602 }
603 for h in handles {
604 h.join().unwrap();
605 }
606 std::fs::remove_file(&path).ok();
607 }
608}