Skip to main content

vyre_libs/nn/linear/inner/linear_4bit/
quantized_spec.rs

1//! Validation of first-class quantized weight metadata and the spec-driven
2//! program builders.
3
4use 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    /// Build a grouped affine INT4 metadata spec.
12    #[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
59/// Build [`linear_4bit_affine_grouped`] from first-class quantized metadata.
60///
61/// # Errors
62/// Returns `Err` when the spec is not `Quantized<I4; PerGroup; PerGroup>`,
63/// when scale/zero-point group sizes differ, or when dimensions are invalid.
64pub 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
87/// Build [`linear_4bit_affine_grouped_batched`] from first-class quantized
88/// metadata.
89///
90/// # Errors
91/// Returns `Err` when quantized metadata, dimensions, or `batch_size` are
92/// invalid.
93pub 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}