#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum Source {
#[default]
Silicon,
Empirical,
Sol,
Estimated,
Mixed,
}
impl Source {
pub fn as_str(self) -> &'static str {
match self {
Self::Silicon => "silicon",
Self::Empirical => "empirical",
Self::Sol => "sol",
Self::Estimated => "estimated",
Self::Mixed => "mixed",
}
}
pub fn combine(self, other: Source) -> Source {
if self == other { self } else { Source::Mixed }
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct SolComponents {
pub math_ms: f64,
pub mem_ms: f64,
}
impl SolComponents {
pub fn new(math_ms: f64, mem_ms: f64) -> Self {
Self { math_ms, mem_ms }
}
pub fn time_ms(self) -> f64 {
self.math_ms.max(self.mem_ms)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct MoeCommFallback {
pub comm_backend: &'static str,
pub requested_ep_size: u32,
pub requested_node_num: u32,
pub measurement_ep_size: u32,
pub measurement_node_num: u32,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct MoeCommFallbacks {
first: Option<MoeCommFallback>,
additional: Vec<MoeCommFallback>,
}
impl MoeCommFallbacks {
pub fn is_empty(&self) -> bool {
self.first.is_none()
}
pub fn iter(&self) -> impl Iterator<Item = &MoeCommFallback> {
self.first.iter().chain(self.additional.iter())
}
fn insert(&mut self, fallback: MoeCommFallback) {
if self.iter().any(|existing| *existing == fallback) {
return;
}
if self.first.is_none() {
self.first = Some(fallback);
} else {
self.additional.push(fallback);
}
}
pub(crate) fn extend(&mut self, other: Self) {
for fallback in other.iter().copied() {
self.insert(fallback);
}
}
}
pub(crate) fn subtract_sol(
a: Option<SolComponents>,
b: Option<SolComponents>,
) -> Option<SolComponents> {
match (a, b) {
(Some(a), Some(b)) => Some(SolComponents::new(
a.math_ms - b.math_ms,
a.mem_ms - b.mem_ms,
)),
_ => None,
}
}
pub(crate) fn blend_sol(
w: f64,
a: Option<SolComponents>,
b: Option<SolComponents>,
) -> Option<SolComponents> {
match (a, b) {
(Some(a), Some(b)) => Some(SolComponents::new(
w * a.math_ms + (1.0 - w) * b.math_ms,
w * a.mem_ms + (1.0 - w) * b.mem_ms,
)),
_ => None,
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct PerformanceResult {
pub latency_ms: f64,
pub energy_wms: f64,
pub source: Source,
pub sol: Option<SolComponents>,
pub moe_comm_fallbacks: MoeCommFallbacks,
}
impl PerformanceResult {
pub fn new(latency_ms: f64, source: Source) -> Self {
Self {
latency_ms,
energy_wms: 0.0,
source,
sol: None,
moe_comm_fallbacks: MoeCommFallbacks::default(),
}
}
pub fn with_energy(latency_ms: f64, energy_wms: f64, source: Source) -> Self {
Self {
latency_ms,
energy_wms,
source,
sol: None,
moe_comm_fallbacks: MoeCommFallbacks::default(),
}
}
pub fn sol(components: SolComponents) -> Self {
Self {
latency_ms: components.time_ms(),
energy_wms: 0.0,
source: Source::Sol,
sol: Some(components),
moe_comm_fallbacks: MoeCommFallbacks::default(),
}
}
pub fn with_sol(mut self, components: SolComponents) -> Self {
self.sol = Some(components);
self
}
pub fn with_moe_comm_fallback(mut self, fallback: MoeCommFallback) -> Self {
self.moe_comm_fallbacks.insert(fallback);
self
}
pub fn with_moe_comm_fallbacks(mut self, fallbacks: MoeCommFallbacks) -> Self {
self.moe_comm_fallbacks.extend(fallbacks);
self
}
pub fn silicon(latency_ms: f64) -> Self {
Self::new(latency_ms, Source::Silicon)
}
pub fn zero() -> Self {
Self::default()
}
pub fn scaled(self, factor: f64) -> Self {
Self {
latency_ms: self.latency_ms * factor,
energy_wms: self.energy_wms * factor,
source: self.source,
sol: self.sol.map(|c| SolComponents {
math_ms: c.math_ms * factor,
mem_ms: c.mem_ms * factor,
}),
moe_comm_fallbacks: self.moe_comm_fallbacks,
}
}
pub fn plus(self, other: PerformanceResult) -> Self {
let mut moe_comm_fallbacks = self.moe_comm_fallbacks;
moe_comm_fallbacks.extend(other.moe_comm_fallbacks);
let (source, sol) = if self.latency_ms == 0.0 && self.energy_wms == 0.0 {
(other.source, other.sol)
} else if other.latency_ms == 0.0 && other.energy_wms == 0.0 {
(self.source, self.sol)
} else {
let sol = match (self.sol, other.sol) {
(Some(a), Some(b)) => Some(SolComponents {
math_ms: a.math_ms + b.math_ms,
mem_ms: a.mem_ms + b.mem_ms,
}),
_ => None,
};
(self.source.combine(other.source), sol)
};
Self {
latency_ms: self.latency_ms + other.latency_ms,
energy_wms: self.energy_wms + other.energy_wms,
source,
sol,
moe_comm_fallbacks,
}
}
pub fn clamp_non_negative(self) -> Self {
Self {
latency_ms: self.latency_ms.max(0.0),
energy_wms: self.energy_wms.max(0.0),
source: self.source,
sol: self.sol.map(|c| SolComponents {
math_ms: c.math_ms.max(0.0),
mem_ms: c.mem_ms.max(0.0),
}),
moe_comm_fallbacks: self.moe_comm_fallbacks,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn source_default_is_silicon() {
assert_eq!(Source::default(), Source::Silicon);
}
#[test]
fn source_combine_same_keeps_tag() {
assert_eq!(Source::Silicon.combine(Source::Silicon), Source::Silicon);
assert_eq!(Source::Sol.combine(Source::Sol), Source::Sol);
}
#[test]
fn source_combine_different_yields_mixed() {
assert_eq!(Source::Silicon.combine(Source::Empirical), Source::Mixed);
assert_eq!(Source::Sol.combine(Source::Silicon), Source::Mixed);
}
#[test]
fn performance_result_scaled() {
let r = PerformanceResult::silicon(10.0).scaled(0.5);
assert_eq!(r.latency_ms, 5.0);
assert_eq!(r.source, Source::Silicon);
}
#[test]
fn performance_result_clamp_non_negative() {
let r = PerformanceResult::silicon(-1.5).clamp_non_negative();
assert_eq!(r.latency_ms, 0.0);
}
#[test]
fn moe_comm_fallbacks_ride_through_result_combinators_without_loss() {
assert_eq!(
PerformanceResult::default()
.moe_comm_fallbacks
.additional
.capacity(),
0
);
let ht = MoeCommFallback {
comm_backend: "deepep_ht",
requested_ep_size: 32,
requested_node_num: 8,
measurement_ep_size: 8,
measurement_node_num: 1,
};
let ll = MoeCommFallback {
comm_backend: "deepep_ll",
..ht
};
let tagged = PerformanceResult::new(-2.0, Source::Estimated).with_moe_comm_fallback(ht);
assert_eq!(tagged.moe_comm_fallbacks.additional.capacity(), 0);
assert_eq!(
tagged
.clone()
.scaled(2.0)
.moe_comm_fallbacks
.iter()
.copied()
.collect::<Vec<_>>(),
vec![ht]
);
assert_eq!(
tagged
.clone()
.clamp_non_negative()
.moe_comm_fallbacks
.iter()
.copied()
.collect::<Vec<_>>(),
vec![ht]
);
assert_eq!(
tagged
.clone()
.plus(PerformanceResult::new(1.0, Source::Silicon))
.moe_comm_fallbacks
.iter()
.copied()
.collect::<Vec<_>>(),
vec![ht]
);
assert_eq!(
tagged
.clone()
.plus(tagged.clone())
.moe_comm_fallbacks
.iter()
.copied()
.collect::<Vec<_>>(),
vec![ht]
);
assert_eq!(
tagged
.plus(PerformanceResult::new(1.0, Source::Estimated).with_moe_comm_fallback(ll))
.moe_comm_fallbacks
.iter()
.copied()
.collect::<Vec<_>>(),
vec![ht, ll]
);
assert_eq!(
PerformanceResult::new(0.0, Source::Estimated)
.with_moe_comm_fallback(ht)
.plus(PerformanceResult::new(0.0, Source::Estimated).with_moe_comm_fallback(ll))
.moe_comm_fallbacks
.iter()
.copied()
.collect::<Vec<_>>(),
vec![ht, ll]
);
}
#[test]
fn sol_components_ride_through_combinators() {
let leaf = PerformanceResult::sol(SolComponents::new(3.0, 5.0));
assert_eq!(leaf.latency_ms, 5.0);
assert_eq!(leaf.source, Source::Sol);
let scaled = leaf.clone().scaled(2.0);
assert_eq!(scaled.sol, Some(SolComponents::new(6.0, 10.0)));
let sum = leaf
.clone()
.plus(PerformanceResult::sol(SolComponents::new(1.0, 0.5)));
assert_eq!(sum.latency_ms, 6.0);
assert_eq!(sum.sol, Some(SolComponents::new(4.0, 5.5)));
let poisoned = leaf.clone().plus(PerformanceResult::new(1.0, Source::Sol));
assert_eq!(poisoned.sol, None);
let zero = PerformanceResult::zero();
assert_eq!(leaf.clone().plus(zero.clone()).sol, leaf.sol);
assert_eq!(zero.plus(leaf.clone()).sol, leaf.sol);
let negative = subtract_sol(
Some(SolComponents::new(1.0, 1.0)),
Some(SolComponents::new(2.0, 0.5)),
)
.unwrap();
assert_eq!(negative, SolComponents::new(-1.0, 0.5));
let clamped = PerformanceResult::new(1.0, Source::Sol)
.with_sol(negative)
.clamp_non_negative();
assert_eq!(clamped.sol, Some(SolComponents::new(0.0, 0.5)));
assert_eq!(subtract_sol(Some(SolComponents::default()), None), None);
assert_eq!(subtract_sol(None, Some(SolComponents::default())), None);
}
#[test]
fn plus_zero_result_is_source_neutral() {
let zero = PerformanceResult::new(0.0, Source::Empirical);
let real = PerformanceResult::with_energy(2.0, 10.0, Source::Silicon);
assert_eq!(zero.clone().plus(real.clone()).source, Source::Silicon);
assert_eq!(real.clone().plus(zero).source, Source::Silicon);
let sol = PerformanceResult::new(1.0, Source::Sol);
assert_eq!(real.clone().plus(sol).source, Source::Mixed);
let energetic_zero = PerformanceResult::with_energy(0.0, 5.0, Source::Empirical);
assert_eq!(real.plus(energetic_zero).source, Source::Mixed);
}
}