1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
//! Overridable batch execution strategy for batched CPU operations (#2142).
//!
//! A batched CPU operation (a strided batched contraction or a
//! packed-LU batch) splits its independent items over the thread budget the
//! lower library receives on `tcontract_exec::Exec`. The vocabulary for that
//! split lives in `tcontract_exec` and is re-exported here so callers keep a
//! `tenferro_cpu` spelling:
//!
//! * [`CpuBatchPolicy`] is `tcontract_exec::BatchPolicy` (a [`CpuBatchStrategy`]
//! plus the [`CpuBatchThresholds`] the `Auto` arm uses),
//! * [`CpuBatchStrategy`] is `tcontract_exec::BatchStrategy`,
//! * [`CpuBatchThresholds`] is `tcontract_exec::WorkModel`.
//!
//! Two variants were renamed when the vocabulary moved into `tcontract-exec`:
//! the old `OuterParallel` is [`CpuBatchStrategy::Outer`], and the old
//! `ProviderItems` is [`CpuBatchStrategy::Inner`]. `Sequential` and `Auto` kept
//! their names. The old vendor-dimension cutoff is deliberately not restored: it
//! was an inert knob once the decision moved into the lower library.
//!
//! # Precedence
//!
//! A batched operation resolves its policy as the innermost scoped override,
//! then the [`crate::CpuEngine`] default:
//!
//! 1. the policy of an enclosing [`with_batch_policy`] scope, if any;
//! 2. otherwise [`crate::CpuEngine::batch_policy`], whose default is
//! [`CpuBatchStrategy::Auto`].
//!
//! Wrapping one call in [`with_batch_policy`] therefore expresses a
//! per-operation choice. A non-CPU engine is rejected with a typed
//! [`tenferro_tensor::ErrorKind::Unsupported`] error; there is no process-global
//! or thread-local policy outside the scope callback, and each scope restores
//! the previous policy on return, error and unwind.
//!
//! # Route-specific `Auto`
//!
//! `Auto` is resolved inside the lower library and keeps that library's legacy
//! rule until the upstream shared model lands (tcontract-rs#123 step 3):
//! `tlinalg`'s 64-extent cutoff that counts RHS columns in the linalg lane,
//! and `cpueinsum`'s width rule in the contraction lane. `CpuBatchThresholds`
//! feeds those rules where the lower library reads the policy's model; a forced
//! strategy ignores the model and never falls back.
//!
//! # The BLAS linalg lane
//!
//! `tlinalg-blas` takes no `Exec` and has no tenferro lanes, so the BLAS linalg
//! lane refuses a forced [`CpuBatchStrategy::Outer`],
//! [`CpuBatchStrategy::Inner`] or `Split` before any write with a typed
//! unsupported error. `Auto` and `Sequential` are accepted; the lane is already
//! serial and the vendor call owns its own threading.
/// How the items of one batched operation are split over a thread budget.
///
/// This is `tcontract_exec::BatchStrategy`. The old `OuterParallel` variant is
/// [`CpuBatchStrategy::Outer`] and the old `ProviderItems` variant is
/// [`CpuBatchStrategy::Inner`]; `Sequential` and `Auto` kept their names.
///
/// # Examples
///
/// ```rust
/// use tenferro_cpu::CpuBatchStrategy;
///
/// assert_eq!(CpuBatchStrategy::default(), CpuBatchStrategy::Auto);
/// ```
pub use BatchStrategy as CpuBatchStrategy;
/// A batch strategy together with the [`CpuBatchThresholds`] `Auto` applies.
///
/// This is `tcontract_exec::BatchPolicy`.
///
/// # Examples
///
/// ```rust
/// use tenferro_cpu::{CpuBatchPolicy, CpuBatchStrategy};
///
/// let policy = CpuBatchPolicy::new(CpuBatchStrategy::Outer);
/// assert_eq!(policy.strategy, CpuBatchStrategy::Outer);
/// ```
pub use BatchPolicy as CpuBatchPolicy;
/// The cost and fan-out model [`CpuBatchStrategy::Auto`] applies.
///
/// This is `tcontract_exec::WorkModel`; the old vendor-dimension cutoff is not
/// part of it.
///
/// # Examples
///
/// ```rust
/// use tenferro_cpu::CpuBatchThresholds;
///
/// assert_eq!(CpuBatchThresholds::default().min_items, 2);
/// ```
pub use WorkModel as CpuBatchThresholds;
/// Run `f` on `engine` with `policy` as the effective CPU batch policy.
///
/// This is the scoped override of the precedence described in the [module
/// docs](self): wrapping a single call expresses a per-operation choice. The
/// previous policy is restored when `f` returns, returns an error or unwinds,
/// and scopes nest with the innermost winning. `f` runs on the caller's own
/// engine, so a forwarding engine keeps its own overrides inside the scope.
/// Nothing here mutates process-global state.
///
/// # Examples
///
/// ```rust
/// use tenferro_cpu::{with_batch_policy, with_cpu_engine, CpuBatchPolicy, CpuBatchStrategy, CpuEngine};
///
/// let mut engine = CpuEngine::serial();
/// let observed = with_batch_policy(
/// &mut engine,
/// CpuBatchPolicy::new(CpuBatchStrategy::Outer),
/// |engine| {
/// with_cpu_engine(engine, |cpu| {
/// cpu.with_linalg_pool(|context, _| Ok(context.batch_policy().strategy))
/// })
/// .expect("a CPU engine")
/// },
/// )??;
/// assert_eq!(observed, CpuBatchStrategy::Outer);
/// assert_eq!(engine.batch_policy().strategy, CpuBatchStrategy::Auto);
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
///
/// # Errors
///
/// Returns [`tenferro_tensor::Error::Unsupported`] without running `f` when
/// `engine` is neither a [`crate::CpuEngine`] nor an engine that forwards one
/// through [`tenferro_tensor::Engine::native_engine`] (a CUDA or other
/// custom engine). `f`'s own result is returned unchanged inside `Ok`.
///
/// # Panics
///
/// A panic in `f` propagates after the previous policy is restored.