Skip to main content

burn_tensor/tensor/api/
float.rs

1use crate::AsIndex;
2use crate::Cast;
3use crate::Device;
4use crate::Tensor;
5use crate::cast::ToElement;
6use crate::check;
7use crate::check::TensorCheck;
8use crate::check::unwrap_dim_index;
9use crate::kind::FloatMath;
10use crate::ops::{BridgeKind, BridgeTensor};
11use crate::quantization::{QuantScheme, QuantizationParameters};
12use crate::tensor::stats;
13use crate::tensor::{Distribution, TensorData};
14use crate::{Bool, Float, Int, TensorPrimitive};
15#[cfg(feature = "std")]
16use burn_backend::AutodiffBackend;
17use burn_backend::ElementConversion;
18use burn_backend::Scalar;
19use burn_backend::TensorMetadata;
20#[cfg(feature = "std")]
21use burn_backend::distributed::DistributedParamId;
22use burn_backend::ops::ActivationOps;
23use burn_backend::ops::FloatTensorOps;
24use burn_backend::ops::GridSampleOptions;
25use burn_backend::ops::QTensorOps;
26use burn_backend::quantization::QuantizationParametersPrimitive;
27use burn_dispatch::Dispatch;
28use core::f32;
29
30/// Default RTOL value for `is_close` and `all_close`.
31pub const DEFAULT_RTOL: f64 = 1e-5;
32
33/// Default ATOL value for `is_close` and `all_close`.
34pub const DEFAULT_ATOL: f64 = 1e-8;
35
36impl<const D: usize> Tensor<D> {
37    /// Applies the [error function](https://en.wikipedia.org/wiki/Error_function) element wise.
38    ///
39    #[cfg_attr(
40        doc,
41        doc = r#"
42$y_i = \text{erf}\(x_i\)$
43
44The error function is defined as:
45
46$$\text{erf}\(x\) = \frac{2}{\sqrt{\pi}} \int_0^x e^{-t^2} dt$$
47"#
48    )]
49    #[cfg_attr(not(doc), doc = "`y_i = erf(x_i)`")]
50    pub fn erf(self) -> Self {
51        Self::new(erf_impl(self.primitive))
52    }
53
54    /// Applies [hypotenuse operation](https://en.wikipedia.org/wiki/Hypotenuse) element wise.
55    ///
56    #[cfg_attr(doc, doc = r#"$y_i = \sqrt{x_i^2 + y_i^2}$"#)]
57    #[cfg_attr(not(doc), doc = "`y_i = sqrt(x_i^2 + y_i^2)`")]
58    pub fn hypot(self, other: Self) -> Self {
59        Self::new(hypot_impl(self.primitive, other.primitive))
60    }
61
62    /// Applies [reciprocal operation](https://en.wikipedia.org/wiki/Multiplicative_inverse)
63    /// (or multiplicative inverse) element wise.
64    ///
65    #[cfg_attr(doc, doc = r#"$y_i = \frac{1}{x_i}$"#)]
66    #[cfg_attr(not(doc), doc = "`y_i = 1/x_i`")]
67    pub fn recip(self) -> Self {
68        Self::new(recip_impl(self.primitive))
69    }
70
71    /// Converts each of the elements of the input tensor from angles in degrees to radians.
72    ///
73    /// # Example
74    /// ```ignore
75    /// let tensor_in_radians = tensor.deg2rad();
76    /// ```
77    pub fn deg2rad(self) -> Self {
78        self.mul_scalar(f32::consts::PI / 180.0)
79    }
80
81    /// Converts each of the elements of the input tensor from angles in radians to degrees.
82    ///
83    /// # Example
84    /// ```ignore
85    /// let tensor_in_degrees = tensor.rad2deg();
86    /// ```
87    pub fn rad2deg(self) -> Self {
88        self.mul_scalar(180.0 / f32::consts::PI)
89    }
90
91    /// Applies element wise round operation.
92    ///
93    /// This function implements the [round half to even](https://en.wikipedia.org/wiki/Rounding#Rounding_half_to_even)
94    /// strategy, with halfway cases rounded to the nearest even integer value.
95    pub fn round(self) -> Self {
96        Self::new(round_impl(self.primitive))
97    }
98
99    /// Applies element wise floor operation.
100    pub fn floor(self) -> Self {
101        Self::new(floor_impl(self.primitive))
102    }
103
104    /// Applies element wise ceil operation.
105    pub fn ceil(self) -> Self {
106        Self::new(ceil_impl(self.primitive))
107    }
108
109    /// Create a tensor from floats (f32) on a given device.
110    ///
111    /// # Example
112    ///
113    /// ```rust
114    /// use burn_tensor::Tensor;
115    ///
116    /// fn example() {
117    ///     let device = Default::default();
118    ///     let _ = Tensor::<1>::from_floats([1.0, 2.0], &device);
119    ///     let _ = Tensor::<2>::from_floats([[1.0, 2.0], [3.0, 4.0]], &device);
120    /// }
121    /// ```
122    pub fn from_floats<A: Into<TensorData>>(floats: A, device: &Device) -> Self {
123        Self::from_data(floats.into().convert::<f32>(), device)
124    }
125
126    /// Returns a new tensor with the same shape and device as the current tensor and the data
127    /// cast to Integer.
128    ///
129    /// # Example
130    ///
131    /// ```rust
132    /// use burn_tensor::Tensor;
133    ///
134    /// fn example() {
135    ///     let device = Default::default();
136    ///     let float_tensor = Tensor::<1>::from_floats([1.0, 2.0], &device);
137    ///     let int_tensor = float_tensor.int();
138    /// }
139    /// ```
140    pub fn int(self) -> Tensor<D, Int> {
141        let device = self.device();
142        Tensor::new(int_impl(self.primitive, device))
143    }
144
145    /// Returns a new tensor with the same shape, dtype, and device as the current tensor filled random
146    /// values sampled from the given distribution.
147    pub fn random_like(&self, distribution: Distribution) -> Self {
148        Self::new(random_like_impl(&self.primitive, distribution))
149    }
150
151    /// Calculate the variance along the given dimension.
152    ///
153    /// Negative dimensions are supported and count from the end.
154    pub fn var<I: AsIndex>(self, dim: I) -> Self {
155        let dim = unwrap_dim_index(dim.try_dim_index(D), "Var");
156        stats::var(self, dim)
157    }
158
159    /// Calculate the variance along the given dimension without applying the Bessel’s correction.
160    ///
161    /// Negative dimensions are supported and count from the end.
162    pub fn var_bias<I: AsIndex>(self, dim: I) -> Self {
163        let dim = unwrap_dim_index(dim.try_dim_index(D), "Var Bias");
164        stats::var_bias(self, dim)
165    }
166
167    /// Calculate the variance along the given dimension and also returns the mean.
168    ///
169    /// Negative dimensions are supported and count from the end.
170    pub fn var_mean<I: AsIndex>(self, dim: I) -> (Self, Self) {
171        let dim = unwrap_dim_index(dim.try_dim_index(D), "Var Mean");
172        let mean = self.clone().mean_dim(dim);
173        let var = stats::var_with_mean(self, mean.clone(), dim);
174        (var, mean)
175    }
176
177    /// Calculate the variance along the given dimension without applying the Bessel’s correction and also returns the mean.
178    ///
179    /// Negative dimensions are supported and count from the end.
180    pub fn var_mean_bias<I: AsIndex>(self, dim: I) -> (Self, Self) {
181        let dim = unwrap_dim_index(dim.try_dim_index(D), "Var Mean Bias");
182        let mean = self.clone().mean_dim(dim);
183        let var = stats::var_with_mean_bias(self, mean.clone(), dim);
184        (var, mean)
185    }
186
187    /// Returns the median value along the specified dimension.
188    ///
189    /// The median is not unique for input tensors with an even number of elements
190    /// in the reduced dimension. In this case, the lower of the two medians is returned,
191    /// following PyTorch's behavior.
192    ///
193    /// # Note
194    ///
195    /// The current implementation performs a full sort along the specified dimension,
196    /// which has O(nlog(n)) complexity. Additionally, most backends currently fall back
197    /// to CPU for the sort operation, which may result in slower performance compared
198    /// to native GPU operations.
199    ///
200    /// # Arguments
201    ///
202    /// - `dim` - The dimension along which to compute the median.
203    ///   Negative dimensions are supported and count from the end.
204    ///
205    /// # Returns
206    ///
207    /// - A tensor containing the median values along the specified dimension.
208    ///
209    /// # Example 1
210    ///
211    /// ```ignore
212    /// let device = Default::default();
213    /// let tensor = Tensor::<2>::from_data(
214    ///     [[1.0, 5.0, 3.0, 2.0], [8.0, 4.0, 6.0, 7.0]],
215    ///     &device,
216    /// );
217    ///
218    /// // Median along dimension 0:
219    /// // sorted columns are [1.0, 8.0], [4.0, 5.0], [3.0, 6.0], [2.0, 7.0]
220    /// let median = tensor.median(0);
221    /// // Result: [[1.0, 4.0, 3.0, 2.0]]
222    ///
223    /// // Median along dimension 1:
224    /// // sorted rows are [1.0, 2.0, 3.0, 5.0] and [4.0, 6.0, 7.0, 8.0]
225    /// let median = tensor.median(1);
226    /// // Result: [[2.0], [6.0]]
227    /// ```
228    ///
229    /// # Example 2
230    ///
231    /// The median across all elements can be calculated as follows:
232    ///
233    /// ```ignore
234    /// // D is the number of dimensions of the tensor
235    /// let flattened_tensor: Tensor<1> = tensor.flatten(0, D - 1);
236    ///
237    /// // Calculate median for dim 0 since the tensor has become 1 dimensional
238    /// let median = flattened_tensor.median(0);
239    /// // Result: [4.0]
240    /// ```
241    pub fn median<I: AsIndex>(self, dim: I) -> Self {
242        let dim = unwrap_dim_index(dim.try_dim_index(D), "Median");
243        // TODO: Allow backend specialization. Optimally, implement a median kernel for cubecl
244        // instead of leveraging a full sort to get the median.
245        stats::median(self, dim)
246    }
247
248    /// Returns the median value along the specified dimension and its index.
249    ///
250    /// The median is not unique for input tensors with an even number of elements
251    /// in the reduced dimension. In this case, the lower of the two medians is returned,
252    /// following PyTorch's behavior.
253    ///
254    /// # Note
255    ///
256    /// The current implementation performs a full sort along the specified dimension,
257    /// which has O(nlog(n)) complexity. Additionally, most backends currently fall back
258    /// to CPU for the sort operation, which may result in slower performance compared
259    /// to native GPU operations.
260    ///
261    /// # Arguments
262    ///
263    /// - `dim` - The dimension along which to compute the median.
264    ///   Negative dimensions are supported and count from the end.
265    ///
266    /// # Returns
267    ///
268    /// A tuple containing:
269    /// - A tensor with the median values.
270    /// - A tensor with the indices of the median values in the original tensor.
271    ///
272    /// # Example
273    ///
274    /// ```ignore
275    /// let device = Default::default();
276    /// let tensor = Tensor::<2>::from_data(
277    ///     [[1.0, 5.0, 3.0, 2.0], [8.0, 4.0, 6.0, 7.0]],
278    ///     &device,
279    /// );
280    ///
281    /// // Median along dimension 1:
282    /// // sorted rows are [1.0, 2.0, 3.0, 5.0] and [4.0, 6.0, 7.0, 8.0]
283    /// let (values, indices) = tensor.median_with_indices(1);
284    /// // values: [[2.0], [6.0]], indices: [[3], [2]] (position in the original tensor)
285    /// ```
286    pub fn median_with_indices<I: AsIndex>(self, dim: I) -> (Self, Tensor<D, Int>) {
287        let dim = unwrap_dim_index(dim.try_dim_index(D), "Median With Indices");
288        // TODO: Allow backend specialization. Optimally, implement a median kernel for cubecl
289        // instead of leveraging a full sort to get the median.
290        stats::median_with_indices(self, dim)
291    }
292
293    /// Converts a tensor to the specified data type.
294    ///
295    /// Supports both within-kind casting (e.g., `FloatDType::F64`) and cross-kind casting
296    /// (e.g., `IntDType::I64` to produce an int tensor).
297    ///
298    /// This is a no-op when casting to the current dtype within the same kind.
299    ///
300    /// # Example
301    ///
302    /// ```rust
303    /// use burn_tensor::{Tensor, FloatDType, IntDType};
304    ///
305    /// fn example() {
306    ///     let device = Default::default();
307    ///     let float_tensor = Tensor::<1>::from_floats([1.0, 2.5], &device);
308    ///
309    ///     // Within-kind cast (float to float)
310    ///     let f64_tensor = float_tensor.clone().cast(FloatDType::F64);
311    ///
312    ///     // Cross-kind cast (float to int)
313    ///     let int_tensor = float_tensor.cast(IntDType::I64);
314    /// }
315    /// ```
316    #[must_use]
317    pub fn cast<T: Cast<D, Float>>(self, dtype: T) -> Tensor<D, T::OutputKind> {
318        T::cast(self, dtype)
319    }
320
321    /// Detach the current tensor from the autodiff graph.
322    ///
323    /// This function does nothing when autodiff is not enabled.
324    /// This can be used in batchers or elsewhere to ensure that previous operations are not
325    /// considered in the autodiff graph.
326    pub fn detach(self) -> Self {
327        Self::new(detach_impl(self.primitive))
328    }
329
330    /// Mark the tensor to keep gradients during the backward pass.
331    ///
332    /// This function does nothing when autodiff is not enabled.
333    pub fn require_grad(self) -> Self {
334        self.set_require_grad(true)
335    }
336
337    /// Returns true if the tensor requires gradients during the backward pass.
338    pub fn is_require_grad(&self) -> bool {
339        is_require_grad_impl(&self.primitive)
340    }
341
342    /// Mark the tensor as tracked or untracked depending on the require_grad argument.
343    /// When tracked, the gradients will be available after the backward pass.
344    ///
345    /// This function does nothing when autodiff is not enabled.
346    pub fn set_require_grad(self, require_grad: bool) -> Self {
347        Self::new(set_require_grad_impl(self.primitive, require_grad))
348    }
349
350    /// Applies the relu function to the tensor.
351    pub(crate) fn relu(self) -> Self {
352        Self::new(relu_impl(self.primitive))
353    }
354
355    /// Calculate covaraince matrix between different entries alongside a given dimension.
356    ///
357    /// # Arguments
358    ///
359    /// * `dim` - The dimension along which to calculate the covariance.
360    ///   Negative dimensions are supported and count from the end.
361    /// * `correction_factor` - Is usually 1 for samples and 0 for population.
362    pub fn cov<I: AsIndex>(self, dim: I, correction_factor: usize) -> Tensor<D> {
363        let dim = unwrap_dim_index(dim.try_dim_index(D), "Cov");
364        let n = self.dims()[dim];
365        let centered = (self.clone() - self.mean_dim(dim)).swap_dims(dim, 0);
366        centered
367            .clone()
368            .transpose()
369            .matmul(centered)
370            .div_scalar(n as f32 - correction_factor as f32)
371    }
372
373    /// Convert the tensor to a lower precision data type based on the quantization scheme.
374    ///
375    /// # Arguments
376    ///
377    /// * `scheme` - The quantization scheme.
378    /// * `qparams` - The pre-computed quantization parameters.
379    ///
380    /// # Returns
381    ///
382    /// The quantized tensor.
383    pub fn quantize(self, scheme: &QuantScheme, qparams: QuantizationParameters) -> Tensor<D> {
384        Tensor::new(quantize_impl(
385            self.primitive,
386            scheme,
387            qparams.scales.primitive,
388        ))
389    }
390
391    /// Dynamically convert the tensor to a lower precision data type based on the quantization scheme.
392    ///
393    /// # Arguments
394    ///
395    /// * `scheme` - The quantization scheme.
396    ///
397    /// # Returns
398    ///
399    /// The quantized tensor.
400    ///
401    /// # Notes
402    /// This uses [min-max calibration](crate::quantization::Calibration::MinMax).
403    pub fn quantize_dynamic(self, scheme: &QuantScheme) -> Tensor<D> {
404        Tensor::new(quantize_dynamic_impl(self.primitive, scheme))
405    }
406
407    /// Convert the tensor back to a higher precision data type.
408    ///
409    /// If the tensor is not quantized, its value is simply returned.
410    ///
411    /// # Returns
412    ///
413    /// The dequantized tensor.
414    pub fn dequantize(self) -> Tensor<D> {
415        Tensor::new(dequantize_impl(self.primitive))
416    }
417
418    /// Checks element wise if the tensor is close to another tensor.
419    ///
420    /// The tolerance is defined by the following equation:
421    ///
422    /// ```text
423    /// abs(a - b) <= (atol + rtol * abs(b))
424    ///
425    /// where `a` is the first tensor, `b` is the second tensor, `rtol` is the relative tolerance,
426    /// and `atol` is the absolute tolerance.
427    /// ```
428    ///
429    /// # Arguments
430    ///
431    /// * `other` - The tensor to compare with.
432    /// * `rtol` - Optional relative tolerance. Default is 1e-5; see `DEFAULT_RTOL`.
433    /// * `atol` - Optional absolute tolerance. Default is 1e-8; see `DEFAULT_ATOL`.
434    ///
435    /// # Returns
436    ///
437    /// A boolean tensor with the same shape as the input tensors.
438    ///
439    /// # Example
440    ///
441    /// ```rust
442    /// use burn_tensor::{Tensor, Shape};
443    ///
444    /// fn example() {
445    ///    let device = Default::default();
446    ///    let tensor1 = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
447    ///    let tensor2 = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
448    ///    let tensor = tensor1.is_close(tensor2, None, None);
449    ///    println!("{tensor}");
450    ///    // [[true, true, true], [true, true, true]]
451    /// }
452    /// ```
453    pub fn is_close(self, other: Self, rtol: Option<f64>, atol: Option<f64>) -> Tensor<D, Bool> {
454        let rtol = rtol.unwrap_or(DEFAULT_RTOL);
455        let atol = atol.unwrap_or(DEFAULT_ATOL);
456
457        // check finite difference is close
458        let is_close_finite_val = self
459            .clone()
460            .sub(other.clone())
461            .abs()
462            .lower_equal(other.clone().abs().mul_scalar(rtol).add_scalar(atol))
463            .bool_and(self.clone().is_finite())
464            .bool_and(other.clone().is_finite());
465
466        // check if both are infinite and have same sign
467        let inf_same_sign = self
468            .clone()
469            .is_finite()
470            .bool_not()
471            .bool_and(other.clone().is_finite().bool_not())
472            .bool_and(self.equal(other));
473
474        is_close_finite_val.bool_or(inf_same_sign)
475    }
476
477    /// Checks if all elements are close to another tensor.
478    ///
479    /// The tolerance is defined by the following equation:
480    ///
481    /// ```text
482    ///
483    /// abs(a - b) <= (atol + rtol * abs(b))
484    ///
485    /// where `a` is the first tensor, `b` is the second tensor, `rtol` is the relative tolerance,
486    /// and `atol` is the absolute tolerance.
487    ///
488    /// ```
489    ///
490    /// # Arguments
491    ///
492    /// * `other` - The tensor to compare with.
493    /// * `rtol` - Optional relative tolerance. Default is 1e-5; see `DEFAULT_RTOL`.
494    /// * `atol` - Optional absolute tolerance. Default is 1e-8; see `DEFAULT_ATOL`.
495    ///
496    /// # Returns
497    ///
498    /// A boolean scalar.
499    ///
500    /// # Remarks
501    ///
502    /// # Example
503    ///
504    /// ```rust
505    /// use burn_tensor::{Tensor, Shape};
506    ///
507    /// fn example() {
508    ///    let device = Default::default();
509    ///    let tensor1 = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
510    ///    let tensor2 = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
511    ///    let result = tensor1.all_close(tensor2, None, None);
512    ///    println!("{}", result);
513    ///    // true
514    /// }
515    /// ```
516    pub fn all_close(self, other: Self, rtol: Option<f64>, atol: Option<f64>) -> bool {
517        self.is_close(other, rtol, atol)
518            .all()
519            .into_scalar::<u8>()
520            .to_bool()
521    }
522
523    /// Returns a new tensor with boolean elements indicating whether each element of the input is NaN.
524    ///
525    /// # Returns
526    ///
527    /// A boolean tensor where `true` indicates NaN and `false` indicates a non-NaN value.
528    ///
529    /// # Example
530    ///
531    /// ```rust
532    /// use burn_tensor::{Tensor, Bool, Shape};
533    ///
534    /// fn example() {
535    ///    let device = Default::default();
536    ///    let tensor = Tensor::<2>::from_data([[1.0, f64::NAN, 3.0], [5.0, 9.0, 6.0]], &device);
537    ///    let tensor = tensor.is_nan();
538    ///    println!("{tensor}");
539    ///    // [[false, true, false], [false, false, false]]
540    /// }
541    /// ```
542    pub fn is_nan(self) -> Tensor<D, Bool> {
543        Tensor::new(is_nan_impl(self.primitive))
544    }
545
546    /// Checks if the tensor contains any NaN values.
547    ///
548    /// # Returns
549    ///
550    /// A boolean tensor with a single element indicating whether the tensor contains any NaN values.
551    ///
552    /// # Example
553    ///
554    /// ```rust
555    /// use burn_tensor::{Tensor, Bool, Shape};
556    ///
557    /// fn example() {
558    ///   let device = Default::default();
559    ///   let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [f64::NAN, 9.0, 6.0]], &device);
560    ///   let tensor = tensor.contains_nan();
561    ///   println!("{tensor}");
562    ///   // [true]
563    ///   let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
564    ///   let tensor = tensor.contains_nan();
565    ///   println!("{tensor}");
566    ///   // [false]
567    /// }
568    /// ```
569    pub fn contains_nan(self) -> Tensor<1, Bool> {
570        self.is_nan().any()
571    }
572
573    /// Returns a new tensor with boolean elements indicating whether each element of the input is infinite (either +INF or -INF).
574    ///
575    /// # Returns
576    ///
577    /// A boolean tensor where `true` indicates that the value is infinite
578    ///
579    /// # Example
580    ///
581    /// ```rust
582    /// use burn_tensor::{Tensor, Bool, Shape};
583    ///
584    /// fn example() {
585    ///    let device = Default::default();
586    ///    let tensor = Tensor::<2>::from_data([[1.0, f64::INFINITY, 3.0], [f64::NAN, 9.0, 6.0]], &device);
587    ///    let tensor = tensor.is_finite();
588    ///    println!("{tensor}");
589    ///    // [[false, true, false], [false, false, false]]
590    /// }
591    /// ```
592    pub fn is_inf(self) -> Tensor<D, Bool> {
593        Tensor::new(is_inf_impl(self.primitive))
594    }
595
596    /// Returns a new tensor with boolean elements indicating whether each element of the input is finite
597    ///
598    /// # Returns
599    ///
600    /// A boolean tensor where `true` indicates that the value is finite and `false` indicates
601    /// either INF, -INF or NAN
602    ///
603    /// # Example
604    ///
605    /// ```rust
606    /// use burn_tensor::{Tensor, Bool, Shape};
607    ///
608    /// fn example() {
609    ///    let device = Default::default();
610    ///    let tensor = Tensor::<2>::from_data([[1.0, f64::INFINITY, 3.0], [f64::NAN, 9.0, 6.0]], &device);
611    ///    let tensor = tensor.is_finite();
612    ///    println!("{tensor}");
613    ///    // [[true, false, true], [false, true, true]]
614    /// }
615    /// ```
616    pub fn is_finite(self) -> Tensor<D, Bool> {
617        self.clone()
618            .is_nan()
619            .bool_not()
620            .bool_and(self.is_inf().bool_not())
621    }
622
623    /// Samples tensor as a two-dimensional spatial grid of (possibly multi-channel) values,
624    /// using the given locations in [-1, 1].
625    ///
626    /// # Arguments
627    ///
628    /// * `grid` - A tensor of locations, with shape (N, H_out, W_out, 2). Values are [-1, 1].
629    ///   A [x = -1, y = -1] means top-left, and [x = 1, y = 1] means bottom-right
630    /// * `options` - Grid sampling options (mode, padding_mode, align_corners)
631    ///
632    /// # Returns
633    ///
634    /// A tensor with shape (N, C, H_out, W_out)
635    ///
636    /// # Example
637    ///
638    /// ```ignore
639    /// use burn_tensor::ops::{GridSampleOptions, GridSamplePaddingMode, InterpolateMode};
640    ///
641    /// // Default options (bilinear, zeros padding, align_corners=false)
642    /// let output = tensor.grid_sample_2d(grid, GridSampleOptions::default());
643    ///
644    /// // Custom options
645    /// let options = GridSampleOptions::new(InterpolateMode::Bilinear)
646    ///     .with_padding_mode(GridSamplePaddingMode::Border)
647    ///     .with_align_corners(true);
648    /// let output = tensor.grid_sample_2d(grid, options);
649    /// ```
650    pub fn grid_sample_2d(
651        self,
652        grid: Tensor<D>,
653        options: impl Into<GridSampleOptions>,
654    ) -> Tensor<D> {
655        Tensor::new(grid_sample_2d_impl(
656            self.primitive,
657            grid.primitive,
658            options.into(),
659        ))
660    }
661
662    /// Computes the cross product of `self` and another tensor along a given dimension.
663    ///
664    /// Both `self` and `other` **must have size 3** along the specified `dim`,
665    /// because the cross product is only defined in three-dimensional space.
666    ///
667    /// # Arguments
668    ///
669    /// * `other` - The other tensor to take the cross product with.
670    /// * `dim`   - The dimension along which to compute the cross product.
671    ///
672    /// # Returns
673    ///
674    /// A tensor containing the cross product of `self` and `other` along `dim`.
675    pub fn cross<Dim: AsIndex>(self, other: Tensor<D>, dim: Dim) -> Tensor<D> {
676        let dim = unwrap_dim_index(dim.try_dim_index(D), "Cross");
677        check!(TensorCheck::cross(&self, &other, dim));
678        Tensor::new(cross_impl(self.primitive, other.primitive, dim))
679    }
680
681    /// Applies element wise power operation with a float Tensor
682    ///
683    /// # Arguments
684    ///
685    /// * `other` - The tensor to apply the power operation with.
686    ///
687    /// # Example
688    ///
689    /// ```rust
690    /// use burn_tensor::{Tensor, Shape};
691    ///
692    /// fn example() {
693    ///    let device = Default::default();
694    ///    let tensor1 = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
695    ///    let tensor2 = Tensor::<2>::from_data([[2.0, 3.0, 4.0], [1.0, 2.0, 3.0]], &device);
696    ///    let tensor = tensor1.powf(tensor2);
697    ///    println!("{tensor}");
698    ///    // [[1.0, 8.0, 81.0], [5.0, 81.0, 216.0]]
699    /// }
700    /// ```
701    pub fn powf(self, other: Self) -> Self {
702        Tensor::new(powf_impl(self.primitive, other.primitive))
703    }
704
705    /// Applies element wise power operation with a float scalar
706    ///
707    /// # Arguments
708    ///
709    /// * `other` - The scalar to apply the power operation with.
710    ///
711    /// # Example
712    ///
713    /// ```rust
714    /// use burn_tensor::{Tensor, Shape};
715    ///
716    /// fn example() {
717    ///    let device = Default::default();
718    ///    let tensor = Tensor::<2>::from_data([[1.0, -2.0, 3.0], [5.0, 9.0, 6.0]], &device);
719    ///    let tensor = tensor.powf_scalar(2.0);
720    ///    println!("{tensor}");
721    ///    // [[1.0, 4.0, 9.0], [25.0, 81.0, 36.0]]
722    /// }
723    /// ```
724    pub fn powf_scalar<E: ElementConversion>(self, other: E) -> Self {
725        let rhs = Scalar::new(other, &self.dtype());
726        Tensor::new(powf_scalar_impl(self.primitive, rhs))
727    }
728}
729
730impl<const D: usize> Tensor<D> {
731    /// Draws samples from a categorical distribution defined by the last dimension
732    /// of the input tensor.
733    ///
734    /// The last dimension is treated as a (possibly unnormalized) set of weights
735    /// defining a categorical distribution over categories. All leading dimensions
736    /// are treated as batch dimensions. The method returns integer indices of the
737    /// sampled categories.
738    ///
739    /// # Arguments
740    ///
741    /// * `num_samples` - Number of samples to draw per distribution. Must be >= 1.
742    ///
743    /// # Panics
744    ///
745    /// Panics if `num_samples` is 0.
746    ///
747    /// # Note
748    ///
749    /// Distributions with all-zero weights produce undefined (NaN-based) sampling
750    /// results. Callers should ensure each distribution has at least one positive
751    /// weight.
752    ///
753    /// # Returns
754    ///
755    /// An integer tensor with the same shape as the input, except the last dimension
756    /// is replaced by `num_samples`, containing sampled category indices in
757    /// `[0, num_categories)`.
758    ///
759    /// # Example
760    ///
761    /// ```rust
762    /// use burn_tensor::Tensor;
763    ///
764    /// fn example() {
765    ///     let device = Default::default();
766    ///     let probs = Tensor::<2>::from_floats(
767    ///         [[0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],
768    ///         &device,
769    ///     );
770    ///     let samples = probs.categorical(4);
771    ///     // First row always samples index 1, second row always samples index 2
772    ///     println!("{samples}");
773    /// }
774    /// ```
775    pub fn categorical(self, num_samples: usize) -> Tensor<D, Int> {
776        assert!(num_samples > 0, "categorical: num_samples must be >= 1");
777
778        let shape = self.shape();
779        let num_categories = shape[D - 1];
780        let batch_size = (shape.num_elements() / num_categories).max(1);
781        let device = self.device();
782        let dtype = self.dtype();
783
784        // Flatten leading dimensions into a single batch dimension: [batch, categories]
785        let flat: Tensor<2> = self.reshape([batch_size, num_categories]);
786
787        // Normalize weights to probabilities
788        let sum = flat.clone().sum_dim(1); // [batch, 1]
789        let probs = flat / sum;
790
791        // Cumulative sum along categories dimension
792        let cumsum = probs.cumsum(1); // [batch, categories]
793
794        // Uniform random values for each sample
795        let uniform = Tensor::<2>::random(
796            [batch_size, num_samples],
797            Distribution::Uniform(0.0, 1.0),
798            (&device, dtype),
799        ); // [batch, num_samples]
800
801        // Expand dimensions for broadcasting:
802        //   cumsum: [batch, categories, 1]
803        //   uniform: [batch, 1, num_samples]
804        let cumsum_3d: Tensor<3> = cumsum.unsqueeze_dim(2);
805        let uniform_3d: Tensor<3> = uniform.unsqueeze_dim(1);
806
807        // Count categories where cumsum < uniform (inverse CDF)
808        let mask: Tensor<3, Bool> = cumsum_3d.lower(uniform_3d);
809        let indices: Tensor<2, Int> = mask.int().sum_dim(1).squeeze_dim::<2>(1);
810
811        // Clamp to valid range to guard against floating-point imprecision in cumsum
812        let indices = indices.clamp(0, num_categories as i64 - 1);
813
814        // Reshape back to [...leading_dims, num_samples]
815        let mut out_shape = shape;
816        out_shape[D - 1] = num_samples;
817        indices.reshape(out_shape)
818    }
819}
820
821#[cfg(feature = "std")]
822impl<const D: usize> Tensor<D> {
823    /// Returns true if the tensor is marked as distributed.
824    pub fn is_distributed(&self) -> bool {
825        is_distributed_impl(&self.primitive)
826    }
827
828    /// Mark the tensor as distributed.
829    ///
830    /// This function does nothing when autodiff or distributed is not enabled.
831    pub fn set_distributed(self, param_id: DistributedParamId) -> Self {
832        Self::new(set_distributed_impl(self.primitive, param_id))
833    }
834}
835
836impl<const D: usize, K> Tensor<D, K>
837where
838    K: FloatMath,
839{
840    /// Applies element wise square operation.
841    ///
842    #[cfg_attr(doc, doc = r#"$y_i = x_i * x_i$"#)]
843    #[cfg_attr(not(doc), doc = "`y_i = x_i * x_i`")]
844    pub fn square(self) -> Self {
845        Self::new(K::square(self.primitive))
846    }
847
848    /// Applies element wise exponential operation.
849    ///
850    #[cfg_attr(doc, doc = r#"$y_i = e^{x_i}$"#)]
851    #[cfg_attr(not(doc), doc = "`y = e^x`")]
852    pub fn exp(self) -> Self {
853        Self::new(K::exp(self.primitive))
854    }
855
856    /// Applies element wise natural logarithm of one plus the input tensor.
857    ///
858    #[cfg_attr(doc, doc = r#"$y_i = \log_e\(x_i + 1\)$"#)]
859    #[cfg_attr(not(doc), doc = "`y_i = log1p(x_i)`")]
860    pub fn log1p(self) -> Self {
861        Self::new(K::log1p(self.primitive))
862    }
863
864    /// Applies element wise natural log operation *ln*.
865    ///
866    #[cfg_attr(doc, doc = r#"$y_i = \log_e\(x_i\)$"#)]
867    #[cfg_attr(not(doc), doc = "`y_i = log(x_i)`")]
868    pub fn log(self) -> Self {
869        Self::new(K::log(self.primitive))
870    }
871
872    /// Applies element wise square root operation.
873    ///
874    pub fn sqrt(self) -> Self {
875        Tensor::new(K::sqrt(self.primitive))
876    }
877    /// Applies element wise cosine operation.
878    ///
879    #[cfg_attr(doc, doc = r#"$y_i = \cos\(x_i\)$"#)]
880    #[cfg_attr(not(doc), doc = "`y_i = cos(x_i)`")]
881    pub fn cos(self) -> Self {
882        Tensor::new(K::cos(self.primitive))
883    }
884
885    /// Applies element wise sine operation.
886    ///
887    #[cfg_attr(doc, doc = r#"$y_i = \sin\(x_i\)$"#)]
888    #[cfg_attr(not(doc), doc = "`y_i = sin(x_i)`")]
889    pub fn sin(self) -> Self {
890        Tensor::new(K::sin(self.primitive))
891    }
892
893    /// Applies element wise tangent operation.
894    ///
895    #[cfg_attr(doc, doc = r#"$y_i = \tan\(x_i\)$"#)]
896    #[cfg_attr(not(doc), doc = "`y_i = tan(x_i)`")]
897    pub fn tan(self) -> Self {
898        Tensor::new(K::tan(self.primitive))
899    }
900
901    /// Applies element wise hyperbolic cosine operation.
902    ///
903    #[cfg_attr(doc, doc = r#"$y_i = \cosh\(x_i\)$"#)]
904    #[cfg_attr(not(doc), doc = "`y_i = cosh(x_i)`")]
905    ///
906    /// # Example
907    ///
908    /// ```rust
909    /// use burn_tensor::Tensor;
910    ///
911    /// fn example() {
912    ///     let device = Default::default();
913    ///
914    ///     let tensor = Tensor::<1>::from_data([0.0, -1.0, 2.0], &device);
915    ///     println!("{}", tensor.cosh()); // [1.0, 1.5430, 3.7621]
916    /// }
917    /// ```
918    pub fn cosh(self) -> Self {
919        Tensor::new(K::cosh(self.primitive))
920    }
921
922    /// Applies element wise hyperbolic sine operation.
923    ///
924    #[cfg_attr(doc, doc = r#"$y_i = \sinh\(x_i\)$"#)]
925    #[cfg_attr(not(doc), doc = "`y_i = sinh(x_i)`")]
926    ///
927    /// # Example
928    ///
929    /// ```rust
930    /// use burn_tensor::Tensor;
931    ///
932    /// fn example() {
933    ///     let device = Default::default();
934    ///
935    ///     let tensor = Tensor::<1>::from_data([0.0, -1.0, 2.0], &device);
936    ///     println!("{}", tensor.sinh()); // [0.0, -1.1752, 3.6269]
937    /// }
938    /// ```
939    pub fn sinh(self) -> Self {
940        Tensor::new(K::sinh(self.primitive))
941    }
942
943    /// Applies element wise hyperbolic tangent operation.
944    ///
945    #[cfg_attr(doc, doc = r#"$y_i = \tanh\(x_i\)$"#)]
946    #[cfg_attr(not(doc), doc = "`y_i = tanh(x_i)`")]
947    ///
948    /// # Example
949    ///
950    /// ```rust
951    /// use burn_tensor::Tensor;
952    ///
953    /// fn example() {
954    ///     let device = Default::default();
955    ///
956    ///     let tensor = Tensor::<1>::from_data([0.0, -1.0, 2.0], &device);
957    ///     println!("{}", tensor.tanh()); // [0.0, -0.7616, 0.9640]
958    /// }
959    /// ```
960    pub fn tanh(self) -> Self {
961        Tensor::new(K::tanh(self.primitive))
962    }
963
964    /// Applies element wise inverse cosine operation.
965    ///
966    #[cfg_attr(doc, doc = r#"$y_i = \acos\(x_i\)$"#)]
967    #[cfg_attr(not(doc), doc = "`y_i = acos(x_i)`")]
968    ///
969    /// # Example
970    ///
971    /// ```rust
972    /// use burn_tensor::Tensor;
973    ///
974    /// fn example() {
975    ///     let device = Default::default();
976    ///
977    ///     let tensor = Tensor::<1>::from_data([0.0, -1.0, 1.0], &device);
978    ///     println!("{}", tensor.acos()); // [1.5708, 3.1416, 0.0]
979    /// }
980    /// ```
981    pub fn acos(self) -> Self {
982        Tensor::new(K::acos(self.primitive))
983    }
984
985    /// Applies element wise inverse hyperbolic cosine operation.
986    ///
987    #[cfg_attr(doc, doc = r#"$y_i = \acosh\(x_i\)$"#)]
988    #[cfg_attr(not(doc), doc = "`y_i = acosh(x_i)`")]
989    ///
990    /// # Example
991    ///
992    /// ```rust
993    /// use burn_tensor::Tensor;
994    ///
995    /// fn example() {
996    ///     let device = Default::default();
997    ///
998    ///     let tensor = Tensor::<1>::from_data([1.0, 2.0, 3.0], &device);
999    ///     println!("{}", tensor.acosh()); // [0.0000, 1.3170, 1.7627]
1000    /// }
1001    /// ```
1002    pub fn acosh(self) -> Self {
1003        Tensor::new(K::acosh(self.primitive))
1004    }
1005
1006    /// Applies element wise inverse sine operation.
1007    ///
1008    #[cfg_attr(doc, doc = r#"$y_i = \asin\(x_i\)$"#)]
1009    #[cfg_attr(not(doc), doc = "`y_i = asin(x_i)`")]
1010    ///
1011    /// # Example
1012    ///
1013    /// ```rust
1014    /// use burn_tensor::Tensor;
1015    ///
1016    /// fn example() {
1017    ///     let device = Default::default();
1018    ///
1019    ///     let tensor = Tensor::<1>::from_data([0.0, -1.0, 1.0], &device);
1020    ///     println!("{}", tensor.asin()); // [ 0.0000, -1.5708,  1.5708]
1021    /// }
1022    /// ```
1023    pub fn asin(self) -> Self {
1024        Tensor::new(K::asin(self.primitive))
1025    }
1026
1027    /// Applies element wise inverse hyperbolic sine operation.
1028    ///
1029    #[cfg_attr(doc, doc = r#"$y_i = \asinh\(x_i\)$"#)]
1030    #[cfg_attr(not(doc), doc = "`y_i = asinh(x_i)`")]
1031    ///
1032    /// # Example
1033    ///
1034    /// ```rust
1035    /// use burn_tensor::Tensor;
1036    ///
1037    /// fn example() {
1038    ///     let device = Default::default();
1039    ///
1040    ///     let tensor = Tensor::<1>::from_data([0.0, -1.0, 1.0], &device);
1041    ///     println!("{}", tensor.asinh()); // [ 0.0000, -0.8814,  0.8814]
1042    /// }
1043    /// ```
1044    pub fn asinh(self) -> Self {
1045        Tensor::new(K::asinh(self.primitive))
1046    }
1047
1048    /// Applies element wise inverse tangent operation.
1049    ///
1050    #[cfg_attr(doc, doc = r#"$y_i = \atan\(x_i\)$"#)]
1051    #[cfg_attr(not(doc), doc = "`y_i = atan(x_i)`")]
1052    ///
1053    /// # Example
1054    ///
1055    /// ```rust
1056    /// use burn_tensor::Tensor;
1057    ///
1058    /// fn example() {
1059    ///     let device = Default::default();
1060    ///
1061    ///     let tensor = Tensor::<1>::from_data([0.0, -1.0, 2.0], &device);
1062    ///     println!("{}", tensor.atan()); // [ 0.0, -0.7854,  1.1071]
1063    /// }
1064    /// ```
1065    pub fn atan(self) -> Self {
1066        Tensor::new(K::atan(self.primitive))
1067    }
1068
1069    /// Applies element wise inverse hyperbolic tangent operation.
1070    ///
1071    #[cfg_attr(doc, doc = r#"$y_i = \atanh\(x_i\)$"#)]
1072    #[cfg_attr(not(doc), doc = "`y_i = atanh(x_i)`")]
1073    ///
1074    /// # Example
1075    ///
1076    /// ```rust
1077    /// use burn_tensor::Tensor;
1078    ///
1079    /// fn example() {
1080    ///     let device = Default::default();
1081    ///
1082    ///     let tensor = Tensor::<1>::from_data([0.0, -0.5, 0.5], &device);
1083    ///     println!("{}", tensor.atanh()); // [ 0.0, -0.5493,  0.5493]
1084    /// }
1085    /// ```
1086    pub fn atanh(self) -> Self {
1087        Tensor::new(K::atanh(self.primitive))
1088    }
1089
1090    /// Applies element wise inverse tangent operation using the signs of arguments to determine the correct quadrant.
1091    ///
1092    #[cfg_attr(doc, doc = r#"$z_i = \atan2\(y_i, x_i\)$"#)]
1093    #[cfg_attr(not(doc), doc = "`z_i = atan2(y_i, x_i)`")]
1094    ///
1095    /// # Example
1096    ///
1097    /// ```rust
1098    /// use burn_tensor::Tensor;
1099    ///
1100    /// fn example() {
1101    ///     let device = Default::default();
1102    ///
1103    ///     let lhs = Tensor::<1>::from_data([-2.0, 2.0, -2.0], &device);
1104    ///     let rhs = Tensor::<1>::from_data([1.0, -1.0, -1.0], &device);
1105    ///     println!("{}", lhs.atan2(rhs)); // [-1.1071,  2.0344, -2.0344]
1106    /// }
1107    /// ```
1108    pub fn atan2(self, other: Self) -> Self {
1109        Tensor::new(K::atan2(self.primitive, other.primitive))
1110    }
1111}
1112
1113// =========================================================================
1114// Non-generic implementation helpers (outlined from the public generic API).
1115// See the crate-level docs for the rationale behind this pattern.
1116// =========================================================================
1117
1118fn erf_impl(p: BridgeTensor) -> BridgeTensor {
1119    BridgeTensor::float(Dispatch::float_erf(p.into_float()))
1120}
1121
1122fn recip_impl(p: BridgeTensor) -> BridgeTensor {
1123    BridgeTensor::float(Dispatch::float_recip(p.into_float()))
1124}
1125
1126fn hypot_impl(lhs: BridgeTensor, rhs: BridgeTensor) -> BridgeTensor {
1127    BridgeTensor::float(Dispatch::float_hypot(lhs.into_float(), rhs.into_float()))
1128}
1129
1130fn round_impl(p: BridgeTensor) -> BridgeTensor {
1131    BridgeTensor::float(Dispatch::float_round(p.into_float()))
1132}
1133
1134fn floor_impl(p: BridgeTensor) -> BridgeTensor {
1135    BridgeTensor::float(Dispatch::float_floor(p.into_float()))
1136}
1137
1138fn ceil_impl(p: BridgeTensor) -> BridgeTensor {
1139    BridgeTensor::float(Dispatch::float_ceil(p.into_float()))
1140}
1141
1142fn int_impl(p: BridgeTensor, device: Device) -> BridgeTensor {
1143    let out_dtype = device.settings().int_dtype;
1144    BridgeTensor::int(Dispatch::float_into_int(p.into_float(), out_dtype))
1145}
1146
1147fn random_like_impl(p: &BridgeTensor, distribution: Distribution) -> BridgeTensor {
1148    BridgeTensor::float(Dispatch::float_random(
1149        p.shape(),
1150        distribution,
1151        &p.as_dispatch().device(),
1152        p.dtype().into(),
1153    ))
1154}
1155
1156fn detach_impl(p: BridgeTensor) -> BridgeTensor {
1157    BridgeTensor::float(Dispatch::float_detach(p.into_float()))
1158}
1159
1160fn is_require_grad_impl(p: &BridgeTensor) -> bool {
1161    let (kind, tensor) = p.as_parts();
1162    match kind {
1163        BridgeKind::Float => Dispatch::float_is_require_grad(tensor),
1164        BridgeKind::QFloat => Dispatch::q_is_require_grad(tensor),
1165        _ => panic!("Should be Float primitive kind"),
1166    }
1167}
1168
1169fn set_require_grad_impl(p: BridgeTensor, require_grad: bool) -> BridgeTensor {
1170    let (kind, tensor) = p.into_parts();
1171    match kind {
1172        BridgeKind::Float => {
1173            BridgeTensor::float(Dispatch::float_set_require_grad(tensor, require_grad))
1174        }
1175        BridgeKind::QFloat => {
1176            BridgeTensor::qfloat(Dispatch::q_set_require_grad(tensor, require_grad))
1177        }
1178        _ => panic!("Should be Float primitive kind"),
1179    }
1180}
1181
1182fn relu_impl(p: BridgeTensor) -> BridgeTensor {
1183    BridgeTensor::float(Dispatch::relu(p.into_float()))
1184}
1185
1186fn quantize_impl(p: BridgeTensor, scheme: &QuantScheme, scales: BridgeTensor) -> BridgeTensor {
1187    BridgeTensor::qfloat(Dispatch::quantize(
1188        p.into_float(),
1189        scheme,
1190        QuantizationParametersPrimitive {
1191            scales: scales.into_float(),
1192        },
1193    ))
1194}
1195
1196fn quantize_dynamic_impl(p: BridgeTensor, scheme: &QuantScheme) -> BridgeTensor {
1197    BridgeTensor::qfloat(Dispatch::quantize_dynamic(p.into_float(), scheme))
1198}
1199
1200fn dequantize_impl(p: BridgeTensor) -> BridgeTensor {
1201    BridgeTensor::float(p.into_float())
1202}
1203
1204fn is_nan_impl(p: BridgeTensor) -> BridgeTensor {
1205    let bool_dtype = p.device_settings().bool_dtype;
1206    BridgeTensor::bool(Dispatch::float_is_nan(p.into_float(), bool_dtype))
1207}
1208
1209fn is_inf_impl(p: BridgeTensor) -> BridgeTensor {
1210    let bool_dtype = p.device_settings().bool_dtype;
1211    BridgeTensor::bool(Dispatch::float_is_inf(p.into_float(), bool_dtype))
1212}
1213
1214fn grid_sample_2d_impl(
1215    p: BridgeTensor,
1216    grid: BridgeTensor,
1217    options: GridSampleOptions,
1218) -> BridgeTensor {
1219    BridgeTensor::float(Dispatch::float_grid_sample_2d(
1220        p.into_float(),
1221        grid.into_float(),
1222        options,
1223    ))
1224}
1225
1226fn cross_impl(p: BridgeTensor, other: BridgeTensor, dim: usize) -> BridgeTensor {
1227    BridgeTensor::float(Dispatch::float_cross(
1228        p.into_float(),
1229        other.into_float(),
1230        dim,
1231    ))
1232}
1233
1234fn powf_impl(lhs: BridgeTensor, rhs: BridgeTensor) -> BridgeTensor {
1235    let (lkind, lhs) = lhs.into_parts();
1236    let (rkind, rhs) = rhs.into_parts();
1237    match (lkind, rkind) {
1238        (BridgeKind::Float, BridgeKind::Float) => {
1239            BridgeTensor::float(Dispatch::float_powf(lhs, rhs))
1240        }
1241        (BridgeKind::QFloat, BridgeKind::QFloat) => match Dispatch::q_powf(lhs, rhs) {
1242            TensorPrimitive::Float(out) => BridgeTensor::float(out),
1243            TensorPrimitive::QFloat(out) => BridgeTensor::qfloat(out),
1244        },
1245        (BridgeKind::QFloat, BridgeKind::Float) => {
1246            let dtype = rhs.dtype();
1247            BridgeTensor::float(Dispatch::float_powf(
1248                Dispatch::dequantize(lhs, dtype.into()),
1249                rhs,
1250            ))
1251        }
1252        (BridgeKind::Float, BridgeKind::QFloat) => {
1253            let dtype = lhs.dtype();
1254            BridgeTensor::float(Dispatch::float_powf(
1255                lhs,
1256                Dispatch::dequantize(rhs, dtype.into()),
1257            ))
1258        }
1259        _ => panic!("Should be Float primitive kind"),
1260    }
1261}
1262
1263fn powf_scalar_impl(p: BridgeTensor, rhs: Scalar) -> BridgeTensor {
1264    let (kind, lhs) = p.into_parts();
1265    match kind {
1266        BridgeKind::Float => BridgeTensor::float(Dispatch::float_powf_scalar(lhs, rhs)),
1267        BridgeKind::QFloat => match Dispatch::q_powf_scalar(lhs, rhs) {
1268            TensorPrimitive::Float(out) => BridgeTensor::float(out),
1269            TensorPrimitive::QFloat(out) => BridgeTensor::qfloat(out),
1270        },
1271        _ => panic!("Should be Float primitive kind"),
1272    }
1273}
1274
1275#[cfg(feature = "std")]
1276fn is_distributed_impl(p: &BridgeTensor) -> bool {
1277    let (kind, tensor) = p.as_parts();
1278    match kind {
1279        BridgeKind::Float => Dispatch::is_distributed(tensor),
1280        BridgeKind::QFloat => unimplemented!(),
1281        _ => panic!("Should be Float primitive kind"),
1282    }
1283}
1284
1285#[cfg(feature = "std")]
1286fn set_distributed_impl(p: BridgeTensor, param_id: DistributedParamId) -> BridgeTensor {
1287    let (kind, tensor) = p.into_parts();
1288    match kind {
1289        BridgeKind::Float => {
1290            BridgeTensor::float(Dispatch::set_distributed_params(tensor, param_id))
1291        }
1292        BridgeKind::QFloat => unimplemented!(),
1293        _ => panic!("Should be Float primitive kind"),
1294    }
1295}