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, Element, IntDType, Shape, Slice, bf16, f16};
10use num_traits::{AsPrimitive, ToPrimitive, WrappingShl, WrappingShr};
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/// Python/PyTorch-style remainder: result has same sign as divisor.
17#[inline]
18fn remainder_int(a: i64, b: i64) -> i64 {
19    let r = a.wrapping_rem(b);
20    if r != 0 && (r < 0) != (b < 0) {
21        r.wrapping_add(b)
22    } else {
23        r
24    }
25}
26
27/// Convert a Scalar to (i64, u64) pair for the given dtype.
28/// Only the matching type's conversion is validated; the other gets a dummy 0.
29fn scalar_to_int_pair(dtype: DType, rhs: &Scalar) -> (i64, u64) {
30    if dtype == DType::U64 {
31        (0, rhs.to_u64().unwrap())
32    } else {
33        (rhs.to_i64().unwrap(), 0)
34    }
35}
36
37enum ShiftAmount {
38    Tensor(FlexTensor),
39    Scalar(u32),
40}
41
42// Shifts run on each dtype's native type instead of going through the i64
43// widening of int_binary_op/int_scalar_op. That keeps u64 right shifts logical
44// and makes wrapping_shl/wrapping_shr mask the shift amount to the operand's
45// own bit width.
46fn int_shift<const LEFT: bool>(lhs: FlexTensor, rhs: ShiftAmount) -> FlexTensor {
47    let (lhs, rhs) = match rhs {
48        ShiftAmount::Tensor(rhs) => {
49            let (lhs, rhs) = crate::ops::expand::broadcast_binary(lhs, rhs);
50            (lhs, ShiftAmount::Tensor(rhs))
51        }
52        scalar => (lhs, scalar),
53    };
54    match lhs.dtype() {
55        DType::I64 => shift_typed::<i64, LEFT>(lhs, rhs),
56        DType::I32 => shift_typed::<i32, LEFT>(lhs, rhs),
57        DType::I16 => shift_typed::<i16, LEFT>(lhs, rhs),
58        DType::I8 => shift_typed::<i8, LEFT>(lhs, rhs),
59        DType::U64 => shift_typed::<u64, LEFT>(lhs, rhs),
60        DType::U32 => shift_typed::<u32, LEFT>(lhs, rhs),
61        DType::U16 => shift_typed::<u16, LEFT>(lhs, rhs),
62        DType::U8 => shift_typed::<u8, LEFT>(lhs, rhs),
63        dtype => panic!("int_shift: unsupported dtype {:?}", dtype),
64    }
65}
66
67fn shift_typed<E, const LEFT: bool>(lhs: FlexTensor, rhs: ShiftAmount) -> FlexTensor
68where
69    E: Element + bytemuck::Pod + WrappingShl + WrappingShr + AsPrimitive<u32>,
70{
71    let shift = |a: E, n: u32| {
72        if LEFT {
73            a.wrapping_shl(n)
74        } else {
75            a.wrapping_shr(n)
76        }
77    };
78    match rhs {
79        ShiftAmount::Tensor(rhs) => binary_op_typed(lhs, rhs, move |a: E, b: E| shift(a, b.as_())),
80        ShiftAmount::Scalar(n) => scalar_op_typed(lhs, E::zeroed(), move |a: E, _| shift(a, n)),
81    }
82}
83
84/// Any integer scalar is accepted; the amount is masked to the operand width.
85fn scalar_shift_amount(rhs: &Scalar) -> ShiftAmount {
86    let n = rhs
87        .to_i64()
88        .map(|v| v as u32)
89        .or_else(|| rhs.to_u64().map(|v| v as u32))
90        .unwrap();
91    ShiftAmount::Scalar(n)
92}
93
94impl IntTensorOps<Flex> for Flex {
95    fn int_from_data(data: TensorData, _device: &Device<Flex>) -> IntTensor<Flex> {
96        FlexTensor::from_data(data)
97    }
98
99    async fn int_into_data(tensor: IntTensor<Flex>) -> Result<TensorData, ExecutionError> {
100        Ok(tensor.into_data())
101    }
102
103    fn int_to_device(tensor: IntTensor<Flex>, _device: &Device<Flex>) -> IntTensor<Flex> {
104        tensor
105    }
106
107    fn int_cat(tensors: Vec<IntTensor<Flex>>, dim: usize) -> IntTensor<Flex> {
108        crate::ops::cat::cat(tensors, dim)
109    }
110
111    fn int_reshape(tensor: IntTensor<Flex>, shape: Shape) -> IntTensor<Flex> {
112        tensor.reshape(shape)
113    }
114
115    fn int_slice(tensor: IntTensor<Flex>, slices: &[Slice]) -> IntTensor<Flex> {
116        crate::ops::slice::slice(tensor, slices)
117    }
118
119    fn int_empty(shape: Shape, _device: &Device<Flex>, dtype: IntDType) -> IntTensor<Flex> {
120        FlexTensor::empty(shape, dtype.into())
121    }
122
123    fn int_mask_where(
124        tensor: IntTensor<Flex>,
125        mask: BoolTensor<Flex>,
126        value: IntTensor<Flex>,
127    ) -> IntTensor<Flex> {
128        debug_assert_eq!(
129            tensor.dtype(),
130            value.dtype(),
131            "int_mask_where: dtype mismatch"
132        );
133        match tensor.dtype() {
134            DType::I64 => crate::ops::mask::mask_where::<i64>(tensor, mask, value),
135            DType::I32 => crate::ops::mask::mask_where::<i32>(tensor, mask, value),
136            DType::I16 => crate::ops::mask::mask_where::<i16>(tensor, mask, value),
137            DType::I8 => crate::ops::mask::mask_where::<i8>(tensor, mask, value),
138            DType::U64 => crate::ops::mask::mask_where::<u64>(tensor, mask, value),
139            DType::U32 => crate::ops::mask::mask_where::<u32>(tensor, mask, value),
140            DType::U16 => crate::ops::mask::mask_where::<u16>(tensor, mask, value),
141            DType::U8 => crate::ops::mask::mask_where::<u8>(tensor, mask, value),
142            dt => panic!("int_mask_where: unsupported dtype {:?}", dt),
143        }
144    }
145
146    fn int_mask_fill(
147        tensor: IntTensor<Flex>,
148        mask: BoolTensor<Flex>,
149        value: Scalar,
150    ) -> IntTensor<Flex> {
151        match tensor.dtype() {
152            DType::I64 => crate::ops::mask::mask_fill(tensor, mask, value.to_i64().unwrap()),
153            DType::I32 => crate::ops::mask::mask_fill(tensor, mask, value.to_i64().unwrap() as i32),
154            DType::I16 => crate::ops::mask::mask_fill(tensor, mask, value.to_i64().unwrap() as i16),
155            DType::I8 => crate::ops::mask::mask_fill(tensor, mask, value.to_i64().unwrap() as i8),
156            DType::U64 => crate::ops::mask::mask_fill(tensor, mask, value.to_u64().unwrap()),
157            DType::U32 => crate::ops::mask::mask_fill(tensor, mask, value.to_u64().unwrap() as u32),
158            DType::U16 => crate::ops::mask::mask_fill(tensor, mask, value.to_u64().unwrap() as u16),
159            DType::U8 => crate::ops::mask::mask_fill(tensor, mask, value.to_u64().unwrap() as u8),
160            dt => panic!("int_mask_fill: unsupported dtype {:?}", dt),
161        }
162    }
163
164    fn int_slice_assign(
165        tensor: IntTensor<Flex>,
166        slices: &[Slice],
167        value: IntTensor<Flex>,
168    ) -> IntTensor<Flex> {
169        crate::ops::slice::slice_assign(tensor, slices, value)
170    }
171
172    /// Gather ints along `dim` at the given indices.
173    ///
174    /// The `tensor` dispatches on its own int dtype (I8/I16/I32/I64 signed or
175    /// U8/U16/U32/U64 unsigned). The `indices` tensor may be any of those
176    /// widths too - it's normalised to `isize` by the shared `read_indices`
177    /// helper in `ops::gather_scatter` before the kernel runs, so callers are
178    /// not required to pre-convert to I64.
179    fn int_gather(
180        dim: usize,
181        tensor: IntTensor<Flex>,
182        indices: IntTensor<Flex>,
183    ) -> IntTensor<Flex> {
184        match tensor.dtype() {
185            DType::I64 => crate::ops::gather_scatter::gather::<i64>(tensor, dim, indices),
186            DType::I32 => crate::ops::gather_scatter::gather::<i32>(tensor, dim, indices),
187            DType::I16 => crate::ops::gather_scatter::gather::<i16>(tensor, dim, indices),
188            DType::I8 => crate::ops::gather_scatter::gather::<i8>(tensor, dim, indices),
189            DType::U64 => crate::ops::gather_scatter::gather::<u64>(tensor, dim, indices),
190            DType::U32 => crate::ops::gather_scatter::gather::<u32>(tensor, dim, indices),
191            DType::U16 => crate::ops::gather_scatter::gather::<u16>(tensor, dim, indices),
192            DType::U8 => crate::ops::gather_scatter::gather::<u8>(tensor, dim, indices),
193            dt => panic!("int_gather: unsupported dtype {:?}", dt),
194        }
195    }
196
197    fn int_scatter(
198        dim: usize,
199        tensor: IntTensor<Flex>,
200        indices: IntTensor<Flex>,
201        value: IntTensor<Flex>,
202        update: burn_backend::tensor::IndexingUpdateOp,
203    ) -> IntTensor<Flex> {
204        match update {
205            burn_backend::tensor::IndexingUpdateOp::Assign => {
206                debug_assert_eq!(tensor.dtype(), value.dtype(), "int_scatter: dtype mismatch");
207                match tensor.dtype() {
208                    DType::I64 => crate::ops::gather_scatter::scatter_assign::<i64>(
209                        tensor, dim, indices, value,
210                    ),
211                    DType::I32 => crate::ops::gather_scatter::scatter_assign::<i32>(
212                        tensor, dim, indices, value,
213                    ),
214                    DType::I16 => crate::ops::gather_scatter::scatter_assign::<i16>(
215                        tensor, dim, indices, value,
216                    ),
217                    DType::I8 => crate::ops::gather_scatter::scatter_assign::<i8>(
218                        tensor, dim, indices, value,
219                    ),
220                    DType::U64 => crate::ops::gather_scatter::scatter_assign::<u64>(
221                        tensor, dim, indices, value,
222                    ),
223                    DType::U32 => crate::ops::gather_scatter::scatter_assign::<u32>(
224                        tensor, dim, indices, value,
225                    ),
226                    DType::U16 => crate::ops::gather_scatter::scatter_assign::<u16>(
227                        tensor, dim, indices, value,
228                    ),
229                    DType::U8 => crate::ops::gather_scatter::scatter_assign::<u8>(
230                        tensor, dim, indices, value,
231                    ),
232                    dt => panic!("int_scatter: unsupported dtype {:?}", dt),
233                }
234            }
235            burn_backend::tensor::IndexingUpdateOp::Add => {
236                debug_assert_eq!(tensor.dtype(), value.dtype(), "int_scatter: dtype mismatch");
237                match tensor.dtype() {
238                    DType::I64 => {
239                        crate::ops::gather_scatter::scatter_add::<i64>(tensor, dim, indices, value)
240                    }
241                    DType::I32 => {
242                        crate::ops::gather_scatter::scatter_add::<i32>(tensor, dim, indices, value)
243                    }
244                    DType::I16 => {
245                        crate::ops::gather_scatter::scatter_add::<i16>(tensor, dim, indices, value)
246                    }
247                    DType::I8 => {
248                        crate::ops::gather_scatter::scatter_add::<i8>(tensor, dim, indices, value)
249                    }
250                    DType::U64 => {
251                        crate::ops::gather_scatter::scatter_add::<u64>(tensor, dim, indices, value)
252                    }
253                    DType::U32 => {
254                        crate::ops::gather_scatter::scatter_add::<u32>(tensor, dim, indices, value)
255                    }
256                    DType::U16 => {
257                        crate::ops::gather_scatter::scatter_add::<u16>(tensor, dim, indices, value)
258                    }
259                    DType::U8 => {
260                        crate::ops::gather_scatter::scatter_add::<u8>(tensor, dim, indices, value)
261                    }
262                    dt => panic!("int_scatter: unsupported dtype {:?}", dt),
263                }
264            }
265            burn_backend::tensor::IndexingUpdateOp::Mul => {
266                debug_assert_eq!(tensor.dtype(), value.dtype(), "int_scatter: dtype mismatch");
267                match tensor.dtype() {
268                    DType::I64 => {
269                        crate::ops::gather_scatter::scatter_mul::<i64>(tensor, dim, indices, value)
270                    }
271                    DType::I32 => {
272                        crate::ops::gather_scatter::scatter_mul::<i32>(tensor, dim, indices, value)
273                    }
274                    DType::I16 => {
275                        crate::ops::gather_scatter::scatter_mul::<i16>(tensor, dim, indices, value)
276                    }
277                    DType::I8 => {
278                        crate::ops::gather_scatter::scatter_mul::<i8>(tensor, dim, indices, value)
279                    }
280                    DType::U64 => {
281                        crate::ops::gather_scatter::scatter_mul::<u64>(tensor, dim, indices, value)
282                    }
283                    DType::U32 => {
284                        crate::ops::gather_scatter::scatter_mul::<u32>(tensor, dim, indices, value)
285                    }
286                    DType::U16 => {
287                        crate::ops::gather_scatter::scatter_mul::<u16>(tensor, dim, indices, value)
288                    }
289                    DType::U8 => {
290                        crate::ops::gather_scatter::scatter_mul::<u8>(tensor, dim, indices, value)
291                    }
292                    dt => panic!("int_scatter: unsupported dtype {:?}", dt),
293                }
294            }
295            burn_backend::tensor::IndexingUpdateOp::Min => {
296                debug_assert_eq!(tensor.dtype(), value.dtype(), "int_scatter: dtype mismatch");
297                match tensor.dtype() {
298                    DType::I64 => {
299                        crate::ops::gather_scatter::scatter_min::<i64>(tensor, dim, indices, value)
300                    }
301                    DType::I32 => {
302                        crate::ops::gather_scatter::scatter_min::<i32>(tensor, dim, indices, value)
303                    }
304                    DType::I16 => {
305                        crate::ops::gather_scatter::scatter_min::<i16>(tensor, dim, indices, value)
306                    }
307                    DType::I8 => {
308                        crate::ops::gather_scatter::scatter_min::<i8>(tensor, dim, indices, value)
309                    }
310                    DType::U64 => {
311                        crate::ops::gather_scatter::scatter_min::<u64>(tensor, dim, indices, value)
312                    }
313                    DType::U32 => {
314                        crate::ops::gather_scatter::scatter_min::<u32>(tensor, dim, indices, value)
315                    }
316                    DType::U16 => {
317                        crate::ops::gather_scatter::scatter_min::<u16>(tensor, dim, indices, value)
318                    }
319                    DType::U8 => {
320                        crate::ops::gather_scatter::scatter_min::<u8>(tensor, dim, indices, value)
321                    }
322                    dt => panic!("int_scatter: unsupported dtype {:?}", dt),
323                }
324            }
325            burn_backend::tensor::IndexingUpdateOp::Max => {
326                debug_assert_eq!(tensor.dtype(), value.dtype(), "int_scatter: dtype mismatch");
327                match tensor.dtype() {
328                    DType::I64 => {
329                        crate::ops::gather_scatter::scatter_max::<i64>(tensor, dim, indices, value)
330                    }
331                    DType::I32 => {
332                        crate::ops::gather_scatter::scatter_max::<i32>(tensor, dim, indices, value)
333                    }
334                    DType::I16 => {
335                        crate::ops::gather_scatter::scatter_max::<i16>(tensor, dim, indices, value)
336                    }
337                    DType::I8 => {
338                        crate::ops::gather_scatter::scatter_max::<i8>(tensor, dim, indices, value)
339                    }
340                    DType::U64 => {
341                        crate::ops::gather_scatter::scatter_max::<u64>(tensor, dim, indices, value)
342                    }
343                    DType::U32 => {
344                        crate::ops::gather_scatter::scatter_max::<u32>(tensor, dim, indices, value)
345                    }
346                    DType::U16 => {
347                        crate::ops::gather_scatter::scatter_max::<u16>(tensor, dim, indices, value)
348                    }
349                    DType::U8 => {
350                        crate::ops::gather_scatter::scatter_max::<u8>(tensor, dim, indices, value)
351                    }
352                    dt => panic!("int_scatter: unsupported dtype {:?}", dt),
353                }
354            }
355        }
356    }
357
358    fn int_scatter_nd(
359        data: IntTensor<Flex>,
360        indices: IntTensor<Flex>,
361        values: IntTensor<Flex>,
362        reduction: burn_backend::tensor::IndexingUpdateOp,
363    ) -> IntTensor<Flex> {
364        match data.dtype() {
365            DType::I64 => {
366                crate::ops::gather_scatter::scatter_nd::<i64>(data, indices, values, reduction)
367            }
368            DType::I32 => {
369                crate::ops::gather_scatter::scatter_nd::<i32>(data, indices, values, reduction)
370            }
371            DType::I16 => {
372                crate::ops::gather_scatter::scatter_nd::<i16>(data, indices, values, reduction)
373            }
374            DType::I8 => {
375                crate::ops::gather_scatter::scatter_nd::<i8>(data, indices, values, reduction)
376            }
377            DType::U64 => {
378                crate::ops::gather_scatter::scatter_nd::<u64>(data, indices, values, reduction)
379            }
380            DType::U32 => {
381                crate::ops::gather_scatter::scatter_nd::<u32>(data, indices, values, reduction)
382            }
383            DType::U16 => {
384                crate::ops::gather_scatter::scatter_nd::<u16>(data, indices, values, reduction)
385            }
386            DType::U8 => {
387                crate::ops::gather_scatter::scatter_nd::<u8>(data, indices, values, reduction)
388            }
389            dt => panic!("int_scatter_nd: unsupported dtype {:?}", dt),
390        }
391    }
392
393    fn int_gather_nd(data: IntTensor<Flex>, indices: IntTensor<Flex>) -> IntTensor<Flex> {
394        match data.dtype() {
395            DType::I64 => crate::ops::gather_scatter::gather_nd::<i64>(data, indices),
396            DType::I32 => crate::ops::gather_scatter::gather_nd::<i32>(data, indices),
397            DType::I16 => crate::ops::gather_scatter::gather_nd::<i16>(data, indices),
398            DType::I8 => crate::ops::gather_scatter::gather_nd::<i8>(data, indices),
399            DType::U64 => crate::ops::gather_scatter::gather_nd::<u64>(data, indices),
400            DType::U32 => crate::ops::gather_scatter::gather_nd::<u32>(data, indices),
401            DType::U16 => crate::ops::gather_scatter::gather_nd::<u16>(data, indices),
402            DType::U8 => crate::ops::gather_scatter::gather_nd::<u8>(data, indices),
403            dt => panic!("int_gather_nd: unsupported dtype {:?}", dt),
404        }
405    }
406
407    /// Select ints along `dim` by a 1D index tensor.
408    ///
409    /// The `indices` tensor may be any supported int width. See
410    /// [`int_gather`](Self::int_gather) for the full index-width policy.
411    fn int_select(
412        tensor: IntTensor<Flex>,
413        dim: usize,
414        indices: IntTensor<Flex>,
415    ) -> IntTensor<Flex> {
416        match tensor.dtype() {
417            DType::I64 => crate::ops::gather_scatter::select::<i64>(tensor, dim, indices),
418            DType::I32 => crate::ops::gather_scatter::select::<i32>(tensor, dim, indices),
419            DType::I16 => crate::ops::gather_scatter::select::<i16>(tensor, dim, indices),
420            DType::I8 => crate::ops::gather_scatter::select::<i8>(tensor, dim, indices),
421            DType::U64 => crate::ops::gather_scatter::select::<u64>(tensor, dim, indices),
422            DType::U32 => crate::ops::gather_scatter::select::<u32>(tensor, dim, indices),
423            DType::U16 => crate::ops::gather_scatter::select::<u16>(tensor, dim, indices),
424            DType::U8 => crate::ops::gather_scatter::select::<u8>(tensor, dim, indices),
425            dt => panic!("int_select: unsupported dtype {:?}", dt),
426        }
427    }
428
429    fn int_select_assign(
430        tensor: IntTensor<Flex>,
431        dim: usize,
432        indices: IntTensor<Flex>,
433        value: IntTensor<Flex>,
434        update: burn_backend::tensor::IndexingUpdateOp,
435    ) -> IntTensor<Flex> {
436        match update {
437            burn_backend::tensor::IndexingUpdateOp::Assign => {
438                debug_assert_eq!(
439                    tensor.dtype(),
440                    value.dtype(),
441                    "int_select_assign: dtype mismatch"
442                );
443                match tensor.dtype() {
444                    DType::I64 => crate::ops::gather_scatter::select_assign::<i64>(
445                        tensor, dim, indices, value,
446                    ),
447                    DType::I32 => crate::ops::gather_scatter::select_assign::<i32>(
448                        tensor, dim, indices, value,
449                    ),
450                    DType::I16 => crate::ops::gather_scatter::select_assign::<i16>(
451                        tensor, dim, indices, value,
452                    ),
453                    DType::I8 => {
454                        crate::ops::gather_scatter::select_assign::<i8>(tensor, dim, indices, value)
455                    }
456                    DType::U64 => crate::ops::gather_scatter::select_assign::<u64>(
457                        tensor, dim, indices, value,
458                    ),
459                    DType::U32 => crate::ops::gather_scatter::select_assign::<u32>(
460                        tensor, dim, indices, value,
461                    ),
462                    DType::U16 => crate::ops::gather_scatter::select_assign::<u16>(
463                        tensor, dim, indices, value,
464                    ),
465                    DType::U8 => {
466                        crate::ops::gather_scatter::select_assign::<u8>(tensor, dim, indices, value)
467                    }
468                    dt => panic!("int_select_assign: unsupported dtype {:?}", dt),
469                }
470            }
471            burn_backend::tensor::IndexingUpdateOp::Add => {
472                debug_assert_eq!(
473                    tensor.dtype(),
474                    value.dtype(),
475                    "int_select_assign: dtype mismatch"
476                );
477                match tensor.dtype() {
478                    DType::I64 => {
479                        crate::ops::gather_scatter::select_add::<i64>(tensor, dim, indices, value)
480                    }
481                    DType::I32 => {
482                        crate::ops::gather_scatter::select_add::<i32>(tensor, dim, indices, value)
483                    }
484                    DType::I16 => {
485                        crate::ops::gather_scatter::select_add::<i16>(tensor, dim, indices, value)
486                    }
487                    DType::I8 => {
488                        crate::ops::gather_scatter::select_add::<i8>(tensor, dim, indices, value)
489                    }
490                    DType::U64 => {
491                        crate::ops::gather_scatter::select_add::<u64>(tensor, dim, indices, value)
492                    }
493                    DType::U32 => {
494                        crate::ops::gather_scatter::select_add::<u32>(tensor, dim, indices, value)
495                    }
496                    DType::U16 => {
497                        crate::ops::gather_scatter::select_add::<u16>(tensor, dim, indices, value)
498                    }
499                    DType::U8 => {
500                        crate::ops::gather_scatter::select_add::<u8>(tensor, dim, indices, value)
501                    }
502                    dt => panic!("int_select_assign: unsupported dtype {:?}", dt),
503                }
504            }
505            burn_backend::tensor::IndexingUpdateOp::Mul => {
506                debug_assert_eq!(
507                    tensor.dtype(),
508                    value.dtype(),
509                    "int_select_assign: dtype mismatch"
510                );
511                match tensor.dtype() {
512                    DType::I64 => {
513                        crate::ops::gather_scatter::select_mul::<i64>(tensor, dim, indices, value)
514                    }
515                    DType::I32 => {
516                        crate::ops::gather_scatter::select_mul::<i32>(tensor, dim, indices, value)
517                    }
518                    DType::I16 => {
519                        crate::ops::gather_scatter::select_mul::<i16>(tensor, dim, indices, value)
520                    }
521                    DType::I8 => {
522                        crate::ops::gather_scatter::select_mul::<i8>(tensor, dim, indices, value)
523                    }
524                    DType::U64 => {
525                        crate::ops::gather_scatter::select_mul::<u64>(tensor, dim, indices, value)
526                    }
527                    DType::U32 => {
528                        crate::ops::gather_scatter::select_mul::<u32>(tensor, dim, indices, value)
529                    }
530                    DType::U16 => {
531                        crate::ops::gather_scatter::select_mul::<u16>(tensor, dim, indices, value)
532                    }
533                    DType::U8 => {
534                        crate::ops::gather_scatter::select_mul::<u8>(tensor, dim, indices, value)
535                    }
536                    dt => panic!("int_select_assign: unsupported dtype {:?}", dt),
537                }
538            }
539            burn_backend::tensor::IndexingUpdateOp::Min => {
540                debug_assert_eq!(
541                    tensor.dtype(),
542                    value.dtype(),
543                    "int_select_assign: dtype mismatch"
544                );
545                match tensor.dtype() {
546                    DType::I64 => {
547                        crate::ops::gather_scatter::select_min::<i64>(tensor, dim, indices, value)
548                    }
549                    DType::I32 => {
550                        crate::ops::gather_scatter::select_min::<i32>(tensor, dim, indices, value)
551                    }
552                    DType::I16 => {
553                        crate::ops::gather_scatter::select_min::<i16>(tensor, dim, indices, value)
554                    }
555                    DType::I8 => {
556                        crate::ops::gather_scatter::select_min::<i8>(tensor, dim, indices, value)
557                    }
558                    DType::U64 => {
559                        crate::ops::gather_scatter::select_min::<u64>(tensor, dim, indices, value)
560                    }
561                    DType::U32 => {
562                        crate::ops::gather_scatter::select_min::<u32>(tensor, dim, indices, value)
563                    }
564                    DType::U16 => {
565                        crate::ops::gather_scatter::select_min::<u16>(tensor, dim, indices, value)
566                    }
567                    DType::U8 => {
568                        crate::ops::gather_scatter::select_min::<u8>(tensor, dim, indices, value)
569                    }
570                    dt => panic!("int_select_assign: unsupported dtype {:?}", dt),
571                }
572            }
573            burn_backend::tensor::IndexingUpdateOp::Max => {
574                debug_assert_eq!(
575                    tensor.dtype(),
576                    value.dtype(),
577                    "int_select_assign: dtype mismatch"
578                );
579                match tensor.dtype() {
580                    DType::I64 => {
581                        crate::ops::gather_scatter::select_max::<i64>(tensor, dim, indices, value)
582                    }
583                    DType::I32 => {
584                        crate::ops::gather_scatter::select_max::<i32>(tensor, dim, indices, value)
585                    }
586                    DType::I16 => {
587                        crate::ops::gather_scatter::select_max::<i16>(tensor, dim, indices, value)
588                    }
589                    DType::I8 => {
590                        crate::ops::gather_scatter::select_max::<i8>(tensor, dim, indices, value)
591                    }
592                    DType::U64 => {
593                        crate::ops::gather_scatter::select_max::<u64>(tensor, dim, indices, value)
594                    }
595                    DType::U32 => {
596                        crate::ops::gather_scatter::select_max::<u32>(tensor, dim, indices, value)
597                    }
598                    DType::U16 => {
599                        crate::ops::gather_scatter::select_max::<u16>(tensor, dim, indices, value)
600                    }
601                    DType::U8 => {
602                        crate::ops::gather_scatter::select_max::<u8>(tensor, dim, indices, value)
603                    }
604                    dt => panic!("int_select_assign: unsupported dtype {:?}", dt),
605                }
606            }
607        }
608    }
609
610    fn int_equal(
611        lhs: IntTensor<Flex>,
612        rhs: IntTensor<Flex>,
613        out_dtype: burn_std::BoolDType,
614    ) -> BoolTensor<Flex> {
615        crate::ops::comparison::int_equal(lhs, rhs, out_dtype)
616    }
617
618    fn int_equal_elem(
619        lhs: IntTensor<Flex>,
620        rhs: Scalar,
621        out_dtype: burn_std::BoolDType,
622    ) -> BoolTensor<Flex> {
623        let (i, u) = scalar_to_int_pair(lhs.dtype(), &rhs);
624        crate::ops::comparison::int_equal_elem(lhs, i, u, out_dtype)
625    }
626
627    fn int_greater(
628        lhs: IntTensor<Flex>,
629        rhs: IntTensor<Flex>,
630        out_dtype: burn_std::BoolDType,
631    ) -> BoolTensor<Flex> {
632        crate::ops::comparison::int_greater(lhs, rhs, out_dtype)
633    }
634
635    fn int_greater_elem(
636        lhs: IntTensor<Flex>,
637        rhs: Scalar,
638        out_dtype: burn_std::BoolDType,
639    ) -> BoolTensor<Flex> {
640        let (i, u) = scalar_to_int_pair(lhs.dtype(), &rhs);
641        crate::ops::comparison::int_greater_elem(lhs, i, u, out_dtype)
642    }
643
644    fn int_greater_equal(
645        lhs: IntTensor<Flex>,
646        rhs: IntTensor<Flex>,
647        out_dtype: burn_std::BoolDType,
648    ) -> BoolTensor<Flex> {
649        crate::ops::comparison::int_greater_equal(lhs, rhs, out_dtype)
650    }
651
652    fn int_greater_equal_elem(
653        lhs: IntTensor<Flex>,
654        rhs: Scalar,
655        out_dtype: burn_std::BoolDType,
656    ) -> BoolTensor<Flex> {
657        let (i, u) = scalar_to_int_pair(lhs.dtype(), &rhs);
658        crate::ops::comparison::int_greater_equal_elem(lhs, i, u, out_dtype)
659    }
660
661    fn int_lower(
662        lhs: IntTensor<Flex>,
663        rhs: IntTensor<Flex>,
664        out_dtype: burn_std::BoolDType,
665    ) -> BoolTensor<Flex> {
666        crate::ops::comparison::int_lower(lhs, rhs, out_dtype)
667    }
668
669    fn int_lower_elem(
670        lhs: IntTensor<Flex>,
671        rhs: Scalar,
672        out_dtype: burn_std::BoolDType,
673    ) -> BoolTensor<Flex> {
674        let (i, u) = scalar_to_int_pair(lhs.dtype(), &rhs);
675        crate::ops::comparison::int_lower_elem(lhs, i, u, out_dtype)
676    }
677
678    fn int_lower_equal(
679        lhs: IntTensor<Flex>,
680        rhs: IntTensor<Flex>,
681        out_dtype: burn_std::BoolDType,
682    ) -> BoolTensor<Flex> {
683        crate::ops::comparison::int_lower_equal(lhs, rhs, out_dtype)
684    }
685
686    fn int_lower_equal_elem(
687        lhs: IntTensor<Flex>,
688        rhs: Scalar,
689        out_dtype: burn_std::BoolDType,
690    ) -> BoolTensor<Flex> {
691        let (i, u) = scalar_to_int_pair(lhs.dtype(), &rhs);
692        crate::ops::comparison::int_lower_equal_elem(lhs, i, u, out_dtype)
693    }
694
695    fn int_add(lhs: IntTensor<Flex>, rhs: IntTensor<Flex>) -> IntTensor<Flex> {
696        int_binary_op(lhs, rhs, |a, b| a + b)
697    }
698
699    fn int_add_scalar(lhs: IntTensor<Flex>, rhs: Scalar) -> IntTensor<Flex> {
700        if lhs.dtype() == DType::U64 {
701            return scalar_op_typed(lhs, rhs.to_u64().unwrap(), |a: u64, b: u64| {
702                a.wrapping_add(b)
703            });
704        }
705        int_scalar_op(lhs, rhs.to_i64().unwrap(), |a, b| a + b)
706    }
707
708    fn int_sub(lhs: IntTensor<Flex>, rhs: IntTensor<Flex>) -> IntTensor<Flex> {
709        int_binary_op(lhs, rhs, |a, b| a - b)
710    }
711
712    fn int_sub_scalar(lhs: IntTensor<Flex>, rhs: Scalar) -> IntTensor<Flex> {
713        if lhs.dtype() == DType::U64 {
714            return scalar_op_typed(lhs, rhs.to_u64().unwrap(), |a: u64, b: u64| {
715                a.wrapping_sub(b)
716            });
717        }
718        int_scalar_op(lhs, rhs.to_i64().unwrap(), |a, b| a - b)
719    }
720
721    fn int_mul(lhs: IntTensor<Flex>, rhs: IntTensor<Flex>) -> IntTensor<Flex> {
722        int_binary_op(lhs, rhs, |a, b| a * b)
723    }
724
725    fn int_mul_scalar(lhs: IntTensor<Flex>, rhs: Scalar) -> IntTensor<Flex> {
726        if lhs.dtype() == DType::U64 {
727            return scalar_op_typed(lhs, rhs.to_u64().unwrap(), |a: u64, b: u64| {
728                a.wrapping_mul(b)
729            });
730        }
731        int_scalar_op(lhs, rhs.to_i64().unwrap(), |a, b| a * b)
732    }
733
734    fn int_div(lhs: IntTensor<Flex>, rhs: IntTensor<Flex>) -> IntTensor<Flex> {
735        // U64 values > i64::MAX produce wrong results through i64 cast
736        if lhs.dtype() == DType::U64 {
737            let (lhs, rhs) = crate::ops::expand::broadcast_binary(lhs, rhs);
738            return binary_op_typed(lhs, rhs, |a: u64, b: u64| a / b);
739        }
740        int_binary_op(lhs, rhs, |a, b| a / b)
741    }
742
743    fn int_div_scalar(lhs: IntTensor<Flex>, rhs: Scalar) -> IntTensor<Flex> {
744        if lhs.dtype() == DType::U64 {
745            return scalar_op_typed(lhs, rhs.to_u64().unwrap(), |a: u64, b: u64| a / b);
746        }
747        int_scalar_op(lhs, rhs.to_i64().unwrap(), |a, b| a / b)
748    }
749
750    fn int_remainder(lhs: IntTensor<Flex>, rhs: IntTensor<Flex>) -> IntTensor<Flex> {
751        // U64 values > i64::MAX produce wrong results through i64 cast
752        if lhs.dtype() == DType::U64 {
753            let (lhs, rhs) = crate::ops::expand::broadcast_binary(lhs, rhs);
754            return binary_op_typed(lhs, rhs, |a: u64, b: u64| a % b);
755        }
756        // Python/PyTorch-style remainder: result has same sign as divisor
757        int_binary_op(lhs, rhs, remainder_int)
758    }
759
760    fn int_remainder_scalar(lhs: IntTensor<Flex>, rhs: Scalar) -> IntTensor<Flex> {
761        if lhs.dtype() == DType::U64 {
762            return scalar_op_typed(lhs, rhs.to_u64().unwrap(), |a: u64, b: u64| a % b);
763        }
764        // Python/PyTorch-style remainder: result has same sign as divisor
765        int_scalar_op(lhs, rhs.to_i64().unwrap(), remainder_int)
766    }
767
768    // Precision limits: i64/u64 > 2^24 for f32/f16/bf16, > 2^53 for f64.
769    fn int_into_float(
770        tensor: IntTensor<Flex>,
771        out_dtype: burn_std::FloatDType,
772    ) -> FloatTensor<Flex> {
773        let tensor = tensor.to_contiguous();
774        let shape = tensor.layout().shape().clone();
775        let src = tensor.dtype();
776        let out_dt = DType::from(out_dtype);
777
778        // Read source ints, applying conversion per-element.
779        // Each arm binds `$x` to the native int value; `$conv` must work for all int types.
780        macro_rules! read_ints {
781            (|$x:ident| $conv:expr) => {
782                match src {
783                    DType::I64 => tensor.storage::<i64>().iter().map(|&$x| $conv).collect(),
784                    DType::I32 => tensor.storage::<i32>().iter().map(|&$x| $conv).collect(),
785                    DType::I16 => tensor.storage::<i16>().iter().map(|&$x| $conv).collect(),
786                    DType::I8 => tensor.storage::<i8>().iter().map(|&$x| $conv).collect(),
787                    DType::U64 => tensor.storage::<u64>().iter().map(|&$x| $conv).collect(),
788                    DType::U32 => tensor.storage::<u32>().iter().map(|&$x| $conv).collect(),
789                    DType::U16 => tensor.storage::<u16>().iter().map(|&$x| $conv).collect(),
790                    DType::U8 => tensor.storage::<u8>().iter().map(|&$x| $conv).collect(),
791                    _ => panic!("int_into_float: unsupported source dtype {:?}", src),
792                }
793            };
794        }
795
796        match out_dtype {
797            FloatDType::F64 => {
798                let data: Vec<f64> = read_ints!(|x| x as f64);
799                FlexTensor::new(Bytes::from_elems(data), Layout::contiguous(shape), out_dt)
800            }
801            FloatDType::F32 | FloatDType::Flex32 => {
802                let data: Vec<f32> = read_ints!(|x| x as f32);
803                FlexTensor::new(Bytes::from_elems(data), Layout::contiguous(shape), out_dt)
804            }
805            FloatDType::F16 => {
806                let data: Vec<f16> = read_ints!(|x| f16::from_f32(x as f32));
807                FlexTensor::new(Bytes::from_elems(data), Layout::contiguous(shape), out_dt)
808            }
809            FloatDType::BF16 => {
810                let data: Vec<bf16> = read_ints!(|x| bf16::from_f32(x as f32));
811                FlexTensor::new(Bytes::from_elems(data), Layout::contiguous(shape), out_dt)
812            }
813        }
814    }
815
816    fn int_swap_dims(tensor: IntTensor<Flex>, dim1: usize, dim2: usize) -> IntTensor<Flex> {
817        tensor.transpose(dim1, dim2)
818    }
819
820    fn int_permute(tensor: IntTensor<Flex>, axes: &[usize]) -> IntTensor<Flex> {
821        tensor.permute(axes)
822    }
823
824    fn int_flip(tensor: IntTensor<Flex>, axes: &[usize]) -> IntTensor<Flex> {
825        crate::ops::flip::flip(tensor, axes)
826    }
827
828    fn int_random(
829        shape: Shape,
830        distribution: Distribution,
831        _device: &Device<Flex>,
832        dtype: IntDType,
833    ) -> IntTensor<Flex> {
834        let mut seed = crate::backend::SEED.lock();
835        let mut rng = seed.take().unwrap_or_else(crate::backend::get_seeded_rng);
836        let data = match dtype {
837            IntDType::I64 => TensorData::random::<i64, _, _>(shape, distribution, &mut rng),
838            IntDType::I32 => TensorData::random::<i32, _, _>(shape, distribution, &mut rng),
839            IntDType::I16 => TensorData::random::<i16, _, _>(shape, distribution, &mut rng),
840            IntDType::I8 => TensorData::random::<i8, _, _>(shape, distribution, &mut rng),
841            IntDType::U64 => TensorData::random::<u64, _, _>(shape, distribution, &mut rng),
842            IntDType::U32 => TensorData::random::<u32, _, _>(shape, distribution, &mut rng),
843            IntDType::U16 => TensorData::random::<u16, _, _>(shape, distribution, &mut rng),
844            IntDType::U8 => TensorData::random::<u8, _, _>(shape, distribution, &mut rng),
845        };
846        *seed = Some(rng);
847        FlexTensor::from_data(data)
848    }
849
850    fn int_expand(tensor: IntTensor<Flex>, shape: Shape) -> IntTensor<Flex> {
851        crate::ops::expand::expand(tensor, shape)
852    }
853
854    fn int_matmul(lhs: IntTensor<Flex>, rhs: IntTensor<Flex>) -> IntTensor<Flex> {
855        matmul::int_matmul(lhs, rhs)
856    }
857
858    fn int_sum(tensor: IntTensor<Flex>) -> IntTensor<Flex> {
859        crate::ops::reduce::sum(tensor)
860    }
861
862    fn int_sum_dim(tensor: IntTensor<Flex>, dim: usize) -> IntTensor<Flex> {
863        crate::ops::reduce::sum_dim(tensor, dim)
864    }
865
866    fn int_prod(tensor: IntTensor<Flex>) -> IntTensor<Flex> {
867        crate::ops::reduce::prod(tensor)
868    }
869
870    fn int_prod_dim(tensor: IntTensor<Flex>, dim: usize) -> IntTensor<Flex> {
871        crate::ops::reduce::prod_dim(tensor, dim)
872    }
873
874    fn int_mean_dim(tensor: IntTensor<Flex>, dim: usize) -> IntTensor<Flex> {
875        crate::ops::reduce::mean_dim(tensor, dim)
876    }
877
878    fn int_cumsum(tensor: IntTensor<Flex>, dim: usize) -> IntTensor<Flex> {
879        match tensor.dtype() {
880            DType::I64 => crate::ops::cumulative::cumsum::<i64>(tensor, dim),
881            DType::I32 => crate::ops::cumulative::cumsum::<i32>(tensor, dim),
882            DType::I16 => crate::ops::cumulative::cumsum::<i16>(tensor, dim),
883            DType::I8 => crate::ops::cumulative::cumsum::<i8>(tensor, dim),
884            DType::U64 => crate::ops::cumulative::cumsum::<u64>(tensor, dim),
885            DType::U32 => crate::ops::cumulative::cumsum::<u32>(tensor, dim),
886            DType::U16 => crate::ops::cumulative::cumsum::<u16>(tensor, dim),
887            DType::U8 => crate::ops::cumulative::cumsum::<u8>(tensor, dim),
888            dt => panic!("int_cumsum: unsupported dtype {:?}", dt),
889        }
890    }
891
892    fn int_cumprod(tensor: IntTensor<Flex>, dim: usize) -> IntTensor<Flex> {
893        match tensor.dtype() {
894            DType::I64 => crate::ops::cumulative::cumprod::<i64>(tensor, dim),
895            DType::I32 => crate::ops::cumulative::cumprod::<i32>(tensor, dim),
896            DType::I16 => crate::ops::cumulative::cumprod::<i16>(tensor, dim),
897            DType::I8 => crate::ops::cumulative::cumprod::<i8>(tensor, dim),
898            DType::U64 => crate::ops::cumulative::cumprod::<u64>(tensor, dim),
899            DType::U32 => crate::ops::cumulative::cumprod::<u32>(tensor, dim),
900            DType::U16 => crate::ops::cumulative::cumprod::<u16>(tensor, dim),
901            DType::U8 => crate::ops::cumulative::cumprod::<u8>(tensor, dim),
902            dt => panic!("int_cumprod: unsupported dtype {:?}", dt),
903        }
904    }
905
906    fn int_cummin(tensor: IntTensor<Flex>, dim: usize) -> IntTensor<Flex> {
907        match tensor.dtype() {
908            DType::I64 => crate::ops::cumulative::cummin::<i64>(tensor, dim),
909            DType::I32 => crate::ops::cumulative::cummin::<i32>(tensor, dim),
910            DType::I16 => crate::ops::cumulative::cummin::<i16>(tensor, dim),
911            DType::I8 => crate::ops::cumulative::cummin::<i8>(tensor, dim),
912            DType::U64 => crate::ops::cumulative::cummin::<u64>(tensor, dim),
913            DType::U32 => crate::ops::cumulative::cummin::<u32>(tensor, dim),
914            DType::U16 => crate::ops::cumulative::cummin::<u16>(tensor, dim),
915            DType::U8 => crate::ops::cumulative::cummin::<u8>(tensor, dim),
916            dt => panic!("int_cummin: unsupported dtype {:?}", dt),
917        }
918    }
919
920    fn int_cummax(tensor: IntTensor<Flex>, dim: usize) -> IntTensor<Flex> {
921        match tensor.dtype() {
922            DType::I64 => crate::ops::cumulative::cummax::<i64>(tensor, dim),
923            DType::I32 => crate::ops::cumulative::cummax::<i32>(tensor, dim),
924            DType::I16 => crate::ops::cumulative::cummax::<i16>(tensor, dim),
925            DType::I8 => crate::ops::cumulative::cummax::<i8>(tensor, dim),
926            DType::U64 => crate::ops::cumulative::cummax::<u64>(tensor, dim),
927            DType::U32 => crate::ops::cumulative::cummax::<u32>(tensor, dim),
928            DType::U16 => crate::ops::cumulative::cummax::<u16>(tensor, dim),
929            DType::U8 => crate::ops::cumulative::cummax::<u8>(tensor, dim),
930            dt => panic!("int_cummax: unsupported dtype {:?}", dt),
931        }
932    }
933
934    fn int_argmax(tensor: IntTensor<Flex>, dim: usize) -> IntTensor<Flex> {
935        crate::ops::reduce::argmax(tensor, dim)
936    }
937
938    fn int_argmin(tensor: IntTensor<Flex>, dim: usize) -> IntTensor<Flex> {
939        crate::ops::reduce::argmin(tensor, dim)
940    }
941
942    fn int_abs(tensor: IntTensor<Flex>) -> IntTensor<Flex> {
943        crate::ops::unary::int_abs(tensor)
944    }
945
946    fn bitwise_and(lhs: IntTensor<Flex>, rhs: IntTensor<Flex>) -> IntTensor<Flex> {
947        int_binary_op(lhs, rhs, |a, b| a & b)
948    }
949
950    fn bitwise_and_scalar(lhs: IntTensor<Flex>, rhs: Scalar) -> IntTensor<Flex> {
951        if lhs.dtype() == DType::U64 {
952            return scalar_op_typed(lhs, rhs.to_u64().unwrap(), |a: u64, b: u64| a & b);
953        }
954        int_scalar_op(lhs, rhs.to_i64().unwrap(), |a, b| a & b)
955    }
956
957    fn bitwise_or(lhs: IntTensor<Flex>, rhs: IntTensor<Flex>) -> IntTensor<Flex> {
958        int_binary_op(lhs, rhs, |a, b| a | b)
959    }
960
961    fn bitwise_or_scalar(lhs: IntTensor<Flex>, rhs: Scalar) -> IntTensor<Flex> {
962        if lhs.dtype() == DType::U64 {
963            return scalar_op_typed(lhs, rhs.to_u64().unwrap(), |a: u64, b: u64| a | b);
964        }
965        int_scalar_op(lhs, rhs.to_i64().unwrap(), |a, b| a | b)
966    }
967
968    fn bitwise_xor(lhs: IntTensor<Flex>, rhs: IntTensor<Flex>) -> IntTensor<Flex> {
969        int_binary_op(lhs, rhs, |a, b| a ^ b)
970    }
971
972    fn bitwise_xor_scalar(lhs: IntTensor<Flex>, rhs: Scalar) -> IntTensor<Flex> {
973        if lhs.dtype() == DType::U64 {
974            return scalar_op_typed(lhs, rhs.to_u64().unwrap(), |a: u64, b: u64| a ^ b);
975        }
976        int_scalar_op(lhs, rhs.to_i64().unwrap(), |a, b| a ^ b)
977    }
978
979    fn bitwise_not(tensor: IntTensor<Flex>) -> IntTensor<Flex> {
980        // Use scalar op with dummy value, only applying NOT to lhs
981        int_scalar_op(tensor, 0, |a, _| !a)
982    }
983
984    fn bitwise_left_shift(lhs: IntTensor<Flex>, rhs: IntTensor<Flex>) -> IntTensor<Flex> {
985        int_shift::<true>(lhs, ShiftAmount::Tensor(rhs))
986    }
987
988    fn bitwise_left_shift_scalar(lhs: IntTensor<Flex>, rhs: Scalar) -> IntTensor<Flex> {
989        int_shift::<true>(lhs, scalar_shift_amount(&rhs))
990    }
991
992    fn bitwise_right_shift(lhs: IntTensor<Flex>, rhs: IntTensor<Flex>) -> IntTensor<Flex> {
993        int_shift::<false>(lhs, ShiftAmount::Tensor(rhs))
994    }
995
996    fn bitwise_right_shift_scalar(lhs: IntTensor<Flex>, rhs: Scalar) -> IntTensor<Flex> {
997        int_shift::<false>(lhs, scalar_shift_amount(&rhs))
998    }
999
1000    fn int_cast(tensor: IntTensor<Flex>, dtype: IntDType) -> IntTensor<Flex> {
1001        let target_dtype: DType = dtype.into();
1002
1003        // If already the target dtype, return as-is
1004        if tensor.dtype() == target_dtype {
1005            return tensor;
1006        }
1007
1008        // Make contiguous for easier iteration
1009        let tensor = tensor.to_contiguous();
1010        let shape = tensor.layout().shape().clone();
1011
1012        // Helper macro to convert between types
1013        macro_rules! cast_impl {
1014            ($storage:ident, $dst_type:ty) => {{
1015                Some(Bytes::from_elems(
1016                    $storage
1017                        .iter()
1018                        .map(|&x| x as $dst_type)
1019                        .collect::<Vec<$dst_type>>(),
1020                ))
1021            }};
1022        }
1023
1024        // Match source dtype to target dtype
1025        let bytes = match tensor.dtype() {
1026            // From I64
1027            DType::I64 => {
1028                let storage: &[i64] = tensor.storage();
1029                match target_dtype {
1030                    DType::I32 => cast_impl!(storage, i32),
1031                    DType::I16 => cast_impl!(storage, i16),
1032                    DType::I8 => cast_impl!(storage, i8),
1033                    DType::U64 => cast_impl!(storage, u64),
1034                    DType::U32 => cast_impl!(storage, u32),
1035                    DType::U16 => cast_impl!(storage, u16),
1036                    DType::U8 => cast_impl!(storage, u8),
1037                    _ => None,
1038                }
1039            }
1040
1041            // From I32
1042            DType::I32 => {
1043                let storage: &[i32] = tensor.storage();
1044                match target_dtype {
1045                    DType::I64 => cast_impl!(storage, i64),
1046                    DType::I16 => cast_impl!(storage, i16),
1047                    DType::I8 => cast_impl!(storage, i8),
1048                    DType::U64 => cast_impl!(storage, u64),
1049                    DType::U32 => cast_impl!(storage, u32),
1050                    DType::U16 => cast_impl!(storage, u16),
1051                    DType::U8 => cast_impl!(storage, u8),
1052                    _ => None,
1053                }
1054            }
1055
1056            // From I16
1057            DType::I16 => {
1058                let storage: &[i16] = tensor.storage();
1059                match target_dtype {
1060                    DType::I64 => cast_impl!(storage, i64),
1061                    DType::I32 => cast_impl!(storage, i32),
1062                    DType::I8 => cast_impl!(storage, i8),
1063                    DType::U64 => cast_impl!(storage, u64),
1064                    DType::U32 => cast_impl!(storage, u32),
1065                    DType::U16 => cast_impl!(storage, u16),
1066                    DType::U8 => cast_impl!(storage, u8),
1067                    _ => None,
1068                }
1069            }
1070
1071            // From I8
1072            DType::I8 => {
1073                let storage: &[i8] = tensor.storage();
1074                match target_dtype {
1075                    DType::I64 => cast_impl!(storage, i64),
1076                    DType::I32 => cast_impl!(storage, i32),
1077                    DType::I16 => cast_impl!(storage, i16),
1078                    DType::U64 => cast_impl!(storage, u64),
1079                    DType::U32 => cast_impl!(storage, u32),
1080                    DType::U16 => cast_impl!(storage, u16),
1081                    DType::U8 => cast_impl!(storage, u8),
1082                    _ => None,
1083                }
1084            }
1085
1086            // From U64
1087            DType::U64 => {
1088                let storage: &[u64] = tensor.storage();
1089                match target_dtype {
1090                    DType::I64 => cast_impl!(storage, i64),
1091                    DType::I32 => cast_impl!(storage, i32),
1092                    DType::I16 => cast_impl!(storage, i16),
1093                    DType::I8 => cast_impl!(storage, i8),
1094                    DType::U32 => cast_impl!(storage, u32),
1095                    DType::U16 => cast_impl!(storage, u16),
1096                    DType::U8 => cast_impl!(storage, u8),
1097                    _ => None,
1098                }
1099            }
1100
1101            // From U32
1102            DType::U32 => {
1103                let storage: &[u32] = tensor.storage();
1104                match target_dtype {
1105                    DType::I64 => cast_impl!(storage, i64),
1106                    DType::I32 => cast_impl!(storage, i32),
1107                    DType::I16 => cast_impl!(storage, i16),
1108                    DType::I8 => cast_impl!(storage, i8),
1109                    DType::U64 => cast_impl!(storage, u64),
1110                    DType::U16 => cast_impl!(storage, u16),
1111                    DType::U8 => cast_impl!(storage, u8),
1112                    _ => None,
1113                }
1114            }
1115
1116            // From U16
1117            DType::U16 => {
1118                let storage: &[u16] = tensor.storage();
1119                match target_dtype {
1120                    DType::I64 => cast_impl!(storage, i64),
1121                    DType::I32 => cast_impl!(storage, i32),
1122                    DType::I16 => cast_impl!(storage, i16),
1123                    DType::I8 => cast_impl!(storage, i8),
1124                    DType::U64 => cast_impl!(storage, u64),
1125                    DType::U32 => cast_impl!(storage, u32),
1126                    DType::U8 => cast_impl!(storage, u8),
1127                    _ => None,
1128                }
1129            }
1130
1131            // From U8
1132            DType::U8 => {
1133                let storage: &[u8] = tensor.storage();
1134                match target_dtype {
1135                    DType::I64 => cast_impl!(storage, i64),
1136                    DType::I32 => cast_impl!(storage, i32),
1137                    DType::I16 => cast_impl!(storage, i16),
1138                    DType::I8 => cast_impl!(storage, i8),
1139                    DType::U64 => cast_impl!(storage, u64),
1140                    DType::U32 => cast_impl!(storage, u32),
1141                    DType::U16 => cast_impl!(storage, u16),
1142                    _ => None,
1143                }
1144            }
1145
1146            _ => None,
1147        };
1148        let Some(bytes) = bytes else {
1149            panic!(
1150                "int_cast: unsupported conversion from {:?} to {:?}",
1151                tensor.dtype(),
1152                target_dtype
1153            )
1154        };
1155        FlexTensor::new(bytes, Layout::contiguous(shape), target_dtype)
1156    }
1157
1158    fn int_unfold(
1159        tensor: IntTensor<Flex>,
1160        dim: usize,
1161        size: usize,
1162        step: usize,
1163    ) -> IntTensor<Flex> {
1164        crate::ops::unfold::unfold_int(tensor, dim, size, step)
1165    }
1166
1167    fn int_neg(tensor: IntTensor<Flex>) -> IntTensor<Flex> {
1168        int_scalar_op(tensor, 0i64, |a, _| a.wrapping_neg())
1169    }
1170
1171    fn int_clamp(tensor: IntTensor<Flex>, min: Scalar, max: Scalar) -> IntTensor<Flex> {
1172        if tensor.dtype() == DType::U64 {
1173            let min_val = min.to_u64().unwrap();
1174            let max_val = max.to_u64().unwrap();
1175            return scalar_op_typed(tensor, 0u64, move |x: u64, _| x.clamp(min_val, max_val));
1176        }
1177        let min_val = min.to_i64().unwrap();
1178        let max_val = max.to_i64().unwrap();
1179        int_scalar_op(tensor, 0i64, move |x, _| x.clamp(min_val, max_val))
1180    }
1181
1182    fn int_clamp_min(tensor: IntTensor<Flex>, min: Scalar) -> IntTensor<Flex> {
1183        if tensor.dtype() == DType::U64 {
1184            let min_val = min.to_u64().unwrap();
1185            return scalar_op_typed(tensor, 0u64, move |x: u64, _| x.max(min_val));
1186        }
1187        let min_val = min.to_i64().unwrap();
1188        int_scalar_op(tensor, 0i64, move |x, _| x.max(min_val))
1189    }
1190
1191    fn int_clamp_max(tensor: IntTensor<Flex>, max: Scalar) -> IntTensor<Flex> {
1192        if tensor.dtype() == DType::U64 {
1193            let max_val = max.to_u64().unwrap();
1194            return scalar_op_typed(tensor, 0u64, move |x: u64, _| x.min(max_val));
1195        }
1196        let max_val = max.to_i64().unwrap();
1197        int_scalar_op(tensor, 0i64, move |x, _| x.min(max_val))
1198    }
1199
1200    fn int_sign(tensor: IntTensor<Flex>) -> IntTensor<Flex> {
1201        if tensor.dtype() == DType::U64 {
1202            return scalar_op_typed(tensor, 0u64, |x: u64, _| if x > 0 { 1 } else { 0 });
1203        }
1204        int_scalar_op(tensor, 0i64, |x, _| {
1205            if x > 0 {
1206                1
1207            } else if x < 0 {
1208                -1
1209            } else {
1210                0
1211            }
1212        })
1213    }
1214
1215    fn int_mean(tensor: IntTensor<Flex>) -> IntTensor<Flex> {
1216        let n = tensor.layout().num_elements();
1217        assert!(n > 0, "int_mean: cannot take mean of empty tensor");
1218        let dtype = tensor.dtype();
1219        let sum_result = crate::ops::reduce::sum(tensor);
1220        // Compute in i64 to avoid truncation of n for small int types
1221        macro_rules! compute_mean {
1222            ($ty:ty) => {{
1223                let data: &[$ty] = sum_result.storage();
1224                let mean_val = (data[0] as i64 / n as i64) as $ty;
1225                FlexTensor::new(
1226                    Bytes::from_elems(alloc::vec![mean_val]),
1227                    Layout::contiguous(Shape::from(alloc::vec![1])),
1228                    dtype,
1229                )
1230            }};
1231        }
1232        match dtype {
1233            DType::I64 => compute_mean!(i64),
1234            DType::I32 => compute_mean!(i32),
1235            DType::I16 => compute_mean!(i16),
1236            DType::I8 => compute_mean!(i8),
1237            other => panic!("int_mean: unsupported dtype {:?}", other),
1238        }
1239    }
1240
1241    fn int_max(tensor: IntTensor<Flex>) -> IntTensor<Flex> {
1242        crate::ops::reduce::max(tensor)
1243    }
1244
1245    fn int_max_dim(tensor: IntTensor<Flex>, dim: usize) -> IntTensor<Flex> {
1246        crate::ops::reduce::max_dim(tensor, dim)
1247    }
1248
1249    fn int_min(tensor: IntTensor<Flex>) -> IntTensor<Flex> {
1250        crate::ops::reduce::min(tensor)
1251    }
1252
1253    fn int_min_dim(tensor: IntTensor<Flex>, dim: usize) -> IntTensor<Flex> {
1254        crate::ops::reduce::min_dim(tensor, dim)
1255    }
1256
1257    fn int_max_dim_with_indices(
1258        tensor: IntTensor<Flex>,
1259        dim: usize,
1260    ) -> (IntTensor<Flex>, IntTensor<Flex>) {
1261        crate::ops::reduce::max_dim_with_indices(tensor, dim)
1262    }
1263
1264    fn int_min_dim_with_indices(
1265        tensor: IntTensor<Flex>,
1266        dim: usize,
1267    ) -> (IntTensor<Flex>, IntTensor<Flex>) {
1268        crate::ops::reduce::min_dim_with_indices(tensor, dim)
1269    }
1270
1271    fn int_any(tensor: IntTensor<Flex>, out_dtype: burn_std::BoolDType) -> BoolTensor<Flex> {
1272        crate::ops::comparison::any_int(tensor, out_dtype)
1273    }
1274
1275    fn int_any_dim(
1276        tensor: IntTensor<Flex>,
1277        dim: usize,
1278        out_dtype: burn_std::BoolDType,
1279    ) -> BoolTensor<Flex> {
1280        crate::ops::comparison::any_int_dim(tensor, dim, out_dtype)
1281    }
1282
1283    fn int_all(tensor: IntTensor<Flex>, out_dtype: burn_std::BoolDType) -> BoolTensor<Flex> {
1284        crate::ops::comparison::all_int(tensor, out_dtype)
1285    }
1286
1287    fn int_all_dim(
1288        tensor: IntTensor<Flex>,
1289        dim: usize,
1290        out_dtype: burn_std::BoolDType,
1291    ) -> BoolTensor<Flex> {
1292        crate::ops::comparison::all_int_dim(tensor, dim, out_dtype)
1293    }
1294
1295    fn int_powi(lhs: IntTensor<Flex>, rhs: IntTensor<Flex>) -> IntTensor<Flex> {
1296        int_binary_op(lhs, rhs, |a, b| a.wrapping_pow(b as u32))
1297    }
1298
1299    fn int_zeros(shape: Shape, _device: &Device<Flex>, dtype: IntDType) -> IntTensor<Flex> {
1300        FlexTensor::zeros(shape, dtype.into())
1301    }
1302
1303    fn int_ones(shape: Shape, _device: &Device<Flex>, dtype: IntDType) -> IntTensor<Flex> {
1304        let dt: DType = dtype.into();
1305        match dt {
1306            DType::I64 => FlexTensor::filled_typed(shape, dt, 1i64),
1307            DType::I32 => FlexTensor::filled_typed(shape, dt, 1i32),
1308            DType::I16 => FlexTensor::filled_typed(shape, dt, 1i16),
1309            DType::I8 => FlexTensor::filled_typed(shape, dt, 1i8),
1310            DType::U64 => FlexTensor::filled_typed(shape, dt, 1u64),
1311            DType::U32 => FlexTensor::filled_typed(shape, dt, 1u32),
1312            DType::U16 => FlexTensor::filled_typed(shape, dt, 1u16),
1313            DType::U8 => FlexTensor::filled_typed(shape, dt, 1u8),
1314            _ => unreachable!(),
1315        }
1316    }
1317
1318    fn int_full(
1319        shape: Shape,
1320        fill_value: burn_backend::Scalar,
1321        _device: &Device<Flex>,
1322        dtype: IntDType,
1323    ) -> IntTensor<Flex> {
1324        let dt: DType = dtype.into();
1325        let v = fill_value.to_i64().unwrap();
1326        match dt {
1327            DType::I64 => FlexTensor::filled_typed(shape, dt, v),
1328            DType::I32 => FlexTensor::filled_typed(shape, dt, v as i32),
1329            DType::I16 => FlexTensor::filled_typed(shape, dt, v as i16),
1330            DType::I8 => FlexTensor::filled_typed(shape, dt, v as i8),
1331            DType::U64 => FlexTensor::filled_typed(shape, dt, v as u64),
1332            DType::U32 => FlexTensor::filled_typed(shape, dt, v as u32),
1333            DType::U16 => FlexTensor::filled_typed(shape, dt, v as u16),
1334            DType::U8 => FlexTensor::filled_typed(shape, dt, v as u8),
1335            _ => unreachable!(),
1336        }
1337    }
1338
1339    fn int_transpose(tensor: IntTensor<Flex>) -> IntTensor<Flex> {
1340        let ndims = tensor.layout().num_dims();
1341        if ndims < 2 {
1342            return tensor;
1343        }
1344        tensor.transpose(ndims - 2, ndims - 1)
1345    }
1346
1347    fn int_repeat_dim(tensor: IntTensor<Flex>, dim: usize, times: usize) -> IntTensor<Flex> {
1348        crate::ops::repeat_dim::repeat_dim(tensor, dim, times)
1349    }
1350
1351    fn int_not_equal(
1352        lhs: IntTensor<Flex>,
1353        rhs: IntTensor<Flex>,
1354        out_dtype: burn_std::BoolDType,
1355    ) -> BoolTensor<Flex> {
1356        crate::ops::comparison::int_not_equal(lhs, rhs, out_dtype)
1357    }
1358
1359    fn int_not_equal_elem(
1360        lhs: IntTensor<Flex>,
1361        rhs: burn_backend::Scalar,
1362        out_dtype: burn_std::BoolDType,
1363    ) -> BoolTensor<Flex> {
1364        let (i, u) = scalar_to_int_pair(lhs.dtype(), &rhs);
1365        crate::ops::comparison::int_not_equal_elem(lhs, i, u, out_dtype)
1366    }
1367
1368    fn int_sort(tensor: IntTensor<Flex>, dim: usize, descending: bool) -> IntTensor<Flex> {
1369        crate::ops::sort::sort(tensor, dim, descending)
1370    }
1371
1372    fn int_sort_with_indices(
1373        tensor: IntTensor<Flex>,
1374        dim: usize,
1375        descending: bool,
1376    ) -> (IntTensor<Flex>, IntTensor<Flex>) {
1377        crate::ops::sort::sort_with_indices(tensor, dim, descending)
1378    }
1379
1380    fn int_argsort(tensor: IntTensor<Flex>, dim: usize, descending: bool) -> IntTensor<Flex> {
1381        crate::ops::sort::argsort(tensor, dim, descending)
1382    }
1383
1384    fn int_powi_scalar(lhs: IntTensor<Flex>, rhs: burn_backend::Scalar) -> IntTensor<Flex> {
1385        use num_traits::ToPrimitive;
1386        match rhs.to_i64().unwrap() {
1387            0 => Self::int_ones(lhs.shape(), &Default::default(), lhs.dtype().into()),
1388            1 => lhs,
1389            2 => Self::int_mul(lhs.clone(), lhs),
1390            _ => Self::int_powi_scalar_impl(lhs, rhs),
1391        }
1392    }
1393
1394    fn int_powi_scalar_impl(lhs: IntTensor<Flex>, rhs: burn_backend::Scalar) -> IntTensor<Flex> {
1395        use num_traits::ToPrimitive;
1396        let exp = rhs.to_i64().unwrap() as u32;
1397        if lhs.dtype() == DType::U64 {
1398            return scalar_op_typed(lhs, exp as u64, move |x: u64, _| x.wrapping_pow(exp));
1399        }
1400        int_scalar_op(lhs, exp as i64, move |x, _| x.wrapping_pow(exp))
1401    }
1402
1403    fn int_max_abs(tensor: IntTensor<Flex>) -> IntTensor<Flex> {
1404        let abs = Self::int_abs(tensor);
1405        crate::ops::reduce::max(abs)
1406    }
1407
1408    fn int_max_abs_dim(tensor: IntTensor<Flex>, dim: usize) -> IntTensor<Flex> {
1409        let abs = Self::int_abs(tensor);
1410        crate::ops::reduce::max_dim(abs, dim)
1411    }
1412
1413    fn int_arange(
1414        range: core::ops::Range<i64>,
1415        _device: &Device<Flex>,
1416        dtype: IntDType,
1417    ) -> IntTensor<Flex> {
1418        Self::int_arange_step(range, 1, &Default::default(), dtype)
1419    }
1420
1421    fn int_arange_step(
1422        range: core::ops::Range<i64>,
1423        step: usize,
1424        _device: &Device<Flex>,
1425        dtype: IntDType,
1426    ) -> IntTensor<Flex> {
1427        let dt: DType = dtype.into();
1428
1429        macro_rules! arange_typed {
1430            ($ty:ty) => {{
1431                let data: Vec<$ty> = range.step_by(step).map(|v| v as $ty).collect();
1432                let shape = Shape::from(alloc::vec![data.len()]);
1433                FlexTensor::new(Bytes::from_elems(data), Layout::contiguous(shape), dt)
1434            }};
1435        }
1436
1437        match dt {
1438            DType::I64 => arange_typed!(i64),
1439            DType::I32 => arange_typed!(i32),
1440            DType::I16 => arange_typed!(i16),
1441            DType::I8 => arange_typed!(i8),
1442            DType::U64 => arange_typed!(u64),
1443            DType::U32 => arange_typed!(u32),
1444            DType::U16 => arange_typed!(u16),
1445            DType::U8 => arange_typed!(u8),
1446            _ => unreachable!(),
1447        }
1448    }
1449}
1450
1451// Tests kept here exercise flex-specific behavior: dtype storage
1452// selection for every int width (I16/I32/U8/U16/U32/I64/U64), and edge
1453// cases of the dtype-specific kernels (u64 wrap, i64::MIN abs/neg, bit
1454// shift at width). Plain int arithmetic, scalar ops, bool->int cast
1455// smokes, and negative-stride (flipped/transposed) variants have been
1456// migrated to burn-backend-tests so they run against every backend.
1457// When adding new tests, keep them here only if they probe flex dtype
1458// storage; otherwise add them to
1459// crates/burn-backend-tests/tests/tensor/int/ops/.
1460#[cfg(test)]
1461mod tests {
1462    use alloc::vec;
1463    use burn_backend::TensorData;
1464    use burn_backend::ops::IntTensorOps;
1465
1466    use crate::Flex;
1467    use crate::FlexTensor;
1468
1469    #[test]
1470    fn test_i64_remainder_overflow() {
1471        // The shared suite uses i32, which Flex promotes to i64. Keep the
1472        // actual i64 limits covered for both tensor and scalar dispatch.
1473        for (a, b, expected) in [(i64::MAX - 1, i64::MAX, i64::MAX - 1), (i64::MIN, -1, 0)] {
1474            let lhs = FlexTensor::from_data(TensorData::new(vec![a], [1]));
1475            let rhs = FlexTensor::from_data(TensorData::new(vec![b], [1]));
1476            for result in [
1477                Flex::int_remainder(lhs.clone(), rhs),
1478                Flex::int_remainder_scalar(lhs, b.into()),
1479            ] {
1480                let values: Vec<i64> = result.into_data().try_into_vec().unwrap();
1481                assert_eq!(values, vec![expected]);
1482            }
1483        }
1484    }
1485
1486    #[test]
1487    fn test_u64_div_large_values() {
1488        let a = FlexTensor::from_data(TensorData::new(vec![u64::MAX], [1]));
1489        let b = FlexTensor::from_data(TensorData::new(vec![2u64], [1]));
1490        let result = Flex::int_div(a, b);
1491        let values: Vec<u64> = bytemuck::cast_slice(result.into_data().as_bytes()).to_vec();
1492        assert_eq!(values[0], u64::MAX / 2);
1493    }
1494
1495    #[test]
1496    fn test_u64_remainder_large_values() {
1497        let a = FlexTensor::from_data(TensorData::new(vec![u64::MAX], [1]));
1498        let b = FlexTensor::from_data(TensorData::new(vec![2u64], [1]));
1499        let result = Flex::int_remainder(a, b);
1500        let values: Vec<u64> = bytemuck::cast_slice(result.into_data().as_bytes()).to_vec();
1501        assert_eq!(values[0], u64::MAX % 2);
1502    }
1503
1504    #[test]
1505    fn test_int_abs_min_value() {
1506        // i64::MIN.abs() panics in debug; wrapping_abs returns MIN (matches PyTorch)
1507        let a = FlexTensor::from_data(TensorData::new(vec![i64::MIN], [1]));
1508        let result = Flex::int_abs(a);
1509        let values: Vec<i64> = bytemuck::cast_slice(result.into_data().as_bytes()).to_vec();
1510        assert_eq!(values[0], i64::MIN.wrapping_abs());
1511    }
1512
1513    #[test]
1514    fn test_int_neg_min_value() {
1515        // i64::MIN negation panics in debug; wrapping_neg returns MIN (matches PyTorch)
1516        let a = FlexTensor::from_data(TensorData::new(vec![i64::MIN], [1]));
1517        let result = Flex::int_neg(a);
1518        let values: Vec<i64> = bytemuck::cast_slice(result.into_data().as_bytes()).to_vec();
1519        assert_eq!(values[0], i64::MIN.wrapping_neg());
1520    }
1521
1522    #[test]
1523    fn test_int_shift_large_amount() {
1524        // Shift by >= bit width panics without wrapping; should not crash
1525        let a = FlexTensor::from_data(TensorData::new(vec![1i64], [1]));
1526        let b = FlexTensor::from_data(TensorData::new(vec![64i64], [1]));
1527        let _left = Flex::bitwise_left_shift(a.clone(), b.clone());
1528        let _right = Flex::bitwise_right_shift(a, b);
1529    }
1530
1531    #[test]
1532    fn test_int_shift_masks_to_operand_width() {
1533        // wrapping_shl/wrapping_shr mask the shift amount to the operand's own
1534        // bit width, so shifting by BITS + 1 behaves like shifting by 1.
1535        macro_rules! check {
1536            ($t:ty) => {{
1537                let amount = <$t>::BITS as $t + 1;
1538                let a = FlexTensor::from_data(TensorData::new(vec![4 as $t], [1]));
1539                let b = FlexTensor::from_data(TensorData::new(vec![amount], [1]));
1540                for (result, expected) in [
1541                    (Flex::bitwise_left_shift(a.clone(), b.clone()), 8 as $t),
1542                    (Flex::bitwise_right_shift(a.clone(), b), 2),
1543                    (
1544                        Flex::bitwise_left_shift_scalar(a.clone(), (amount as i64).into()),
1545                        8,
1546                    ),
1547                    (
1548                        Flex::bitwise_right_shift_scalar(a, (amount as i64).into()),
1549                        2,
1550                    ),
1551                ] {
1552                    let data: Vec<$t> = result.into_data().try_into_vec().unwrap();
1553                    assert_eq!(data, vec![expected], "{}", stringify!($t));
1554                }
1555            }};
1556        }
1557        check!(i8);
1558        check!(i16);
1559        check!(i32);
1560        check!(i64);
1561        check!(u8);
1562        check!(u16);
1563        check!(u32);
1564        check!(u64);
1565    }
1566
1567    #[test]
1568    fn test_int_shift_negative_amount() {
1569        // -1 as u32 is masked to BITS - 1, the same on tensor and scalar paths.
1570        let a = FlexTensor::from_data(TensorData::new(vec![1i32], [1]));
1571        let min = FlexTensor::from_data(TensorData::new(vec![i32::MIN], [1]));
1572        let b = FlexTensor::from_data(TensorData::new(vec![-1i32], [1]));
1573        for (result, expected) in [
1574            (Flex::bitwise_left_shift(a.clone(), b.clone()), i32::MIN),
1575            (Flex::bitwise_left_shift_scalar(a, (-1i64).into()), i32::MIN),
1576            (Flex::bitwise_right_shift(min.clone(), b), -1),
1577            (Flex::bitwise_right_shift_scalar(min, (-1i64).into()), -1),
1578        ] {
1579            let data: Vec<i32> = result.into_data().try_into_vec().unwrap();
1580            assert_eq!(data, vec![expected]);
1581        }
1582    }
1583
1584    #[test]
1585    fn test_shift_scalar_accepts_any_int_amount() {
1586        // Negative and above-i64::MAX scalars are masked to the operand width
1587        // instead of failing to_u64/to_i64, whatever the operand's signedness.
1588        let a = FlexTensor::from_data(TensorData::new(vec![1u64], [1]));
1589        let result = Flex::bitwise_left_shift_scalar(a.clone(), u64::MAX.into());
1590        let data: Vec<u64> = result.into_data().try_into_vec().unwrap();
1591        assert_eq!(data, vec![1u64 << 63]);
1592        let result = Flex::bitwise_left_shift_scalar(a, (-1i64).into());
1593        let data: Vec<u64> = result.into_data().try_into_vec().unwrap();
1594        assert_eq!(data, vec![1u64 << 63]);
1595
1596        let a = FlexTensor::from_data(TensorData::new(vec![1u8], [1]));
1597        let result = Flex::bitwise_left_shift_scalar(a, u64::MAX.into());
1598        let data: Vec<u8> = result.into_data().try_into_vec().unwrap();
1599        assert_eq!(data, vec![1u8 << 7]);
1600    }
1601
1602    #[test]
1603    fn test_u64_right_shift_is_logical() {
1604        // Values above i64::MAX have the top bit set; the right shift must still fill with zeros.
1605        let values = vec![u64::MAX, 1u64 << 63, 12];
1606        let a = FlexTensor::from_data(TensorData::new(values.clone(), [3]));
1607        let b = FlexTensor::from_data(TensorData::new(vec![1u64, 63, 2], [3]));
1608        let result = Flex::bitwise_right_shift(a.clone(), b);
1609        let data: Vec<u64> = result.into_data().try_into_vec().unwrap();
1610        assert_eq!(data, vec![u64::MAX >> 1, 1, 3]);
1611
1612        let result = Flex::bitwise_right_shift_scalar(a, burn_backend::Scalar::from(1i64));
1613        let data: Vec<u64> = result.into_data().try_into_vec().unwrap();
1614        assert_eq!(data, values.iter().map(|v| v >> 1).collect::<Vec<_>>());
1615    }
1616
1617    #[test]
1618    fn test_u64_right_shift_broadcast() {
1619        let a = FlexTensor::from_data(TensorData::new(vec![u64::MAX, 1u64 << 63], [2, 1]));
1620        let b = FlexTensor::from_data(TensorData::new(vec![1u64, 4], [1, 2]));
1621        let result = Flex::bitwise_right_shift(a, b);
1622        assert_eq!(result.layout().shape().to_vec(), vec![2, 2]);
1623        let data: Vec<u64> = result.into_data().try_into_vec().unwrap();
1624        assert_eq!(
1625            data,
1626            vec![u64::MAX >> 1, u64::MAX >> 4, 1u64 << 62, 1u64 << 59]
1627        );
1628    }
1629
1630    #[test]
1631    fn test_int_into_float_f64() {
1632        use burn_backend::ops::IntTensorOps;
1633        use burn_std::FloatDType;
1634
1635        let t = FlexTensor::from_data(TensorData::new(vec![1i64, 2, -3], [3]));
1636        let result = Flex::int_into_float(t, FloatDType::F64);
1637        assert_eq!(result.dtype(), burn_backend::DType::F64);
1638        let data: Vec<f64> = result.into_data().try_into_vec().unwrap();
1639        assert_eq!(data, vec![1.0f64, 2.0, -3.0]);
1640    }
1641
1642    #[test]
1643    fn test_u64_add_scalar_large() {
1644        let t = FlexTensor::from_data(TensorData::new(vec![1u64, 2, 3], [3]));
1645        let big: u64 = (i64::MAX as u64) + 100;
1646        let result = Flex::int_add_scalar(t, burn_backend::Scalar::from(big));
1647        let data: Vec<u64> = result.into_data().try_into_vec().unwrap();
1648        assert_eq!(data, vec![big + 1, big + 2, big + 3]);
1649    }
1650
1651    #[test]
1652    fn test_u64_greater_elem_large() {
1653        let big: u64 = (i64::MAX as u64) + 100;
1654        let t = FlexTensor::from_data(TensorData::new(vec![big, big + 1, big - 1], [3]));
1655        let result = Flex::int_greater_elem(
1656            t,
1657            burn_backend::Scalar::from(big),
1658            burn_std::BoolStore::Native,
1659        );
1660        let data: Vec<bool> = result.into_data().try_into_vec().unwrap();
1661        assert_eq!(data, vec![false, true, false]);
1662    }
1663
1664    #[test]
1665    fn test_int_mask_fill_i32() {
1666        let t = FlexTensor::from_data(TensorData::new(vec![1i32, 2, 3, 4], [4]));
1667        let mask = FlexTensor::from_data(TensorData::new(vec![true, false, true, false], [4]));
1668        let result = Flex::int_mask_fill(t, mask, burn_backend::Scalar::from(0i64));
1669        let data: Vec<i32> = result.into_data().try_into_vec().unwrap();
1670        assert_eq!(data, vec![0, 2, 0, 4]);
1671    }
1672
1673    #[test]
1674    fn test_int_mask_fill_i16() {
1675        let t = FlexTensor::from_data(TensorData::new(vec![10i16, 20, 30, 40], [4]));
1676        let mask = FlexTensor::from_data(TensorData::new(vec![false, true, false, true], [4]));
1677        let result = Flex::int_mask_fill(t, mask, burn_backend::Scalar::from(-1i64));
1678        let data: Vec<i16> = result.into_data().try_into_vec().unwrap();
1679        assert_eq!(data, vec![10, -1, 30, -1]);
1680    }
1681
1682    #[test]
1683    fn test_int_mask_fill_u8() {
1684        let t = FlexTensor::from_data(TensorData::new(vec![1u8, 2, 3, 4], [4]));
1685        let mask = FlexTensor::from_data(TensorData::new(vec![true, true, false, false], [4]));
1686        let result = Flex::int_mask_fill(t, mask, burn_backend::Scalar::from(255i64));
1687        let data: Vec<u8> = result.into_data().try_into_vec().unwrap();
1688        assert_eq!(data, vec![255, 255, 3, 4]);
1689    }
1690
1691    #[test]
1692    fn test_int_mask_fill_u32() {
1693        let t = FlexTensor::from_data(TensorData::new(vec![100u32, 200, 300], [3]));
1694        let mask = FlexTensor::from_data(TensorData::new(vec![true, false, true], [3]));
1695        let result = Flex::int_mask_fill(t, mask, burn_backend::Scalar::from(0i64));
1696        let data: Vec<u32> = result.into_data().try_into_vec().unwrap();
1697        assert_eq!(data, vec![0, 200, 0]);
1698    }
1699
1700    #[test]
1701    fn test_int_mask_where_i32() {
1702        let t = FlexTensor::from_data(TensorData::new(vec![1i32, 2, 3, 4], [4]));
1703        let mask = FlexTensor::from_data(TensorData::new(vec![true, false, true, false], [4]));
1704        let v = FlexTensor::from_data(TensorData::new(vec![10i32, 20, 30, 40], [4]));
1705        let result = Flex::int_mask_where(t, mask, v);
1706        let data: Vec<i32> = result.into_data().try_into_vec().unwrap();
1707        assert_eq!(data, vec![10, 2, 30, 4]);
1708    }
1709
1710    #[test]
1711    fn test_int_mask_where_u8() {
1712        let t = FlexTensor::from_data(TensorData::new(vec![1u8, 2, 3, 4], [4]));
1713        let mask = FlexTensor::from_data(TensorData::new(vec![false, true, false, true], [4]));
1714        let v = FlexTensor::from_data(TensorData::new(vec![10u8, 20, 30, 40], [4]));
1715        let result = Flex::int_mask_where(t, mask, v);
1716        let data: Vec<u8> = result.into_data().try_into_vec().unwrap();
1717        assert_eq!(data, vec![1, 20, 3, 40]);
1718    }
1719
1720    #[test]
1721    fn test_int_gather_i32() {
1722        let t = FlexTensor::from_data(TensorData::new(vec![10i32, 20, 30, 40, 50, 60], [2, 3]));
1723        let indices = FlexTensor::from_data(TensorData::new(vec![2i64, 0, 1, 2], [2, 2]));
1724        let result = Flex::int_gather(1, t, indices);
1725        let data: Vec<i32> = result.into_data().try_into_vec().unwrap();
1726        assert_eq!(data, vec![30, 10, 50, 60]);
1727    }
1728
1729    #[test]
1730    fn test_int_select_u16() {
1731        let t = FlexTensor::from_data(TensorData::new(vec![10u16, 20, 30, 40, 50, 60], [2, 3]));
1732        let indices = FlexTensor::from_data(TensorData::new(vec![0i64, 1], [2]));
1733        let result = Flex::int_select(t, 1, indices);
1734        let data: Vec<u16> = result.into_data().try_into_vec().unwrap();
1735        assert_eq!(data, vec![10, 20, 40, 50]);
1736    }
1737
1738    #[test]
1739    fn test_int_cumsum_i32() {
1740        let t = FlexTensor::from_data(TensorData::new(vec![1i32, 2, 3, 4], [4]));
1741        let result = Flex::int_cumsum(t, 0);
1742        let data: Vec<i32> = result.into_data().try_into_vec().unwrap();
1743        assert_eq!(data, vec![1, 3, 6, 10]);
1744    }
1745
1746    #[test]
1747    fn test_int_cumprod_u8() {
1748        let t = FlexTensor::from_data(TensorData::new(vec![1u8, 2, 3, 4], [4]));
1749        let result = Flex::int_cumprod(t, 0);
1750        let data: Vec<u8> = result.into_data().try_into_vec().unwrap();
1751        assert_eq!(data, vec![1, 2, 6, 24]);
1752    }
1753
1754    #[test]
1755    fn test_int_cummin_i32() {
1756        let t = FlexTensor::from_data(TensorData::new(vec![3i32, 1, 4, 1, 5], [5]));
1757        let result = Flex::int_cummin(t, 0);
1758        let data: Vec<i32> = result.into_data().try_into_vec().unwrap();
1759        assert_eq!(data, vec![3, 1, 1, 1, 1]);
1760    }
1761
1762    #[test]
1763    fn test_int_cummax_u16() {
1764        let t = FlexTensor::from_data(TensorData::new(vec![3u16, 1, 4, 1, 5], [5]));
1765        let result = Flex::int_cummax(t, 0);
1766        let data: Vec<u16> = result.into_data().try_into_vec().unwrap();
1767        assert_eq!(data, vec![3, 3, 4, 4, 5]);
1768    }
1769
1770    #[test]
1771    fn test_int_scatter_add_i32() {
1772        let t = FlexTensor::from_data(TensorData::new(vec![0i32, 0, 0], [1, 3]));
1773        let indices = FlexTensor::from_data(TensorData::new(vec![0i64, 2, 1], [1, 3]));
1774        let values = FlexTensor::from_data(TensorData::new(vec![10i32, 20, 30], [1, 3]));
1775        let result = Flex::int_scatter(
1776            1,
1777            t,
1778            indices,
1779            values,
1780            burn_backend::tensor::IndexingUpdateOp::Add,
1781        );
1782        let data: Vec<i32> = result.into_data().try_into_vec().unwrap();
1783        assert_eq!(data, vec![10, 30, 20]);
1784    }
1785
1786    #[test]
1787    fn test_int_select_add_u8() {
1788        let t = FlexTensor::from_data(TensorData::new(vec![1u8, 2, 3], [3]));
1789        let indices = FlexTensor::from_data(TensorData::new(vec![0i64, 2], [2]));
1790        let values = FlexTensor::from_data(TensorData::new(vec![10u8, 20], [2]));
1791        let result = Flex::int_select_assign(
1792            t,
1793            0,
1794            indices,
1795            values,
1796            burn_backend::tensor::IndexingUpdateOp::Add,
1797        );
1798        let data: Vec<u8> = result.into_data().try_into_vec().unwrap();
1799        assert_eq!(data, vec![11, 2, 23]);
1800    }
1801
1802    #[test]
1803    fn test_int_random_i32() {
1804        use burn_backend::{DType, Distribution, ops::IntTensorOps};
1805        use burn_std::{IntDType, Shape};
1806
1807        let shape = Shape::from(vec![100]);
1808        let dist = Distribution::Uniform(0.0, 10.0);
1809        let device = crate::FlexDevice;
1810        let t = Flex::int_random(shape, dist, &device, IntDType::I32);
1811        assert_eq!(t.dtype(), DType::I32);
1812        let data: Vec<i32> = t.into_data().try_into_vec().unwrap();
1813        assert!(data.iter().all(|&v| (0..=10).contains(&v)));
1814    }
1815
1816    #[test]
1817    fn test_int_random_u8() {
1818        use burn_backend::{DType, Distribution, ops::IntTensorOps};
1819        use burn_std::{IntDType, Shape};
1820
1821        let shape = Shape::from(vec![50]);
1822        let dist = Distribution::Uniform(0.0, 100.0);
1823        let device = crate::FlexDevice;
1824        let t = Flex::int_random(shape, dist, &device, IntDType::U8);
1825        assert_eq!(t.dtype(), DType::U8);
1826    }
1827
1828    #[test]
1829    fn test_int_mean_i32() {
1830        use burn_backend::{DType, ops::IntTensorOps};
1831
1832        let t = FlexTensor::from_data(TensorData::new(vec![10i32, 20, 30], [3]));
1833        let result = Flex::int_mean(t);
1834        assert_eq!(result.dtype(), DType::I32);
1835        let data: Vec<i32> = result.into_data().try_into_vec().unwrap();
1836        assert_eq!(data, vec![20]); // (10 + 20 + 30) / 3 = 20
1837    }
1838}