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