Skip to main content

burn_flex/ops/
float.rs

1//! Float tensor operations for the Flex backend.
2
3use alloc::vec;
4use alloc::vec::Vec;
5use burn_backend::{
6    DType, Distribution, Element, ExecutionError, FloatDType, Scalar, TensorData, TensorMetadata,
7    ops::{FloatTensorOps, GridSampleOptions, IntTensorOps},
8    tensor::{BoolTensor, Device, FloatTensor, IntTensor},
9};
10use burn_std::{Bytes, IntDType, Shape, Slice, bf16, f16};
11use num_traits::{Float, ToPrimitive};
12
13use crate::Layout;
14use crate::ops::binary::{BinaryOp, binary_op, scalar_op};
15use crate::ops::matmul;
16use crate::ops::unary;
17use crate::{Flex, FlexTensor};
18
19/// Python/PyTorch-style remainder: result has same sign as divisor.
20#[inline]
21fn remainder_float<T: Float>(a: T, b: T) -> T {
22    let r = a % b;
23    if r != T::zero() && (r < T::zero()) != (b < T::zero()) {
24        r + b
25    } else {
26        r
27    }
28}
29
30fn powi_float<T: Float>(base: T, exponent: i32) -> T {
31    // Keep the exponent integer: native powi can lower to powf on some targets.
32    let magnitude = exponent.unsigned_abs() as usize;
33    if exponent < 0 {
34        // Computing base^magnitude first can overflow even when its reciprocal is
35        // representable (e.g. 2^-149 in f32). Invert the half power instead.
36        // Taking 1/base first would amplify its rounding error for large exponents.
37        let reciprocal = num_traits::pow(base, magnitude / 2).recip();
38        let result = reciprocal * reciprocal;
39        if magnitude & 1 == 1 {
40            result / base
41        } else {
42            result
43        }
44    } else {
45        num_traits::pow(base, magnitude)
46    }
47}
48
49impl FloatTensorOps<Flex> for Flex {
50    fn float_from_data(data: TensorData, _device: &Device<Flex>) -> FloatTensor<Flex> {
51        FlexTensor::from_data(data)
52    }
53
54    fn float_random(
55        shape: Shape,
56        distribution: Distribution,
57        _device: &Device<Flex>,
58        dtype: FloatDType,
59    ) -> FloatTensor<Flex> {
60        let mut seed = crate::backend::SEED.lock();
61        let mut rng = seed.take().unwrap_or_else(crate::backend::get_seeded_rng);
62        let data = match dtype {
63            FloatDType::F64 => TensorData::random::<f64, _, _>(shape, distribution, &mut rng),
64            FloatDType::F32 | FloatDType::Flex32 => {
65                TensorData::random::<f32, _, _>(shape, distribution, &mut rng)
66            }
67            FloatDType::F16 => TensorData::random::<f16, _, _>(shape, distribution, &mut rng),
68            FloatDType::BF16 => TensorData::random::<bf16, _, _>(shape, distribution, &mut rng),
69        };
70        *seed = Some(rng);
71        FlexTensor::from_data(data)
72    }
73
74    async fn float_into_data(tensor: FloatTensor<Flex>) -> Result<TensorData, ExecutionError> {
75        Ok(tensor.into_data())
76    }
77
78    fn float_to_device(tensor: FloatTensor<Flex>, _device: &Device<Flex>) -> FloatTensor<Flex> {
79        // CPU backend: no-op, tensors are always on CPU
80        tensor
81    }
82
83    fn float_detach(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
84        tensor
85    }
86
87    fn float_into_int(tensor: FloatTensor<Flex>, out_dtype: burn_std::IntDType) -> IntTensor<Flex> {
88        let tensor = tensor.to_contiguous();
89        let shape = tensor.layout().shape().clone();
90        let src = tensor.dtype();
91        let out_dt = DType::from(out_dtype);
92
93        // Read source floats as f64 (lossless for f32/f16/bf16).
94        macro_rules! read_floats {
95            (|$x:ident| $conv:expr) => {
96                match src {
97                    DType::F32 => tensor
98                        .storage::<f32>()
99                        .iter()
100                        .map(|v| {
101                            let $x = *v as f64;
102                            $conv
103                        })
104                        .collect(),
105                    DType::F64 => tensor
106                        .storage::<f64>()
107                        .iter()
108                        .map(|v| {
109                            let $x = *v;
110                            $conv
111                        })
112                        .collect(),
113                    DType::F16 => tensor
114                        .storage::<f16>()
115                        .iter()
116                        .map(|v| {
117                            let $x = f32::from(*v) as f64;
118                            $conv
119                        })
120                        .collect(),
121                    DType::BF16 => tensor
122                        .storage::<bf16>()
123                        .iter()
124                        .map(|v| {
125                            let $x = f32::from(*v) as f64;
126                            $conv
127                        })
128                        .collect(),
129                    _ => panic!("float_into_int: unsupported source dtype {:?}", src),
130                }
131            };
132        }
133
134        macro_rules! convert {
135            ($int_ty:ty) => {{
136                let data: Vec<$int_ty> = read_floats!(|x| x as $int_ty);
137                FlexTensor::new(Bytes::from_elems(data), Layout::contiguous(shape), out_dt)
138            }};
139        }
140
141        match out_dtype {
142            IntDType::I64 => convert!(i64),
143            IntDType::I32 => convert!(i32),
144            IntDType::I16 => convert!(i16),
145            IntDType::I8 => convert!(i8),
146            IntDType::U64 => convert!(u64),
147            IntDType::U32 => convert!(u32),
148            IntDType::U16 => convert!(u16),
149            IntDType::U8 => convert!(u8),
150        }
151    }
152
153    fn float_empty(shape: Shape, _device: &Device<Flex>, dtype: FloatDType) -> FloatTensor<Flex> {
154        FlexTensor::empty(shape, dtype.into())
155    }
156
157    fn float_add(lhs: FloatTensor<Flex>, rhs: FloatTensor<Flex>) -> FloatTensor<Flex> {
158        binary_op(lhs, rhs, |a, b| a + b, |a, b| a + b, Some(BinaryOp::Add))
159    }
160
161    fn float_add_scalar(lhs: FloatTensor<Flex>, rhs: Scalar) -> FloatTensor<Flex> {
162        let rhs_val = rhs.to_f64().unwrap();
163        scalar_op(lhs, rhs_val, |a, b| a + b, |a, b| a + b)
164    }
165
166    fn float_sub(lhs: FloatTensor<Flex>, rhs: FloatTensor<Flex>) -> FloatTensor<Flex> {
167        binary_op(lhs, rhs, |a, b| a - b, |a, b| a - b, Some(BinaryOp::Sub))
168    }
169
170    fn float_sub_scalar(lhs: FloatTensor<Flex>, rhs: Scalar) -> FloatTensor<Flex> {
171        let rhs_val = rhs.to_f64().unwrap();
172        scalar_op(lhs, rhs_val, |a, b| a - b, |a, b| a - b)
173    }
174
175    fn float_mul(lhs: FloatTensor<Flex>, rhs: FloatTensor<Flex>) -> FloatTensor<Flex> {
176        binary_op(lhs, rhs, |a, b| a * b, |a, b| a * b, Some(BinaryOp::Mul))
177    }
178
179    fn float_mul_scalar(lhs: FloatTensor<Flex>, rhs: Scalar) -> FloatTensor<Flex> {
180        let rhs_val = rhs.to_f64().unwrap();
181        scalar_op(lhs, rhs_val, |a, b| a * b, |a, b| a * b)
182    }
183
184    fn float_div(lhs: FloatTensor<Flex>, rhs: FloatTensor<Flex>) -> FloatTensor<Flex> {
185        binary_op(lhs, rhs, |a, b| a / b, |a, b| a / b, Some(BinaryOp::Div))
186    }
187
188    fn float_div_scalar(lhs: FloatTensor<Flex>, rhs: Scalar) -> FloatTensor<Flex> {
189        let rhs_val = rhs.to_f64().unwrap();
190        scalar_op(lhs, rhs_val, |a, b| a / b, |a, b| a / b)
191    }
192
193    fn float_remainder(lhs: FloatTensor<Flex>, rhs: FloatTensor<Flex>) -> FloatTensor<Flex> {
194        // Python/PyTorch-style remainder: result has same sign as divisor
195        binary_op(lhs, rhs, remainder_float, remainder_float, None)
196    }
197
198    fn float_remainder_scalar(lhs: FloatTensor<Flex>, rhs: Scalar) -> FloatTensor<Flex> {
199        // Python/PyTorch-style remainder: result has same sign as divisor
200        scalar_op(lhs, rhs.to_f64().unwrap(), remainder_float, remainder_float)
201    }
202
203    fn float_fmod(lhs: FloatTensor<Flex>, rhs: FloatTensor<Flex>) -> FloatTensor<Flex> {
204        // Rust's `%` on floats is C's fmod: result has same sign as dividend
205        binary_op(lhs, rhs, |a, b| a % b, |a, b| a % b, None)
206    }
207
208    fn float_fmod_scalar(lhs: FloatTensor<Flex>, rhs: Scalar) -> FloatTensor<Flex> {
209        scalar_op(lhs, rhs.to_f64().unwrap(), |a, b| a % b, |a, b| a % b)
210    }
211
212    fn float_matmul(lhs: FloatTensor<Flex>, rhs: FloatTensor<Flex>) -> FloatTensor<Flex> {
213        matmul::matmul(lhs, rhs)
214    }
215
216    fn float_cross(
217        lhs: FloatTensor<Flex>,
218        rhs: FloatTensor<Flex>,
219        dim: usize,
220    ) -> FloatTensor<Flex> {
221        let shape = lhs.layout().shape();
222        let ndims = shape.num_dims();
223        assert_eq!(
224            shape[dim], 3,
225            "cross product requires dimension {} to have size 3, got {}",
226            dim, shape[dim]
227        );
228
229        // Helper to create slices that select index `idx` along `dim`
230        let make_slices = |idx: usize| -> alloc::vec::Vec<Slice> {
231            (0..ndims)
232                .map(|d| {
233                    if d == dim {
234                        Slice::new(idx as isize, Some((idx + 1) as isize), 1)
235                    } else {
236                        Slice::new(0, None, 1)
237                    }
238                })
239                .collect()
240        };
241
242        // Extract components along the dimension
243        // a = [a0, a1, a2], b = [b0, b1, b2]
244        let a0 = Self::float_slice(lhs.clone(), &make_slices(0));
245        let a1 = Self::float_slice(lhs.clone(), &make_slices(1));
246        let a2 = Self::float_slice(lhs, &make_slices(2));
247
248        let b0 = Self::float_slice(rhs.clone(), &make_slices(0));
249        let b1 = Self::float_slice(rhs.clone(), &make_slices(1));
250        let b2 = Self::float_slice(rhs, &make_slices(2));
251
252        // Cross product: c = a × b
253        // c0 = a1*b2 - a2*b1
254        // c1 = a2*b0 - a0*b2
255        // c2 = a0*b1 - a1*b0
256        let c0 = Self::float_sub(
257            Self::float_mul(a1.clone(), b2.clone()),
258            Self::float_mul(a2.clone(), b1.clone()),
259        );
260        let c1 = Self::float_sub(
261            Self::float_mul(a2, b0.clone()),
262            Self::float_mul(a0.clone(), b2),
263        );
264        let c2 = Self::float_sub(Self::float_mul(a0, b1), Self::float_mul(a1, b0));
265
266        // Concatenate along the dimension
267        Self::float_cat(vec![c0, c1, c2], dim)
268    }
269
270    fn float_recip(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
271        unary::recip(tensor)
272    }
273
274    fn float_swap_dims(tensor: FloatTensor<Flex>, dim1: usize, dim2: usize) -> FloatTensor<Flex> {
275        tensor.transpose(dim1, dim2)
276    }
277
278    fn float_permute(tensor: FloatTensor<Flex>, axes: &[usize]) -> FloatTensor<Flex> {
279        tensor.permute(axes)
280    }
281
282    fn float_flip(tensor: FloatTensor<Flex>, axes: &[usize]) -> FloatTensor<Flex> {
283        crate::ops::flip::flip(tensor, axes)
284    }
285
286    fn float_cat(tensors: Vec<FloatTensor<Flex>>, dim: usize) -> FloatTensor<Flex> {
287        crate::ops::cat::cat(tensors, dim)
288    }
289
290    fn float_reshape(tensor: FloatTensor<Flex>, shape: Shape) -> FloatTensor<Flex> {
291        tensor.reshape(shape)
292    }
293
294    fn float_gather(
295        dim: usize,
296        tensor: FloatTensor<Flex>,
297        indices: IntTensor<Flex>,
298    ) -> FloatTensor<Flex> {
299        match tensor.dtype() {
300            DType::F32 => crate::ops::gather_scatter::gather::<f32>(tensor, dim, indices),
301            DType::F64 => crate::ops::gather_scatter::gather::<f64>(tensor, dim, indices),
302            DType::F16 => crate::ops::gather_scatter::gather::<f16>(tensor, dim, indices),
303            DType::BF16 => crate::ops::gather_scatter::gather::<bf16>(tensor, dim, indices),
304            _ => panic!("float_gather: unsupported dtype {:?}", tensor.dtype()),
305        }
306    }
307
308    fn float_scatter(
309        dim: usize,
310        tensor: FloatTensor<Flex>,
311        indices: IntTensor<Flex>,
312        value: FloatTensor<Flex>,
313        update: burn_backend::tensor::IndexingUpdateOp,
314    ) -> FloatTensor<Flex> {
315        match update {
316            burn_backend::tensor::IndexingUpdateOp::Assign => match tensor.dtype() {
317                DType::F32 => {
318                    crate::ops::gather_scatter::scatter_assign::<f32>(tensor, dim, indices, value)
319                }
320                DType::F64 => {
321                    crate::ops::gather_scatter::scatter_assign::<f64>(tensor, dim, indices, value)
322                }
323                DType::F16 => {
324                    crate::ops::gather_scatter::scatter_assign::<f16>(tensor, dim, indices, value)
325                }
326                DType::BF16 => {
327                    crate::ops::gather_scatter::scatter_assign::<bf16>(tensor, dim, indices, value)
328                }
329                _ => panic!("float_scatter: unsupported dtype {:?}", tensor.dtype()),
330            },
331            burn_backend::tensor::IndexingUpdateOp::Add => match tensor.dtype() {
332                DType::F32 => {
333                    crate::ops::gather_scatter::scatter_add::<f32>(tensor, dim, indices, value)
334                }
335                DType::F64 => {
336                    crate::ops::gather_scatter::scatter_add::<f64>(tensor, dim, indices, value)
337                }
338                DType::F16 => {
339                    crate::ops::gather_scatter::scatter_add::<f16>(tensor, dim, indices, value)
340                }
341                DType::BF16 => {
342                    crate::ops::gather_scatter::scatter_add::<bf16>(tensor, dim, indices, value)
343                }
344                _ => panic!("float_scatter: unsupported dtype {:?}", tensor.dtype()),
345            },
346            burn_backend::tensor::IndexingUpdateOp::Mul => match tensor.dtype() {
347                DType::F32 => {
348                    crate::ops::gather_scatter::scatter_mul::<f32>(tensor, dim, indices, value)
349                }
350                DType::F64 => {
351                    crate::ops::gather_scatter::scatter_mul::<f64>(tensor, dim, indices, value)
352                }
353                DType::F16 => {
354                    crate::ops::gather_scatter::scatter_mul::<f16>(tensor, dim, indices, value)
355                }
356                DType::BF16 => {
357                    crate::ops::gather_scatter::scatter_mul::<bf16>(tensor, dim, indices, value)
358                }
359                _ => panic!("float_scatter: unsupported dtype {:?}", tensor.dtype()),
360            },
361            burn_backend::tensor::IndexingUpdateOp::Min => match tensor.dtype() {
362                DType::F32 => {
363                    crate::ops::gather_scatter::scatter_min::<f32>(tensor, dim, indices, value)
364                }
365                DType::F64 => {
366                    crate::ops::gather_scatter::scatter_min::<f64>(tensor, dim, indices, value)
367                }
368                DType::F16 => {
369                    crate::ops::gather_scatter::scatter_min::<f16>(tensor, dim, indices, value)
370                }
371                DType::BF16 => {
372                    crate::ops::gather_scatter::scatter_min::<bf16>(tensor, dim, indices, value)
373                }
374                _ => panic!("float_scatter: unsupported dtype {:?}", tensor.dtype()),
375            },
376            burn_backend::tensor::IndexingUpdateOp::Max => match tensor.dtype() {
377                DType::F32 => {
378                    crate::ops::gather_scatter::scatter_max::<f32>(tensor, dim, indices, value)
379                }
380                DType::F64 => {
381                    crate::ops::gather_scatter::scatter_max::<f64>(tensor, dim, indices, value)
382                }
383                DType::F16 => {
384                    crate::ops::gather_scatter::scatter_max::<f16>(tensor, dim, indices, value)
385                }
386                DType::BF16 => {
387                    crate::ops::gather_scatter::scatter_max::<bf16>(tensor, dim, indices, value)
388                }
389                _ => panic!("float_scatter: unsupported dtype {:?}", tensor.dtype()),
390            },
391        }
392    }
393
394    fn float_scatter_nd(
395        data: FloatTensor<Flex>,
396        indices: IntTensor<Flex>,
397        values: FloatTensor<Flex>,
398        reduction: burn_backend::tensor::IndexingUpdateOp,
399    ) -> FloatTensor<Flex> {
400        match data.dtype() {
401            DType::F32 => {
402                crate::ops::gather_scatter::scatter_nd::<f32>(data, indices, values, reduction)
403            }
404            DType::F64 => {
405                crate::ops::gather_scatter::scatter_nd::<f64>(data, indices, values, reduction)
406            }
407            DType::F16 => {
408                crate::ops::gather_scatter::scatter_nd::<f16>(data, indices, values, reduction)
409            }
410            DType::BF16 => {
411                crate::ops::gather_scatter::scatter_nd::<bf16>(data, indices, values, reduction)
412            }
413            _ => panic!("float_scatter_nd: unsupported dtype {:?}", data.dtype()),
414        }
415    }
416
417    fn float_gather_nd(data: FloatTensor<Flex>, indices: IntTensor<Flex>) -> FloatTensor<Flex> {
418        match data.dtype() {
419            DType::F32 => crate::ops::gather_scatter::gather_nd::<f32>(data, indices),
420            DType::F64 => crate::ops::gather_scatter::gather_nd::<f64>(data, indices),
421            DType::F16 => crate::ops::gather_scatter::gather_nd::<f16>(data, indices),
422            DType::BF16 => crate::ops::gather_scatter::gather_nd::<bf16>(data, indices),
423            _ => panic!("float_gather_nd: unsupported dtype {:?}", data.dtype()),
424        }
425    }
426
427    fn float_select(
428        tensor: FloatTensor<Flex>,
429        dim: usize,
430        indices: IntTensor<Flex>,
431    ) -> FloatTensor<Flex> {
432        match tensor.dtype() {
433            DType::F32 => crate::ops::gather_scatter::select::<f32>(tensor, dim, indices),
434            DType::F64 => crate::ops::gather_scatter::select::<f64>(tensor, dim, indices),
435            DType::F16 => crate::ops::gather_scatter::select::<f16>(tensor, dim, indices),
436            DType::BF16 => crate::ops::gather_scatter::select::<bf16>(tensor, dim, indices),
437            _ => panic!("float_select: unsupported dtype {:?}", tensor.dtype()),
438        }
439    }
440
441    fn float_select_assign(
442        tensor: FloatTensor<Flex>,
443        dim: usize,
444        indices: IntTensor<Flex>,
445        value: FloatTensor<Flex>,
446        update: burn_backend::tensor::IndexingUpdateOp,
447    ) -> FloatTensor<Flex> {
448        match update {
449            burn_backend::tensor::IndexingUpdateOp::Assign => match tensor.dtype() {
450                DType::F32 => {
451                    crate::ops::gather_scatter::select_assign::<f32>(tensor, dim, indices, value)
452                }
453                DType::F64 => {
454                    crate::ops::gather_scatter::select_assign::<f64>(tensor, dim, indices, value)
455                }
456                DType::F16 => {
457                    crate::ops::gather_scatter::select_assign::<f16>(tensor, dim, indices, value)
458                }
459                DType::BF16 => {
460                    crate::ops::gather_scatter::select_assign::<bf16>(tensor, dim, indices, value)
461                }
462                _ => panic!(
463                    "float_select_assign: unsupported dtype {:?}",
464                    tensor.dtype()
465                ),
466            },
467            burn_backend::tensor::IndexingUpdateOp::Add => match tensor.dtype() {
468                DType::F32 => {
469                    crate::ops::gather_scatter::select_add::<f32>(tensor, dim, indices, value)
470                }
471                DType::F64 => {
472                    crate::ops::gather_scatter::select_add::<f64>(tensor, dim, indices, value)
473                }
474                DType::F16 => {
475                    crate::ops::gather_scatter::select_add::<f16>(tensor, dim, indices, value)
476                }
477                DType::BF16 => {
478                    crate::ops::gather_scatter::select_add::<bf16>(tensor, dim, indices, value)
479                }
480                _ => panic!(
481                    "float_select_assign: unsupported dtype {:?}",
482                    tensor.dtype()
483                ),
484            },
485            burn_backend::tensor::IndexingUpdateOp::Mul => match tensor.dtype() {
486                DType::F32 => {
487                    crate::ops::gather_scatter::select_mul::<f32>(tensor, dim, indices, value)
488                }
489                DType::F64 => {
490                    crate::ops::gather_scatter::select_mul::<f64>(tensor, dim, indices, value)
491                }
492                DType::F16 => {
493                    crate::ops::gather_scatter::select_mul::<f16>(tensor, dim, indices, value)
494                }
495                DType::BF16 => {
496                    crate::ops::gather_scatter::select_mul::<bf16>(tensor, dim, indices, value)
497                }
498                _ => panic!(
499                    "float_select_assign: unsupported dtype {:?}",
500                    tensor.dtype()
501                ),
502            },
503            burn_backend::tensor::IndexingUpdateOp::Min => match tensor.dtype() {
504                DType::F32 => {
505                    crate::ops::gather_scatter::select_min::<f32>(tensor, dim, indices, value)
506                }
507                DType::F64 => {
508                    crate::ops::gather_scatter::select_min::<f64>(tensor, dim, indices, value)
509                }
510                DType::F16 => {
511                    crate::ops::gather_scatter::select_min::<f16>(tensor, dim, indices, value)
512                }
513                DType::BF16 => {
514                    crate::ops::gather_scatter::select_min::<bf16>(tensor, dim, indices, value)
515                }
516                _ => panic!(
517                    "float_select_assign: unsupported dtype {:?}",
518                    tensor.dtype()
519                ),
520            },
521            burn_backend::tensor::IndexingUpdateOp::Max => match tensor.dtype() {
522                DType::F32 => {
523                    crate::ops::gather_scatter::select_max::<f32>(tensor, dim, indices, value)
524                }
525                DType::F64 => {
526                    crate::ops::gather_scatter::select_max::<f64>(tensor, dim, indices, value)
527                }
528                DType::F16 => {
529                    crate::ops::gather_scatter::select_max::<f16>(tensor, dim, indices, value)
530                }
531                DType::BF16 => {
532                    crate::ops::gather_scatter::select_max::<bf16>(tensor, dim, indices, value)
533                }
534                _ => panic!(
535                    "float_select_assign: unsupported dtype {:?}",
536                    tensor.dtype()
537                ),
538            },
539        }
540    }
541
542    fn float_slice(tensor: FloatTensor<Flex>, slices: &[Slice]) -> FloatTensor<Flex> {
543        crate::ops::slice::slice(tensor, slices)
544    }
545
546    fn float_slice_assign(
547        tensor: FloatTensor<Flex>,
548        slices: &[Slice],
549        value: FloatTensor<Flex>,
550    ) -> FloatTensor<Flex> {
551        crate::ops::slice::slice_assign(tensor, slices, value)
552    }
553
554    fn float_mask_where(
555        tensor: FloatTensor<Flex>,
556        mask: BoolTensor<Flex>,
557        value: FloatTensor<Flex>,
558    ) -> FloatTensor<Flex> {
559        match tensor.dtype() {
560            DType::F32 => crate::ops::mask::mask_where_f32(tensor, mask, value),
561            DType::F64 => crate::ops::mask::mask_where_f64(tensor, mask, value),
562            DType::F16 => crate::ops::mask::mask_where_f16(tensor, mask, value),
563            DType::BF16 => crate::ops::mask::mask_where_bf16(tensor, mask, value),
564            dtype => panic!("float_mask_where: unsupported dtype {:?}", dtype),
565        }
566    }
567
568    fn float_mask_fill(
569        tensor: FloatTensor<Flex>,
570        mask: BoolTensor<Flex>,
571        value: Scalar,
572    ) -> FloatTensor<Flex> {
573        match tensor.dtype() {
574            DType::F32 => crate::ops::mask::mask_fill_f32(tensor, mask, value.to_f32().unwrap()),
575            DType::F64 => crate::ops::mask::mask_fill_f64(tensor, mask, value.to_f64().unwrap()),
576            DType::F16 => crate::ops::mask::mask_fill_f16(
577                tensor,
578                mask,
579                f16::from_f64(value.to_f64().unwrap()),
580            ),
581            DType::BF16 => crate::ops::mask::mask_fill_bf16(
582                tensor,
583                mask,
584                bf16::from_f64(value.to_f64().unwrap()),
585            ),
586            dtype => panic!("float_mask_fill: unsupported dtype {:?}", dtype),
587        }
588    }
589
590    fn float_equal(
591        lhs: FloatTensor<Flex>,
592        rhs: FloatTensor<Flex>,
593        out_dtype: burn_std::BoolDType,
594    ) -> BoolTensor<Flex> {
595        crate::ops::comparison::equal(lhs, rhs, out_dtype)
596    }
597
598    fn float_equal_elem(
599        lhs: FloatTensor<Flex>,
600        rhs: Scalar,
601        out_dtype: burn_std::BoolDType,
602    ) -> BoolTensor<Flex> {
603        crate::ops::comparison::equal_elem(lhs, rhs.to_f64().unwrap(), out_dtype)
604    }
605
606    fn float_greater(
607        lhs: FloatTensor<Flex>,
608        rhs: FloatTensor<Flex>,
609        out_dtype: burn_std::BoolDType,
610    ) -> BoolTensor<Flex> {
611        crate::ops::comparison::greater(lhs, rhs, out_dtype)
612    }
613
614    fn float_greater_elem(
615        lhs: FloatTensor<Flex>,
616        rhs: Scalar,
617        out_dtype: burn_std::BoolDType,
618    ) -> BoolTensor<Flex> {
619        crate::ops::comparison::greater_elem(lhs, rhs.to_f64().unwrap(), out_dtype)
620    }
621
622    fn float_greater_equal(
623        lhs: FloatTensor<Flex>,
624        rhs: FloatTensor<Flex>,
625        out_dtype: burn_std::BoolDType,
626    ) -> BoolTensor<Flex> {
627        crate::ops::comparison::greater_equal(lhs, rhs, out_dtype)
628    }
629
630    fn float_greater_equal_elem(
631        lhs: FloatTensor<Flex>,
632        rhs: Scalar,
633        out_dtype: burn_std::BoolDType,
634    ) -> BoolTensor<Flex> {
635        crate::ops::comparison::greater_equal_elem(lhs, rhs.to_f64().unwrap(), out_dtype)
636    }
637
638    fn float_lower(
639        lhs: FloatTensor<Flex>,
640        rhs: FloatTensor<Flex>,
641        out_dtype: burn_std::BoolDType,
642    ) -> BoolTensor<Flex> {
643        crate::ops::comparison::lower(lhs, rhs, out_dtype)
644    }
645
646    fn float_lower_elem(
647        lhs: FloatTensor<Flex>,
648        rhs: Scalar,
649        out_dtype: burn_std::BoolDType,
650    ) -> BoolTensor<Flex> {
651        crate::ops::comparison::lower_elem(lhs, rhs.to_f64().unwrap(), out_dtype)
652    }
653
654    fn float_lower_equal(
655        lhs: FloatTensor<Flex>,
656        rhs: FloatTensor<Flex>,
657        out_dtype: burn_std::BoolDType,
658    ) -> BoolTensor<Flex> {
659        crate::ops::comparison::lower_equal(lhs, rhs, out_dtype)
660    }
661
662    fn float_lower_equal_elem(
663        lhs: FloatTensor<Flex>,
664        rhs: Scalar,
665        out_dtype: burn_std::BoolDType,
666    ) -> BoolTensor<Flex> {
667        crate::ops::comparison::lower_equal_elem(lhs, rhs.to_f64().unwrap(), out_dtype)
668    }
669
670    fn float_not_equal(
671        lhs: FloatTensor<Flex>,
672        rhs: FloatTensor<Flex>,
673        out_dtype: burn_std::BoolDType,
674    ) -> BoolTensor<Flex> {
675        crate::ops::comparison::not_equal(lhs, rhs, out_dtype)
676    }
677
678    fn float_not_equal_elem(
679        lhs: FloatTensor<Flex>,
680        rhs: Scalar,
681        out_dtype: burn_std::BoolDType,
682    ) -> BoolTensor<Flex> {
683        crate::ops::comparison::not_equal_elem(lhs, rhs.to_f64().unwrap(), out_dtype)
684    }
685
686    fn float_neg(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
687        unary::unary_op(tensor, |x: f32| -x, |x: f64| -x)
688    }
689
690    fn float_clamp(tensor: FloatTensor<Flex>, min: Scalar, max: Scalar) -> FloatTensor<Flex> {
691        let min32 = min.to_f32().unwrap();
692        let max32 = max.to_f32().unwrap();
693        let min64 = min.to_f64().unwrap();
694        let max64 = max.to_f64().unwrap();
695        // `f32::clamp` panics on a NaN bound, so take that case out first and let the rest
696        // keep the native clamp, which decides signed zero and rejects `min > max`. A NaN
697        // bound gives NaN, agreeing with `clamp_min`/`clamp_max` below and with PyTorch.
698        unary::unary_op(
699            tensor,
700            move |x: f32| {
701                if min32.is_nan() || max32.is_nan() {
702                    f32::NAN
703                } else {
704                    x.clamp(min32, max32)
705                }
706            },
707            move |x: f64| {
708                if min64.is_nan() || max64.is_nan() {
709                    f64::NAN
710                } else {
711                    x.clamp(min64, max64)
712                }
713            },
714        )
715    }
716
717    fn float_clamp_min(tensor: FloatTensor<Flex>, min: Scalar) -> FloatTensor<Flex> {
718        let min32 = min.to_f32().unwrap();
719        let min64 = min.to_f64().unwrap();
720        // `max` returns the non-NaN operand, which would map NaN to the bound.
721        // Testing `is_nan` first lets NaN propagate, as PyTorch does.
722        unary::unary_op(
723            tensor,
724            move |x: f32| if x.is_nan() || x > min32 { x } else { min32 },
725            move |x: f64| if x.is_nan() || x > min64 { x } else { min64 },
726        )
727    }
728
729    fn float_clamp_max(tensor: FloatTensor<Flex>, max: Scalar) -> FloatTensor<Flex> {
730        let max32 = max.to_f32().unwrap();
731        let max64 = max.to_f64().unwrap();
732        // `min` returns the non-NaN operand, which would map NaN to the bound.
733        // Testing `is_nan` first lets NaN propagate, as PyTorch does.
734        unary::unary_op(
735            tensor,
736            move |x: f32| if x.is_nan() || x < max32 { x } else { max32 },
737            move |x: f64| if x.is_nan() || x < max64 { x } else { max64 },
738        )
739    }
740
741    // Uses `copysign` rather than a `> 0.0` / `< 0.0` branch chain. The branch
742    // chain is compiled into a lookup from a `[2 x float]` constant pool at -O2
743    // and above, and some backends (notably Xtensa) have no instruction
744    // selection pattern for a PC-relative reference to a constant pool, so the
745    // whole crate fails to compile for those targets. One scalar constant
746    // leaves the pool with nothing to hold.
747    //
748    // `copysign(1.0, -0.0)` is `-1.0`, and NaN isn't `== 0.0` on either sign
749    // bit, so both must be checked before falling into `copysign`
750    fn float_sign(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
751        unary::unary_op(
752            tensor,
753            |x: f32| {
754                if x.is_nan() || x == 0.0 {
755                    0.0
756                } else {
757                    libm::copysignf(1.0, x)
758                }
759            },
760            |x: f64| {
761                if x.is_nan() || x == 0.0 {
762                    0.0
763                } else {
764                    libm::copysign(1.0, x)
765                }
766            },
767        )
768    }
769
770    fn float_mean(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
771        crate::ops::reduce::mean(tensor)
772    }
773
774    fn float_max(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
775        crate::ops::reduce::max(tensor)
776    }
777
778    fn float_max_dim(tensor: FloatTensor<Flex>, dim: usize) -> FloatTensor<Flex> {
779        crate::ops::reduce::max_dim(tensor, dim)
780    }
781
782    fn float_min(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
783        crate::ops::reduce::min(tensor)
784    }
785
786    fn float_min_dim(tensor: FloatTensor<Flex>, dim: usize) -> FloatTensor<Flex> {
787        crate::ops::reduce::min_dim(tensor, dim)
788    }
789
790    fn float_max_dim_with_indices(
791        tensor: FloatTensor<Flex>,
792        dim: usize,
793        indices_dtype: burn_std::IntDType,
794    ) -> (FloatTensor<Flex>, IntTensor<Flex>) {
795        let (values, indices) = crate::ops::reduce::max_dim_with_indices(tensor, dim);
796        if indices.dtype() != DType::from(indices_dtype) {
797            (values, Flex::int_cast(indices, indices_dtype))
798        } else {
799            (values, indices)
800        }
801    }
802
803    fn float_min_dim_with_indices(
804        tensor: FloatTensor<Flex>,
805        dim: usize,
806        indices_dtype: burn_std::IntDType,
807    ) -> (FloatTensor<Flex>, IntTensor<Flex>) {
808        let (values, indices) = crate::ops::reduce::min_dim_with_indices(tensor, dim);
809        if indices.dtype() != DType::from(indices_dtype) {
810            (values, Flex::int_cast(indices, indices_dtype))
811        } else {
812            (values, indices)
813        }
814    }
815
816    fn float_any(tensor: FloatTensor<Flex>, out_dtype: burn_std::BoolDType) -> BoolTensor<Flex> {
817        crate::ops::comparison::any_float(tensor, out_dtype)
818    }
819
820    fn float_any_dim(
821        tensor: FloatTensor<Flex>,
822        dim: usize,
823        out_dtype: burn_std::BoolDType,
824    ) -> BoolTensor<Flex> {
825        crate::ops::comparison::any_float_dim(tensor, dim, out_dtype)
826    }
827
828    fn float_all(tensor: FloatTensor<Flex>, out_dtype: burn_std::BoolDType) -> BoolTensor<Flex> {
829        crate::ops::comparison::all_float(tensor, out_dtype)
830    }
831
832    fn float_all_dim(
833        tensor: FloatTensor<Flex>,
834        dim: usize,
835        out_dtype: burn_std::BoolDType,
836    ) -> BoolTensor<Flex> {
837        crate::ops::comparison::all_float_dim(tensor, dim, out_dtype)
838    }
839
840    fn float_sum(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
841        crate::ops::reduce::sum(tensor)
842    }
843
844    fn float_sum_dim(tensor: FloatTensor<Flex>, dim: usize) -> FloatTensor<Flex> {
845        crate::ops::reduce::sum_dim(tensor, dim)
846    }
847
848    fn float_mean_dim(tensor: FloatTensor<Flex>, dim: usize) -> FloatTensor<Flex> {
849        crate::ops::reduce::mean_dim(tensor, dim)
850    }
851
852    fn float_prod(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
853        crate::ops::reduce::prod(tensor)
854    }
855
856    fn float_prod_dim(tensor: FloatTensor<Flex>, dim: usize) -> FloatTensor<Flex> {
857        crate::ops::reduce::prod_dim(tensor, dim)
858    }
859
860    fn float_cumsum(tensor: FloatTensor<Flex>, dim: usize) -> FloatTensor<Flex> {
861        match tensor.dtype() {
862            DType::F32 => crate::ops::cumulative::cumsum_f32(tensor, dim),
863            DType::F64 => crate::ops::cumulative::cumsum_f64(tensor, dim),
864            DType::F16 => {
865                crate::ops::cumulative::cumsum_half(tensor, dim, f16::to_f32, f16::from_f32)
866            }
867            DType::BF16 => {
868                crate::ops::cumulative::cumsum_half(tensor, dim, bf16::to_f32, bf16::from_f32)
869            }
870            _ => panic!("float_cumsum: unsupported dtype {:?}", tensor.dtype()),
871        }
872    }
873
874    fn float_cumprod(tensor: FloatTensor<Flex>, dim: usize) -> FloatTensor<Flex> {
875        match tensor.dtype() {
876            DType::F32 => crate::ops::cumulative::cumprod_f32(tensor, dim),
877            DType::F64 => crate::ops::cumulative::cumprod_f64(tensor, dim),
878            DType::F16 => {
879                crate::ops::cumulative::cumprod_half(tensor, dim, f16::to_f32, f16::from_f32)
880            }
881            DType::BF16 => {
882                crate::ops::cumulative::cumprod_half(tensor, dim, bf16::to_f32, bf16::from_f32)
883            }
884            _ => panic!("float_cumprod: unsupported dtype {:?}", tensor.dtype()),
885        }
886    }
887
888    fn float_cummin(tensor: FloatTensor<Flex>, dim: usize) -> FloatTensor<Flex> {
889        match tensor.dtype() {
890            DType::F32 => crate::ops::cumulative::cummin_f32(tensor, dim),
891            DType::F64 => crate::ops::cumulative::cummin_f64(tensor, dim),
892            DType::F16 => {
893                crate::ops::cumulative::cummin_half(tensor, dim, f16::to_f32, f16::from_f32)
894            }
895            DType::BF16 => {
896                crate::ops::cumulative::cummin_half(tensor, dim, bf16::to_f32, bf16::from_f32)
897            }
898            _ => panic!("float_cummin: unsupported dtype {:?}", tensor.dtype()),
899        }
900    }
901
902    fn float_cummax(tensor: FloatTensor<Flex>, dim: usize) -> FloatTensor<Flex> {
903        match tensor.dtype() {
904            DType::F32 => crate::ops::cumulative::cummax_f32(tensor, dim),
905            DType::F64 => crate::ops::cumulative::cummax_f64(tensor, dim),
906            DType::F16 => {
907                crate::ops::cumulative::cummax_half(tensor, dim, f16::to_f32, f16::from_f32)
908            }
909            DType::BF16 => {
910                crate::ops::cumulative::cummax_half(tensor, dim, bf16::to_f32, bf16::from_f32)
911            }
912            _ => panic!("float_cummax: unsupported dtype {:?}", tensor.dtype()),
913        }
914    }
915
916    fn float_cast(tensor: FloatTensor<Flex>, dtype: FloatDType) -> FloatTensor<Flex> {
917        use crate::Layout;
918        use burn_std::{Bytes, bf16, f16};
919
920        let src_dtype = tensor.dtype();
921        let target_dtype = DType::from(dtype);
922
923        // No-op if already the same dtype
924        if src_dtype == target_dtype {
925            return tensor;
926        }
927
928        let tensor = tensor.to_contiguous();
929        let shape = tensor.layout().shape().clone();
930
931        fn cast_slice<Src: Element + bytemuck::Pod, Dst: Element + bytemuck::Pod>(
932            src: &[Src],
933        ) -> Vec<Dst> {
934            use core::any::TypeId;
935            if TypeId::of::<Src>() == TypeId::of::<f64>()
936                && TypeId::of::<Dst>() == TypeId::of::<f16>()
937            {
938                let f64_slice: &[f64] = bytemuck::cast_slice(src);
939                let mut out = vec![Dst::default(); src.len()];
940                let f16_slice: &mut [f16] = bytemuck::cast_slice_mut(&mut out);
941                for (dst, &v) in f16_slice.iter_mut().zip(f64_slice.iter()) {
942                    *dst = f16::from_f64(v);
943                }
944                return out;
945            }
946            if TypeId::of::<Src>() == TypeId::of::<f64>()
947                && TypeId::of::<Dst>() == TypeId::of::<bf16>()
948            {
949                let f64_slice: &[f64] = bytemuck::cast_slice(src);
950                let mut out = vec![Dst::default(); src.len()];
951                let bf16_slice: &mut [bf16] = bytemuck::cast_slice_mut(&mut out);
952                for (dst, &v) in bf16_slice.iter_mut().zip(f64_slice.iter()) {
953                    *dst = bf16::from_f64(v);
954                }
955                return out;
956            }
957            src.iter().map(|&v| Dst::from_elem(v)).collect()
958        }
959
960        macro_rules! cast_from {
961            ($src_ty:ty) => {{
962                let src: &[$src_ty] = tensor.storage();
963                match target_dtype {
964                    DType::F32 => {
965                        let result = cast_slice::<$src_ty, f32>(src);
966                        FlexTensor::new(
967                            Bytes::from_elems(result),
968                            Layout::contiguous(shape),
969                            DType::F32,
970                        )
971                    }
972                    DType::F64 => {
973                        let result = cast_slice::<$src_ty, f64>(src);
974                        FlexTensor::new(
975                            Bytes::from_elems(result),
976                            Layout::contiguous(shape),
977                            DType::F64,
978                        )
979                    }
980                    DType::F16 => {
981                        let result = cast_slice::<$src_ty, f16>(src);
982                        FlexTensor::new(
983                            Bytes::from_elems(result),
984                            Layout::contiguous(shape),
985                            DType::F16,
986                        )
987                    }
988                    DType::BF16 => {
989                        let result = cast_slice::<$src_ty, bf16>(src);
990                        FlexTensor::new(
991                            Bytes::from_elems(result),
992                            Layout::contiguous(shape),
993                            DType::BF16,
994                        )
995                    }
996                    _ => panic!("float_cast: unsupported target dtype {:?}", target_dtype),
997                }
998            }};
999        }
1000
1001        match src_dtype {
1002            DType::F32 => cast_from!(f32),
1003            DType::F64 => cast_from!(f64),
1004            DType::F16 => cast_from!(f16),
1005            DType::BF16 => cast_from!(bf16),
1006            _ => panic!("float_cast: unsupported source dtype {:?}", src_dtype),
1007        }
1008    }
1009
1010    fn float_exp(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
1011        unary::exp(tensor)
1012    }
1013
1014    fn float_log(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
1015        unary::log(tensor)
1016    }
1017
1018    fn float_log1p(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
1019        unary::log1p(tensor)
1020    }
1021
1022    fn float_powf(lhs: FloatTensor<Flex>, rhs: FloatTensor<Flex>) -> FloatTensor<Flex> {
1023        binary_op(lhs, rhs, |a: f32, b| a.powf(b), |a: f64, b| a.powf(b), None)
1024    }
1025
1026    fn float_powf_scalar_impl(tensor: FloatTensor<Flex>, value: Scalar) -> FloatTensor<Flex> {
1027        let exp = value.to_f64().unwrap();
1028        scalar_op(tensor, exp, |a: f32, b| a.powf(b), |a: f64, b| a.powf(b))
1029    }
1030
1031    fn float_sqrt(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
1032        unary::sqrt(tensor)
1033    }
1034
1035    fn float_abs(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
1036        unary::abs(tensor)
1037    }
1038
1039    fn float_cos(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
1040        unary::cos(tensor)
1041    }
1042
1043    fn float_sin(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
1044        unary::sin(tensor)
1045    }
1046
1047    fn float_tan(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
1048        unary::tan(tensor)
1049    }
1050
1051    fn float_cosh(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
1052        unary::cosh(tensor)
1053    }
1054
1055    fn float_sinh(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
1056        unary::sinh(tensor)
1057    }
1058
1059    fn float_tanh(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
1060        unary::tanh(tensor)
1061    }
1062
1063    fn float_acos(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
1064        unary::acos(tensor)
1065    }
1066
1067    fn float_acosh(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
1068        unary::acosh(tensor)
1069    }
1070
1071    fn float_asin(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
1072        unary::asin(tensor)
1073    }
1074
1075    fn float_asinh(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
1076        unary::asinh(tensor)
1077    }
1078
1079    fn float_atan(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
1080        unary::atan(tensor)
1081    }
1082
1083    fn float_atanh(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
1084        unary::atanh(tensor)
1085    }
1086
1087    fn float_atan2(lhs: FloatTensor<Flex>, rhs: FloatTensor<Flex>) -> FloatTensor<Flex> {
1088        binary_op(
1089            lhs,
1090            rhs,
1091            |a: f32, b| a.atan2(b),
1092            |a: f64, b| a.atan2(b),
1093            None,
1094        )
1095    }
1096
1097    fn float_round(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
1098        unary::round(tensor)
1099    }
1100
1101    fn float_floor(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
1102        unary::floor(tensor)
1103    }
1104
1105    fn float_ceil(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
1106        unary::ceil(tensor)
1107    }
1108
1109    fn float_trunc(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
1110        unary::trunc(tensor)
1111    }
1112
1113    fn float_erf(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
1114        unary::erf(tensor)
1115    }
1116
1117    fn float_argmax(
1118        tensor: FloatTensor<Flex>,
1119        dim: usize,
1120        out_dtype: burn_std::IntDType,
1121    ) -> IntTensor<Flex> {
1122        let result = crate::ops::reduce::argmax(tensor, dim);
1123        if result.dtype() != DType::from(out_dtype) {
1124            Flex::int_cast(result, out_dtype)
1125        } else {
1126            result
1127        }
1128    }
1129
1130    fn float_argmin(
1131        tensor: FloatTensor<Flex>,
1132        dim: usize,
1133        out_dtype: burn_std::IntDType,
1134    ) -> IntTensor<Flex> {
1135        let result = crate::ops::reduce::argmin(tensor, dim);
1136        if result.dtype() != DType::from(out_dtype) {
1137            Flex::int_cast(result, out_dtype)
1138        } else {
1139            result
1140        }
1141    }
1142
1143    fn float_expand(tensor: FloatTensor<Flex>, shape: Shape) -> FloatTensor<Flex> {
1144        crate::ops::expand::expand(tensor, shape)
1145    }
1146
1147    fn float_unfold(
1148        tensor: FloatTensor<Flex>,
1149        dim: usize,
1150        size: usize,
1151        step: usize,
1152    ) -> FloatTensor<Flex> {
1153        // unfold is now type-agnostic (zero-copy strided view)
1154        crate::ops::unfold::unfold(tensor, dim, size, step)
1155    }
1156
1157    fn float_grid_sample_2d(
1158        tensor: FloatTensor<Flex>,
1159        grid: FloatTensor<Flex>,
1160        options: GridSampleOptions,
1161    ) -> FloatTensor<Flex> {
1162        crate::ops::grid_sample::grid_sample_2d(tensor, grid, options)
1163    }
1164
1165    fn float_zeros(shape: Shape, _device: &Device<Flex>, dtype: FloatDType) -> FloatTensor<Flex> {
1166        FlexTensor::zeros(shape, dtype.into())
1167    }
1168
1169    fn float_ones(shape: Shape, _device: &Device<Flex>, dtype: FloatDType) -> FloatTensor<Flex> {
1170        let dt: burn_backend::DType = dtype.into();
1171        match dt {
1172            DType::F32 => FlexTensor::filled_typed(shape, dt, 1.0f32),
1173            DType::F64 => FlexTensor::filled_typed(shape, dt, 1.0f64),
1174            DType::F16 => FlexTensor::filled_typed(shape, dt, f16::ONE),
1175            DType::BF16 => FlexTensor::filled_typed(shape, dt, bf16::ONE),
1176            _ => unreachable!(),
1177        }
1178    }
1179
1180    fn float_full(
1181        shape: Shape,
1182        fill_value: Scalar,
1183        _device: &Device<Flex>,
1184        dtype: FloatDType,
1185    ) -> FloatTensor<Flex> {
1186        let dt: burn_backend::DType = dtype.into();
1187        match dt {
1188            DType::F32 => FlexTensor::filled_typed(shape, dt, fill_value.to_f32().unwrap()),
1189            DType::F64 => FlexTensor::filled_typed(shape, dt, fill_value.to_f64().unwrap()),
1190            DType::F16 => {
1191                FlexTensor::filled_typed(shape, dt, f16::from_f32(fill_value.to_f32().unwrap()))
1192            }
1193            DType::BF16 => {
1194                FlexTensor::filled_typed(shape, dt, bf16::from_f32(fill_value.to_f32().unwrap()))
1195            }
1196            _ => unreachable!(),
1197        }
1198    }
1199
1200    fn float_transpose(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
1201        let ndims = tensor.layout().num_dims();
1202        if ndims < 2 {
1203            return tensor;
1204        }
1205        tensor.transpose(ndims - 2, ndims - 1)
1206    }
1207
1208    fn float_repeat_dim(tensor: FloatTensor<Flex>, dim: usize, times: usize) -> FloatTensor<Flex> {
1209        crate::ops::repeat_dim::repeat_dim(tensor, dim, times)
1210    }
1211
1212    fn float_sort(tensor: FloatTensor<Flex>, dim: usize, descending: bool) -> FloatTensor<Flex> {
1213        crate::ops::sort::sort(tensor, dim, descending)
1214    }
1215
1216    fn float_sort_with_indices(
1217        tensor: FloatTensor<Flex>,
1218        dim: usize,
1219        descending: bool,
1220        indices_dtype: burn_std::IntDType,
1221    ) -> (FloatTensor<Flex>, IntTensor<Flex>) {
1222        let (values, indices) = crate::ops::sort::sort_with_indices(tensor, dim, descending);
1223        let indices = if indices.dtype() != DType::from(indices_dtype) {
1224            Flex::int_cast(indices, indices_dtype)
1225        } else {
1226            indices
1227        };
1228        (values, indices)
1229    }
1230
1231    fn float_argsort(
1232        tensor: FloatTensor<Flex>,
1233        dim: usize,
1234        descending: bool,
1235        out_dtype: burn_std::IntDType,
1236    ) -> IntTensor<Flex> {
1237        let indices = crate::ops::sort::argsort(tensor, dim, descending);
1238        if indices.dtype() != DType::from(out_dtype) {
1239            Flex::int_cast(indices, out_dtype)
1240        } else {
1241            indices
1242        }
1243    }
1244
1245    fn float_powi(lhs: FloatTensor<Flex>, rhs: IntTensor<Flex>) -> FloatTensor<Flex> {
1246        let dtype = lhs.dtype();
1247        Self::float_powf(lhs, Flex::int_into_float(rhs, dtype.into()))
1248    }
1249
1250    fn float_powi_scalar(lhs: FloatTensor<Flex>, rhs: Scalar) -> FloatTensor<Flex> {
1251        match rhs.to_i64().unwrap() {
1252            0 => Self::float_ones(lhs.shape(), &Default::default(), lhs.dtype().into()),
1253            1 => lhs,
1254            2 => Self::float_mul(lhs.clone(), lhs),
1255            -1 => Self::float_recip(lhs),
1256            -2 => Self::float_recip(Self::float_mul(lhs.clone(), lhs)),
1257            _ => Self::float_powi_scalar_impl(lhs, rhs),
1258        }
1259    }
1260
1261    fn float_powi_scalar_impl(lhs: FloatTensor<Flex>, rhs: Scalar) -> FloatTensor<Flex> {
1262        let exponent = rhs.to_f64().unwrap();
1263        if f64::from(exponent as i32) != exponent {
1264            return Self::float_powf_scalar_impl(lhs, rhs);
1265        }
1266        let exponent = exponent as i32;
1267        unary::unary_op(
1268            lhs,
1269            |x| powi_float(x, exponent),
1270            |x| powi_float(x, exponent),
1271        )
1272    }
1273
1274    fn float_powf_scalar(tensor: FloatTensor<Flex>, value: Scalar) -> FloatTensor<Flex> {
1275        if let Some(exp) = value.try_as_integer() {
1276            Self::float_powi_scalar(tensor, exp)
1277        } else {
1278            Self::float_powf_scalar_impl(tensor, value)
1279        }
1280    }
1281
1282    fn float_max_abs(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
1283        let abs = unary::abs(tensor);
1284        crate::ops::reduce::max(abs)
1285    }
1286
1287    fn float_max_abs_dim(tensor: FloatTensor<Flex>, dim: usize) -> FloatTensor<Flex> {
1288        let abs = unary::abs(tensor);
1289        crate::ops::reduce::max_dim(abs, dim)
1290    }
1291
1292    fn float_is_nan(tensor: FloatTensor<Flex>, out_dtype: burn_std::BoolDType) -> BoolTensor<Flex> {
1293        unary::float_predicate(tensor, out_dtype, |x: f32| x.is_nan(), |x: f64| x.is_nan())
1294    }
1295
1296    fn float_is_inf(tensor: FloatTensor<Flex>, out_dtype: burn_std::BoolDType) -> BoolTensor<Flex> {
1297        unary::float_predicate(
1298            tensor,
1299            out_dtype,
1300            |x: f32| x.is_infinite(),
1301            |x: f64| x.is_infinite(),
1302        )
1303    }
1304
1305    fn float_hypot(lhs: FloatTensor<Flex>, rhs: FloatTensor<Flex>) -> FloatTensor<Flex> {
1306        binary_op(
1307            lhs,
1308            rhs,
1309            |a: f32, b| a.hypot(b),
1310            |a: f64, b| a.hypot(b),
1311            None,
1312        )
1313    }
1314}
1315
1316// Tests kept here exercise flex-specific behavior: direct `Flex::`
1317// backend-op calls with explicit IntDType/FloatDType to pin dtype storage
1318// selection (U8/I32/I64, F16/F64). Plain arithmetic, math, cast, cross,
1319// unfold, and random smoke tests have been dropped in favor of the
1320// equivalent coverage in burn-backend-tests, which exercises every backend.
1321// When adding new tests, keep them here only if they probe flex dtype
1322// storage or flex internals; otherwise add them to
1323// crates/burn-backend-tests/tests/tensor/float/ops/.
1324#[cfg(test)]
1325mod tests {
1326    use burn_backend::TensorData;
1327
1328    use crate::Flex;
1329
1330    #[test]
1331    fn powi_scalar_preserves_f32_subnormal() {
1332        use burn_backend::ops::FloatTensorOps;
1333
1334        let tensor = crate::FlexTensor::from_data(TensorData::from([2.0f32]));
1335        Flex::float_powi_scalar(tensor, (-149).into())
1336            .into_data()
1337            .assert_eq(&TensorData::from([f32::from_bits(1)]), true);
1338    }
1339
1340    #[test]
1341    fn powi_scalar_preserves_f64_subnormal() {
1342        use burn_backend::ops::FloatTensorOps;
1343
1344        let tensor = crate::FlexTensor::from_data(TensorData::from([2.0f64]));
1345        Flex::float_powi_scalar(tensor, (-1074).into())
1346            .into_data()
1347            .assert_eq(&TensorData::from([f64::from_bits(1)]), true);
1348    }
1349
1350    #[test]
1351    fn test_float_into_int_i32() {
1352        use burn_backend::ops::FloatTensorOps;
1353        use burn_std::IntDType;
1354
1355        let t = crate::FlexTensor::from_data(TensorData::from([1.5f32, -2.7, 0.0, 255.9]));
1356        let result = Flex::float_into_int(t, IntDType::I32);
1357        assert_eq!(result.dtype(), burn_backend::DType::I32);
1358        let data: Vec<i32> = result.into_data().try_into_vec().unwrap();
1359        assert_eq!(data, vec![1, -2, 0, 255]);
1360    }
1361
1362    #[test]
1363    fn test_float_into_int_u8() {
1364        use burn_backend::ops::FloatTensorOps;
1365        use burn_std::IntDType;
1366
1367        let t = crate::FlexTensor::from_data(TensorData::from([0.0f32, 1.9, 127.5, 255.0]));
1368        let result = Flex::float_into_int(t, IntDType::U8);
1369        assert_eq!(result.dtype(), burn_backend::DType::U8);
1370        let data: Vec<u8> = result.into_data().try_into_vec().unwrap();
1371        assert_eq!(data, vec![0, 1, 127, 255]);
1372    }
1373
1374    #[test]
1375    fn test_float_argmax_i32_out_dtype() {
1376        use burn_backend::ops::FloatTensorOps;
1377        use burn_std::IntDType;
1378
1379        let t = crate::FlexTensor::from_data(TensorData::from([[1.0f32, 3.0, 2.0]]));
1380        let result = Flex::float_argmax(t, 1, IntDType::I32);
1381        assert_eq!(result.dtype(), burn_backend::DType::I32);
1382        let data: Vec<i32> = result.into_data().try_into_vec().unwrap();
1383        assert_eq!(data, vec![1]);
1384    }
1385
1386    #[test]
1387    fn test_float_argmin_i32_out_dtype() {
1388        use burn_backend::ops::FloatTensorOps;
1389        use burn_std::IntDType;
1390
1391        let t = crate::FlexTensor::from_data(TensorData::from([[3.0f32, 1.0, 2.0]]));
1392        let result = Flex::float_argmin(t, 1, IntDType::I32);
1393        assert_eq!(result.dtype(), burn_backend::DType::I32);
1394        let data: Vec<i32> = result.into_data().try_into_vec().unwrap();
1395        assert_eq!(data, vec![1]);
1396    }
1397
1398    #[test]
1399    fn test_float_argmax_i64_out_dtype() {
1400        use burn_backend::ops::FloatTensorOps;
1401        use burn_std::IntDType;
1402
1403        let t = crate::FlexTensor::from_data(TensorData::from([[1.0f32, 3.0, 2.0]]));
1404        let result = Flex::float_argmax(t, 1, IntDType::I64);
1405        assert_eq!(result.dtype(), burn_backend::DType::I64);
1406        let data: Vec<i64> = result.into_data().try_into_vec().unwrap();
1407        assert_eq!(data, vec![1]);
1408    }
1409
1410    #[test]
1411    fn test_float_max_dim_with_indices_i32() {
1412        use burn_backend::ops::FloatTensorOps;
1413        use burn_std::IntDType;
1414
1415        let t = crate::FlexTensor::from_data(TensorData::from([[1.0f32, 5.0], [3.0, 2.0]]));
1416        let (values, indices) = Flex::float_max_dim_with_indices(t, 1, IntDType::I32);
1417        assert_eq!(indices.dtype(), burn_backend::DType::I32);
1418        let idx: Vec<i32> = indices.into_data().try_into_vec().unwrap();
1419        assert_eq!(idx, vec![1, 0]);
1420        let vals: Vec<f32> = values.into_data().try_into_vec().unwrap();
1421        assert_eq!(vals, vec![5.0, 3.0]);
1422    }
1423
1424    #[test]
1425    fn test_float_min_dim_with_indices_i32() {
1426        use burn_backend::ops::FloatTensorOps;
1427        use burn_std::IntDType;
1428
1429        let t = crate::FlexTensor::from_data(TensorData::from([[1.0f32, 5.0], [3.0, 2.0]]));
1430        let (values, indices) = Flex::float_min_dim_with_indices(t, 1, IntDType::I32);
1431        assert_eq!(indices.dtype(), burn_backend::DType::I32);
1432        let idx: Vec<i32> = indices.into_data().try_into_vec().unwrap();
1433        assert_eq!(idx, vec![0, 1]);
1434        let vals: Vec<f32> = values.into_data().try_into_vec().unwrap();
1435        assert_eq!(vals, vec![1.0, 2.0]);
1436    }
1437
1438    #[test]
1439    fn test_float_random_f64() {
1440        use burn_backend::{DType, FloatDType, ops::FloatTensorOps};
1441
1442        let shape = burn_std::Shape::from(vec![100]);
1443        let dist = burn_backend::Distribution::Uniform(0.0, 1.0);
1444        let device = crate::FlexDevice;
1445        let t = Flex::float_random(shape, dist, &device, FloatDType::F64);
1446        assert_eq!(t.dtype(), DType::F64);
1447        let data: Vec<f64> = t.into_data().try_into_vec().unwrap();
1448        assert!(data.iter().all(|&v| (0.0..=1.0).contains(&v)));
1449    }
1450
1451    #[test]
1452    fn test_float_random_f16() {
1453        use burn_backend::{DType, FloatDType, ops::FloatTensorOps};
1454
1455        let shape = burn_std::Shape::from(vec![100]);
1456        let dist = burn_backend::Distribution::Uniform(0.0, 1.0);
1457        let device = crate::FlexDevice;
1458        let t = Flex::float_random(shape, dist, &device, FloatDType::F16);
1459        assert_eq!(t.dtype(), DType::F16);
1460    }
1461}