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