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