use thiserror::Error;
use crate::{CpuBackendKind, CpuContextError, CpuSet, CpuTopology, CpuTopologyError, NumaNodeId};
#[derive(Debug, Error)]
pub enum CpuEngineConstructionError {
#[error("managed CPU engine construction failed: {0}")]
Context(#[source] CpuContextError),
#[error("tensor CPU engine construction failed: {0}")]
Tensor(#[source] tenferro_tensor::Error),
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum CpuPlacement {
#[default]
Auto,
NumaNode(NumaNodeId),
AllAllowed,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum CpuPlacementGuarantee {
ExactDeclared,
AdvisoryDeclared,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ResolvedCpuPlacement {
NumaNode {
id: NumaNodeId,
cpus: CpuSet,
},
AllAllowed {
cpus: CpuSet,
},
}
impl ResolvedCpuPlacement {
pub fn cpus(&self) -> &CpuSet {
match self {
Self::NumaNode { cpus, .. } | Self::AllAllowed { cpus } => cpus,
}
}
pub fn node_id(&self) -> Option<NumaNodeId> {
match self {
Self::NumaNode { id, .. } => Some(*id),
Self::AllAllowed { .. } => None,
}
}
}
#[derive(Debug, Error)]
pub enum CpuPlacementError {
#[error("cannot resolve {requested:?} for {backend:?}: topology discovery failed: {source}")]
TopologyDiscovery {
requested: CpuPlacement,
backend: CpuBackendKind,
#[source]
source: CpuTopologyError,
},
#[error(
"cannot resolve {requested:?} for {backend:?}: managed worker affinity is unavailable"
)]
ManagedAffinityUnavailable {
requested: CpuPlacement,
backend: CpuBackendKind,
},
#[error("cannot resolve {requested:?} for {backend:?}: NUMA discovery is unavailable")]
NumaDiscoveryUnavailable {
requested: CpuPlacement,
backend: CpuBackendKind,
},
#[error("cannot resolve {requested:?} for {backend:?}: NUMA node {node} is unavailable")]
UnknownNumaNode {
requested: CpuPlacement,
backend: CpuBackendKind,
node: NumaNodeId,
},
#[error(
"cannot resolve {requested:?} for {backend:?}: external provider worker affinity is unmanaged"
)]
ExternalProviderAffinityUnmanaged {
requested: CpuPlacement,
backend: CpuBackendKind,
},
#[error("externally managed CPU coordinator has no registered domain for {requested:?}")]
UnregisteredExternalPlacement {
requested: CpuPlacement,
},
#[error("externally managed CPU coordinator has no registered domain {domain:?}")]
UnregisteredExternalDomain {
domain: crate::CpuDomainId,
},
#[error("cannot resolve {requested:?} for {backend:?}: engine construction failed: {source}")]
EngineConstruction {
requested: CpuPlacement,
backend: CpuBackendKind,
#[source]
source: CpuEngineConstructionError,
},
#[error("cannot resolve {requested:?} for {backend:?}: {message}")]
InternalState {
requested: CpuPlacement,
backend: CpuBackendKind,
message: &'static str,
},
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum ResolvedCpuExecution {
Compatibility,
Managed(ResolvedCpuPlacement),
ExternalManaged(ResolvedCpuPlacement),
ExternalCallerManaged,
ProviderDefaultExclusive,
}
pub(crate) fn resolve_placement(
backend: CpuBackendKind,
requested: CpuPlacement,
topology: &CpuTopology,
) -> Result<ResolvedCpuExecution, CpuPlacementError> {
resolve_placement_with_affinity(
backend,
requested,
topology,
cfg!(any(target_os = "linux", target_os = "android")),
)
}
pub(crate) fn resolve_placement_with_affinity(
backend: CpuBackendKind,
requested: CpuPlacement,
topology: &CpuTopology,
managed_affinity_available: bool,
) -> Result<ResolvedCpuExecution, CpuPlacementError> {
if backend == CpuBackendKind::Blas {
return match requested {
CpuPlacement::Auto => Ok(ResolvedCpuExecution::ProviderDefaultExclusive),
CpuPlacement::NumaNode(_) | CpuPlacement::AllAllowed => {
Err(CpuPlacementError::ExternalProviderAffinityUnmanaged { requested, backend })
}
};
}
if !managed_affinity_available {
return match requested {
CpuPlacement::Auto => Ok(ResolvedCpuExecution::Compatibility),
CpuPlacement::NumaNode(_) | CpuPlacement::AllAllowed => {
Err(CpuPlacementError::ManagedAffinityUnavailable { requested, backend })
}
};
}
let placement = match requested {
CpuPlacement::Auto | CpuPlacement::AllAllowed => ResolvedCpuPlacement::AllAllowed {
cpus: topology.allowed_cpus().clone(),
},
CpuPlacement::NumaNode(node) => {
if !topology.has_numa_nodes() {
return Err(CpuPlacementError::NumaDiscoveryUnavailable { requested, backend });
}
let cpus = topology
.node(node)
.ok_or(CpuPlacementError::UnknownNumaNode {
requested,
backend,
node,
})?;
ResolvedCpuPlacement::NumaNode {
id: node,
cpus: cpus.cpus().clone(),
}
}
};
Ok(ResolvedCpuExecution::Managed(placement))
}
#[cfg(test)]
mod tests;