monarch_rdma/backend/ibverbs/
device_selection.rs1use std::sync::LazyLock;
13
14use anyhow::Context;
15use anyhow::Result;
16use dashmap::DashMap;
17
18use super::device::IbvDevice;
19use super::device::IbvDeviceImpl;
20use super::primitives::IbvDeviceInfo;
21use crate::device_selection::MemoryLocation;
22use crate::device_selection::PCIAddress;
23use crate::device_selection::PciPath;
24use crate::device_selection::cpu_path;
25use crate::device_selection::get_cuda_pci_address;
26use crate::device_selection::pci_path;
27
28#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
31pub enum IbvDeviceTarget {
32 MemoryLocation(MemoryLocation),
34 Nic(String),
36}
37
38impl IbvDeviceTarget {
39 pub fn cpu(numa: u32) -> Self {
41 Self::MemoryLocation(MemoryLocation::Cpu(Some(numa)))
42 }
43
44 pub fn gpu(ordinal: u32) -> Self {
46 Self::MemoryLocation(MemoryLocation::Gpu(Some(ordinal)))
47 }
48
49 pub fn nic(name: impl Into<String>) -> Self {
51 Self::Nic(name.into())
52 }
53}
54
55pub fn get_pci_address(device: &IbvDeviceInfo) -> Result<PCIAddress> {
58 let link = format!("/sys/class/infiniband/{}/device", device.name());
59 let resolved =
60 std::fs::canonicalize(&link).with_context(|| format!("resolving sysfs link {link}"))?;
61 resolved
62 .file_name()
63 .and_then(|name| name.to_str())
64 .and_then(PCIAddress::parse)
65 .with_context(|| format!("no PCI address in resolved path {resolved:?}"))
66}
67
68pub fn select_optimal_ibv_devices<I: IbvDeviceImpl>(
76 location: MemoryLocation,
77) -> Vec<IbvDeviceInfo> {
78 static CACHE: LazyLock<DashMap<(&'static str, MemoryLocation), Vec<IbvDeviceInfo>>> =
79 LazyLock::new(DashMap::new);
80 let key = (I::typename(), location);
81 if let Some(cached) = CACHE.get(&key) {
82 return cached.value().clone();
83 }
84 let result = compute_optimal_ibv_devices::<I>(location);
85 CACHE.insert(key, result.clone());
86 result
87}
88
89fn compute_optimal_ibv_devices<I: IbvDeviceImpl>(location: MemoryLocation) -> Vec<IbvDeviceInfo> {
91 let mut best: Option<PciPath> = None;
92 let mut devices: Vec<IbvDeviceInfo> = Vec::new();
93 for nic in IbvDevice::<I>::list() {
94 let Some(path) = nic_path(&nic, location) else {
95 continue;
96 };
97 match best {
98 Some(current) if current.is_better_than(&path) => continue,
99 Some(current) if path.is_better_than(¤t) => devices.clear(),
100 _ => {}
101 }
102 best = Some(path);
103 devices.push(nic);
104 }
105 devices
106}
107
108fn nic_path(nic: &IbvDeviceInfo, location: MemoryLocation) -> Option<PciPath> {
112 let nic_addr = get_pci_address(nic).ok()?;
113 let base = match location {
114 MemoryLocation::Cpu(numa) => cpu_path(&nic_addr, numa),
115 MemoryLocation::Gpu(Some(ordinal)) => pci_path(&get_cuda_pci_address(ordinal)?, &nic_addr),
116 MemoryLocation::Gpu(None) => best_gpu_path(&nic_addr)?,
117 };
118 Some(cap_by_port_speed(base, nic))
119}
120
121fn best_gpu_path(nic_addr: &PCIAddress) -> Option<PciPath> {
124 (0..cuda_device_count())
125 .filter_map(|ordinal| get_cuda_pci_address(ordinal as u32))
126 .map(|gpu_addr| pci_path(&gpu_addr, nic_addr))
127 .reduce(|a, b| if b.is_better_than(&a) { b } else { a })
128}
129
130fn cap_by_port_speed(path: PciPath, nic: &IbvDeviceInfo) -> PciPath {
134 PciPath {
135 bottleneck_mbytes_per_sec: path
136 .bottleneck_mbytes_per_sec
137 .min(nic.port_speed_mbytes_per_sec()),
138 ..path
139 }
140}
141
142pub(crate) fn cuda_device_count() -> i32 {
150 std::fs::read_dir("/proc/driver/nvidia/gpus")
151 .map(|entries| entries.flatten().count() as i32)
152 .unwrap_or(0)
153}
154
155pub fn resolve_target<I: IbvDeviceImpl>(target: &IbvDeviceTarget) -> Option<IbvDeviceInfo> {
160 match target {
161 IbvDeviceTarget::Nic(name) => IbvDevice::<I>::list()
162 .into_iter()
163 .find(|device| device.name() == name),
164 IbvDeviceTarget::MemoryLocation(location) => select_optimal_ibv_devices::<I>(*location)
165 .into_iter()
166 .next(),
167 }
168}
169
170pub fn get_cuda_device_to_ibv_device<I: IbvDeviceImpl>() -> &'static Vec<Option<IbvDeviceInfo>> {
173 static CACHE: LazyLock<DashMap<&'static str, &'static Vec<Option<IbvDeviceInfo>>>> =
178 LazyLock::new(DashMap::new);
179 *CACHE.entry(I::typename()).or_insert_with(|| {
180 let result: Vec<Option<IbvDeviceInfo>> = (0..cuda_device_count())
181 .map(|ordinal| {
182 select_optimal_ibv_devices::<I>(MemoryLocation::Gpu(Some(ordinal as u32)))
183 .into_iter()
184 .next()
185 })
186 .collect();
187 Box::leak(Box::new(result))
188 })
189}