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