use std::fmt;
use std::str::FromStr;
use super::CudaError;
pub const PTX_TIER_ENV: &str = "SPEAKRS_CUDA_PTX_TIER";
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ComputeCapability {
pub major: u32,
pub minor: u32,
}
impl ComputeCapability {
pub const fn new(major: u32, minor: u32) -> Self {
Self { major, minor }
}
}
impl fmt::Display for ComputeCapability {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}", self.major, self.minor)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum PtxTier {
Sm75,
Sm80,
Sm90,
Sm120,
}
impl PtxTier {
pub const BASELINE: Self = Self::Sm75;
pub const SHIPPED: [Self; 1] = [Self::Sm75];
pub fn native(device: ComputeCapability) -> Self {
Self::SHIPPED
.into_iter()
.filter(|tier| tier.min_capability() <= device)
.max()
.unwrap_or(Self::BASELINE)
}
pub const fn feature(self) -> &'static str {
match self {
Self::Sm75 => "cuda-sm75",
Self::Sm80 => "cuda-sm80",
Self::Sm90 => "cuda-sm90",
Self::Sm120 => "cuda-sm120",
}
}
pub const ALL: [Self; 4] = [Self::Sm75, Self::Sm80, Self::Sm90, Self::Sm120];
pub const fn name(self) -> &'static str {
match self {
Self::Sm75 => "sm75",
Self::Sm80 => "sm80",
Self::Sm90 => "sm90",
Self::Sm120 => "sm120",
}
}
pub const fn min_capability(self) -> ComputeCapability {
match self {
Self::Sm75 => ComputeCapability::new(7, 5),
Self::Sm80 => ComputeCapability::new(8, 0),
Self::Sm90 => ComputeCapability::new(9, 0),
Self::Sm120 => ComputeCapability::new(12, 0),
}
}
pub const fn is_compiled_in(self) -> bool {
match self {
Self::Sm75 => cfg!(feature = "cuda-sm75"),
Self::Sm80 => cfg!(feature = "cuda-sm80"),
Self::Sm90 => cfg!(feature = "cuda-sm90"),
Self::Sm120 => cfg!(feature = "cuda-sm120"),
}
}
pub fn compiled_in() -> impl Iterator<Item = Self> {
Self::ALL.into_iter().filter(|tier| tier.is_compiled_in())
}
pub fn from_env() -> Result<Option<Self>, CudaError> {
match std::env::var(PTX_TIER_ENV) {
Ok(value) if !value.trim().is_empty() => value.parse().map(Some),
_ => Ok(None),
}
}
pub fn select(
capability: ComputeCapability,
requested: Option<Self>,
) -> Result<Self, CudaError> {
if capability < Self::BASELINE.min_capability() {
return Err(CudaError::UnsupportedDevice {
capability,
baseline: Self::BASELINE,
});
}
let Some(tier) = requested else {
let supported = Self::compiled_in().filter(|tier| tier.min_capability() <= capability);
return supported.max().ok_or_else(|| {
let tier = Self::native(capability);
CudaError::TierNotCompiledIn {
tier,
device: capability,
feature: tier.feature(),
}
});
};
if !tier.is_compiled_in() {
return Err(CudaError::TierNotCompiledIn {
tier,
device: capability,
feature: tier.feature(),
});
}
if tier.min_capability() > capability {
return Err(CudaError::PtxTierAboveDevice { tier, capability });
}
Ok(tier)
}
}
impl fmt::Display for PtxTier {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
impl FromStr for PtxTier {
type Err = CudaError;
fn from_str(value: &str) -> Result<Self, Self::Err> {
let value = value.trim();
Self::ALL
.into_iter()
.find(|tier| tier.name() == value)
.ok_or_else(|| CudaError::InvalidPtxTier {
value: value.to_string(),
})
}
}
#[cfg(test)]
mod tests {
use super::{ComputeCapability, CudaError, PtxTier};
#[test]
fn automatic_target_requires_an_enabled_tier_the_device_supports() {
for device in [
ComputeCapability::new(7, 5),
ComputeCapability::new(8, 0),
ComputeCapability::new(8, 6),
ComputeCapability::new(9, 0),
ComputeCapability::new(12, 0),
] {
let expected = PtxTier::compiled_in()
.filter(|tier| tier.min_capability() <= device)
.max();
let selected = PtxTier::select(device, None);
match expected {
Some(tier) => assert_eq!(selected.unwrap(), tier),
None => assert!(matches!(selected, Err(CudaError::TierNotCompiledIn {
device: actual, feature: "cuda-sm75", ..
}) if actual == device)),
}
}
assert!(matches!(
PtxTier::select(ComputeCapability::new(7, 0), None),
Err(CudaError::UnsupportedDevice { .. })
));
}
#[test]
fn overrides_require_the_target_feature_and_device_capability() {
let device = ComputeCapability::new(12, 0);
for tier in PtxTier::ALL {
let selected = PtxTier::select(device, Some(tier));
if tier.is_compiled_in() {
assert_eq!(selected.unwrap(), tier);
if tier != PtxTier::Sm75 {
assert!(matches!(
PtxTier::select(ComputeCapability::new(7, 5), Some(tier)),
Err(CudaError::PtxTierAboveDevice { .. })
));
}
continue;
}
assert!(matches!(selected, Err(CudaError::TierNotCompiledIn {
tier: actual, feature, device: actual_device
}) if actual == tier && feature == tier.feature() && actual_device == device));
}
}
}