Skip to main content

burn_flex/ops/
int.rs

1//! Int tensor operations for the Flex backend.
2
3use alloc::vec::Vec;
4use burn_backend::{
5    DType, Distribution, ExecutionError, FloatDType, Scalar, TensorData, TensorMetadata,
6    ops::IntTensorOps,
7    tensor::{BoolTensor, Device, FloatTensor, IntTensor},
8};
9use burn_std::{Bytes, IntDType, Shape, Slice, bf16, f16};
10use num_traits::ToPrimitive;
11
12use crate::Layout;
13use crate::ops::binary::{binary_op_typed, int_binary_op, int_scalar_op, scalar_op_typed};
14use crate::{Flex, FlexTensor, ops::matmul};
15
16/// Convert a Scalar to (i64, u64) pair for the given dtype.
17/// Only the matching type's conversion is validated; the other gets a dummy 0.
18fn scalar_to_int_pair(dtype: DType, rhs: &Scalar) -> (i64, u64) {
19    if dtype == DType::U64 {
20        (0, rhs.to_u64().unwrap())
21    } else {
22        (rhs.to_i64().unwrap(), 0)
23    }
24}
25
26impl IntTensorOps<Flex> for Flex {
27    fn int_from_data(data: TensorData, _device: &Device<Flex>) -> IntTensor<Flex> {
28        FlexTensor::from_data(data)
29    }
30
31    async fn int_into_data(tensor: IntTensor<Flex>) -> Result<TensorData, ExecutionError> {
32        Ok(tensor.into_data())
33    }
34
35    fn int_to_device(tensor: IntTensor<Flex>, _device: &Device<Flex>) -> IntTensor<Flex> {
36        tensor
37    }
38
39    fn int_cat(tensors: Vec<IntTensor<Flex>>, dim: usize) -> IntTensor<Flex> {
40        crate::ops::cat::cat(tensors, dim)
41    }
42
43    fn int_reshape(tensor: IntTensor<Flex>, shape: Shape) -> IntTensor<Flex> {
44        tensor.reshape(shape)
45    }
46
47    fn int_slice(tensor: IntTensor<Flex>, slices: &[Slice]) -> IntTensor<Flex> {
48        crate::ops::slice::slice(tensor, slices)
49    }
50
51    fn int_empty(shape: Shape, _device: &Device<Flex>, dtype: IntDType) -> IntTensor<Flex> {
52        FlexTensor::empty(shape, dtype.into())
53    }
54
55    fn int_mask_where(
56        tensor: IntTensor<Flex>,
57        mask: BoolTensor<Flex>,
58        value: IntTensor<Flex>,
59    ) -> IntTensor<Flex> {
60        debug_assert_eq!(
61            tensor.dtype(),
62            value.dtype(),
63            "int_mask_where: dtype mismatch"
64        );
65        match tensor.dtype() {
66            DType::I64 => crate::ops::mask::mask_where::<i64>(tensor, mask, value),
67            DType::I32 => crate::ops::mask::mask_where::<i32>(tensor, mask, value),
68            DType::I16 => crate::ops::mask::mask_where::<i16>(tensor, mask, value),
69            DType::I8 => crate::ops::mask::mask_where::<i8>(tensor, mask, value),
70            DType::U64 => crate::ops::mask::mask_where::<u64>(tensor, mask, value),
71            DType::U32 => crate::ops::mask::mask_where::<u32>(tensor, mask, value),
72            DType::U16 => crate::ops::mask::mask_where::<u16>(tensor, mask, value),
73            DType::U8 => crate::ops::mask::mask_where::<u8>(tensor, mask, value),
74            dt => panic!("int_mask_where: unsupported dtype {:?}", dt),
75        }
76    }
77
78    fn int_mask_fill(
79        tensor: IntTensor<Flex>,
80        mask: BoolTensor<Flex>,
81        value: Scalar,
82    ) -> IntTensor<Flex> {
83        match tensor.dtype() {
84            DType::I64 => crate::ops::mask::mask_fill(tensor, mask, value.to_i64().unwrap()),
85            DType::I32 => crate::ops::mask::mask_fill(tensor, mask, value.to_i64().unwrap() as i32),
86            DType::I16 => crate::ops::mask::mask_fill(tensor, mask, value.to_i64().unwrap() as i16),
87            DType::I8 => crate::ops::mask::mask_fill(tensor, mask, value.to_i64().unwrap() as i8),
88            DType::U64 => crate::ops::mask::mask_fill(tensor, mask, value.to_u64().unwrap()),
89            DType::U32 => crate::ops::mask::mask_fill(tensor, mask, value.to_u64().unwrap() as u32),
90            DType::U16 => crate::ops::mask::mask_fill(tensor, mask, value.to_u64().unwrap() as u16),
91            DType::U8 => crate::ops::mask::mask_fill(tensor, mask, value.to_u64().unwrap() as u8),
92            dt => panic!("int_mask_fill: unsupported dtype {:?}", dt),
93        }
94    }
95
96    fn int_slice_assign(
97        tensor: IntTensor<Flex>,
98        slices: &[Slice],
99        value: IntTensor<Flex>,
100    ) -> IntTensor<Flex> {
101        crate::ops::slice::slice_assign(tensor, slices, value)
102    }
103
104    /// Gather ints along `dim` at the given indices.
105    ///
106    /// The `tensor` dispatches on its own int dtype (I8/I16/I32/I64 signed or
107    /// U8/U16/U32/U64 unsigned). The `indices` tensor may be any of those
108    /// widths too - it's normalised to `isize` by the shared `read_indices`
109    /// helper in `ops::gather_scatter` before the kernel runs, so callers are
110    /// not required to pre-convert to I64.
111    fn int_gather(
112        dim: usize,
113        tensor: IntTensor<Flex>,
114        indices: IntTensor<Flex>,
115    ) -> IntTensor<Flex> {
116        match tensor.dtype() {
117            DType::I64 => crate::ops::gather_scatter::gather::<i64>(tensor, dim, indices),
118            DType::I32 => crate::ops::gather_scatter::gather::<i32>(tensor, dim, indices),
119            DType::I16 => crate::ops::gather_scatter::gather::<i16>(tensor, dim, indices),
120            DType::I8 => crate::ops::gather_scatter::gather::<i8>(tensor, dim, indices),
121            DType::U64 => crate::ops::gather_scatter::gather::<u64>(tensor, dim, indices),
122            DType::U32 => crate::ops::gather_scatter::gather::<u32>(tensor, dim, indices),
123            DType::U16 => crate::ops::gather_scatter::gather::<u16>(tensor, dim, indices),
124            DType::U8 => crate::ops::gather_scatter::gather::<u8>(tensor, dim, indices),
125            dt => panic!("int_gather: unsupported dtype {:?}", dt),
126        }
127    }
128
129    /// Scatter-add int values at the given indices along `dim`.
130    ///
131    /// `tensor` and `value` must share the same int dtype; `indices` may be
132    /// any supported int width. See [`int_gather`](Self::int_gather) for the
133    /// full index-width policy.
134    fn int_scatter_add(
135        dim: usize,
136        tensor: IntTensor<Flex>,
137        indices: IntTensor<Flex>,
138        value: IntTensor<Flex>,
139    ) -> IntTensor<Flex> {
140        debug_assert_eq!(
141            tensor.dtype(),
142            value.dtype(),
143            "int_scatter_add: dtype mismatch"
144        );
145        match tensor.dtype() {
146            DType::I64 => {
147                crate::ops::gather_scatter::scatter_add::<i64>(tensor, dim, indices, value)
148            }
149            DType::I32 => {
150                crate::ops::gather_scatter::scatter_add::<i32>(tensor, dim, indices, value)
151            }
152            DType::I16 => {
153                crate::ops::gather_scatter::scatter_add::<i16>(tensor, dim, indices, value)
154            }
155            DType::I8 => crate::ops::gather_scatter::scatter_add::<i8>(tensor, dim, indices, value),
156            DType::U64 => {
157                crate::ops::gather_scatter::scatter_add::<u64>(tensor, dim, indices, value)
158            }
159            DType::U32 => {
160                crate::ops::gather_scatter::scatter_add::<u32>(tensor, dim, indices, value)
161            }
162            DType::U16 => {
163                crate::ops::gather_scatter::scatter_add::<u16>(tensor, dim, indices, value)
164            }
165            DType::U8 => crate::ops::gather_scatter::scatter_add::<u8>(tensor, dim, indices, value),
166            dt => panic!("int_scatter_add: unsupported dtype {:?}", dt),
167        }
168    }
169
170    fn int_scatter_nd(
171        data: IntTensor<Flex>,
172        indices: IntTensor<Flex>,
173        values: IntTensor<Flex>,
174        reduction: burn_backend::tensor::IndexingUpdateOp,
175    ) -> IntTensor<Flex> {
176        match data.dtype() {
177            DType::I64 => {
178                crate::ops::gather_scatter::scatter_nd::<i64>(data, indices, values, reduction)
179            }
180            DType::I32 => {
181                crate::ops::gather_scatter::scatter_nd::<i32>(data, indices, values, reduction)
182            }
183            DType::I16 => {
184                crate::ops::gather_scatter::scatter_nd::<i16>(data, indices, values, reduction)
185            }
186            DType::I8 => {
187                crate::ops::gather_scatter::scatter_nd::<i8>(data, indices, values, reduction)
188            }
189            DType::U64 => {
190                crate::ops::gather_scatter::scatter_nd::<u64>(data, indices, values, reduction)
191            }
192            DType::U32 => {
193                crate::ops::gather_scatter::scatter_nd::<u32>(data, indices, values, reduction)
194            }
195            DType::U16 => {
196                crate::ops::gather_scatter::scatter_nd::<u16>(data, indices, values, reduction)
197            }
198            DType::U8 => {
199                crate::ops::gather_scatter::scatter_nd::<u8>(data, indices, values, reduction)
200            }
201            dt => panic!("int_scatter_nd: unsupported dtype {:?}", dt),
202        }
203    }
204
205    fn int_gather_nd(data: IntTensor<Flex>, indices: IntTensor<Flex>) -> IntTensor<Flex> {
206        match data.dtype() {
207            DType::I64 => crate::ops::gather_scatter::gather_nd::<i64>(data, indices),
208            DType::I32 => crate::ops::gather_scatter::gather_nd::<i32>(data, indices),
209            DType::I16 => crate::ops::gather_scatter::gather_nd::<i16>(data, indices),
210            DType::I8 => crate::ops::gather_scatter::gather_nd::<i8>(data, indices),
211            DType::U64 => crate::ops::gather_scatter::gather_nd::<u64>(data, indices),
212            DType::U32 => crate::ops::gather_scatter::gather_nd::<u32>(data, indices),
213            DType::U16 => crate::ops::gather_scatter::gather_nd::<u16>(data, indices),
214            DType::U8 => crate::ops::gather_scatter::gather_nd::<u8>(data, indices),
215            dt => panic!("int_gather_nd: unsupported dtype {:?}", dt),
216        }
217    }
218
219    /// Select ints along `dim` by a 1D index tensor.
220    ///
221    /// The `indices` tensor may be any supported int width. See
222    /// [`int_gather`](Self::int_gather) for the full index-width policy.
223    fn int_select(
224        tensor: IntTensor<Flex>,
225        dim: usize,
226        indices: IntTensor<Flex>,
227    ) -> IntTensor<Flex> {
228        match tensor.dtype() {
229            DType::I64 => crate::ops::gather_scatter::select::<i64>(tensor, dim, indices),
230            DType::I32 => crate::ops::gather_scatter::select::<i32>(tensor, dim, indices),
231            DType::I16 => crate::ops::gather_scatter::select::<i16>(tensor, dim, indices),
232            DType::I8 => crate::ops::gather_scatter::select::<i8>(tensor, dim, indices),
233            DType::U64 => crate::ops::gather_scatter::select::<u64>(tensor, dim, indices),
234            DType::U32 => crate::ops::gather_scatter::select::<u32>(tensor, dim, indices),
235            DType::U16 => crate::ops::gather_scatter::select::<u16>(tensor, dim, indices),
236            DType::U8 => crate::ops::gather_scatter::select::<u8>(tensor, dim, indices),
237            dt => panic!("int_select: unsupported dtype {:?}", dt),
238        }
239    }
240
241    /// Select-add int values at a 1D index tensor along `dim`.
242    ///
243    /// `tensor` and `value` must share the same int dtype; `indices` may be
244    /// any supported int width. See [`int_gather`](Self::int_gather) for the
245    /// full index-width policy.
246    fn int_select_add(
247        tensor: IntTensor<Flex>,
248        dim: usize,
249        indices: IntTensor<Flex>,
250        value: IntTensor<Flex>,
251    ) -> IntTensor<Flex> {
252        debug_assert_eq!(
253            tensor.dtype(),
254            value.dtype(),
255            "int_select_add: dtype mismatch"
256        );
257        match tensor.dtype() {
258            DType::I64 => {
259                crate::ops::gather_scatter::select_add::<i64>(tensor, dim, indices, value)
260            }
261            DType::I32 => {
262                crate::ops::gather_scatter::select_add::<i32>(tensor, dim, indices, value)
263            }
264            DType::I16 => {
265                crate::ops::gather_scatter::select_add::<i16>(tensor, dim, indices, value)
266            }
267            DType::I8 => crate::ops::gather_scatter::select_add::<i8>(tensor, dim, indices, value),
268            DType::U64 => {
269                crate::ops::gather_scatter::select_add::<u64>(tensor, dim, indices, value)
270            }
271            DType::U32 => {
272                crate::ops::gather_scatter::select_add::<u32>(tensor, dim, indices, value)
273            }
274            DType::U16 => {
275                crate::ops::gather_scatter::select_add::<u16>(tensor, dim, indices, value)
276            }
277            DType::U8 => crate::ops::gather_scatter::select_add::<u8>(tensor, dim, indices, value),
278            dt => panic!("int_select_add: unsupported dtype {:?}", dt),
279        }
280    }
281
282    fn int_equal(
283        lhs: IntTensor<Flex>,
284        rhs: IntTensor<Flex>,
285        out_dtype: burn_std::BoolDType,
286    ) -> BoolTensor<Flex> {
287        crate::ops::comparison::int_equal(lhs, rhs, out_dtype)
288    }
289
290    fn int_equal_elem(
291        lhs: IntTensor<Flex>,
292        rhs: Scalar,
293        out_dtype: burn_std::BoolDType,
294    ) -> BoolTensor<Flex> {
295        let (i, u) = scalar_to_int_pair(lhs.dtype(), &rhs);
296        crate::ops::comparison::int_equal_elem(lhs, i, u, out_dtype)
297    }
298
299    fn int_greater(
300        lhs: IntTensor<Flex>,
301        rhs: IntTensor<Flex>,
302        out_dtype: burn_std::BoolDType,
303    ) -> BoolTensor<Flex> {
304        crate::ops::comparison::int_greater(lhs, rhs, out_dtype)
305    }
306
307    fn int_greater_elem(
308        lhs: IntTensor<Flex>,
309        rhs: Scalar,
310        out_dtype: burn_std::BoolDType,
311    ) -> BoolTensor<Flex> {
312        let (i, u) = scalar_to_int_pair(lhs.dtype(), &rhs);
313        crate::ops::comparison::int_greater_elem(lhs, i, u, out_dtype)
314    }
315
316    fn int_greater_equal(
317        lhs: IntTensor<Flex>,
318        rhs: IntTensor<Flex>,
319        out_dtype: burn_std::BoolDType,
320    ) -> BoolTensor<Flex> {
321        crate::ops::comparison::int_greater_equal(lhs, rhs, out_dtype)
322    }
323
324    fn int_greater_equal_elem(
325        lhs: IntTensor<Flex>,
326        rhs: Scalar,
327        out_dtype: burn_std::BoolDType,
328    ) -> BoolTensor<Flex> {
329        let (i, u) = scalar_to_int_pair(lhs.dtype(), &rhs);
330        crate::ops::comparison::int_greater_equal_elem(lhs, i, u, out_dtype)
331    }
332
333    fn int_lower(
334        lhs: IntTensor<Flex>,
335        rhs: IntTensor<Flex>,
336        out_dtype: burn_std::BoolDType,
337    ) -> BoolTensor<Flex> {
338        crate::ops::comparison::int_lower(lhs, rhs, out_dtype)
339    }
340
341    fn int_lower_elem(
342        lhs: IntTensor<Flex>,
343        rhs: Scalar,
344        out_dtype: burn_std::BoolDType,
345    ) -> BoolTensor<Flex> {
346        let (i, u) = scalar_to_int_pair(lhs.dtype(), &rhs);
347        crate::ops::comparison::int_lower_elem(lhs, i, u, out_dtype)
348    }
349
350    fn int_lower_equal(
351        lhs: IntTensor<Flex>,
352        rhs: IntTensor<Flex>,
353        out_dtype: burn_std::BoolDType,
354    ) -> BoolTensor<Flex> {
355        crate::ops::comparison::int_lower_equal(lhs, rhs, out_dtype)
356    }
357
358    fn int_lower_equal_elem(
359        lhs: IntTensor<Flex>,
360        rhs: Scalar,
361        out_dtype: burn_std::BoolDType,
362    ) -> BoolTensor<Flex> {
363        let (i, u) = scalar_to_int_pair(lhs.dtype(), &rhs);
364        crate::ops::comparison::int_lower_equal_elem(lhs, i, u, out_dtype)
365    }
366
367    fn int_add(lhs: IntTensor<Flex>, rhs: IntTensor<Flex>) -> IntTensor<Flex> {
368        int_binary_op(lhs, rhs, |a, b| a + b)
369    }
370
371    fn int_add_scalar(lhs: IntTensor<Flex>, rhs: Scalar) -> IntTensor<Flex> {
372        if lhs.dtype() == DType::U64 {
373            return scalar_op_typed(lhs, rhs.to_u64().unwrap(), |a: u64, b: u64| {
374                a.wrapping_add(b)
375            });
376        }
377        int_scalar_op(lhs, rhs.to_i64().unwrap(), |a, b| a + b)
378    }
379
380    fn int_sub(lhs: IntTensor<Flex>, rhs: IntTensor<Flex>) -> IntTensor<Flex> {
381        int_binary_op(lhs, rhs, |a, b| a - b)
382    }
383
384    fn int_sub_scalar(lhs: IntTensor<Flex>, rhs: Scalar) -> IntTensor<Flex> {
385        if lhs.dtype() == DType::U64 {
386            return scalar_op_typed(lhs, rhs.to_u64().unwrap(), |a: u64, b: u64| {
387                a.wrapping_sub(b)
388            });
389        }
390        int_scalar_op(lhs, rhs.to_i64().unwrap(), |a, b| a - b)
391    }
392
393    fn int_mul(lhs: IntTensor<Flex>, rhs: IntTensor<Flex>) -> IntTensor<Flex> {
394        int_binary_op(lhs, rhs, |a, b| a * b)
395    }
396
397    fn int_mul_scalar(lhs: IntTensor<Flex>, rhs: Scalar) -> IntTensor<Flex> {
398        if lhs.dtype() == DType::U64 {
399            return scalar_op_typed(lhs, rhs.to_u64().unwrap(), |a: u64, b: u64| {
400                a.wrapping_mul(b)
401            });
402        }
403        int_scalar_op(lhs, rhs.to_i64().unwrap(), |a, b| a * b)
404    }
405
406    fn int_div(lhs: IntTensor<Flex>, rhs: IntTensor<Flex>) -> IntTensor<Flex> {
407        // U64 values > i64::MAX produce wrong results through i64 cast
408        if lhs.dtype() == DType::U64 {
409            let (lhs, rhs) = crate::ops::expand::broadcast_binary(lhs, rhs);
410            return binary_op_typed(lhs, &rhs, |a: u64, b: u64| a / b);
411        }
412        int_binary_op(lhs, rhs, |a, b| a / b)
413    }
414
415    fn int_div_scalar(lhs: IntTensor<Flex>, rhs: Scalar) -> IntTensor<Flex> {
416        if lhs.dtype() == DType::U64 {
417            return scalar_op_typed(lhs, rhs.to_u64().unwrap(), |a: u64, b: u64| a / b);
418        }
419        int_scalar_op(lhs, rhs.to_i64().unwrap(), |a, b| a / b)
420    }
421
422    fn int_remainder(lhs: IntTensor<Flex>, rhs: IntTensor<Flex>) -> IntTensor<Flex> {
423        // U64 values > i64::MAX produce wrong results through i64 cast
424        if lhs.dtype() == DType::U64 {
425            let (lhs, rhs) = crate::ops::expand::broadcast_binary(lhs, rhs);
426            return binary_op_typed(lhs, &rhs, |a: u64, b: u64| a % b);
427        }
428        // Python/PyTorch-style remainder: result has same sign as divisor
429        int_binary_op(lhs, rhs, |a, b| ((a % b) + b) % b)
430    }
431
432    fn int_remainder_scalar(lhs: IntTensor<Flex>, rhs: Scalar) -> IntTensor<Flex> {
433        if lhs.dtype() == DType::U64 {
434            return scalar_op_typed(lhs, rhs.to_u64().unwrap(), |a: u64, b: u64| a % b);
435        }
436        // Python/PyTorch-style remainder: result has same sign as divisor
437        int_scalar_op(lhs, rhs.to_i64().unwrap(), |a, b| ((a % b) + b) % b)
438    }
439
440    // Precision limits: i64/u64 > 2^24 for f32/f16/bf16, > 2^53 for f64.
441    fn int_into_float(
442        tensor: IntTensor<Flex>,
443        out_dtype: burn_std::FloatDType,
444    ) -> FloatTensor<Flex> {
445        let tensor = tensor.to_contiguous();
446        let shape = tensor.layout().shape().clone();
447        let src = tensor.dtype();
448        let out_dt = DType::from(out_dtype);
449
450        // Read source ints, applying conversion per-element.
451        // Each arm binds `$x` to the native int value; `$conv` must work for all int types.
452        macro_rules! read_ints {
453            (|$x:ident| $conv:expr) => {
454                match src {
455                    DType::I64 => tensor.storage::<i64>().iter().map(|&$x| $conv).collect(),
456                    DType::I32 => tensor.storage::<i32>().iter().map(|&$x| $conv).collect(),
457                    DType::I16 => tensor.storage::<i16>().iter().map(|&$x| $conv).collect(),
458                    DType::I8 => tensor.storage::<i8>().iter().map(|&$x| $conv).collect(),
459                    DType::U64 => tensor.storage::<u64>().iter().map(|&$x| $conv).collect(),
460                    DType::U32 => tensor.storage::<u32>().iter().map(|&$x| $conv).collect(),
461                    DType::U16 => tensor.storage::<u16>().iter().map(|&$x| $conv).collect(),
462                    DType::U8 => tensor.storage::<u8>().iter().map(|&$x| $conv).collect(),
463                    _ => panic!("int_into_float: unsupported source dtype {:?}", src),
464                }
465            };
466        }
467
468        match out_dtype {
469            FloatDType::F64 => {
470                let data: Vec<f64> = read_ints!(|x| x as f64);
471                FlexTensor::new(Bytes::from_elems(data), Layout::contiguous(shape), out_dt)
472            }
473            FloatDType::F32 | FloatDType::Flex32 => {
474                let data: Vec<f32> = read_ints!(|x| x as f32);
475                FlexTensor::new(Bytes::from_elems(data), Layout::contiguous(shape), out_dt)
476            }
477            FloatDType::F16 => {
478                let data: Vec<f16> = read_ints!(|x| f16::from_f32(x as f32));
479                FlexTensor::new(Bytes::from_elems(data), Layout::contiguous(shape), out_dt)
480            }
481            FloatDType::BF16 => {
482                let data: Vec<bf16> = read_ints!(|x| bf16::from_f32(x as f32));
483                FlexTensor::new(Bytes::from_elems(data), Layout::contiguous(shape), out_dt)
484            }
485        }
486    }
487
488    fn int_swap_dims(tensor: IntTensor<Flex>, dim1: usize, dim2: usize) -> IntTensor<Flex> {
489        tensor.transpose(dim1, dim2)
490    }
491
492    fn int_permute(tensor: IntTensor<Flex>, axes: &[usize]) -> IntTensor<Flex> {
493        tensor.permute(axes)
494    }
495
496    fn int_flip(tensor: IntTensor<Flex>, axes: &[usize]) -> IntTensor<Flex> {
497        crate::ops::flip::flip(tensor, axes)
498    }
499
500    fn int_random(
501        shape: Shape,
502        distribution: Distribution,
503        _device: &Device<Flex>,
504        dtype: IntDType,
505    ) -> IntTensor<Flex> {
506        let mut seed = crate::backend::SEED.lock().unwrap();
507        let mut rng = seed.take().unwrap_or_else(crate::backend::get_seeded_rng);
508        let data = match dtype {
509            IntDType::I64 => TensorData::random::<i64, _, _>(shape, distribution, &mut rng),
510            IntDType::I32 => TensorData::random::<i32, _, _>(shape, distribution, &mut rng),
511            IntDType::I16 => TensorData::random::<i16, _, _>(shape, distribution, &mut rng),
512            IntDType::I8 => TensorData::random::<i8, _, _>(shape, distribution, &mut rng),
513            IntDType::U64 => TensorData::random::<u64, _, _>(shape, distribution, &mut rng),
514            IntDType::U32 => TensorData::random::<u32, _, _>(shape, distribution, &mut rng),
515            IntDType::U16 => TensorData::random::<u16, _, _>(shape, distribution, &mut rng),
516            IntDType::U8 => TensorData::random::<u8, _, _>(shape, distribution, &mut rng),
517        };
518        *seed = Some(rng);
519        FlexTensor::from_data(data)
520    }
521
522    fn int_expand(tensor: IntTensor<Flex>, shape: Shape) -> IntTensor<Flex> {
523        crate::ops::expand::expand(tensor, shape)
524    }
525
526    fn int_matmul(lhs: IntTensor<Flex>, rhs: IntTensor<Flex>) -> IntTensor<Flex> {
527        matmul::int_matmul(lhs, rhs)
528    }
529
530    fn int_sum(tensor: IntTensor<Flex>) -> IntTensor<Flex> {
531        crate::ops::reduce::sum(tensor)
532    }
533
534    fn int_sum_dim(tensor: IntTensor<Flex>, dim: usize) -> IntTensor<Flex> {
535        crate::ops::reduce::sum_dim(tensor, dim)
536    }
537
538    fn int_prod(tensor: IntTensor<Flex>) -> IntTensor<Flex> {
539        crate::ops::reduce::prod(tensor)
540    }
541
542    fn int_prod_dim(tensor: IntTensor<Flex>, dim: usize) -> IntTensor<Flex> {
543        crate::ops::reduce::prod_dim(tensor, dim)
544    }
545
546    fn int_mean_dim(tensor: IntTensor<Flex>, dim: usize) -> IntTensor<Flex> {
547        crate::ops::reduce::mean_dim(tensor, dim)
548    }
549
550    fn int_cumsum(tensor: IntTensor<Flex>, dim: usize) -> IntTensor<Flex> {
551        match tensor.dtype() {
552            DType::I64 => crate::ops::cumulative::cumsum::<i64>(tensor, dim),
553            DType::I32 => crate::ops::cumulative::cumsum::<i32>(tensor, dim),
554            DType::I16 => crate::ops::cumulative::cumsum::<i16>(tensor, dim),
555            DType::I8 => crate::ops::cumulative::cumsum::<i8>(tensor, dim),
556            DType::U64 => crate::ops::cumulative::cumsum::<u64>(tensor, dim),
557            DType::U32 => crate::ops::cumulative::cumsum::<u32>(tensor, dim),
558            DType::U16 => crate::ops::cumulative::cumsum::<u16>(tensor, dim),
559            DType::U8 => crate::ops::cumulative::cumsum::<u8>(tensor, dim),
560            dt => panic!("int_cumsum: unsupported dtype {:?}", dt),
561        }
562    }
563
564    fn int_cumprod(tensor: IntTensor<Flex>, dim: usize) -> IntTensor<Flex> {
565        match tensor.dtype() {
566            DType::I64 => crate::ops::cumulative::cumprod::<i64>(tensor, dim),
567            DType::I32 => crate::ops::cumulative::cumprod::<i32>(tensor, dim),
568            DType::I16 => crate::ops::cumulative::cumprod::<i16>(tensor, dim),
569            DType::I8 => crate::ops::cumulative::cumprod::<i8>(tensor, dim),
570            DType::U64 => crate::ops::cumulative::cumprod::<u64>(tensor, dim),
571            DType::U32 => crate::ops::cumulative::cumprod::<u32>(tensor, dim),
572            DType::U16 => crate::ops::cumulative::cumprod::<u16>(tensor, dim),
573            DType::U8 => crate::ops::cumulative::cumprod::<u8>(tensor, dim),
574            dt => panic!("int_cumprod: unsupported dtype {:?}", dt),
575        }
576    }
577
578    fn int_cummin(tensor: IntTensor<Flex>, dim: usize) -> IntTensor<Flex> {
579        match tensor.dtype() {
580            DType::I64 => crate::ops::cumulative::cummin::<i64>(tensor, dim),
581            DType::I32 => crate::ops::cumulative::cummin::<i32>(tensor, dim),
582            DType::I16 => crate::ops::cumulative::cummin::<i16>(tensor, dim),
583            DType::I8 => crate::ops::cumulative::cummin::<i8>(tensor, dim),
584            DType::U64 => crate::ops::cumulative::cummin::<u64>(tensor, dim),
585            DType::U32 => crate::ops::cumulative::cummin::<u32>(tensor, dim),
586            DType::U16 => crate::ops::cumulative::cummin::<u16>(tensor, dim),
587            DType::U8 => crate::ops::cumulative::cummin::<u8>(tensor, dim),
588            dt => panic!("int_cummin: unsupported dtype {:?}", dt),
589        }
590    }
591
592    fn int_cummax(tensor: IntTensor<Flex>, dim: usize) -> IntTensor<Flex> {
593        match tensor.dtype() {
594            DType::I64 => crate::ops::cumulative::cummax::<i64>(tensor, dim),
595            DType::I32 => crate::ops::cumulative::cummax::<i32>(tensor, dim),
596            DType::I16 => crate::ops::cumulative::cummax::<i16>(tensor, dim),
597            DType::I8 => crate::ops::cumulative::cummax::<i8>(tensor, dim),
598            DType::U64 => crate::ops::cumulative::cummax::<u64>(tensor, dim),
599            DType::U32 => crate::ops::cumulative::cummax::<u32>(tensor, dim),
600            DType::U16 => crate::ops::cumulative::cummax::<u16>(tensor, dim),
601            DType::U8 => crate::ops::cumulative::cummax::<u8>(tensor, dim),
602            dt => panic!("int_cummax: unsupported dtype {:?}", dt),
603        }
604    }
605
606    fn int_argmax(tensor: IntTensor<Flex>, dim: usize) -> IntTensor<Flex> {
607        crate::ops::reduce::argmax(tensor, dim)
608    }
609
610    fn int_argmin(tensor: IntTensor<Flex>, dim: usize) -> IntTensor<Flex> {
611        crate::ops::reduce::argmin(tensor, dim)
612    }
613
614    fn int_abs(tensor: IntTensor<Flex>) -> IntTensor<Flex> {
615        crate::ops::unary::int_abs(tensor)
616    }
617
618    fn bitwise_and(lhs: IntTensor<Flex>, rhs: IntTensor<Flex>) -> IntTensor<Flex> {
619        int_binary_op(lhs, rhs, |a, b| a & b)
620    }
621
622    fn bitwise_and_scalar(lhs: IntTensor<Flex>, rhs: Scalar) -> IntTensor<Flex> {
623        if lhs.dtype() == DType::U64 {
624            return scalar_op_typed(lhs, rhs.to_u64().unwrap(), |a: u64, b: u64| a & b);
625        }
626        int_scalar_op(lhs, rhs.to_i64().unwrap(), |a, b| a & b)
627    }
628
629    fn bitwise_or(lhs: IntTensor<Flex>, rhs: IntTensor<Flex>) -> IntTensor<Flex> {
630        int_binary_op(lhs, rhs, |a, b| a | b)
631    }
632
633    fn bitwise_or_scalar(lhs: IntTensor<Flex>, rhs: Scalar) -> IntTensor<Flex> {
634        if lhs.dtype() == DType::U64 {
635            return scalar_op_typed(lhs, rhs.to_u64().unwrap(), |a: u64, b: u64| a | b);
636        }
637        int_scalar_op(lhs, rhs.to_i64().unwrap(), |a, b| a | b)
638    }
639
640    fn bitwise_xor(lhs: IntTensor<Flex>, rhs: IntTensor<Flex>) -> IntTensor<Flex> {
641        int_binary_op(lhs, rhs, |a, b| a ^ b)
642    }
643
644    fn bitwise_xor_scalar(lhs: IntTensor<Flex>, rhs: Scalar) -> IntTensor<Flex> {
645        if lhs.dtype() == DType::U64 {
646            return scalar_op_typed(lhs, rhs.to_u64().unwrap(), |a: u64, b: u64| a ^ b);
647        }
648        int_scalar_op(lhs, rhs.to_i64().unwrap(), |a, b| a ^ b)
649    }
650
651    fn bitwise_not(tensor: IntTensor<Flex>) -> IntTensor<Flex> {
652        // Use scalar op with dummy value, only applying NOT to lhs
653        int_scalar_op(tensor, 0, |a, _| !a)
654    }
655
656    // Shift amounts masked to type width via wrapping_shl/wrapping_shr.
657    fn bitwise_left_shift(lhs: IntTensor<Flex>, rhs: IntTensor<Flex>) -> IntTensor<Flex> {
658        int_binary_op(lhs, rhs, |a, b| a.wrapping_shl(b as u32))
659    }
660
661    fn bitwise_left_shift_scalar(lhs: IntTensor<Flex>, rhs: Scalar) -> IntTensor<Flex> {
662        int_scalar_op(lhs, rhs.to_i64().unwrap(), |a, b| a.wrapping_shl(b as u32))
663    }
664
665    fn bitwise_right_shift(lhs: IntTensor<Flex>, rhs: IntTensor<Flex>) -> IntTensor<Flex> {
666        int_binary_op(lhs, rhs, |a, b| a.wrapping_shr(b as u32))
667    }
668
669    fn bitwise_right_shift_scalar(lhs: IntTensor<Flex>, rhs: Scalar) -> IntTensor<Flex> {
670        int_scalar_op(lhs, rhs.to_i64().unwrap(), |a, b| a.wrapping_shr(b as u32))
671    }
672
673    fn int_cast(tensor: IntTensor<Flex>, dtype: IntDType) -> IntTensor<Flex> {
674        let target_dtype: DType = dtype.into();
675
676        // If already the target dtype, return as-is
677        if tensor.dtype() == target_dtype {
678            return tensor;
679        }
680
681        // Make contiguous for easier iteration
682        let tensor = tensor.to_contiguous();
683        let shape = tensor.layout().shape().clone();
684
685        // Helper macro to convert between types
686        macro_rules! cast_impl {
687            ($src_type:ty, $dst_type:ty, $dst_dtype:expr) => {{
688                let src: &[$src_type] = tensor.storage();
689                let dst: Vec<$dst_type> = src.iter().map(|&x| x as $dst_type).collect();
690                FlexTensor::new(
691                    Bytes::from_elems(dst),
692                    Layout::contiguous(shape),
693                    $dst_dtype,
694                )
695            }};
696        }
697
698        // Match source dtype to target dtype
699        match (tensor.dtype(), target_dtype) {
700            // From I64
701            (DType::I64, DType::I32) => cast_impl!(i64, i32, DType::I32),
702            (DType::I64, DType::I16) => cast_impl!(i64, i16, DType::I16),
703            (DType::I64, DType::I8) => cast_impl!(i64, i8, DType::I8),
704            (DType::I64, DType::U64) => cast_impl!(i64, u64, DType::U64),
705            (DType::I64, DType::U32) => cast_impl!(i64, u32, DType::U32),
706            (DType::I64, DType::U16) => cast_impl!(i64, u16, DType::U16),
707            (DType::I64, DType::U8) => cast_impl!(i64, u8, DType::U8),
708
709            // From I32
710            (DType::I32, DType::I64) => cast_impl!(i32, i64, DType::I64),
711            (DType::I32, DType::I16) => cast_impl!(i32, i16, DType::I16),
712            (DType::I32, DType::I8) => cast_impl!(i32, i8, DType::I8),
713            (DType::I32, DType::U64) => cast_impl!(i32, u64, DType::U64),
714            (DType::I32, DType::U32) => cast_impl!(i32, u32, DType::U32),
715            (DType::I32, DType::U16) => cast_impl!(i32, u16, DType::U16),
716            (DType::I32, DType::U8) => cast_impl!(i32, u8, DType::U8),
717
718            // From I16
719            (DType::I16, DType::I64) => cast_impl!(i16, i64, DType::I64),
720            (DType::I16, DType::I32) => cast_impl!(i16, i32, DType::I32),
721            (DType::I16, DType::I8) => cast_impl!(i16, i8, DType::I8),
722            (DType::I16, DType::U64) => cast_impl!(i16, u64, DType::U64),
723            (DType::I16, DType::U32) => cast_impl!(i16, u32, DType::U32),
724            (DType::I16, DType::U16) => cast_impl!(i16, u16, DType::U16),
725            (DType::I16, DType::U8) => cast_impl!(i16, u8, DType::U8),
726
727            // From I8
728            (DType::I8, DType::I64) => cast_impl!(i8, i64, DType::I64),
729            (DType::I8, DType::I32) => cast_impl!(i8, i32, DType::I32),
730            (DType::I8, DType::I16) => cast_impl!(i8, i16, DType::I16),
731            (DType::I8, DType::U64) => cast_impl!(i8, u64, DType::U64),
732            (DType::I8, DType::U32) => cast_impl!(i8, u32, DType::U32),
733            (DType::I8, DType::U16) => cast_impl!(i8, u16, DType::U16),
734            (DType::I8, DType::U8) => cast_impl!(i8, u8, DType::U8),
735
736            // From U64
737            (DType::U64, DType::I64) => cast_impl!(u64, i64, DType::I64),
738            (DType::U64, DType::I32) => cast_impl!(u64, i32, DType::I32),
739            (DType::U64, DType::I16) => cast_impl!(u64, i16, DType::I16),
740            (DType::U64, DType::I8) => cast_impl!(u64, i8, DType::I8),
741            (DType::U64, DType::U32) => cast_impl!(u64, u32, DType::U32),
742            (DType::U64, DType::U16) => cast_impl!(u64, u16, DType::U16),
743            (DType::U64, DType::U8) => cast_impl!(u64, u8, DType::U8),
744
745            // From U32
746            (DType::U32, DType::I64) => cast_impl!(u32, i64, DType::I64),
747            (DType::U32, DType::I32) => cast_impl!(u32, i32, DType::I32),
748            (DType::U32, DType::I16) => cast_impl!(u32, i16, DType::I16),
749            (DType::U32, DType::I8) => cast_impl!(u32, i8, DType::I8),
750            (DType::U32, DType::U64) => cast_impl!(u32, u64, DType::U64),
751            (DType::U32, DType::U16) => cast_impl!(u32, u16, DType::U16),
752            (DType::U32, DType::U8) => cast_impl!(u32, u8, DType::U8),
753
754            // From U16
755            (DType::U16, DType::I64) => cast_impl!(u16, i64, DType::I64),
756            (DType::U16, DType::I32) => cast_impl!(u16, i32, DType::I32),
757            (DType::U16, DType::I16) => cast_impl!(u16, i16, DType::I16),
758            (DType::U16, DType::I8) => cast_impl!(u16, i8, DType::I8),
759            (DType::U16, DType::U64) => cast_impl!(u16, u64, DType::U64),
760            (DType::U16, DType::U32) => cast_impl!(u16, u32, DType::U32),
761            (DType::U16, DType::U8) => cast_impl!(u16, u8, DType::U8),
762
763            // From U8
764            (DType::U8, DType::I64) => cast_impl!(u8, i64, DType::I64),
765            (DType::U8, DType::I32) => cast_impl!(u8, i32, DType::I32),
766            (DType::U8, DType::I16) => cast_impl!(u8, i16, DType::I16),
767            (DType::U8, DType::I8) => cast_impl!(u8, i8, DType::I8),
768            (DType::U8, DType::U64) => cast_impl!(u8, u64, DType::U64),
769            (DType::U8, DType::U32) => cast_impl!(u8, u32, DType::U32),
770            (DType::U8, DType::U16) => cast_impl!(u8, u16, DType::U16),
771
772            _ => panic!(
773                "int_cast: unsupported conversion from {:?} to {:?}",
774                tensor.dtype(),
775                target_dtype
776            ),
777        }
778    }
779
780    fn int_unfold(
781        tensor: IntTensor<Flex>,
782        dim: usize,
783        size: usize,
784        step: usize,
785    ) -> IntTensor<Flex> {
786        crate::ops::unfold::unfold_int(tensor, dim, size, step)
787    }
788
789    fn int_neg(tensor: IntTensor<Flex>) -> IntTensor<Flex> {
790        int_scalar_op(tensor, 0i64, |a, _| a.wrapping_neg())
791    }
792
793    fn int_clamp(tensor: IntTensor<Flex>, min: Scalar, max: Scalar) -> IntTensor<Flex> {
794        if tensor.dtype() == DType::U64 {
795            let min_val = min.to_u64().unwrap();
796            let max_val = max.to_u64().unwrap();
797            return scalar_op_typed(tensor, 0u64, move |x: u64, _| x.clamp(min_val, max_val));
798        }
799        let min_val = min.to_i64().unwrap();
800        let max_val = max.to_i64().unwrap();
801        int_scalar_op(tensor, 0i64, move |x, _| x.clamp(min_val, max_val))
802    }
803
804    fn int_clamp_min(tensor: IntTensor<Flex>, min: Scalar) -> IntTensor<Flex> {
805        if tensor.dtype() == DType::U64 {
806            let min_val = min.to_u64().unwrap();
807            return scalar_op_typed(tensor, 0u64, move |x: u64, _| x.max(min_val));
808        }
809        let min_val = min.to_i64().unwrap();
810        int_scalar_op(tensor, 0i64, move |x, _| x.max(min_val))
811    }
812
813    fn int_clamp_max(tensor: IntTensor<Flex>, max: Scalar) -> IntTensor<Flex> {
814        if tensor.dtype() == DType::U64 {
815            let max_val = max.to_u64().unwrap();
816            return scalar_op_typed(tensor, 0u64, move |x: u64, _| x.min(max_val));
817        }
818        let max_val = max.to_i64().unwrap();
819        int_scalar_op(tensor, 0i64, move |x, _| x.min(max_val))
820    }
821
822    fn int_sign(tensor: IntTensor<Flex>) -> IntTensor<Flex> {
823        if tensor.dtype() == DType::U64 {
824            return scalar_op_typed(tensor, 0u64, |x: u64, _| if x > 0 { 1 } else { 0 });
825        }
826        int_scalar_op(tensor, 0i64, |x, _| {
827            if x > 0 {
828                1
829            } else if x < 0 {
830                -1
831            } else {
832                0
833            }
834        })
835    }
836
837    fn int_mean(tensor: IntTensor<Flex>) -> IntTensor<Flex> {
838        let n = tensor.layout().num_elements();
839        assert!(n > 0, "int_mean: cannot take mean of empty tensor");
840        let dtype = tensor.dtype();
841        let sum_result = crate::ops::reduce::sum(tensor);
842        // Compute in i64 to avoid truncation of n for small int types
843        macro_rules! compute_mean {
844            ($ty:ty) => {{
845                let data: &[$ty] = sum_result.storage();
846                let mean_val = (data[0] as i64 / n as i64) as $ty;
847                FlexTensor::new(
848                    Bytes::from_elems(alloc::vec![mean_val]),
849                    Layout::contiguous(Shape::from(alloc::vec![1])),
850                    dtype,
851                )
852            }};
853        }
854        match dtype {
855            DType::I64 => compute_mean!(i64),
856            DType::I32 => compute_mean!(i32),
857            DType::I16 => compute_mean!(i16),
858            DType::I8 => compute_mean!(i8),
859            other => panic!("int_mean: unsupported dtype {:?}", other),
860        }
861    }
862
863    fn int_max(tensor: IntTensor<Flex>) -> IntTensor<Flex> {
864        crate::ops::reduce::max(tensor)
865    }
866
867    fn int_max_dim(tensor: IntTensor<Flex>, dim: usize) -> IntTensor<Flex> {
868        crate::ops::reduce::max_dim(tensor, dim)
869    }
870
871    fn int_min(tensor: IntTensor<Flex>) -> IntTensor<Flex> {
872        crate::ops::reduce::min(tensor)
873    }
874
875    fn int_min_dim(tensor: IntTensor<Flex>, dim: usize) -> IntTensor<Flex> {
876        crate::ops::reduce::min_dim(tensor, dim)
877    }
878
879    fn int_max_dim_with_indices(
880        tensor: IntTensor<Flex>,
881        dim: usize,
882    ) -> (IntTensor<Flex>, IntTensor<Flex>) {
883        crate::ops::reduce::max_dim_with_indices(tensor, dim)
884    }
885
886    fn int_min_dim_with_indices(
887        tensor: IntTensor<Flex>,
888        dim: usize,
889    ) -> (IntTensor<Flex>, IntTensor<Flex>) {
890        crate::ops::reduce::min_dim_with_indices(tensor, dim)
891    }
892
893    fn int_any(tensor: IntTensor<Flex>, out_dtype: burn_std::BoolDType) -> BoolTensor<Flex> {
894        crate::ops::comparison::any_int(tensor, out_dtype)
895    }
896
897    fn int_any_dim(
898        tensor: IntTensor<Flex>,
899        dim: usize,
900        out_dtype: burn_std::BoolDType,
901    ) -> BoolTensor<Flex> {
902        crate::ops::comparison::any_int_dim(tensor, dim, out_dtype)
903    }
904
905    fn int_all(tensor: IntTensor<Flex>, out_dtype: burn_std::BoolDType) -> BoolTensor<Flex> {
906        crate::ops::comparison::all_int(tensor, out_dtype)
907    }
908
909    fn int_all_dim(
910        tensor: IntTensor<Flex>,
911        dim: usize,
912        out_dtype: burn_std::BoolDType,
913    ) -> BoolTensor<Flex> {
914        crate::ops::comparison::all_int_dim(tensor, dim, out_dtype)
915    }
916
917    fn int_powi(lhs: IntTensor<Flex>, rhs: IntTensor<Flex>) -> IntTensor<Flex> {
918        int_binary_op(lhs, rhs, |a, b| a.wrapping_pow(b as u32))
919    }
920
921    fn int_zeros(shape: Shape, _device: &Device<Flex>, dtype: IntDType) -> IntTensor<Flex> {
922        FlexTensor::zeros(shape, dtype.into())
923    }
924
925    fn int_ones(shape: Shape, _device: &Device<Flex>, dtype: IntDType) -> IntTensor<Flex> {
926        let dt: DType = dtype.into();
927        match dt {
928            DType::I64 => FlexTensor::filled_typed(shape, dt, 1i64),
929            DType::I32 => FlexTensor::filled_typed(shape, dt, 1i32),
930            DType::I16 => FlexTensor::filled_typed(shape, dt, 1i16),
931            DType::I8 => FlexTensor::filled_typed(shape, dt, 1i8),
932            DType::U64 => FlexTensor::filled_typed(shape, dt, 1u64),
933            DType::U32 => FlexTensor::filled_typed(shape, dt, 1u32),
934            DType::U16 => FlexTensor::filled_typed(shape, dt, 1u16),
935            DType::U8 => FlexTensor::filled_typed(shape, dt, 1u8),
936            _ => unreachable!(),
937        }
938    }
939
940    fn int_full(
941        shape: Shape,
942        fill_value: burn_backend::Scalar,
943        _device: &Device<Flex>,
944        dtype: IntDType,
945    ) -> IntTensor<Flex> {
946        let dt: DType = dtype.into();
947        let v = fill_value.to_i64().unwrap();
948        match dt {
949            DType::I64 => FlexTensor::filled_typed(shape, dt, v),
950            DType::I32 => FlexTensor::filled_typed(shape, dt, v as i32),
951            DType::I16 => FlexTensor::filled_typed(shape, dt, v as i16),
952            DType::I8 => FlexTensor::filled_typed(shape, dt, v as i8),
953            DType::U64 => FlexTensor::filled_typed(shape, dt, v as u64),
954            DType::U32 => FlexTensor::filled_typed(shape, dt, v as u32),
955            DType::U16 => FlexTensor::filled_typed(shape, dt, v as u16),
956            DType::U8 => FlexTensor::filled_typed(shape, dt, v as u8),
957            _ => unreachable!(),
958        }
959    }
960
961    fn int_transpose(tensor: IntTensor<Flex>) -> IntTensor<Flex> {
962        let ndims = tensor.layout().num_dims();
963        if ndims < 2 {
964            return tensor;
965        }
966        tensor.transpose(ndims - 2, ndims - 1)
967    }
968
969    fn int_repeat_dim(tensor: IntTensor<Flex>, dim: usize, times: usize) -> IntTensor<Flex> {
970        crate::ops::repeat_dim::repeat_dim(tensor, dim, times)
971    }
972
973    fn int_not_equal(
974        lhs: IntTensor<Flex>,
975        rhs: IntTensor<Flex>,
976        out_dtype: burn_std::BoolDType,
977    ) -> BoolTensor<Flex> {
978        crate::ops::comparison::int_not_equal(lhs, rhs, out_dtype)
979    }
980
981    fn int_not_equal_elem(
982        lhs: IntTensor<Flex>,
983        rhs: burn_backend::Scalar,
984        out_dtype: burn_std::BoolDType,
985    ) -> BoolTensor<Flex> {
986        let (i, u) = scalar_to_int_pair(lhs.dtype(), &rhs);
987        crate::ops::comparison::int_not_equal_elem(lhs, i, u, out_dtype)
988    }
989
990    fn int_sort(tensor: IntTensor<Flex>, dim: usize, descending: bool) -> IntTensor<Flex> {
991        crate::ops::sort::sort(tensor, dim, descending)
992    }
993
994    fn int_sort_with_indices(
995        tensor: IntTensor<Flex>,
996        dim: usize,
997        descending: bool,
998    ) -> (IntTensor<Flex>, IntTensor<Flex>) {
999        crate::ops::sort::sort_with_indices(tensor, dim, descending)
1000    }
1001
1002    fn int_argsort(tensor: IntTensor<Flex>, dim: usize, descending: bool) -> IntTensor<Flex> {
1003        crate::ops::sort::argsort(tensor, dim, descending)
1004    }
1005
1006    fn int_powi_scalar(lhs: IntTensor<Flex>, rhs: burn_backend::Scalar) -> IntTensor<Flex> {
1007        use num_traits::ToPrimitive;
1008        match rhs.to_i64().unwrap() {
1009            0 => Self::int_ones(lhs.shape(), &Default::default(), lhs.dtype().into()),
1010            1 => lhs,
1011            2 => Self::int_mul(lhs.clone(), lhs),
1012            _ => Self::int_powi_scalar_impl(lhs, rhs),
1013        }
1014    }
1015
1016    fn int_powi_scalar_impl(lhs: IntTensor<Flex>, rhs: burn_backend::Scalar) -> IntTensor<Flex> {
1017        use num_traits::ToPrimitive;
1018        let exp = rhs.to_i64().unwrap() as u32;
1019        if lhs.dtype() == DType::U64 {
1020            return scalar_op_typed(lhs, exp as u64, move |x: u64, _| x.wrapping_pow(exp));
1021        }
1022        int_scalar_op(lhs, exp as i64, move |x, _| x.wrapping_pow(exp))
1023    }
1024
1025    fn int_max_abs(tensor: IntTensor<Flex>) -> IntTensor<Flex> {
1026        let abs = Self::int_abs(tensor);
1027        crate::ops::reduce::max(abs)
1028    }
1029
1030    fn int_max_abs_dim(tensor: IntTensor<Flex>, dim: usize) -> IntTensor<Flex> {
1031        let abs = Self::int_abs(tensor);
1032        crate::ops::reduce::max_dim(abs, dim)
1033    }
1034
1035    fn int_arange(
1036        range: core::ops::Range<i64>,
1037        _device: &Device<Flex>,
1038        dtype: IntDType,
1039    ) -> IntTensor<Flex> {
1040        Self::int_arange_step(range, 1, &Default::default(), dtype)
1041    }
1042
1043    fn int_arange_step(
1044        range: core::ops::Range<i64>,
1045        step: usize,
1046        _device: &Device<Flex>,
1047        dtype: IntDType,
1048    ) -> IntTensor<Flex> {
1049        let dt: DType = dtype.into();
1050
1051        macro_rules! arange_typed {
1052            ($ty:ty) => {{
1053                let data: Vec<$ty> = range.step_by(step).map(|v| v as $ty).collect();
1054                let shape = Shape::from(alloc::vec![data.len()]);
1055                FlexTensor::new(Bytes::from_elems(data), Layout::contiguous(shape), dt)
1056            }};
1057        }
1058
1059        match dt {
1060            DType::I64 => arange_typed!(i64),
1061            DType::I32 => arange_typed!(i32),
1062            DType::I16 => arange_typed!(i16),
1063            DType::I8 => arange_typed!(i8),
1064            DType::U64 => arange_typed!(u64),
1065            DType::U32 => arange_typed!(u32),
1066            DType::U16 => arange_typed!(u16),
1067            DType::U8 => arange_typed!(u8),
1068            _ => unreachable!(),
1069        }
1070    }
1071}
1072
1073// Tests kept here exercise flex-specific behavior: dtype storage
1074// selection for every int width (I16/I32/U8/U16/U32/I64/U64), and edge
1075// cases of the dtype-specific kernels (u64 wrap, i64::MIN abs/neg, bit
1076// shift at width). Plain int arithmetic, scalar ops, bool->int cast
1077// smokes, and negative-stride (flipped/transposed) variants have been
1078// migrated to burn-backend-tests so they run against every backend.
1079// When adding new tests, keep them here only if they probe flex dtype
1080// storage; otherwise add them to
1081// crates/burn-backend-tests/tests/tensor/int/ops/.
1082#[cfg(test)]
1083mod tests {
1084    use alloc::vec;
1085    use burn_backend::TensorData;
1086    use burn_backend::ops::IntTensorOps;
1087
1088    use crate::Flex;
1089    use crate::FlexTensor;
1090
1091    #[test]
1092    fn test_u64_div_large_values() {
1093        let a = FlexTensor::from_data(TensorData::new(vec![u64::MAX], [1]));
1094        let b = FlexTensor::from_data(TensorData::new(vec![2u64], [1]));
1095        let result = Flex::int_div(a, b);
1096        let values: Vec<u64> = bytemuck::cast_slice(&result.into_data().bytes).to_vec();
1097        assert_eq!(values[0], u64::MAX / 2);
1098    }
1099
1100    #[test]
1101    fn test_u64_remainder_large_values() {
1102        let a = FlexTensor::from_data(TensorData::new(vec![u64::MAX], [1]));
1103        let b = FlexTensor::from_data(TensorData::new(vec![2u64], [1]));
1104        let result = Flex::int_remainder(a, b);
1105        let values: Vec<u64> = bytemuck::cast_slice(&result.into_data().bytes).to_vec();
1106        assert_eq!(values[0], u64::MAX % 2);
1107    }
1108
1109    #[test]
1110    fn test_int_abs_min_value() {
1111        // i64::MIN.abs() panics in debug; wrapping_abs returns MIN (matches PyTorch)
1112        let a = FlexTensor::from_data(TensorData::new(vec![i64::MIN], [1]));
1113        let result = Flex::int_abs(a);
1114        let values: Vec<i64> = bytemuck::cast_slice(&result.into_data().bytes).to_vec();
1115        assert_eq!(values[0], i64::MIN.wrapping_abs());
1116    }
1117
1118    #[test]
1119    fn test_int_neg_min_value() {
1120        // i64::MIN negation panics in debug; wrapping_neg returns MIN (matches PyTorch)
1121        let a = FlexTensor::from_data(TensorData::new(vec![i64::MIN], [1]));
1122        let result = Flex::int_neg(a);
1123        let values: Vec<i64> = bytemuck::cast_slice(&result.into_data().bytes).to_vec();
1124        assert_eq!(values[0], i64::MIN.wrapping_neg());
1125    }
1126
1127    #[test]
1128    fn test_int_shift_large_amount() {
1129        // Shift by >= bit width panics without wrapping; should not crash
1130        let a = FlexTensor::from_data(TensorData::new(vec![1i64], [1]));
1131        let b = FlexTensor::from_data(TensorData::new(vec![64i64], [1]));
1132        let _left = Flex::bitwise_left_shift(a.clone(), b.clone());
1133        let _right = Flex::bitwise_right_shift(a, b);
1134    }
1135
1136    #[test]
1137    fn test_int_into_float_f64() {
1138        use burn_backend::ops::IntTensorOps;
1139        use burn_std::FloatDType;
1140
1141        let t = FlexTensor::from_data(TensorData::new(vec![1i64, 2, -3], [3]));
1142        let result = Flex::int_into_float(t, FloatDType::F64);
1143        assert_eq!(result.dtype(), burn_backend::DType::F64);
1144        let data: Vec<f64> = result.into_data().to_vec().unwrap();
1145        assert_eq!(data, vec![1.0f64, 2.0, -3.0]);
1146    }
1147
1148    #[test]
1149    fn test_u64_add_scalar_large() {
1150        let t = FlexTensor::from_data(TensorData::new(vec![1u64, 2, 3], [3]));
1151        let big: u64 = (i64::MAX as u64) + 100;
1152        let result = Flex::int_add_scalar(t, burn_backend::Scalar::from(big));
1153        let data: Vec<u64> = result.into_data().to_vec().unwrap();
1154        assert_eq!(data, vec![big + 1, big + 2, big + 3]);
1155    }
1156
1157    #[test]
1158    fn test_u64_greater_elem_large() {
1159        let big: u64 = (i64::MAX as u64) + 100;
1160        let t = FlexTensor::from_data(TensorData::new(vec![big, big + 1, big - 1], [3]));
1161        let result = Flex::int_greater_elem(
1162            t,
1163            burn_backend::Scalar::from(big),
1164            burn_std::BoolStore::Native,
1165        );
1166        let data: Vec<bool> = result.into_data().to_vec().unwrap();
1167        assert_eq!(data, vec![false, true, false]);
1168    }
1169
1170    #[test]
1171    fn test_int_mask_fill_i32() {
1172        let t = FlexTensor::from_data(TensorData::new(vec![1i32, 2, 3, 4], [4]));
1173        let mask = FlexTensor::from_data(TensorData::new(vec![true, false, true, false], [4]));
1174        let result = Flex::int_mask_fill(t, mask, burn_backend::Scalar::from(0i64));
1175        let data: Vec<i32> = result.into_data().to_vec().unwrap();
1176        assert_eq!(data, vec![0, 2, 0, 4]);
1177    }
1178
1179    #[test]
1180    fn test_int_mask_fill_i16() {
1181        let t = FlexTensor::from_data(TensorData::new(vec![10i16, 20, 30, 40], [4]));
1182        let mask = FlexTensor::from_data(TensorData::new(vec![false, true, false, true], [4]));
1183        let result = Flex::int_mask_fill(t, mask, burn_backend::Scalar::from(-1i64));
1184        let data: Vec<i16> = result.into_data().to_vec().unwrap();
1185        assert_eq!(data, vec![10, -1, 30, -1]);
1186    }
1187
1188    #[test]
1189    fn test_int_mask_fill_u8() {
1190        let t = FlexTensor::from_data(TensorData::new(vec![1u8, 2, 3, 4], [4]));
1191        let mask = FlexTensor::from_data(TensorData::new(vec![true, true, false, false], [4]));
1192        let result = Flex::int_mask_fill(t, mask, burn_backend::Scalar::from(255i64));
1193        let data: Vec<u8> = result.into_data().to_vec().unwrap();
1194        assert_eq!(data, vec![255, 255, 3, 4]);
1195    }
1196
1197    #[test]
1198    fn test_int_mask_fill_u32() {
1199        let t = FlexTensor::from_data(TensorData::new(vec![100u32, 200, 300], [3]));
1200        let mask = FlexTensor::from_data(TensorData::new(vec![true, false, true], [3]));
1201        let result = Flex::int_mask_fill(t, mask, burn_backend::Scalar::from(0i64));
1202        let data: Vec<u32> = result.into_data().to_vec().unwrap();
1203        assert_eq!(data, vec![0, 200, 0]);
1204    }
1205
1206    #[test]
1207    fn test_int_mask_where_i32() {
1208        let t = FlexTensor::from_data(TensorData::new(vec![1i32, 2, 3, 4], [4]));
1209        let mask = FlexTensor::from_data(TensorData::new(vec![true, false, true, false], [4]));
1210        let v = FlexTensor::from_data(TensorData::new(vec![10i32, 20, 30, 40], [4]));
1211        let result = Flex::int_mask_where(t, mask, v);
1212        let data: Vec<i32> = result.into_data().to_vec().unwrap();
1213        assert_eq!(data, vec![10, 2, 30, 4]);
1214    }
1215
1216    #[test]
1217    fn test_int_mask_where_u8() {
1218        let t = FlexTensor::from_data(TensorData::new(vec![1u8, 2, 3, 4], [4]));
1219        let mask = FlexTensor::from_data(TensorData::new(vec![false, true, false, true], [4]));
1220        let v = FlexTensor::from_data(TensorData::new(vec![10u8, 20, 30, 40], [4]));
1221        let result = Flex::int_mask_where(t, mask, v);
1222        let data: Vec<u8> = result.into_data().to_vec().unwrap();
1223        assert_eq!(data, vec![1, 20, 3, 40]);
1224    }
1225
1226    #[test]
1227    fn test_int_gather_i32() {
1228        let t = FlexTensor::from_data(TensorData::new(vec![10i32, 20, 30, 40, 50, 60], [2, 3]));
1229        let indices = FlexTensor::from_data(TensorData::new(vec![2i64, 0, 1, 2], [2, 2]));
1230        let result = Flex::int_gather(1, t, indices);
1231        let data: Vec<i32> = result.into_data().to_vec().unwrap();
1232        assert_eq!(data, vec![30, 10, 50, 60]);
1233    }
1234
1235    #[test]
1236    fn test_int_select_u16() {
1237        let t = FlexTensor::from_data(TensorData::new(vec![10u16, 20, 30, 40, 50, 60], [2, 3]));
1238        let indices = FlexTensor::from_data(TensorData::new(vec![0i64, 1], [2]));
1239        let result = Flex::int_select(t, 1, indices);
1240        let data: Vec<u16> = result.into_data().to_vec().unwrap();
1241        assert_eq!(data, vec![10, 20, 40, 50]);
1242    }
1243
1244    #[test]
1245    fn test_int_cumsum_i32() {
1246        let t = FlexTensor::from_data(TensorData::new(vec![1i32, 2, 3, 4], [4]));
1247        let result = Flex::int_cumsum(t, 0);
1248        let data: Vec<i32> = result.into_data().to_vec().unwrap();
1249        assert_eq!(data, vec![1, 3, 6, 10]);
1250    }
1251
1252    #[test]
1253    fn test_int_cumprod_u8() {
1254        let t = FlexTensor::from_data(TensorData::new(vec![1u8, 2, 3, 4], [4]));
1255        let result = Flex::int_cumprod(t, 0);
1256        let data: Vec<u8> = result.into_data().to_vec().unwrap();
1257        assert_eq!(data, vec![1, 2, 6, 24]);
1258    }
1259
1260    #[test]
1261    fn test_int_cummin_i32() {
1262        let t = FlexTensor::from_data(TensorData::new(vec![3i32, 1, 4, 1, 5], [5]));
1263        let result = Flex::int_cummin(t, 0);
1264        let data: Vec<i32> = result.into_data().to_vec().unwrap();
1265        assert_eq!(data, vec![3, 1, 1, 1, 1]);
1266    }
1267
1268    #[test]
1269    fn test_int_cummax_u16() {
1270        let t = FlexTensor::from_data(TensorData::new(vec![3u16, 1, 4, 1, 5], [5]));
1271        let result = Flex::int_cummax(t, 0);
1272        let data: Vec<u16> = result.into_data().to_vec().unwrap();
1273        assert_eq!(data, vec![3, 3, 4, 4, 5]);
1274    }
1275
1276    #[test]
1277    fn test_int_scatter_add_i32() {
1278        let t = FlexTensor::from_data(TensorData::new(vec![0i32, 0, 0], [1, 3]));
1279        let indices = FlexTensor::from_data(TensorData::new(vec![0i64, 2, 1], [1, 3]));
1280        let values = FlexTensor::from_data(TensorData::new(vec![10i32, 20, 30], [1, 3]));
1281        let result = Flex::int_scatter_add(1, t, indices, values);
1282        let data: Vec<i32> = result.into_data().to_vec().unwrap();
1283        assert_eq!(data, vec![10, 30, 20]);
1284    }
1285
1286    #[test]
1287    fn test_int_select_add_u8() {
1288        let t = FlexTensor::from_data(TensorData::new(vec![1u8, 2, 3], [3]));
1289        let indices = FlexTensor::from_data(TensorData::new(vec![0i64, 2], [2]));
1290        let values = FlexTensor::from_data(TensorData::new(vec![10u8, 20], [2]));
1291        let result = Flex::int_select_add(t, 0, indices, values);
1292        let data: Vec<u8> = result.into_data().to_vec().unwrap();
1293        assert_eq!(data, vec![11, 2, 23]);
1294    }
1295
1296    #[test]
1297    fn test_int_random_i32() {
1298        use burn_backend::{DType, Distribution, ops::IntTensorOps};
1299        use burn_std::{IntDType, Shape};
1300
1301        let shape = Shape::from(vec![100]);
1302        let dist = Distribution::Uniform(0.0, 10.0);
1303        let device = crate::FlexDevice;
1304        let t = Flex::int_random(shape, dist, &device, IntDType::I32);
1305        assert_eq!(t.dtype(), DType::I32);
1306        let data: Vec<i32> = t.into_data().to_vec().unwrap();
1307        assert!(data.iter().all(|&v| (0..=10).contains(&v)));
1308    }
1309
1310    #[test]
1311    fn test_int_random_u8() {
1312        use burn_backend::{DType, Distribution, ops::IntTensorOps};
1313        use burn_std::{IntDType, Shape};
1314
1315        let shape = Shape::from(vec![50]);
1316        let dist = Distribution::Uniform(0.0, 100.0);
1317        let device = crate::FlexDevice;
1318        let t = Flex::int_random(shape, dist, &device, IntDType::U8);
1319        assert_eq!(t.dtype(), DType::U8);
1320    }
1321
1322    #[test]
1323    fn test_int_mean_i32() {
1324        use burn_backend::{DType, ops::IntTensorOps};
1325
1326        let t = FlexTensor::from_data(TensorData::new(vec![10i32, 20, 30], [3]));
1327        let result = Flex::int_mean(t);
1328        assert_eq!(result.dtype(), DType::I32);
1329        let data: Vec<i32> = result.into_data().to_vec().unwrap();
1330        assert_eq!(data, vec![20]); // (10 + 20 + 30) / 3 = 20
1331    }
1332}