Skip to main content

burn_backend/backend/ops/
qtensor.rs

1use alloc::vec::Vec;
2use burn_std::{
3    BoolDType, FloatDType, IntDType, Shape, Slice,
4    quantization::{QuantPropagation, QuantScheme},
5};
6
7use crate::{
8    Backend, ExecutionError, TensorData, TensorMetadata, TensorPrimitive,
9    get_or_init_device_settings,
10};
11use crate::{
12    Scalar,
13    quantization::{Calibration, QuantizationParametersPrimitive, compute_q_params, compute_range},
14    tensor::{BoolTensor, Device, FloatTensor, IntTensor, QuantizedTensor},
15};
16
17/// Automatically applies `dequantization -> float operation -> quantization`.
18///
19/// Used for tensor ops that should always return a quantized output.
20#[macro_export]
21macro_rules! dequant_op_quant {
22    // Binary tensor float op w/ lhs & rhs
23    (
24        float_op $float_op:expr, $t1:expr, $t2:expr
25    ) => {{
26        // Heuristic: prioritize lhs scheme
27        let scheme = $t1.scheme().clone();
28
29        let t1_f = Self::dequantize($t1);
30        let t2_f = Self::dequantize($t2);
31        #[allow(clippy::redundant_closure_call)]
32        let out_f = $float_op(t1_f, t2_f);
33
34        Self::quantize_dynamic(out_f, &scheme)
35    }};
36    // Unary tensor float op
37    (
38        float_op $float_op:expr, $tensor:expr
39    ) => {{
40        let scheme = $tensor.scheme().clone();
41        let dtype = get_or_init_device_settings::<B>(&$tensor.device()).float_dtype;
42
43        let tensor_f = Self::dequantize($tensor, dtype);
44        #[allow(clippy::redundant_closure_call)]
45        let out_f = $float_op(tensor_f);
46
47        Self::quantize_dynamic(out_f, &scheme)
48    }};
49}
50
51/// Automatically applies `dequantization -> float operation [-> quantization]`.
52///
53/// The output quantization step is optional.
54/// It is only performed when the input quantization scheme is propagated.
55#[macro_export]
56macro_rules! dequant_op_flow {
57    // Binary tensor float op w/ lhs & rhs
58    (
59        float_op $float_op:expr, $t1:expr, $t2:expr
60    ) => {{
61        // Heuristic: prioritize lhs scheme
62        let scheme = $t1.scheme().clone();
63        let settings = get_or_init_device_settings::<B>(&$t1.device());
64        let dtype = settings.float_dtype;
65        let propagation = settings.quantization.propagation;
66
67        let t1_f = Self::dequantize($t1, dtype);
68        let t2_f = Self::dequantize($t2, dtype);
69        #[allow(clippy::redundant_closure_call)]
70        let out_f = $float_op(t1_f, t2_f);
71
72        match propagation {
73            QuantPropagation::Propagate => {
74                TensorPrimitive::QFloat(Self::quantize_dynamic(out_f, &scheme))
75            }
76            QuantPropagation::Inhibit => TensorPrimitive::Float(out_f),
77        }
78    }};
79    // Unary tensor float op
80    (
81        float_op $float_op:expr, $tensor:expr
82    ) => {{
83        let scheme = $tensor.scheme().clone();
84        let settings = get_or_init_device_settings::<B>(&$tensor.device());
85        let dtype = settings.float_dtype;
86        let propagation = settings.quantization.propagation;
87
88        let tensor_f = Self::dequantize($tensor, dtype);
89        #[allow(clippy::redundant_closure_call)]
90        let out_f = $float_op(tensor_f);
91
92        match propagation {
93            QuantPropagation::Propagate => {
94                TensorPrimitive::QFloat(Self::quantize_dynamic(out_f, &scheme))
95            }
96            QuantPropagation::Inhibit => TensorPrimitive::Float(out_f),
97        }
98    }};
99}
100
101/// Operations on quantized tensors.
102///
103/// # Return Type Semantics
104///
105/// The return type of each operation indicates how quantization is handled:
106///
107/// ## [`QuantizedTensor<B>`]
108/// If the method returns a `QuantizedTensor<B>`, the operation is expected to preserve the quantized
109/// representation. Implementations should avoid dequantizing when possible to maintain performance.
110/// For example, shape or layout changes such as expand or transpose preserve quantization.
111///
112/// *Note: while this currently doesn't affect the quantized tensor parameters (only per-tensor is
113/// supported at the time of writing), other quantization levels (e.g., per-block) may require re-ordering
114/// the quantization parameters to match the new layout.*
115///
116///
117/// ## [`TensorPrimitive<B>`]
118/// If the method returns a `TensorPrimitive<B>` enum, the return type should align with propagation
119/// strategy specified in the quantization scheme. The output should remain quantized ([`TensorPrimitive::QFloat`])
120/// returned in floating-point form ([`TensorPrimitive::Float`]).
121///
122/// This distinction allows for fine-grained control over mixed-precision flows while still operating
123/// through a unified API.
124pub trait QTensorOps<B: Backend> {
125    /// Creates a new tensor from the data structure.
126    ///
127    /// # Arguments
128    ///
129    /// * `data` - The data structure.
130    /// * `device` - The device to create the tensor on.
131    ///
132    /// # Returns
133    ///
134    /// The tensor with the given data.
135    fn q_from_data(data: TensorData, device: &Device<B>) -> QuantizedTensor<B>;
136
137    /// Convert the tensor to a lower precision data type based on the quantization scheme and parameters.
138    fn quantize(
139        tensor: FloatTensor<B>,
140        scheme: &QuantScheme,
141        qparams: QuantizationParametersPrimitive<B>,
142    ) -> QuantizedTensor<B>;
143
144    /// Dynamically convert the tensor to a lower precision data type based on the quantization scheme.
145    fn quantize_dynamic(tensor: FloatTensor<B>, scheme: &QuantScheme) -> QuantizedTensor<B> {
146        // Dynamically compute min/max tensor range and qparams before quantizing
147        let (min, max) = compute_range::<B>(scheme, tensor.clone(), &Calibration::MinMax);
148        let qparams = compute_q_params(scheme, min, max);
149        Self::quantize(tensor, scheme, qparams)
150    }
151
152    /// Convert the tensor back to a higher precision data type.
153    fn dequantize(tensor: QuantizedTensor<B>, dtype: FloatDType) -> FloatTensor<B>;
154
155    /// Moves the tensor to the given device.
156    ///
157    /// # Arguments
158    ///
159    /// * `tensor` - The tensor.
160    /// * `device` - The device to move the tensor to.
161    ///
162    /// # Returns
163    ///
164    /// The tensor on the given device.
165    fn q_to_device(tensor: QuantizedTensor<B>, device: &Device<B>) -> QuantizedTensor<B>;
166
167    /// Reshapes a tensor.
168    ///
169    /// # Arguments
170    ///
171    /// * `tensor` - The tensor to reshape.
172    /// * `shape` - The new shape of the tensor.
173    ///
174    /// # Returns
175    ///
176    /// The tensor with the new shape.
177    fn q_reshape(tensor: QuantizedTensor<B>, shape: Shape) -> QuantizedTensor<B>;
178
179    /// Converts the tensor to a data structure.
180    ///
181    /// # Arguments
182    ///
183    /// * `tensor` - The tensor.
184    ///
185    /// # Returns
186    ///
187    /// The data structure with the tensor's data.
188    fn q_into_data(
189        tensor: QuantizedTensor<B>,
190    ) -> impl Future<Output = Result<TensorData, ExecutionError>> + Send;
191
192    /// Detaches a tensor from the computation graph.
193    fn q_detach(tensor: QuantizedTensor<B>) -> QuantizedTensor<B> {
194        // Should only be overridden by autodiff backends.
195        tensor
196    }
197
198    /// Sets the `require_grad` flag of a tensor.
199    fn q_set_require_grad(tensor: QuantizedTensor<B>, _require_grad: bool) -> QuantizedTensor<B> {
200        // Should only be overridden by autodiff backends.
201        tensor
202    }
203
204    /// Returns the `require_grad` flag of a tensor.
205    fn q_is_require_grad(_tensor: &QuantizedTensor<B>) -> bool {
206        // Should only be overridden by autodiff backends.
207        false
208    }
209
210    /// Broadcasts the `tensor` to the given `shape`.
211    fn q_expand(tensor: QuantizedTensor<B>, shape: Shape) -> QuantizedTensor<B> {
212        // Default implementation. Backends can expand on the quantized values when supported.
213        dequant_op_quant!(float_op | tensor | B::float_expand(tensor, shape), tensor)
214    }
215
216    /// Transposes a tensor.
217    ///
218    /// # Arguments
219    ///
220    /// * `tensor` - The tensor to transpose.
221    ///
222    /// # Returns
223    ///
224    /// The transposed tensor.
225    fn q_transpose(tensor: QuantizedTensor<B>) -> QuantizedTensor<B> {
226        let ndims = tensor.shape().num_dims();
227        Self::q_swap_dims(tensor, ndims - 2, ndims - 1)
228    }
229
230    /// Swaps two dimensions of a tensor.
231    ///
232    /// # Arguments
233    ///
234    /// * `tensor` - The tensor to swap the dimensions of.
235    /// * `dim1` - The first dimension to swap.
236    /// * `dim2` - The second dimension to swap.
237    ///
238    /// # Returns
239    ///
240    /// The tensor with the dimensions swapped.
241    fn q_swap_dims(tensor: QuantizedTensor<B>, dim1: usize, dim2: usize) -> QuantizedTensor<B>;
242
243    /// Permutes the dimensions of a tensor.
244    ///
245    /// # Arguments
246    ///
247    /// * `tensor` - The tensor to permute the dimensions of.
248    /// * `axes` - The new order of the dimensions.
249    /// # Returns
250    ///
251    /// The tensor with the dimensions permuted.
252    fn q_permute(tensor: QuantizedTensor<B>, axes: &[usize]) -> QuantizedTensor<B>;
253
254    /// Reverse the order of elements in a tensor along the given axes.
255    ///
256    /// # Arguments
257    ///
258    /// * `tensor` - The tensor to reverse.
259    /// * `axes` - The axes to reverse.
260    ///
261    /// The tensor with the elements reversed.
262    fn q_flip(tensor: QuantizedTensor<B>, axes: &[usize]) -> QuantizedTensor<B>;
263
264    /// Select tensor elements along the given dimension corresponding for the given indices.
265    ///
266    /// # Arguments
267    ///
268    /// * `tensor` - The tensor to select from.
269    /// * `dim` - The dimension to select from.
270    /// * `indices` - The indices to select.
271    ///
272    /// # Returns
273    ///
274    /// The selected elements.
275    fn q_select(
276        tensor: QuantizedTensor<B>,
277        dim: usize,
278        indices: IntTensor<B>,
279    ) -> QuantizedTensor<B> {
280        // Default implementation. Backends can select on the quantized values when supported.
281        dequant_op_quant!(
282            float_op | tensor | B::float_select(tensor, dim, indices),
283            tensor
284        )
285    }
286
287    /// Select tensor elements corresponding to the given slices.
288    ///
289    /// # Arguments
290    ///
291    /// * `tensor` - The tensor to select from.
292    /// * `slices` - The slices specifying ranges and steps for each dimension.
293    ///
294    /// # Returns
295    ///
296    /// The selected elements in a new tensor.
297    fn q_slice(tensor: QuantizedTensor<B>, slices: &[Slice]) -> QuantizedTensor<B> {
298        // Default implementation. Backends can slice on the quantized values when supported.
299        dequant_op_quant!(float_op | tensor | B::float_slice(tensor, slices), tensor)
300    }
301
302    /// Gather elements from a tensor.
303    ///
304    /// # Arguments
305    ///
306    /// * `dim` - The dimension to gather from.
307    /// * `tensor` - The tensor to gather from.
308    /// * `indices` - The indices to gather.
309    ///
310    /// # Returns
311    ///
312    /// The gathered elements.
313    fn q_gather(
314        dim: usize,
315        tensor: QuantizedTensor<B>,
316        indices: IntTensor<B>,
317    ) -> QuantizedTensor<B> {
318        // Default implementation. Backends can gather on the quantized values when supported.
319        dequant_op_quant!(
320            float_op | tensor | B::float_gather(dim, tensor, indices),
321            tensor
322        )
323    }
324
325    /// Repeat the tensor along the given dimension.
326    ///
327    /// # Arguments
328    ///
329    /// * `tensor` - The tensor.
330    /// * `dim` - The dimension to repeat.
331    /// * `times` - The number of times to repeat the dimension.
332    ///
333    /// # Returns
334    ///
335    /// The tensor with the given dimension repeated.
336    fn q_repeat_dim(tensor: QuantizedTensor<B>, dim: usize, times: usize) -> QuantizedTensor<B> {
337        dequant_op_quant!(
338            float_op | tensor | B::float_repeat_dim(tensor, dim, times),
339            tensor
340        )
341    }
342
343    /// Adds two tensors together.
344    ///
345    /// # Arguments
346    ///
347    /// * `lhs` - The left hand side tensor.
348    /// * `rhs` - The right hand side tensor.
349    ///
350    /// # Returns
351    ///
352    /// The result of adding the two tensors together.
353    fn q_add(lhs: QuantizedTensor<B>, rhs: QuantizedTensor<B>) -> TensorPrimitive<B> {
354        dequant_op_flow!(float_op | lhs, rhs | B::float_add(lhs, rhs), lhs, rhs)
355    }
356
357    /// Adds a scalar to a tensor.
358    ///
359    /// # Arguments
360    ///
361    /// * `lhs` - The left hand side tensor.
362    /// * `rhs` - The right hand side scalar.
363    ///
364    /// # Returns
365    ///
366    /// The result of adding the scalar to the tensor.
367    fn q_add_scalar(lhs: QuantizedTensor<B>, rhs: Scalar) -> TensorPrimitive<B> {
368        dequant_op_flow!(float_op | tensor | B::float_add_scalar(tensor, rhs), lhs)
369    }
370
371    /// Clamps a tensor under a minimum value.
372    ///
373    /// # Arguments
374    ///
375    /// * `tensor` - The tensor to clamp.
376    /// * `min` - The minimum value.
377    ///
378    /// # Returns
379    ///
380    /// The clamped tensor.
381    fn q_clamp_min(tensor: QuantizedTensor<B>, min: Scalar) -> TensorPrimitive<B> {
382        dequant_op_flow!(float_op | tensor | B::float_clamp_min(tensor, min), tensor)
383    }
384
385    /// Clamps a tensor over a maximum value.
386    ///
387    /// # Arguments
388    ///
389    /// * `tensor` - The tensor to clamp.
390    /// * `max` - The maximum value.
391    ///
392    /// # Returns
393    ///
394    /// The clamped tensor.
395    fn q_clamp_max(tensor: QuantizedTensor<B>, max: Scalar) -> TensorPrimitive<B> {
396        dequant_op_flow!(float_op | tensor | B::float_clamp_max(tensor, max), tensor)
397    }
398
399    /// Clamps a tensor between a minimum and maximum value.
400    ///
401    /// # Arguments
402    ///
403    /// * `tensor` - The tensor to clamp.
404    /// * `min` - The minimum value.
405    /// * `max` - The maximum value.
406    ///
407    /// # Returns
408    ///
409    /// The clamped tensor.
410    fn q_clamp(tensor: QuantizedTensor<B>, min: Scalar, max: Scalar) -> TensorPrimitive<B> {
411        dequant_op_flow!(float_op | tensor | B::float_clamp(tensor, min, max), tensor)
412    }
413
414    /// Subtracts two tensors.
415    ///
416    /// # Arguments
417    ///
418    /// * `lhs` - The left hand side tensor.
419    /// * `rhs` - The right hand side tensor.
420    ///
421    /// # Returns
422    ///
423    /// The result of subtracting the two tensors.
424    fn q_sub(lhs: QuantizedTensor<B>, rhs: QuantizedTensor<B>) -> TensorPrimitive<B> {
425        dequant_op_flow!(float_op | lhs, rhs | B::float_sub(lhs, rhs), lhs, rhs)
426    }
427
428    /// Subtracts a scalar from a tensor.
429    ///
430    /// # Arguments
431    ///
432    /// * `lhs` - The left hand side tensor.
433    /// * `rhs` - The right hand side scalar.
434    ///
435    /// # Returns
436    ///
437    /// The result of subtracting the scalar from the tensor.
438    fn q_sub_scalar(lhs: QuantizedTensor<B>, rhs: Scalar) -> TensorPrimitive<B> {
439        dequant_op_flow!(float_op | tensor | B::float_sub_scalar(tensor, rhs), lhs)
440    }
441
442    /// Multiplies two tensors together element-wise.
443    fn q_mul(lhs: QuantizedTensor<B>, rhs: QuantizedTensor<B>) -> TensorPrimitive<B> {
444        dequant_op_flow!(float_op | lhs, rhs | B::float_mul(lhs, rhs), lhs, rhs)
445    }
446
447    /// Multiplies a tensor by a scalar.
448    ///
449    /// # Arguments
450    ///
451    /// * `lhs` - The left hand side tensor.
452    /// * `rhs` - The right hand side scalar.
453    ///
454    /// # Returns
455    ///
456    /// The result of multiplying the tensor by the scalar.
457    fn q_mul_scalar(lhs: QuantizedTensor<B>, rhs: Scalar) -> TensorPrimitive<B> {
458        dequant_op_flow!(float_op | tensor | B::float_mul_scalar(tensor, rhs), lhs)
459    }
460
461    /// Divides two tensors element-wise.
462    ///
463    /// # Arguments
464    ///
465    /// * `lhs` - The left hand side tensor.
466    /// * `rhs` - The right hand side tensor.
467    ///
468    /// # Returns
469    ///
470    /// The result of dividing the two tensors.
471    fn q_div(lhs: QuantizedTensor<B>, rhs: QuantizedTensor<B>) -> TensorPrimitive<B> {
472        dequant_op_flow!(float_op | lhs, rhs | B::float_div(lhs, rhs), lhs, rhs)
473    }
474
475    /// Divides a tensor by a scalar.
476    ///
477    /// # Arguments
478    ///
479    /// * `lhs` - The left hand side tensor.
480    /// * `rhs` - The right hand side scalar.
481    ///
482    /// # Returns
483    ///
484    /// The result of dividing the tensor by the scalar.
485    fn q_div_scalar(lhs: QuantizedTensor<B>, rhs: Scalar) -> TensorPrimitive<B> {
486        dequant_op_flow!(float_op | tensor | B::float_div_scalar(tensor, rhs), lhs)
487    }
488
489    /// Multiplies two tensors together using matrix multiplication.
490    ///
491    /// # Arguments
492    ///
493    /// * `lhs` - The left hand side tensor.
494    /// * `rhs` - The right hand side tensor.
495    ///
496    /// # Returns
497    ///
498    /// The result of multiplying the two tensors together using matrix multiplication.
499    fn q_matmul(lhs: TensorPrimitive<B>, rhs: TensorPrimitive<B>) -> TensorPrimitive<B> {
500        let mut propagation = QuantPropagation::Inhibit;
501        let mut scheme = QuantScheme::default();
502
503        // Pick a target dtype for any dequantization. If either operand is already
504        // a Float tensor, take its dtype so a Float-QFloat (or QFloat-Float) pair
505        // ends up matching after dequantize and `float_matmul` doesn't see a
506        // dtype mismatch. Only when both operands are QFloat do we fall back to
507        // the device default.
508        let target_dtype: Option<FloatDType> = match (&lhs, &rhs) {
509            (TensorPrimitive::Float(t), _) | (_, TensorPrimitive::Float(t)) => {
510                Some(t.dtype().into())
511            }
512            _ => None,
513        };
514
515        let lhs = match lhs {
516            TensorPrimitive::Float(lhs) => lhs,
517            TensorPrimitive::QFloat(lhs) => {
518                let settings = get_or_init_device_settings::<B>(&lhs.device());
519                propagation = settings.quantization.propagation;
520                scheme = lhs.scheme();
521                let float_dtype = target_dtype.unwrap_or(settings.float_dtype);
522
523                Self::dequantize(lhs, float_dtype)
524            }
525        };
526        let rhs = match rhs {
527            TensorPrimitive::Float(rhs) => rhs,
528            TensorPrimitive::QFloat(rhs) => {
529                let settings = get_or_init_device_settings::<B>(&rhs.device());
530                propagation = settings.quantization.propagation;
531                scheme = rhs.scheme();
532                let float_dtype = target_dtype.unwrap_or(settings.float_dtype);
533
534                Self::dequantize(rhs, float_dtype)
535            }
536        };
537
538        let out_f = B::float_matmul(lhs, rhs);
539        match propagation {
540            QuantPropagation::Propagate => {
541                TensorPrimitive::QFloat(<Self>::quantize_dynamic(out_f, &scheme))
542            }
543            QuantPropagation::Inhibit => TensorPrimitive::Float(out_f),
544        }
545    }
546
547    /// Negates a tensor element-wise.
548    fn q_neg(tensor: QuantizedTensor<B>) -> TensorPrimitive<B> {
549        dequant_op_flow!(float_op | tensor | B::float_neg(tensor), tensor)
550    }
551
552    /// Calculates the reciprocals element-wise
553    fn q_recip(tensor: QuantizedTensor<B>) -> TensorPrimitive<B> {
554        dequant_op_flow!(float_op | tensor | B::float_recip(tensor), tensor)
555    }
556
557    /// Sum of all elements in a tensor.
558    ///
559    /// # Arguments
560    ///
561    /// * `tensor` - The tensor to sum.
562    ///
563    /// # Returns
564    ///
565    /// A scalar tensor with the sum of all elements in `tensor`.
566    fn q_sum(tensor: QuantizedTensor<B>) -> TensorPrimitive<B> {
567        dequant_op_flow!(float_op | tensor | B::float_sum(tensor), tensor)
568    }
569
570    /// Sum of all elements in a tensor along a dimension.
571    ///
572    /// # Arguments
573    ///
574    /// * `tensor` - The tensor to sum.
575    /// * `dim` - The dimension along which to sum.
576    ///
577    /// # Returns
578    ///
579    /// A tensor with the sum of all elements in `tensor` along `dim`.
580    fn q_sum_dim(tensor: QuantizedTensor<B>, dim: usize) -> TensorPrimitive<B> {
581        dequant_op_flow!(float_op | tensor | B::float_sum_dim(tensor, dim), tensor)
582    }
583
584    /// Product of all elements in a tensor.
585    ///
586    /// # Arguments
587    ///
588    /// * `tensor` - The tensor to product.
589    ///
590    /// # Returns
591    ///
592    /// A scalar tensor with the product of all elements in `tensor`.
593    fn q_prod(tensor: QuantizedTensor<B>) -> TensorPrimitive<B> {
594        dequant_op_flow!(float_op | tensor | B::float_prod(tensor), tensor)
595    }
596
597    /// Product of all elements in a tensor along a dimension.
598    ///
599    /// # Arguments
600    ///
601    /// * `tensor` - The tensor to product.
602    ///
603    /// # Returns
604    ///
605    /// A tensor with the product of all elements in `tensor` along `dim`.
606    fn q_prod_dim(tensor: QuantizedTensor<B>, dim: usize) -> TensorPrimitive<B> {
607        dequant_op_flow!(float_op | tensor | B::float_prod_dim(tensor, dim), tensor)
608    }
609
610    /// Mean of all elements in a tensor.
611    ///
612    /// # Arguments
613    ///
614    /// * `tensor` - The tensor to mean.
615    ///
616    /// # Returns
617    ///
618    /// A scalar tensor with the mean of all elements in `tensor`.
619    fn q_mean(tensor: QuantizedTensor<B>) -> TensorPrimitive<B> {
620        dequant_op_flow!(float_op | tensor | B::float_mean(tensor), tensor)
621    }
622
623    /// Mean of all elements in a tensor along a dimension.
624    ///
625    /// # Arguments
626    ///
627    /// * `tensor` - The tensor to mean.
628    /// * `dim` - The dimension along which to mean.
629    ///
630    /// # Returns
631    ///
632    /// A tensor with the mean of all elements in `tensor` along `dim`.
633    fn q_mean_dim(tensor: QuantizedTensor<B>, dim: usize) -> TensorPrimitive<B> {
634        dequant_op_flow!(float_op | tensor | B::float_mean_dim(tensor, dim), tensor)
635    }
636
637    /// Computes the cumulative sum of elements along a dimension.
638    ///
639    /// # Arguments
640    ///
641    /// * `tensor` - The tensor to compute the cumulative sum of.
642    /// * `dim` - The dimension along which to compute the cumulative sum.
643    ///
644    /// # Returns
645    ///
646    /// A tensor with the same shape where each element is the cumulative sum
647    /// of all elements up to and including that position along the dimension.
648    fn q_cumsum(tensor: QuantizedTensor<B>, dim: usize) -> TensorPrimitive<B> {
649        dequant_op_flow!(float_op | tensor | B::float_cumsum(tensor, dim), tensor)
650    }
651
652    /// Computes the cumulative product of elements along a dimension.
653    ///
654    /// # Arguments
655    ///
656    /// * `tensor` - The tensor to compute the cumulative product of.
657    /// * `dim` - The dimension along which to compute the cumulative product.
658    ///
659    /// # Returns
660    ///
661    /// A tensor with the same shape where each element is the cumulative product
662    /// of all elements up to and including that position along the dimension.
663    fn q_cumprod(tensor: QuantizedTensor<B>, dim: usize) -> TensorPrimitive<B> {
664        dequant_op_flow!(float_op | tensor | B::float_cumprod(tensor, dim), tensor)
665    }
666
667    /// Computes the cumulative minimum of elements along a dimension.
668    ///
669    /// # Arguments
670    ///
671    /// * `tensor` - The tensor to compute the cumulative minimum of.
672    /// * `dim` - The dimension along which to compute the cumulative minimum.
673    ///
674    /// # Returns
675    ///
676    /// A tensor with the same shape where each element is the minimum
677    /// of all elements up to and including that position along the dimension.
678    fn q_cummin(tensor: QuantizedTensor<B>, dim: usize) -> TensorPrimitive<B> {
679        dequant_op_flow!(float_op | tensor | B::float_cummin(tensor, dim), tensor)
680    }
681
682    /// Computes the cumulative maximum of elements along a dimension.
683    ///
684    /// # Arguments
685    ///
686    /// * `tensor` - The tensor to compute the cumulative maximum of.
687    /// * `dim` - The dimension along which to compute the cumulative maximum.
688    ///
689    /// # Returns
690    ///
691    /// A tensor with the same shape where each element is the maximum
692    /// of all elements up to and including that position along the dimension.
693    fn q_cummax(tensor: QuantizedTensor<B>, dim: usize) -> TensorPrimitive<B> {
694        dequant_op_flow!(float_op | tensor | B::float_cummax(tensor, dim), tensor)
695    }
696
697    /// Returns a new tensor with exponential values.
698    ///
699    /// # Arguments
700    ///
701    /// * `tensor` - The tensor to exponentiate.
702    ///
703    /// # Returns
704    ///
705    /// A tensor with the same shape as `tensor` with exponential values.
706    fn q_exp(tensor: QuantizedTensor<B>) -> TensorPrimitive<B> {
707        dequant_op_flow!(float_op | tensor | B::float_exp(tensor), tensor)
708    }
709
710    /// Returns a new tensor with natural logarithm values.
711    ///
712    /// # Arguments
713    ///
714    /// * `tensor` - The tensor to take the logarithm of.
715    ///
716    /// # Returns
717    ///
718    /// A tensor with the same shape as `tensor` with natural logarithm values.
719    fn q_log(tensor: QuantizedTensor<B>) -> TensorPrimitive<B> {
720        dequant_op_flow!(float_op | tensor | B::float_log(tensor), tensor)
721    }
722
723    /// Returns a new tensor with logarithm values of (1 + Xi).
724    ///
725    /// # Arguments
726    ///
727    /// * `tensor` - The tensor to take the logarithm of.
728    ///
729    /// # Returns
730    ///
731    /// A tensor with the same shape as `tensor` with logarithm values of (1 + Xi).
732    fn q_log1p(tensor: QuantizedTensor<B>) -> TensorPrimitive<B> {
733        dequant_op_flow!(float_op | tensor | B::float_log1p(tensor), tensor)
734    }
735
736    /// Element-wise power with another tensor.
737    ///
738    /// # Arguments
739    ///
740    /// * `lhs` - The left hand side tensor.
741    /// * `rhs` - The right hand side tensor.
742    ///
743    /// # Returns
744    ///
745    /// The elements of `lhs` raised to the power of the elements of `rhs`.
746    fn q_powf(lhs: QuantizedTensor<B>, rhs: QuantizedTensor<B>) -> TensorPrimitive<B> {
747        dequant_op_flow!(float_op | lhs, rhs | B::float_powf(lhs, rhs), lhs, rhs)
748    }
749
750    /// Element-wise power with an IntTensor.
751    ///
752    /// # Arguments
753    ///
754    /// * `lhs` - The left hand side tensor.
755    /// * `rhs` - The right hand side floatTensor.
756    ///
757    /// # Returns
758    ///
759    /// The elements of `lhs` raised to the value of `rhs`. Result is an IntTensor.
760    fn q_powi(lhs: QuantizedTensor<B>, rhs: IntTensor<B>) -> TensorPrimitive<B> {
761        dequant_op_flow!(float_op | tensor | B::float_powi(tensor, rhs), lhs)
762    }
763
764    /// Element-wise power with an int scalar.
765    ///
766    /// # Arguments
767    ///
768    /// * `lhs` - The left hand side tensor.
769    /// * `rhs` - The right hand side scalar.
770    ///
771    /// # Returns
772    ///
773    /// The elements of `lhs` raised to the value of `rhs`.
774    fn q_powi_scalar(lhs: QuantizedTensor<B>, rhs: Scalar) -> TensorPrimitive<B> {
775        dequant_op_flow!(float_op | tensor | B::float_powi_scalar(tensor, rhs), lhs)
776    }
777
778    /// Element-wise power with a float scalar.
779    ///
780    /// # Arguments
781    ///
782    /// * `tensor` - The tensor to exponentiate.
783    /// * `value` - The exponent.
784    ///
785    /// # Returns
786    ///
787    /// A tensor with the same shape as `tensor` with values raised to the power of `value`.
788    fn q_powf_scalar(tensor: QuantizedTensor<B>, value: Scalar) -> TensorPrimitive<B> {
789        dequant_op_flow!(
790            float_op | tensor | B::float_powf_scalar(tensor, value),
791            tensor
792        )
793    }
794
795    /// Returns a new tensor with square root values.
796    ///
797    /// # Arguments
798    ///
799    /// * `tensor` - The tensor to take the square root of.
800    ///
801    /// # Returns
802    ///
803    /// A tensor with the same shape as `tensor` with square root values.
804    fn q_sqrt(tensor: QuantizedTensor<B>) -> TensorPrimitive<B> {
805        dequant_op_flow!(float_op | tensor | B::float_sqrt(tensor), tensor)
806    }
807
808    /// Returns a new tensor with absolute values.
809    ///
810    /// # Arguments
811    ///
812    /// * `tensor` - The tensor to take absolute value of.
813    ///
814    /// # Returns
815    ///
816    /// A tensor with the same shape as `tensor` with absolute values.
817    fn q_abs(tensor: QuantizedTensor<B>) -> QuantizedTensor<B> {
818        dequant_op_quant!(float_op | tensor | B::float_abs(tensor), tensor)
819    }
820
821    /// Returns a new tensor with cosine values.
822    ///
823    /// # Arguments
824    ///
825    /// * `tensor` - The tensor to take the cosine of.
826    ///
827    /// # Returns
828    ///
829    /// A tensor with the same shape as `tensor` with cosine values.
830    fn q_cos(tensor: QuantizedTensor<B>) -> TensorPrimitive<B> {
831        dequant_op_flow!(float_op | tensor | B::float_cos(tensor), tensor)
832    }
833
834    /// Returns a new tensor with sine values.
835    ///
836    /// # Arguments
837    ///
838    /// * `tensor` - The tensor to take the sine of.
839    ///
840    /// # Returns
841    ///
842    /// A tensor with the same shape as `tensor` with sine values.
843    fn q_sin(tensor: QuantizedTensor<B>) -> TensorPrimitive<B> {
844        dequant_op_flow!(float_op | tensor | B::float_sin(tensor), tensor)
845    }
846
847    /// Returns a new tensor with tangent values.
848    ///
849    /// # Arguments
850    ///
851    /// * `tensor` - The tensor to take the tangent of.
852    ///
853    /// # Returns
854    ///
855    /// A tensor with the same shape as `tensor` with tangent values.
856    fn q_tan(tensor: QuantizedTensor<B>) -> TensorPrimitive<B> {
857        dequant_op_flow!(float_op | tensor | B::float_tan(tensor), tensor)
858    }
859
860    /// Returns a new tensor with hyperbolic cosine values.
861    ///
862    /// # Arguments
863    ///
864    /// * `tensor` - The tensor to take the hyperbolic cosine of.
865    ///
866    /// # Returns
867    ///
868    /// A tensor with the same shape as `tensor` with hyperbolic cosine values.
869    fn q_cosh(tensor: QuantizedTensor<B>) -> TensorPrimitive<B> {
870        dequant_op_flow!(float_op | tensor | B::float_cosh(tensor), tensor)
871    }
872
873    /// Returns a new tensor with hyperbolic sine values.
874    ///
875    /// # Arguments
876    ///
877    /// * `tensor` - The tensor to take the hyperbolic sine of.
878    ///
879    /// # Returns
880    ///
881    /// A tensor with the same shape as `tensor` with hyperbolic sine values.
882    fn q_sinh(tensor: QuantizedTensor<B>) -> TensorPrimitive<B> {
883        dequant_op_flow!(float_op | tensor | B::float_sinh(tensor), tensor)
884    }
885
886    /// Returns a new tensor with hyperbolic tangent values.
887    ///
888    /// # Arguments
889    ///
890    /// * `tensor` - The tensor to take the hyperbolic tangent of.
891    ///
892    /// # Returns
893    ///
894    /// A tensor with the same shape as `tensor` with hyperbolic tangent values.
895    fn q_tanh(tensor: QuantizedTensor<B>) -> TensorPrimitive<B> {
896        dequant_op_flow!(float_op | tensor | B::float_tanh(tensor), tensor)
897    }
898
899    /// Returns a new tensor with the error function values.
900    ///
901    /// # Arguments
902    ///
903    /// * `tensor` - The tensor to take the error function of.
904    ///
905    /// # Returns
906    ///
907    /// A tensor with the same shape as `tensor` with error function values.
908    fn q_erf(tensor: QuantizedTensor<B>) -> TensorPrimitive<B> {
909        dequant_op_flow!(float_op | tensor | B::float_erf(tensor), tensor)
910    }
911
912    /// Concatenates tensors along a dimension.
913    ///
914    /// # Arguments
915    ///
916    /// * `tensors` - The tensors to concatenate.
917    /// * `dim` - The dimension along which to concatenate.
918    ///
919    /// # Returns
920    ///
921    /// A tensor with the concatenated tensors along `dim`.
922    fn q_cat(tensors: Vec<QuantizedTensor<B>>, dim: usize) -> QuantizedTensor<B> {
923        // Heuristic: prioritize first tensor scheme
924        let first = tensors.first().unwrap();
925        let scheme = first.scheme();
926        let dtype = get_or_init_device_settings::<B>(&first.device()).float_dtype;
927
928        let tensor_f = tensors
929            .into_iter()
930            .map(|tensor| Self::dequantize(tensor, dtype))
931            .collect();
932
933        let out_f = B::float_cat(tensor_f, dim);
934
935        Self::quantize_dynamic(out_f, &scheme)
936    }
937
938    /// Gets the indices of the maximum elements of a tensor along an axis.
939    ///
940    /// # Arguments
941    ///
942    /// * `tensor` - The tensor to get the maximum elements of.
943    /// * `dim` - The dimension along which to get the maximum elements.
944    /// * `out_dtype` - The output tensor dtype.
945    ///
946    /// # Returns
947    ///
948    /// A tensor with the indices of the maximum elements of `tensor` along `dim`.
949    fn q_argmax(tensor: QuantizedTensor<B>, dim: usize, out_dtype: IntDType) -> IntTensor<B> {
950        let dtype = get_or_init_device_settings::<B>(&tensor.device()).float_dtype;
951        let tensor_f = Self::dequantize(tensor, dtype);
952        B::float_argmax(tensor_f, dim, out_dtype)
953    }
954
955    /// Gets the indices of the k maximum elements of a tensor along an axis.
956    /// If two elements are equals, order them by the lowest indices
957    ///
958    /// # Arguments
959    ///
960    /// * `tensor` - The tensor to get the k maximum elements of.
961    /// * `dim` - The dimension along which to get the maximum elements.
962    /// * `k` - number of k maximums to keep
963    /// * `out_dtype` - The output tensor dtype.
964    ///
965    /// # Returns
966    ///
967    /// A tensor with the indices of the `k` maximum elements of `tensor` along `dim`.
968    fn q_argtopk(
969        tensor: QuantizedTensor<B>,
970        dim: usize,
971        k: usize,
972        out_dtype: IntDType,
973    ) -> IntTensor<B> {
974        let dtype = get_or_init_device_settings::<B>(&tensor.device()).float_dtype;
975        let tensor_f = Self::dequantize(tensor, dtype);
976        B::float_argtopk(tensor_f, dim, k, out_dtype)
977    }
978
979    /// Gets the values of the k maximum elements of a tensor along an axis.
980    ///
981    /// # Arguments
982    ///
983    /// * `tensor` - The tensor to get the k maximum elements of.
984    /// * `dim` - The dimension along which to get the maximum elements.
985    /// * `k` - number of k maximums to keep
986    /// * `out_dtype` - The output tensor dtype.
987    ///
988    /// # Returns
989    ///
990    /// A tensor with the values of the `k` maximum elements of `tensor` along `dim`.
991    fn q_topk(tensor: QuantizedTensor<B>, dim: usize, k: usize) -> QuantizedTensor<B> {
992        dequant_op_quant!(float_op | tensor | B::float_topk(tensor, dim, k), tensor)
993    }
994
995    /// Gets the values of the k maximum elements of a tensor along an axis, and their indices.
996    ///
997    /// # Arguments
998    ///
999    /// * `tensor` - The tensor to get the k maximum elements of.
1000    /// * `dim` - The dimension along which to get the maximum elements.
1001    /// * `k` - number of k maximums to keep
1002    /// * `out_dtype` - The indices tensor dtype.
1003    ///
1004    /// # Returns
1005    ///
1006    /// A tuple with the values of the `k` maximum elements of `tensor` along `dim`, and their
1007    /// indices.
1008    /// The default sorts once and keeps the first `k` of each half, mirroring
1009    /// [`crate::ops::FloatTensorOps::float_topk_with_indices`].
1010    fn q_topk_with_indices(
1011        tensor: QuantizedTensor<B>,
1012        dim: usize,
1013        k: usize,
1014        out_dtype: IntDType,
1015    ) -> (QuantizedTensor<B>, IntTensor<B>) {
1016        let device = tensor.device();
1017        let dtype = get_or_init_device_settings::<B>(&device).int_dtype;
1018        let k_indices = B::int_arange(0..k as i64, &device, dtype);
1019        let (values, indices) = Self::q_sort_with_indices(tensor, dim, true, out_dtype);
1020
1021        (
1022            Self::q_select(values, dim, k_indices.clone()),
1023            B::int_select(indices, dim, k_indices),
1024        )
1025    }
1026
1027    /// Gets the indices of the minimum elements of a tensor along an axis.
1028    ///
1029    /// # Arguments
1030    ///
1031    /// * `tensor` - The tensor to get the minimum elements of.
1032    /// * `dim` - The dimension along which to get the minimum elements.
1033    /// * `out_dtype` - The output tensor dtype.
1034    ///
1035    /// # Returns
1036    ///
1037    /// A tensor with the indices of the minimum elements of `tensor` along `dim`.
1038    fn q_argmin(tensor: QuantizedTensor<B>, dim: usize, out_dtype: IntDType) -> IntTensor<B> {
1039        let dtype = get_or_init_device_settings::<B>(&tensor.device()).float_dtype;
1040        let tensor_f = Self::dequantize(tensor, dtype);
1041        B::float_argmin(tensor_f, dim, out_dtype)
1042    }
1043
1044    /// Gets the maximum element of a tensor.
1045    ///
1046    /// # Arguments
1047    ///
1048    /// * `tensor` - The tensor to get the maximum elements of.
1049    ///
1050    /// # Returns
1051    ///
1052    /// A tensor with the maximum element of `tensor`.
1053    fn q_max(tensor: QuantizedTensor<B>) -> QuantizedTensor<B> {
1054        let shape = tensor.shape();
1055        let tensor = B::q_reshape(tensor, Shape::new([shape.num_elements()]));
1056
1057        B::q_max_dim(tensor, 0)
1058    }
1059
1060    /// Gets the maximum elements of a tensor along an axis.
1061    ///
1062    /// # Arguments
1063    ///
1064    /// * `tensor` - The tensor to get the maximum elements of.
1065    /// * `dim` - The dimension along which to get the maximum elements.
1066    ///
1067    /// # Returns
1068    ///
1069    /// A tensor with the maximum elements of `tensor` along `dim`.
1070    fn q_max_dim(tensor: QuantizedTensor<B>, dim: usize) -> QuantizedTensor<B> {
1071        let int_dtype = get_or_init_device_settings::<B>(&tensor.device()).int_dtype;
1072        let index = B::q_argmax(tensor.clone(), dim, int_dtype);
1073
1074        B::q_gather(dim, tensor, index)
1075    }
1076
1077    /// Gets the maximum elements of a tensor along an axis and their indices.
1078    ///
1079    /// # Arguments
1080    ///
1081    /// * `tensor` - The tensor to get the maximum elements of.
1082    /// * `dim` - The dimension along which to get the maximum elements.
1083    ///
1084    /// # Returns
1085    ///
1086    /// A tuple with the maximum elements of `tensor` along `dim` and their indices.
1087    fn q_max_dim_with_indices(
1088        tensor: QuantizedTensor<B>,
1089        dim: usize,
1090        out_dtype: IntDType,
1091    ) -> (QuantizedTensor<B>, IntTensor<B>) {
1092        let index = B::q_argmax(tensor.clone(), dim, out_dtype);
1093        let values = B::q_gather(dim, tensor, index.clone());
1094
1095        (values, index)
1096    }
1097
1098    /// Gets the minimum element of a tensor.
1099    ///
1100    /// # Arguments
1101    ///
1102    /// * `tensor` - The tensor to get the minimum elements of.
1103    ///
1104    /// # Returns
1105    ///
1106    /// A tensor with the minimum element of `tensor`.
1107    fn q_min(tensor: QuantizedTensor<B>) -> QuantizedTensor<B> {
1108        let shape = tensor.shape();
1109        let tensor = B::q_reshape(tensor, Shape::new([shape.num_elements()]));
1110
1111        B::q_min_dim(tensor, 0)
1112    }
1113
1114    /// Gets the minimum elements of a tensor along an axis.
1115    ///
1116    /// # Arguments
1117    ///
1118    /// * `tensor` - The tensor to get the minimum elements of.
1119    /// * `dim` - The dimension along which to get the minimum elements.
1120    ///
1121    /// # Returns
1122    ///
1123    /// A tensor with the minimum elements of `tensor` along `dim`.
1124    fn q_min_dim(tensor: QuantizedTensor<B>, dim: usize) -> QuantizedTensor<B> {
1125        let int_dtype = get_or_init_device_settings::<B>(&tensor.device()).int_dtype;
1126        let index = B::q_argmin(tensor.clone(), dim, int_dtype);
1127
1128        B::q_gather(dim, tensor, index)
1129    }
1130
1131    /// Gets the minimum elements of a tensor along an axis and their indices.
1132    ///
1133    /// # Arguments
1134    ///
1135    /// * `tensor` - The tensor to get the minimum elements of.
1136    /// * `dim` - The dimension along which to get the minimum elements.
1137    ///
1138    /// # Returns
1139    ///
1140    /// A tuple with the minimum elements of `tensor` along `dim` and their indices.
1141    fn q_min_dim_with_indices(
1142        tensor: QuantizedTensor<B>,
1143        dim: usize,
1144        out_dtype: IntDType,
1145    ) -> (QuantizedTensor<B>, IntTensor<B>) {
1146        let index = B::q_argmin(tensor.clone(), dim, out_dtype);
1147        let values = B::q_gather(dim, tensor, index.clone());
1148
1149        (values, index)
1150    }
1151
1152    /// Gets the maximum element of a tensor.
1153    ///
1154    /// # Arguments
1155    ///
1156    /// * `tensor` - The tensor to get the maximum elements of.
1157    ///
1158    /// # Returns
1159    ///
1160    /// A tensor with the maximum element of `tensor`.
1161    fn q_max_abs(tensor: QuantizedTensor<B>) -> QuantizedTensor<B> {
1162        let shape = tensor.shape();
1163        let tensor = B::q_reshape(tensor, Shape::new([shape.num_elements()]));
1164
1165        B::q_max_abs_dim(tensor, 0)
1166    }
1167
1168    /// Gets the maximum elements of a tensor along an axis.
1169    ///
1170    /// # Arguments
1171    ///
1172    /// * `tensor` - The tensor to get the maximum elements of.
1173    /// * `dim` - The dimension along which to get the maximum elements.
1174    ///
1175    /// # Returns
1176    ///
1177    /// A tensor with the maximum elements of `tensor` along `dim`.
1178    fn q_max_abs_dim(tensor: QuantizedTensor<B>, dim: usize) -> QuantizedTensor<B> {
1179        let int_dtype = get_or_init_device_settings::<B>(&tensor.device()).int_dtype;
1180        let index = B::q_argmax(B::q_abs(tensor.clone()), dim, int_dtype);
1181
1182        B::q_gather(dim, tensor, index)
1183    }
1184
1185    /// Tests if any element in the `tensor` evaluates to True.
1186    ///
1187    /// # Arguments
1188    ///
1189    /// * `tensor` - The tensor to test.
1190    ///
1191    /// # Returns
1192    ///
1193    /// A boolean tensor with a single element, True if any element in the tensor is True, False otherwise.
1194    fn q_any(tensor: QuantizedTensor<B>, out_dtype: BoolDType) -> BoolTensor<B> {
1195        let dtype = get_or_init_device_settings::<B>(&tensor.device()).float_dtype;
1196        let tensor_f = Self::dequantize(tensor, dtype);
1197        B::float_any(tensor_f, out_dtype)
1198    }
1199
1200    /// Tests if any element in the float `tensor` evaluates to True along a given dimension `dim`.
1201    ///
1202    /// # Arguments
1203    ///
1204    /// * `tensor` - The tensor to test.
1205    /// * `dim` - The axis along which to test.
1206    ///
1207    /// # Returns
1208    ///
1209    /// A boolean tensor `Tensor<B, D, Bool>` with the same size as input `tensor`, except in the `dim` axis
1210    /// where the size is 1. The elem in the `dim` axis is True if any element along this dim in the
1211    /// input evaluates to True, False otherwise.
1212    fn q_any_dim(tensor: QuantizedTensor<B>, dim: usize, out_dtype: BoolDType) -> BoolTensor<B> {
1213        let dtype = get_or_init_device_settings::<B>(&tensor.device()).float_dtype;
1214        let tensor_f = Self::dequantize(tensor, dtype);
1215        B::float_any_dim(tensor_f, dim, out_dtype)
1216    }
1217
1218    /// Tests if all elements in the `tensor` evaluate to True.
1219    ///
1220    /// # Arguments
1221    ///
1222    /// * `tensor` - The tensor to test.
1223    ///
1224    /// # Returns
1225    ///
1226    /// A boolean tensor `Tensor<B, 1, Bool>` with a single element, True if all elements in the input tensor
1227    /// evaluate to True, False otherwise.
1228    fn q_all(tensor: QuantizedTensor<B>, out_dtype: BoolDType) -> BoolTensor<B> {
1229        let dtype = get_or_init_device_settings::<B>(&tensor.device()).float_dtype;
1230        let tensor_f = Self::dequantize(tensor, dtype);
1231        B::float_all(tensor_f, out_dtype)
1232    }
1233
1234    /// Tests if all elements in the `tensor` evaluate to True along a given dimension `dim`.
1235    ///
1236    /// # Arguments
1237    ///
1238    /// * `tensor` - The tensor to test.
1239    /// * `dim` - The axis along which to test.
1240    ///
1241    /// # Returns
1242    ///
1243    /// A boolean tensor `Tensor<B, D, Bool>` with the same size as input `tensor`, except in the `dim` axis
1244    /// where the size is 1. The elem in the `dim` axis is True if all elements along this dim in the input
1245    /// evaluates to True, False otherwise.
1246    fn q_all_dim(tensor: QuantizedTensor<B>, dim: usize, out_dtype: BoolDType) -> BoolTensor<B> {
1247        let dtype = get_or_init_device_settings::<B>(&tensor.device()).float_dtype;
1248        let tensor_f = Self::dequantize(tensor, dtype);
1249        B::float_all_dim(tensor_f, dim, out_dtype)
1250    }
1251
1252    /// Sort the elements of the input `tensor` by value in along a given dimension.
1253    ///
1254    /// This sort is unstable (i.e., may reorder equal elements).
1255    ///
1256    /// # Arguments
1257    ///
1258    /// * `tensor` - The input tensor.
1259    /// * `dim` - The axis along which to sort.
1260    /// * `descending` - The sorting order.
1261    ///
1262    /// # Returns
1263    ///
1264    /// A tensor with the same shape as the input tensor, where the elements are sorted by value.
1265    fn q_sort(tensor: QuantizedTensor<B>, dim: usize, descending: bool) -> QuantizedTensor<B> {
1266        // Default implementation. Backends can sort on the int values since qparams remain the same.
1267        dequant_op_quant!(
1268            float_op | tensor | B::float_sort(tensor, dim, descending),
1269            tensor
1270        )
1271    }
1272
1273    /// Sort the elements of the input `tensor` by value in along a given dimension.
1274    ///
1275    /// This sort is unstable (i.e., may reorder equal elements).
1276    ///
1277    /// # Arguments
1278    ///
1279    /// * `tensor` - The input tensor.
1280    /// * `dim` - The axis along which to sort.
1281    /// * `descending` - The sorting order.
1282    ///
1283    /// # Returns
1284    ///
1285    /// A tensor with the same shape as the input tensor and corresponding indices, where
1286    /// the elements are sorted by value and the indices map back to the original input tensor.
1287    fn q_sort_with_indices(
1288        tensor: QuantizedTensor<B>,
1289        dim: usize,
1290        descending: bool,
1291        out_dtype: IntDType,
1292    ) -> (QuantizedTensor<B>, IntTensor<B>) {
1293        let scheme = tensor.scheme();
1294        let dtype = get_or_init_device_settings::<B>(&tensor.device()).float_dtype;
1295
1296        let tensor_f = Self::dequantize(tensor, dtype);
1297        let (out_f, indices) = B::float_sort_with_indices(tensor_f, dim, descending, out_dtype);
1298
1299        (Self::quantize_dynamic(out_f, &scheme), indices)
1300    }
1301
1302    /// Returns the indices that sort the elements of the input `tensor` by value along a given dimension.
1303    ///
1304    /// This sort is unstable (i.e., may reorder equal elements).
1305    ///
1306    /// # Arguments
1307    ///
1308    /// * `tensor` - The input tensor.
1309    /// * `dim` - The axis along which to sort.
1310    /// * `descending` - The sorting order.
1311    ///
1312    /// # Returns
1313    ///
1314    /// A tensor with the same shape as the input tensor the indices map back to the original input tensor.
1315    fn q_argsort(
1316        tensor: QuantizedTensor<B>,
1317        dim: usize,
1318        descending: bool,
1319        out_dtype: IntDType,
1320    ) -> IntTensor<B> {
1321        let dtype = get_or_init_device_settings::<B>(&tensor.device()).float_dtype;
1322        let tensor_f = Self::dequantize(tensor, dtype);
1323        B::float_argsort(tensor_f, dim, descending, out_dtype)
1324    }
1325}