use crate::device::{GpuInfo, NvLinkRemoteDevice, NvLinkRemoteType};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EdgeClass {
SelfCell,
NvLink { count: u32, generation: Option<u8> },
#[allow(dead_code)]
NvSwitch { count: u32 },
NvSwitchMesh,
#[allow(dead_code)]
PcieSameRoot,
PcieSameNuma,
SysInterconnect,
Unknown,
}
impl EdgeClass {
pub fn label(&self) -> String {
match self {
Self::SelfCell => "X".to_string(),
Self::NvLink {
count: _,
generation: Some(g),
} => format!("NV{g}"),
Self::NvLink {
count: _,
generation: None,
} => "NV".to_string(),
Self::NvSwitch { .. } => "NSW".to_string(),
Self::NvSwitchMesh => "NV".to_string(),
Self::PcieSameRoot => "PXB".to_string(),
Self::PcieSameNuma => "NODE".to_string(),
Self::SysInterconnect => "SYS".to_string(),
Self::Unknown => "--".to_string(),
}
}
}
pub fn bandwidth_to_generation(bandwidth_mb_s: u32) -> Option<u8> {
match bandwidth_mb_s {
0 => None,
1..=22_000 => Some(1),
22_001..=40_000 => Some(4),
40_001..=80_000 => Some(5),
_ => Some(6),
}
}
fn remote_is_gpu(d: &NvLinkRemoteDevice) -> bool {
matches!(d.remote_type, NvLinkRemoteType::Gpu)
}
fn remote_is_switch(d: &NvLinkRemoteDevice) -> bool {
matches!(d.remote_type, NvLinkRemoteType::Switch)
}
pub fn classify(
a_index: u32,
b_index: u32,
a: &GpuInfo,
b: &GpuInfo,
total_gpu_count: u32,
) -> EdgeClass {
if a_index == b_index {
return EdgeClass::SelfCell;
}
let gpu_remote_count = a
.nvlink_remote_devices
.iter()
.filter(|d| remote_is_gpu(d))
.count() as u32;
let switch_remote_count = a
.nvlink_remote_devices
.iter()
.filter(|d| remote_is_switch(d))
.count() as u32;
if gpu_remote_count == 0 && switch_remote_count == 0 {
return pcie_class(a, b);
}
let peer_count = total_gpu_count.saturating_sub(1);
if peer_count > 0 && gpu_remote_count >= peer_count {
let generation = dominant_generation(&a.nvlink_remote_devices);
return EdgeClass::NvLink {
count: (gpu_remote_count / peer_count.max(1)).max(1),
generation,
};
}
if switch_remote_count > 0 {
return EdgeClass::NvSwitchMesh;
}
if gpu_remote_count > 0 {
let generation = dominant_generation(&a.nvlink_remote_devices);
let count = gpu_remote_count
.checked_div(peer_count.max(1))
.unwrap_or(0)
.max(1);
return EdgeClass::NvLink { count, generation };
}
pcie_class(a, b)
}
fn pcie_class(a: &GpuInfo, b: &GpuInfo) -> EdgeClass {
match (a.numa_node_id, b.numa_node_id) {
(Some(na), Some(nb)) if na == nb => EdgeClass::PcieSameNuma,
(Some(_), Some(_)) => EdgeClass::SysInterconnect,
_ => EdgeClass::Unknown,
}
}
fn dominant_generation(links: &[NvLinkRemoteDevice]) -> Option<u8> {
let mut counts = [0u32; 7]; let mut any = false;
for link in links {
if let Some(bw) = link.bandwidth_mb_s
&& let Some(generation) = bandwidth_to_generation(bw)
&& (generation as usize) < counts.len()
{
counts[generation as usize] += 1;
any = true;
}
}
if !any {
return None;
}
let (mut best_gen, mut best_count, mut tied) = (0u8, 0u32, false);
for (generation, &cnt) in counts.iter().enumerate().skip(1) {
if cnt > best_count {
best_gen = generation as u8;
best_count = cnt;
tied = false;
} else if cnt == best_count && cnt > 0 {
tied = true;
}
}
if tied { None } else { Some(best_gen) }
}
#[cfg(test)]
mod tests {
use super::*;
use crate::device::NvLinkRemoteType;
use std::collections::HashMap;
fn gpu_with_numa(numa: Option<i32>) -> GpuInfo {
GpuInfo {
uuid: "GPU-X".to_string(),
time: String::new(),
name: "Test".to_string(),
device_type: "GPU".to_string(),
host_id: "h".to_string(),
hostname: "h".to_string(),
instance: "h".to_string(),
utilization: 0.0,
ane_utilization: 0.0,
dla_utilization: None,
tensorcore_utilization: None,
temperature: 0,
used_memory: 0,
total_memory: 0,
frequency: 0,
power_consumption: 0.0,
gpu_core_count: None,
temperature_threshold_slowdown: None,
temperature_threshold_shutdown: None,
temperature_threshold_max_operating: None,
temperature_threshold_acoustic: None,
performance_state: None,
fan_speed_rpm: None,
numa_node_id: numa,
gsp_firmware_mode: None,
gsp_firmware_version: None,
nvlink_remote_devices: Vec::new(),
gpm_metrics: None,
detail: HashMap::new(),
}
}
fn link(remote: NvLinkRemoteType, bw: Option<u32>) -> NvLinkRemoteDevice {
NvLinkRemoteDevice {
link_index: 0,
remote_type: remote,
bandwidth_mb_s: bw,
}
}
#[test]
fn self_cell_is_identity() {
let a = gpu_with_numa(Some(0));
assert_eq!(classify(0, 0, &a, &a, 8), EdgeClass::SelfCell);
}
#[test]
fn no_nvlink_same_numa_is_node() {
let a = gpu_with_numa(Some(0));
let b = gpu_with_numa(Some(0));
assert_eq!(classify(0, 1, &a, &b, 8), EdgeClass::PcieSameNuma);
}
#[test]
fn no_nvlink_cross_numa_is_sys() {
let a = gpu_with_numa(Some(0));
let b = gpu_with_numa(Some(1));
assert_eq!(classify(0, 1, &a, &b, 8), EdgeClass::SysInterconnect);
}
#[test]
fn missing_numa_is_unknown() {
let a = gpu_with_numa(None);
let b = gpu_with_numa(None);
assert_eq!(classify(0, 1, &a, &b, 8), EdgeClass::Unknown);
}
#[test]
fn full_mesh_gpu_links_classify_as_nvlink() {
let mut a = gpu_with_numa(Some(0));
a.nvlink_remote_devices = (0..7)
.map(|i| NvLinkRemoteDevice {
link_index: i,
remote_type: NvLinkRemoteType::Gpu,
bandwidth_mb_s: Some(50_000),
})
.collect();
let b = gpu_with_numa(Some(0));
let edge = classify(0, 1, &a, &b, 8);
match edge {
EdgeClass::NvLink { generation, .. } => assert_eq!(generation, Some(5)),
other => panic!("expected NvLink, got {other:?}"),
}
}
#[test]
fn switch_remotes_without_full_gpu_mesh_classify_as_mesh() {
let mut a = gpu_with_numa(Some(0));
a.nvlink_remote_devices = (0..5)
.map(|_| link(NvLinkRemoteType::Gpu, Some(50_000)))
.enumerate()
.map(|(i, mut d)| {
d.link_index = i as u32;
d
})
.collect();
a.nvlink_remote_devices.push(NvLinkRemoteDevice {
link_index: 5,
remote_type: NvLinkRemoteType::Switch,
bandwidth_mb_s: None,
});
let b = gpu_with_numa(Some(0));
assert_eq!(classify(0, 1, &a, &b, 8), EdgeClass::NvSwitchMesh);
}
#[test]
fn bandwidth_to_generation_thresholds() {
assert_eq!(bandwidth_to_generation(0), None);
assert_eq!(bandwidth_to_generation(20_000), Some(1));
assert_eq!(bandwidth_to_generation(25_000), Some(4));
assert_eq!(bandwidth_to_generation(50_000), Some(5));
assert_eq!(bandwidth_to_generation(90_000), Some(6));
assert_eq!(bandwidth_to_generation(150_000), Some(6));
}
#[test]
fn edge_label_falls_back_to_nv_when_generation_unknown() {
assert_eq!(
EdgeClass::NvLink {
count: 4,
generation: None,
}
.label(),
"NV"
);
assert_eq!(
EdgeClass::NvLink {
count: 4,
generation: Some(5),
}
.label(),
"NV5"
);
}
#[test]
fn dominant_generation_requires_populated_hints() {
let links = vec![
link(NvLinkRemoteType::Gpu, None),
link(NvLinkRemoteType::Gpu, None),
];
assert_eq!(dominant_generation(&links), None);
}
#[test]
fn dominant_generation_picks_majority() {
let links = vec![
link(NvLinkRemoteType::Gpu, Some(50_000)),
link(NvLinkRemoteType::Gpu, Some(50_000)),
link(NvLinkRemoteType::Gpu, Some(25_000)),
];
assert_eq!(dominant_generation(&links), Some(5));
}
#[test]
fn dominant_generation_ties_resolve_to_none() {
let links = vec![
link(NvLinkRemoteType::Gpu, Some(50_000)),
link(NvLinkRemoteType::Gpu, Some(25_000)),
];
assert_eq!(dominant_generation(&links), None);
}
}