metrale_model_layers/layers/ops/
gemv_q2_vec.rs1use anyhow::Result;
14use metrale_gpu_runtime::gpu::{DevicePtr, GpuBackend, KernelHandle};
15use metrale_gpu_runtime::kernel_args::{KernelLaunch, div_ceil};
16
17use crate::weight_map::PackedQ2Weight;
18
19pub fn q2_0_gemv_vec(
23 gpu: &dyn GpuBackend,
24 kernel: KernelHandle,
25 input: DevicePtr,
26 weight: &PackedQ2Weight,
27 output: DevicePtr,
28 stream: u64,
29) -> Result<()> {
30 KernelLaunch::new(gpu, kernel)
31 .grid([div_ceil(weight.n, 8), 1, 1])
32 .block([256, 1, 1])
33 .arg_ptr(input)
34 .arg_ptr(weight.weight)
35 .arg_ptr(output)
36 .arg_u32(weight.n)
37 .arg_u32(weight.k)
38 .arg_u32(weight.group as u32)
39 .launch(stream)
40}
41
42pub const Q2_BATCHM_MAX_M: u32 = 8;
47
48fn batchm_row_chunks(m: u32) -> impl Iterator<Item = (u32, u32)> {
53 (0..m)
54 .step_by(Q2_BATCHM_MAX_M as usize)
55 .map(move |r0| (r0, (m - r0).min(Q2_BATCHM_MAX_M)))
56}
57
58fn batchm_launch_chunks(m: u32, k: u32, n: u32) -> impl Iterator<Item = (usize, usize, u32)> {
61 batchm_row_chunks(m).map(move |(r0, rows)| {
62 (
63 r0 as usize * k as usize * 2,
64 r0 as usize * n as usize * 2,
65 rows,
66 )
67 })
68}
69
70#[allow(clippy::too_many_arguments)]
76pub fn q2_0_gemv_vec_batchm(
77 gpu: &dyn GpuBackend,
78 kernel: KernelHandle,
79 input: DevicePtr,
80 weight: &PackedQ2Weight,
81 output: DevicePtr,
82 m: u32,
83 stream: u64,
84) -> Result<()> {
85 for (input_offset, output_offset, m_chunk) in batchm_launch_chunks(m, weight.k, weight.n) {
86 KernelLaunch::new(gpu, kernel)
87 .grid([div_ceil(weight.n, 8), 1, 1])
88 .block([256, 1, 1])
89 .arg_ptr(input.offset(input_offset))
90 .arg_ptr(weight.weight)
91 .arg_ptr(output.offset(output_offset))
92 .arg_u32(weight.n)
93 .arg_u32(weight.k)
94 .arg_u32(weight.group as u32)
95 .arg_u32(m_chunk)
96 .launch(stream)?;
97 }
98 Ok(())
99}
100
101#[cfg(test)]
102mod tests {
103 use super::{batchm_launch_chunks, batchm_row_chunks};
104
105 use half::{bf16, f16};
106
107 fn f16_rt(x: f32) -> f32 {
110 f16::from_f32(x).to_f32()
111 }
112
113 fn gemv_row(a: &[f32], codes: &[u8], scales: &[f32], n: usize, k: usize) -> Vec<u16> {
116 let bpr = k / 128; let mut out = vec![0u16; n];
118 for (col, o) in out.iter_mut().enumerate() {
119 let mut acc = 0f32;
120 for ki in 0..k {
121 let d = f16_rt(scales[col * bpr + ki / 128]);
122 let w = ((codes[col * k + ki] & 0x3) as i32 - 1) as f32 * d;
123 acc += a[ki] * w;
124 }
125 *o = bf16::from_f32(acc).to_bits();
126 }
127 out
128 }
129
130 fn gen_inputs(m: usize, n: usize, k: usize) -> (Vec<f32>, Vec<u8>, Vec<f32>) {
131 let mut act = vec![0f32; m * k];
132 for (i, a) in act.iter_mut().enumerate() {
133 *a = (i as f32 * 0.09131).sin() * 1.3;
134 }
135 let mut codes = vec![0u8; n * k];
136 for (i, c) in codes.iter_mut().enumerate() {
137 *c = (((i * 2654435761usize) >> 6) % 3) as u8;
138 }
139 let mut scales = vec![0f32; n * (k / 128)];
140 for (i, s) in scales.iter_mut().enumerate() {
141 *s = 0.015 + 0.01 * ((i % 5) as f32);
142 }
143 (act, codes, scales)
144 }
145
146 #[test]
147 fn batchm_row_chunks_cover_all_rows_exactly() {
148 for &m in &[0u32, 1, 2, 3, 7, 8, 9, 15, 16, 17] {
149 let chunks: Vec<(u32, u32)> = batchm_row_chunks(m).collect();
150 let mut next_r0 = 0u32;
151 let mut total = 0u32;
152 for &(r0, mc) in &chunks {
153 assert_eq!(r0, next_r0, "M={m}: chunk starts must be contiguous");
154 assert!(
155 (1..=super::Q2_BATCHM_MAX_M).contains(&mc),
156 "M={m}: bad chunk {mc}"
157 );
158 next_r0 += mc;
159 total += mc;
160 }
161 assert_eq!(total, m, "M={m}: chunks must cover every row once");
162 assert_eq!(chunks.is_empty(), m == 0);
163 }
164 }
165
166 #[test]
167 fn launch_chunks_advance_bf16_input_and_output_rows_independently() {
168 assert_eq!(
169 batchm_launch_chunks(17, 256, 5).collect::<Vec<_>>(),
170 vec![(0, 0, 8), (4096, 80, 8), (8192, 160, 1)]
171 );
172 }
173
174 #[test]
178 fn chunked_batchm_equals_per_row_gemv() {
179 let (n, k) = (5usize, 256usize);
180 for &m in &[1usize, 2, 3, 8, 9, 16] {
181 let (act, codes, scales) = gen_inputs(m, n, k);
182
183 let mut reference = vec![0u16; m * n];
184 for row in 0..m {
185 let r = gemv_row(&act[row * k..(row + 1) * k], &codes, &scales, n, k);
186 reference[row * n..(row + 1) * n].copy_from_slice(&r);
187 }
188
189 let mut got = vec![0u16; m * n];
190 for (input_offset, output_offset, mc) in
191 batchm_launch_chunks(m as u32, k as u32, n as u32)
192 {
193 let r0 = input_offset / (k * 2);
194 assert_eq!(output_offset, r0 * n * 2);
195 for i in 0..mc as usize {
196 let row = r0 + i;
197 let r = gemv_row(&act[row * k..(row + 1) * k], &codes, &scales, n, k);
198 got[row * n..(row + 1) * n].copy_from_slice(&r);
199 }
200 }
201 assert_eq!(
202 got, reference,
203 "M={m}: chunked batchm must equal per-row M=1 GEMV bit-for-bit"
204 );
205 }
206 }
207}