1use std::num::NonZeroUsize;
2
3use thiserror::Error;
4
5use crate::{CpuPlacementGuarantee, CpuSet, ParallelMode};
6
7#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
19pub enum CpuThreadCountControl {
20 Sequential,
22 PerCallUpperBound,
24 BinaryClampToOne,
30 #[default]
32 GlobalOrUncontrolled,
33}
34
35#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
47pub enum CpuPlacementControl {
48 EngineWorkers,
50 CallingThread,
52 ExternalWorkers,
54 #[default]
56 None,
57}
58
59#[derive(Clone, Copy, Debug, Eq, PartialEq)]
81pub struct CpuProviderExecutionCapabilities {
82 pub thread_count: CpuThreadCountControl,
84 pub placement: CpuPlacementControl,
86 pub worker_local_sequential: bool,
88 pub accepts_sequential: bool,
90 pub accepts_outer: bool,
92 pub accepts_inner: bool,
94}
95
96impl Default for CpuProviderExecutionCapabilities {
97 fn default() -> Self {
98 Self {
99 thread_count: CpuThreadCountControl::GlobalOrUncontrolled,
100 placement: CpuPlacementControl::None,
101 worker_local_sequential: false,
102 accepts_sequential: false,
103 accepts_outer: false,
104 accepts_inner: true,
105 }
106 }
107}
108
109#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
122pub enum CpuProviderDomainError {
123 #[error(
125 "provider thread-count control {control:?} cannot enforce thread budget {thread_budget}"
126 )]
127 ThreadCountNotEnforceable {
128 thread_budget: usize,
130 control: CpuThreadCountControl,
132 },
133 #[error(
135 "provider placement control {placement:?} cannot enforce {guarantee:?} placement for thread budget {thread_budget}"
136 )]
137 PlacementNotEnforceable {
138 thread_budget: usize,
140 placement: CpuPlacementControl,
142 guarantee: CpuPlacementGuarantee,
144 },
145 #[error("provider cannot honor requested CPU parallel mode {mode:?}")]
147 ParallelModeNotSupported {
148 mode: ParallelMode,
150 },
151}
152
153impl CpuProviderExecutionCapabilities {
154 pub(crate) fn accepts_mode(self, mode: ParallelMode) -> bool {
155 match mode {
156 ParallelMode::Sequential => self.accepts_sequential && self.worker_local_sequential,
157 ParallelMode::Outer => self.accepts_outer && self.worker_local_sequential,
158 ParallelMode::Inner => self.accepts_inner,
159 }
160 }
161}
162
163#[cfg(test)]
164#[derive(Clone, Copy, Debug, Eq, PartialEq)]
165pub(crate) enum OpenBlasParallelism {
166 Sequential,
167 Pthread,
168 OpenMp,
169 Unknown,
170}
171
172#[cfg(test)]
173#[derive(Clone, Copy, Debug, Eq, PartialEq)]
174pub(crate) struct OpenBlasProbe {
175 pub(crate) parallelism: OpenBlasParallelism,
176 pub(crate) process_global_set_restore_wired: bool,
177}
178
179#[cfg(test)]
180#[derive(Clone, Copy, Debug, Eq, PartialEq)]
181pub(crate) struct AccelerateProbe {
182 pub(crate) binary_thread_local_control_wired: bool,
183}
184
185#[cfg(test)]
192#[derive(Clone, Copy, Debug, Eq, PartialEq)]
193pub(crate) enum CpuProviderProbe {
194 FaerOrNative,
195 Mkl { thread_local_setter_wired: bool },
196 OpenBlas(OpenBlasProbe),
197 Accelerate(AccelerateProbe),
198 ArmPlOpenMp,
199 ArmPlSerial,
200 NvplSerial,
201 UnknownBlas,
202 Injected(Option<CpuProviderExecutionCapabilities>),
203}
204
205#[cfg(test)]
206pub(crate) fn classify_provider(probe: CpuProviderProbe) -> CpuProviderExecutionCapabilities {
207 match probe {
208 CpuProviderProbe::FaerOrNative => engine_worker_capabilities(),
209 CpuProviderProbe::Mkl {
210 thread_local_setter_wired: true,
211 } => controlled_external_capabilities(CpuThreadCountControl::PerCallUpperBound),
212 CpuProviderProbe::Mkl {
213 thread_local_setter_wired: false,
214 }
215 | CpuProviderProbe::ArmPlOpenMp => uncontrolled_external_capabilities(),
216 CpuProviderProbe::OpenBlas(probe) => classify_openblas(probe),
217 CpuProviderProbe::Accelerate(probe) => classify_accelerate(probe),
218 CpuProviderProbe::ArmPlSerial | CpuProviderProbe::NvplSerial => serial_capabilities(),
219 CpuProviderProbe::UnknownBlas | CpuProviderProbe::Injected(None) => {
220 CpuProviderExecutionCapabilities::default()
221 }
222 CpuProviderProbe::Injected(Some(capabilities)) => capabilities,
223 }
224}
225
226#[cfg(test)]
227fn classify_openblas(probe: OpenBlasProbe) -> CpuProviderExecutionCapabilities {
228 match (probe.parallelism, probe.process_global_set_restore_wired) {
229 (OpenBlasParallelism::Sequential, _) => serial_capabilities(),
230 (OpenBlasParallelism::Pthread | OpenBlasParallelism::OpenMp, _) => {
231 uncontrolled_external_capabilities()
232 }
233 (OpenBlasParallelism::Unknown, _) => CpuProviderExecutionCapabilities::default(),
234 }
235}
236
237#[cfg(test)]
238fn classify_accelerate(probe: AccelerateProbe) -> CpuProviderExecutionCapabilities {
239 if probe.binary_thread_local_control_wired {
240 controlled_external_capabilities(CpuThreadCountControl::BinaryClampToOne)
241 } else {
242 uncontrolled_external_capabilities()
243 }
244}
245
246pub(crate) fn engine_worker_capabilities() -> CpuProviderExecutionCapabilities {
247 CpuProviderExecutionCapabilities {
248 thread_count: CpuThreadCountControl::PerCallUpperBound,
249 placement: CpuPlacementControl::EngineWorkers,
250 worker_local_sequential: true,
251 accepts_sequential: true,
252 accepts_outer: true,
253 accepts_inner: true,
254 }
255}
256
257#[cfg(test)]
258fn controlled_external_capabilities(
259 thread_count: CpuThreadCountControl,
260) -> CpuProviderExecutionCapabilities {
261 CpuProviderExecutionCapabilities {
262 thread_count,
263 placement: CpuPlacementControl::ExternalWorkers,
264 worker_local_sequential: true,
265 accepts_sequential: true,
266 accepts_outer: true,
267 accepts_inner: true,
268 }
269}
270
271#[cfg(any(test, feature = "cpu-blas"))]
272fn uncontrolled_external_capabilities() -> CpuProviderExecutionCapabilities {
273 CpuProviderExecutionCapabilities {
274 thread_count: CpuThreadCountControl::GlobalOrUncontrolled,
275 placement: CpuPlacementControl::ExternalWorkers,
276 worker_local_sequential: false,
277 accepts_sequential: false,
278 accepts_outer: false,
279 accepts_inner: true,
280 }
281}
282
283#[cfg(any(test, not(feature = "cpu-blas")))]
284pub(crate) fn serial_capabilities() -> CpuProviderExecutionCapabilities {
285 CpuProviderExecutionCapabilities {
286 thread_count: CpuThreadCountControl::Sequential,
287 placement: CpuPlacementControl::CallingThread,
288 worker_local_sequential: true,
289 accepts_sequential: true,
290 accepts_outer: true,
291 accepts_inner: true,
292 }
293}
294
295#[cfg(any(test, feature = "cpu-blas"))]
300pub(crate) fn builtin_blas_execution_capabilities() -> CpuProviderExecutionCapabilities {
301 uncontrolled_external_capabilities()
302}
303
304pub(crate) fn validate_provider_for_domain(
305 capabilities: CpuProviderExecutionCapabilities,
306 thread_budget: NonZeroUsize,
307 placement_guarantee: CpuPlacementGuarantee,
308 domain_cpus: &CpuSet,
309 process_allowed_cpus: &CpuSet,
310) -> Result<(), CpuProviderDomainError> {
311 if enforced_provider_thread_limit(capabilities.thread_count, thread_budget).is_none() {
312 return Err(CpuProviderDomainError::ThreadCountNotEnforceable {
313 thread_budget: thread_budget.get(),
314 control: capabilities.thread_count,
315 });
316 }
317
318 match capabilities.placement {
319 CpuPlacementControl::EngineWorkers | CpuPlacementControl::CallingThread => Ok(()),
320 CpuPlacementControl::ExternalWorkers => {
321 if thread_budget.get() == 1 && capabilities.worker_local_sequential {
322 return Ok(());
323 }
324 if placement_guarantee == CpuPlacementGuarantee::AdvisoryDeclared
325 || domain_cpus == process_allowed_cpus
326 {
327 return Ok(());
328 }
329 Err(CpuProviderDomainError::PlacementNotEnforceable {
330 thread_budget: thread_budget.get(),
331 placement: capabilities.placement,
332 guarantee: placement_guarantee,
333 })
334 }
335 CpuPlacementControl::None => {
336 if placement_guarantee == CpuPlacementGuarantee::AdvisoryDeclared {
337 Ok(())
338 } else {
339 Err(CpuProviderDomainError::PlacementNotEnforceable {
340 thread_budget: thread_budget.get(),
341 placement: capabilities.placement,
342 guarantee: placement_guarantee,
343 })
344 }
345 }
346 }
347}
348
349fn enforced_provider_thread_limit(
350 control: CpuThreadCountControl,
351 thread_budget: NonZeroUsize,
352) -> Option<NonZeroUsize> {
353 match control {
354 CpuThreadCountControl::Sequential | CpuThreadCountControl::BinaryClampToOne => {
355 NonZeroUsize::new(1)
356 }
357 CpuThreadCountControl::PerCallUpperBound => Some(thread_budget),
358 CpuThreadCountControl::GlobalOrUncontrolled => None,
359 }
360}
361
362#[cfg(test)]
363mod tests;