vyre_libs/nn/linear/inner/linear_4bit/
quantized_spec.rs1use vyre_foundation::ir::{DataType, Program};
5use vyre_spec::{QuantizationScale, QuantizationZeroPoint};
6
7use super::affine_grouped::{linear_4bit_affine_grouped, linear_4bit_affine_grouped_batched};
8use super::QuantizedLinear4BitSpec;
9
10impl QuantizedLinear4BitSpec {
11 #[must_use]
13 pub fn affine_grouped(in_dim: u32, out_dim: u32, group_size: u32) -> Self {
14 Self {
15 in_dim,
16 out_dim,
17 weight_type: DataType::Quantized {
18 storage: Box::new(DataType::I4),
19 scale: QuantizationScale::PerGroup { group_size },
20 zero_point: QuantizationZeroPoint::PerGroup { group_size },
21 },
22 }
23 }
24
25 pub(super) fn affine_group_size(&self) -> Result<u32, String> {
26 match &self.weight_type {
27 DataType::Quantized {
28 storage,
29 scale: QuantizationScale::PerGroup { group_size },
30 zero_point:
31 QuantizationZeroPoint::PerGroup {
32 group_size: zp_group_size,
33 },
34 } => {
35 if storage.as_ref() != &DataType::I4 {
36 return Err(format!(
37 "Fix: grouped INT4 linear requires DataType::Quantized storage I4, got {storage}."
38 ));
39 }
40 if group_size != zp_group_size {
41 return Err(format!(
42 "Fix: grouped INT4 linear requires scale and zero-point group sizes to match, got scale={group_size}, zero_point={zp_group_size}."
43 ));
44 }
45 if *group_size == 0 {
46 return Err(
47 "Fix: grouped INT4 linear requires quantized group_size > 0.".to_string()
48 );
49 }
50 Ok(*group_size)
51 }
52 other => Err(format!(
53 "Fix: grouped INT4 linear requires DataType::Quantized<I4; PerGroup scale; PerGroup zero-point>, got {other}."
54 )),
55 }
56 }
57}
58
59pub fn linear_4bit_affine_grouped_typed(
65 spec: &QuantizedLinear4BitSpec,
66 x: &str,
67 w_packed: &str,
68 scale: &str,
69 zero_point: &str,
70 b: &str,
71 out: &str,
72) -> Result<Program, String> {
73 let group_size = spec.affine_group_size()?;
74 linear_4bit_affine_grouped(
75 x,
76 w_packed,
77 scale,
78 zero_point,
79 b,
80 out,
81 spec.in_dim,
82 spec.out_dim,
83 group_size,
84 )
85}
86
87pub fn linear_4bit_affine_grouped_batched_typed(
94 spec: &QuantizedLinear4BitSpec,
95 batch_size: u32,
96 x: &str,
97 w_packed: &str,
98 scale: &str,
99 zero_point: &str,
100 b: &str,
101 out: &str,
102) -> Result<Program, String> {
103 let group_size = spec.affine_group_size()?;
104 linear_4bit_affine_grouped_batched(
105 x,
106 w_packed,
107 scale,
108 zero_point,
109 b,
110 out,
111 spec.in_dim,
112 spec.out_dim,
113 group_size,
114 batch_size,
115 )
116}
117
118#[cfg(test)]
119mod tests {
120 use vyre_foundation::ir::DataType;
121 use vyre_spec::{QuantizationScale, QuantizationZeroPoint};
122
123 use super::super::QuantizedLinear4BitSpec;
124 use super::linear_4bit_affine_grouped_typed;
125
126 #[test]
127 fn typed_affine_grouped_builder_uses_quantized_metadata() {
128 let spec = QuantizedLinear4BitSpec::affine_grouped(32, 7, 8);
129 let program = linear_4bit_affine_grouped_typed(&spec, "x", "w", "scale", "zp", "b", "out")
130 .expect("Fix: valid typed grouped INT4 spec must build");
131
132 assert_eq!(program.buffers()[1].name(), "w");
133 assert_eq!(program.buffers()[1].element(), DataType::U32);
134 assert_eq!(program.buffers()[1].count(), 28);
135 assert!(matches!(
136 spec.weight_type,
137 DataType::Quantized {
138 scale: QuantizationScale::PerGroup { group_size: 8 },
139 zero_point: QuantizationZeroPoint::PerGroup { group_size: 8 },
140 ..
141 }
142 ));
143 }
144 #[test]
145 fn typed_affine_grouped_builder_rejects_mismatched_quantized_metadata() {
146 let bad_storage = QuantizedLinear4BitSpec {
147 in_dim: 32,
148 out_dim: 4,
149 weight_type: DataType::Quantized {
150 storage: Box::new(DataType::I8),
151 scale: QuantizationScale::PerGroup { group_size: 8 },
152 zero_point: QuantizationZeroPoint::PerGroup { group_size: 8 },
153 },
154 };
155 let error =
156 linear_4bit_affine_grouped_typed(&bad_storage, "x", "w", "scale", "zp", "b", "out")
157 .unwrap_err();
158 assert!(
159 error.contains("storage I4"),
160 "Fix: storage mismatch should be explicit: {error}"
161 );
162
163 let bad_sidecar = QuantizedLinear4BitSpec {
164 in_dim: 32,
165 out_dim: 4,
166 weight_type: DataType::Quantized {
167 storage: Box::new(DataType::I4),
168 scale: QuantizationScale::PerGroup { group_size: 8 },
169 zero_point: QuantizationZeroPoint::PerGroup { group_size: 16 },
170 },
171 };
172 let error =
173 linear_4bit_affine_grouped_typed(&bad_sidecar, "x", "w", "scale", "zp", "b", "out")
174 .unwrap_err();
175 assert!(
176 error.contains("group sizes to match"),
177 "Fix: sidecar mismatch should be explicit: {error}"
178 );
179 }
180 #[test]
181 fn generated_typed_affine_grouped_specs_build_or_reject_by_metadata_contract() {
182 let mut accepted = 0usize;
183 let mut rejected = 0usize;
184 for in_dim in [8u32, 10, 16, 18, 24, 32, 64, 128] {
185 for out_dim in [1u32, 2, 3, 7, 16, 31] {
186 for group_size in [1u32, 2, 4, 8, 16, 32] {
187 let spec = QuantizedLinear4BitSpec::affine_grouped(in_dim, out_dim, group_size);
188 let result = linear_4bit_affine_grouped_typed(
189 &spec, "x", "w", "scale", "zp", "b", "out",
190 );
191 if in_dim % 8 == 0 {
192 let program = result.expect("Fix: generated valid typed spec must build");
193 let output = &program.buffers()[5];
194 assert!(
195 output.count() >= out_dim,
196 "Fix: grouped INT4 output storage must cover the logical outputs after launch padding."
197 );
198 assert_eq!(
199 output.output_byte_range(),
200 Some(0..(out_dim as usize * core::mem::size_of::<f32>())),
201 "Fix: grouped INT4 output byte range must trim padded launch storage to the logical tensor."
202 );
203 accepted += 1;
204 } else {
205 let error = result.expect_err(
206 "Fix: generated indivisible typed spec must reject before dispatch",
207 );
208 assert!(error.contains("divisible by 8"));
209 rejected += 1;
210 }
211 }
212 }
213 }
214
215 assert!(
216 accepted + rejected >= 216,
217 "Fix: generated typed quantized specs should cover hundreds of layouts"
218 );
219 }
220}