1use anyhow::Result;
15use metrale_gpu_runtime::gpu::{DevicePtr, GpuBackend, KernelHandle};
16use metrale_gpu_runtime::kernel_args::KernelLaunch;
17
18use super::q4k_mmq::QK_K;
19
20pub const KQUANT_MODULE: &str = "kquant_moe";
21pub const Q2K_BLOCK_BYTES: usize = 84;
23pub const Q3K_BLOCK_BYTES: usize = 110;
24pub const Q6K_BLOCK_BYTES: usize = 210;
25
26#[derive(Clone, Copy, Debug)]
30pub enum ResidentMat {
31 Bf16(DevicePtr),
32 Q2K(DevicePtr),
33 Q3K(DevicePtr),
34}
35
36impl ResidentMat {
37 pub fn at_rows(self, rows: usize, k: usize) -> ResidentMat {
39 let at = |p: DevicePtr, off: usize| DevicePtr(p.0 + off as u64);
40 match self {
41 ResidentMat::Bf16(p) => ResidentMat::Bf16(at(p, rows * k * 2)),
42 ResidentMat::Q2K(p) => ResidentMat::Q2K(at(p, rows * (k / 256) * Q2K_BLOCK_BYTES)),
43 ResidentMat::Q3K(p) => ResidentMat::Q3K(at(p, rows * (k / 256) * Q3K_BLOCK_BYTES)),
44 }
45 }
46}
47pub const Q2K_MMQ_SMEM: u32 = 512 + 128 * 100 * 4 + 128 * 144;
52pub const Q3K_MMQ_SMEM: u32 = 512 + 128 * 84 * 4 + 128 * 144;
53pub const Q8_1_BLOCK_BYTES: usize = 36;
55
56fn div_ceil(a: u32, b: u32) -> u32 {
57 a.div_ceil(b)
58}
59
60pub fn kquant_weight_bytes(n: u32, k: u32, block_bytes: usize) -> usize {
62 (n as usize) * (k as usize / QK_K as usize) * block_bytes
63}
64
65pub fn kquant_q8_1_rows_bytes(m: u32, k: u32) -> usize {
67 (m as usize) * (k as usize / 32) * Q8_1_BLOCK_BYTES
68}
69
70pub fn kquant_mmq_act_bytes(m: u32, k: u32) -> usize {
73 let mpad = div_ceil(m, 128) * 128;
74 let kpad = div_ceil(k, QK_K) * QK_K;
75 (mpad as usize) * (kpad as usize / 128) * 144
76}
77
78pub fn kquant_q8_1_rows(
81 gpu: &dyn GpuBackend,
82 kernel: KernelHandle,
83 x_bf16: DevicePtr,
84 out_q8: DevicePtr,
85 m: u32,
86 k: u32,
87 stream: u64,
88) -> Result<()> {
89 anyhow::ensure!(
90 k.is_multiple_of(32),
91 "kquant_q8_1_rows: k={k} is not a multiple of 32"
92 );
93 let warps = m * (k / 32);
94 KernelLaunch::new(gpu, kernel)
95 .grid([div_ceil(warps * 32, 128), 1, 1])
96 .block([128, 1, 1])
97 .arg_ptr(x_bf16)
98 .arg_ptr(out_q8)
99 .arg_u32(k)
100 .arg_u32(m)
101 .launch(stream)
102}
103
104#[allow(clippy::too_many_arguments)]
109pub fn kquant_swiglu_q8_1_rows(
110 gpu: &dyn GpuBackend,
111 kernel: KernelHandle,
112 gate: DevicePtr,
113 up: DevicePtr,
114 w: DevicePtr,
115 h_bf16: DevicePtr,
116 out_q8: DevicePtr,
117 rows: u32,
118 inter: u32,
119 limit: f32,
120 stream: u64,
121) -> Result<()> {
122 anyhow::ensure!(
123 inter.is_multiple_of(32),
124 "kquant_swiglu_q8_1_rows: inter={inter} is not a multiple of 32"
125 );
126 let warps = rows * (inter / 32);
127 KernelLaunch::new(gpu, kernel)
128 .grid([div_ceil(warps * 32, 128), 1, 1])
129 .block([128, 1, 1])
130 .arg_ptr(gate)
131 .arg_ptr(up)
132 .arg_ptr(w)
133 .arg_ptr(h_bf16)
134 .arg_ptr(out_q8)
135 .arg_u32(rows)
136 .arg_u32(inter)
137 .arg_f32(limit)
138 .launch(stream)
139}
140
141pub fn kquant_mmvq(
146 gpu: &dyn GpuBackend,
147 kernel: KernelHandle,
148 w_blocks: DevicePtr,
149 y_q8: DevicePtr,
150 out_bf16: DevicePtr,
151 n: u32,
152 k: u32,
153 m: u32,
154 stream: u64,
155) -> Result<()> {
156 anyhow::ensure!((1..=8).contains(&m), "kquant_mmvq: m={m} outside 1..=8");
157 anyhow::ensure!(
158 k.is_multiple_of(QK_K),
159 "kquant_mmvq: k={k} is not a multiple of {QK_K}"
160 );
161 KernelLaunch::new(gpu, kernel)
162 .grid([n, 1, 1])
163 .block([32, 4, 1])
164 .arg_ptr(w_blocks)
165 .arg_ptr(y_q8)
166 .arg_ptr(out_bf16)
167 .arg_u32(k)
168 .arg_u32(n)
169 .arg_u32(m)
170 .launch(stream)
171}
172
173#[allow(clippy::too_many_arguments)]
174pub fn kquant_mmvq_experts(
179 gpu: &dyn GpuBackend,
180 kernel: KernelHandle,
181 vxs: DevicePtr,
182 y_q8: DevicePtr,
183 out_bf16: DevicePtr,
184 n: u32,
185 k: u32,
186 m: u32,
187 n_experts: u32,
188 y_stride_bytes: u32,
189 stream: u64,
190) -> Result<()> {
191 anyhow::ensure!(
192 (1..=8).contains(&m),
193 "kquant_mmvq_experts: m={m} outside 1..=8"
194 );
195 anyhow::ensure!(
196 k.is_multiple_of(QK_K),
197 "kquant_mmvq_experts: k={k} is not a multiple of {QK_K}"
198 );
199 KernelLaunch::new(gpu, kernel)
200 .grid([n, n_experts, 1])
201 .block([32, 4, 1])
202 .arg_ptr(vxs)
203 .arg_ptr(y_q8)
204 .arg_ptr(out_bf16)
205 .arg_u32(k)
206 .arg_u32(n)
207 .arg_u32(m)
208 .arg_u32(y_stride_bytes)
209 .launch(stream)
210}
211
212pub fn kquant_mmvq_w(
215 gpu: &dyn GpuBackend,
216 kernel: KernelHandle,
217 w_blocks: DevicePtr,
218 y_q8: DevicePtr,
219 out_bf16: DevicePtr,
220 n: u32,
221 k: u32,
222 m: u32,
223 stream: u64,
224) -> Result<()> {
225 anyhow::ensure!((1..=8).contains(&m), "kquant_mmvq_w: m={m} outside 1..=8");
226 anyhow::ensure!(
227 k.is_multiple_of(QK_K),
228 "kquant_mmvq_w: k={k} is not a multiple of {QK_K}"
229 );
230 KernelLaunch::new(gpu, kernel)
231 .grid([div_ceil(n, 4), 1, 1])
232 .block([32, 4, 1])
233 .arg_ptr(w_blocks)
234 .arg_ptr(y_q8)
235 .arg_ptr(out_bf16)
236 .arg_u32(k)
237 .arg_u32(n)
238 .arg_u32(m)
239 .launch(stream)
240}
241
242#[allow(clippy::too_many_arguments)]
248pub fn kquant_mmvq_groups_w(
249 gpu: &dyn GpuBackend,
250 kernel: KernelHandle,
251 w_blocks: DevicePtr,
252 y_q8: DevicePtr,
253 out_bf16: DevicePtr,
254 n: u32,
255 k: u32,
256 m: u32,
257 n_groups: u32,
258 stream: u64,
259) -> Result<()> {
260 anyhow::ensure!(
261 (1..=8).contains(&m),
262 "kquant_mmvq_groups_w: m={m} outside 1..=8"
263 );
264 anyhow::ensure!(
265 k.is_multiple_of(QK_K),
266 "kquant_mmvq_groups_w: k={k} is not a multiple of {QK_K}"
267 );
268 anyhow::ensure!(n_groups >= 1, "kquant_mmvq_groups_w: no groups");
269 KernelLaunch::new(gpu, kernel)
270 .grid([div_ceil(n, 4), n_groups, 1])
271 .block([32, 4, 1])
272 .arg_ptr(w_blocks)
273 .arg_ptr(y_q8)
274 .arg_ptr(out_bf16)
275 .arg_u32(k)
276 .arg_u32(n)
277 .arg_u32(m)
278 .arg_u32(n_groups)
279 .launch(stream)
280}
281
282#[allow(clippy::too_many_arguments)]
286pub fn kquant_mmvq_pair_w(
287 gpu: &dyn GpuBackend,
288 kernel: KernelHandle,
289 (w0, out0, n0): (DevicePtr, DevicePtr, u32),
290 (w1, out1, n1): (DevicePtr, DevicePtr, u32),
291 y_q8: DevicePtr,
292 k: u32,
293 m: u32,
294 stream: u64,
295) -> Result<()> {
296 anyhow::ensure!(
297 (1..=8).contains(&m),
298 "kquant_mmvq_pair_w: m={m} outside 1..=8"
299 );
300 anyhow::ensure!(
301 k.is_multiple_of(QK_K),
302 "kquant_mmvq_pair_w: k={k} is not a multiple of {QK_K}"
303 );
304 anyhow::ensure!(n0 >= 1 && n1 >= 1, "kquant_mmvq_pair_w: empty projection");
305 KernelLaunch::new(gpu, kernel)
306 .grid([div_ceil(n0.max(n1), 4), 2, 1])
307 .block([32, 4, 1])
308 .arg_ptr(w0)
309 .arg_ptr(out0)
310 .arg_u32(n0)
311 .arg_ptr(w1)
312 .arg_ptr(out1)
313 .arg_u32(n1)
314 .arg_ptr(y_q8)
315 .arg_u32(k)
316 .arg_u32(m)
317 .launch(stream)
318}
319
320#[allow(clippy::too_many_arguments)]
324pub fn kquant_mmvq_experts_wn(
325 gpu: &dyn GpuBackend,
326 kernel: KernelHandle,
327 w_tables: DevicePtr,
328 y_q8: DevicePtr,
329 out_bf16: DevicePtr,
330 n: u32,
331 k: u32,
332 m: u32,
333 n_experts: u32,
334 y_stride_bytes: u32,
335 nwarps: u32,
336 stream: u64,
337) -> Result<()> {
338 anyhow::ensure!(
339 (1..=8).contains(&m),
340 "kquant_mmvq_experts_wn: m={m} outside 1..=8"
341 );
342 anyhow::ensure!(
343 k.is_multiple_of(QK_K),
344 "kquant_mmvq_experts_wn: k={k} is not a multiple of {QK_K}"
345 );
346 anyhow::ensure!(
347 matches!(nwarps, 2 | 4 | 8),
348 "kquant_mmvq_experts_wn: nwarps={nwarps} not in 2/4/8"
349 );
350 KernelLaunch::new(gpu, kernel)
351 .grid([div_ceil(n, nwarps), n_experts, 1])
352 .block([32, nwarps, 1])
353 .arg_ptr(w_tables)
354 .arg_ptr(y_q8)
355 .arg_ptr(out_bf16)
356 .arg_u32(k)
357 .arg_u32(n)
358 .arg_u32(m)
359 .arg_u32(y_stride_bytes)
360 .launch(stream)
361}
362
363pub fn kquant_mmvq_experts_w(
366 gpu: &dyn GpuBackend,
367 kernel: KernelHandle,
368 vxs: DevicePtr,
369 y_q8: DevicePtr,
370 out_bf16: DevicePtr,
371 n: u32,
372 k: u32,
373 m: u32,
374 n_experts: u32,
375 y_stride_bytes: u32,
376 stream: u64,
377) -> Result<()> {
378 anyhow::ensure!(
379 (1..=8).contains(&m),
380 "kquant_mmvq_experts_w: m={m} outside 1..=8"
381 );
382 anyhow::ensure!(
383 k.is_multiple_of(QK_K),
384 "kquant_mmvq_experts_w: k={k} is not a multiple of {QK_K}"
385 );
386 KernelLaunch::new(gpu, kernel)
387 .grid([div_ceil(n, 4), n_experts, 1])
388 .block([32, 4, 1])
389 .arg_ptr(vxs)
390 .arg_ptr(y_q8)
391 .arg_ptr(out_bf16)
392 .arg_u32(k)
393 .arg_u32(n)
394 .arg_u32(m)
395 .arg_u32(y_stride_bytes)
396 .launch(stream)
397}
398
399pub fn kquant_mmq_gemm(
406 gpu: &dyn GpuBackend,
407 kernel_nc: KernelHandle,
408 kernel_wc: KernelHandle,
409 a_q8: DevicePtr,
410 w_blocks: DevicePtr,
411 out_bf16: DevicePtr,
412 m: u32,
413 n: u32,
414 k: u32,
415 smem: u32,
416 stream: u64,
417) -> Result<()> {
418 anyhow::ensure!(
419 k.is_multiple_of(QK_K),
420 "kquant_mmq_gemm: k={k} is not a multiple of {QK_K}"
421 );
422 let kernel = if n.is_multiple_of(128) {
423 kernel_nc
424 } else {
425 kernel_wc
426 };
427 KernelLaunch::new(gpu, kernel)
428 .grid([div_ceil(n, 128), div_ceil(m, 128), 1])
429 .block([32, 8, 1])
430 .shared_mem(smem)
431 .arg_ptr(w_blocks)
432 .arg_ptr(a_q8)
433 .arg_ptr(out_bf16)
434 .arg_u32(n)
435 .arg_u32(m)
436 .arg_u32(k)
437 .arg_u32(k / QK_K)
438 .arg_u32(m)
439 .arg_u32(n)
440 .launch(stream)
441}
442
443#[cfg(test)]
444mod tests {
445 use super::*;
446
447 #[test]
448 fn sizes_match_the_block_geometry() {
449 assert_eq!(kquant_weight_bytes(2304, 5120, Q2K_BLOCK_BYTES), 3_870_720);
452 assert_eq!(kquant_weight_bytes(5120, 2304, Q3K_BLOCK_BYTES), 5_068_800);
453 assert_eq!(kquant_q8_1_rows_bytes(1, 5120), 160 * 36);
454 assert_eq!(kquant_mmq_act_bytes(200, 5120), 256 * 40 * 144);
455 assert_eq!((Q2K_MMQ_SMEM, Q3K_MMQ_SMEM), (70_144, 61_952));
456 }
457}