use crate::expert_cache::{CopyPlan, ExpertId, GatherPlan};
use crate::residency::{BankResidency, CopyRoute, ResidencyError};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SlotGeometry {
pub num_layers: usize,
pub slots: usize,
pub row_bytes: Vec<usize>,
}
impl SlotGeometry {
pub fn banks(&self) -> usize {
self.row_bytes.len()
}
pub fn bytes(&self) -> u64 {
self.row_bytes
.iter()
.map(|b| *b as u64 * self.slots as u64)
.sum()
}
}
pub trait SlotDevice {
fn begin_plan(&mut self, route: CopyRoute) -> Result<(), String> {
let _ = route;
Ok(())
}
fn write_slot(&mut self, bank: usize, dst_slot: u32, src: &[u8]) -> Result<(), String>;
fn copy_slot(&mut self, bank: usize, dst_slot: u32, src_slot: u32) -> Result<(), String>;
fn flush(&mut self) -> Result<(), String> {
Ok(())
}
}
pub trait ExpertRows {
fn row(&self, bank: usize, layer: u32, row: u32) -> Option<&[u8]>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Applied {
pub rows: u64,
pub bytes: u64,
pub warm: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct SlotStats {
pub plans: u64,
pub warm_plans: u64,
pub host_rows: u64,
pub host_bytes: u64,
pub device_rows: u64,
pub device_bytes: u64,
}
impl SlotStats {
pub fn warm_plan_rate(&self) -> f64 {
if self.plans == 0 {
return 0.0;
}
self.warm_plans as f64 / self.plans as f64
}
pub fn host_bytes_per_plan(&self) -> f64 {
if self.plans == 0 {
return 0.0;
}
self.host_bytes as f64 / self.plans as f64
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SlotFault {
PlanHalvesDisagree { dst_slots: usize, src_rows: usize },
SlotOutOfRange { slot: u32, slots: usize },
SlotWrittenTwice { slot: u32 },
LayerOutOfRange { layer: u32, num_layers: usize },
RowMissing { bank: usize, layer: u32, row: u32 },
RowSizeMismatch {
bank: usize,
layer: u32,
row: u32,
expected: usize,
got: usize,
},
Residency(ResidencyError),
Device {
bank: usize,
slot: u32,
detail: String,
},
DeviceFlush { slots: Vec<u32>, detail: String },
}
impl std::fmt::Display for SlotFault {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SlotFault::PlanHalvesDisagree {
dst_slots,
src_rows,
} => write!(
f,
"copy plan has {dst_slots} destination slots and {src_rows} source rows: the \
pairs do not line up, so applying it would load experts into each other's slots"
),
SlotFault::SlotOutOfRange { slot, slots } => write!(
f,
"plan names slot {slot} but the pool has {slots}: the planner and the pool were \
built with different cache sizes"
),
SlotFault::SlotWrittenTwice { slot } => write!(
f,
"plan writes slot {slot} twice: one of the two experts would be absent from the \
slot the plan promised it in"
),
SlotFault::LayerOutOfRange { layer, num_layers } => write!(
f,
"plan names layer {layer} but the pool was built for {num_layers}"
),
SlotFault::RowMissing { bank, layer, row } => {
write!(f, "bank {bank} has no row {row} for layer {layer}")
}
SlotFault::RowSizeMismatch {
bank,
layer,
row,
expected,
got,
} => write!(
f,
"bank {bank} row {row} of layer {layer} is {got} bytes, not {expected}: a \
short row would leave the slot's tail holding the previous occupant"
),
SlotFault::Residency(e) => write!(f, "{e}"),
SlotFault::Device { bank, slot, detail } => write!(
f,
"device refused the copy into bank {bank} slot {slot}: {detail}"
),
SlotFault::DeviceFlush { slots, detail } => write!(
f,
"device failed to complete {} deferred cop{}: {detail}; slots {slots:?} are all \
suspect, since the backend cannot say which landed",
slots.len(),
if slots.len() == 1 { "y" } else { "ies" },
),
}
}
}
impl std::error::Error for SlotFault {}
impl From<ResidencyError> for SlotFault {
fn from(e: ResidencyError) -> Self {
SlotFault::Residency(e)
}
}
pub struct ExpertSlots {
geometry: SlotGeometry,
residency: BankResidency,
occupant: Vec<Option<ExpertId>>,
stats: SlotStats,
}
impl ExpertSlots {
pub fn new(geometry: SlotGeometry) -> Result<Self, SlotFault> {
if geometry.slots == 0 {
return Err(SlotFault::SlotOutOfRange { slot: 0, slots: 0 });
}
if geometry.num_layers == 0 {
return Err(SlotFault::LayerOutOfRange {
layer: 0,
num_layers: 0,
});
}
for (bank, bytes) in geometry.row_bytes.iter().enumerate() {
if *bytes == 0 {
return Err(SlotFault::RowSizeMismatch {
bank,
layer: 0,
row: 0,
expected: 0,
got: 0,
});
}
}
let residency = BankResidency::all_pinned(geometry.num_layers);
Ok(ExpertSlots {
occupant: vec![None; geometry.slots],
geometry,
residency,
stats: SlotStats::default(),
})
}
pub fn with_residency(mut self, residency: BankResidency) -> Self {
self.residency = residency;
self
}
pub fn geometry(&self) -> &SlotGeometry {
&self.geometry
}
pub fn stats(&self) -> SlotStats {
self.stats
}
pub fn reset_stats(&mut self) {
self.stats = SlotStats::default();
}
pub fn occupant(&self, slot: u32) -> Option<ExpertId> {
self.occupant.get(slot as usize).copied().flatten()
}
pub fn occupied(&self) -> usize {
self.occupant.iter().filter(|o| o.is_some()).count()
}
pub fn invalidate_slot(&mut self, slot: u32) {
if let Some(o) = self.occupant.get_mut(slot as usize) {
*o = None;
}
}
pub fn invalidate_all(&mut self) {
self.occupant.iter_mut().for_each(|o| *o = None);
}
pub fn resize(&mut self, slots: usize) -> Result<(), SlotFault> {
if slots == 0 {
return Err(SlotFault::SlotOutOfRange { slot: 0, slots: 0 });
}
self.geometry.slots = slots;
self.occupant = vec![None; slots];
Ok(())
}
pub fn apply_copy_plan(
&mut self,
layer: u32,
plan: &CopyPlan,
rows: &dyn ExpertRows,
device: &mut dyn SlotDevice,
) -> Result<Applied, SlotFault> {
self.apply_plan(layer, plan, false, rows, device)
}
pub fn apply_materialize(
&mut self,
layer: u32,
plan: &CopyPlan,
rows: &dyn ExpertRows,
device: &mut dyn SlotDevice,
) -> Result<Applied, SlotFault> {
self.apply_plan(layer, plan, true, rows, device)
}
fn apply_plan(
&mut self,
layer: u32,
plan: &CopyPlan,
whole_layer: bool,
rows: &dyn ExpertRows,
device: &mut dyn SlotDevice,
) -> Result<Applied, SlotFault> {
if plan.dst_slots.len() != plan.src_rows.len() {
return Err(SlotFault::PlanHalvesDisagree {
dst_slots: plan.dst_slots.len(),
src_rows: plan.src_rows.len(),
});
}
if layer as usize >= self.geometry.num_layers {
return Err(SlotFault::LayerOutOfRange {
layer,
num_layers: self.geometry.num_layers,
});
}
let route = self.residency.copy_route(layer, whole_layer)?;
self.validate_slots(&plan.dst_slots)?;
self.validate_rows(layer, &plan.src_rows, rows)?;
self.stats.plans += 1;
if plan.is_empty() {
self.stats.warm_plans += 1;
return Ok(Applied {
rows: 0,
bytes: 0,
warm: true,
});
}
device
.begin_plan(route)
.map_err(|detail| SlotFault::DeviceFlush {
slots: plan.dst_slots.clone(),
detail,
})?;
let mut applied = Applied::default();
for (&dst, &src) in plan.dst_slots.iter().zip(plan.src_rows.iter()) {
self.occupant[dst as usize] = None;
for bank in 0..self.geometry.banks() {
let bytes = rows
.row(bank, layer, src)
.expect("validated by validate_rows");
device
.write_slot(bank, dst, bytes)
.map_err(|detail| SlotFault::Device {
bank,
slot: dst,
detail,
})?;
applied.rows += 1;
applied.bytes += bytes.len() as u64;
}
self.occupant[dst as usize] = Some(ExpertId { layer, expert: src });
}
self.flush(device, &plan.dst_slots)?;
self.stats.host_rows += applied.rows;
self.stats.host_bytes += applied.bytes;
Ok(applied)
}
fn flush(&mut self, device: &mut dyn SlotDevice, written: &[u32]) -> Result<(), SlotFault> {
let Err(detail) = device.flush() else {
return Ok(());
};
for &slot in written {
self.invalidate_slot(slot);
}
Err(SlotFault::DeviceFlush {
slots: written.to_vec(),
detail,
})
}
pub fn apply_gather_plan(
&mut self,
plan: &GatherPlan,
device: &mut dyn SlotDevice,
) -> Result<Applied, SlotFault> {
if plan.dst_slots.len() != plan.src_slots.len() {
return Err(SlotFault::PlanHalvesDisagree {
dst_slots: plan.dst_slots.len(),
src_rows: plan.src_slots.len(),
});
}
self.validate_slots(&plan.dst_slots)?;
for &src in &plan.src_slots {
if src as usize >= self.geometry.slots {
return Err(SlotFault::SlotOutOfRange {
slot: src,
slots: self.geometry.slots,
});
}
if self.occupant(src).is_none() {
return Err(SlotFault::Device {
bank: 0,
slot: src,
detail: "gather source holds no known expert; a failed copy would be \
propagated into a second slot"
.to_string(),
});
}
}
self.stats.plans += 1;
if plan.is_empty() {
self.stats.warm_plans += 1;
return Ok(Applied {
rows: 0,
bytes: 0,
warm: true,
});
}
let mut applied = Applied::default();
for (&dst, &src) in plan.dst_slots.iter().zip(plan.src_slots.iter()) {
let carried = self.occupant(src);
self.occupant[dst as usize] = None;
for bank in 0..self.geometry.banks() {
device
.copy_slot(bank, dst, src)
.map_err(|detail| SlotFault::Device {
bank,
slot: dst,
detail,
})?;
applied.rows += 1;
applied.bytes += self.geometry.row_bytes[bank] as u64;
}
self.occupant[dst as usize] = carried;
}
self.flush(device, &plan.dst_slots)?;
self.stats.device_rows += applied.rows;
self.stats.device_bytes += applied.bytes;
Ok(applied)
}
fn validate_slots(&self, slots: &[u32]) -> Result<(), SlotFault> {
for (i, &slot) in slots.iter().enumerate() {
if slot as usize >= self.geometry.slots {
return Err(SlotFault::SlotOutOfRange {
slot,
slots: self.geometry.slots,
});
}
if slots[..i].contains(&slot) {
return Err(SlotFault::SlotWrittenTwice { slot });
}
}
Ok(())
}
fn validate_rows(
&self,
layer: u32,
src_rows: &[u32],
rows: &dyn ExpertRows,
) -> Result<(), SlotFault> {
for &row in src_rows {
for (bank, &expected) in self.geometry.row_bytes.iter().enumerate() {
let Some(bytes) = rows.row(bank, layer, row) else {
return Err(SlotFault::RowMissing { bank, layer, row });
};
if bytes.len() != expected {
return Err(SlotFault::RowSizeMismatch {
bank,
layer,
row,
expected,
got: bytes.len(),
});
}
}
}
Ok(())
}
}
pub struct HostSlotMemory {
banks: Vec<Vec<u8>>,
row_bytes: Vec<usize>,
}
impl HostSlotMemory {
pub fn new(geometry: &SlotGeometry) -> Self {
HostSlotMemory {
banks: geometry
.row_bytes
.iter()
.map(|b| vec![0u8; b * geometry.slots])
.collect(),
row_bytes: geometry.row_bytes.clone(),
}
}
pub fn slot(&self, bank: usize, slot: u32) -> &[u8] {
let w = self.row_bytes[bank];
let at = w * slot as usize;
&self.banks[bank][at..at + w]
}
}
impl SlotDevice for HostSlotMemory {
fn write_slot(&mut self, bank: usize, dst_slot: u32, src: &[u8]) -> Result<(), String> {
let w = self.row_bytes[bank];
let at = w * dst_slot as usize;
self.banks[bank][at..at + w].copy_from_slice(src);
Ok(())
}
fn copy_slot(&mut self, bank: usize, dst_slot: u32, src_slot: u32) -> Result<(), String> {
let w = self.row_bytes[bank];
let (dst, src) = (w * dst_slot as usize, w * src_slot as usize);
self.banks[bank].copy_within(src..src + w, dst);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::expert_cache::ExpertCache;
use crate::residency::HostResidency;
const LAYERS: usize = 3;
const EXPERTS: usize = 8;
const GATE: usize = 6;
const UP: usize = 6;
const DOWN: usize = 4;
fn geometry(slots: usize) -> SlotGeometry {
SlotGeometry {
num_layers: LAYERS,
slots,
row_bytes: vec![GATE, UP, DOWN],
}
}
struct NamedRows {
banks: Vec<Vec<Vec<u8>>>,
}
impl NamedRows {
fn new() -> Self {
let banks = [GATE, UP, DOWN]
.iter()
.enumerate()
.map(|(bank, &width)| {
(0..LAYERS as u32)
.flat_map(|layer| {
(0..EXPERTS as u32).map(move |row| named_row(bank, width, layer, row))
})
.collect()
})
.collect();
NamedRows { banks }
}
fn expected(&self, bank: usize, layer: u32, row: u32) -> &[u8] {
&self.banks[bank][layer as usize * EXPERTS + row as usize]
}
}
fn named_row(bank: usize, width: usize, layer: u32, row: u32) -> Vec<u8> {
(0..width)
.map(|i| {
(bank as u8 + 1)
.wrapping_mul(37)
.wrapping_add(layer as u8)
.wrapping_add((row as u8) << 3)
^ i as u8
})
.collect()
}
impl ExpertRows for NamedRows {
fn row(&self, bank: usize, layer: u32, row: u32) -> Option<&[u8]> {
if bank >= self.banks.len() || layer as usize >= LAYERS || row as usize >= EXPERTS {
return None;
}
Some(self.expected(bank, layer, row))
}
}
#[test]
fn a_step_that_hits_the_cache_copies_nothing() {
let mut cache = ExpertCache::new(LAYERS, EXPERTS, 32);
let mut slots = ExpertSlots::new(geometry(32)).unwrap();
let rows = NamedRows::new();
let mut device = HostSlotMemory::new(slots.geometry());
let routed = [1u32, 4, 6];
let cold = cache.ensure(0, &routed);
let first = slots
.apply_copy_plan(0, &cold.copy, &rows, &mut device)
.unwrap();
assert!(!first.warm);
assert_eq!(first.rows, 3 * 3, "three experts across three banks");
assert_eq!(first.bytes as usize, 3 * (GATE + UP + DOWN));
let before = slots.stats();
for _ in 0..10 {
let warm = cache.ensure(0, &routed);
assert!(warm.copy.is_empty(), "the cache should report every hit");
let applied = slots
.apply_copy_plan(0, &warm.copy, &rows, &mut device)
.unwrap();
assert!(applied.warm);
assert_eq!(applied.bytes, 0);
}
let after = slots.stats();
assert_eq!(
after.host_bytes, before.host_bytes,
"ten warm steps moved bytes"
);
assert_eq!(after.warm_plans, before.warm_plans + 10);
assert_eq!(after.warm_plan_rate(), 10.0 / 11.0);
}
#[test]
fn every_slot_holds_the_expert_the_plan_promised() {
let mut cache = ExpertCache::new(LAYERS, EXPERTS, 16);
let mut slots = ExpertSlots::new(geometry(16)).unwrap();
let rows = NamedRows::new();
let mut device = HostSlotMemory::new(slots.geometry());
for layer in 0..LAYERS as u32 {
let routed: Vec<u32> = (0..4).map(|e| (e + layer) % EXPERTS as u32).collect();
let plan = cache.ensure(layer, &routed);
slots
.apply_copy_plan(layer, &plan.copy, &rows, &mut device)
.unwrap();
for (&expert, slot) in routed.iter().zip(plan.slots.iter()) {
let slot = slot.expect("pure offload places every route");
assert_eq!(
slots.occupant(slot),
Some(ExpertId { layer, expert }),
"layer {layer} expert {expert}"
);
for bank in 0..3 {
assert_eq!(
device.slot(bank, slot),
rows.expected(bank, layer, expert),
"layer {layer} expert {expert} bank {bank}"
);
}
}
}
assert_eq!(slots.occupied(), 12, "three layers of four experts");
}
#[test]
fn an_evicted_slot_is_overwritten_and_not_merely_relabelled() {
let mut cache = ExpertCache::new(2, 2, 2);
let mut slots = ExpertSlots::new(SlotGeometry {
num_layers: 2,
slots: 2,
row_bytes: vec![GATE, UP, DOWN],
})
.unwrap();
let rows = NamedRows::new();
let mut device = HostSlotMemory::new(slots.geometry());
let first = cache.ensure(0, &[0, 1]);
slots
.apply_copy_plan(0, &first.copy, &rows, &mut device)
.unwrap();
let second = cache.ensure(1, &[0, 1]);
assert_eq!(second.missing, 2, "layer 1 must evict layer 0");
slots
.apply_copy_plan(1, &second.copy, &rows, &mut device)
.unwrap();
for (&expert, slot) in [0u32, 1].iter().zip(second.slots.iter()) {
let slot = slot.unwrap();
assert_eq!(slots.occupant(slot), Some(ExpertId { layer: 1, expert }));
assert_eq!(
device.slot(0, slot),
rows.expected(0, 1, expert),
"the slot must hold layer 1's bytes, not layer 0's"
);
}
assert_eq!(cache.slot_of(0, 0), None, "layer 0's expert 0 was evicted");
}
#[test]
fn a_plan_whose_halves_disagree_is_refused_untouched() {
let mut slots = ExpertSlots::new(geometry(8)).unwrap();
let rows = NamedRows::new();
let mut device = HostSlotMemory::new(slots.geometry());
let plan = CopyPlan {
dst_slots: vec![0, 1, 2],
src_rows: vec![0, 1],
};
assert_eq!(
slots.apply_copy_plan(0, &plan, &rows, &mut device),
Err(SlotFault::PlanHalvesDisagree {
dst_slots: 3,
src_rows: 2,
})
);
assert_eq!(slots.occupied(), 0);
assert_eq!(
slots.stats().plans,
0,
"a refused plan is not an applied one"
);
}
#[test]
fn a_plan_that_writes_one_slot_twice_is_refused() {
let mut slots = ExpertSlots::new(geometry(8)).unwrap();
let rows = NamedRows::new();
let mut device = HostSlotMemory::new(slots.geometry());
let plan = CopyPlan {
dst_slots: vec![3, 1, 3],
src_rows: vec![0, 1, 2],
};
assert_eq!(
slots.apply_copy_plan(0, &plan, &rows, &mut device),
Err(SlotFault::SlotWrittenTwice { slot: 3 })
);
assert_eq!(slots.occupied(), 0);
}
#[test]
fn a_slot_the_pool_does_not_have_is_refused() {
let mut slots = ExpertSlots::new(geometry(4)).unwrap();
let rows = NamedRows::new();
let mut device = HostSlotMemory::new(slots.geometry());
let plan = CopyPlan {
dst_slots: vec![0, 9],
src_rows: vec![0, 1],
};
assert_eq!(
slots.apply_copy_plan(0, &plan, &rows, &mut device),
Err(SlotFault::SlotOutOfRange { slot: 9, slots: 4 })
);
assert_eq!(slots.occupied(), 0);
}
#[test]
fn a_row_that_is_not_exactly_one_slot_wide_is_refused() {
struct ShortDownBank;
impl ExpertRows for ShortDownBank {
fn row(&self, bank: usize, _layer: u32, _row: u32) -> Option<&[u8]> {
match bank {
0 => Some(&[0u8; GATE]),
1 => Some(&[0u8; UP]),
_ => Some(&[0u8; DOWN - 1]),
}
}
}
let mut slots = ExpertSlots::new(geometry(8)).unwrap();
let mut device = HostSlotMemory::new(slots.geometry());
let plan = CopyPlan {
dst_slots: vec![0],
src_rows: vec![5],
};
assert_eq!(
slots.apply_copy_plan(0, &plan, &ShortDownBank, &mut device),
Err(SlotFault::RowSizeMismatch {
bank: 2,
layer: 0,
row: 5,
expected: DOWN,
got: DOWN - 1,
})
);
assert_eq!(slots.occupied(), 0, "nothing was written");
}
#[test]
fn a_missing_host_row_names_its_bank() {
let mut slots = ExpertSlots::new(geometry(8)).unwrap();
let rows = NamedRows::new();
let mut device = HostSlotMemory::new(slots.geometry());
let plan = CopyPlan {
dst_slots: vec![0],
src_rows: vec![EXPERTS as u32],
};
assert_eq!(
slots.apply_copy_plan(0, &plan, &rows, &mut device),
Err(SlotFault::RowMissing {
bank: 0,
layer: 0,
row: EXPERTS as u32,
})
);
}
#[test]
fn an_unpinned_layer_refuses_an_lru_remap_but_takes_a_materialize() {
let residency = BankResidency::new(
&[
HostResidency::Pinned,
HostResidency::Pageable,
HostResidency::Pinned,
],
LAYERS,
&[1u32].into_iter().collect(),
false,
)
.unwrap();
let mut slots = ExpertSlots::new(geometry(EXPERTS * LAYERS))
.unwrap()
.with_residency(residency);
let rows = NamedRows::new();
let mut device = HostSlotMemory::new(slots.geometry());
let remap = CopyPlan {
dst_slots: vec![0],
src_rows: vec![3],
};
assert!(matches!(
slots.apply_copy_plan(1, &remap, &rows, &mut device),
Err(SlotFault::Residency(
ResidencyError::SlotRemapOnUnpinnedLayer { layer: 1 }
))
));
let whole = CopyPlan {
dst_slots: (0..EXPERTS as u32).collect(),
src_rows: (0..EXPERTS as u32).collect(),
};
let applied = slots
.apply_materialize(1, &whole, &rows, &mut device)
.unwrap();
assert_eq!(applied.rows, EXPERTS as u64 * 3);
assert!(slots.apply_copy_plan(0, &remap, &rows, &mut device).is_ok());
}
#[test]
fn a_device_fault_leaves_its_slot_unknown_and_names_it() {
struct FailsOnDownBank;
impl SlotDevice for FailsOnDownBank {
fn write_slot(&mut self, bank: usize, _d: u32, _s: &[u8]) -> Result<(), String> {
if bank == 2 {
return Err("out of device memory".to_string());
}
Ok(())
}
fn copy_slot(&mut self, _b: usize, _d: u32, _s: u32) -> Result<(), String> {
Ok(())
}
}
let mut slots = ExpertSlots::new(geometry(8)).unwrap();
let rows = NamedRows::new();
let plan = CopyPlan {
dst_slots: vec![5],
src_rows: vec![2],
};
let err = slots
.apply_copy_plan(0, &plan, &rows, &mut FailsOnDownBank)
.unwrap_err();
assert_eq!(
err,
SlotFault::Device {
bank: 2,
slot: 5,
detail: "out of device memory".to_string(),
}
);
assert_eq!(
slots.occupant(5),
None,
"a slot whose copy failed must not read back as resident"
);
}
#[test]
fn a_failing_flush_forgets_every_slot_the_plan_wrote() {
struct FlushFails;
impl SlotDevice for FlushFails {
fn write_slot(&mut self, _b: usize, _d: u32, _s: &[u8]) -> Result<(), String> {
Ok(())
}
fn copy_slot(&mut self, _b: usize, _d: u32, _s: u32) -> Result<(), String> {
Ok(())
}
fn flush(&mut self) -> Result<(), String> {
Err("copy engine reported an error".to_string())
}
}
let mut slots = ExpertSlots::new(geometry(8)).unwrap();
let rows = NamedRows::new();
let plan = CopyPlan {
dst_slots: vec![1, 4, 6],
src_rows: vec![0, 2, 3],
};
assert_eq!(
slots.apply_copy_plan(0, &plan, &rows, &mut FlushFails),
Err(SlotFault::DeviceFlush {
slots: vec![1, 4, 6],
detail: "copy engine reported an error".to_string(),
})
);
assert_eq!(
slots.occupied(),
0,
"no slot may read back as resident after an unconfirmed flush"
);
}
#[test]
fn a_gather_is_counted_separately_from_host_traffic() {
let mut slots = ExpertSlots::new(geometry(8)).unwrap();
let rows = NamedRows::new();
let mut device = HostSlotMemory::new(slots.geometry());
slots
.apply_copy_plan(
0,
&CopyPlan {
dst_slots: vec![4],
src_rows: vec![6],
},
&rows,
&mut device,
)
.unwrap();
let gather = GatherPlan {
dst_slots: vec![1],
src_slots: vec![4],
};
let applied = slots.apply_gather_plan(&gather, &mut device).unwrap();
assert_eq!(applied.rows, 3);
assert_eq!(applied.bytes as usize, GATE + UP + DOWN);
let stats = slots.stats();
assert_eq!(stats.device_bytes as usize, GATE + UP + DOWN);
assert_eq!(
stats.host_bytes as usize,
GATE + UP + DOWN,
"the gather must not be billed to the link"
);
assert_eq!(
slots.occupant(1),
Some(ExpertId {
layer: 0,
expert: 6
}),
"the gathered slot carries the source's identity"
);
assert_eq!(device.slot(1, 1), rows.expected(1, 0, 6));
}
#[test]
fn a_gather_from_an_unknown_slot_is_refused() {
let mut slots = ExpertSlots::new(geometry(8)).unwrap();
let mut device = HostSlotMemory::new(slots.geometry());
let gather = GatherPlan {
dst_slots: vec![0],
src_slots: vec![7],
};
assert!(matches!(
slots.apply_gather_plan(&gather, &mut device),
Err(SlotFault::Device { slot: 7, .. })
));
}
#[test]
fn a_resize_drops_residency_but_keeps_the_counters() {
let mut slots = ExpertSlots::new(geometry(8)).unwrap();
let rows = NamedRows::new();
let mut device = HostSlotMemory::new(slots.geometry());
slots
.apply_copy_plan(
0,
&CopyPlan {
dst_slots: vec![0, 1],
src_rows: vec![0, 1],
},
&rows,
&mut device,
)
.unwrap();
let before = slots.stats();
assert_eq!(slots.occupied(), 2);
slots.resize(64).unwrap();
assert_eq!(slots.occupied(), 0);
assert_eq!(slots.geometry().slots, 64);
assert_eq!(slots.stats(), before);
assert_eq!(
slots.resize(0),
Err(SlotFault::SlotOutOfRange { slot: 0, slots: 0 })
);
}
#[test]
fn a_degenerate_geometry_is_refused_at_construction() {
assert!(ExpertSlots::new(geometry(0)).is_err());
assert!(ExpertSlots::new(SlotGeometry {
num_layers: 0,
slots: 4,
row_bytes: vec![GATE],
})
.is_err());
assert!(ExpertSlots::new(SlotGeometry {
num_layers: 1,
slots: 4,
row_bytes: vec![GATE, 0],
})
.is_err());
}
#[test]
fn the_geometry_reports_the_device_bytes_it_needs() {
let g = geometry(1024);
assert_eq!(g.bytes(), 1024 * (GATE + UP + DOWN) as u64);
assert_eq!(g.banks(), 3);
}
#[test]
fn a_forgotten_slot_is_refetched_by_the_next_step() {
let mut cache = ExpertCache::new(1, EXPERTS, 8);
let mut slots = ExpertSlots::new(SlotGeometry {
num_layers: 1,
slots: 8,
row_bytes: vec![GATE, UP, DOWN],
})
.unwrap();
let rows = NamedRows::new();
let mut device = HostSlotMemory::new(slots.geometry());
let plan = cache.ensure(0, &[2]);
slots
.apply_copy_plan(0, &plan.copy, &rows, &mut device)
.unwrap();
let slot = plan.slots[0].unwrap();
assert_eq!(
cache.forget_slot(slot),
Some(ExpertId {
layer: 0,
expert: 2
})
);
slots.invalidate_slot(slot);
let again = cache.ensure(0, &[2]);
assert_eq!(again.missing, 1, "a forgotten expert must miss");
assert!(!again.copy.is_empty(), "and must be re-fetched");
slots
.apply_copy_plan(0, &again.copy, &rows, &mut device)
.unwrap();
assert_eq!(
slots.occupant(again.slots[0].unwrap()),
Some(ExpertId {
layer: 0,
expert: 2
})
);
assert_eq!(
again.slots[0],
Some(slot),
"a forgotten slot is the first candidate, so the re-fetch reclaims \
it rather than spending a slot that really holds an expert"
);
let empty = (0..8).find(|&s| cache.resident_in(s).is_none()).unwrap();
assert_eq!(cache.forget_slot(empty), None);
assert_eq!(cache.forget_slot(9_999), None, "and an unknown slot too");
}
}