use crate::core::resources::ResourceSpec;
use crate::providers::common::InstanceSelection;
pub struct AkashInstanceMapper;
impl AkashInstanceMapper {
pub fn map(spec: &ResourceSpec) -> InstanceSelection {
let gpu_count = spec.gpu_count.unwrap_or(1).max(1);
let instance_type = match gpu_count {
1 => select_single_gpu(spec),
8 if spec.memory_gb >= 1024.0 => "gpu-h100-8x",
8 => "gpu-a100-80gb-8x",
_ if spec.memory_gb >= 256.0 => "gpu-h100",
_ => "gpu-a100-80gb",
};
InstanceSelection {
instance_type: instance_type.to_string(),
spot_capable: false,
estimated_hourly_cost: Some(Self::estimate_hourly_cost(instance_type)),
}
}
pub fn estimate_hourly_cost(instance_type: &str) -> f64 {
match instance_type {
"gpu-t4-small" => 0.12,
"gpu-t4" => 0.20,
"gpu-a10" => 0.45,
"gpu-a100-40gb" => 1.10,
"gpu-a100-80gb" => 1.50,
"gpu-h100" => 2.50,
"gpu-h200" => 3.20,
"gpu-a100-80gb-8x" => 10.00,
"gpu-h100-8x" => 18.00,
_ => 1.00,
}
}
}
fn select_single_gpu(spec: &ResourceSpec) -> &'static str {
if spec.memory_gb >= 256.0 {
"gpu-h200"
} else if spec.memory_gb >= 160.0 {
"gpu-h100"
} else if spec.memory_gb >= 80.0 {
"gpu-a100-80gb"
} else if spec.memory_gb >= 40.0 {
"gpu-a100-40gb"
} else if spec.memory_gb >= 24.0 {
"gpu-a10"
} else if spec.memory_gb >= 16.0 {
"gpu-t4"
} else {
"gpu-t4-small"
}
}
#[cfg(test)]
mod tests {
use super::*;
fn spec(cpu: f32, mem: f32, gpus: Option<u32>) -> ResourceSpec {
ResourceSpec {
cpu,
memory_gb: mem,
storage_gb: 100.0,
gpu_count: gpus,
allow_spot: false,
qos: Default::default(),
}
}
#[test]
fn selects_t4_small_for_minimal_spec() {
let selection = AkashInstanceMapper::map(&spec(2.0, 8.0, Some(1)));
assert_eq!(selection.instance_type, "gpu-t4-small");
assert!(!selection.spot_capable);
}
#[test]
fn selects_a100_80gb_for_mid_memory() {
let selection = AkashInstanceMapper::map(&spec(8.0, 96.0, Some(1)));
assert_eq!(selection.instance_type, "gpu-a100-80gb");
}
#[test]
fn selects_h200_for_huge_memory() {
let selection = AkashInstanceMapper::map(&spec(16.0, 320.0, Some(1)));
assert_eq!(selection.instance_type, "gpu-h200");
}
#[test]
fn selects_8x_h100_for_large_cluster() {
let selection = AkashInstanceMapper::map(&spec(64.0, 1536.0, Some(8)));
assert_eq!(selection.instance_type, "gpu-h100-8x");
}
#[test]
fn selects_8x_a100_for_smaller_cluster() {
let selection = AkashInstanceMapper::map(&spec(64.0, 512.0, Some(8)));
assert_eq!(selection.instance_type, "gpu-a100-80gb-8x");
}
#[test]
fn defaults_to_one_gpu_when_unset() {
let selection = AkashInstanceMapper::map(&spec(2.0, 8.0, None));
assert_eq!(selection.instance_type, "gpu-t4-small");
}
#[test]
fn never_spot_capable() {
let selection = AkashInstanceMapper::map(&ResourceSpec {
allow_spot: true,
..spec(4.0, 16.0, Some(1))
});
assert!(!selection.spot_capable);
}
#[test]
fn cost_estimate_increases_with_class() {
let t4 = AkashInstanceMapper::estimate_hourly_cost("gpu-t4-small");
let a100 = AkashInstanceMapper::estimate_hourly_cost("gpu-a100-80gb");
let h100_8x = AkashInstanceMapper::estimate_hourly_cost("gpu-h100-8x");
assert!(a100 > t4);
assert!(h100_8x > a100);
}
#[test]
fn unknown_profile_falls_back_to_default_cost() {
let cost = AkashInstanceMapper::estimate_hourly_cost("gpu-unknown");
assert!(
(cost - 1.00).abs() < f64::EPSILON,
"expected 1.00, got {cost}"
);
}
}