Skip to main content

burn_backend/backend/ops/
tensor.rs

1use super::cat::cat_with_slice_assign;
2use super::grid_sample::float_grid_sample_2d_ref;
3use super::repeat_dim::repeat_with_slice_assign;
4use super::sort::{argsort, sort, sort_with_indices};
5use crate::ops::GridSampleOptions;
6use crate::tensor::{BoolTensor, Device, FloatTensor, IntTensor};
7use crate::{Backend, Distribution, TensorData, get_device_settings};
8use crate::{ExecutionError, Scalar, TensorMetadata};
9use alloc::vec::Vec;
10use burn_std::reader::try_read_sync;
11use burn_std::{BoolDType, FloatDType, IndexingUpdateOp, IntDType, PadMode, Shape, Slice};
12
13/// Operations on float tensors.
14pub trait FloatTensorOps<B: Backend> {
15    /// Creates a new tensor from the data structure.
16    ///
17    /// # Arguments
18    ///
19    /// * `data` - The data structure.
20    /// * `device` - The device to create the tensor on.
21    ///
22    /// # Returns
23    ///
24    /// The tensor with the given data.
25    fn float_from_data(data: TensorData, device: &Device<B>) -> FloatTensor<B>;
26
27    /// Creates a new tensor with random values.
28    ///
29    /// # Arguments
30    ///
31    /// * `shape` - The shape of the tensor.
32    /// * `distribution` - The distribution to sample from.
33    /// * `device` - The device to create the tensor on.
34    /// * `dtype` - The target data type.
35    ///
36    /// # Returns
37    ///
38    /// The tensor with the given shape and random values.
39    fn float_random(
40        shape: Shape,
41        distribution: Distribution,
42        device: &Device<B>,
43        dtype: FloatDType,
44    ) -> FloatTensor<B>;
45
46    /// Creates a new tensor with zeros.
47    ///
48    /// # Arguments
49    ///
50    /// * `shape` - The shape of the tensor.
51    /// * `device` - The device to create the tensor on.
52    /// * `dtype` - The target data type.
53    ///
54    /// # Returns
55    ///
56    /// The tensor with the given shape and zeros.
57    fn float_zeros(shape: Shape, device: &Device<B>, dtype: FloatDType) -> FloatTensor<B> {
58        Self::float_from_data(TensorData::full_dtype(shape, 0., dtype.into()), device)
59    }
60
61    /// Creates a new tensor with ones.
62    ///
63    /// # Arguments
64    ///
65    /// * `shape` - The shape of the tensor.
66    /// * `device` - The device to create the tensor on.
67    /// * `dtype` - The target data type.
68    ///
69    /// # Returns
70    ///
71    /// The tensor with the given shape and ones.
72    fn float_ones(shape: Shape, device: &Device<B>, dtype: FloatDType) -> FloatTensor<B> {
73        Self::float_from_data(TensorData::full_dtype(shape, 1., dtype.into()), device)
74    }
75
76    /// Creates a tensor filled with given value.
77    ///
78    /// # Arguments
79    ///
80    /// * `shape` - The shape of the tensor.
81    /// * `fill_value` - The value with which to fill the tensor.
82    /// * `device` - The device to create the tensor on.
83    /// * `dtype` - The target data type.
84    ///
85    /// # Returns
86    ///
87    /// The tensor filled with given value
88    fn float_full(
89        shape: Shape,
90        fill_value: Scalar,
91        device: &Device<B>,
92        dtype: FloatDType,
93    ) -> FloatTensor<B> {
94        Self::float_from_data(
95            TensorData::full_dtype(shape, fill_value, dtype.into()),
96            device,
97        )
98    }
99
100    /// Converts the tensor to a data structure.
101    ///
102    /// # Arguments
103    ///
104    /// * `tensor` - The tensor.
105    ///
106    /// # Returns
107    ///
108    /// The data structure with the tensor's data.
109    fn float_into_data(
110        tensor: FloatTensor<B>,
111    ) -> impl Future<Output = Result<TensorData, ExecutionError>> + Send;
112
113    /// Moves the tensor to the given device.
114    ///
115    /// # Arguments
116    ///
117    /// * `tensor` - The tensor.
118    /// * `device` - The device to move the tensor to.
119    ///
120    /// # Returns
121    ///
122    /// The tensor on the given device.
123    fn float_to_device(tensor: FloatTensor<B>, device: &Device<B>) -> FloatTensor<B>;
124
125    /// Converts float tensor to int tensor.
126    ///
127    /// # Arguments
128    ///
129    /// * `tensor` - The tensor.
130    /// * `out_dtype` - The output tensor dtype.
131    ///
132    /// # Returns
133    ///
134    /// The int tensor with the same data as the float tensor.
135    fn float_into_int(tensor: FloatTensor<B>, out_dtype: IntDType) -> IntTensor<B>;
136
137    /// Creates an empty tensor with the given shape.
138    ///
139    /// # Arguments
140    ///
141    /// * `shape` - The shape of the tensor.
142    /// * `device` - The device to create the tensor on.
143    /// * `dtype` - The target data type.
144    ///
145    /// # Returns
146    ///
147    /// The empty tensor with the given shape.
148    fn float_empty(shape: Shape, device: &Device<B>, dtype: FloatDType) -> FloatTensor<B>;
149
150    /// Repeat the tensor along the given dimension.
151    ///
152    /// # Arguments
153    ///
154    /// * `tensor` - The tensor.
155    /// * `dim` - The dimension to repeat.
156    /// * `times` - The number of times to repeat the dimension.
157    ///
158    /// # Returns
159    ///
160    /// The tensor with the given dimension repeated.
161    fn float_repeat_dim(tensor: FloatTensor<B>, dim: usize, times: usize) -> FloatTensor<B> {
162        let device = tensor.device();
163        repeat_with_slice_assign::<B, _, _, _>(
164            tensor,
165            dim,
166            times,
167            device,
168            |shape, device, dtype| B::float_empty(shape, device, dtype.into()),
169            B::float_slice_assign,
170        )
171    }
172
173    /// Adds two tensors together.
174    ///
175    /// # Arguments
176    ///
177    /// * `lhs` - The left-hand side tensor.
178    /// * `rhs` - The right-hand side tensor.
179    ///
180    /// # Returns
181    ///
182    /// The result of adding the two tensors together.
183    fn float_add(lhs: FloatTensor<B>, rhs: FloatTensor<B>) -> FloatTensor<B>;
184
185    /// Adds a scalar to a tensor.
186    ///
187    /// # Arguments
188    ///
189    /// * `lhs` - The left-hand side tensor.
190    /// * `rhs` - The right-hand side scalar.
191    ///
192    /// # Returns
193    ///
194    /// The result of adding the scalar to the tensor.
195    fn float_add_scalar(lhs: FloatTensor<B>, rhs: Scalar) -> FloatTensor<B>;
196
197    /// Clamps a tensor under a minimum value.
198    ///
199    /// # Arguments
200    ///
201    /// * `tensor` - The tensor to clamp.
202    /// * `min` - The minimum value.
203    ///
204    /// # Returns
205    ///
206    /// The clamped tensor.
207    fn float_clamp_min(tensor: FloatTensor<B>, min: Scalar) -> FloatTensor<B> {
208        let dtype = get_device_settings::<B>(&tensor.device()).bool_dtype;
209        let mask = Self::float_lower_elem(tensor.clone(), min, dtype);
210        B::float_mask_fill(tensor, mask, min)
211    }
212
213    /// Clamps a tensor over a maximum value.
214    ///
215    /// # Arguments
216    ///
217    /// * `tensor` - The tensor to clamp.
218    /// * `max` - The maximum value.
219    ///
220    /// # Returns
221    ///
222    /// The clamped tensor.
223    fn float_clamp_max(tensor: FloatTensor<B>, max: Scalar) -> FloatTensor<B> {
224        let dtype = get_device_settings::<B>(&tensor.device()).bool_dtype;
225        let mask = Self::float_greater_elem(tensor.clone(), max, dtype);
226        B::float_mask_fill(tensor, mask, max)
227    }
228
229    /// Clamps a tensor between a minimum and maximum value.
230    ///
231    /// # Arguments
232    ///
233    /// * `tensor` - The tensor to clamp.
234    /// * `min` - The minimum value.
235    /// * `max` - The maximum value.
236    ///
237    /// # Returns
238    ///
239    /// The clamped tensor.
240    fn float_clamp(tensor: FloatTensor<B>, min: Scalar, max: Scalar) -> FloatTensor<B> {
241        // Default implementation
242        Self::float_clamp_min(Self::float_clamp_max(tensor, max), min)
243    }
244
245    /// Subtracts two tensors.
246    ///
247    /// # Arguments
248    ///
249    /// * `lhs` - The left-hand side tensor.
250    /// * `rhs` - The right-hand side tensor.
251    ///
252    /// # Returns
253    ///
254    /// The result of subtracting the two tensors.
255    fn float_sub(lhs: FloatTensor<B>, rhs: FloatTensor<B>) -> FloatTensor<B>;
256
257    /// Subtracts a scalar from a tensor.
258    ///
259    /// # Arguments
260    ///
261    /// * `lhs` - The left-hand side tensor.
262    /// * `rhs` - The right-hand side scalar.
263    ///
264    /// # Returns
265    ///
266    /// The result of subtracting the scalar from the tensor.
267    fn float_sub_scalar(lhs: FloatTensor<B>, rhs: Scalar) -> FloatTensor<B>;
268
269    /// Multiplies two tensors together element-wise.
270    fn float_mul(lhs: FloatTensor<B>, rhs: FloatTensor<B>) -> FloatTensor<B>;
271
272    /// Multiplies a tensor by a scalar.
273    ///
274    /// # Arguments
275    ///
276    /// * `lhs` - The left-hand side tensor.
277    /// * `rhs` - The right-hand side scalar.
278    ///
279    /// # Returns
280    ///
281    /// The result of multiplying the tensor by the scalar.
282    fn float_mul_scalar(lhs: FloatTensor<B>, rhs: Scalar) -> FloatTensor<B>;
283
284    /// Divides two tensors element-wise.
285    ///
286    /// # Arguments
287    ///
288    /// * `lhs` - The left-hand side tensor.
289    /// * `rhs` - The right-hand side tensor.
290    ///
291    /// # Returns
292    ///
293    /// The result of dividing the two tensors.
294    fn float_div(lhs: FloatTensor<B>, rhs: FloatTensor<B>) -> FloatTensor<B>;
295
296    /// Divides a tensor by a scalar.
297    ///
298    /// # Arguments
299    ///
300    /// * `lhs` - The left-hand side tensor.
301    /// * `rhs` - The right-hand side scalar.
302    ///
303    /// # Returns
304    ///
305    /// The result of dividing the tensor by the scalar.
306    fn float_div_scalar(lhs: FloatTensor<B>, rhs: Scalar) -> FloatTensor<B>;
307
308    /// Computes the remainder of division between two tensors element-wise.
309    ///
310    /// # Arguments
311    ///
312    /// * `lhs` - The left-hand side tensor.
313    /// * `rhs` - The right-hand side tensor.
314    ///
315    /// # Returns
316    ///
317    /// The element-wise remainder when dividing `lhs` by `rhs`.
318    fn float_remainder(lhs: FloatTensor<B>, rhs: FloatTensor<B>) -> FloatTensor<B>;
319
320    /// Computes the modulus of a tensor given a scalar.
321    ///
322    /// # Arguments
323    /// * `lhs` - The left-hand side tensor.
324    /// * `rhs` - The right-hand side scalar.
325    ///
326    /// # Returns
327    ///
328    /// The result of applying the modulus of the scalar to the tensor.
329    fn float_remainder_scalar(lhs: FloatTensor<B>, rhs: Scalar) -> FloatTensor<B>;
330
331    /// Multiplies two tensors together using matrix multiplication.
332    ///
333    /// # Arguments
334    ///
335    /// * `lhs` - The left-hand side tensor.
336    /// * `rhs` - The right-hand side tensor.
337    ///
338    /// # Returns
339    ///
340    /// The result of multiplying the two tensors together using matrix multiplication.
341    fn float_matmul(lhs: FloatTensor<B>, rhs: FloatTensor<B>) -> FloatTensor<B>;
342
343    /// Computes the cross product of two tensors along a given dimension.
344    ///
345    /// # Arguments
346    ///
347    /// * `lhs` - The left-hand side tensor.
348    /// * `rhs` - The right-hand side tensor.
349    /// * `dim` - The dimension to compute the cross product along.
350    ///
351    /// # Returns
352    ///
353    /// The cross product of the two tensors.
354    fn float_cross(lhs: FloatTensor<B>, rhs: FloatTensor<B>, dim: usize) -> FloatTensor<B>;
355
356    /// Negates a tensor element-wise.
357    fn float_neg(tensor: FloatTensor<B>) -> FloatTensor<B> {
358        Self::float_mul_scalar(tensor, (-1f32).into())
359    }
360
361    /// Calculates the reciprocals element-wise
362    fn float_recip(tensor: FloatTensor<B>) -> FloatTensor<B>;
363
364    /// Transposes a tensor.
365    ///
366    /// # Arguments
367    ///
368    /// * `tensor` - The tensor to transpose.
369    ///
370    /// # Returns
371    ///
372    /// The transposed tensor.
373    fn float_transpose(tensor: FloatTensor<B>) -> FloatTensor<B> {
374        let ndims = tensor.shape().num_dims();
375        Self::float_swap_dims(tensor, ndims - 2, ndims - 1)
376    }
377
378    /// Swaps two dimensions of a tensor.
379    ///
380    /// # Arguments
381    ///
382    /// * `tensor` - The tensor to swap the dimensions of.
383    /// * `dim1` - The first dimension to swap.
384    /// * `dim2` - The second dimension to swap.
385    ///
386    /// # Returns
387    ///
388    /// The tensor with the dimensions swapped.
389    fn float_swap_dims(tensor: FloatTensor<B>, dim1: usize, dim2: usize) -> FloatTensor<B>;
390
391    /// Permutes the dimensions of a tensor.
392    ///
393    /// # Arguments
394    ///
395    /// * `tensor` - The tensor to permute the dimensions of.
396    /// * `axes` - The new order of the dimensions.
397    /// # Returns
398    ///
399    /// The tensor with the dimensions permuted.
400    fn float_permute(tensor: FloatTensor<B>, axes: &[usize]) -> FloatTensor<B>;
401
402    /// Reverse the order of elements in a tensor along the given axes.
403    ///
404    /// # Arguments
405    ///
406    /// * `tensor` - The tensor to reverse.
407    /// * `axes` - The axes to reverse.
408    ///
409    /// The tensor with the elements reversed.
410    fn float_flip(tensor: FloatTensor<B>, axes: &[usize]) -> FloatTensor<B>;
411
412    /// Reshapes a tensor.
413    ///
414    /// # Arguments
415    ///
416    /// * `tensor` - The tensor to reshape.
417    /// * `shape` - The new shape of the tensor.
418    ///
419    /// # Returns
420    ///
421    /// The tensor with the new shape.
422    fn float_reshape(tensor: FloatTensor<B>, shape: Shape) -> FloatTensor<B>;
423
424    /// Gather elements from a tensor.
425    ///
426    /// # Arguments
427    ///
428    /// * `dim` - The dimension to gather from.
429    /// * `tensor` - The tensor to gather from.
430    /// * `indices` - The indices to gather.
431    ///
432    /// # Returns
433    ///
434    /// The gathered elements.
435    fn float_gather(dim: usize, tensor: FloatTensor<B>, indices: IntTensor<B>) -> FloatTensor<B>;
436
437    /// Scatter elements into a tensor using the specified update operation.
438    fn float_scatter(
439        dim: usize,
440        tensor: FloatTensor<B>,
441        indices: IntTensor<B>,
442        value: FloatTensor<B>,
443        update: IndexingUpdateOp,
444    ) -> FloatTensor<B>;
445
446    /// Multi-dimensional scatter: update `data` at locations specified by `indices` with `values`.
447    ///
448    /// # Arguments
449    ///
450    /// * `data` - The tensor to scatter into.
451    /// * `indices` - An M-dimensional integer tensor whose last dimension indexes into `data`.
452    /// * `values` - The values to scatter.
453    /// * `reduction` - How to combine with existing values.
454    ///
455    /// # Returns
456    ///
457    /// The tensor with scattered values.
458    fn float_scatter_nd(
459        _data: FloatTensor<B>,
460        _indices: IntTensor<B>,
461        _values: FloatTensor<B>,
462        _reduction: crate::tensor::IndexingUpdateOp,
463    ) -> FloatTensor<B> {
464        unimplemented!("float_scatter_nd is not implemented for this backend")
465    }
466
467    /// Multi-dimensional gather: collect slices from `data` at locations specified by `indices`.
468    ///
469    /// # Arguments
470    ///
471    /// * `data` - The tensor to gather from.
472    /// * `indices` - An M-dimensional integer tensor whose last dimension indexes into `data`.
473    ///
474    /// # Returns
475    ///
476    /// The gathered tensor.
477    fn float_gather_nd(_data: FloatTensor<B>, _indices: IntTensor<B>) -> FloatTensor<B> {
478        unimplemented!("float_gather_nd is not implemented for this backend")
479    }
480
481    /// Select tensor elements along the given dimension corresponding for the given indices.
482    ///
483    /// # Arguments
484    ///
485    /// * `tensor` - The tensor to select from.
486    /// * `dim` - The dimension to select from.
487    /// * `indices` - The indices to select.
488    ///
489    /// # Returns
490    ///
491    /// The selected elements.
492    fn float_select(tensor: FloatTensor<B>, dim: usize, indices: IntTensor<B>) -> FloatTensor<B>;
493
494    /// Assign selected elements along a dimension using the specified update operation.
495    fn float_select_assign(
496        tensor: FloatTensor<B>,
497        dim: usize,
498        indices: IntTensor<B>,
499        value: FloatTensor<B>,
500        update: IndexingUpdateOp,
501    ) -> FloatTensor<B>;
502
503    /// Select tensor elements corresponding to the given slices.
504    ///
505    /// # Arguments
506    ///
507    /// * `tensor` - The tensor to select from.
508    /// * `slices` - The slices specifying ranges and steps for each dimension.
509    ///
510    /// # Returns
511    ///
512    /// The selected elements in a new tensor.
513    ///
514    /// # Note
515    ///
516    /// Empty slices (where start >= end) are handled at the high-level tensor API and will not
517    /// be passed to this method. Backend implementations do not need to handle empty slices.
518    fn float_slice(tensor: FloatTensor<B>, slices: &[Slice]) -> FloatTensor<B>;
519
520    /// Assign the selected elements corresponding to the given slices to the given value.
521    ///
522    /// # Arguments
523    ///
524    /// * `tensor` - The tensor to select from.
525    /// * `ranges` - The ranges to select.
526    /// * `value` - The value to assign.
527    ///
528    /// # Returns
529    ///
530    /// The tensor with the selected elements assigned to the given value.
531    ///
532    /// # Note
533    ///
534    /// Empty slice assignments (where any slice range produces 0 elements) are handled at the
535    /// high-level tensor API and will not be passed to this method. Backend implementations do
536    /// not need to handle empty slice assignments.
537    fn float_slice_assign(
538        tensor: FloatTensor<B>,
539        slices: &[Slice],
540        value: FloatTensor<B>,
541    ) -> FloatTensor<B>;
542
543    /// Update the given tensor with the value tensor where the mask is true.
544    ///
545    /// # Arguments
546    ///
547    /// * `tensor` - The tensor to select from.
548    /// * `mask` - The boolean mask to select with.
549    /// * `value` - The value to assign to the selected elements from the value tensor.
550    ///
551    /// # Returns
552    ///
553    /// The tensor with the selected elements assigned to the given value.
554    fn float_mask_where(
555        tensor: FloatTensor<B>,
556        mask: BoolTensor<B>,
557        value: FloatTensor<B>,
558    ) -> FloatTensor<B>;
559
560    /// Update the given tensor with the value where the mask is true.
561    ///
562    /// # Arguments
563    ///
564    /// * `tensor` - The tensor to select from.
565    /// * `mask` - The boolean mask to select with.
566    /// * `value` - The value to assign to the selected elements.
567    ///
568    /// # Returns
569    ///
570    /// The tensor with the selected elements assigned to the given value.
571    fn float_mask_fill(
572        tensor: FloatTensor<B>,
573        mask: BoolTensor<B>,
574        value: Scalar,
575    ) -> FloatTensor<B>;
576
577    /// Selects the elements of the tensor where the mask is true, returned as a 1D tensor.
578    ///
579    /// The elements are collected in row-major order. Because the number of selected elements
580    /// depends on the mask values, the output shape is data-dependent: computing it may require
581    /// synchronizing with the device, which is why this operation is asynchronous.
582    ///
583    /// # Arguments
584    ///
585    /// * `tensor` - The tensor to select from.
586    /// * `mask` - The boolean mask, with the same shape as the tensor.
587    ///
588    /// # Returns
589    ///
590    /// A 1D tensor containing the selected elements.
591    fn float_mask_select(
592        tensor: FloatTensor<B>,
593        mask: BoolTensor<B>,
594    ) -> impl Future<Output = FloatTensor<B>> + 'static + Send {
595        async move {
596            // Data-dependent output length, so we defer to `bool_argwhere` (the only pre-existing
597            // data-dependent op) to collect the flat indices of the true mask values, then select.
598            let n = mask.shape().num_elements();
599            let int_dtype = get_device_settings::<B>(&mask.device()).int_dtype;
600            let mask = B::bool_reshape(mask, Shape::new([n]));
601            let indices = B::bool_argwhere(mask, int_dtype).await; // [count, 1]
602            let count = indices.shape()[0];
603            let indices = B::int_reshape(indices, Shape::new([count])); // squeeze to [count]
604            let tensor = B::float_reshape(tensor, Shape::new([n]));
605            B::float_select(tensor, 0, indices)
606        }
607    }
608
609    /// Equal comparison of two tensors.
610    ///
611    /// # Arguments
612    ///
613    /// * `lhs` - The left-hand side tensor.
614    /// * `rhs` - The right-hand side tensor.
615    /// * `out_dtype` - The output tensor dtype.
616    ///
617    /// # Returns
618    ///
619    /// A boolean tensor with the result of the comparison.
620    fn float_equal(lhs: FloatTensor<B>, rhs: FloatTensor<B>, out_dtype: BoolDType)
621    -> BoolTensor<B>;
622
623    /// Element-wise non-equality comparison.
624    ///
625    /// # Arguments
626    ///
627    /// * `lhs` - The left-hand side tensor.
628    /// * `rhs` - The right-hand side tensor.
629    /// * `out_dtype` - The output tensor dtype.
630    ///
631    /// # Returns
632    ///
633    /// A boolean tensor with the result of the comparison.
634    fn float_not_equal(
635        lhs: FloatTensor<B>,
636        rhs: FloatTensor<B>,
637        out_dtype: BoolDType,
638    ) -> BoolTensor<B> {
639        let equal_tensor = B::float_equal(lhs, rhs, out_dtype);
640        B::bool_not(equal_tensor)
641    }
642
643    /// Equal comparison of a tensor and a scalar.
644    ///
645    /// # Arguments
646    ///
647    /// * `lhs` - The left-hand side tensor.
648    /// * `rhs` - The right-hand side scalar.
649    /// * `out_dtype` - The output tensor dtype.
650    ///
651    /// # Returns
652    ///
653    /// A boolean tensor with the result of the comparison.
654    fn float_equal_elem(lhs: FloatTensor<B>, rhs: Scalar, out_dtype: BoolDType) -> BoolTensor<B>;
655
656    /// Element-wise non-equality comparison with a scalar.
657    ///
658    /// # Arguments
659    ///
660    /// * `lhs` - The left-hand side tensor.
661    /// * `rhs` - The right-hand side scalar.
662    /// * `out_dtype` - The output tensor dtype.
663    ///
664    /// # Returns
665    ///
666    /// A boolean tensor with the result of the comparison.
667    fn float_not_equal_elem(
668        lhs: FloatTensor<B>,
669        rhs: Scalar,
670        out_dtype: BoolDType,
671    ) -> BoolTensor<B> {
672        let equal_tensor = B::float_equal_elem(lhs, rhs, out_dtype);
673        B::bool_not(equal_tensor)
674    }
675
676    /// Greater than comparison of two tensors.
677    ///
678    /// # Arguments
679    ///
680    /// * `lhs` - The left-hand side tensor.
681    /// * `rhs` - The right-hand side tensor.
682    /// * `out_dtype` - The output tensor dtype.
683    ///
684    /// # Returns
685    ///
686    /// A boolean tensor with the result of the comparison.
687    fn float_greater(
688        lhs: FloatTensor<B>,
689        rhs: FloatTensor<B>,
690        out_dtype: BoolDType,
691    ) -> BoolTensor<B>;
692
693    /// Greater than comparison of a tensor and a scalar.
694    ///
695    /// # Arguments
696    ///
697    /// * `lhs` - The left-hand side tensor.
698    /// * `rhs` - The right-hand side scalar.
699    /// * `out_dtype` - The output tensor dtype.
700    ///
701    /// # Returns
702    ///
703    /// A boolean tensor with the result of the comparison.
704    fn float_greater_elem(lhs: FloatTensor<B>, rhs: Scalar, out_dtype: BoolDType) -> BoolTensor<B>;
705
706    /// Greater than or equal comparison of two tensors.
707    ///
708    /// # Arguments
709    ///
710    /// * `lhs` - The left-hand side tensor.
711    /// * `rhs` - The right-hand side tensor.
712    /// * `out_dtype` - The output tensor dtype.
713    ///
714    /// # Returns
715    ///
716    /// A boolean tensor with the result of the comparison.
717    fn float_greater_equal(
718        lhs: FloatTensor<B>,
719        rhs: FloatTensor<B>,
720        out_dtype: BoolDType,
721    ) -> BoolTensor<B>;
722
723    /// Greater than or equal comparison of a tensor and a scalar.
724    ///
725    /// # Arguments
726    ///
727    /// * `lhs` - The left-hand side tensor.
728    /// * `rhs` - The right-hand side scalar.
729    /// * `out_dtype` - The output tensor dtype.
730    ///
731    /// # Returns
732    ///
733    /// A boolean tensor with the result of the comparison.
734    fn float_greater_equal_elem(
735        lhs: FloatTensor<B>,
736        rhs: Scalar,
737        out_dtype: BoolDType,
738    ) -> BoolTensor<B>;
739
740    /// Less than comparison of two tensors.
741    ///
742    /// # Arguments
743    ///
744    /// * `lhs` - The left-hand side tensor.
745    /// * `rhs` - The right-hand side tensor.
746    /// * `out_dtype` - The output tensor dtype.
747    ///
748    /// # Returns
749    ///
750    /// A boolean tensor with the result of the comparison.
751    fn float_lower(lhs: FloatTensor<B>, rhs: FloatTensor<B>, out_dtype: BoolDType)
752    -> BoolTensor<B>;
753
754    /// Less than comparison of a tensor and a scalar.
755    ///
756    /// # Arguments
757    ///
758    /// * `lhs` - The left-hand side tensor.
759    /// * `rhs` - The right-hand side scalar.
760    /// * `out_dtype` - The output tensor dtype.
761    ///
762    /// # Returns
763    ///
764    /// A boolean tensor with the result of the comparison.
765    fn float_lower_elem(lhs: FloatTensor<B>, rhs: Scalar, out_dtype: BoolDType) -> BoolTensor<B>;
766
767    /// Less than or equal comparison of two tensors.
768    ///
769    /// # Arguments
770    ///
771    /// * `lhs` - The left-hand side tensor.
772    /// * `rhs` - The right-hand side tensor.
773    /// * `out_dtype` - The output tensor dtype.
774    ///
775    /// # Returns
776    ///
777    /// A boolean tensor with the result of the comparison.
778    fn float_lower_equal(
779        lhs: FloatTensor<B>,
780        rhs: FloatTensor<B>,
781        out_dtype: BoolDType,
782    ) -> BoolTensor<B>;
783
784    /// Less than or equal comparison of a tensor and a scalar.
785    ///
786    /// # Arguments
787    ///
788    /// * `lhs` - The left-hand side tensor.
789    /// * `rhs` - The right-hand side scalar.
790    /// * `out_dtype` - The output tensor dtype.
791    ///
792    /// # Returns
793    ///
794    /// A boolean tensor with the result of the comparison.
795    fn float_lower_equal_elem(
796        lhs: FloatTensor<B>,
797        rhs: Scalar,
798        out_dtype: BoolDType,
799    ) -> BoolTensor<B>;
800
801    /// Detaches a tensor from the computation graph.
802    fn float_detach(tensor: FloatTensor<B>) -> FloatTensor<B> {
803        // Should only be overridden by autodiff backends.
804        tensor
805    }
806
807    /// Sets the `require_grad` flag of a tensor.
808    fn float_set_require_grad(tensor: FloatTensor<B>, _require_grad: bool) -> FloatTensor<B> {
809        // Should only be overridden by autodiff backends.
810        tensor
811    }
812
813    /// Returns the `require_grad` flag of a tensor.
814    fn float_is_require_grad(_tensor: &FloatTensor<B>) -> bool {
815        // Should only be overridden by autodiff backends.
816        false
817    }
818
819    /// Sum of all elements in a tensor.
820    ///
821    /// # Arguments
822    ///
823    /// * `tensor` - The tensor to sum.
824    ///
825    /// # Returns
826    ///
827    /// A scalar tensor with the sum of all elements in `tensor`.
828    fn float_sum(tensor: FloatTensor<B>) -> FloatTensor<B>;
829
830    /// Sum of all elements in a tensor along a dimension.
831    ///
832    /// # Arguments
833    ///
834    /// * `tensor` - The tensor to sum.
835    /// * `dim` - The dimension along which to sum.
836    ///
837    /// # Returns
838    ///
839    /// A tensor with the sum of all elements in `tensor` along `dim`.
840    fn float_sum_dim(tensor: FloatTensor<B>, dim: usize) -> FloatTensor<B>;
841
842    /// Sum the tensor along several dimensions at once, keeping each of them
843    /// with length one.
844    ///
845    /// # Arguments
846    ///
847    /// * `tensor` - The tensor to sum.
848    /// * `dims` - The dimensions along which to sum.
849    ///
850    /// # Returns
851    ///
852    /// A tensor with the same rank, and length one along each of `dims`.
853    ///
854    /// The default reduces one dimension at a time, which writes and reads
855    /// back an intermediate per dimension. A backend that can fold the
856    /// dimensions into fewer reductions should override this.
857    fn float_sum_dims(tensor: FloatTensor<B>, dims: &[usize]) -> FloatTensor<B> {
858        dims.iter()
859            .fold(tensor, |tensor, &dim| B::float_sum_dim(tensor, dim))
860    }
861
862    /// Product of all elements in a tensor.
863    ///
864    /// # Arguments
865    ///
866    /// * `tensor` - The tensor to product.
867    ///
868    /// # Returns
869    ///
870    /// A scalar tensor with the product of all elements in `tensor`.
871    fn float_prod(tensor: FloatTensor<B>) -> FloatTensor<B> {
872        // Product of all elements in a tensor
873        B::float_exp(B::float_sum(B::float_log(tensor)))
874    }
875
876    /// Product of all elements in a tensor along a dimension.
877    ///
878    /// # Arguments
879    ///
880    /// * `tensor` - The tensor to product.
881    ///
882    /// # Returns
883    ///
884    /// A tensor with the product of all elements in `tensor` along `dim`.
885    fn float_prod_dim(tensor: FloatTensor<B>, dim: usize) -> FloatTensor<B> {
886        // Product of all elements in a tensor along a dimension
887        B::float_exp(B::float_sum_dim(B::float_log(tensor), dim))
888    }
889
890    /// Mean of all elements in a tensor.
891    ///
892    /// # Arguments
893    ///
894    /// * `tensor` - The tensor to mean.
895    ///
896    /// # Returns
897    ///
898    /// A scalar tensor with the mean of all elements in `tensor`.
899    fn float_mean(tensor: FloatTensor<B>) -> FloatTensor<B> {
900        let num_elems = tensor.shape().num_elements() as f32;
901        B::float_div_scalar(B::float_sum(tensor), num_elems.into())
902    }
903
904    /// Mean of all elements in a tensor along a dimension.
905    ///
906    /// # Arguments
907    ///
908    /// * `tensor` - The tensor to mean.
909    /// * `dim` - The dimension along which to mean.
910    ///
911    /// # Returns
912    ///
913    /// A tensor with the mean of all elements in `tensor` along `dim`.
914    fn float_mean_dim(tensor: FloatTensor<B>, dim: usize) -> FloatTensor<B>;
915
916    /// Computes the cumulative sum of elements along a dimension.
917    ///
918    /// # Arguments
919    ///
920    /// * `tensor` - The tensor to compute the cumulative sum of.
921    /// * `dim` - The dimension along which to compute the cumulative sum.
922    ///
923    /// # Returns
924    ///
925    /// A tensor with the same shape where each element is the cumulative sum
926    /// of all elements up to and including that position along the dimension.
927    fn float_cumsum(tensor: FloatTensor<B>, dim: usize) -> FloatTensor<B>;
928
929    /// Computes the cumulative product of elements along a dimension.
930    ///
931    /// # Arguments
932    ///
933    /// * `tensor` - The tensor to compute the cumulative product of.
934    /// * `dim` - The dimension along which to compute the cumulative product.
935    ///
936    /// # Returns
937    ///
938    /// A tensor with the same shape where each element is the cumulative product
939    /// of all elements up to and including that position along the dimension.
940    fn float_cumprod(tensor: FloatTensor<B>, dim: usize) -> FloatTensor<B>;
941
942    /// Computes the cumulative minimum of elements along a dimension.
943    ///
944    /// # Arguments
945    ///
946    /// * `tensor` - The tensor to compute the cumulative minimum of.
947    /// * `dim` - The dimension along which to compute the cumulative minimum.
948    ///
949    /// # Returns
950    ///
951    /// A tensor with the same shape where each element is the minimum
952    /// of all elements up to and including that position along the dimension.
953    fn float_cummin(tensor: FloatTensor<B>, dim: usize) -> FloatTensor<B>;
954
955    /// Computes the cumulative maximum of elements along a dimension.
956    ///
957    /// # Arguments
958    ///
959    /// * `tensor` - The tensor to compute the cumulative maximum of.
960    /// * `dim` - The dimension along which to compute the cumulative maximum.
961    ///
962    /// # Returns
963    ///
964    /// A tensor with the same shape where each element is the maximum
965    /// of all elements up to and including that position along the dimension.
966    fn float_cummax(tensor: FloatTensor<B>, dim: usize) -> FloatTensor<B>;
967
968    /// Converts a tensor to another floating point data type.
969    ///
970    /// # Arguments
971    ///
972    /// * `tensor` - The tensor to convert.
973    /// * `dtype` - The target data type.
974    ///
975    /// # Returns
976    ///
977    /// A tensor with the same values as `tensor` but in the target floating point data type.
978    fn float_cast(tensor: FloatTensor<B>, dtype: FloatDType) -> FloatTensor<B>;
979
980    /// Returns a new tensor with exponential values.
981    ///
982    /// # Arguments
983    ///
984    /// * `tensor` - The tensor to exponentiate.
985    ///
986    /// # Returns
987    ///
988    /// A tensor with the same shape as `tensor` with exponential values.
989    fn float_exp(tensor: FloatTensor<B>) -> FloatTensor<B>;
990
991    /// Returns a new tensor with natural logarithm values.
992    ///
993    /// # Arguments
994    ///
995    /// * `tensor` - The tensor to take the logarithm of.
996    ///
997    /// # Returns
998    ///
999    /// A tensor with the same shape as `tensor` with natural logarithm values.
1000    fn float_log(tensor: FloatTensor<B>) -> FloatTensor<B>;
1001
1002    /// Returns a new tensor with logarithm values of (1 + Xi).
1003    ///
1004    /// # Arguments
1005    ///
1006    /// * `tensor` - The tensor to take the logarithm of.
1007    ///
1008    /// # Returns
1009    ///
1010    /// A tensor with the same shape as `tensor` with logarithm values of (1 + Xi).
1011    fn float_log1p(tensor: FloatTensor<B>) -> FloatTensor<B>;
1012
1013    /// Element-wise power with a FloatTensor.
1014    ///
1015    /// # Arguments
1016    ///
1017    /// * `lhs` - The left-hand side tensor.
1018    /// * `rhs` - The right-hand side tensor.
1019    ///
1020    /// # Returns
1021    ///
1022    /// The elements of `lhs` raised to the power of the elements of `rhs`.
1023    fn float_powf(lhs: FloatTensor<B>, rhs: FloatTensor<B>) -> FloatTensor<B>;
1024
1025    /// Element-wise power with an IntTensor.
1026    ///
1027    /// # Arguments
1028    ///
1029    /// * `lhs` - The left-hand side tensor.
1030    /// * `rhs` - The right-hand side floatTensor.
1031    ///
1032    /// # Returns
1033    ///
1034    /// The elements of `lhs` raised to the value of `rhs`. Result is an IntTensor.
1035    fn float_powi(lhs: FloatTensor<B>, rhs: IntTensor<B>) -> FloatTensor<B> {
1036        let dtype = lhs.dtype();
1037        Self::float_powf(lhs, B::int_into_float(rhs, dtype.into()))
1038    }
1039
1040    /// Raises a tensor to the power of an int scalar.
1041    ///
1042    /// # Backend Implementors Note
1043    ///
1044    /// A number of common exponent cases can be implemented with operations
1045    /// which are much cheaper than generic exponentiation.
1046    ///
1047    /// This (`Backend` impl overridable) operation handles generic optimizations
1048    /// for several common integer exponent cases; and then dispatches to
1049    /// the (`Backend` impl overridable) [`Self::float_powi_scalar_impl`]
1050    /// operation to handle the generic case.
1051    ///
1052    /// # Arguments
1053    ///
1054    /// * `lhs` - The left-hand side tensor.
1055    /// * `rhs` - The right-hand side scalar.
1056    ///
1057    /// # Returns
1058    ///
1059    /// The elements of `lhs` raised to the value of `rhs`.
1060    fn float_powi_scalar(lhs: FloatTensor<B>, rhs: Scalar) -> FloatTensor<B> {
1061        match rhs.elem::<i64>() {
1062            0 => Self::float_ones(lhs.shape(), &lhs.device(), lhs.dtype().into()),
1063            1 => lhs,
1064            2 => B::float_mul(lhs.clone(), lhs),
1065            -1 => Self::float_recip(lhs),
1066            -2 => Self::float_recip(B::float_mul(lhs.clone(), lhs)),
1067            _ => Self::float_powi_scalar_impl(lhs, rhs),
1068        }
1069    }
1070
1071    /// Raises a tensor to the power of an int scalar.
1072    ///
1073    /// # Backend Implementors Note
1074    ///
1075    /// This is the generic implementation of integer exponentiation
1076    /// called by [`Self::float_powi_scalar`] in the fallback case.
1077    ///
1078    /// As a general rule, this should not be called directly.
1079    ///
1080    /// # Arguments
1081    ///
1082    /// * `lhs` - The left-hand side tensor.
1083    /// * `rhs` - The right-hand side scalar.
1084    ///
1085    /// # Returns
1086    ///
1087    /// The elements of `lhs` raised to the value of `rhs`.
1088    fn float_powi_scalar_impl(lhs: FloatTensor<B>, rhs: Scalar) -> FloatTensor<B> {
1089        // Avoid a recursive loop by deferring directly to float_powf_scalar_impl.
1090        Self::float_powf_scalar_impl(lhs, rhs)
1091    }
1092
1093    /// Returns a new tensor with values raised to the power of float `value`.
1094    ///
1095    /// # Backend Implementors Note
1096    ///
1097    /// This (`Backend` impl overridable) operation dispatches integer exponentiation
1098    /// to [`Self::float_powi_scalar`], and the remaining non-integer exponent cases to
1099    /// the (`Backend` impl overridable) [`Self::float_powf_scalar_impl`]
1100    /// operation to handle the generic case.
1101    ///
1102    /// # Arguments
1103    ///
1104    /// * `tensor` - The tensor to exponentiate.
1105    /// * `value` - The exponent.
1106    ///
1107    /// # Returns
1108    ///
1109    /// A tensor with the same shape as `tensor` with values raised to the power of `value`.
1110    fn float_powf_scalar(tensor: FloatTensor<B>, value: Scalar) -> FloatTensor<B> {
1111        if let Some(exp) = value.try_as_integer() {
1112            Self::float_powi_scalar(tensor, exp)
1113        } else {
1114            Self::float_powf_scalar_impl(tensor, value)
1115        }
1116    }
1117
1118    /// Returns a new tensor with values raised to the power of float `value`.
1119    ///
1120    /// # Backend Implementors Note
1121    ///
1122    /// This is the generic implementation of integer exponentiation
1123    /// called by [`Self::float_powf_scalar`] in the fallback case.
1124    ///
1125    /// This is the minimal required support a `Backend` must implement
1126    /// for exponentiation.
1127    ///
1128    /// As a general rule, this should not be called directly.
1129    ///
1130    /// # Arguments
1131    ///
1132    /// * `tensor` - The tensor to exponentiate.
1133    /// * `value` - The exponent.
1134    ///
1135    /// # Returns
1136    ///
1137    /// A tensor with the same shape as `tensor` with values raised to the power of `value`.
1138    fn float_powf_scalar_impl(tensor: FloatTensor<B>, value: Scalar) -> FloatTensor<B>;
1139
1140    /// Returns a new tensor with square root values.
1141    ///
1142    /// # Arguments
1143    ///
1144    /// * `tensor` - The tensor to take the square root of.
1145    ///
1146    /// # Returns
1147    ///
1148    /// A tensor with the same shape as `tensor` with square root values.
1149    fn float_sqrt(tensor: FloatTensor<B>) -> FloatTensor<B>;
1150
1151    /// Returns a new tensor with the Euclidean distance values.
1152    ///
1153    /// # Arguments
1154    ///
1155    /// * `lhs` - The left-hand side tensor.
1156    /// * `rhs` - The right-hand side tensor.
1157    ///
1158    /// # Returns
1159    ///
1160    /// A tensor with the same shape as `lhs` and `rhs` with hypotenuse values.
1161    fn float_hypot(lhs: FloatTensor<B>, rhs: FloatTensor<B>) -> FloatTensor<B> {
1162        // default implementation for any backend that can't either iterator over elements or doesn't have
1163        // a native hypot implementation
1164
1165        // Mirrors glibc's approach: scale by max(|lhs|, |rhs|) to avoid
1166        // overflow/underflow in the intermediate squaring step.
1167        //
1168        // hypot(x, y) = |max| * sqrt(1 + (min/max)^2)
1169        //
1170        // Edge cases:
1171        //   - If max == 0, both inputs are 0, result is 0 (division guarded by clamp)
1172        //   - If max is inf, result is inf (propagates naturally through sqrt)
1173        //   - NaN propagates naturally
1174        let abs_lhs = B::float_abs(lhs);
1175        let abs_rhs = B::float_abs(rhs);
1176
1177        let diff = B::float_clamp_min(B::float_sub(abs_rhs.clone(), abs_lhs.clone()), 0.0.into());
1178        let max = B::float_add(abs_lhs.clone(), diff.clone());
1179        let min = B::float_sub(abs_rhs, diff);
1180
1181        // Clamp max to at least epsilon to avoid 0/0; result will be 0 anyway
1182        // since min <= max, so (min/clamped_max)^2 won't blow up meaningfully.
1183        let max_safe = B::float_clamp_min(
1184            max.clone(),
1185            max.dtype().finfo().unwrap().min_positive.into(),
1186        );
1187
1188        let ratio = B::float_div(min, max_safe);
1189        let ratio_sq = B::float_mul(ratio.clone(), ratio);
1190
1191        let inner = B::float_add_scalar(ratio_sq, 1.0.into());
1192
1193        B::float_mul(max, B::float_sqrt(inner))
1194    }
1195
1196    /// Returns a new tensor with absolute values.
1197    ///
1198    /// # Arguments
1199    ///
1200    /// * `tensor` - The tensor to take absolute value of.
1201    ///
1202    /// # Returns
1203    ///
1204    /// A tensor with the same shape as `tensor` with absolute values.
1205    fn float_abs(tensor: FloatTensor<B>) -> FloatTensor<B>;
1206
1207    /// Returns a new tensor with cosine values.
1208    ///
1209    /// # Arguments
1210    ///
1211    /// * `tensor` - The tensor to take the cosine of.
1212    ///
1213    /// # Returns
1214    ///
1215    /// A tensor with the same shape as `tensor` with cosine values.
1216    fn float_cos(tensor: FloatTensor<B>) -> FloatTensor<B>;
1217
1218    /// Returns a new tensor with sine values.
1219    ///
1220    /// # Arguments
1221    ///
1222    /// * `tensor` - The tensor to take the sine of.
1223    ///
1224    /// # Returns
1225    ///
1226    /// A tensor with the same shape as `tensor` with sine values.
1227    fn float_sin(tensor: FloatTensor<B>) -> FloatTensor<B>;
1228
1229    /// Returns a new tensor with tangent values.
1230    ///
1231    /// # Arguments
1232    ///
1233    /// * `tensor` - The tensor to take the tangent of.
1234    ///
1235    /// # Returns
1236    ///
1237    /// A tensor with the same shape as `tensor` with tangent values.
1238    fn float_tan(tensor: FloatTensor<B>) -> FloatTensor<B>;
1239
1240    /// Returns a new tensor with hyperbolic cosine values.
1241    ///
1242    /// # Arguments
1243    ///
1244    /// * `tensor` - The tensor to take the hyperbolic cosine of.
1245    ///
1246    /// # Returns
1247    ///
1248    /// A tensor with the same shape as `tensor` with hyperbolic cosine values.
1249    fn float_cosh(tensor: FloatTensor<B>) -> FloatTensor<B>;
1250
1251    /// Returns a new tensor with hyperbolic sine values.
1252    ///
1253    /// # Arguments
1254    ///
1255    /// * `tensor` - The tensor to take the hyperbolic sine of.
1256    ///
1257    /// # Returns
1258    ///
1259    /// A tensor with the same shape as `tensor` with hyperbolic sine values.
1260    fn float_sinh(tensor: FloatTensor<B>) -> FloatTensor<B>;
1261
1262    /// Returns a new tensor with hyperbolic tangent values.
1263    ///
1264    /// # Arguments
1265    ///
1266    /// * `tensor` - The tensor to take the hyperbolic tangent of.
1267    ///
1268    /// # Returns
1269    ///
1270    /// A tensor with the same shape as `tensor` with hyperbolic tangent values.
1271    fn float_tanh(tensor: FloatTensor<B>) -> FloatTensor<B>;
1272
1273    /// Returns a new tensor with inverse cosine values.
1274    ///
1275    /// # Arguments
1276    ///
1277    /// * `tensor` - The input tensor.
1278    ///
1279    /// # Returns
1280    ///
1281    /// A tensor with the same shape as `tensor` with inverse cosine values.
1282    fn float_acos(tensor: FloatTensor<B>) -> FloatTensor<B>;
1283
1284    /// Returns a new tensor with inverse hyperbolic cosine values.
1285    ///
1286    /// # Arguments
1287    ///
1288    /// * `tensor` - The input tensor.
1289    ///
1290    /// # Returns
1291    ///
1292    /// A tensor with the same shape as `tensor` with inverse hyperbolic cosine values.
1293    fn float_acosh(tensor: FloatTensor<B>) -> FloatTensor<B>;
1294
1295    /// Returns a new tensor with inverse sine values.
1296    ///
1297    /// # Arguments
1298    ///
1299    /// * `tensor` - The input tensor.
1300    ///
1301    /// # Returns
1302    ///
1303    /// A tensor with the same shape as `tensor` with inverse sine values.
1304    fn float_asin(tensor: FloatTensor<B>) -> FloatTensor<B>;
1305
1306    /// Returns a new tensor with inverse hyperbolic sine values.
1307    ///
1308    /// # Arguments
1309    ///
1310    /// * `tensor` - The input tensor.
1311    ///
1312    /// # Returns
1313    ///
1314    /// A tensor with the same shape as `tensor` with inverse hyperbolic sine values.
1315    fn float_asinh(tensor: FloatTensor<B>) -> FloatTensor<B>;
1316
1317    /// Returns a new tensor with the inverse tangent values.
1318    ///
1319    /// # Arguments
1320    ///
1321    /// * `tensor` - The input tensor.
1322    ///
1323    /// # Returns
1324    ///
1325    /// A tensor with the same shape as `tensor` with the inverse tangent values.
1326    fn float_atan(tensor: FloatTensor<B>) -> FloatTensor<B>;
1327
1328    /// Returns a new tensor with the inverse hyperbolic tangent values.
1329    ///
1330    /// # Arguments
1331    ///
1332    /// * `tensor` - The input tensor.
1333    ///
1334    /// # Returns
1335    ///
1336    /// A tensor with the same shape as `tensor` with the inverse hyperbolic tangent values.
1337    fn float_atanh(tensor: FloatTensor<B>) -> FloatTensor<B>;
1338
1339    /// Returns a tensor with the four-quadrant inverse tangent values of `y` and `x`.
1340    ///
1341    /// # Arguments
1342    ///
1343    /// * `lhs` - The tensor with y coordinates.
1344    /// * `rhs` - The tensor with x coordinates.
1345    ///
1346    /// # Returns
1347    ///
1348    /// A tensor with the four-quadrant inverse tangent values.
1349    fn float_atan2(lhs: FloatTensor<B>, rhs: FloatTensor<B>) -> FloatTensor<B>;
1350
1351    /// Returns a new tensor with rounded values.
1352    ///
1353    /// This function should implement the [round half to even](https://en.wikipedia.org/wiki/Rounding#Rounding_half_to_even)
1354    /// strategy, with halfway cases rounded to the nearest even integer value.
1355    ///
1356    /// # Arguments
1357    ///
1358    /// * `tensor` - The tensor to be rounded.
1359    ///
1360    /// # Returns
1361    ///
1362    /// A tensor with the same shape as `tensor` with rounded values.
1363    fn float_round(tensor: FloatTensor<B>) -> FloatTensor<B>;
1364
1365    /// Returns a new tensor with floored values.
1366    ///
1367    /// # Arguments
1368    ///
1369    /// * `tensor` - The tensor to be floored.
1370    ///
1371    /// # Returns
1372    ///
1373    /// A tensor with the same shape as `tensor` with floored values.
1374    fn float_floor(tensor: FloatTensor<B>) -> FloatTensor<B>;
1375
1376    /// Returns a new tensor with ceiled values.
1377    ///
1378    /// # Arguments
1379    ///
1380    /// * `tensor` - The tensor to be ceiled.
1381    ///
1382    /// # Returns
1383    ///
1384    /// A tensor with the same shape as `tensor` with ceiled values.
1385    fn float_ceil(tensor: FloatTensor<B>) -> FloatTensor<B>;
1386
1387    /// Returns a new tensor with truncated values.
1388    ///
1389    /// # Arguments
1390    ///
1391    /// * `tensor` - The tensor to be truncated.
1392    ///
1393    /// # Returns
1394    ///
1395    /// A tensor with the same shape as `tensor` with truncated values.
1396    fn float_trunc(tensor: FloatTensor<B>) -> FloatTensor<B>;
1397
1398    /// Returns a new tensor with the error function values.
1399    ///
1400    /// # Arguments
1401    ///
1402    /// * `tensor` - The tensor to take the error function of.
1403    ///
1404    /// # Returns
1405    ///
1406    /// A tensor with the same shape as `tensor` with error function values.
1407    fn float_erf(tensor: FloatTensor<B>) -> FloatTensor<B>;
1408
1409    /// Concatenates tensors along a dimension.
1410    ///
1411    /// # Arguments
1412    ///
1413    /// * `tensors` - The tensors to concatenate.
1414    /// * `dim` - The dimension along which to concatenate.
1415    ///
1416    /// # Returns
1417    ///
1418    /// A tensor with the concatenated tensors along `dim`.
1419    ///
1420    /// # Note
1421    ///
1422    /// Empty tensors (where the concatenation dimension has size 0) are filtered out at the
1423    /// high-level tensor API and will not be passed to this method. Backend implementations do
1424    /// not need to handle empty tensors.
1425    fn float_cat(tensors: Vec<FloatTensor<B>>, dim: usize) -> FloatTensor<B> {
1426        let first_tensor = tensors.first().expect("Tensors should not be empty");
1427        let device = first_tensor.device();
1428
1429        cat_with_slice_assign::<B, _, _, _>(
1430            tensors,
1431            dim,
1432            device,
1433            |shape, device, dtype| B::float_empty(shape, device, dtype.into()),
1434            B::float_slice_assign,
1435        )
1436    }
1437
1438    /// Gets the indices of the maximum elements of a tensor along an axis.
1439    ///
1440    /// # Arguments
1441    ///
1442    /// * `tensor` - The tensor to get the maximum elements of.
1443    /// * `dim` - The dimension along which to get the maximum elements.
1444    /// * `out_dtype` - The output tensor dtype.
1445    ///
1446    /// # Returns
1447    ///
1448    /// A tensor with the indices of the maximum elements of `tensor` along `dim`.
1449    fn float_argmax(tensor: FloatTensor<B>, dim: usize, out_dtype: IntDType) -> IntTensor<B>;
1450
1451    /// Gets the indices of the k maximum elements of a tensor along an axis.
1452    /// if two elements are equals, it will be ordered by lowest indices
1453    ///
1454    /// # Arguments
1455    ///
1456    /// * `tensor` - The tensor to get the maximum elements of.
1457    /// * `dim` - The dimension along which to get the maximum elements.
1458    /// * `k` - number of maximum elements
1459    /// * `out_dtype` - The output tensor dtype.
1460    ///
1461    /// # Returns
1462    ///
1463    /// A tensor with the indices of the maximum elements of `tensor` along `dim`.
1464    fn float_argtopk(
1465        tensor: FloatTensor<B>,
1466        dim: usize,
1467        k: usize,
1468        out_dtype: IntDType,
1469    ) -> IntTensor<B> {
1470        let device = tensor.device();
1471        let dtype = get_device_settings::<B>(&device).int_dtype;
1472        let k_indices = B::int_arange(0..k as i64, &device, dtype);
1473        B::int_select(
1474            Self::float_argsort(tensor, dim, true, out_dtype),
1475            dim,
1476            k_indices,
1477        )
1478    }
1479
1480    /// Gets the values of the k maximum elements of a tensor along an axis.
1481    ///
1482    /// # Arguments
1483    ///
1484    /// * `tensor` - The tensor to get the maximum elements of.
1485    /// * `dim` - The dimension along which to get the maximum elements.
1486    /// * `k` - number of maximum elements
1487    /// * `out_dtype` - The output tensor dtype.
1488    ///
1489    /// # Returns
1490    ///
1491    /// A tensor with the values of the maximum elements of `tensor` along `dim`.
1492    fn float_topk(tensor: FloatTensor<B>, dim: usize, k: usize) -> FloatTensor<B> {
1493        let device = tensor.device();
1494        let dtype = get_device_settings::<B>(&device).int_dtype;
1495        let k_indices = B::int_arange(0..k as i64, &device, dtype);
1496        Self::float_select(Self::float_sort(tensor, dim, true), dim, k_indices)
1497    }
1498
1499    /// Gets the values of the k maximum elements of a tensor along an axis, and their indices.
1500    ///
1501    /// # Arguments
1502    ///
1503    /// * `tensor` - The tensor to get the maximum elements of.
1504    /// * `dim` - The dimension along which to get the maximum elements.
1505    /// * `k` - number of maximum elements
1506    /// * `out_dtype` - The indices tensor dtype.
1507    ///
1508    /// # Returns
1509    ///
1510    /// A tuple with the values of the k maximum elements of `tensor` along `dim`, and their
1511    /// indices.
1512    ///
1513    /// The default sorts once and keeps the first `k` of each half. It deliberately does not
1514    /// compose `float_topk` with `float_argtopk`: those default to a sort each, so that would
1515    /// sort twice, and it would also require `float_argtopk` from backends that only have
1516    /// sorting. Backends whose top-k already carries both results should override this and
1517    /// produce them in a single pass.
1518    fn float_topk_with_indices(
1519        tensor: FloatTensor<B>,
1520        dim: usize,
1521        k: usize,
1522        out_dtype: IntDType,
1523    ) -> (FloatTensor<B>, IntTensor<B>) {
1524        let device = tensor.device();
1525        let dtype = get_device_settings::<B>(&device).int_dtype;
1526        let k_indices = B::int_arange(0..k as i64, &device, dtype);
1527        let (values, indices) = Self::float_sort_with_indices(tensor, dim, true, out_dtype);
1528
1529        (
1530            Self::float_select(values, dim, k_indices.clone()),
1531            B::int_select(indices, dim, k_indices),
1532        )
1533    }
1534
1535    /// Gets the indices of the minimum elements of a tensor along an axis.
1536    ///
1537    /// # Arguments
1538    ///
1539    /// * `tensor` - The tensor to get the minimum elements of.
1540    /// * `dim` - The dimension along which to get the minimum elements.
1541    /// * `out_dtype` - The output tensor dtype.
1542    ///
1543    /// # Returns
1544    ///
1545    /// A tensor with the indices of the minimum elements of `tensor` along `dim`.
1546    fn float_argmin(tensor: FloatTensor<B>, dim: usize, out_dtype: IntDType) -> IntTensor<B>;
1547
1548    /// Gets the maximum element of a tensor.
1549    ///
1550    /// # Arguments
1551    ///
1552    /// * `tensor` - The tensor to get the maximum elements of.
1553    ///
1554    /// # Returns
1555    ///
1556    /// A tensor with the maximum element of `tensor`.
1557    fn float_max(tensor: FloatTensor<B>) -> FloatTensor<B> {
1558        let shape = tensor.shape();
1559        let tensor = B::float_reshape(tensor, Shape::new([shape.num_elements()]));
1560
1561        B::float_max_dim(tensor, 0)
1562    }
1563
1564    /// Gets the maximum elements of a tensor along an axis.
1565    ///
1566    /// # Arguments
1567    ///
1568    /// * `tensor` - The tensor to get the maximum elements of.
1569    /// * `dim` - The dimension along which to get the maximum elements.
1570    ///
1571    /// # Returns
1572    ///
1573    /// A tensor with the maximum elements of `tensor` along `dim`.
1574    fn float_max_dim(tensor: FloatTensor<B>, dim: usize) -> FloatTensor<B> {
1575        let dtype = get_device_settings::<B>(&tensor.device()).int_dtype;
1576        let index = B::float_argmax(tensor.clone(), dim, dtype);
1577
1578        B::float_gather(dim, tensor, index)
1579    }
1580
1581    /// Gets the maximum elements of a tensor along an axis and their indices.
1582    ///
1583    /// # Arguments
1584    ///
1585    /// * `tensor` - The tensor to get the maximum elements of.
1586    /// * `dim` - The dimension along which to get the maximum elements.
1587    /// * `indices_dtype` - The indices tensor dtype.
1588    ///
1589    /// # Returns
1590    ///
1591    /// A tuple with the maximum elements of `tensor` along `dim` and their indices.
1592    fn float_max_dim_with_indices(
1593        tensor: FloatTensor<B>,
1594        dim: usize,
1595        indices_dtype: IntDType,
1596    ) -> (FloatTensor<B>, IntTensor<B>) {
1597        let index = B::float_argmax(tensor.clone(), dim, indices_dtype);
1598        let values = B::float_gather(dim, tensor, index.clone());
1599
1600        (values, index)
1601    }
1602
1603    /// Gets the minimum element of a tensor.
1604    ///
1605    /// # Arguments
1606    ///
1607    /// * `tensor` - The tensor to get the minimum elements of.
1608    ///
1609    /// # Returns
1610    ///
1611    /// A tensor with the minimum element of `tensor`.
1612    fn float_min(tensor: FloatTensor<B>) -> FloatTensor<B> {
1613        let shape = tensor.shape();
1614        let tensor = B::float_reshape(tensor, Shape::new([shape.num_elements()]));
1615
1616        B::float_min_dim(tensor, 0)
1617    }
1618
1619    /// Gets the minimum elements of a tensor along an axis.
1620    ///
1621    /// # Arguments
1622    ///
1623    /// * `tensor` - The tensor to get the minimum elements of.
1624    /// * `dim` - The dimension along which to get the minimum elements.
1625    ///
1626    /// # Returns
1627    ///
1628    /// A tensor with the minimum elements of `tensor` along `dim`.
1629    fn float_min_dim(tensor: FloatTensor<B>, dim: usize) -> FloatTensor<B> {
1630        let dtype = get_device_settings::<B>(&tensor.device()).int_dtype;
1631        let index = B::float_argmin(tensor.clone(), dim, dtype);
1632
1633        B::float_gather(dim, tensor, index)
1634    }
1635
1636    /// Gets the minimum elements of a tensor along an axis and their indices.
1637    ///
1638    /// # Arguments
1639    ///
1640    /// * `tensor` - The tensor to get the minimum elements of.
1641    /// * `dim` - The dimension along which to get the minimum elements.
1642    /// * `indices_dtype` - The indices tensor dtype.
1643    ///
1644    /// # Returns
1645    ///
1646    /// A tuple with the minimum elements of `tensor` along `dim` and their indices.
1647    fn float_min_dim_with_indices(
1648        tensor: FloatTensor<B>,
1649        dim: usize,
1650        indices_dtype: IntDType,
1651    ) -> (FloatTensor<B>, IntTensor<B>) {
1652        let index = B::float_argmin(tensor.clone(), dim, indices_dtype);
1653        let values = B::float_gather(dim, tensor, index.clone());
1654
1655        (values, index)
1656    }
1657
1658    /// Gets the maximum absolute element of a tensor.
1659    ///
1660    /// # Arguments
1661    ///
1662    /// * `tensor` - The tensor to get the maximum elements of.
1663    ///
1664    /// # Returns
1665    ///
1666    /// A tensor with the maximum element of `tensor`.
1667    fn float_max_abs(tensor: FloatTensor<B>) -> FloatTensor<B> {
1668        let shape = tensor.shape();
1669        let tensor = B::float_reshape(tensor, Shape::new([shape.num_elements()]));
1670
1671        B::float_max_abs_dim(tensor, 0)
1672    }
1673
1674    /// Gets the maximum absolute elements of a tensor along an axis.
1675    ///
1676    /// # Arguments
1677    ///
1678    /// * `tensor` - The tensor to get the maximum elements of.
1679    /// * `dim` - The dimension along which to get the maximum elements.
1680    ///
1681    /// # Returns
1682    ///
1683    /// A tensor with the maximum elements of `tensor` along `dim`.
1684    fn float_max_abs_dim(tensor: FloatTensor<B>, dim: usize) -> FloatTensor<B> {
1685        B::float_max_dim(B::float_abs(tensor), dim)
1686    }
1687
1688    /// Tests if any element in the float `tensor` evaluates to True.
1689    ///
1690    /// # Arguments
1691    ///
1692    /// * `tensor` - The tensor to test.
1693    /// * `out_dtype` - The output tensor dtype.
1694    ///
1695    /// # Returns
1696    ///
1697    /// A boolean tensor with a single element, True if any element in the tensor is True, False otherwise.
1698    fn float_any(tensor: FloatTensor<B>, out_dtype: BoolDType) -> BoolTensor<B> {
1699        let float_dtype = tensor.dtype();
1700        let bool_tensor = B::float_equal_elem(tensor, 0f32.into(), out_dtype);
1701        let bool_tensor = B::bool_not(bool_tensor);
1702        let sum = B::float_sum(B::bool_into_float(bool_tensor, float_dtype.into()));
1703        B::float_greater_elem(sum, 0f32.into(), out_dtype)
1704    }
1705
1706    /// Tests if any element in the float `tensor` evaluates to True along a given dimension `dim`.
1707    ///
1708    /// # Arguments
1709    ///
1710    /// * `tensor` - The tensor to test.
1711    /// * `dim` - The axis along which to test.
1712    /// * `out_dtype` - The output tensor dtype.
1713    ///
1714    /// # Returns
1715    ///
1716    /// A boolean tensor `Tensor<B, D, Bool>` with the same size as input `tensor`, except in the `dim` axis
1717    /// where the size is 1. The elem in the `dim` axis is True if any element along this dim in the
1718    /// input evaluates to True, False otherwise.
1719    fn float_any_dim(tensor: FloatTensor<B>, dim: usize, out_dtype: BoolDType) -> BoolTensor<B> {
1720        let float_dtype = tensor.dtype();
1721        let bool_tensor = B::float_equal_elem(tensor, 0f32.into(), out_dtype);
1722        let bool_tensor = B::bool_not(bool_tensor);
1723        let sum = B::float_sum_dim(B::bool_into_float(bool_tensor, float_dtype.into()), dim);
1724        B::float_greater_elem(sum, 0f32.into(), out_dtype)
1725    }
1726
1727    /// Tests if all elements in the float `tensor` evaluate to True.
1728    ///
1729    /// # Arguments
1730    ///
1731    /// * `tensor` - The tensor to test.
1732    /// * `out_dtype` - The output tensor dtype.
1733    ///
1734    /// # Returns
1735    ///
1736    /// A boolean tensor `Tensor<B, 1, Bool>` with a single element, True if all elements in the input tensor
1737    /// evaluate to True, False otherwise.
1738    fn float_all(tensor: FloatTensor<B>, out_dtype: BoolDType) -> BoolTensor<B> {
1739        let float_dtype = tensor.dtype();
1740        let num_elems = tensor.shape().num_elements() as f32;
1741        let bool_tensor = B::float_equal_elem(tensor, 0f32.into(), out_dtype);
1742        let bool_tensor = B::bool_not(bool_tensor);
1743        let sum = B::float_sum(B::bool_into_float(bool_tensor, float_dtype.into()));
1744        B::float_equal_elem(sum, num_elems.into(), out_dtype)
1745    }
1746
1747    /// Tests if all elements in the float `tensor` evaluate to True along a given dimension `dim`.
1748    ///
1749    /// # Arguments
1750    ///
1751    /// * `tensor` - The tensor to test.
1752    /// * `dim` - The axis along which to test.
1753    /// * `out_dtype` - The output tensor dtype.
1754    ///
1755    /// # Returns
1756    ///
1757    /// A boolean tensor `Tensor<B, D, Bool>` with the same size as input `tensor`, except in the `dim` axis
1758    /// where the size is 1. The elem in the `dim` axis is True if all elements along this dim in the input
1759    /// evaluates to True, False otherwise.
1760    fn float_all_dim(tensor: FloatTensor<B>, dim: usize, out_dtype: BoolDType) -> BoolTensor<B> {
1761        let float_dtype = tensor.dtype();
1762        let num_elems = tensor.shape()[dim] as f32;
1763        let bool_tensor = B::float_equal_elem(tensor, 0f32.into(), out_dtype);
1764        let bool_tensor = B::bool_not(bool_tensor);
1765        let sum = B::float_sum_dim(B::bool_into_float(bool_tensor, float_dtype.into()), dim);
1766        B::float_equal_elem(sum, num_elems.into(), out_dtype)
1767    }
1768
1769    /// Returns the signs of the float `tensor`.
1770    ///
1771    /// # Arguments
1772    ///
1773    /// * `tensor` - The tensor to extract the signs from.
1774    ///
1775    /// # Returns
1776    ///
1777    /// A tensor with the same shape as `tensor` containing the signs of the elements of `tensor`:
1778    /// `1` where positive, `-1` where negative, and `0` where zero (either sign) or NaN.
1779    ///
1780    /// `sign(NaN) == 0` is part of this contract and every backend override must uphold it too.
1781    fn float_sign(tensor: FloatTensor<B>) -> FloatTensor<B> {
1782        let device = tensor.device();
1783        let bool_dtype = get_device_settings::<B>(&tensor.device()).bool_dtype;
1784        let zeros = B::float_zeros(tensor.shape(), &device, tensor.dtype().into());
1785        let less_than_zero = B::float_lower_elem(tensor.clone(), 0f32.into(), bool_dtype);
1786        let greater_than_zero = B::float_greater_elem(tensor, 0f32.into(), bool_dtype);
1787
1788        let mut result = B::float_mask_fill(zeros, less_than_zero, (-1f32).into());
1789        result = B::float_mask_fill(result, greater_than_zero, 1f32.into());
1790        result
1791    }
1792
1793    /// Broadcasts the float `tensor` to the given `shape`.
1794    fn float_expand(tensor: FloatTensor<B>, shape: Shape) -> FloatTensor<B>;
1795
1796    /// Sort the elements of the input `tensor` by value in along a given dimension.
1797    ///
1798    /// This sort is unstable (i.e., may reorder equal elements).
1799    ///
1800    /// # Arguments
1801    ///
1802    /// * `tensor` - The input tensor.
1803    /// * `dim` - The axis along which to sort.
1804    /// * `descending` - The sorting order.
1805    ///
1806    /// # Returns
1807    ///
1808    /// A tensor with the same shape as the input tensor, where the elements are sorted by value.
1809    fn float_sort(tensor: FloatTensor<B>, dim: usize, descending: bool) -> FloatTensor<B> {
1810        let device = tensor.device();
1811        sort::<B, _, _, _>(
1812            tensor,
1813            dim,
1814            descending,
1815            device,
1816            |tensor| {
1817                let msg = "Failed to synchronously read tensor data. This operation is not supported until this backend has a GPU sorting implementation.";
1818                try_read_sync(B::float_into_data(tensor))
1819                    .expect(msg)
1820                    .expect(msg)
1821            },
1822            |data, device, _dtype| B::float_from_data(data, device),
1823        )
1824    }
1825
1826    /// Sort the elements of the input `tensor` by value in along a given dimension.
1827    ///
1828    /// This sort is unstable (i.e., may reorder equal elements).
1829    ///
1830    /// # Arguments
1831    ///
1832    /// * `tensor` - The input tensor.
1833    /// * `dim` - The axis along which to sort.
1834    /// * `descending` - The sorting order.
1835    /// * `indices_dtype` - The indices tensor dtype.
1836    ///
1837    /// # Returns
1838    ///
1839    /// A tensor with the same shape as the input tensor and corresponding indices, where
1840    /// the elements are sorted by value and the indices map back to the original input tensor.
1841    fn float_sort_with_indices(
1842        tensor: FloatTensor<B>,
1843        dim: usize,
1844        descending: bool,
1845        indices_dtype: IntDType,
1846    ) -> (FloatTensor<B>, IntTensor<B>) {
1847        let device = tensor.device();
1848        sort_with_indices::<B, _, _, _>(
1849            tensor,
1850            dim,
1851            descending,
1852            indices_dtype,
1853            device,
1854            |tensor| {
1855                let msg = "Failed to synchronously read tensor data. This operation is not supported until this backend has a GPU sorting implementation.";
1856                try_read_sync(B::float_into_data(tensor))
1857                    .expect(msg)
1858                    .expect(msg)
1859            },
1860            |data, device, _dtype| B::float_from_data(data, device),
1861        )
1862    }
1863
1864    /// Returns the indices that sort the elements of the input `tensor` by value along a given dimension.
1865    ///
1866    /// This sort is unstable (i.e., may reorder equal elements).
1867    ///
1868    /// # Arguments
1869    ///
1870    /// * `tensor` - The input tensor.
1871    /// * `dim` - The axis along which to sort.
1872    /// * `descending` - The sorting order.
1873    /// * `out_dtype` - The output tensor dtype.
1874    ///
1875    /// # Returns
1876    ///
1877    /// A tensor with the same shape as the input tensor the indices map back to the original input tensor.
1878    fn float_argsort(
1879        tensor: FloatTensor<B>,
1880        dim: usize,
1881        descending: bool,
1882        out_dtype: IntDType,
1883    ) -> IntTensor<B> {
1884        let device = tensor.device();
1885        argsort::<B, _, _>(tensor, dim, descending, out_dtype, device, |tensor| {
1886            let msg = "Failed to synchronously read tensor data. This operation is not supported until this backend has a GPU sorting implementation.";
1887            try_read_sync(B::float_into_data(tensor))
1888                .expect(msg)
1889                .expect(msg)
1890        })
1891    }
1892
1893    /// Samples tensor as a two-dimensional spatial grid of (possibly multi-channel) values,
1894    /// using the given locations in [-1, 1].
1895    ///
1896    /// # Arguments
1897    ///
1898    /// * `tensor` - The tensor being sampled from, must be contiguous with shape (N, C, H_in, W_in)
1899    /// * `grid` - A tensor of locations, with shape (N, H_out, W_out, 2). Values are [-1, 1].
1900    ///   A [x = -1, y = -1] means top-left, and [x = 1, y = 1] means bottom-right
1901    /// * `options` - Grid sampling options (mode, padding_mode, align_corners)
1902    ///
1903    /// # Returns
1904    ///
1905    /// A tensor with shape (N, C, H_out, W_out)
1906    fn float_grid_sample_2d(
1907        tensor: FloatTensor<B>,
1908        grid: FloatTensor<B>,
1909        options: GridSampleOptions,
1910    ) -> FloatTensor<B> {
1911        // TODO: default impl should get int default dtype
1912        float_grid_sample_2d_ref::<B>(tensor, grid, options)
1913    }
1914
1915    /// Unfold windows along a dimension.
1916    ///
1917    /// Returns a view of the tensor with all complete windows of size `size` in dimension `dim`;
1918    /// where windows are advanced by `step` at each index.
1919    ///
1920    /// The number of windows is `0` when `shape[dim] < size`, and otherwise
1921    /// `(shape[dim] - size) / step + 1`.
1922    ///
1923    /// # Arguments
1924    ///
1925    /// * `tensor` - The input tensor to unfold; of shape ``[pre=..., dim shape, post=...]``
1926    /// * `dim` - the selected dim.
1927    /// * `size` - the size of each unfolded window.
1928    /// * `step` - the step between each window.
1929    ///
1930    /// # Returns
1931    ///
1932    /// A tensor view with shape ``[pre=..., windows, size, post=...]``.
1933    fn float_unfold(tensor: FloatTensor<B>, dim: usize, size: usize, step: usize)
1934    -> FloatTensor<B>;
1935
1936    /// Returns a new tensor with boolean elements indicating whether each element of the input is NaN.
1937    ///
1938    /// # Returns
1939    ///
1940    /// A boolean tensor where `true` indicates NaN and `false` indicates a non-NaN value.
1941    fn float_is_nan(tensor: FloatTensor<B>, out_dtype: BoolDType) -> BoolTensor<B> {
1942        // Check if the input tensor is NaN by comparing it to itself
1943        // NaN is the only value that is not equal to itself
1944        B::float_not_equal(tensor.clone(), tensor, out_dtype)
1945    }
1946
1947    /// Returns a new tensor with boolean elements indicating whether each element of the input is infinite (either +INF or -INF).
1948    ///
1949    /// # Returns
1950    ///
1951    /// A boolean tensor where `true` indicates that the value is infinite
1952    fn float_is_inf(tensor: FloatTensor<B>, out_dtype: BoolDType) -> BoolTensor<B> {
1953        B::float_equal_elem(B::float_abs(tensor), f64::INFINITY.into(), out_dtype)
1954    }
1955
1956    /// Pads a tensor with one `(before, after)` pair per dimension.
1957    fn float_pad(
1958        tensor: FloatTensor<B>,
1959        padding: &[(usize, usize)],
1960        mode: PadMode,
1961    ) -> FloatTensor<B> {
1962        super::pad::float_pad::<B>(tensor, padding, mode)
1963    }
1964}