1use alloc::format;
2use alloc::string::String;
3use core::fmt::{Display, Formatter};
4use cubecl_ir::{DeviceIdentity, DeviceProperties};
5
6#[derive(Clone, Debug, PartialEq, Eq, Hash)]
12pub struct HardwareIdentity {
13 runtime: String,
14 name: String,
15 fingerprint: String,
16 parallelism: u32,
17}
18
19impl HardwareIdentity {
20 pub fn new(runtime: &str, properties: &DeviceProperties) -> Self {
23 let DeviceIdentity {
26 name,
27 fingerprint,
28 physical: _,
29 } = &properties.identity;
30 let hardware = &properties.hardware;
31 Self {
32 runtime: segment(runtime),
33 name: segment(name),
34 fingerprint: segment(fingerprint),
35 parallelism: hardware
36 .num_cpu_cores
37 .or(hardware.num_streaming_multiprocessors)
38 .unwrap_or(0),
39 }
40 }
41
42 pub(crate) fn name_with_memory(&self, memory: u64) -> String {
45 format!(
46 "{}_{}_{}_mem{memory}_par{}",
47 self.runtime, self.fingerprint, self.name, self.parallelism
48 )
49 }
50}
51
52fn segment(text: &str) -> String {
55 text.replace(|c: char| !c.is_ascii_alphanumeric(), "-")
56}
57
58impl Display for HardwareIdentity {
59 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
60 write!(
61 f,
62 "{}_{}_{}_par{}",
63 self.runtime, self.fingerprint, self.name, self.parallelism
64 )
65 }
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71 use alloc::string::ToString;
72 use alloc::vec;
73 use cubecl_common::profile::TimingMethod;
74 use cubecl_ir::{HardwareProperties, MemoryDeviceProperties, VectorSize, features::Features};
75
76 #[test]
77 fn a_gpu_is_identified_by_its_runtime_target_name_and_multiprocessors() {
78 assert_eq!(
79 HardwareIdentity::new("cuda", &l4()),
80 HardwareIdentity {
81 runtime: "cuda".to_string(),
82 name: "NVIDIA-L4".to_string(),
83 fingerprint: "ptx-sm89".to_string(),
84 parallelism: 58,
85 }
86 );
87 }
88
89 #[test]
90 fn cores_count_ahead_of_multiprocessors() {
91 let mut properties = l4();
92 properties.hardware.num_cpu_cores = Some(8);
93
94 assert_eq!(HardwareIdentity::new("cuda", &properties).parallelism, 8);
95 }
96
97 #[test]
98 fn names_that_differ_only_in_punctuation_identify_the_same_hardware() {
99 let named = |name: &str| {
100 let mut properties = l4();
101 properties.identity.name = name.to_string();
102 HardwareIdentity::new("wgpu<spirv>", &properties)
103 };
104
105 assert_eq!(
106 named("Intel(R) Arc(tm) A770"),
107 named("Intel-R- Arc-tm- A770")
108 );
109 }
110
111 #[test]
112 fn an_identity_is_one_path_segment() {
113 let mut properties = l4();
114 properties.identity.name = "Intel(R) Arc(tm) B390 (PTL)".to_string();
115 properties.identity.fingerprint = "spirv_32902_45184".to_string();
116 let written = HardwareIdentity::new("wgpu<spirv>", &properties).to_string();
117
118 assert!(
119 written
120 .chars()
121 .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_'),
122 "{written}"
123 );
124 }
125
126 #[test]
128 fn the_name_with_memory_keeps_the_throughput_layout() {
129 assert_eq!(
130 HardwareIdentity::new("cuda", &l4()).name_with_memory(5 << 30),
131 "cuda_ptx-sm89_NVIDIA-L4_mem5368709120_par58"
132 );
133 }
134
135 #[test]
136 fn every_field_tells_hardware_apart() {
137 let identity = rtx_2060();
138 let others = vec![
139 HardwareIdentity {
140 runtime: "wgpu-spirv-".to_string(),
141 ..rtx_2060()
142 },
143 HardwareIdentity {
144 name: "NVIDIA-GeForce-GTX-1660-SUPER".to_string(),
145 ..rtx_2060()
146 },
147 HardwareIdentity {
148 fingerprint: "ptx-sm86".to_string(),
149 ..rtx_2060()
150 },
151 HardwareIdentity {
152 parallelism: 4,
153 ..rtx_2060()
154 },
155 ];
156
157 for other in others {
158 assert_ne!(identity.to_string(), other.to_string(), "{other:?}");
159 }
160 }
161
162 fn l4() -> DeviceProperties {
163 let hardware = HardwareProperties {
164 load_width: 128,
165 vector_register_count: None,
166 plane_size_min: 32,
167 plane_size_max: 32,
168 max_bindings: 32,
169 max_shared_memory_size: 101376,
170 max_cube_count: (u32::MAX, u16::MAX as u32, u16::MAX as u32),
171 max_units_per_cube: 1024,
172 max_cube_dim: (1024, 1024, 64),
173 num_streaming_multiprocessors: Some(58),
174 num_tensor_cores: None,
175 min_tensor_cores_dim: None,
176 num_cpu_cores: None,
177 last_level_cache_size: None,
178 max_vector_size: VectorSize::MAX,
179 cube_mma_reserved_shared_memory: 0,
180 };
181 DeviceProperties::new(
182 Features::default(),
183 MemoryDeviceProperties::new(5 << 30, 256),
184 hardware,
185 TimingMethod::Device,
186 DeviceIdentity {
187 name: "NVIDIA L4".to_string(),
188 fingerprint: "ptx_sm89".to_string(),
189 physical: None,
190 },
191 )
192 }
193
194 fn rtx_2060() -> HardwareIdentity {
195 HardwareIdentity {
196 runtime: "cuda".to_string(),
197 name: "NVIDIA-GeForce-RTX-2060".to_string(),
198 fingerprint: "ptx-sm75".to_string(),
199 parallelism: 30,
200 }
201 }
202}