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