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