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