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}