1#![doc(hidden)]
2
3pub type Result<T> = tenferro_tensor::Result<T>;
15pub use tenferro_tensor::{DType, Error, Tensor, TypedTensor};
16
17use std::mem::size_of_val;
18use strided_basic::{ErasedRawStridedPtr, ErasedRawStridedRef, Exec, KernelDType};
19use strided_fused::{ErasedFusedPlan, FusedInst, FusedOp, FusedPlan};
20use tenferro_cpu_basic::{
21 erased_raw_strided_ref, erased_raw_strided_uninit_mut, typed_host_data, BufferPool, PoolScalar,
22 PooledUninitOutput,
23};
24use tenferro_tensor::backend::{
25 ElementwiseFusionInputView, ElementwiseFusionOp, ElementwiseFusionPlan,
26};
27use tenferro_tensor::col_major_strides;
28use tenferro_tensor::{ShapeVec, StrideVec};
29
30const ELEMENTWISE_FUSION_OP: &str = "execute_elementwise_fusion";
31
32const ELEMENTWISE_FUSION_MIN_ELEMENTS: usize = 16 * 1024;
33
34pub const ERASED_FUSION_MAX_INPUTS: usize = 4;
46
47fn validate_elementwise_fusion_inputs(
48 inputs: &[&Tensor],
49 plan: &ElementwiseFusionPlan,
50) -> crate::Result<bool> {
51 if inputs.len() != plan.input_count() {
52 return Err(crate::Error::invalid_argument(
53 ELEMENTWISE_FUSION_OP,
54 "inputs",
55 format!(
56 "plan expects {} inputs but backend received {}",
57 plan.input_count(),
58 inputs.len()
59 ),
60 ));
61 }
62 if plan.outputs().is_empty() {
63 return Ok(false);
64 }
65 for input in inputs {
66 if input.dtype() != plan.dtype() {
67 return Err(crate::Error::dtype_mismatch(
68 ELEMENTWISE_FUSION_OP,
69 input.dtype(),
70 plan.dtype(),
71 ));
72 }
73 }
74 Ok(true)
75}
76
77fn strided_fused_op(op: ElementwiseFusionOp) -> FusedOp {
78 match op {
79 ElementwiseFusionOp::Add => FusedOp::Add,
80 ElementwiseFusionOp::Multiply => FusedOp::Multiply,
81 ElementwiseFusionOp::Negate => FusedOp::Negate,
82 ElementwiseFusionOp::Conj => FusedOp::Conj,
83 ElementwiseFusionOp::Divide => FusedOp::Divide,
84 ElementwiseFusionOp::Abs => FusedOp::Abs,
85 ElementwiseFusionOp::Exp => FusedOp::Exp,
86 ElementwiseFusionOp::Log => FusedOp::Log,
87 ElementwiseFusionOp::Sin => FusedOp::Sin,
88 ElementwiseFusionOp::Cos => FusedOp::Cos,
89 ElementwiseFusionOp::Tanh => FusedOp::Tanh,
90 ElementwiseFusionOp::Sqrt => FusedOp::Sqrt,
91 ElementwiseFusionOp::Rsqrt => FusedOp::Rsqrt,
92 ElementwiseFusionOp::Pow => FusedOp::Pow,
93 ElementwiseFusionOp::Expm1 => FusedOp::Expm1,
94 ElementwiseFusionOp::Log1p => FusedOp::Log1p,
95 ElementwiseFusionOp::Maximum => FusedOp::Maximum,
98 ElementwiseFusionOp::Minimum => FusedOp::Minimum,
99 ElementwiseFusionOp::Clamp => FusedOp::Clamp,
100 ElementwiseFusionOp::Remainder | ElementwiseFusionOp::Erf => {
103 unreachable!(
104 "ops without a selected strided_fused instruction are declined before CPU fusion"
105 )
106 }
107 }
108}
109
110fn plan_uses_unfused_op(plan: &ElementwiseFusionPlan) -> bool {
115 plan.ops().iter().any(|inst| {
116 matches!(
117 inst.op(),
118 ElementwiseFusionOp::Remainder | ElementwiseFusionOp::Erf
119 )
120 })
121}
122
123fn should_defer_to_broadcast_multiply_special_case(plan: &ElementwiseFusionPlan) -> bool {
124 !plan.input_views().iter().all(|view| view.is_identity())
125 && plan.ops().len() == 1
126 && plan.outputs() == [plan.input_count()]
127 && plan.ops()[0].op() == ElementwiseFusionOp::Multiply
128}
129
130fn strided_fused_plan(plan: &ElementwiseFusionPlan) -> FusedPlan {
131 FusedPlan::new(
132 plan.input_count(),
133 plan.outputs().iter().copied(),
134 plan.ops()
135 .iter()
136 .map(|inst| FusedInst::new(strided_fused_op(inst.op()), inst.inputs().iter().copied())),
137 )
138}
139
140#[derive(Clone, Copy)]
142enum FusedDType {
143 F32,
144 F64,
145}
146
147impl FusedDType {
148 fn kernel(self) -> KernelDType {
150 match self {
151 FusedDType::F32 => KernelDType::F32,
152 FusedDType::F64 => KernelDType::F64,
153 }
154 }
155}
156
157fn select_fused_dtype(plan: &ElementwiseFusionPlan) -> Option<FusedDType> {
167 match plan.dtype() {
168 DType::F32 => Some(FusedDType::F32),
169 DType::F64 => Some(FusedDType::F64),
170 _ => None,
171 }
172}
173
174fn typed_bytes<T>(data: &[T]) -> &[u8] {
175 unsafe { std::slice::from_raw_parts(data.as_ptr().cast::<u8>(), size_of_val(data)) }
178}
179
180struct ErasedFusionInput<'a> {
181 data: &'a [u8],
182 dims: ShapeVec,
183 strides: StrideVec,
184}
185
186fn tensor_host_bytes(dtype: FusedDType, input: &Tensor) -> crate::Result<&[u8]> {
187 let op = ELEMENTWISE_FUSION_OP;
188 macro_rules! bytes {
189 ($tensor:expr) => {
190 typed_host_data(op, $tensor).map(typed_bytes)
191 };
192 }
193
194 match dtype {
195 FusedDType::F32 => bytes!(fused_host::<f32>(op, input)?),
196 FusedDType::F64 => bytes!(fused_host::<f64>(op, input)?),
197 }
198}
199fn fused_host<'a, T: tenferro_tensor::TensorScalar>(
204 op: &'static str,
205 input: &'a Tensor,
206) -> crate::Result<&'a TypedTensor<T>> {
207 input.as_typed::<T>().ok_or_else(|| {
208 crate::Error::unsupported_dtype(
209 op,
210 input.dtype(),
211 "an externally defined payload is not a fused input",
212 )
213 })
214}
215
216fn erased_fusion_input<'a>(
217 dtype: FusedDType,
218 input: &'a Tensor,
219 view: &ElementwiseFusionInputView,
220) -> crate::Result<ErasedFusionInput<'a>> {
221 let data = tensor_host_bytes(dtype, input)?;
222 let base_shape = input.shape();
223 let base_strides = col_major_strides(base_shape)?;
224 let ElementwiseFusionInputView::BroadcastInDim { shape, dims } = view else {
225 return Ok(ErasedFusionInput {
226 data,
227 dims: ShapeVec::from(base_shape),
228 strides: base_strides,
229 });
230 };
231
232 if dims.len() != base_shape.len() {
233 return Err(crate::Error::invalid_argument(
234 ELEMENTWISE_FUSION_OP,
235 "configuration",
236 format!(
237 "broadcast dims length {} does not match input rank {}",
238 dims.len(),
239 base_shape.len()
240 ),
241 ));
242 }
243
244 let mut strides: StrideVec = std::iter::repeat_n(0isize, shape.len()).collect();
245 let mut seen = vec![false; shape.len()];
246 for (source_axis, &target_axis) in dims.iter().enumerate() {
247 if target_axis >= shape.len() {
248 return Err(crate::Error::axis_out_of_bounds(
249 ELEMENTWISE_FUSION_OP,
250 target_axis,
251 shape.len(),
252 ));
253 }
254 if seen[target_axis] {
255 return Err(crate::Error::duplicate_axis(
256 ELEMENTWISE_FUSION_OP,
257 target_axis,
258 "broadcast dims",
259 ));
260 }
261 seen[target_axis] = true;
262 let source_dim = base_shape[source_axis];
263 let target_dim = shape[target_axis];
264 if source_dim != target_dim && source_dim != 1 {
265 return Err(crate::Error::shape_mismatch(
266 ELEMENTWISE_FUSION_OP,
267 shape.to_vec(),
268 base_shape.to_vec(),
269 ));
270 }
271 if source_dim == target_dim {
272 strides[target_axis] = base_strides[source_axis];
273 }
274 }
275
276 Ok(ErasedFusionInput {
277 data,
278 dims: ShapeVec::from(shape.as_slice()),
279 strides,
280 })
281}
282
283#[doc(hidden)]
284pub fn elementwise_fusion_with_pool(
285 buffers: &mut BufferPool,
286 inputs: &[&Tensor],
287 plan: &ElementwiseFusionPlan,
288 exec: Exec<'_>,
289) -> crate::Result<Option<Vec<Tensor>>> {
290 if !validate_elementwise_fusion_inputs(inputs, plan)? {
291 return Ok(None);
292 }
293 if inputs.is_empty() || inputs.len() > ERASED_FUSION_MAX_INPUTS {
294 return Ok(None);
295 }
296 if plan_uses_unfused_op(plan) {
297 return Ok(None);
298 }
299 if should_defer_to_broadcast_multiply_special_case(plan) {
300 return Ok(None);
301 }
302 let Some(dtype) = select_fused_dtype(plan) else {
303 return Ok(None);
304 };
305
306 let input_layouts = inputs
307 .iter()
308 .zip(plan.input_views())
309 .map(|(input, view)| erased_fusion_input(dtype, input, view))
310 .collect::<crate::Result<Vec<_>>>()?;
311 let shape = input_layouts[0].dims.clone();
312 if input_layouts
313 .iter()
314 .skip(1)
315 .any(|input| input.dims != shape)
316 {
317 return Ok(None);
318 }
319 let element_count =
320 tenferro_tensor::validate::checked_shape_product(ELEMENTWISE_FUSION_OP, "shape", &shape)?;
321 if element_count < ELEMENTWISE_FUSION_MIN_ELEMENTS {
322 return Ok(None);
323 }
324
325 let input_refs = input_layouts
326 .iter()
327 .map(|input| {
328 unsafe {
332 erased_raw_strided_ref(dtype.kernel(), input.data, &input.dims, &input.strides, 0)
333 }
334 .map_err(|err| crate::Error::backend_source(ELEMENTWISE_FUSION_OP, err))
335 })
336 .collect::<crate::Result<Vec<_>>>()?;
337
338 execute_erased_fused_outputs(buffers, dtype, &input_refs, &shape, plan, exec).map(Some)
339}
340
341fn execute_erased_fused_outputs(
342 buffers: &mut BufferPool,
343 dtype: FusedDType,
344 input_refs: &[ErasedRawStridedRef<'_>],
345 shape: &[usize],
346 plan: &ElementwiseFusionPlan,
347 exec: Exec<'_>,
348) -> crate::Result<Vec<Tensor>> {
349 let input_ptrs: Vec<_> = input_refs
350 .iter()
351 .map(ErasedRawStridedPtr::from_ref)
352 .collect();
353 let kernel = dtype.kernel();
354 match dtype {
355 FusedDType::F32 => {
356 execute_erased_fused_plan::<f32>(buffers, kernel, &input_ptrs, shape, plan, exec)
357 }
358 FusedDType::F64 => {
359 execute_erased_fused_plan::<f64>(buffers, kernel, &input_ptrs, shape, plan, exec)
360 }
361 }
362}
363
364fn execute_erased_fused_plan<T>(
371 buffers: &mut BufferPool,
372 dtype: KernelDType,
373 input_ptrs: &[ErasedRawStridedPtr<'_>],
374 shape: &[usize],
375 plan: &ElementwiseFusionPlan,
376 exec: Exec<'_>,
377) -> crate::Result<Vec<Tensor>>
378where
379 T: Clone + PoolScalar,
380{
381 let erased_plan = ErasedFusedPlan::compile(dtype, strided_fused_plan(plan))
382 .map_err(|err| crate::Error::backend_source(ELEMENTWISE_FUSION_OP, err))?;
383 let output_strides = col_major_strides(shape)?;
384 let mut outputs = plan
385 .outputs()
386 .iter()
387 .map(|_| PooledUninitOutput::<T>::new(buffers, shape))
388 .collect::<crate::Result<Vec<_>>>()?;
389 let mut dests = outputs
395 .iter_mut()
396 .map(|output| {
397 unsafe {
399 erased_raw_strided_uninit_mut(
400 dtype,
401 output.as_uninit_bytes_mut(),
402 shape,
403 &output_strides,
404 0,
405 )
406 }
407 .map_err(|err| crate::Error::backend_source(ELEMENTWISE_FUSION_OP, err))
408 })
409 .collect::<crate::Result<Vec<_>>>()?;
410 erased_plan
411 .execute_outputs_uninit(&mut dests, input_ptrs, exec)
412 .map_err(|err| crate::Error::backend_source(ELEMENTWISE_FUSION_OP, err))?;
413 drop(dests);
414 outputs
415 .drain(..)
416 .map(|output| {
417 let tensor = unsafe { output.assume_init_recycled()? };
420 Ok(Tensor::from_typed::<T>(tensor))
421 })
422 .collect()
423}
424
425#[cfg(test)]
426mod tests;