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().unwrap();
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    fn float_sign(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
559        unary::unary_op(
560            tensor,
561            |x: f32| {
562                if x.is_nan() {
563                    x
564                } else if x > 0.0 {
565                    1.0
566                } else if x < 0.0 {
567                    -1.0
568                } else {
569                    0.0
570                }
571            },
572            |x: f64| {
573                if x.is_nan() {
574                    x
575                } else if x > 0.0 {
576                    1.0
577                } else if x < 0.0 {
578                    -1.0
579                } else {
580                    0.0
581                }
582            },
583        )
584    }
585
586    fn float_mean(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
587        crate::ops::reduce::mean(tensor)
588    }
589
590    fn float_max(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
591        crate::ops::reduce::max(tensor)
592    }
593
594    fn float_max_dim(tensor: FloatTensor<Flex>, dim: usize) -> FloatTensor<Flex> {
595        crate::ops::reduce::max_dim(tensor, dim)
596    }
597
598    fn float_min(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
599        crate::ops::reduce::min(tensor)
600    }
601
602    fn float_min_dim(tensor: FloatTensor<Flex>, dim: usize) -> FloatTensor<Flex> {
603        crate::ops::reduce::min_dim(tensor, dim)
604    }
605
606    fn float_max_dim_with_indices(
607        tensor: FloatTensor<Flex>,
608        dim: usize,
609        indices_dtype: burn_std::IntDType,
610    ) -> (FloatTensor<Flex>, IntTensor<Flex>) {
611        let (values, indices) = crate::ops::reduce::max_dim_with_indices(tensor, dim);
612        if indices.dtype() != DType::from(indices_dtype) {
613            (values, Flex::int_cast(indices, indices_dtype))
614        } else {
615            (values, indices)
616        }
617    }
618
619    fn float_min_dim_with_indices(
620        tensor: FloatTensor<Flex>,
621        dim: usize,
622        indices_dtype: burn_std::IntDType,
623    ) -> (FloatTensor<Flex>, IntTensor<Flex>) {
624        let (values, indices) = crate::ops::reduce::min_dim_with_indices(tensor, dim);
625        if indices.dtype() != DType::from(indices_dtype) {
626            (values, Flex::int_cast(indices, indices_dtype))
627        } else {
628            (values, indices)
629        }
630    }
631
632    fn float_any(tensor: FloatTensor<Flex>, out_dtype: burn_std::BoolDType) -> BoolTensor<Flex> {
633        crate::ops::comparison::any_float(tensor, out_dtype)
634    }
635
636    fn float_any_dim(
637        tensor: FloatTensor<Flex>,
638        dim: usize,
639        out_dtype: burn_std::BoolDType,
640    ) -> BoolTensor<Flex> {
641        crate::ops::comparison::any_float_dim(tensor, dim, out_dtype)
642    }
643
644    fn float_all(tensor: FloatTensor<Flex>, out_dtype: burn_std::BoolDType) -> BoolTensor<Flex> {
645        crate::ops::comparison::all_float(tensor, out_dtype)
646    }
647
648    fn float_all_dim(
649        tensor: FloatTensor<Flex>,
650        dim: usize,
651        out_dtype: burn_std::BoolDType,
652    ) -> BoolTensor<Flex> {
653        crate::ops::comparison::all_float_dim(tensor, dim, out_dtype)
654    }
655
656    fn float_sum(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
657        crate::ops::reduce::sum(tensor)
658    }
659
660    fn float_sum_dim(tensor: FloatTensor<Flex>, dim: usize) -> FloatTensor<Flex> {
661        crate::ops::reduce::sum_dim(tensor, dim)
662    }
663
664    fn float_mean_dim(tensor: FloatTensor<Flex>, dim: usize) -> FloatTensor<Flex> {
665        crate::ops::reduce::mean_dim(tensor, dim)
666    }
667
668    fn float_prod(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
669        crate::ops::reduce::prod(tensor)
670    }
671
672    fn float_prod_dim(tensor: FloatTensor<Flex>, dim: usize) -> FloatTensor<Flex> {
673        crate::ops::reduce::prod_dim(tensor, dim)
674    }
675
676    fn float_cumsum(tensor: FloatTensor<Flex>, dim: usize) -> FloatTensor<Flex> {
677        match tensor.dtype() {
678            DType::F32 => crate::ops::cumulative::cumsum_f32(tensor, dim),
679            DType::F64 => crate::ops::cumulative::cumsum_f64(tensor, dim),
680            DType::F16 => {
681                crate::ops::cumulative::cumsum_half(tensor, dim, f16::to_f32, f16::from_f32)
682            }
683            DType::BF16 => {
684                crate::ops::cumulative::cumsum_half(tensor, dim, bf16::to_f32, bf16::from_f32)
685            }
686            _ => panic!("float_cumsum: unsupported dtype {:?}", tensor.dtype()),
687        }
688    }
689
690    fn float_cumprod(tensor: FloatTensor<Flex>, dim: usize) -> FloatTensor<Flex> {
691        match tensor.dtype() {
692            DType::F32 => crate::ops::cumulative::cumprod_f32(tensor, dim),
693            DType::F64 => crate::ops::cumulative::cumprod_f64(tensor, dim),
694            DType::F16 => {
695                crate::ops::cumulative::cumprod_half(tensor, dim, f16::to_f32, f16::from_f32)
696            }
697            DType::BF16 => {
698                crate::ops::cumulative::cumprod_half(tensor, dim, bf16::to_f32, bf16::from_f32)
699            }
700            _ => panic!("float_cumprod: unsupported dtype {:?}", tensor.dtype()),
701        }
702    }
703
704    fn float_cummin(tensor: FloatTensor<Flex>, dim: usize) -> FloatTensor<Flex> {
705        match tensor.dtype() {
706            DType::F32 => crate::ops::cumulative::cummin_f32(tensor, dim),
707            DType::F64 => crate::ops::cumulative::cummin_f64(tensor, dim),
708            DType::F16 => {
709                crate::ops::cumulative::cummin_half(tensor, dim, f16::to_f32, f16::from_f32)
710            }
711            DType::BF16 => {
712                crate::ops::cumulative::cummin_half(tensor, dim, bf16::to_f32, bf16::from_f32)
713            }
714            _ => panic!("float_cummin: unsupported dtype {:?}", tensor.dtype()),
715        }
716    }
717
718    fn float_cummax(tensor: FloatTensor<Flex>, dim: usize) -> FloatTensor<Flex> {
719        match tensor.dtype() {
720            DType::F32 => crate::ops::cumulative::cummax_f32(tensor, dim),
721            DType::F64 => crate::ops::cumulative::cummax_f64(tensor, dim),
722            DType::F16 => {
723                crate::ops::cumulative::cummax_half(tensor, dim, f16::to_f32, f16::from_f32)
724            }
725            DType::BF16 => {
726                crate::ops::cumulative::cummax_half(tensor, dim, bf16::to_f32, bf16::from_f32)
727            }
728            _ => panic!("float_cummax: unsupported dtype {:?}", tensor.dtype()),
729        }
730    }
731
732    fn float_cast(tensor: FloatTensor<Flex>, dtype: FloatDType) -> FloatTensor<Flex> {
733        use crate::Layout;
734        use burn_std::{Bytes, bf16, f16};
735
736        let src_dtype = tensor.dtype();
737        let target_dtype = DType::from(dtype);
738
739        // No-op if already the same dtype
740        if src_dtype == target_dtype {
741            return tensor;
742        }
743
744        let tensor = tensor.to_contiguous();
745        let shape = tensor.layout().shape().clone();
746
747        // Convert to f64 intermediate, then to target
748        let f64_values: Vec<f64> = match src_dtype {
749            DType::F32 => {
750                let src: &[f32] = tensor.storage();
751                src.iter().map(|&v| v as f64).collect()
752            }
753            DType::F64 => {
754                let src: &[f64] = tensor.storage();
755                src.to_vec()
756            }
757            DType::F16 => {
758                let src: &[f16] = tensor.storage();
759                src.iter().map(|&v| v.to_f32() as f64).collect()
760            }
761            DType::BF16 => {
762                let src: &[bf16] = tensor.storage();
763                src.iter().map(|&v| v.to_f32() as f64).collect()
764            }
765            _ => panic!("float_cast: unsupported source dtype {:?}", src_dtype),
766        };
767
768        // Convert from f64 to target dtype
769        match target_dtype {
770            DType::F32 => {
771                let result: Vec<f32> = f64_values.iter().map(|&v| v as f32).collect();
772                let bytes = Bytes::from_elems(result);
773                FlexTensor::new(bytes, Layout::contiguous(shape), DType::F32)
774            }
775            DType::F64 => {
776                let bytes = Bytes::from_elems(f64_values);
777                FlexTensor::new(bytes, Layout::contiguous(shape), DType::F64)
778            }
779            DType::F16 => {
780                let result: Vec<f16> = f64_values.iter().map(|&v| f16::from_f64(v)).collect();
781                let bytes = Bytes::from_elems(result);
782                FlexTensor::new(bytes, Layout::contiguous(shape), DType::F16)
783            }
784            DType::BF16 => {
785                let result: Vec<bf16> = f64_values.iter().map(|&v| bf16::from_f64(v)).collect();
786                let bytes = Bytes::from_elems(result);
787                FlexTensor::new(bytes, Layout::contiguous(shape), DType::BF16)
788            }
789            _ => panic!("float_cast: unsupported target dtype {:?}", target_dtype),
790        }
791    }
792
793    fn float_exp(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
794        unary::exp(tensor)
795    }
796
797    fn float_log(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
798        unary::log(tensor)
799    }
800
801    fn float_log1p(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
802        unary::log1p(tensor)
803    }
804
805    fn float_powf(lhs: FloatTensor<Flex>, rhs: FloatTensor<Flex>) -> FloatTensor<Flex> {
806        binary_op(lhs, rhs, |a: f32, b| a.powf(b), |a: f64, b| a.powf(b), None)
807    }
808
809    fn float_powf_scalar_impl(tensor: FloatTensor<Flex>, value: Scalar) -> FloatTensor<Flex> {
810        let exp = value.to_f64().unwrap();
811        scalar_op(tensor, exp, |a: f32, b| a.powf(b), |a: f64, b| a.powf(b))
812    }
813
814    fn float_sqrt(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
815        unary::sqrt(tensor)
816    }
817
818    fn float_abs(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
819        unary::abs(tensor)
820    }
821
822    fn float_cos(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
823        unary::cos(tensor)
824    }
825
826    fn float_sin(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
827        unary::sin(tensor)
828    }
829
830    fn float_tan(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
831        unary::tan(tensor)
832    }
833
834    fn float_cosh(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
835        unary::cosh(tensor)
836    }
837
838    fn float_sinh(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
839        unary::sinh(tensor)
840    }
841
842    fn float_tanh(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
843        unary::tanh(tensor)
844    }
845
846    fn float_acos(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
847        unary::acos(tensor)
848    }
849
850    fn float_acosh(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
851        unary::acosh(tensor)
852    }
853
854    fn float_asin(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
855        unary::asin(tensor)
856    }
857
858    fn float_asinh(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
859        unary::asinh(tensor)
860    }
861
862    fn float_atan(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
863        unary::atan(tensor)
864    }
865
866    fn float_atanh(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
867        unary::atanh(tensor)
868    }
869
870    fn float_atan2(lhs: FloatTensor<Flex>, rhs: FloatTensor<Flex>) -> FloatTensor<Flex> {
871        binary_op(
872            lhs,
873            rhs,
874            |a: f32, b| a.atan2(b),
875            |a: f64, b| a.atan2(b),
876            None,
877        )
878    }
879
880    fn float_round(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
881        unary::round(tensor)
882    }
883
884    fn float_floor(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
885        unary::floor(tensor)
886    }
887
888    fn float_ceil(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
889        unary::ceil(tensor)
890    }
891
892    fn float_trunc(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
893        unary::trunc(tensor)
894    }
895
896    fn float_erf(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
897        unary::erf(tensor)
898    }
899
900    fn float_argmax(
901        tensor: FloatTensor<Flex>,
902        dim: usize,
903        out_dtype: burn_std::IntDType,
904    ) -> IntTensor<Flex> {
905        let result = crate::ops::reduce::argmax(tensor, dim);
906        if result.dtype() != DType::from(out_dtype) {
907            Flex::int_cast(result, out_dtype)
908        } else {
909            result
910        }
911    }
912
913    fn float_argmin(
914        tensor: FloatTensor<Flex>,
915        dim: usize,
916        out_dtype: burn_std::IntDType,
917    ) -> IntTensor<Flex> {
918        let result = crate::ops::reduce::argmin(tensor, dim);
919        if result.dtype() != DType::from(out_dtype) {
920            Flex::int_cast(result, out_dtype)
921        } else {
922            result
923        }
924    }
925
926    fn float_expand(tensor: FloatTensor<Flex>, shape: Shape) -> FloatTensor<Flex> {
927        crate::ops::expand::expand(tensor, shape)
928    }
929
930    fn float_unfold(
931        tensor: FloatTensor<Flex>,
932        dim: usize,
933        size: usize,
934        step: usize,
935    ) -> FloatTensor<Flex> {
936        // unfold is now type-agnostic (zero-copy strided view)
937        crate::ops::unfold::unfold(tensor, dim, size, step)
938    }
939
940    fn float_grid_sample_2d(
941        tensor: FloatTensor<Flex>,
942        grid: FloatTensor<Flex>,
943        options: GridSampleOptions,
944    ) -> FloatTensor<Flex> {
945        crate::ops::grid_sample::grid_sample_2d(tensor, grid, options)
946    }
947
948    fn float_zeros(shape: Shape, _device: &Device<Flex>, dtype: FloatDType) -> FloatTensor<Flex> {
949        FlexTensor::zeros(shape, dtype.into())
950    }
951
952    fn float_ones(shape: Shape, _device: &Device<Flex>, dtype: FloatDType) -> FloatTensor<Flex> {
953        let dt: burn_backend::DType = dtype.into();
954        match dt {
955            DType::F32 => FlexTensor::filled_typed(shape, dt, 1.0f32),
956            DType::F64 => FlexTensor::filled_typed(shape, dt, 1.0f64),
957            DType::F16 => FlexTensor::filled_typed(shape, dt, f16::ONE),
958            DType::BF16 => FlexTensor::filled_typed(shape, dt, bf16::ONE),
959            _ => unreachable!(),
960        }
961    }
962
963    fn float_full(
964        shape: Shape,
965        fill_value: Scalar,
966        _device: &Device<Flex>,
967        dtype: FloatDType,
968    ) -> FloatTensor<Flex> {
969        let dt: burn_backend::DType = dtype.into();
970        match dt {
971            DType::F32 => FlexTensor::filled_typed(shape, dt, fill_value.to_f32().unwrap()),
972            DType::F64 => FlexTensor::filled_typed(shape, dt, fill_value.to_f64().unwrap()),
973            DType::F16 => {
974                FlexTensor::filled_typed(shape, dt, f16::from_f32(fill_value.to_f32().unwrap()))
975            }
976            DType::BF16 => {
977                FlexTensor::filled_typed(shape, dt, bf16::from_f32(fill_value.to_f32().unwrap()))
978            }
979            _ => unreachable!(),
980        }
981    }
982
983    fn float_transpose(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
984        let ndims = tensor.layout().num_dims();
985        if ndims < 2 {
986            return tensor;
987        }
988        tensor.transpose(ndims - 2, ndims - 1)
989    }
990
991    fn float_repeat_dim(tensor: FloatTensor<Flex>, dim: usize, times: usize) -> FloatTensor<Flex> {
992        crate::ops::repeat_dim::repeat_dim(tensor, dim, times)
993    }
994
995    fn float_sort(tensor: FloatTensor<Flex>, dim: usize, descending: bool) -> FloatTensor<Flex> {
996        crate::ops::sort::sort(tensor, dim, descending)
997    }
998
999    fn float_sort_with_indices(
1000        tensor: FloatTensor<Flex>,
1001        dim: usize,
1002        descending: bool,
1003        indices_dtype: burn_std::IntDType,
1004    ) -> (FloatTensor<Flex>, IntTensor<Flex>) {
1005        let (values, indices) = crate::ops::sort::sort_with_indices(tensor, dim, descending);
1006        let indices = if indices.dtype() != DType::from(indices_dtype) {
1007            Flex::int_cast(indices, indices_dtype)
1008        } else {
1009            indices
1010        };
1011        (values, indices)
1012    }
1013
1014    fn float_argsort(
1015        tensor: FloatTensor<Flex>,
1016        dim: usize,
1017        descending: bool,
1018        out_dtype: burn_std::IntDType,
1019    ) -> IntTensor<Flex> {
1020        let indices = crate::ops::sort::argsort(tensor, dim, descending);
1021        if indices.dtype() != DType::from(out_dtype) {
1022            Flex::int_cast(indices, out_dtype)
1023        } else {
1024            indices
1025        }
1026    }
1027
1028    fn float_powi(lhs: FloatTensor<Flex>, rhs: IntTensor<Flex>) -> FloatTensor<Flex> {
1029        let dtype = lhs.dtype();
1030        Self::float_powf(lhs, Flex::int_into_float(rhs, dtype.into()))
1031    }
1032
1033    fn float_powi_scalar(lhs: FloatTensor<Flex>, rhs: Scalar) -> FloatTensor<Flex> {
1034        match rhs.to_i64().unwrap() {
1035            0 => Self::float_ones(lhs.shape(), &Default::default(), lhs.dtype().into()),
1036            1 => lhs,
1037            2 => Self::float_mul(lhs.clone(), lhs),
1038            -1 => Self::float_recip(lhs),
1039            -2 => Self::float_recip(Self::float_mul(lhs.clone(), lhs)),
1040            _ => Self::float_powf_scalar_impl(lhs, rhs),
1041        }
1042    }
1043
1044    fn float_powf_scalar(tensor: FloatTensor<Flex>, value: Scalar) -> FloatTensor<Flex> {
1045        if let Some(exp) = value.try_as_integer() {
1046            Self::float_powi_scalar(tensor, exp)
1047        } else {
1048            Self::float_powf_scalar_impl(tensor, value)
1049        }
1050    }
1051
1052    fn float_max_abs(tensor: FloatTensor<Flex>) -> FloatTensor<Flex> {
1053        let abs = unary::abs(tensor);
1054        crate::ops::reduce::max(abs)
1055    }
1056
1057    fn float_max_abs_dim(tensor: FloatTensor<Flex>, dim: usize) -> FloatTensor<Flex> {
1058        let abs = unary::abs(tensor);
1059        crate::ops::reduce::max_dim(abs, dim)
1060    }
1061
1062    fn float_is_nan(tensor: FloatTensor<Flex>, out_dtype: burn_std::BoolDType) -> BoolTensor<Flex> {
1063        unary::float_predicate(tensor, out_dtype, |x: f32| x.is_nan(), |x: f64| x.is_nan())
1064    }
1065
1066    fn float_is_inf(tensor: FloatTensor<Flex>, out_dtype: burn_std::BoolDType) -> BoolTensor<Flex> {
1067        unary::float_predicate(
1068            tensor,
1069            out_dtype,
1070            |x: f32| x.is_infinite(),
1071            |x: f64| x.is_infinite(),
1072        )
1073    }
1074
1075    fn float_hypot(lhs: FloatTensor<Flex>, rhs: FloatTensor<Flex>) -> FloatTensor<Flex> {
1076        binary_op(
1077            lhs,
1078            rhs,
1079            |a: f32, b| a.hypot(b),
1080            |a: f64, b| a.hypot(b),
1081            None,
1082        )
1083    }
1084}
1085
1086// Tests kept here exercise flex-specific behavior: direct `Flex::`
1087// backend-op calls with explicit IntDType/FloatDType to pin dtype storage
1088// selection (U8/I32/I64, F16/F64). Plain arithmetic, math, cast, cross,
1089// unfold, and random smoke tests have been dropped in favor of the
1090// equivalent coverage in burn-backend-tests, which exercises every backend.
1091// When adding new tests, keep them here only if they probe flex dtype
1092// storage or flex internals; otherwise add them to
1093// crates/burn-backend-tests/tests/tensor/float/ops/.
1094#[cfg(test)]
1095mod tests {
1096    use burn_backend::TensorData;
1097
1098    use crate::Flex;
1099
1100    #[test]
1101    fn test_float_into_int_i32() {
1102        use burn_backend::ops::FloatTensorOps;
1103        use burn_std::IntDType;
1104
1105        let t = crate::FlexTensor::from_data(TensorData::from([1.5f32, -2.7, 0.0, 255.9]));
1106        let result = Flex::float_into_int(t, IntDType::I32);
1107        assert_eq!(result.dtype(), burn_backend::DType::I32);
1108        let data: Vec<i32> = result.into_data().to_vec().unwrap();
1109        assert_eq!(data, vec![1, -2, 0, 255]);
1110    }
1111
1112    #[test]
1113    fn test_float_into_int_u8() {
1114        use burn_backend::ops::FloatTensorOps;
1115        use burn_std::IntDType;
1116
1117        let t = crate::FlexTensor::from_data(TensorData::from([0.0f32, 1.9, 127.5, 255.0]));
1118        let result = Flex::float_into_int(t, IntDType::U8);
1119        assert_eq!(result.dtype(), burn_backend::DType::U8);
1120        let data: Vec<u8> = result.into_data().to_vec().unwrap();
1121        assert_eq!(data, vec![0, 1, 127, 255]);
1122    }
1123
1124    #[test]
1125    fn test_float_argmax_i32_out_dtype() {
1126        use burn_backend::ops::FloatTensorOps;
1127        use burn_std::IntDType;
1128
1129        let t = crate::FlexTensor::from_data(TensorData::from([[1.0f32, 3.0, 2.0]]));
1130        let result = Flex::float_argmax(t, 1, IntDType::I32);
1131        assert_eq!(result.dtype(), burn_backend::DType::I32);
1132        let data: Vec<i32> = result.into_data().to_vec().unwrap();
1133        assert_eq!(data, vec![1]);
1134    }
1135
1136    #[test]
1137    fn test_float_argmin_i32_out_dtype() {
1138        use burn_backend::ops::FloatTensorOps;
1139        use burn_std::IntDType;
1140
1141        let t = crate::FlexTensor::from_data(TensorData::from([[3.0f32, 1.0, 2.0]]));
1142        let result = Flex::float_argmin(t, 1, IntDType::I32);
1143        assert_eq!(result.dtype(), burn_backend::DType::I32);
1144        let data: Vec<i32> = result.into_data().to_vec().unwrap();
1145        assert_eq!(data, vec![1]);
1146    }
1147
1148    #[test]
1149    fn test_float_argmax_i64_out_dtype() {
1150        use burn_backend::ops::FloatTensorOps;
1151        use burn_std::IntDType;
1152
1153        let t = crate::FlexTensor::from_data(TensorData::from([[1.0f32, 3.0, 2.0]]));
1154        let result = Flex::float_argmax(t, 1, IntDType::I64);
1155        assert_eq!(result.dtype(), burn_backend::DType::I64);
1156        let data: Vec<i64> = result.into_data().to_vec().unwrap();
1157        assert_eq!(data, vec![1]);
1158    }
1159
1160    #[test]
1161    fn test_float_max_dim_with_indices_i32() {
1162        use burn_backend::ops::FloatTensorOps;
1163        use burn_std::IntDType;
1164
1165        let t = crate::FlexTensor::from_data(TensorData::from([[1.0f32, 5.0], [3.0, 2.0]]));
1166        let (values, indices) = Flex::float_max_dim_with_indices(t, 1, IntDType::I32);
1167        assert_eq!(indices.dtype(), burn_backend::DType::I32);
1168        let idx: Vec<i32> = indices.into_data().to_vec().unwrap();
1169        assert_eq!(idx, vec![1, 0]);
1170        let vals: Vec<f32> = values.into_data().to_vec().unwrap();
1171        assert_eq!(vals, vec![5.0, 3.0]);
1172    }
1173
1174    #[test]
1175    fn test_float_min_dim_with_indices_i32() {
1176        use burn_backend::ops::FloatTensorOps;
1177        use burn_std::IntDType;
1178
1179        let t = crate::FlexTensor::from_data(TensorData::from([[1.0f32, 5.0], [3.0, 2.0]]));
1180        let (values, indices) = Flex::float_min_dim_with_indices(t, 1, IntDType::I32);
1181        assert_eq!(indices.dtype(), burn_backend::DType::I32);
1182        let idx: Vec<i32> = indices.into_data().to_vec().unwrap();
1183        assert_eq!(idx, vec![0, 1]);
1184        let vals: Vec<f32> = values.into_data().to_vec().unwrap();
1185        assert_eq!(vals, vec![1.0, 2.0]);
1186    }
1187
1188    #[test]
1189    fn test_float_random_f64() {
1190        use burn_backend::{DType, FloatDType, ops::FloatTensorOps};
1191
1192        let shape = burn_std::Shape::from(vec![100]);
1193        let dist = burn_backend::Distribution::Uniform(0.0, 1.0);
1194        let device = crate::FlexDevice;
1195        let t = Flex::float_random(shape, dist, &device, FloatDType::F64);
1196        assert_eq!(t.dtype(), DType::F64);
1197        let data: Vec<f64> = t.into_data().to_vec().unwrap();
1198        assert!(data.iter().all(|&v| (0.0..=1.0).contains(&v)));
1199    }
1200
1201    #[test]
1202    fn test_float_random_f16() {
1203        use burn_backend::{DType, FloatDType, ops::FloatTensorOps};
1204
1205        let shape = burn_std::Shape::from(vec![100]);
1206        let dist = burn_backend::Distribution::Uniform(0.0, 1.0);
1207        let device = crate::FlexDevice;
1208        let t = Flex::float_random(shape, dist, &device, FloatDType::F16);
1209        assert_eq!(t.dtype(), DType::F16);
1210    }
1211}