1use cubecl_common::quant::scheme::{QuantScheme, ScaleDtype};
2use cubecl_core::prelude::Scalar;
3
4pub trait RunWithQuantType {
7 type Output;
8
9 fn execute<Q: Scalar, S: Scalar>(self) -> Self::Output;
10}
11
12pub fn check_scale_bindings(scheme: &QuantScheme, bindings: usize) {
22 let levels = scheme.num_levels();
23 assert!(
24 bindings == levels,
25 "a scheme with {levels} scale level(s) takes as many scale bindings, but {bindings} were provided",
26 );
27 check_global_levels(scheme);
28}
29
30pub fn check_global_levels(scheme: &QuantScheme) {
33 if scheme.block_scale().is_some()
34 && let Some(tensor) = scheme.tensor_scale()
35 {
36 assert!(
37 tensor == ScaleDtype::F32,
38 "an global scale binds as f32, but the scheme stores it as {tensor:?}",
39 );
40 }
41}
42
43pub fn check_table_bindings(scheme: &QuantScheme, table_provided: bool) {
55 use cubecl_common::quant::scheme::{QuantMode, QuantStore, QuantValue};
56 match (scheme.mode, table_provided) {
57 (QuantMode::Lookup, false) => {
58 panic!(
59 "{:?} takes a lookup table, but none was provided",
60 scheme.mode
61 )
62 }
63 (QuantMode::Lookup, true) => {
64 assert!(
65 matches!(scheme.store, QuantStore::PackedU32(_)),
66 "lookup decode is only wired for packed-u32 storage, got {:?}",
67 scheme.store
68 );
69 assert!(
70 !matches!(
71 scheme.value,
72 QuantValue::E5M2 | QuantValue::E4M3 | QuantValue::E2M1
73 ),
74 "a lookup field is an index, so a minifloat value ({:?}) has nothing to mean; \
75 use the integer value of the same width",
76 scheme.value
77 );
78 }
79 (_, true) => {
80 panic!(
81 "a lookup table was provided, but {:?} does not take one",
82 scheme.mode
83 )
84 }
85 (_, false) => {}
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use super::{check_scale_bindings, check_table_bindings};
92 use cubecl_common::quant::scheme::{
93 QuantMode, QuantScheme, QuantStore, QuantValue, ScaleDtype,
94 };
95
96 #[test]
97 fn a_one_level_scheme_takes_one_binding() {
98 check_scale_bindings(&QuantScheme::default().per_tensor(ScaleDtype::F32), 1);
99 check_scale_bindings(&QuantScheme::default().per_block([32], ScaleDtype::F32), 1);
100 }
101
102 #[test]
103 fn a_two_level_scheme_takes_two_bindings() {
104 check_scale_bindings(
105 &QuantScheme::default()
106 .per_block([32], ScaleDtype::F32)
107 .per_tensor(ScaleDtype::F32),
108 2,
109 );
110 }
111
112 #[test]
114 #[should_panic(expected = "binds as f32, but")]
115 fn a_two_level_scheme_storing_the_tensor_scale_narrower_is_rejected() {
116 check_scale_bindings(
117 &QuantScheme::default()
118 .per_block([32], ScaleDtype::F32)
119 .per_tensor(ScaleDtype::BF16),
120 2,
121 );
122 }
123
124 #[test]
125 #[should_panic(expected = "takes as many scale bindings, but 1 were provided")]
126 fn a_two_level_scheme_with_one_binding_is_rejected() {
127 check_scale_bindings(
129 &QuantScheme::default()
130 .per_block([32], ScaleDtype::F32)
131 .per_tensor(ScaleDtype::F32),
132 1,
133 );
134 }
135
136 #[test]
137 #[should_panic(expected = "takes as many scale bindings, but 2 were provided")]
138 fn a_one_level_scheme_with_two_bindings_is_rejected() {
139 check_scale_bindings(&QuantScheme::default().per_tensor(ScaleDtype::F32), 2);
140 }
141
142 fn lookup_scheme() -> QuantScheme {
143 QuantScheme::default()
144 .with_value(QuantValue::Q4F)
145 .with_mode(QuantMode::Lookup)
146 }
147
148 #[test]
149 fn a_lookup_scheme_takes_a_table() {
150 check_table_bindings(&lookup_scheme(), true);
151 }
152
153 #[test]
154 fn a_symmetric_scheme_takes_no_table() {
155 check_table_bindings(&QuantScheme::default(), false);
156 }
157
158 #[test]
159 #[should_panic(expected = "takes a lookup table, but none was provided")]
160 fn a_lookup_scheme_without_a_table_is_rejected() {
161 check_table_bindings(&lookup_scheme(), false);
163 }
164
165 #[test]
166 #[should_panic(expected = "does not take one")]
167 fn a_symmetric_scheme_with_a_table_is_rejected() {
168 check_table_bindings(&QuantScheme::default(), true);
169 }
170
171 #[test]
174 #[should_panic(expected = "only wired for packed-u32 storage")]
175 fn a_native_lookup_scheme_is_rejected() {
176 let scheme = QuantScheme::default()
177 .with_value(QuantValue::Q8F)
178 .with_store(QuantStore::Native)
179 .with_mode(QuantMode::Lookup);
180 check_table_bindings(&scheme, true);
181 }
182
183 #[test]
184 #[should_panic(expected = "a lookup field is an index")]
185 fn a_minifloat_lookup_scheme_is_rejected() {
186 let scheme = QuantScheme::default()
187 .with_value(QuantValue::E4M3)
188 .with_mode(QuantMode::Lookup);
189 check_table_bindings(&scheme, true);
190 }
191}