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