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