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