1use std::marker::PhantomData;
2
3use super::*;
4use crate::tensor::{
5 View, ViewExpand, ViewOperations, ViewOperationsExpand,
6 launch::{ScaleBindings, ScaleBindingsCompilationArg, ViewArg, ViewCompilationArg},
7 layout::Coordinates,
8};
9use cubecl::prelude::*;
10use cubecl_common::{
11 e2m1x2, e4m3, e5m2,
12 quant::scheme::{QuantScheme, QuantStore, QuantValue, ScaleDtype},
13 ue8m0,
14};
15use cubecl_core::{
16 self as cubecl, define_size,
17 ir::{ElemType, FloatKind, VectorSize},
18 prelude::barrier::Barrier,
19 unexpanded,
20};
21use half::{bf16, f16};
22
23#[derive(Clone, Copy, CubeType, CubeLaunch)]
28#[expand(derive(Clone, Copy))]
29pub enum KnownScale {
30 None,
32 Global(f32),
35 Whole(f32),
38}
39
40#[cube]
41impl KnownScale {
42 pub fn effective(&self, scale: f32) -> f32 {
45 #[comptime]
46 match self {
47 KnownScale::None => scale,
48 KnownScale::Global(global) => global * scale,
49 KnownScale::Whole(whole) => *whole,
50 }
51 }
52}
53
54#[expect(dead_code, reason = "only used in expand")]
62#[derive(CubeType, CubeLaunch, Clone)]
63pub struct QuantizedView<
64 'a,
65 Q: Scalar,
66 NQ: Size,
67 S: Scalar,
68 F: Numeric,
69 NF: Size,
70 C: Coordinates + 'static,
71> {
72 values: View<'a, Vector<Q, NQ>, C>,
73 scales: View<'a, S, C>,
74 known_scale: KnownScale,
75 table: ComptimeOption<Box<[f32]>>,
78 #[cube(comptime)]
79 scheme: QuantScheme,
80 #[cube(comptime)]
81 _ty: PhantomData<(F, NF)>,
82}
83
84#[cube]
85impl<'a, Q: Scalar, NQ: Size, S: Scalar, F: Numeric, NF: Size, C: Coordinates + 'static>
86 QuantizedView<'a, Q, NQ, S, F, NF, C>
87{
88 pub fn new(
94 values: View<'a, Vector<Q, NQ>, C>,
95 scales: View<'a, S, C>,
96 table: ComptimeOption<Box<[f32]>>,
97 #[comptime] scheme: QuantScheme,
98 ) -> Self {
99 comptime!(crate::quant::check_scale_bindings(&scheme, 1));
100 QuantizedView::<'a, Q, NQ, S, F, NF, C> {
101 values,
102 scales,
103 known_scale: KnownScale::new_None(),
104 table,
105 scheme,
106 _ty: PhantomData,
107 }
108 }
109
110 pub fn new_with_known_scale(
115 values: View<'a, Vector<Q, NQ>, C>,
116 scales: View<'a, S, C>,
117 known_scale: KnownScale,
118 table: ComptimeOption<Box<[f32]>>,
119 #[comptime] scheme: QuantScheme,
120 ) -> Self {
121 QuantizedView::<'a, Q, NQ, S, F, NF, C> {
122 values,
123 scales,
124 known_scale,
125 table,
126 scheme,
127 _ty: PhantomData,
128 }
129 }
130}
131
132impl<'a, Q: Scalar, NQ: Size, S: Scalar, F: Numeric, NF: Size, C: Coordinates + 'static>
133 QuantizedView<'a, Q, NQ, S, F, NF, C>
134{
135 pub fn view(self) -> View<'a, Vector<F, NF>, C> {
136 unexpanded!()
137 }
138
139 pub fn __expand_view(
140 scope: &Scope,
141 this: QuantizedViewExpand<'a, Q, NQ, S, F, NF, C>,
142 ) -> ViewExpand<'a, Vector<F, NF>, C> {
143 this.__expand_view_method(scope)
144 }
145}
146
147impl<'a, Q: Scalar, NQ: Size, S: Scalar, F: Numeric, NF: Size, C: Coordinates + 'static>
148 QuantizedViewExpand<'a, Q, NQ, S, F, NF, C>
149{
150 pub fn new(
151 values: ViewExpand<'a, Vector<Q, NQ>, C>,
152 scales: ViewExpand<'a, S, C>,
153 known_scale: KnownScaleExpand,
154 table: ComptimeOptionExpand<Box<[f32]>>,
155 scheme: QuantScheme,
156 ) -> Self {
157 QuantizedViewExpand::<'a, Q, NQ, S, F, NF, C> {
158 values,
159 scales,
160 known_scale,
161 table,
162 scheme,
163 _ty: PhantomData,
164 }
165 }
166
167 fn dequant(
170 &self,
171 scope: &Scope,
172 value: NativeExpand<Vector<Q, NQ>>,
173 read_scale: impl FnOnce(&Scope) -> NativeExpand<S>,
174 ) -> NativeExpand<Vector<F, NF>> {
175 check_table_bindings(&self.scheme, self.table.is_some());
179 match self.known_scale {
180 KnownScaleExpand::None => {
181 assert!(
182 self.scheme.num_levels() == 1,
183 "every scale is read from the scales view, but {:?} has a per-tensor scale nothing multiplies in",
184 self.scheme,
185 );
186 let scale = read_scale(scope);
187 dequantize_aligned::expand::<Q, S, F, NQ, NF>(
188 scope,
189 value,
190 scale,
191 self.table.clone(),
192 self.scheme,
193 )
194 }
195 KnownScaleExpand::Global(global_scale) => {
196 assert!(
197 self.scheme.num_levels() > 1,
198 "an global scale rides in a register, but {:?} has no per-tensor level over its blocks it could hold",
199 self.scheme,
200 );
201 check_global_levels(&self.scheme);
202 let scale = read_scale(scope);
203 let scale = multiply_global_scale::expand::<S>(scope, global_scale, scale);
204 dequantize_aligned_wide::expand::<Q, F, NQ, NF>(
205 scope,
206 value,
207 scale,
208 self.table.clone(),
209 self.scheme,
210 )
211 }
212 KnownScaleExpand::Whole(scale) => dequantize_aligned_wide::expand::<Q, F, NQ, NF>(
213 scope,
214 value,
215 scale,
216 self.table.clone(),
217 self.scheme,
218 ),
219 }
220 }
221
222 pub fn __expand_view_method(self, scope: &Scope) -> ViewExpand<'a, Vector<F, NF>, C> {
223 ViewExpand::new(scope, self)
224 }
225}
226
227impl<'a, Q: Scalar, NQ: Size, S: Scalar, F: Numeric, NF: Size, C: Coordinates + 'static> Vectorized
228 for QuantizedView<'a, Q, NQ, S, F, NF, C>
229{
230}
231impl<'a, Q: Scalar, NQ: Size, S: Scalar, F: Numeric, NF: Size, C: Coordinates + 'static>
232 VectorizedExpand for QuantizedViewExpand<'a, Q, NQ, S, F, NF, C>
233{
234 fn __expand_vector_size_method(&self, scope: &Scope) -> VectorSize {
235 self.values.__expand_vector_size_method(scope) * self.scheme.num_quants()
236 }
237}
238
239impl<'a, Q: Scalar, NQ: Size, S: Scalar, F: Numeric, NF: Size, C: Coordinates + 'static>
240 ViewOperations<Vector<F, NF>, C> for QuantizedView<'a, Q, NQ, S, F, NF, C>
241{
242}
243
244impl<'a, Q: Scalar, NQ: Size, S: Scalar, F: Numeric, NF: Size, C: Coordinates + 'static>
245 ViewOperationsExpand<Vector<F, NF>, C> for QuantizedViewExpand<'a, Q, NQ, S, F, NF, C>
246{
247 fn __expand_read_method(
248 &self,
249 scope: &Scope,
250 pos: <C>::ExpandType,
251 ) -> NativeExpand<Vector<F, NF>> {
252 let value = self.values.clone().__expand_read_method(scope, pos.clone());
253 self.dequant(scope, value, |scope| {
254 self.scales.clone().__expand_read_method(scope, pos)
255 })
256 }
257
258 fn __expand_read_checked_method(
259 &self,
260 scope: &Scope,
261 pos: <C>::ExpandType,
262 ) -> NativeExpand<Vector<F, NF>> {
263 let value = self
264 .values
265 .clone()
266 .__expand_read_checked_method(scope, pos.clone());
267 self.dequant(scope, value, |scope| {
268 self.scales.clone().__expand_read_checked_method(scope, pos)
269 })
270 }
271
272 fn __expand_read_masked_method(
273 &self,
274 scope: &Scope,
275 pos: <C>::ExpandType,
276 mask_value: NativeExpand<Vector<F, NF>>,
277 ) -> NativeExpand<Vector<F, NF>> {
278 let value = self
279 .values
280 .clone()
281 .__expand_read_checked_method(scope, pos.clone());
282 let in_bounds = self.__expand_is_in_bounds_method(scope, pos.clone());
283
284 let value = self.dequant(scope, value, |scope| {
285 self.scales.clone().__expand_read_checked_method(scope, pos)
286 });
287 select::expand::<Vector<F, NF>>(scope, in_bounds, value, mask_value)
288 }
289
290 fn __expand_read_unchecked_method(
291 &self,
292 scope: &Scope,
293 pos: <C>::ExpandType,
294 ) -> NativeExpand<Vector<F, NF>> {
295 let value = self
296 .values
297 .clone()
298 .__expand_read_unchecked_method(scope, pos.clone());
299 self.dequant(scope, value, |scope| {
300 self.scales
301 .clone()
302 .__expand_read_unchecked_method(scope, pos)
303 })
304 }
305
306 fn __expand_as_linear_slice_method(
307 &self,
308 _scope: &Scope,
309 _pos: <C>::ExpandType,
310 _end: <C>::ExpandType,
311 ) -> &SliceExpand<Vector<F, NF>> {
312 panic!("Can't create raw slice for quantized view")
313 }
314
315 fn __expand_shape_method(&self, scope: &Scope) -> <C>::ExpandType {
316 self.values.clone().__expand_shape_method(scope)
317 }
318
319 fn __expand_is_in_bounds_method(
320 &self,
321 scope: &Scope,
322 pos: C::ExpandType,
323 ) -> NativeExpand<bool> {
324 self.values.clone().__expand_is_in_bounds_method(scope, pos)
325 }
326
327 fn __expand_tensor_map_load_method(
328 &self,
329 _scope: &Scope,
330 _barrier: &NativeExpand<Barrier>,
331 _shared_memory: &mut SliceExpand<Vector<F, NF>>,
332 _pos: C::ExpandType,
333 ) {
334 panic!("Can't use tensor map functions on quantized view");
335 }
336}
337
338fn quant_vector_size_q(vector_size: usize, num_quants: usize) -> usize {
341 assert!(
342 vector_size >= num_quants && vector_size.is_multiple_of(num_quants),
343 "quantized view float vector size {vector_size} must be a positive multiple of num_quants {num_quants}"
344 );
345 vector_size / num_quants
346}
347
348fn register_global_scale<R: Runtime>(
351 global_scale: Option<BufferArg<R>>,
352 launcher: &mut KernelLauncher<R>,
353) -> Option<BufferCompilationArg> {
354 global_scale.map(|global_scale| <[f32] as LaunchArg>::register(global_scale, launcher))
355}
356
357fn register_table<R: Runtime>(
361 table: Option<BufferArg<R>>,
362 scheme: &QuantScheme,
363 launcher: &mut KernelLauncher<R>,
364) -> Option<BufferCompilationArg> {
365 check_table_bindings(scheme, table.is_some());
366 table.map(|table| {
367 let entries = 1usize << scheme.size_bits_value();
371 assert_eq!(
372 table.len(),
373 entries,
374 "a {}-bit lookup scheme indexes a table of exactly {entries} entries",
375 scheme.size_bits_value()
376 );
377 <[f32] as LaunchArg>::register(table, launcher)
378 })
379}
380
381fn expand_table(
385 table: Option<&BufferCompilationArg>,
386 scheme: &QuantScheme,
387 builder: &mut KernelBuilder,
388) -> ComptimeOptionExpand<Box<[f32]>> {
389 check_table_bindings(scheme, table.is_some());
390 match table {
391 Some(table) => {
392 ComptimeOptionExpand::Some(<Box<[f32]> as LaunchArg>::expand(table, builder))
393 }
394 None => ComptimeOptionExpand::None,
395 }
396}
397
398fn expand_known_scale(
405 global_scale: Option<&BufferCompilationArg>,
406 builder: &mut KernelBuilder,
407) -> KnownScaleExpand {
408 match global_scale {
409 Some(global_scale) => {
410 let buffer = <[f32] as LaunchArg>::expand(global_scale, builder);
411 let pos = NativeExpand::<usize>::from_lit(&builder.scope, 0);
412 KnownScaleExpand::Global(*buffer.__expand_index_method(&builder.scope, pos))
413 }
414 None => KnownScaleExpand::None,
415 }
416}
417
418struct ExpandDynamic<'a, E: Numeric, N: Size, C: Coordinates + 'static> {
419 values: &'a ViewCompilationArg<C>,
420 scales: &'a ScaleBindingsCompilationArg<C>,
421 scheme: QuantScheme,
422 builder: &'a mut KernelBuilder,
423 _ty: PhantomData<(E, N)>,
424}
425
426impl<'a, E: Numeric, N: Size, C: Coordinates + 'static> RunWithQuantType
427 for ExpandDynamic<'a, E, N, C>
428{
429 type Output = ViewExpand<'static, Vector<E, N>, C>;
430
431 fn execute<Q: Scalar, S: Scalar>(self) -> Self::Output {
432 define_size!(NQ);
433
434 let vector_size = N::__expand_value(&self.builder.scope);
435 let vector_size_q = quant_vector_size_q(vector_size, self.scheme.num_quants());
436 self.builder.scope.register_size::<NQ>(vector_size_q);
437
438 check_scale_bindings(&self.scheme, self.scales.len());
439
440 let values = View::<Vector<Q, NQ>, C>::expand(self.values, self.builder);
441 let scales = View::<S, C>::expand(&self.scales.inner, self.builder);
442 let known_scale = expand_known_scale(self.scales.global_scale.as_ref(), self.builder);
443 let table = expand_table(self.scales.table.as_ref(), &self.scheme, self.builder);
444 let view = QuantizedViewExpand::new(values, scales, known_scale, table, self.scheme);
445 ViewExpand::new(&self.builder.scope, view)
446 }
447}
448
449pub(crate) struct RegisterDynamic<'a, E: CubePrimitive, C: Coordinates + 'static, R: Runtime> {
450 pub values: ViewArg<C, R>,
451 pub scales: ScaleBindings<C, R>,
452 pub scheme: QuantScheme,
453 pub launcher: &'a mut KernelLauncher<R>,
454 pub _ty: PhantomData<E>,
455}
456
457impl<'a, E: CubePrimitive, C: Coordinates + 'static, R: Runtime> RunWithQuantType
458 for RegisterDynamic<'a, E, C, R>
459{
460 type Output = ViewCompilationArg<C>;
461
462 fn execute<Q: Scalar, S: Scalar>(self) -> Self::Output {
463 define_size!(NQ);
464
465 self.launcher.with_scope(|scope| {
466 let vector_size_q =
467 quant_vector_size_q(E::__expand_vector_size(scope), self.scheme.num_quants());
468 scope.register_size::<NQ>(vector_size_q);
469 });
470
471 check_scale_bindings(&self.scheme, self.scales.len());
472
473 let values = View::<Vector<Q, NQ>, C>::register(self.values, self.launcher);
474 let inner = View::<S, C>::register(*self.scales.inner, self.launcher);
475 let global_scale = register_global_scale(self.scales.global_scale, self.launcher);
476 let table = register_table(self.scales.table, &self.scheme, self.launcher);
477 ViewCompilationArg::Quantized {
478 values: Box::new(values),
479 scales: ScaleBindingsCompilationArg {
480 inner: Box::new(inner),
481 global_scale,
482 table,
483 },
484 scheme: self.scheme,
485 }
486 }
487}
488
489pub fn run_with_quant_type<F: RunWithQuantType>(func: F, scheme: QuantScheme) -> F::Output {
492 fn run_with_q<F: RunWithQuantType, Q: Scalar>(func: F, scheme: QuantScheme) -> F::Output {
493 match scheme.scale_dtype() {
494 ScaleDtype::F32 => func.execute::<Q, f32>(),
495 ScaleDtype::F16 => func.execute::<Q, f16>(),
496 ScaleDtype::BF16 => func.execute::<Q, bf16>(),
497 ScaleDtype::UE8M0 => func.execute::<Q, ue8m0>(),
498 ScaleDtype::UE4M3 => func.execute::<Q, e4m3>(),
499 }
500 }
501
502 let run_q = match scheme.store {
503 QuantStore::Native => match scheme.value {
504 QuantValue::Q8F => run_with_q::<F, i8>,
505 QuantValue::Q8S => run_with_q::<F, i8>,
506 QuantValue::E5M2 => run_with_q::<F, e5m2>,
507 QuantValue::E4M3 => run_with_q::<F, e4m3>,
508 QuantValue::Q4F
509 | QuantValue::Q4S
510 | QuantValue::Q2F
511 | QuantValue::Q2S
512 | QuantValue::E2M1 => {
513 panic!("Sub-byte quantization can't be native")
514 }
515 },
516 QuantStore::PackedU32(_) => run_with_q::<F, u32>,
517 QuantStore::PackedNative(_) => run_with_q::<F, e2m1x2>,
518 };
519 run_q(func, scheme)
520}
521
522pub(crate) fn expand_dynamic<E: CubePrimitive, C: Coordinates + 'static>(
525 values: &ViewCompilationArg<C>,
526 scales: &ScaleBindingsCompilationArg<C>,
527 scheme: QuantScheme,
528 builder: &mut KernelBuilder,
529) -> ViewExpand<'static, E, C> {
530 use core::mem::transmute as t;
531
532 fn expand_dynamic_f<F: Numeric, NF: Size, C: Coordinates + 'static>(
534 values: &ViewCompilationArg<C>,
535 scales: &ScaleBindingsCompilationArg<C>,
536 scheme: QuantScheme,
537 builder: &mut KernelBuilder,
538 ) -> ViewExpand<'static, Vector<F, NF>, C> {
539 let func = ExpandDynamic {
540 values,
541 scales,
542 scheme,
543 builder,
544 _ty: PhantomData::<(F, NF)>,
545 };
546 run_with_quant_type(func, scheme)
547 }
548
549 define_size!(NF);
550
551 let vector_size = E::__expand_vector_size(builder);
552
553 builder.scope.register_size::<NF>(vector_size);
554
555 #[allow(clippy::missing_transmute_annotations)]
556 unsafe {
557 match E::Scalar::elem_type(builder) {
558 ElemType::Float(ty) => match ty {
559 FloatKind::F16 => t(expand_dynamic_f::<f16, NF, C>(
560 values, scales, scheme, builder,
561 )),
562 FloatKind::BF16 => t(expand_dynamic_f::<bf16, NF, C>(
563 values, scales, scheme, builder,
564 )),
565 FloatKind::Flex32 => t(expand_dynamic_f::<flex32, NF, C>(
566 values, scales, scheme, builder,
567 )),
568 FloatKind::F32 => t(expand_dynamic_f::<f32, NF, C>(
569 values, scales, scheme, builder,
570 )),
571 FloatKind::TF32 => t(expand_dynamic_f::<tf32, NF, C>(
572 values, scales, scheme, builder,
573 )),
574 FloatKind::F64 => t(expand_dynamic_f::<f64, NF, C>(
575 values, scales, scheme, builder,
576 )),
577 FloatKind::E2M1
578 | FloatKind::E2M1x2
579 | FloatKind::E2M3
580 | FloatKind::E3M2
581 | FloatKind::E4M3
582 | FloatKind::E5M2
583 | FloatKind::UE8M0 => unreachable!("Minifloats don't implement `Float` ops"),
584 },
585 _ => unreachable!("Quantized view should only be used with floats"),
586 }
587 }
588}
589
590#[cfg(test)]
591mod tests {
592 use super::{RunWithQuantType, quant_vector_size_q, run_with_quant_type};
593 use cubecl_common::quant::scheme::{QuantScheme, ScaleDtype};
594 use cubecl_core::prelude::Scalar;
595
596 struct Dispatched;
597
598 impl RunWithQuantType for Dispatched {
599 type Output = bool;
600
601 fn execute<Q: Scalar, S: Scalar>(self) -> bool {
602 true
603 }
604 }
605
606 #[test]
607 fn one_level_scheme_dispatches() {
608 assert!(run_with_quant_type(Dispatched, QuantScheme::default()));
609 }
610
611 #[test]
614 fn two_level_scheme_dispatches() {
615 let scheme = QuantScheme::default()
616 .per_block([32], ScaleDtype::F32)
617 .per_tensor(ScaleDtype::F32);
618 assert!(run_with_quant_type(Dispatched, scheme));
619 }
620
621 #[test]
622 fn vector_size_q_exact_multiple() {
623 assert_eq!(quant_vector_size_q(8, 8), 1);
624 assert_eq!(quant_vector_size_q(16, 8), 2);
625 assert_eq!(quant_vector_size_q(16, 16), 1);
626 }
627
628 #[test]
629 #[should_panic(expected = "positive multiple of num_quants")]
630 fn vector_size_q_non_multiple_panics() {
631 let _ = quant_vector_size_q(8, 16);
632 }
633}