1use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
18use std::sync::Arc;
19use cudarc::driver::{CudaEvent, CudaSlice, CudaStream, HostSlice, SyncOnDrop};
20use crate::Engine;
21use crate::model::{ExpertKeepalive, ExpertSource};
22use crate::spill_pread::{PreadPool, PreadStats, ReadTicket, SpillIoMode};
23
24pub const PROJ_GATE: u8 = 0;
26pub const PROJ_UP: u8 = 1;
27pub const PROJ_DOWN: u8 = 2;
28
29#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
31pub struct BlockId {
32 pub layer: u16,
33 pub proj: u8,
34 pub ex: u16,
35}
36impl BlockId {
37 #[inline]
38 pub fn new(layer: u16, proj: u8, ex: u16) -> Self { BlockId { layer, proj, ex } }
39}
40
41#[derive(Clone, Copy, Debug)]
44pub enum DispatchSlot {
45 Resident(usize),
46}
47
48struct SlotClass {
51 capacity: usize,
52 probation: VecDeque<usize>,
53 protected: VecDeque<usize>,
54 free: Vec<usize>,
55 protected_cap: usize,
56}
57
58pub struct MoeSlotCache {
61 slots: Vec<CudaSlice<u8>>, slot_class: Vec<usize>, classes: Vec<SlotClass>,
64 occupant: Vec<Option<BlockId>>, table: HashMap<BlockId, usize>, frequencies: HashMap<BlockId, f32>,
70 pending: HashMap<BlockId, PendingBlock>,
75 inflight_sources: Vec<(Arc<CudaEvent>, ExpertKeepalive)>,
78 quarantined_sources: Vec<ExpertKeepalive>,
81 compute_sources: HashMap<KeepaliveKey, ExpertKeepalive>,
85 pread: Option<PreadPool>,
88 worker_reads: HashMap<BlockId, WorkerRead>,
91 pread_requested: bool,
92 pread_fallbacks: u64,
93 copy_stream: Arc<CudaStream>,
96 copy_stream_unknown: bool,
97 compute_stream: Arc<CudaStream>,
98 compute_stream_unknown: bool,
99
100 n: usize,
101 max_block_bytes: usize,
102 size_aware: bool,
103 frequency_evict: bool,
104 frequency_decay: Option<f32>,
105 mtp_frequency_weight: f32,
110 last_forward_layer: Option<u16>,
111 last_forward_t: usize,
112 frozen: bool,
116
117 per_layer: HashMap<u16, u32>,
124 dev_rows: HashMap<u16, CudaSlice<u64>>,
129 prewarm_tried: HashSet<u16>,
132
133 pub hits: u64,
135 pub misses: u64,
136 pub staged_bytes: u64, }
138
139struct PendingBlock {
140 slot: usize,
141 ready: Arc<CudaEvent>,
142 keepalive: Option<ExpertKeepalive>,
143}
144
145#[derive(Clone, Copy)]
146struct WorkerRead {
147 ticket: ReadTicket,
148 len: usize,
149}
150
151#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
152enum KeepaliveKey {
153 Pinned(usize),
154 Buffer(usize),
155 Mmap(usize),
156}
157
158impl KeepaliveKey {
159 fn from_owner(owner: &ExpertKeepalive) -> Self {
160 match owner {
161 ExpertKeepalive::Pinned(value) => Self::Pinned(Arc::as_ptr(value) as usize),
162 ExpertKeepalive::Buffer(value) => Self::Buffer(Arc::as_ptr(value) as usize),
163 ExpertKeepalive::Mmap(value) => Self::Mmap(Arc::as_ptr(value) as usize),
164 }
165 }
166}
167
168struct ExactPinnedPrefix<'a>(&'a [u8]);
173
174impl HostSlice<u8> for ExactPinnedPrefix<'_> {
175 fn len(&self) -> usize { self.0.len() }
176
177 unsafe fn stream_synced_slice<'a>(
178 &'a self,
179 _stream: &'a CudaStream,
180 ) -> (&'a [u8], SyncOnDrop<'a>) {
181 (self.0, SyncOnDrop::Record(None))
184 }
185
186
187
188 unsafe fn stream_synced_mut_slice<'a>(
189 &'a mut self,
190 _stream: &'a CudaStream,
191 ) -> (&'a mut [u8], SyncOnDrop<'a>) {
192 panic!("ExactPinnedPrefix is a source-only HostSlice")
193 }
194}
195
196fn stage_on_copy_stream(
197 e: &Engine,
198 host_bytes: &[u8],
199 slot: &mut CudaSlice<u8>,
200) -> Result<Arc<CudaEvent>, (Box<dyn std::error::Error>, bool)> {
201 let prior = match e.stream().record_event(None) {
203 Ok(prior) => prior,
204 Err(err) => return Err((err.into(), true)),
205 };
206 if let Err(err) = e.copy_stream.wait(&prior) {
207 return Err((err.into(), true));
208 }
209 match e.stage_expert_async(host_bytes, slot, 0) {
210 Ok(ready) => Ok(Arc::new(ready)),
211 Err(err) => {
212 match e.copy_stream.synchronize() {
215 Ok(()) => Err((err, true)),
216 Err(sync_err) => Err((std::io::Error::other(format!(
217 "copy-stream H2D setup failed ({err}); stream drain also failed ({sync_err})"
218 )).into(), false)),
219 }
220 }
221 }
222}
223
224fn stage_pread_on_compute_stream(
225 e: &Engine,
226 host_bytes: &[u8],
227 slot: &mut CudaSlice<u8>,
228) -> Result<Arc<CudaEvent>, Box<dyn std::error::Error>> {
229 let ready = Arc::new(e.ctx().new_event(None)?);
230 let source = ExactPinnedPrefix(host_bytes);
231 let mut dst = slot.slice_mut(0..host_bytes.len());
232 e.stream().memcpy_htod(&source, &mut dst)?;
233 ready.record(&e.stream())?;
234 Ok(ready)
235}
236
237fn stage_pread_prefetch_on_copy_stream(
238 e: &Engine,
239 host_bytes: &[u8],
240 slot: &mut CudaSlice<u8>,
241) -> Result<Arc<CudaEvent>, (Box<dyn std::error::Error>, bool)> {
242 let ready = match e.ctx().new_event(None) {
243 Ok(ready) => Arc::new(ready),
244 Err(err) => return Err((err.into(), true)),
245 };
246 let source = ExactPinnedPrefix(host_bytes);
247 let mut dst = slot.slice_mut(0..host_bytes.len());
248 let submitted = e
249 .copy_stream
250 .memcpy_htod(&source, &mut dst)
251 .and_then(|()| ready.record(&e.copy_stream));
252 match submitted {
253 Ok(()) => Ok(ready),
254 Err(err) => match e.copy_stream.synchronize() {
255 Ok(()) => Err((err.into(), true)),
256 Err(sync_err) => Err((
257 std::io::Error::other(format!(
258 "pread copy-stream H2D setup failed ({err}); stream drain also failed ({sync_err})"
259 ))
260 .into(),
261 false,
262 )),
263 },
264 }
265}
266
267fn size_class_plan(block_bytes: &[usize], budget_bytes: usize) -> Vec<(usize, usize)> {
270 let mut counts: BTreeMap<usize, usize> = BTreeMap::new();
271 for &bytes in block_bytes.iter().filter(|&&bytes| bytes > 0) {
272 *counts.entry(bytes).or_insert(0) += 1;
273 }
274 if counts.is_empty() || budget_bytes == 0 {
275 return Vec::new();
276 }
277 let total_bytes: u128 = counts
278 .iter()
279 .map(|(&bytes, &count)| (bytes as u128 + 8) * count as u128)
280 .sum();
281 let budget = budget_bytes as u128;
282 let mut plan: Vec<(usize, usize, u128)> = counts
283 .iter()
284 .map(|(&bytes, &count)| {
285 let scaled = count as u128 * budget;
286 (
287 bytes,
288 (scaled / total_bytes).min(count as u128) as usize,
289 scaled % total_bytes,
290 )
291 })
292 .collect();
293 let mut used: u128 = plan
294 .iter()
295 .map(|(bytes, count, _)| (*bytes as u128 + 8) * *count as u128)
296 .sum();
297
298 let mut order: Vec<usize> = (0..plan.len()).collect();
301 order.sort_by(|&a, &b| plan[b].2.cmp(&plan[a].2).then(a.cmp(&b)));
302 for index in order {
303 let (bytes, count, _) = plan[index];
304 let available = counts[&bytes];
305 let required = bytes as u128 + 8;
306 if count < available && used + required <= budget {
307 plan[index].1 += 1;
308 used += required;
309 }
310 }
311 plan.into_iter()
312 .filter_map(|(bytes, count, _)| (count > 0).then_some((bytes, count)))
313 .collect()
314}
315
316impl MoeSlotCache {
317 pub fn new(e: &Engine, max_block_bytes: usize) -> Result<Self, Box<dyn std::error::Error>> {
324 let (free, _total) = e.ctx().mem_get_info()?;
325 let hard_frac = cache_hard_vram_frac();
328 let hard_bytes =
329 ((free as f64 * hard_frac) as usize).saturating_sub(2 * (max_block_bytes + 8));
330 let forced_slots = std::env::var("MEMRA_MOE_SLOTS")
331 .ok()
332 .and_then(|s| s.parse::<usize>().ok());
333 let requested_bytes = if let Some(n) = forced_slots {
334 n.saturating_mul(max_block_bytes + 8)
335 } else {
336 let frac = std::env::var("MEMRA_MOE_VRAM_FRAC") .ok()
342 .and_then(|s| s.parse::<f64>().ok())
343 .unwrap_or(0.85);
344 (free as f64 * frac) as usize
345 };
346 let budget_bytes = requested_bytes.min(hard_bytes);
347 let layout = e.moe_cache_layout().unwrap_or_default();
348 let size_aware = forced_slots.is_none()
349 && std::env::var("MEMRA_MOE_SIZE_AWARE").as_deref() == Ok("1")
350 && !layout.is_empty();
351 let frequency_evict = std::env::var("MEMRA_MOE_LFU").as_deref() == Ok("1");
352 let frequency_decay = if frequency_evict {
353 cache_lfu_decay()
354 } else {
355 None
356 };
357 let mtp_frequency_weight = cache_lfu_mtp_weight();
358 let mut class_plan = if size_aware {
359 size_class_plan(&layout, budget_bytes)
360 } else {
361 Vec::new()
362 };
363 if class_plan.iter().map(|(_, count)| count).sum::<usize>() < 8 {
364 let n = (budget_bytes / (max_block_bytes + 8)).max(8);
365 class_plan = vec![(max_block_bytes, n)];
366 }
367 let n: usize = class_plan.iter().map(|(_, count)| count).sum();
368
369 let mut slots = Vec::with_capacity(n);
370 let mut slot_class = Vec::with_capacity(n);
371 let mut classes = Vec::with_capacity(class_plan.len());
372 let mut occupant = Vec::with_capacity(n);
373 for (class_index, &(capacity, count)) in class_plan.iter().enumerate() {
374 let start = slots.len();
375 for _ in 0..count {
376 slots.push(e.alloc_u8(capacity + 8)?);
378 slot_class.push(class_index);
379 occupant.push(None);
380 }
381 let free_slots = (start..start + count).rev().collect();
382 classes.push(SlotClass {
383 capacity,
384 probation: VecDeque::new(),
385 protected: VecDeque::new(),
386 free: free_slots,
387 protected_cap: ((count as f64 * 0.8) as usize).max(1),
388 });
389 }
390 if size_aware {
391 let allocated: usize = class_plan
392 .iter()
393 .map(|(bytes, count)| (bytes + 8) * count)
394 .sum();
395 eprintln!(
396 "[moe-cache] size-aware fixed slots: {n} slots in {} classes, {:.2} GB / {:.2} GB budget",
397 class_plan.len(),
398 allocated as f64 / 1e9,
399 budget_bytes as f64 / 1e9
400 );
401 }
402 let pread_mode = crate::spill_pread::configured_mode();
403 let pread_requested = pread_mode != SpillIoMode::Mmap;
404 let pread = if pread_requested {
405
406 match PreadPool::try_new(e, max_block_bytes, pread_mode) {
407 Ok(pool) => Some(pool),
408 Err(err) => {
409 eprintln!("[spill-pread] pinned-buffer initialization failed ({err}); using mmap");
410 None
411 }
412 }
413 } else { None };
414
415
416 Ok(MoeSlotCache {
417 slots,
418 slot_class,
419 classes,
420 occupant,
421 table: HashMap::with_capacity(n * 2),
422 frequencies: HashMap::with_capacity(layout.len().max(n * 2)),
423 pending: HashMap::new(),
424 inflight_sources: Vec::new(),
425 quarantined_sources: Vec::new(),
426 compute_sources: HashMap::new(),
427 pread, worker_reads: HashMap::new(), pread_requested, pread_fallbacks: 0,
428 copy_stream: e.copy_stream.clone(), copy_stream_unknown: false,
429 compute_stream: e.stream().clone(),
430 compute_stream_unknown: false,
431 n,
432 max_block_bytes,
433 size_aware,
434 frequency_evict,
435 frequency_decay,
436 mtp_frequency_weight,
437 last_forward_layer: None,
438 last_forward_t: 0,
439 frozen: false,
440 per_layer: HashMap::new(),
441 dev_rows: HashMap::new(),
442 prewarm_tried: HashSet::new(),
443 hits: 0, misses: 0, staged_bytes: 0,
444 })
445 }
446
447 #[inline]
448 pub fn n_slots(&self) -> usize { self.n
449 }
450 #[inline]
451 pub fn is_frozen(&self) -> bool {
452 self.frozen
453 }
454 pub fn freeze(&mut self) {
455 if !self.frozen {
456 self.frozen = true;
457 let (_, complete, one_projection, two_projections, stranded_blocks) =
458 self.expert_residency_shape();
459 eprintln!(
460 "[moe-cache] residency frozen: {} slots, {} resident blocks; \
461 {complete} complete experts, {one_projection} one-projection fragments, \
462 {two_projections} two-projection fragments ({stranded_blocks} stranded blocks)",
463 self.n,
464 self.table.len()
465 );
466 let mut mtp_masks = HashMap::<u16, u8>::new();
467 for id in self.table.keys().filter(|id| id.layer == u16::MAX) {
468 *mtp_masks.entry(id.ex).or_insert(0) |= 1u8 << id.proj;
469 }
470 if !mtp_masks.is_empty() {
471 let complete = mtp_masks.values().filter(|&&mask| mask == 0b111).count();
472 eprintln!(
473 "[moe-cache] frozen MTP residency: {} blocks, {complete} complete experts",
474 mtp_masks.values().map(|mask| mask.count_ones() as usize).sum::<usize>()
475 );
476 }
477 }
478 }
479
480 pub(crate) fn expert_residency_shape(&self) -> (usize, usize, usize, usize, usize) {
481 let mut masks = HashMap::<(u16, u16), u8>::new();
482 for id in self.table.keys() {
483 *masks.entry((id.layer, id.ex)).or_insert(0) |= 1u8 << id.proj;
484 }
485 let complete = masks.values().filter(|&&mask| mask == 0b111).count();
486 let one_projection = masks
487 .values()
488 .filter(|&&mask| mask.count_ones() == 1)
489 .count();
490 let two_projections = masks
491 .values()
492 .filter(|&&mask| mask.count_ones() == 2)
493 .count();
494 let stranded_blocks = one_projection + 2 * two_projections;
495 (
496 masks.len(),
497 complete,
498 one_projection,
499 two_projections,
500 stranded_blocks,
501 )
502 }
503
504 #[inline]
505 pub fn max_block_bytes(&self) -> usize {
506 self.max_block_bytes
507 }
508
509
510 #[inline]
512 pub fn resident(&self, id: BlockId) -> Option<usize> { self.table.get(&id).copied() }
513
514 #[inline]
515 fn frequency_increment(&self, id: BlockId) -> f32 {
516 if id.layer == u16::MAX { self.mtp_frequency_weight } else { 1.0 }
517 }
518
519 pub(crate) fn note_profile_hit(&mut self, id: BlockId) {
523 if self.frozen || !self.table.contains_key(&id) {
524 return;
525 }
526 let increment = self.frequency_increment(id);
527 *self.frequencies.entry(id).or_insert(0.0) += increment;
528 }
529
530 fn on_hit(&mut self, slot: usize) {
541 let class_index = self.slot_class[slot];
542 let class = &mut self.classes[class_index];
543 if !class.free.is_empty() {
544 return;
545 }
546 if let Some(pos) = class.probation.iter().position(|&x| x == slot) {
547 class.probation.remove(pos);
548 self.push_protected(slot);
549 } else if let Some(pos) = class.protected.iter().position(|&x| x == slot) {
550 class.protected.remove(pos);
551 class.protected.push_back(slot); } else {
553 self.push_protected(slot);
555
556 }
557 }
558
559
560 fn push_protected(&mut self, slot: usize) {
562 let class = &mut self.classes[self.slot_class[slot]];
563 class.protected.push_back(slot);
564 while class.protected.len() > class.protected_cap {
565 if let Some(demoted) = class.protected.pop_front() {
566 class.probation.push_back(demoted);
567 } else {
568 break;
569 }
570 }
571 }
572
573 fn remove_occupant(&mut self, slot: usize) {
574 if let Some(old) = self.occupant[slot].take() {
575 self.table.remove(&old);
576 self.on_block_evicted(old.layer);
577 }
578 }
579
580 fn frequency_victim_in_class(&mut self, class_index: usize, keep: &[BlockId]) -> Option<usize> {
584 let class = &self.classes[class_index];
585 let probation_len = class.probation.len();
586 let candidate = class
587 .probation
588 .iter()
589 .chain(class.protected.iter())
590 .enumerate()
591 .filter_map(|(position, &slot)| {
592 let id = self.occupant[slot]?;
593 (!keep.contains(&id)).then_some((
594 self.frequencies.get(&id).copied().unwrap_or(0.0),
595 position,
596 slot,
597 ))
598 })
599 .min_by(|a, b| a.0.total_cmp(&b.0).then(a.1.cmp(&b.1)));
600 let (_, position, slot) = candidate?;
601 if position < probation_len {
602 self.classes[class_index].probation.remove(position);
603 } else {
604 self.classes[class_index]
605 .protected
606 .remove(position - probation_len);
607 }
608 Some(slot)
609 }
610
611 fn evict_one(&mut self, required: usize) -> Option<usize> {
613 for class_index in 0..self.classes.len() {
614 if self.classes[class_index].capacity < required {
615 continue;
616 }
617 let slot = if self.frequency_evict {
618 self.frequency_victim_in_class(class_index, &[])
619 } else {
620 self.classes[class_index]
621 .probation
622 .pop_front()
623 .or_else(|| self.classes[class_index].protected.pop_front())
624 };
625 if let Some(slot) = slot {
626 self.remove_occupant(slot);
627 return Some(slot);
628 }
629 }
630 None
631 }
632
633 fn evict_one_excluding(&mut self, required: usize, keep: &[BlockId]) -> Option<usize> {
637 let take = |q: &mut VecDeque<usize>, occupant: &[Option<BlockId>]| {
638 q.iter()
639 .position(|&s| occupant[s].is_some_and(|id| !keep.contains(&id)))
640 .and_then(|pos| q.remove(pos))
641 };
642 for class_index in 0..self.classes.len() {
643 if self.classes[class_index].capacity < required {
644 continue;
645 }
646 let slot = if self.frequency_evict {
647 self.frequency_victim_in_class(class_index, keep)
648 } else {
649 take(&mut self.classes[class_index].probation, &self.occupant)
650 .or_else(|| take(&mut self.classes[class_index].protected, &self.occupant))
651 };
652 if let Some(slot) = slot {
653 self.remove_occupant(slot);
654 return Some(slot);
655 }
656 }
657 None
658 }
659
660 fn on_block_evicted(&mut self, layer: u16) {
666 if let Some(c) = self.per_layer.get_mut(&layer) { *c -= 1; }
667 self.dev_rows.remove(&layer);
668 }
669
670 fn reserve_slot(&mut self, required: usize) -> Option<usize> {
671 for class in &mut self.classes {
672 if class.capacity >= required {
673 if let Some(slot) = class.free.pop() {
674 return Some(slot);
675 }
676 }
677 }
678 self.evict_one(required)
679 }
680
681 fn release_reserved_slot(&mut self, slot: usize) {
682 debug_assert!(self.occupant[slot].is_none());
683 self.classes[self.slot_class[slot]].free.push(slot);
684 }
685
686 fn publish(&mut self, id: BlockId, slot: usize) {
687 self.occupant[slot] = Some(id);
688 self.table.insert(id, slot);
689 self.classes[self.slot_class[slot]]
690 .probation
691 .push_back(slot);
692 *self.per_layer.entry(id.layer).or_insert(0) += 1;
693 }
694
695
696
697 fn reap_copy_sources(&mut self) {
698 self.inflight_sources.retain(|(ready, _)| !ready.is_complete());
699 }
700
701 fn retain_compute_source(&mut self, owner: Option<ExpertKeepalive>) {
702 if let Some(owner) = owner {
703 let key = KeepaliveKey::from_owner(&owner);
704 self.compute_sources.entry(key).or_insert(owner);
705 }
706 }
707
708 fn admit(&mut self, id: BlockId, host_bytes: &[u8], e: &Engine)
711 -> Result<usize, Box<dyn std::error::Error>> {
712 let slot = self.reserve_slot(host_bytes.len()).ok_or_else(|| {
713 std::io::Error::other(format!(
714 "no MoE cache slot can hold {} bytes (max class {})",
715 host_bytes.len(),
716 self.classes.last().map(|class| class.capacity).unwrap_or(0)
717 ))
718 })?;
719 if let Err(err) = e.stage_expert(host_bytes, &mut self.slots[slot], 0) {
722 return match e.stream().synchronize() {
723 Ok(()) => {
724 self.release_reserved_slot(slot);
725 Err(err)
726 }
727 Err(sync_err) => {
728 self.compute_stream_unknown = true;
731 Err(std::io::Error::other(format!(
732 "compute-stream H2D setup failed ({err}); stream drain also failed ({sync_err})"
733 )).into())
734 }
735 };
736 }
737 self.staged_bytes += host_bytes.len() as u64;
738 self.publish(id, slot);
739 Ok(slot)
740 }
741
742 fn note_pread_fallback(&mut self, reason: &dyn std::fmt::Display) {
743 self.pread_fallbacks += 1;
744 if let Some(pool) = self.pread.as_mut() {
745 pool.note_fallback();
746 }
747 if self.pread_fallbacks <= 3 {
748 eprintln!("[spill-pread] falling back to mmap: {reason}");
749 }
750 }
751
752 pub(crate) fn begin_worker_scope(&mut self) {
756 if self.worker_reads.is_empty() { return; }
757 let tickets: Vec<_> = self
758 .worker_reads
759 .drain()
760 .map(|(_, read)| read.ticket)
761 .collect();
762 if let Some(pool) = self.pread.as_mut().filter(|pool| pool.is_worker()) {
763 for ticket in tickets {
764 let _ = pool.cancel_worker(ticket); }
765 }
766 }
767
768 pub(crate) fn begin_forward_epoch(&mut self, layer: u16, t: usize) {
778 let Some(decay) = self.frequency_decay else {
779 self.last_forward_layer = Some(layer);
780 self.last_forward_t = t;
781 return;
782 };
783 let new_sweep = self
784 .last_forward_layer
785 .is_some_and(|previous| layer <= previous);
786 if new_sweep && t == 1 {
787 if self.last_forward_t != 1 {
788 self.frequencies.clear();
789 } else {
790 self.frequencies.retain(|_, score| {
791 *score *= decay;
792 *score >= 1.0e-3
793 });
794 }
795 }
796 self.last_forward_layer = Some(layer);
797 self.last_forward_t = t;
798 }
799
800 pub(crate) fn promote_worker_reads_at_safe_boundary(
807 &mut self,
808 order: &[BlockId],
809 keep: &[BlockId],
810 e: &Engine,
811 ) -> Result<usize, Box<dyn std::error::Error>> {
812 if !crate::spill_pread::copy_h2d_enabled() {
813 return Ok(0);
814 }
815 let mut promoted = 0usize;
816 for &id in order {
817 if self.table.contains_key(&id) || self.pending.contains_key(&id) {
818 if let Some(read) = self.worker_reads.remove(&id) {
819 if let Some(pool) = self.pread.as_mut() {
820 let _ = pool.cancel_worker(read.ticket);
821 }
822 }
823 continue;
824 }
825 let Some(read) = self.worker_reads.get(&id).copied() else {
826 continue;
827 };
828 let Some(slot) = self.reserve_prefetch_slot(read.len, keep) else {
829 continue;
830 };
831 self.worker_reads.remove(&id);
832
833 let index = match self.pread.as_mut().unwrap().wait_worker(read.ticket) {
834 Ok(index) => index,
835 Err(err) => {
836 let _ = self.pread.as_mut().unwrap().cancel_worker(read.ticket);
837 self.release_reserved_slot(slot);
838 self.note_pread_fallback(err.as_ref());
839 continue;
840 }
841 };
842 let ready = {
843 let bytes = match self.pread.as_ref().unwrap().bytes(index, read.len) {
844 Ok(bytes) => bytes,
845 Err(err) => {
846 self.pread.as_mut().unwrap().abort_read(index);
847 self.release_reserved_slot(slot);
848 self.note_pread_fallback(err.as_ref());
849 continue;
850 }
851 };
852 stage_pread_prefetch_on_copy_stream(e, bytes, &mut self.slots[slot])
853 };
854 let ready = match ready {
855 Ok(ready) => ready,
856 Err((err, reusable)) => {
857 if reusable {
858 self.pread.as_mut().unwrap().abort_read(index);
859 self.release_reserved_slot(slot);
860 self.note_pread_fallback(err.as_ref());
861 continue;
862 }
863 self.pread.as_mut().unwrap().mark_unknown_h2d(index);
864 self.copy_stream_unknown = true;
865 return Err(err);
866 }
867 };
868 self.pread.as_mut().unwrap().mark_h2d(index, ready.clone());
869 self.occupant[slot] = Some(id);
870 self.pending.insert(
871 id,
872 PendingBlock {
873 slot,
874 ready,
875 keepalive: None,
876 },
877 );
878 self.staged_bytes += read.len as u64;
879 promoted += 1;
880 }
881 Ok(promoted)
882 }
883
884 fn dispatch_disk(
885 &mut self,
886 id: BlockId,
887 file: &Arc<std::fs::File>,
888 offset: u64,
889 len: usize,
890 fallback: &[u8],
891 e: &Engine,
892 ) -> Result<DispatchSlot, Box<dyn std::error::Error>> {
893 if self.pread.is_none() {
894 if self.pread_requested {
895 self.note_pread_fallback(&"pinned-buffer backend unavailable");
896 }
897 return Ok(DispatchSlot::Resident(self.admit(id, fallback, e)?));
898 }
899
900 let pending = self.worker_reads.remove(&id);
901 let pool = self.pread.as_mut().unwrap();
902 let read = if pool.is_worker() {
903 let ticket = match pending {
904 Some(read) => Ok(Some(read.ticket)),
905 None => pool.submit_worker(file.clone(), offset, len),
906 };
907 match ticket {
908
909 Ok(Some(ticket)) => match pool.wait_worker(ticket) {
910 Ok(index) => Ok(index),
911 Err(err) => {
912 let _ = pool.cancel_worker(ticket);
915 Err(err)
916 }
917 },
918 Ok(None) => Err(std::io::Error::other("worker read ring is busy").into()),
919 Err(err) => Err(err),
920 }
921 } else {
922 debug_assert!(pending.is_none());
923 pool.read(file.as_ref(), offset, len)
924 };
925 let index = match read {
926 Ok(index) => index,
927 Err(err) => {
928 self.note_pread_fallback(err.as_ref());
929 return Ok(DispatchSlot::Resident(self.admit(id, fallback, e)?));
930 }
931 };
932
933
934 let slot = self.reserve_slot(len).ok_or_else(|| {
937 std::io::Error::other(format!(
938 "no MoE cache slot can hold {len} bytes (max class {})",
939 self.classes.last().map(|class| class.capacity).unwrap_or(0)
940 ))
941 })?;
942 let ready = {
943 let bytes = match self.pread.as_ref().unwrap().bytes(index, len) {
944 Ok(bytes) => bytes,
945 Err(err) => {
946 self.pread.as_mut().unwrap().abort_read(index);
947 self.release_reserved_slot(slot);
948 self.note_pread_fallback(err.as_ref());
949 return Ok(DispatchSlot::Resident(self.admit(id, fallback, e)?));
950 }
951 };
952 stage_pread_on_compute_stream(e, bytes, &mut self.slots[slot])
953 };
954 let ready = match ready {
955 Ok(ready) => ready,
956 Err(err) => {
957 match e.stream().synchronize() {
961 Ok(()) => {
962 self.pread.as_mut().unwrap().abort_read(index);
963 self.release_reserved_slot(slot);
964 self.note_pread_fallback(err.as_ref());
965 return Ok(DispatchSlot::Resident(self.admit(id, fallback, e)?));
966 }
967 Err(sync_err) => {
968 self.pread.as_mut().unwrap().mark_unknown_h2d(index);
969 return Err(std::io::Error::other(format!(
970 "pread H2D setup failed ({err}); CUDA stream drain also failed ({sync_err})"
971 )).into());
972 }
973 }
974 }
975 };
976 self.pread.as_mut().unwrap().mark_h2d(index, ready);
977 self.staged_bytes += len as u64;
980 self.publish(id, slot);
981 Ok(DispatchSlot::Resident(slot))
982 }
983
984 pub fn dispatch(&mut self, id: BlockId, host_bytes: &[u8], e: &Engine)
992 -> Result<DispatchSlot, Box<dyn std::error::Error>> {
993 self.dispatch_source(id, ExpertSource::Memory { bytes: host_bytes, keepalive: None }, e)
994 }
995
996 pub(crate) fn dispatch_source(&mut self, id: BlockId, source: ExpertSource<'_>, e: &Engine)
997 -> Result<DispatchSlot, Box<dyn std::error::Error>> {
998 self.reap_copy_sources();
999 let increment = self.frequency_increment(id);
1000 *self.frequencies.entry(id).or_insert(0.0) += increment;
1001 if let Some(s) = self.table.get(&id).copied() {
1002 self.hits += 1;
1003 self.on_hit(s);
1004 return Ok(DispatchSlot::Resident(s));
1005 }
1006 if let Some(pending) = self.pending.remove(&id) {
1007 if let Err(err) = e.compute_wait(pending.ready.as_ref()) {
1008 self.pending.insert(id, pending);
1009 return Err(err);
1010 }
1011 self.misses += 1;
1012 let slot = pending.slot;
1013 if let Some(keepalive) = pending.keepalive {
1014 self.inflight_sources.push((pending.ready, keepalive));
1015 }
1016 self.publish(id, slot);
1017 return Ok(DispatchSlot::Resident(slot));
1018 }
1019 self.misses += 1;
1020 match source {
1030 ExpertSource::Memory { bytes, keepalive } => {
1031 self.retain_compute_source(keepalive);
1034 let slot = self.admit(id, bytes, e)?;
1035 Ok(DispatchSlot::Resident(slot))
1036 }
1037 ExpertSource::Disk { file, offset, len, fallback, keepalive } => {
1038 self.retain_compute_source(Some(keepalive));
1041 self.dispatch_disk(id, file, offset, len, fallback, e)
1042 }
1043 }
1044 }
1045
1046 pub fn prefetch(&mut self, id: BlockId, host_bytes: &[u8], keep: &[BlockId], e: &Engine)
1055 -> Result<bool, Box<dyn std::error::Error>> {
1056 self.prefetch_source(
1057 id,
1058 ExpertSource::Memory { bytes: host_bytes, keepalive: None },
1059 keep,
1060 e,
1061 )
1062 }
1063
1064 fn reserve_prefetch_slot(&mut self, required: usize, keep: &[BlockId]) -> Option<usize> {
1065 for class in &mut self.classes {
1066 if class.capacity >= required {
1067 if let Some(slot) = class.free.pop() {
1068 return Some(slot);
1069 }
1070 }
1071 }
1072 self.evict_one_excluding(required, keep)
1073 }
1074
1075 fn prefetch_bytes(
1076&mut self, id: BlockId, host_bytes: &[u8],
1077 keepalive: Option<ExpertKeepalive>, keep: &[BlockId], e: &Engine)
1078 -> Result<bool, Box<dyn std::error::Error>> {
1079 let Some(slot) = self.reserve_prefetch_slot(host_bytes.len(), keep) else { return Ok(false) };
1080 let ready = match stage_on_copy_stream(e, host_bytes, &mut self.slots[slot]) {
1081 Ok(ready) => ready,
1082 Err((err, reusable)) => {
1083 if reusable {
1084 self.release_reserved_slot(slot);
1085 } else {
1086 self.copy_stream_unknown = true;
1089 if let Some(keepalive) = keepalive {
1090 self.quarantined_sources.push(keepalive);
1091 }
1092 eprintln!("[moe-cache] quarantining slot {slot} after unprovable copy completion");
1093 }
1094 return Err(err);
1095 }
1096 };
1097 self.occupant[slot] = Some(id);
1098 self.pending.insert(id, PendingBlock { slot, ready, keepalive });
1099 self.staged_bytes += host_bytes.len() as u64;
1100 Ok(true)
1101 }
1102
1103 pub(crate) fn prefetch_source(
1104 &mut self,
1105 id: BlockId,
1106 source: ExpertSource<'_>,
1107 keep: &[BlockId],
1108 e: &Engine,
1109 ) -> Result<bool, Box<dyn std::error::Error>> {
1110 self.reap_copy_sources();
1111 if self.table.contains_key(&id) || self.pending.contains_key(&id)
1112 || self.worker_reads.contains_key(&id) {
1113 return Ok(false);
1114 }
1115 match source {
1116 ExpertSource::Memory { bytes, keepalive } => {
1117 self.prefetch_bytes(id, bytes, keepalive, keep, e)
1118 }
1119 ExpertSource::Disk { file, offset, len, fallback, keepalive } => {
1120 if self.pread.as_ref().is_some_and(PreadPool::is_worker) {
1121 match self.pread.as_mut().unwrap()
1122 .submit_worker_speculative(file.clone(), offset, len) {
1123 Ok(Some(ticket)) => {
1124 self.worker_reads.insert(id, WorkerRead { ticket, len });
1125 Ok(true)
1126 }
1127 Ok(None) => Ok(false),
1128 Err(err) => {
1129 self.note_pread_fallback(err.as_ref());
1130 Ok(false)
1131 }
1132 }
1133 } else if self.pread.is_some() {
1134 Ok(false)
1136 } else {
1137 self.prefetch_bytes(id, fallback, Some(keepalive), keep, e)
1138 }
1139 }
1140 }
1141 }
1142
1143 pub fn force_admit(&mut self, id: BlockId, host_bytes: &[u8], e: &Engine)
1145 -> Result<usize, Box<dyn std::error::Error>> {
1146 if let Some(s) = self.table.get(&id).copied() { return Ok(s); }
1147 self.admit(id, host_bytes, e)
1148 }
1149
1150 pub fn export_residency(&self) -> Vec<(u16, u8, u16)> {
1158 self.occupant
1159 .iter()
1160 .flatten()
1161 .map(|id| (id.layer, id.proj, id.ex))
1162 .collect()
1163 }
1164
1165 pub fn restage_block(&mut self, id: BlockId, m: &crate::hybrid::MoeWeights, e: &Engine)
1170 -> Result<bool, Box<dyn std::error::Error>> {
1171 if self.table.contains_key(&id) {
1172 return Ok(true);
1173 }
1174 let exps = match id.proj {
1175 PROJ_GATE => &m.gate_exps,
1176 PROJ_UP => &m.up_exps,
1177 PROJ_DOWN => &m.down_exps,
1178 _ => return Ok(false),
1179 };
1180 if id.ex as usize >= exps.n_expert {
1181 return Ok(false);
1182 }
1183 if m.active_experts.as_ref().is_some_and(|active| !active[id.ex as usize]) {
1184 return Ok(false);
1185 }
1186 if exps.expert_layout(id.ex as usize).len == 0 {
1187 return Ok(false);
1188 }
1189 match exps.expert_source(id.ex as usize) {
1190 ExpertSource::Memory { bytes, keepalive } => {
1191 self.retain_compute_source(keepalive);
1192 self.admit(id, bytes, e)?;
1193 }
1194 ExpertSource::Disk { fallback, keepalive, .. } => {
1195 self.retain_compute_source(Some(keepalive));
1196 self.admit(id, fallback, e)?;
1197 }
1198 }
1199 Ok(true)
1200 }
1201
1202 pub fn prewarm_layer(&mut self, layer: u16, m: &crate::hybrid::MoeWeights, e: &Engine)
1203 -> Result<(), Box<dyn std::error::Error>> {
1204 if !self.prewarm_tried.insert(layer) { return Ok(()); }
1205 let n_expert = m.gate_exps.n_expert;
1206 if self.pread.is_some() && (0..n_expert).any(|ex| {
1207 matches!(m.gate_exps.expert_source(ex), ExpertSource::Disk { .. })
1208 || matches!(m.up_exps.expert_source(ex), ExpertSource::Disk { .. })
1209 || matches!(m.down_exps.expert_source(ex), ExpertSource::Disk { .. })
1210 }) {
1211 return Ok(());
1214 }
1215 let resident = self.per_layer.get(&layer).copied().unwrap_or(0) as usize;
1216 let missing = 3 * n_expert - resident;
1217 if self.size_aware {
1218 return Ok(());
1219 } if self
1221 .classes
1222 .iter()
1223 .map(|class| class.free.len())
1224 .sum::<usize>()
1225 < missing
1226 {
1227 return Ok(()); }
1229 for ex in 0..n_expert {
1230 for (proj, exps) in [
1231 (PROJ_GATE, &m.gate_exps),
1232 (PROJ_UP, &m.up_exps),
1233 (PROJ_DOWN, &m.down_exps)] {
1234 let id = BlockId::new(layer, proj, ex as u16);
1235 if self.table.contains_key(&id) { continue; }
1236 match exps.expert_source(ex) {
1237 ExpertSource::Memory { bytes, keepalive } => {
1238 self.retain_compute_source(keepalive);
1239 self.admit(id, bytes, e)?;
1240 }
1241 ExpertSource::Disk { fallback, keepalive, .. } => {
1242 self.retain_compute_source(Some(keepalive));
1243 self.admit(id, fallback, e)?;
1244 }
1245 }
1246 }
1247 }
1248 Ok(())
1249 }
1250
1251 pub fn layer_dev_row(&mut self, layer: u16, n_expert: usize, e: &Engine)
1257 -> Result<Option<&CudaSlice<u64>>, Box<dyn std::error::Error>> {
1258 if self.per_layer.get(&layer).copied().unwrap_or(0) as usize != 3 * n_expert {
1259 return Ok(None);
1260 }
1261 if !self.dev_rows.contains_key(&layer) {
1262 use cudarc::driver::DevicePtr;
1263 let mut host = vec![0u64; 3 * n_expert];
1264 for proj in 0..3u8 {
1265 for ex in 0..n_expert {
1266 let Some(&s) = self.table.get(&BlockId::new(layer, proj, ex as u16)) else {
1267 return Ok(None);
1269 };
1270 let __s_ev = e.stream();
1271 let (p, _ev) = self.slots[s].device_ptr(&__s_ev);
1272 host[proj as usize * n_expert + ex] = p as u64;
1273 }
1274 }
1275 let row = e.stream().clone_htod(&host)?;
1276 self.dev_rows.insert(layer, row);
1277 }
1278 Ok(self.dev_rows.get(&layer))
1279 }
1280
1281 #[inline]
1283 pub fn buf(&self, d: DispatchSlot) -> &CudaSlice<u8> {
1284 match d {
1285 DispatchSlot::Resident(s) => &self.slots[s],
1286 }
1287 }
1288
1289 #[inline]
1291 pub fn slot(&self, s: usize) -> &CudaSlice<u8> { &self.slots[s] }
1292
1293 pub fn hit_rate(&self) -> f64 {
1295 let tot = self.hits + self.misses;
1296 if tot == 0 { 0.0 } else { self.hits as f64 / tot as f64 }
1297 }
1298
1299 pub fn reset_counters(&mut self) {
1301 self.hits = 0; self.misses = 0; self.staged_bytes = 0;
1302 }
1303
1304 pub(crate) fn pread_stats(&self) -> Option<PreadStats> {
1305 if !self.pread_requested { return None; }
1306 let mut stats = self.pread.as_ref().map(PreadPool::stats).unwrap_or_default();
1307 stats.fallbacks = self.pread_fallbacks;
1308 Some(stats)
1309 }
1310}
1311
1312fn cache_lfu_decay() -> Option<f32> {
1313 let raw = std::env::var("MEMRA_MOE_LFU_DECAY").ok()?;
1314 match parse_cache_lfu_decay(Some(&raw)) {
1315 Ok(value) => value,
1316 Err(reason) => {
1317 eprintln!(
1318 "[moe-cache] invalid MEMRA_MOE_LFU_DECAY={raw:?} ({reason}); disabling LFU decay"
1319 );
1320 None
1321 }
1322 }
1323}
1324
1325fn cache_lfu_mtp_weight() -> f32 {
1326 const DEFAULT: f32 = 1.0;
1327 let raw = std::env::var("MEMRA_MOE_LFU_MTP_WEIGHT").ok();
1328 match parse_cache_lfu_mtp_weight(raw.as_deref()) {
1329 Ok(value) => value,
1330 Err(reason) => {
1331 eprintln!(
1332 "[moe-cache] invalid MEMRA_MOE_LFU_MTP_WEIGHT={:?} ({reason}); using {DEFAULT}",
1333 raw.as_deref().unwrap_or("")
1334 );
1335 DEFAULT
1336 }
1337 }
1338}
1339
1340fn parse_cache_lfu_mtp_weight(raw: Option<&str>) -> Result<f32, &'static str> {
1341 let value = raw
1342 .unwrap_or("1")
1343 .parse::<f32>()
1344 .map_err(|_| "expected a number")?;
1345 if value.is_finite() && (0.25..=64.0).contains(&value) {
1346 Ok(value)
1347 } else {
1348 Err("expected a finite multiplier from 0.25 through 64")
1349 }
1350}
1351
1352fn parse_cache_lfu_decay(raw: Option<&str>) -> Result<Option<f32>, &'static str> {
1353 let Some(raw) = raw else { return Ok(None) };
1354 let value = raw.parse::<f32>().map_err(|_| "expected a number")?;
1355 if value.is_finite() && value > 0.0 && value <= 1.0 {
1356 Ok(Some(value))
1357 } else {
1358 Err("expected a finite fraction greater than 0 and at most 1")
1359 }
1360}
1361
1362fn cache_hard_vram_frac() -> f64 {
1363 const DEFAULT: f64 = 0.80;
1364 let raw = std::env::var("MEMRA_MOE_HARD_VRAM_FRAC").ok();
1365 match parse_cache_hard_vram_frac(raw.as_deref()) {
1366 Ok(value) => value,
1367 Err(reason) => {
1368 eprintln!(
1369 "[moe-cache] invalid MEMRA_MOE_HARD_VRAM_FRAC={:?} ({reason}); using {DEFAULT}",
1370 raw.as_deref().unwrap_or("")
1371 );
1372 DEFAULT
1373 }
1374 }
1375}
1376
1377fn parse_cache_hard_vram_frac(raw: Option<&str>) -> Result<f64, &'static str> {
1378 let value = raw
1379 .unwrap_or("0.80")
1380 .parse::<f64>()
1381 .map_err(|_| "expected a number")?;
1382 if value.is_finite() && (0.10..=0.95).contains(&value) {
1383 Ok(value)
1384 } else {
1385 Err("expected a finite fraction from 0.10 through 0.95")
1386 }
1387}
1388
1389#[cfg(test)]
1390mod vram_fraction_tests {
1391 use super::{
1392 parse_cache_hard_vram_frac, parse_cache_lfu_decay, parse_cache_lfu_mtp_weight,
1393 size_class_plan,
1394 };
1395
1396 #[test]
1397 fn hard_vram_fraction_defaults_and_rejects_unsafe_values() {
1398 assert_eq!(parse_cache_hard_vram_frac(None), Ok(0.80));
1399 assert_eq!(parse_cache_hard_vram_frac(Some("0.82")), Ok(0.82));
1400 assert!(parse_cache_hard_vram_frac(Some("NaN")).is_err());
1401 assert_eq!(parse_cache_hard_vram_frac(Some("0.95")), Ok(0.95));
1402 assert!(parse_cache_hard_vram_frac(Some("0.96")).is_err());
1403 assert!(parse_cache_hard_vram_frac(Some("1.0")).is_err());
1404 assert!(parse_cache_hard_vram_frac(Some("bad")).is_err());
1405 }
1406
1407 #[test]
1408 fn lfu_decay_is_opt_in_and_bounded() {
1409 assert_eq!(parse_cache_lfu_decay(None), Ok(None));
1410 assert_eq!(parse_cache_lfu_decay(Some("0.8")), Ok(Some(0.8)));
1411 assert_eq!(parse_cache_lfu_decay(Some("1")), Ok(Some(1.0)));
1412 for value in ["0", "-0.1", "1.1", "NaN", "bad"] {
1413 assert!(
1414 parse_cache_lfu_decay(Some(value)).is_err(),
1415 "accepted {value}"
1416 );
1417 }
1418 }
1419
1420 #[test]
1421 fn lfu_mtp_weight_defaults_and_is_bounded() {
1422 assert_eq!(parse_cache_lfu_mtp_weight(None), Ok(1.0));
1423 assert_eq!(parse_cache_lfu_mtp_weight(Some("4")), Ok(4.0));
1424 for value in ["0", "0.1", "65", "NaN", "bad"] {
1425 assert!(
1426 parse_cache_lfu_mtp_weight(Some(value)).is_err(),
1427 "accepted {value}"
1428 );
1429 }
1430 }
1431
1432 #[test]
1433 fn size_class_plan_preserves_classes_and_never_exceeds_budget() {
1434 let blocks = [100usize, 100, 100, 200, 200, 400];
1435 let budget = (108 * 2) + 208 + 408;
1436 let plan = size_class_plan(&blocks, budget);
1437 assert!(plan.iter().all(|(_, count)| *count > 0));
1438 assert!(
1439 plan.iter()
1440 .map(|(bytes, count)| (bytes + 8) * count)
1441 .sum::<usize>()
1442 <= budget
1443 );
1444 assert!(plan.iter().all(|(bytes, count)| {
1445 *count <= blocks.iter().filter(|block| **block == *bytes).count()
1446 }));
1447 }
1448
1449 #[test]
1450 fn size_class_plan_returns_full_inventory_when_it_fits() {
1451 let blocks = [100usize, 100, 200, 400];
1452 let budget: usize = blocks.iter().map(|bytes| bytes + 8).sum();
1453 assert_eq!(
1454 size_class_plan(&blocks, budget),
1455 vec![(100, 2), (200, 1), (400, 1)]
1456 );
1457 }
1458
1459 #[test]
1460 fn size_class_plan_does_not_overflow_on_pathological_sizes() {
1461 let plan = size_class_plan(&[usize::MAX, usize::MAX], usize::MAX);
1462 assert!(plan.is_empty());
1463 }
1464
1465}
1466
1467impl Drop for MoeSlotCache {
1468 fn drop(&mut self) {
1469 let mut safe_to_drop_slots = true;
1472 if self.compute_stream_unknown || !self.compute_sources.is_empty() {
1473 if let Err(err) = self.compute_stream.synchronize() {
1474 safe_to_drop_slots = false;
1475 eprintln!("[moe-cache] unknown compute-stream drain failed ({err}); leaking GPU slots for safety");
1476 for (_, keepalive) in self.compute_sources.drain() {
1477 std::mem::forget(keepalive);
1478 }
1479 } else {
1480 self.compute_stream_unknown = false;
1481 self.compute_sources.clear();
1482 }
1483 }
1484 let need_copy_drain = self.copy_stream_unknown || !self.pending.is_empty()
1485 || !self.inflight_sources.is_empty() || !self.quarantined_sources.is_empty();
1486 if need_copy_drain {
1487 if let Err(err) = self.copy_stream.synchronize() {
1488 safe_to_drop_slots = false;
1489 eprintln!("[moe-cache] unknown copy-stream drain failed ({err}); leaking GPU slots for safety");
1490 for (_, keepalive) in self.inflight_sources.drain(..) {
1491 std::mem::forget(keepalive);
1492 }
1493 for keepalive in self.quarantined_sources.drain(..) {
1494 std::mem::forget(keepalive);
1495 }
1496 for (_, pending) in self.pending.drain() {
1497 if let Some(keepalive) = pending.keepalive {
1498 std::mem::forget(keepalive);
1499 }
1500 }
1501 } else {
1502 self.copy_stream_unknown = false;
1503 self.inflight_sources.clear();
1504 self.quarantined_sources.clear();
1505 self.pending.clear();
1506 }
1507 }
1508 if let Some(pool) = self.pread.as_mut() {
1509 safe_to_drop_slots &= pool.drain();
1510 } else if self.pread_requested && self.pread_fallbacks != 0 {
1511 eprintln!(
1512 "[spill-pread] backend unavailable; mmap_fallbacks={}",
1513 self.pread_fallbacks
1514 );
1515 }
1516 if !safe_to_drop_slots {
1517 for slot in self.slots.drain(..) {
1518 std::mem::forget(slot);
1519 }
1520 }
1521 }
1522}