1use alloc::string::String;
2use core::{
3 fmt,
4 hash::{BuildHasher, Hash, Hasher},
5 str::FromStr,
6};
7
8use crate::{
9 AddressType, ElemType, EnumSet, EnumSetType, OpaqueType, SemanticType, Type, TypeHash,
10 VectorSize,
11 features::{AtomicUsage, ComplexUsage, Features, TypeUsage},
12};
13use cubecl_common::profile::TimingMethod;
14
15#[derive(Debug, Clone, PartialEq, Eq, Hash)]
30pub struct HardwareProperties {
31 pub load_width: u32,
33 pub plane_size_min: u32,
35 pub plane_size_max: u32,
37 pub max_bindings: u32,
39 pub max_shared_memory_size: usize,
41 pub max_cube_count: (u32, u32, u32),
43 pub max_units_per_cube: u32,
45 pub max_cube_dim: (u32, u32, u32),
47 pub num_streaming_multiprocessors: Option<u32>,
49 pub num_cpu_cores: Option<u32>,
51 pub last_level_cache_size: Option<usize>,
57 pub num_tensor_cores: Option<u32>,
59 pub min_tensor_cores_dim: Option<u32>,
64 pub max_vector_size: VectorSize,
66 pub cube_mma_reserved_shared_memory: usize,
68}
69
70#[derive(Debug, Clone, PartialEq, Eq, Hash)]
72#[non_exhaustive]
73pub struct MemoryDeviceProperties {
74 pub max_page_size: u64,
76 pub alignment: u64,
78 max_memory: Option<u64>,
81}
82
83impl MemoryDeviceProperties {
84 pub const fn new(max_page_size: u64, alignment: u64) -> Self {
87 Self {
88 max_page_size,
89 alignment,
90 max_memory: None,
91 }
92 }
93
94 pub const fn max_memory(&self) -> Option<u64> {
106 self.max_memory
107 }
108
109 pub const fn with_max_memory(mut self, max_memory: u64) -> Self {
111 self.set_max_memory(max_memory);
112 self
113 }
114
115 pub const fn set_max_memory(&mut self, max_memory: u64) {
118 self.max_memory = match max_memory {
119 0 => None,
120 size => Some(size),
121 };
122 }
123}
124
125#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
140pub struct DeviceIdentity {
141 pub name: String,
145 pub fingerprint: String,
149 pub physical: Option<PhysicalDevice>,
152}
153
154#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
157#[non_exhaustive]
158pub struct PhysicalDevice {
159 pub pci_address: Option<PciAddress>,
161 pub luid: Option<AdapterLuid>,
163 pub vendor: Option<PciVendor>,
164}
165
166impl PhysicalDevice {
167 pub fn is_same_card(&self, other: &Self) -> bool {
171 if let (Some(mine), Some(theirs)) = (self.pci_address, other.pci_address) {
172 return mine == theirs;
173 }
174 matches!((self.luid, other.luid), (Some(mine), Some(theirs)) if mine == theirs)
175 }
176
177 pub fn fill_from(&mut self, other: &Self) {
179 let Self {
180 pci_address,
181 luid,
182 vendor,
183 } = *other;
184 self.pci_address = self.pci_address.or(pci_address);
185 self.luid = self.luid.or(luid);
186 self.vendor = self.vendor.or(vendor);
187 }
188}
189
190#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
193pub struct AdapterLuid([u8; 8]);
194
195impl AdapterLuid {
196 pub fn new(bytes: [u8; 8]) -> Self {
198 Self(bytes)
199 }
200
201 pub fn from_parts(low_part: u32, high_part: i32) -> Self {
202 let mut bytes = [0; 8];
203 bytes[..4].copy_from_slice(&low_part.to_le_bytes());
204 bytes[4..].copy_from_slice(&high_part.to_le_bytes());
205 Self(bytes)
206 }
207
208 pub fn bytes(self) -> [u8; 8] {
209 self.0
210 }
211}
212
213#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
215pub enum PciVendor {
216 Nvidia,
217 Amd,
218 Intel,
219 Apple,
220 Arm,
222 Qualcomm,
224 Other(u32),
225}
226
227impl PciVendor {
228 pub fn id(self) -> u32 {
229 match self {
230 Self::Nvidia => 0x10de,
231 Self::Amd => 0x1002,
232 Self::Intel => 0x8086,
233 Self::Apple => 0x106b,
234 Self::Arm => 0x13b5,
235 Self::Qualcomm => 0x5143,
236 Self::Other(id) => id,
237 }
238 }
239}
240
241impl From<u32> for PciVendor {
242 fn from(id: u32) -> Self {
243 match id {
244 0x10de => Self::Nvidia,
245 0x1002 => Self::Amd,
246 0x8086 => Self::Intel,
247 0x106b => Self::Apple,
248 0x13b5 => Self::Arm,
249 0x5143 => Self::Qualcomm,
250 other => Self::Other(other),
251 }
252 }
253}
254
255impl fmt::Display for PciVendor {
256 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
257 match self {
258 Self::Nvidia => f.write_str("NVIDIA"),
259 Self::Amd => f.write_str("AMD"),
260 Self::Intel => f.write_str("Intel"),
261 Self::Apple => f.write_str("Apple"),
262 Self::Arm => f.write_str("Arm"),
263 Self::Qualcomm => f.write_str("Qualcomm"),
264 Self::Other(id) => write!(f, "{id:#06x}"),
265 }
266 }
267}
268
269#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
271pub struct PciAddress {
272 pub domain: u32,
273 pub bus: u8,
274 pub device: u8,
275 pub function: u8,
276}
277
278impl fmt::Display for PciAddress {
279 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
280 write!(
281 f,
282 "{:04x}:{:02x}:{:02x}.{:x}",
283 self.domain, self.bus, self.device, self.function
284 )
285 }
286}
287
288#[derive(Debug, Clone, PartialEq, Eq)]
289pub struct PciAddressError(pub String);
290
291impl fmt::Display for PciAddressError {
292 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
293 write!(f, "not a PCI address: {}", self.0)
294 }
295}
296
297impl core::error::Error for PciAddressError {}
298
299impl FromStr for PciAddress {
300 type Err = PciAddressError;
301
302 fn from_str(text: &str) -> Result<Self, Self::Err> {
304 let err = || PciAddressError(String::from(text));
305 let (rest, function) = text.rsplit_once('.').ok_or_else(err)?;
306 let mut parts = rest.rsplitn(3, ':');
307 let device = parts.next().ok_or_else(err)?;
308 let bus = parts.next().ok_or_else(err)?;
309 let domain = parts.next().unwrap_or("0");
310 Ok(Self {
311 domain: u32::from_str_radix(domain, 16).map_err(|_| err())?,
312 bus: u8::from_str_radix(bus, 16).map_err(|_| err())?,
313 device: u8::from_str_radix(device, 16).map_err(|_| err())?,
314 function: u8::from_str_radix(function, 16).map_err(|_| err())?,
315 })
316 }
317}
318
319#[derive(Debug, Clone, PartialEq, Eq)]
322pub struct DeviceProperties {
323 pub features: Features,
325 pub memory: MemoryDeviceProperties,
327 pub hardware: HardwareProperties,
329 pub timing_method: TimingMethod,
331 pub identity: DeviceIdentity,
333}
334
335impl TypeHash for DeviceProperties {
336 fn write_hash(_hasher: &mut impl core::hash::Hasher) {
337 }
339}
340
341impl DeviceProperties {
342 pub fn new(
349 features: Features,
350 memory_props: MemoryDeviceProperties,
351 hardware: HardwareProperties,
352 timing_method: TimingMethod,
353 identity: DeviceIdentity,
354 ) -> Self {
355 DeviceProperties {
356 features,
357 memory: memory_props,
358 hardware,
359 timing_method,
360 identity,
361 }
362 }
363
364 pub fn type_usage(&self, ty: ElemType) -> EnumSet<TypeUsage> {
366 self.features.type_usage(ty)
367 }
368
369 pub fn complex_usage(&self, ty: ElemType) -> EnumSet<ComplexUsage> {
371 self.features.complex_usage(ty)
372 }
373
374 pub fn supports_complex_usage(&self, ty: ElemType, usage: ComplexUsage) -> bool {
376 self.features.supports_complex_usage(ty, usage)
377 }
378
379 pub fn atomic_type_usage(&self, ty: Type) -> EnumSet<AtomicUsage> {
381 self.features.atomic_type_usage(ty)
382 }
383
384 pub fn supports_type(&self, ty: impl Into<Type>) -> bool {
386 self.features.supports_type(ty)
387 }
388
389 pub fn supports_address(&self, ty: impl Into<AddressType>) -> bool {
391 self.features.supports_address(ty)
392 }
393
394 pub fn register_address_type(&mut self, ty: impl Into<AddressType>) {
396 self.features.types.address.insert(ty.into());
397 }
398
399 pub fn register_atomic_type_usage(&mut self, ty: Type, uses: impl Into<EnumSet<AtomicUsage>>) {
401 *self.features.types.atomic.entry(ty).or_default() |= uses.into();
402 }
403
404 pub fn register_type_usage(
406 &mut self,
407 ty: impl Into<ElemType>,
408 uses: impl Into<EnumSet<TypeUsage>>,
409 ) {
410 *self.features.types.elem.entry(ty.into()).or_default() |= uses.into();
411 }
412
413 pub fn register_complex_usage(
415 &mut self,
416 ty: impl Into<ElemType>,
417 uses: impl Into<EnumSet<ComplexUsage>>,
418 ) {
419 *self.features.types.complex.entry(ty.into()).or_default() |= uses.into();
420 }
421
422 pub fn register_semantic_type(&mut self, ty: SemanticType) {
424 self.features.types.semantic.insert(ty);
425 }
426
427 pub fn register_opaque_type(&mut self, ty: OpaqueType) {
429 self.features.types.opaque.insert(ty);
430 }
431
432 pub fn checksum(&self) -> u64 {
435 let state = foldhash::fast::FixedState::default();
436 let mut hasher = state.build_hasher();
437 self.features.hash(&mut hasher);
438 self.hardware.hash(&mut hasher);
439 hasher.finish()
440 }
441}
442
443#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
446#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, TypeHash, EnumSetType)]
447pub enum FastMath {
448 NotNaN,
450 NotInf,
453 UnsignedZero,
455 AllowReciprocal,
457 AllowContraction,
460 AllowReassociation,
462 AllowTransform,
465 ReducedPrecision,
467}
468
469impl FastMath {
470 pub const fn all() -> EnumSet<FastMath> {
471 EnumSet::all()
472 }
473}
474
475#[cfg(test)]
476mod tests {
477 use super::*;
478 use alloc::string::ToString;
479
480 #[test]
485 fn a_memory_states_no_capacity_until_one_is_read() {
486 let props = MemoryDeviceProperties::new(1024, 32);
487 assert_eq!(props.max_memory(), None);
488 assert_eq!(props.clone().with_max_memory(4096).max_memory(), Some(4096));
489 }
490
491 #[test]
496 fn a_capacity_of_zero_is_no_capacity() {
497 let mut props = MemoryDeviceProperties::new(1024, 32).with_max_memory(4096);
498 props.set_max_memory(0);
499 assert_eq!(props.max_memory(), None);
500 }
501
502 #[test]
503 fn a_vendor_keeps_its_id_whether_named_or_not() {
504 for id in [0x10de, 0x1002, 0x8086, 0x106b, 0x13b5, 0x5143, 0x1af4] {
505 assert_eq!(PciVendor::from(id).id(), id);
506 }
507 assert_eq!(PciVendor::from(0x10de), PciVendor::Nvidia);
508 assert_eq!(PciVendor::from(0x1af4), PciVendor::Other(0x1af4));
509 assert_eq!(PciVendor::Other(0x1af4).to_string(), "0x1af4");
510 }
511
512 #[test]
513 fn a_luid_from_parts_is_the_bytes_vulkan_reports() {
514 let bytes = [0x8a, 0x1d, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00];
515 assert_eq!(
516 AdapterLuid::from_parts(0x0001_1d8a, 0),
517 AdapterLuid::new(bytes)
518 );
519 assert_eq!(
520 AdapterLuid::from_parts(1, -1).bytes(),
521 [1, 0, 0, 0, 0xff, 0xff, 0xff, 0xff]
522 );
523 }
524
525 #[test]
526 fn a_card_is_matched_by_address_then_by_luid() {
527 let address = |bus| {
528 Some(PciAddress {
529 domain: 0,
530 bus,
531 device: 0,
532 function: 0,
533 })
534 };
535 let luid = |low| Some(AdapterLuid::from_parts(low, 0));
536 let card = |pci_address, luid| PhysicalDevice {
537 pci_address,
538 luid,
539 vendor: None,
540 };
541
542 assert!(card(address(7), None).is_same_card(&card(address(7), luid(1))));
543 assert!(!card(address(7), luid(1)).is_same_card(&card(address(8), luid(1))));
544 assert!(card(None, luid(1)).is_same_card(&card(address(7), luid(1))));
545 assert!(!card(None, luid(1)).is_same_card(&card(None, luid(2))));
546 assert!(!card(None, None).is_same_card(&card(None, None)));
547 }
548
549 #[test]
550 fn a_card_filled_from_another_runtime_keeps_what_it_reported() {
551 let address = |bus| {
552 Some(PciAddress {
553 domain: 0,
554 bus,
555 device: 0,
556 function: 0,
557 })
558 };
559 let luid = Some(AdapterLuid::from_parts(1, 0));
560 let mut card = PhysicalDevice {
561 pci_address: address(7),
562 luid: None,
563 vendor: None,
564 };
565
566 card.fill_from(&PhysicalDevice {
567 pci_address: address(8),
568 luid,
569 vendor: Some(PciVendor::Nvidia),
570 });
571
572 assert_eq!(
573 card,
574 PhysicalDevice {
575 pci_address: address(7),
576 luid,
577 vendor: Some(PciVendor::Nvidia),
578 }
579 );
580 }
581
582 #[test]
583 fn a_pci_address_round_trips_and_defaults_its_domain() {
584 let id = PciAddress {
585 domain: 0,
586 bus: 7,
587 device: 0,
588 function: 0,
589 };
590 assert_eq!(id.to_string(), "0000:07:00.0");
591 assert_eq!("0000:07:00.0".parse::<PciAddress>(), Ok(id));
592 assert_eq!("07:00.0".parse::<PciAddress>(), Ok(id));
593 assert_eq!(
594 "0001:a3:1f.7".parse::<PciAddress>(),
595 Ok(PciAddress {
596 domain: 1,
597 bus: 0xa3,
598 device: 0x1f,
599 function: 7
600 })
601 );
602 assert!("07:00".parse::<PciAddress>().is_err());
603 assert!("gpu".parse::<PciAddress>().is_err());
604 }
605}