Skip to main content

av_denoise_core/
enumerate.rs

1use cubecl::device::DeviceId;
2use cubecl::prelude::*;
3
4use crate::accelerate::Accelerator;
5use crate::device::Device;
6use crate::probe::open_client;
7
8/// What one backend reports about this machine.
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct BackendDevices {
11    /// The backend that was asked.
12    pub accelerator: Accelerator,
13    /// Whether the backend started at all.
14    ///
15    /// A build can enable a backend the machine has no driver for, and
16    /// that backend reports no devices because it never ran, not
17    /// because the machine has no hardware.
18    pub available: bool,
19    /// The devices the backend can see, in the order it lists them.
20    ///
21    /// Always empty when `available` is false.
22    pub devices: Vec<Device>,
23}
24
25/// Asks each backend in `enable` which devices it can see.
26///
27/// Backends are reported in the order given, including the ones that
28/// could not start, so a caller can tell "no such hardware" apart from
29/// "no such driver".
30pub fn enumerate_devices(enable: &[Accelerator]) -> Vec<BackendDevices> {
31    enable
32        .iter()
33        .map(|accelerator| match accelerator {
34            #[cfg(feature = "cuda")]
35            Accelerator::Cuda => match Device::Default.to_cuda() {
36                Ok(dev) => query_runtime::<cubecl::cuda::CudaRuntime>(*accelerator, &dev),
37                Err(_) => unavailable(*accelerator),
38            },
39            #[cfg(feature = "rocm")]
40            Accelerator::Rocm => match Device::Default.to_amd() {
41                Ok(dev) => query_runtime::<cubecl::hip::HipRuntime>(*accelerator, &dev),
42                Err(_) => unavailable(*accelerator),
43            },
44            #[cfg(feature = "vulkan")]
45            Accelerator::Vulkan => match Device::Default.to_wgpu() {
46                Ok(dev) => query_runtime::<cubecl::wgpu::WgpuRuntime>(*accelerator, &dev),
47                Err(_) => unavailable(*accelerator),
48            },
49            #[cfg(feature = "metal")]
50            Accelerator::Metal => match Device::Default.to_wgpu() {
51                Ok(dev) => query_runtime::<cubecl::wgpu::WgpuRuntime>(*accelerator, &dev),
52                Err(_) => unavailable(*accelerator),
53            },
54            // Keeps the match exhaustive on docs.rs, where `cfg(docsrs)`
55            // widens the `Accelerator` enum to include variants whose
56            // backend feature is not enabled. Never reached at runtime.
57            #[cfg(docsrs)]
58            #[allow(unreachable_patterns)]
59            _ => unreachable!(),
60        })
61        .collect()
62}
63
64fn unavailable(accelerator: Accelerator) -> BackendDevices {
65    BackendDevices {
66        accelerator,
67        available: false,
68        devices: Vec::new(),
69    }
70}
71
72/// Opens a client on `device` and lists what that backend can see.
73///
74/// A backend that cannot open a client at all, because its driver
75/// libraries are missing, is reported as unavailable rather than
76/// allowed to take the process down. See [`probe`](crate::probe).
77fn query_runtime<R: Runtime>(accelerator: Accelerator, device: &R::Device) -> BackendDevices {
78    let Some(client) = open_client::<R>(accelerator, device) else {
79        return unavailable(accelerator);
80    };
81
82    // Type ids 0 to 3 are the device kinds `Device` can name. Anything
83    // else the runtime reports is hardware this tool cannot select.
84    //
85    // Not every backend filters by the type id it is given. ROCm and
86    // CUDA report their whole device list for each one, so the same
87    // device comes back on every pass and is kept only once.
88    let mut devices: Vec<Device> = Vec::new();
89    for type_id in 0..=3 {
90        for id in client.enumerate_devices(type_id) {
91            if let Some(device) = to_device(id)
92                && !devices.contains(&device)
93            {
94                devices.push(device);
95            }
96        }
97    }
98
99    BackendDevices {
100        accelerator,
101        available: true,
102        devices,
103    }
104}
105
106/// Maps a cubecl device id onto the selector that names it.
107///
108/// The type ids come from cubecl's own ordering of device kinds.
109/// Backends that report a kind this tool cannot select return `None`.
110fn to_device(id: DeviceId) -> Option<Device> {
111    let index = id.index_id as usize;
112    match id.type_id {
113        0 => Some(Device::Discrete { index }),
114        1 => Some(Device::Integrated { index }),
115        2 => Some(Device::Virtual { index }),
116        3 => Some(Device::Cpu),
117        _ => None,
118    }
119}
120
121#[cfg(test)]
122mod tests {
123    use super::*;
124
125    #[test]
126    fn device_kinds_map_from_type_ids() {
127        assert_eq!(
128            to_device(DeviceId::new(0, 1)),
129            Some(Device::Discrete { index: 1 }),
130        );
131        assert_eq!(
132            to_device(DeviceId::new(1, 0)),
133            Some(Device::Integrated { index: 0 }),
134        );
135        assert_eq!(to_device(DeviceId::new(2, 2)), Some(Device::Virtual { index: 2 }),);
136        assert_eq!(to_device(DeviceId::new(3, 0)), Some(Device::Cpu));
137    }
138
139    #[test]
140    fn unknown_type_ids_are_skipped() {
141        assert_eq!(to_device(DeviceId::new(4, 0)), None);
142    }
143
144    #[test]
145    fn no_backends_lists_nothing() {
146        assert!(enumerate_devices(&[]).is_empty());
147    }
148
149    #[cfg(feature = "vulkan")]
150    #[test]
151    fn vulkan_reports_at_least_one_device() {
152        let reported = enumerate_devices(&[Accelerator::Vulkan]);
153        assert_eq!(reported.len(), 1);
154
155        let vulkan = &reported[0];
156        assert_eq!(vulkan.accelerator, Accelerator::Vulkan);
157        assert!(vulkan.available, "the vulkan backend did not start");
158        assert!(
159            !vulkan.devices.is_empty(),
160            "the vulkan backend started but listed no devices",
161        );
162
163        let mut unique = vulkan.devices.clone();
164        unique.dedup();
165        assert_eq!(
166            unique.len(),
167            vulkan.devices.len(),
168            "a device was listed more than once: {:?}",
169            vulkan.devices,
170        );
171    }
172}