1use core::marker::PhantomData;
25use std::sync::OnceLock;
26
27use cubecl::prelude::*;
28use cubecl::server::Handle;
29
30use crate::{ModelError, Result};
31
32pub const Q4_0_BLOCK: usize = 32;
34pub const Q4_0_BLOCK_BYTES: usize = 18;
36
37pub fn repack_q4_0(data: &[u8]) -> Result<(Vec<u32>, Vec<f32>)> {
42 if data.is_empty() || data.len() % Q4_0_BLOCK_BYTES != 0 {
43 return Err(ModelError::BadShape {
44 tensor: "q4_0 block stream".into(),
45 expected: vec![Q4_0_BLOCK_BYTES],
46 got: vec![data.len()],
47 });
48 }
49 let n_blocks = data.len() / Q4_0_BLOCK_BYTES;
50 let mut qs = Vec::with_capacity(n_blocks * 4);
51 let mut d = Vec::with_capacity(n_blocks);
52 for block in data.chunks_exact(Q4_0_BLOCK_BYTES) {
53 d.push(burn::tensor::f16::from_le_bytes([block[0], block[1]]).to_f32());
54 for w in 0..4 {
55 let o = 2 + 4 * w;
56 qs.push(u32::from_le_bytes([
57 block[o],
58 block[o + 1],
59 block[o + 2],
60 block[o + 3],
61 ]));
62 }
63 }
64 Ok((qs, d))
65}
66
67#[cube(launch_unchecked)]
71fn q4_0_dequant_kernel(qs: &Array<u32>, d: &Array<f32>, out: &mut Array<f32>, n: usize) {
72 if ABSOLUTE_POS < n {
73 let block = ABSOLUTE_POS / 32;
74 let j = ABSOLUTE_POS % 32;
75 let byte_idx = j % 16;
76 let word = qs[block * 4 + byte_idx / 4];
77 let byte = (word >> (u32::cast_from(byte_idx % 4) * 8)) & 0xFF;
78 let mut nib = byte & 0xF;
79 if j >= 16 {
80 nib = byte >> 4;
81 }
82 out[ABSOLUTE_POS] = f32::cast_from(i32::cast_from(nib) - 8) * d[block];
83 }
84}
85
86#[cube(launch_unchecked)]
92fn q4_0_matmul_kernel(
93 x: &Array<f32>,
94 qs: &Array<u32>,
95 d: &Array<f32>,
96 out: &mut Array<f32>,
97 m: usize,
98 k: usize,
99 n_out: usize,
100) {
101 if ABSOLUTE_POS < m * n_out {
102 let row = ABSOLUTE_POS / n_out;
103 let col = ABSOLUTE_POS % n_out;
104 let blocks_per_row = k / 32;
105 let mut acc = 0.0f32;
106 for kb in 0..blocks_per_row {
107 let block = col * blocks_per_row + kb;
108 let x_base = row * k + kb * 32;
109 let mut block_acc = 0.0f32;
110 for w in 0..4usize {
111 let word = qs[block * 4 + w];
112 for b in 0..4usize {
113 let byte = (word >> (u32::cast_from(b) * 8)) & 0xFF;
114 let jj = w * 4 + b;
115 let lo = f32::cast_from(i32::cast_from(byte & 0xF) - 8);
116 let hi = f32::cast_from(i32::cast_from(byte >> 4) - 8);
117 block_acc += lo * x[x_base + jj];
118 block_acc += hi * x[x_base + 16 + jj];
119 }
120 }
121 acc += d[block] * block_acc;
122 }
123 out[row * n_out + col] = acc;
124 }
125}
126
127const CUBE_DIM: u32 = 256;
129
130const MAX_CUBES_PER_DIM: u32 = 65535;
132
133fn cube_count_1d(total: u32) -> CubeCount {
134 CubeCount::Static(total.div_ceil(CUBE_DIM).max(1), 1, 1)
135}
136
137fn cube_count_capped(total: u32) -> CubeCount {
142 let cubes = total.div_ceil(CUBE_DIM).max(1);
143 if cubes <= MAX_CUBES_PER_DIM {
144 CubeCount::Static(cubes, 1, 1)
145 } else {
146 let y = cubes.div_ceil(MAX_CUBES_PER_DIM);
147 CubeCount::Static(MAX_CUBES_PER_DIM, y, 1)
148 }
149}
150
151fn cube_count_tiled(n_out: u32, m: u32) -> CubeCount {
156 CubeCount::Static(n_out.div_ceil(CUBE_DIM).max(1), m.max(1), 1)
157}
158
159fn tiled_enabled() -> bool {
162 static DISABLED: OnceLock<bool> = OnceLock::new();
163 !*DISABLED.get_or_init(|| {
164 matches!(std::env::var("COMBS_NO_TILED_MATMUL").as_deref(), Ok("1"))
165 })
166}
167
168pub fn dequantize_q4_0_gpu<R: Runtime>(client: &ComputeClient<R>, data: &[u8]) -> Result<Vec<f32>> {
172 let (qs, d) = repack_q4_0(data)?;
173 let n = d.len() * Q4_0_BLOCK;
174 let qs_h = client.create_from_slice(u32::as_bytes(&qs));
175 let d_h = client.create_from_slice(f32::as_bytes(&d));
176 let out_h = client.empty(n * core::mem::size_of::<f32>());
177 unsafe {
178 q4_0_dequant_kernel::launch_unchecked::<R>(
179 client,
180 cube_count_1d(n as u32),
181 CubeDim::new_1d(CUBE_DIM),
182 ArrayArg::from_raw_parts(qs_h, qs.len()),
183 ArrayArg::from_raw_parts(d_h, d.len()),
184 ArrayArg::from_raw_parts(out_h.clone(), n),
185 n,
186 );
187 }
188 let bytes = client.read_one_unchecked(out_h);
189 Ok(f32::from_bytes(&bytes).to_vec())
190}
191
192pub struct Q40Weight<R: Runtime> {
196 qs: Handle,
197 d: Handle,
198 n_out: usize,
199 k: usize,
200 _runtime: PhantomData<R>,
201}
202
203impl<R: Runtime> Q40Weight<R> {
204 pub fn from_gguf_bytes(
207 client: &ComputeClient<R>,
208 data: &[u8],
209 n_out: usize,
210 k: usize,
211 ) -> Result<Self> {
212 if k == 0 || k % Q4_0_BLOCK != 0 || data.len() != n_out * k / Q4_0_BLOCK * Q4_0_BLOCK_BYTES
213 {
214 return Err(ModelError::BadShape {
215 tensor: "q4_0 weight".into(),
216 expected: vec![n_out, k / Q4_0_BLOCK.max(1) * Q4_0_BLOCK_BYTES],
217 got: vec![data.len()],
218 });
219 }
220 let (qs, d) = repack_q4_0(data)?;
221 Ok(Q40Weight {
222 qs: client.create_from_slice(u32::as_bytes(&qs)),
223 d: client.create_from_slice(f32::as_bytes(&d)),
224 n_out,
225 k,
226 _runtime: PhantomData,
227 })
228 }
229
230 pub fn n_out(&self) -> usize {
232 self.n_out
233 }
234
235 pub fn k(&self) -> usize {
237 self.k
238 }
239
240 pub fn vram_bytes(&self) -> usize {
243 let n_blocks = self.n_out * self.k / Q4_0_BLOCK;
244 n_blocks * (16 + core::mem::size_of::<f32>())
245 }
246
247 pub fn matmul_device(&self, client: &ComputeClient<R>, x: Handle, m: usize) -> Handle {
251 let out_len = m * self.n_out;
252 let out_h = client.empty(out_len * core::mem::size_of::<f32>());
253 let n_blocks = self.n_out * self.k / Q4_0_BLOCK;
254 unsafe {
255 q4_0_matmul_kernel::launch_unchecked::<R>(
256 client,
257 cube_count_capped(out_len as u32),
258 CubeDim::new_1d(CUBE_DIM),
259 ArrayArg::from_raw_parts(x, m * self.k),
260 ArrayArg::from_raw_parts(self.qs.clone(), n_blocks * 4),
261 ArrayArg::from_raw_parts(self.d.clone(), n_blocks),
262 ArrayArg::from_raw_parts(out_h.clone(), out_len),
263 m,
264 self.k,
265 self.n_out,
266 );
267 }
268 out_h
269 }
270
271 pub fn matmul_host(&self, client: &ComputeClient<R>, x: &[f32], m: usize) -> Result<Vec<f32>> {
274 if m == 0 || x.len() != m * self.k {
275 return Err(ModelError::BadShape {
276 tensor: "q4_0 matmul input".into(),
277 expected: vec![m, self.k],
278 got: vec![x.len()],
279 });
280 }
281 let x_h = client.create_from_slice(f32::as_bytes(x));
282 let out_h = self.matmul_device(client, x_h, m);
283 let bytes = client.read_one_unchecked(out_h);
284 Ok(f32::from_bytes(&bytes).to_vec())
285 }
286}
287
288pub const Q5_0_BLOCK_BYTES: usize = 22;
296pub const Q8_0_BLOCK_BYTES: usize = 34;
298
299pub fn repack_q5_0(data: &[u8]) -> Result<(Vec<u32>, Vec<u32>, Vec<f32>)> {
302 if data.is_empty() || data.len() % Q5_0_BLOCK_BYTES != 0 {
303 return Err(ModelError::BadShape {
304 tensor: "q5_0 block stream".into(),
305 expected: vec![Q5_0_BLOCK_BYTES],
306 got: vec![data.len()],
307 });
308 }
309 let n_blocks = data.len() / Q5_0_BLOCK_BYTES;
310 let mut qs = Vec::with_capacity(n_blocks * 4);
311 let mut qh = Vec::with_capacity(n_blocks);
312 let mut d = Vec::with_capacity(n_blocks);
313 for block in data.chunks_exact(Q5_0_BLOCK_BYTES) {
314 d.push(burn::tensor::f16::from_le_bytes([block[0], block[1]]).to_f32());
315 qh.push(u32::from_le_bytes([block[2], block[3], block[4], block[5]]));
316 for w in 0..4 {
317 let o = 6 + 4 * w;
318 qs.push(u32::from_le_bytes([
319 block[o],
320 block[o + 1],
321 block[o + 2],
322 block[o + 3],
323 ]));
324 }
325 }
326 Ok((qs, qh, d))
327}
328
329pub fn repack_q8_0(data: &[u8]) -> Result<(Vec<u32>, Vec<f32>)> {
332 if data.is_empty() || data.len() % Q8_0_BLOCK_BYTES != 0 {
333 return Err(ModelError::BadShape {
334 tensor: "q8_0 block stream".into(),
335 expected: vec![Q8_0_BLOCK_BYTES],
336 got: vec![data.len()],
337 });
338 }
339 let n_blocks = data.len() / Q8_0_BLOCK_BYTES;
340 let mut qs = Vec::with_capacity(n_blocks * 8);
341 let mut d = Vec::with_capacity(n_blocks);
342 for block in data.chunks_exact(Q8_0_BLOCK_BYTES) {
343 d.push(burn::tensor::f16::from_le_bytes([block[0], block[1]]).to_f32());
344 for w in 0..8 {
345 let o = 2 + 4 * w;
346 qs.push(u32::from_le_bytes([
347 block[o],
348 block[o + 1],
349 block[o + 2],
350 block[o + 3],
351 ]));
352 }
353 }
354 Ok((qs, d))
355}
356
357#[cube(launch_unchecked)]
361fn q5_0_dequant_kernel(
362 qs: &Array<u32>,
363 qh: &Array<u32>,
364 d: &Array<f32>,
365 out: &mut Array<f32>,
366 n: usize,
367) {
368 if ABSOLUTE_POS < n {
369 let block = ABSOLUTE_POS / 32;
370 let j = ABSOLUTE_POS % 32;
371 let byte_idx = j % 16;
372 let word = qs[block * 4 + byte_idx / 4];
373 let byte = (word >> (u32::cast_from(byte_idx % 4) * 8)) & 0xFF;
374 let mut nib = byte & 0xF;
375 if j >= 16 {
376 nib = byte >> 4;
377 }
378 let hi_bit = (qh[block] >> u32::cast_from(j)) & 1;
379 let q = i32::cast_from(nib | (hi_bit << 4)) - 16;
380 out[ABSOLUTE_POS] = f32::cast_from(q) * d[block];
381 }
382}
383
384#[cube(launch_unchecked)]
386fn q5_0_matmul_kernel(
387 x: &Array<f32>,
388 qs: &Array<u32>,
389 qh: &Array<u32>,
390 d: &Array<f32>,
391 out: &mut Array<f32>,
392 m: usize,
393 k: usize,
394 n_out: usize,
395) {
396 if ABSOLUTE_POS < m * n_out {
397 let row = ABSOLUTE_POS / n_out;
398 let col = ABSOLUTE_POS % n_out;
399 let blocks_per_row = k / 32;
400 let mut acc = 0.0f32;
401 for kb in 0..blocks_per_row {
402 let block = col * blocks_per_row + kb;
403 let x_base = row * k + kb * 32;
404 let bits = qh[block];
405 let mut block_acc = 0.0f32;
406 for w in 0..4usize {
407 let word = qs[block * 4 + w];
408 for b in 0..4usize {
409 let byte = (word >> (u32::cast_from(b) * 8)) & 0xFF;
410 let jj = w * 4 + b;
411 let lo_bit = (bits >> u32::cast_from(jj)) & 1;
412 let hi_bit = (bits >> u32::cast_from(jj + 16)) & 1;
413 let lo = f32::cast_from(i32::cast_from((byte & 0xF) | (lo_bit << 4)) - 16);
414 let hi = f32::cast_from(i32::cast_from((byte >> 4) | (hi_bit << 4)) - 16);
415 block_acc += lo * x[x_base + jj];
416 block_acc += hi * x[x_base + 16 + jj];
417 }
418 }
419 acc += d[block] * block_acc;
420 }
421 out[row * n_out + col] = acc;
422 }
423}
424
425#[cube(launch_unchecked)]
427fn q8_0_dequant_kernel(qs: &Array<u32>, d: &Array<f32>, out: &mut Array<f32>, n: usize) {
428 if ABSOLUTE_POS < n {
429 let block = ABSOLUTE_POS / 32;
430 let j = ABSOLUTE_POS % 32;
431 let word = qs[block * 8 + j / 4];
432 let byte = (word >> (u32::cast_from(j % 4) * 8)) & 0xFF;
433 let q = (i32::cast_from(byte) << 24) >> 24;
434 out[ABSOLUTE_POS] = f32::cast_from(q) * d[block];
435 }
436}
437
438#[cube(launch_unchecked)]
440fn q8_0_matmul_kernel(
441 x: &Array<f32>,
442 qs: &Array<u32>,
443 d: &Array<f32>,
444 out: &mut Array<f32>,
445 m: usize,
446 k: usize,
447 n_out: usize,
448) {
449 if ABSOLUTE_POS < m * n_out {
450 let row = ABSOLUTE_POS / n_out;
451 let col = ABSOLUTE_POS % n_out;
452 let blocks_per_row = k / 32;
453 let mut acc = 0.0f32;
454 for kb in 0..blocks_per_row {
455 let block = col * blocks_per_row + kb;
456 let x_base = row * k + kb * 32;
457 let mut block_acc = 0.0f32;
458 for w in 0..8usize {
459 let word = qs[block * 8 + w];
460 for b in 0..4usize {
461 let byte = (word >> (u32::cast_from(b) * 8)) & 0xFF;
462 let q = (i32::cast_from(byte) << 24) >> 24;
463 block_acc += f32::cast_from(q) * x[x_base + w * 4 + b];
464 }
465 }
466 acc += d[block] * block_acc;
467 }
468 out[row * n_out + col] = acc;
469 }
470}
471
472#[cube(launch_unchecked)]
481fn q8_0_matmul_tiled_kernel(
482 x: &Array<f32>,
483 qs: &Array<u32>,
484 d: &Array<f32>,
485 out: &mut Array<f32>,
486 k: usize,
487 n_out: usize,
488) {
489 let mut staged = SharedMemory::<f32>::new(256usize);
490 let unit = UNIT_POS as usize;
491 let row = CUBE_POS_Y as usize;
492 let col = (CUBE_POS_X * CUBE_DIM + UNIT_POS) as usize;
493 let blocks_per_row = k / 32;
494 let n_tiles = (k + 255) / 256;
495 let mut acc = 0.0f32;
496 for t in 0..n_tiles {
497 let k0 = t * 256;
498 if k0 + unit < k {
499 staged[unit] = x[row * k + k0 + unit];
500 }
501 sync_cube();
502 if col < n_out {
503 let mut kb_end = (k0 + 256) / 32;
504 if blocks_per_row < kb_end {
505 kb_end = blocks_per_row;
506 }
507 for kb in (k0 / 32)..kb_end {
508 let block = col * blocks_per_row + kb;
509 let s_base = kb * 32 - k0;
510 let mut block_acc = 0.0f32;
511 for w in 0..8usize {
512 let word = qs[block * 8 + w];
513 for b in 0..4usize {
514 let byte = (word >> (u32::cast_from(b) * 8)) & 0xFF;
515 let q = (i32::cast_from(byte) << 24) >> 24;
516 block_acc += f32::cast_from(q) * staged[s_base + w * 4 + b];
517 }
518 }
519 acc += d[block] * block_acc;
520 }
521 }
522 sync_cube();
523 }
524 if col < n_out {
525 out[row * n_out + col] = acc;
526 }
527}
528
529pub fn dequantize_q5_0_gpu<R: Runtime>(client: &ComputeClient<R>, data: &[u8]) -> Result<Vec<f32>> {
531 let (qs, qh, d) = repack_q5_0(data)?;
532 let n = d.len() * Q4_0_BLOCK;
533 let qs_h = client.create_from_slice(u32::as_bytes(&qs));
534 let qh_h = client.create_from_slice(u32::as_bytes(&qh));
535 let d_h = client.create_from_slice(f32::as_bytes(&d));
536 let out_h = client.empty(n * core::mem::size_of::<f32>());
537 unsafe {
538 q5_0_dequant_kernel::launch_unchecked::<R>(
539 client,
540 cube_count_1d(n as u32),
541 CubeDim::new_1d(CUBE_DIM),
542 ArrayArg::from_raw_parts(qs_h, qs.len()),
543 ArrayArg::from_raw_parts(qh_h, qh.len()),
544 ArrayArg::from_raw_parts(d_h, d.len()),
545 ArrayArg::from_raw_parts(out_h.clone(), n),
546 n,
547 );
548 }
549 let bytes = client.read_one_unchecked(out_h);
550 Ok(f32::from_bytes(&bytes).to_vec())
551}
552
553pub fn dequantize_q8_0_gpu<R: Runtime>(client: &ComputeClient<R>, data: &[u8]) -> Result<Vec<f32>> {
555 let (qs, d) = repack_q8_0(data)?;
556 let n = d.len() * Q4_0_BLOCK;
557 let qs_h = client.create_from_slice(u32::as_bytes(&qs));
558 let d_h = client.create_from_slice(f32::as_bytes(&d));
559 let out_h = client.empty(n * core::mem::size_of::<f32>());
560 unsafe {
561 q8_0_dequant_kernel::launch_unchecked::<R>(
562 client,
563 cube_count_1d(n as u32),
564 CubeDim::new_1d(CUBE_DIM),
565 ArrayArg::from_raw_parts(qs_h, qs.len()),
566 ArrayArg::from_raw_parts(d_h, d.len()),
567 ArrayArg::from_raw_parts(out_h.clone(), n),
568 n,
569 );
570 }
571 let bytes = client.read_one_unchecked(out_h);
572 Ok(f32::from_bytes(&bytes).to_vec())
573}
574
575pub struct Q50Weight<R: Runtime> {
578 qs: Handle,
579 qh: Handle,
580 d: Handle,
581 n_out: usize,
582 k: usize,
583 _runtime: PhantomData<R>,
584}
585
586impl<R: Runtime> Q50Weight<R> {
587 pub fn from_gguf_bytes(
589 client: &ComputeClient<R>,
590 data: &[u8],
591 n_out: usize,
592 k: usize,
593 ) -> Result<Self> {
594 if k == 0 || k % Q4_0_BLOCK != 0 || data.len() != n_out * k / Q4_0_BLOCK * Q5_0_BLOCK_BYTES
595 {
596 return Err(ModelError::BadShape {
597 tensor: "q5_0 weight".into(),
598 expected: vec![n_out, k],
599 got: vec![data.len()],
600 });
601 }
602 let (qs, qh, d) = repack_q5_0(data)?;
603 Ok(Q50Weight {
604 qs: client.create_from_slice(u32::as_bytes(&qs)),
605 qh: client.create_from_slice(u32::as_bytes(&qh)),
606 d: client.create_from_slice(f32::as_bytes(&d)),
607 n_out,
608 k,
609 _runtime: PhantomData,
610 })
611 }
612
613 pub fn vram_bytes(&self) -> usize {
615 (self.n_out * self.k / Q4_0_BLOCK) * 24
616 }
617
618 pub fn matmul_device(&self, client: &ComputeClient<R>, x: Handle, m: usize) -> Handle {
620 let out_len = m * self.n_out;
621 let out_h = client.empty(out_len * core::mem::size_of::<f32>());
622 let n_blocks = self.n_out * self.k / Q4_0_BLOCK;
623 unsafe {
624 q5_0_matmul_kernel::launch_unchecked::<R>(
625 client,
626 cube_count_capped(out_len as u32),
627 CubeDim::new_1d(CUBE_DIM),
628 ArrayArg::from_raw_parts(x, m * self.k),
629 ArrayArg::from_raw_parts(self.qs.clone(), n_blocks * 4),
630 ArrayArg::from_raw_parts(self.qh.clone(), n_blocks),
631 ArrayArg::from_raw_parts(self.d.clone(), n_blocks),
632 ArrayArg::from_raw_parts(out_h.clone(), out_len),
633 m,
634 self.k,
635 self.n_out,
636 );
637 }
638 out_h
639 }
640
641 pub fn matmul_host(&self, client: &ComputeClient<R>, x: &[f32], m: usize) -> Result<Vec<f32>> {
643 if m == 0 || x.len() != m * self.k {
644 return Err(ModelError::BadShape {
645 tensor: "q5_0 matmul input".into(),
646 expected: vec![m, self.k],
647 got: vec![x.len()],
648 });
649 }
650 let x_h = client.create_from_slice(f32::as_bytes(x));
651 let out_h = self.matmul_device(client, x_h, m);
652 let bytes = client.read_one_unchecked(out_h);
653 Ok(f32::from_bytes(&bytes).to_vec())
654 }
655}
656
657pub struct Q80Weight<R: Runtime> {
660 qs: Handle,
661 d: Handle,
662 n_out: usize,
663 k: usize,
664 _runtime: PhantomData<R>,
665}
666
667impl<R: Runtime> Q80Weight<R> {
668 pub fn from_gguf_bytes(
670 client: &ComputeClient<R>,
671 data: &[u8],
672 n_out: usize,
673 k: usize,
674 ) -> Result<Self> {
675 if k == 0 || k % Q4_0_BLOCK != 0 || data.len() != n_out * k / Q4_0_BLOCK * Q8_0_BLOCK_BYTES
676 {
677 return Err(ModelError::BadShape {
678 tensor: "q8_0 weight".into(),
679 expected: vec![n_out, k],
680 got: vec![data.len()],
681 });
682 }
683 let (qs, d) = repack_q8_0(data)?;
684 Ok(Q80Weight {
685 qs: client.create_from_slice(u32::as_bytes(&qs)),
686 d: client.create_from_slice(f32::as_bytes(&d)),
687 n_out,
688 k,
689 _runtime: PhantomData,
690 })
691 }
692
693 pub fn vram_bytes(&self) -> usize {
695 (self.n_out * self.k / Q4_0_BLOCK) * 36
696 }
697
698 pub fn matmul_device(&self, client: &ComputeClient<R>, x: Handle, m: usize) -> Handle {
702 self.matmul_device_with(client, x, m, m > 1 && tiled_enabled())
703 }
704
705 pub(crate) fn matmul_device_with(
708 &self,
709 client: &ComputeClient<R>,
710 x: Handle,
711 m: usize,
712 tiled: bool,
713 ) -> Handle {
714 let out_len = m * self.n_out;
715 let out_h = client.empty(out_len * core::mem::size_of::<f32>());
716 let n_blocks = self.n_out * self.k / Q4_0_BLOCK;
717 if tiled {
718 unsafe {
719 q8_0_matmul_tiled_kernel::launch_unchecked::<R>(
720 client,
721 cube_count_tiled(self.n_out as u32, m as u32),
722 CubeDim::new_1d(CUBE_DIM),
723 ArrayArg::from_raw_parts(x, m * self.k),
724 ArrayArg::from_raw_parts(self.qs.clone(), n_blocks * 8),
725 ArrayArg::from_raw_parts(self.d.clone(), n_blocks),
726 ArrayArg::from_raw_parts(out_h.clone(), out_len),
727 self.k,
728 self.n_out,
729 );
730 }
731 } else {
732 unsafe {
733 q8_0_matmul_kernel::launch_unchecked::<R>(
734 client,
735 cube_count_capped(out_len as u32),
736 CubeDim::new_1d(CUBE_DIM),
737 ArrayArg::from_raw_parts(x, m * self.k),
738 ArrayArg::from_raw_parts(self.qs.clone(), n_blocks * 8),
739 ArrayArg::from_raw_parts(self.d.clone(), n_blocks),
740 ArrayArg::from_raw_parts(out_h.clone(), out_len),
741 m,
742 self.k,
743 self.n_out,
744 );
745 }
746 }
747 out_h
748 }
749
750 pub fn matmul_host(&self, client: &ComputeClient<R>, x: &[f32], m: usize) -> Result<Vec<f32>> {
752 if m == 0 || x.len() != m * self.k {
753 return Err(ModelError::BadShape {
754 tensor: "q8_0 matmul input".into(),
755 expected: vec![m, self.k],
756 got: vec![x.len()],
757 });
758 }
759 let x_h = client.create_from_slice(f32::as_bytes(x));
760 let out_h = self.matmul_device(client, x_h, m);
761 let bytes = client.read_one_unchecked(out_h);
762 Ok(f32::from_bytes(&bytes).to_vec())
763 }
764}
765
766pub const K_SUPERBLOCK: usize = 256;
772pub const Q4_K_BLOCK_BYTES: usize = 144;
774pub const Q5_K_BLOCK_BYTES: usize = 176;
776pub const Q6_K_BLOCK_BYTES: usize = 210;
778
779#[cube]
781fn byte_at(words: &Array<u32>, idx: usize) -> u32 {
782 (words[idx / 4] >> (u32::cast_from(idx % 4) * 8)) & 0xFF
783}
784
785#[cube]
787fn i8_at(words: &Array<u32>, idx: usize) -> i32 {
788 (i32::cast_from(byte_at(words, idx)) << 24) >> 24
789}
790
791#[cube]
795fn k4_scale(scales: &Array<u32>, base: usize, j: usize) -> u32 {
796 let mut v = 0u32;
797 if j < 4 {
798 v = byte_at(scales, base + j) & 63;
799 } else {
800 v = (byte_at(scales, base + j + 4) & 0xF) | ((byte_at(scales, base + j - 4) >> 6) << 4);
801 }
802 v
803}
804
805#[cube]
807fn k4_min(scales: &Array<u32>, base: usize, j: usize) -> u32 {
808 let mut v = 0u32;
809 if j < 4 {
810 v = byte_at(scales, base + j + 4) & 63;
811 } else {
812 v = (byte_at(scales, base + j + 4) >> 4) | ((byte_at(scales, base + j) >> 6) << 4);
813 }
814 v
815}
816
817pub fn repack_q4_k(data: &[u8]) -> Result<(Vec<u32>, Vec<f32>, Vec<u32>)> {
821 if data.is_empty() || data.len() % Q4_K_BLOCK_BYTES != 0 {
822 return Err(ModelError::BadShape {
823 tensor: "q4_k superblock stream".into(),
824 expected: vec![Q4_K_BLOCK_BYTES],
825 got: vec![data.len()],
826 });
827 }
828 let n_sb = data.len() / Q4_K_BLOCK_BYTES;
829 let mut qs = Vec::with_capacity(n_sb * 32);
830 let mut dd = Vec::with_capacity(n_sb * 2);
831 let mut scales = Vec::with_capacity(n_sb * 3);
832 for sb in data.chunks_exact(Q4_K_BLOCK_BYTES) {
833 dd.push(burn::tensor::f16::from_le_bytes([sb[0], sb[1]]).to_f32());
834 dd.push(burn::tensor::f16::from_le_bytes([sb[2], sb[3]]).to_f32());
835 for w in 0..3 {
836 let o = 4 + 4 * w;
837 scales.push(u32::from_le_bytes([sb[o], sb[o + 1], sb[o + 2], sb[o + 3]]));
838 }
839 for w in 0..32 {
840 let o = 16 + 4 * w;
841 qs.push(u32::from_le_bytes([sb[o], sb[o + 1], sb[o + 2], sb[o + 3]]));
842 }
843 }
844 Ok((qs, dd, scales))
845}
846
847#[cube(launch_unchecked)]
850fn q4_k_dequant_kernel(
851 qs: &Array<u32>,
852 dd: &Array<f32>,
853 scales: &Array<u32>,
854 out: &mut Array<f32>,
855 n: usize,
856) {
857 if ABSOLUTE_POS < n {
858 let sb = ABSOLUTE_POS / 256;
859 let r = ABSOLUTE_POS % 256;
860 let j = r / 64; let t = (r % 64) / 32; let l = r % 32;
863 let byte = byte_at(qs, sb * 128 + j * 32 + l);
864 let mut q = byte & 0xF;
865 if t == 1 {
866 q = byte >> 4;
867 }
868 let sidx = 2 * j + t;
869 let sc = k4_scale(scales, sb * 12, sidx);
870 let mn = k4_min(scales, sb * 12, sidx);
871 let d1 = dd[sb * 2] * f32::cast_from(sc);
872 let fmin = dd[sb * 2 + 1] * f32::cast_from(mn);
873 out[ABSOLUTE_POS] = d1 * f32::cast_from(q) - fmin;
874 }
875}
876
877#[cube(launch_unchecked)]
881fn q4_k_matmul_kernel(
882 x: &Array<f32>,
883 qs: &Array<u32>,
884 dd: &Array<f32>,
885 scales: &Array<u32>,
886 out: &mut Array<f32>,
887 m: usize,
888 k: usize,
889 n_out: usize,
890) {
891 if ABSOLUTE_POS < m * n_out {
892 let row = ABSOLUTE_POS / n_out;
893 let col = ABSOLUTE_POS % n_out;
894 let sb_per_row = k / 256;
895 let mut acc = 0.0f32;
896 for sbi in 0..sb_per_row {
897 let sb = col * sb_per_row + sbi;
898 let d = dd[sb * 2];
899 let dmin = dd[sb * 2 + 1];
900 let s_base = sb * 12;
901 let x_base = row * k + sbi * 256;
902 for j in 0..4usize {
903 let mut sum_lo = 0.0f32;
904 let mut sum_hi = 0.0f32;
905 let mut xs_lo = 0.0f32;
906 let mut xs_hi = 0.0f32;
907 for w in 0..8usize {
908 let word = qs[sb * 32 + j * 8 + w];
909 for b in 0..4usize {
910 let byte = (word >> (u32::cast_from(b) * 8)) & 0xFF;
911 let l = 4 * w + b;
912 let x1 = x[x_base + 64 * j + l];
913 let x2 = x[x_base + 64 * j + 32 + l];
914 sum_lo += f32::cast_from(byte & 0xF) * x1;
915 sum_hi += f32::cast_from(byte >> 4) * x2;
916 xs_lo += x1;
917 xs_hi += x2;
918 }
919 }
920 let sc1 = f32::cast_from(k4_scale(scales, s_base, 2 * j));
921 let mn1 = f32::cast_from(k4_min(scales, s_base, 2 * j));
922 let sc2 = f32::cast_from(k4_scale(scales, s_base, 2 * j + 1));
923 let mn2 = f32::cast_from(k4_min(scales, s_base, 2 * j + 1));
924 acc += d * sc1 * sum_lo - dmin * mn1 * xs_lo;
925 acc += d * sc2 * sum_hi - dmin * mn2 * xs_hi;
926 }
927 }
928 out[row * n_out + col] = acc;
929 }
930}
931
932#[cube(launch_unchecked)]
939fn q4_k_matmul_tiled_kernel(
940 x: &Array<f32>,
941 qs: &Array<u32>,
942 dd: &Array<f32>,
943 scales: &Array<u32>,
944 out: &mut Array<f32>,
945 k: usize,
946 n_out: usize,
947) {
948 let mut staged = SharedMemory::<f32>::new(256usize);
949 let unit = UNIT_POS as usize;
950 let row = CUBE_POS_Y as usize;
951 let col = (CUBE_POS_X * CUBE_DIM + UNIT_POS) as usize;
952 let sb_per_row = k / 256;
953 let mut acc = 0.0f32;
954 for sbi in 0..sb_per_row {
955 staged[unit] = x[row * k + sbi * 256 + unit];
956 sync_cube();
957 if col < n_out {
958 let sb = col * sb_per_row + sbi;
959 let d = dd[sb * 2];
960 let dmin = dd[sb * 2 + 1];
961 let s_base = sb * 12;
962 for j in 0..4usize {
963 let mut sum_lo = 0.0f32;
964 let mut sum_hi = 0.0f32;
965 let mut xs_lo = 0.0f32;
966 let mut xs_hi = 0.0f32;
967 for w in 0..8usize {
968 let word = qs[sb * 32 + j * 8 + w];
969 for b in 0..4usize {
970 let byte = (word >> (u32::cast_from(b) * 8)) & 0xFF;
971 let l = 4 * w + b;
972 let x1 = staged[64 * j + l];
973 let x2 = staged[64 * j + 32 + l];
974 sum_lo += f32::cast_from(byte & 0xF) * x1;
975 sum_hi += f32::cast_from(byte >> 4) * x2;
976 xs_lo += x1;
977 xs_hi += x2;
978 }
979 }
980 let sc1 = f32::cast_from(k4_scale(scales, s_base, 2 * j));
981 let mn1 = f32::cast_from(k4_min(scales, s_base, 2 * j));
982 let sc2 = f32::cast_from(k4_scale(scales, s_base, 2 * j + 1));
983 let mn2 = f32::cast_from(k4_min(scales, s_base, 2 * j + 1));
984 acc += d * sc1 * sum_lo - dmin * mn1 * xs_lo;
985 acc += d * sc2 * sum_hi - dmin * mn2 * xs_hi;
986 }
987 }
988 sync_cube();
989 }
990 if col < n_out {
991 out[row * n_out + col] = acc;
992 }
993}
994
995pub fn repack_q5_k(data: &[u8]) -> Result<(Vec<u32>, Vec<u32>, Vec<f32>, Vec<u32>)> {
999 if data.is_empty() || data.len() % Q5_K_BLOCK_BYTES != 0 {
1000 return Err(ModelError::BadShape {
1001 tensor: "q5_k superblock stream".into(),
1002 expected: vec![Q5_K_BLOCK_BYTES],
1003 got: vec![data.len()],
1004 });
1005 }
1006 let n_sb = data.len() / Q5_K_BLOCK_BYTES;
1007 let word = |sb: &[u8], o: usize| u32::from_le_bytes([sb[o], sb[o + 1], sb[o + 2], sb[o + 3]]);
1008 let mut qs = Vec::with_capacity(n_sb * 32);
1009 let mut qh = Vec::with_capacity(n_sb * 8);
1010 let mut dd = Vec::with_capacity(n_sb * 2);
1011 let mut scales = Vec::with_capacity(n_sb * 3);
1012 for sb in data.chunks_exact(Q5_K_BLOCK_BYTES) {
1013 dd.push(burn::tensor::f16::from_le_bytes([sb[0], sb[1]]).to_f32());
1014 dd.push(burn::tensor::f16::from_le_bytes([sb[2], sb[3]]).to_f32());
1015 for w in 0..3 {
1016 scales.push(word(sb, 4 + 4 * w));
1017 }
1018 for w in 0..8 {
1019 qh.push(word(sb, 16 + 4 * w));
1020 }
1021 for w in 0..32 {
1022 qs.push(word(sb, 48 + 4 * w));
1023 }
1024 }
1025 Ok((qs, qh, dd, scales))
1026}
1027
1028#[cube(launch_unchecked)]
1032fn q5_k_dequant_kernel(
1033 qs: &Array<u32>,
1034 qh: &Array<u32>,
1035 dd: &Array<f32>,
1036 scales: &Array<u32>,
1037 out: &mut Array<f32>,
1038 n: usize,
1039) {
1040 if ABSOLUTE_POS < n {
1041 let sb = ABSOLUTE_POS / 256;
1042 let r = ABSOLUTE_POS % 256;
1043 let j = r / 64; let t = (r % 64) / 32; let l = r % 32;
1046 let byte = byte_at(qs, sb * 128 + j * 32 + l);
1047 let mut nib = byte & 0xF;
1048 if t == 1 {
1049 nib = byte >> 4;
1050 }
1051 let hi = (byte_at(qh, sb * 32 + l) >> u32::cast_from(2 * j + t)) & 1;
1052 let sidx = 2 * j + t;
1053 let sc = k4_scale(scales, sb * 12, sidx);
1054 let mn = k4_min(scales, sb * 12, sidx);
1055 let d1 = dd[sb * 2] * f32::cast_from(sc);
1056 let fmin = dd[sb * 2 + 1] * f32::cast_from(mn);
1057 out[ABSOLUTE_POS] = d1 * f32::cast_from(nib | (hi << 4)) - fmin;
1058 }
1059}
1060
1061#[cube(launch_unchecked)]
1064fn q5_k_matmul_kernel(
1065 x: &Array<f32>,
1066 qs: &Array<u32>,
1067 qh: &Array<u32>,
1068 dd: &Array<f32>,
1069 scales: &Array<u32>,
1070 out: &mut Array<f32>,
1071 m: usize,
1072 k: usize,
1073 n_out: usize,
1074) {
1075 if ABSOLUTE_POS < m * n_out {
1076 let row = ABSOLUTE_POS / n_out;
1077 let col = ABSOLUTE_POS % n_out;
1078 let sb_per_row = k / 256;
1079 let mut acc = 0.0f32;
1080 for sbi in 0..sb_per_row {
1081 let sb = col * sb_per_row + sbi;
1082 let d = dd[sb * 2];
1083 let dmin = dd[sb * 2 + 1];
1084 let s_base = sb * 12;
1085 let x_base = row * k + sbi * 256;
1086 for j in 0..4usize {
1087 let mut sum_lo = 0.0f32;
1088 let mut sum_hi = 0.0f32;
1089 let mut xs_lo = 0.0f32;
1090 let mut xs_hi = 0.0f32;
1091 for w in 0..8usize {
1092 let word = qs[sb * 32 + j * 8 + w];
1093 for b in 0..4usize {
1094 let byte = (word >> (u32::cast_from(b) * 8)) & 0xFF;
1095 let l = 4 * w + b;
1096 let hb = byte_at(qh, sb * 32 + l);
1097 let hi_lo = (hb >> u32::cast_from(2 * j)) & 1;
1098 let hi_hi = (hb >> u32::cast_from(2 * j + 1)) & 1;
1099 let x1 = x[x_base + 64 * j + l];
1100 let x2 = x[x_base + 64 * j + 32 + l];
1101 sum_lo += f32::cast_from((byte & 0xF) | (hi_lo << 4)) * x1;
1102 sum_hi += f32::cast_from((byte >> 4) | (hi_hi << 4)) * x2;
1103 xs_lo += x1;
1104 xs_hi += x2;
1105 }
1106 }
1107 let sc1 = f32::cast_from(k4_scale(scales, s_base, 2 * j));
1108 let mn1 = f32::cast_from(k4_min(scales, s_base, 2 * j));
1109 let sc2 = f32::cast_from(k4_scale(scales, s_base, 2 * j + 1));
1110 let mn2 = f32::cast_from(k4_min(scales, s_base, 2 * j + 1));
1111 acc += d * sc1 * sum_lo - dmin * mn1 * xs_lo;
1112 acc += d * sc2 * sum_hi - dmin * mn2 * xs_hi;
1113 }
1114 }
1115 out[row * n_out + col] = acc;
1116 }
1117}
1118
1119pub fn repack_q6_k(data: &[u8]) -> Result<(Vec<u32>, Vec<u32>, Vec<u32>, Vec<f32>)> {
1123 if data.is_empty() || data.len() % Q6_K_BLOCK_BYTES != 0 {
1124 return Err(ModelError::BadShape {
1125 tensor: "q6_k superblock stream".into(),
1126 expected: vec![Q6_K_BLOCK_BYTES],
1127 got: vec![data.len()],
1128 });
1129 }
1130 let n_sb = data.len() / Q6_K_BLOCK_BYTES;
1131 let word = |sb: &[u8], o: usize| u32::from_le_bytes([sb[o], sb[o + 1], sb[o + 2], sb[o + 3]]);
1132 let mut ql = Vec::with_capacity(n_sb * 32);
1133 let mut qh = Vec::with_capacity(n_sb * 16);
1134 let mut sc = Vec::with_capacity(n_sb * 4);
1135 let mut d = Vec::with_capacity(n_sb);
1136 for sb in data.chunks_exact(Q6_K_BLOCK_BYTES) {
1137 for w in 0..32 {
1138 ql.push(word(sb, 4 * w));
1139 }
1140 for w in 0..16 {
1141 qh.push(word(sb, 128 + 4 * w));
1142 }
1143 for w in 0..4 {
1144 sc.push(word(sb, 192 + 4 * w));
1145 }
1146 d.push(burn::tensor::f16::from_le_bytes([sb[208], sb[209]]).to_f32());
1147 }
1148 Ok((ql, qh, sc, d))
1149}
1150
1151#[cube(launch_unchecked)]
1155fn q6_k_dequant_kernel(
1156 ql: &Array<u32>,
1157 qh: &Array<u32>,
1158 sc: &Array<u32>,
1159 d: &Array<f32>,
1160 out: &mut Array<f32>,
1161 n: usize,
1162) {
1163 if ABSOLUTE_POS < n {
1164 let sb = ABSOLUTE_POS / 256;
1165 let r = ABSOLUTE_POS % 256;
1166 let half = r / 128;
1167 let t = (r % 128) / 32; let l = r % 32;
1169 let ql_byte = byte_at(ql, sb * 128 + half * 64 + (t % 2) * 32 + l);
1170 let mut nib = ql_byte & 0xF;
1171 if t >= 2 {
1172 nib = ql_byte >> 4;
1173 }
1174 let hi = (byte_at(qh, sb * 64 + half * 32 + l) >> (u32::cast_from(t) * 2)) & 3;
1175 let q = i32::cast_from(nib | (hi << 4)) - 32;
1176 let scale = i8_at(sc, sb * 16 + half * 8 + l / 16 + 2 * t);
1177 out[ABSOLUTE_POS] = d[sb] * f32::cast_from(scale) * f32::cast_from(q);
1178 }
1179}
1180
1181#[cube(launch_unchecked)]
1184fn q6_k_matmul_kernel(
1185 x: &Array<f32>,
1186 ql: &Array<u32>,
1187 qh: &Array<u32>,
1188 sc: &Array<u32>,
1189 d: &Array<f32>,
1190 out: &mut Array<f32>,
1191 m: usize,
1192 k: usize,
1193 n_out: usize,
1194) {
1195 if ABSOLUTE_POS < m * n_out {
1196 let row = ABSOLUTE_POS / n_out;
1197 let col = ABSOLUTE_POS % n_out;
1198 let sb_per_row = k / 256;
1199 let mut acc = 0.0f32;
1200 for sbi in 0..sb_per_row {
1201 let sb = col * sb_per_row + sbi;
1202 let dsb = d[sb];
1203 let x_base = row * k + sbi * 256;
1204 for half in 0..2usize {
1205 for t in 0..4usize {
1206 for g in 0..2usize {
1207 let mut sum = 0.0f32;
1208 for l0 in 0..16usize {
1209 let l = g * 16 + l0;
1210 let ql_byte = byte_at(ql, sb * 128 + half * 64 + (t % 2) * 32 + l);
1211 let mut nib = ql_byte & 0xF;
1212 if t >= 2 {
1213 nib = ql_byte >> 4;
1214 }
1215 let hi =
1216 (byte_at(qh, sb * 64 + half * 32 + l) >> (u32::cast_from(t) * 2))
1217 & 3;
1218 let q = i32::cast_from(nib | (hi << 4)) - 32;
1219 sum += f32::cast_from(q) * x[x_base + half * 128 + t * 32 + l];
1220 }
1221 let scale = i8_at(sc, sb * 16 + half * 8 + g + 2 * t);
1222 acc += dsb * f32::cast_from(scale) * sum;
1223 }
1224 }
1225 }
1226 }
1227 out[row * n_out + col] = acc;
1228 }
1229}
1230
1231pub fn dequantize_q4_k_gpu<R: Runtime>(client: &ComputeClient<R>, data: &[u8]) -> Result<Vec<f32>> {
1233 let (qs, dd, scales) = repack_q4_k(data)?;
1234 let n = (dd.len() / 2) * K_SUPERBLOCK;
1235 let qs_h = client.create_from_slice(u32::as_bytes(&qs));
1236 let dd_h = client.create_from_slice(f32::as_bytes(&dd));
1237 let sc_h = client.create_from_slice(u32::as_bytes(&scales));
1238 let out_h = client.empty(n * core::mem::size_of::<f32>());
1239 unsafe {
1240 q4_k_dequant_kernel::launch_unchecked::<R>(
1241 client,
1242 cube_count_1d(n as u32),
1243 CubeDim::new_1d(CUBE_DIM),
1244 ArrayArg::from_raw_parts(qs_h, qs.len()),
1245 ArrayArg::from_raw_parts(dd_h, dd.len()),
1246 ArrayArg::from_raw_parts(sc_h, scales.len()),
1247 ArrayArg::from_raw_parts(out_h.clone(), n),
1248 n,
1249 );
1250 }
1251 let bytes = client.read_one_unchecked(out_h);
1252 Ok(f32::from_bytes(&bytes).to_vec())
1253}
1254
1255pub fn dequantize_q5_k_gpu<R: Runtime>(client: &ComputeClient<R>, data: &[u8]) -> Result<Vec<f32>> {
1257 let (qs, qh, dd, scales) = repack_q5_k(data)?;
1258 let n = (dd.len() / 2) * K_SUPERBLOCK;
1259 let qs_h = client.create_from_slice(u32::as_bytes(&qs));
1260 let qh_h = client.create_from_slice(u32::as_bytes(&qh));
1261 let dd_h = client.create_from_slice(f32::as_bytes(&dd));
1262 let sc_h = client.create_from_slice(u32::as_bytes(&scales));
1263 let out_h = client.empty(n * core::mem::size_of::<f32>());
1264 unsafe {
1265 q5_k_dequant_kernel::launch_unchecked::<R>(
1266 client,
1267 cube_count_1d(n as u32),
1268 CubeDim::new_1d(CUBE_DIM),
1269 ArrayArg::from_raw_parts(qs_h, qs.len()),
1270 ArrayArg::from_raw_parts(qh_h, qh.len()),
1271 ArrayArg::from_raw_parts(dd_h, dd.len()),
1272 ArrayArg::from_raw_parts(sc_h, scales.len()),
1273 ArrayArg::from_raw_parts(out_h.clone(), n),
1274 n,
1275 );
1276 }
1277 let bytes = client.read_one_unchecked(out_h);
1278 Ok(f32::from_bytes(&bytes).to_vec())
1279}
1280
1281pub fn dequantize_q6_k_gpu<R: Runtime>(client: &ComputeClient<R>, data: &[u8]) -> Result<Vec<f32>> {
1283 let (ql, qh, sc, d) = repack_q6_k(data)?;
1284 let n = d.len() * K_SUPERBLOCK;
1285 let ql_h = client.create_from_slice(u32::as_bytes(&ql));
1286 let qh_h = client.create_from_slice(u32::as_bytes(&qh));
1287 let sc_h = client.create_from_slice(u32::as_bytes(&sc));
1288 let d_h = client.create_from_slice(f32::as_bytes(&d));
1289 let out_h = client.empty(n * core::mem::size_of::<f32>());
1290 unsafe {
1291 q6_k_dequant_kernel::launch_unchecked::<R>(
1292 client,
1293 cube_count_1d(n as u32),
1294 CubeDim::new_1d(CUBE_DIM),
1295 ArrayArg::from_raw_parts(ql_h, ql.len()),
1296 ArrayArg::from_raw_parts(qh_h, qh.len()),
1297 ArrayArg::from_raw_parts(sc_h, sc.len()),
1298 ArrayArg::from_raw_parts(d_h, d.len()),
1299 ArrayArg::from_raw_parts(out_h.clone(), n),
1300 n,
1301 );
1302 }
1303 let bytes = client.read_one_unchecked(out_h);
1304 Ok(f32::from_bytes(&bytes).to_vec())
1305}
1306
1307pub struct Q4KWeight<R: Runtime> {
1310 qs: Handle,
1311 dd: Handle,
1312 scales: Handle,
1313 n_out: usize,
1314 k: usize,
1315 _runtime: PhantomData<R>,
1316}
1317
1318impl<R: Runtime> Q4KWeight<R> {
1319 pub fn from_gguf_bytes(
1321 client: &ComputeClient<R>,
1322 data: &[u8],
1323 n_out: usize,
1324 k: usize,
1325 ) -> Result<Self> {
1326 if k == 0
1327 || k % K_SUPERBLOCK != 0
1328 || data.len() != n_out * k / K_SUPERBLOCK * Q4_K_BLOCK_BYTES
1329 {
1330 return Err(ModelError::BadShape {
1331 tensor: "q4_k weight".into(),
1332 expected: vec![n_out, k],
1333 got: vec![data.len()],
1334 });
1335 }
1336 let (qs, dd, scales) = repack_q4_k(data)?;
1337 Ok(Q4KWeight {
1338 qs: client.create_from_slice(u32::as_bytes(&qs)),
1339 dd: client.create_from_slice(f32::as_bytes(&dd)),
1340 scales: client.create_from_slice(u32::as_bytes(&scales)),
1341 n_out,
1342 k,
1343 _runtime: PhantomData,
1344 })
1345 }
1346
1347 pub fn vram_bytes(&self) -> usize {
1349 (self.n_out * self.k / K_SUPERBLOCK) * (128 + 12 + 8)
1350 }
1351
1352 pub fn matmul_device(&self, client: &ComputeClient<R>, x: Handle, m: usize) -> Handle {
1356 self.matmul_device_with(client, x, m, m > 1 && tiled_enabled())
1357 }
1358
1359 pub(crate) fn matmul_device_with(
1362 &self,
1363 client: &ComputeClient<R>,
1364 x: Handle,
1365 m: usize,
1366 tiled: bool,
1367 ) -> Handle {
1368 let out_len = m * self.n_out;
1369 let out_h = client.empty(out_len * core::mem::size_of::<f32>());
1370 let n_sb = self.n_out * self.k / K_SUPERBLOCK;
1371 if tiled {
1372 unsafe {
1373 q4_k_matmul_tiled_kernel::launch_unchecked::<R>(
1374 client,
1375 cube_count_tiled(self.n_out as u32, m as u32),
1376 CubeDim::new_1d(CUBE_DIM),
1377 ArrayArg::from_raw_parts(x, m * self.k),
1378 ArrayArg::from_raw_parts(self.qs.clone(), n_sb * 32),
1379 ArrayArg::from_raw_parts(self.dd.clone(), n_sb * 2),
1380 ArrayArg::from_raw_parts(self.scales.clone(), n_sb * 3),
1381 ArrayArg::from_raw_parts(out_h.clone(), out_len),
1382 self.k,
1383 self.n_out,
1384 );
1385 }
1386 } else {
1387 unsafe {
1388 q4_k_matmul_kernel::launch_unchecked::<R>(
1389 client,
1390 cube_count_capped(out_len as u32),
1391 CubeDim::new_1d(CUBE_DIM),
1392 ArrayArg::from_raw_parts(x, m * self.k),
1393 ArrayArg::from_raw_parts(self.qs.clone(), n_sb * 32),
1394 ArrayArg::from_raw_parts(self.dd.clone(), n_sb * 2),
1395 ArrayArg::from_raw_parts(self.scales.clone(), n_sb * 3),
1396 ArrayArg::from_raw_parts(out_h.clone(), out_len),
1397 m,
1398 self.k,
1399 self.n_out,
1400 );
1401 }
1402 }
1403 out_h
1404 }
1405
1406 pub fn matmul_host(&self, client: &ComputeClient<R>, x: &[f32], m: usize) -> Result<Vec<f32>> {
1408 if m == 0 || x.len() != m * self.k {
1409 return Err(ModelError::BadShape {
1410 tensor: "q4_k matmul input".into(),
1411 expected: vec![m, self.k],
1412 got: vec![x.len()],
1413 });
1414 }
1415 let x_h = client.create_from_slice(f32::as_bytes(x));
1416 let out_h = self.matmul_device(client, x_h, m);
1417 let bytes = client.read_one_unchecked(out_h);
1418 Ok(f32::from_bytes(&bytes).to_vec())
1419 }
1420}
1421
1422pub struct Q5KWeight<R: Runtime> {
1427 qs: Handle,
1428 qh: Handle,
1429 dd: Handle,
1430 scales: Handle,
1431 n_out: usize,
1432 k: usize,
1433 _runtime: PhantomData<R>,
1434}
1435
1436impl<R: Runtime> Q5KWeight<R> {
1437 pub fn from_gguf_bytes(
1439 client: &ComputeClient<R>,
1440 data: &[u8],
1441 n_out: usize,
1442 k: usize,
1443 ) -> Result<Self> {
1444 if k == 0
1445 || k % K_SUPERBLOCK != 0
1446 || data.len() != n_out * k / K_SUPERBLOCK * Q5_K_BLOCK_BYTES
1447 {
1448 return Err(ModelError::BadShape {
1449 tensor: "q5_k weight".into(),
1450 expected: vec![n_out, k],
1451 got: vec![data.len()],
1452 });
1453 }
1454 let (qs, qh, dd, scales) = repack_q5_k(data)?;
1455 Ok(Q5KWeight {
1456 qs: client.create_from_slice(u32::as_bytes(&qs)),
1457 qh: client.create_from_slice(u32::as_bytes(&qh)),
1458 dd: client.create_from_slice(f32::as_bytes(&dd)),
1459 scales: client.create_from_slice(u32::as_bytes(&scales)),
1460 n_out,
1461 k,
1462 _runtime: PhantomData,
1463 })
1464 }
1465
1466 pub fn vram_bytes(&self) -> usize {
1468 (self.n_out * self.k / K_SUPERBLOCK) * (128 + 32 + 12 + 8)
1469 }
1470
1471 pub fn matmul_device(&self, client: &ComputeClient<R>, x: Handle, m: usize) -> Handle {
1474 let out_len = m * self.n_out;
1475 let out_h = client.empty(out_len * core::mem::size_of::<f32>());
1476 let n_sb = self.n_out * self.k / K_SUPERBLOCK;
1477 unsafe {
1478 q5_k_matmul_kernel::launch_unchecked::<R>(
1479 client,
1480 cube_count_capped(out_len as u32),
1481 CubeDim::new_1d(CUBE_DIM),
1482 ArrayArg::from_raw_parts(x, m * self.k),
1483 ArrayArg::from_raw_parts(self.qs.clone(), n_sb * 32),
1484 ArrayArg::from_raw_parts(self.qh.clone(), n_sb * 8),
1485 ArrayArg::from_raw_parts(self.dd.clone(), n_sb * 2),
1486 ArrayArg::from_raw_parts(self.scales.clone(), n_sb * 3),
1487 ArrayArg::from_raw_parts(out_h.clone(), out_len),
1488 m,
1489 self.k,
1490 self.n_out,
1491 );
1492 }
1493 out_h
1494 }
1495
1496 pub fn matmul_host(&self, client: &ComputeClient<R>, x: &[f32], m: usize) -> Result<Vec<f32>> {
1498 if m == 0 || x.len() != m * self.k {
1499 return Err(ModelError::BadShape {
1500 tensor: "q5_k matmul input".into(),
1501 expected: vec![m, self.k],
1502 got: vec![x.len()],
1503 });
1504 }
1505 let x_h = client.create_from_slice(f32::as_bytes(x));
1506 let out_h = self.matmul_device(client, x_h, m);
1507 let bytes = client.read_one_unchecked(out_h);
1508 Ok(f32::from_bytes(&bytes).to_vec())
1509 }
1510}
1511
1512pub struct Q6KWeight<R: Runtime> {
1513 ql: Handle,
1514 qh: Handle,
1515 sc: Handle,
1516 d: Handle,
1517 n_out: usize,
1518 k: usize,
1519 _runtime: PhantomData<R>,
1520}
1521
1522impl<R: Runtime> Q6KWeight<R> {
1523 pub fn from_gguf_bytes(
1525 client: &ComputeClient<R>,
1526 data: &[u8],
1527 n_out: usize,
1528 k: usize,
1529 ) -> Result<Self> {
1530 if k == 0
1531 || k % K_SUPERBLOCK != 0
1532 || data.len() != n_out * k / K_SUPERBLOCK * Q6_K_BLOCK_BYTES
1533 {
1534 return Err(ModelError::BadShape {
1535 tensor: "q6_k weight".into(),
1536 expected: vec![n_out, k],
1537 got: vec![data.len()],
1538 });
1539 }
1540 let (ql, qh, sc, d) = repack_q6_k(data)?;
1541 Ok(Q6KWeight {
1542 ql: client.create_from_slice(u32::as_bytes(&ql)),
1543 qh: client.create_from_slice(u32::as_bytes(&qh)),
1544 sc: client.create_from_slice(u32::as_bytes(&sc)),
1545 d: client.create_from_slice(f32::as_bytes(&d)),
1546 n_out,
1547 k,
1548 _runtime: PhantomData,
1549 })
1550 }
1551
1552 pub fn vram_bytes(&self) -> usize {
1554 (self.n_out * self.k / K_SUPERBLOCK) * (128 + 64 + 16 + 4)
1555 }
1556
1557 pub fn matmul_device(&self, client: &ComputeClient<R>, x: Handle, m: usize) -> Handle {
1560 let out_len = m * self.n_out;
1561 let out_h = client.empty(out_len * core::mem::size_of::<f32>());
1562 let n_sb = self.n_out * self.k / K_SUPERBLOCK;
1563 unsafe {
1564 q6_k_matmul_kernel::launch_unchecked::<R>(
1565 client,
1566 cube_count_capped(out_len as u32),
1567 CubeDim::new_1d(CUBE_DIM),
1568 ArrayArg::from_raw_parts(x, m * self.k),
1569 ArrayArg::from_raw_parts(self.ql.clone(), n_sb * 32),
1570 ArrayArg::from_raw_parts(self.qh.clone(), n_sb * 16),
1571 ArrayArg::from_raw_parts(self.sc.clone(), n_sb * 4),
1572 ArrayArg::from_raw_parts(self.d.clone(), n_sb),
1573 ArrayArg::from_raw_parts(out_h.clone(), out_len),
1574 m,
1575 self.k,
1576 self.n_out,
1577 );
1578 }
1579 out_h
1580 }
1581
1582 pub fn matmul_host(&self, client: &ComputeClient<R>, x: &[f32], m: usize) -> Result<Vec<f32>> {
1584 if m == 0 || x.len() != m * self.k {
1585 return Err(ModelError::BadShape {
1586 tensor: "q6_k matmul input".into(),
1587 expected: vec![m, self.k],
1588 got: vec![x.len()],
1589 });
1590 }
1591 let x_h = client.create_from_slice(f32::as_bytes(x));
1592 let out_h = self.matmul_device(client, x_h, m);
1593 let bytes = client.read_one_unchecked(out_h);
1594 Ok(f32::from_bytes(&bytes).to_vec())
1595 }
1596}
1597
1598pub enum QuantWeight {
1602 Q40(Q40Weight<cubecl::wgpu::WgpuRuntime>),
1604 Q50(Q50Weight<cubecl::wgpu::WgpuRuntime>),
1606 Q80(Q80Weight<cubecl::wgpu::WgpuRuntime>),
1608 Q4K(Q4KWeight<cubecl::wgpu::WgpuRuntime>),
1610 Q5K(Q5KWeight<cubecl::wgpu::WgpuRuntime>),
1612 Q6K(Q6KWeight<cubecl::wgpu::WgpuRuntime>),
1614}
1615
1616impl QuantWeight {
1617 pub fn from_quant_tensor(
1620 client: &ComputeClient<cubecl::wgpu::WgpuRuntime>,
1621 format: combs_formats::QuantFormat,
1622 data: &[u8],
1623 n_out: usize,
1624 k: usize,
1625 ) -> Result<Self> {
1626 use combs_formats::QuantFormat;
1627 Ok(match format {
1628 QuantFormat::Q4_0 => QuantWeight::Q40(Q40Weight::from_gguf_bytes(client, data, n_out, k)?),
1629 QuantFormat::Q5_0 => QuantWeight::Q50(Q50Weight::from_gguf_bytes(client, data, n_out, k)?),
1630 QuantFormat::Q8_0 => QuantWeight::Q80(Q80Weight::from_gguf_bytes(client, data, n_out, k)?),
1631 QuantFormat::Q4K => QuantWeight::Q4K(Q4KWeight::from_gguf_bytes(client, data, n_out, k)?),
1632 QuantFormat::Q5K => QuantWeight::Q5K(Q5KWeight::from_gguf_bytes(client, data, n_out, k)?),
1633 QuantFormat::Q6K => QuantWeight::Q6K(Q6KWeight::from_gguf_bytes(client, data, n_out, k)?),
1634 })
1635 }
1636
1637 pub fn n_out(&self) -> usize {
1639 match self {
1640 QuantWeight::Q40(w) => w.n_out,
1641 QuantWeight::Q50(w) => w.n_out,
1642 QuantWeight::Q80(w) => w.n_out,
1643 QuantWeight::Q4K(w) => w.n_out,
1644 QuantWeight::Q5K(w) => w.n_out,
1645 QuantWeight::Q6K(w) => w.n_out,
1646 }
1647 }
1648
1649 pub fn k(&self) -> usize {
1651 match self {
1652 QuantWeight::Q40(w) => w.k,
1653 QuantWeight::Q50(w) => w.k,
1654 QuantWeight::Q80(w) => w.k,
1655 QuantWeight::Q4K(w) => w.k,
1656 QuantWeight::Q5K(w) => w.k,
1657 QuantWeight::Q6K(w) => w.k,
1658 }
1659 }
1660
1661 pub fn vram_bytes(&self) -> usize {
1663 match self {
1664 QuantWeight::Q40(w) => w.vram_bytes(),
1665 QuantWeight::Q50(w) => w.vram_bytes(),
1666 QuantWeight::Q80(w) => w.vram_bytes(),
1667 QuantWeight::Q4K(w) => w.vram_bytes(),
1668 QuantWeight::Q5K(w) => w.vram_bytes(),
1669 QuantWeight::Q6K(w) => w.vram_bytes(),
1670 }
1671 }
1672
1673 pub fn matmul_device(
1675 &self,
1676 client: &ComputeClient<cubecl::wgpu::WgpuRuntime>,
1677 x: Handle,
1678 m: usize,
1679 ) -> Handle {
1680 match self {
1681 QuantWeight::Q40(w) => w.matmul_device(client, x, m),
1682 QuantWeight::Q50(w) => w.matmul_device(client, x, m),
1683 QuantWeight::Q80(w) => w.matmul_device(client, x, m),
1684 QuantWeight::Q4K(w) => w.matmul_device(client, x, m),
1685 QuantWeight::Q5K(w) => w.matmul_device(client, x, m),
1686 QuantWeight::Q6K(w) => w.matmul_device(client, x, m),
1687 }
1688 }
1689}
1690
1691#[cfg(test)]
1692mod tests {
1693 use super::*;
1694 use cubecl::wgpu::WgpuRuntime;
1695
1696 fn synth_q4_0(n_blocks: usize) -> Vec<u8> {
1699 let mut out = Vec::with_capacity(n_blocks * Q4_0_BLOCK_BYTES);
1700 let mut s = 0x12345678u32;
1701 for b in 0..n_blocks {
1702 let scale = burn::tensor::f16::from_f32(0.003 * ((b % 11) as f32 + 1.0));
1703 out.extend_from_slice(&scale.to_le_bytes());
1704 for _ in 0..16 {
1705 s = s.wrapping_mul(1664525).wrapping_add(1013904223);
1706 out.push((s >> 24) as u8);
1707 }
1708 }
1709 out
1710 }
1711
1712 fn ref_matmul(x: &[f32], w: &[f32], m: usize, k: usize, n_out: usize) -> Vec<f32> {
1714 let mut out = vec![0f32; m * n_out];
1715 for r in 0..m {
1716 for c in 0..n_out {
1717 let mut acc = 0f32;
1718 for i in 0..k {
1719 acc += x[r * k + i] * w[c * k + i];
1720 }
1721 out[r * n_out + c] = acc;
1722 }
1723 }
1724 out
1725 }
1726
1727 #[test]
1732 fn dequant_kernel_is_bit_exact_vs_cpu_reference() {
1733 if crate::skip_no_gpu() {
1734 return;
1735 }
1736 let n_blocks = 33; let data = synth_q4_0(n_blocks);
1738 let n = n_blocks * Q4_0_BLOCK;
1739 let expect = combs_formats::quants::dequantize_q4_0(&data, n).unwrap();
1740
1741 let device = Default::default();
1742 let client = WgpuRuntime::client(&device);
1743 let got = dequantize_q4_0_gpu::<WgpuRuntime>(&client, &data).unwrap();
1744
1745 assert_eq!(got, expect, "GPU dequant must be bit-exact vs gguf.rs");
1746 }
1747
1748 #[test]
1752 fn fused_matmul_matches_reference() {
1753 if crate::skip_no_gpu() {
1754 return;
1755 }
1756 let (n_out, k) = (67, 128); let n_blocks = n_out * k / Q4_0_BLOCK;
1758 let data = synth_q4_0(n_blocks);
1759 let w = combs_formats::quants::dequantize_q4_0(&data, n_out * k).unwrap();
1760
1761 let device = Default::default();
1762 let client = WgpuRuntime::client(&device);
1763 let weight = Q40Weight::<WgpuRuntime>::from_gguf_bytes(&client, &data, n_out, k).unwrap();
1764 assert_eq!(weight.vram_bytes(), n_blocks * 20);
1765
1766 for m in [1usize, 3] {
1767 let x: Vec<f32> = (0..m * k)
1768 .map(|i| ((i * 7 % 13) as f32 - 6.0) / 8.0)
1769 .collect();
1770 let expect = ref_matmul(&x, &w, m, k, n_out);
1771 let got = weight.matmul_host(&client, &x, m).unwrap();
1772 assert_eq!(got.len(), expect.len());
1773 for (i, (g, e)) in got.iter().zip(expect.iter()).enumerate() {
1774 let tol = 1e-4 * e.abs().max(1.0);
1775 assert!(
1776 (g - e).abs() <= tol,
1777 "m={m} out[{i}]: got {g}, expect {e}"
1778 );
1779 }
1780 }
1781 }
1782
1783 fn lcg_bytes(n: usize, seed: u32) -> Vec<u8> {
1785 let mut s = seed;
1786 (0..n)
1787 .map(|_| {
1788 s = s.wrapping_mul(1664525).wrapping_add(1013904223);
1789 (s >> 24) as u8
1790 })
1791 .collect()
1792 }
1793
1794 fn synth_q4_k(n_sb: usize) -> Vec<u8> {
1796 let mut out = Vec::with_capacity(n_sb * Q4_K_BLOCK_BYTES);
1797 for b in 0..n_sb {
1798 let d = burn::tensor::f16::from_f32(0.002 * ((b % 9) as f32 + 1.0));
1799 let dmin = burn::tensor::f16::from_f32(0.001 * ((b % 5) as f32 + 1.0));
1800 out.extend_from_slice(&d.to_le_bytes());
1801 out.extend_from_slice(&dmin.to_le_bytes());
1802 out.extend_from_slice(&lcg_bytes(140, 0xC0FFEE ^ b as u32));
1803 }
1804 out
1805 }
1806
1807 fn synth_q5_k(n_sb: usize) -> Vec<u8> {
1809 let mut out = Vec::with_capacity(n_sb * Q5_K_BLOCK_BYTES);
1810 for b in 0..n_sb {
1811 let d = burn::tensor::f16::from_f32(0.003 * ((b % 7) as f32 + 1.0));
1812 let dmin = burn::tensor::f16::from_f32(0.001 * ((b % 5) as f32 + 1.0));
1813 out.extend_from_slice(&d.to_le_bytes());
1814 out.extend_from_slice(&dmin.to_le_bytes());
1815 out.extend_from_slice(&lcg_bytes(172, 0x5EED ^ b as u32));
1816 }
1817 out
1818 }
1819
1820 fn synth_q6_k(n_sb: usize) -> Vec<u8> {
1822 let mut out = Vec::with_capacity(n_sb * Q6_K_BLOCK_BYTES);
1823 for b in 0..n_sb {
1824 out.extend_from_slice(&lcg_bytes(208, 0xBEE5 ^ b as u32));
1825 let d = burn::tensor::f16::from_f32(0.002 * ((b % 9) as f32 + 1.0));
1826 out.extend_from_slice(&d.to_le_bytes());
1827 }
1828 out
1829 }
1830
1831 fn assert_close(got: &[f32], expect: &[f32], rel: f32, what: &str) {
1832 assert_eq!(got.len(), expect.len(), "{what}: length");
1833 for (i, (g, e)) in got.iter().zip(expect.iter()).enumerate() {
1834 let tol = rel * e.abs().max(1.0);
1835 assert!((g - e).abs() <= tol, "{what}[{i}]: got {g}, expect {e}");
1836 }
1837 }
1838
1839 #[test]
1844 fn q4_k_dequant_matches_cpu_reference() {
1845 if crate::skip_no_gpu() {
1846 return;
1847 }
1848 let n_sb = 9;
1849 let data = synth_q4_k(n_sb);
1850 let n = n_sb * K_SUPERBLOCK;
1851 let expect = combs_formats::quants::dequantize_q4_k(&data, n).unwrap();
1852
1853 let device = Default::default();
1854 let client = WgpuRuntime::client(&device);
1855 let got = dequantize_q4_k_gpu::<WgpuRuntime>(&client, &data).unwrap();
1856 assert_close(&got, &expect, 1e-6, "q4_k dequant");
1857 }
1858
1859 #[test]
1861 fn q5_k_dequant_matches_cpu_reference() {
1862 if crate::skip_no_gpu() {
1863 return;
1864 }
1865 let n_sb = 9;
1866 let data = synth_q5_k(n_sb);
1867 let n = n_sb * K_SUPERBLOCK;
1868 let expect = combs_formats::quants::dequantize_q5_k(&data, n).unwrap();
1869
1870 let device = Default::default();
1871 let client = WgpuRuntime::client(&device);
1872 let got = dequantize_q5_k_gpu::<WgpuRuntime>(&client, &data).unwrap();
1873 assert_close(&got, &expect, 1e-6, "q5_k dequant");
1874 }
1875
1876 #[test]
1878 fn q6_k_dequant_matches_cpu_reference() {
1879 if crate::skip_no_gpu() {
1880 return;
1881 }
1882 let n_sb = 9;
1883 let data = synth_q6_k(n_sb);
1884 let n = n_sb * K_SUPERBLOCK;
1885 let expect = combs_formats::quants::dequantize_q6_k(&data, n).unwrap();
1886
1887 let device = Default::default();
1888 let client = WgpuRuntime::client(&device);
1889 let got = dequantize_q6_k_gpu::<WgpuRuntime>(&client, &data).unwrap();
1890 assert_close(&got, &expect, 1e-6, "q6_k dequant");
1891 }
1892
1893 #[test]
1896 fn q5_k_fused_matmul_matches_reference() {
1897 if crate::skip_no_gpu() {
1898 return;
1899 }
1900 let (n_out, k) = (35, 512); let n_sb = n_out * k / K_SUPERBLOCK;
1902 let data = synth_q5_k(n_sb);
1903 let w = combs_formats::quants::dequantize_q5_k(&data, n_out * k).unwrap();
1904
1905 let device = Default::default();
1906 let client = WgpuRuntime::client(&device);
1907 let weight = Q5KWeight::<WgpuRuntime>::from_gguf_bytes(&client, &data, n_out, k).unwrap();
1908 assert_eq!(weight.vram_bytes(), n_sb * 180);
1909
1910 for m in [1usize, 3] {
1911 let x: Vec<f32> = (0..m * k)
1912 .map(|i| ((i * 7 % 13) as f32 - 6.0) / 8.0)
1913 .collect();
1914 let expect = ref_matmul(&x, &w, m, k, n_out);
1915 let got = weight.matmul_host(&client, &x, m).unwrap();
1916 assert_close(&got, &expect, 1e-3, &format!("q5_k matmul m={m}"));
1917 }
1918 }
1919
1920 #[test]
1923 fn q4_k_fused_matmul_matches_reference() {
1924 if crate::skip_no_gpu() {
1925 return;
1926 }
1927 let (n_out, k) = (35, 512); let n_sb = n_out * k / K_SUPERBLOCK;
1929 let data = synth_q4_k(n_sb);
1930 let w = combs_formats::quants::dequantize_q4_k(&data, n_out * k).unwrap();
1931
1932 let device = Default::default();
1933 let client = WgpuRuntime::client(&device);
1934 let weight = Q4KWeight::<WgpuRuntime>::from_gguf_bytes(&client, &data, n_out, k).unwrap();
1935 assert_eq!(weight.vram_bytes(), n_sb * 148);
1936
1937 for m in [1usize, 3] {
1938 let x: Vec<f32> = (0..m * k)
1939 .map(|i| ((i * 7 % 13) as f32 - 6.0) / 8.0)
1940 .collect();
1941 let expect = ref_matmul(&x, &w, m, k, n_out);
1942 let got = weight.matmul_host(&client, &x, m).unwrap();
1943 assert_close(&got, &expect, 1e-3, &format!("q4_k matmul m={m}"));
1944 }
1945 }
1946
1947 #[test]
1949 fn q6_k_fused_matmul_matches_reference() {
1950 if crate::skip_no_gpu() {
1951 return;
1952 }
1953 let (n_out, k) = (35, 512);
1954 let n_sb = n_out * k / K_SUPERBLOCK;
1955 let data = synth_q6_k(n_sb);
1956 let w = combs_formats::quants::dequantize_q6_k(&data, n_out * k).unwrap();
1957
1958 let device = Default::default();
1959 let client = WgpuRuntime::client(&device);
1960 let weight = Q6KWeight::<WgpuRuntime>::from_gguf_bytes(&client, &data, n_out, k).unwrap();
1961 assert_eq!(weight.vram_bytes(), n_sb * 212);
1962
1963 for m in [1usize, 3] {
1964 let x: Vec<f32> = (0..m * k)
1965 .map(|i| ((i * 7 % 13) as f32 - 6.0) / 8.0)
1966 .collect();
1967 let expect = ref_matmul(&x, &w, m, k, n_out);
1968 let got = weight.matmul_host(&client, &x, m).unwrap();
1969 assert_close(&got, &expect, 1e-3, &format!("q6_k matmul m={m}"));
1970 }
1971 }
1972
1973 fn synth_q5_0(n_blocks: usize) -> Vec<u8> {
1975 let mut out = Vec::with_capacity(n_blocks * Q5_0_BLOCK_BYTES);
1976 for b in 0..n_blocks {
1977 let scale = burn::tensor::f16::from_f32(0.003 * ((b % 11) as f32 + 1.0));
1978 out.extend_from_slice(&scale.to_le_bytes());
1979 out.extend_from_slice(&lcg_bytes(20, 0x51D0 ^ b as u32));
1980 }
1981 out
1982 }
1983
1984 fn synth_q8_0(n_blocks: usize) -> Vec<u8> {
1986 let mut out = Vec::with_capacity(n_blocks * Q8_0_BLOCK_BYTES);
1987 for b in 0..n_blocks {
1988 let scale = burn::tensor::f16::from_f32(0.003 * ((b % 11) as f32 + 1.0));
1989 out.extend_from_slice(&scale.to_le_bytes());
1990 out.extend_from_slice(&lcg_bytes(32, 0x80C0 ^ b as u32));
1991 }
1992 out
1993 }
1994
1995 #[test]
1998 fn q5_0_and_q8_0_dequant_are_bit_exact() {
1999 if crate::skip_no_gpu() {
2000 return;
2001 }
2002 let device = Default::default();
2003 let client = WgpuRuntime::client(&device);
2004
2005 let data = synth_q5_0(33);
2006 let n = 33 * Q4_0_BLOCK;
2007 let expect = combs_formats::quants::dequantize_q5_0(&data, n).unwrap();
2008 let got = dequantize_q5_0_gpu::<WgpuRuntime>(&client, &data).unwrap();
2009 assert_eq!(got, expect, "q5_0 GPU dequant must be bit-exact");
2010
2011 let data = synth_q8_0(33);
2012 let expect = combs_formats::quants::dequantize_q8_0(&data, n).unwrap();
2013 let got = dequantize_q8_0_gpu::<WgpuRuntime>(&client, &data).unwrap();
2014 assert_eq!(got, expect, "q8_0 GPU dequant must be bit-exact");
2015 }
2016
2017 #[test]
2020 fn q5_0_and_q8_0_fused_matmul_match_reference() {
2021 if crate::skip_no_gpu() {
2022 return;
2023 }
2024 let device = Default::default();
2025 let client = WgpuRuntime::client(&device);
2026 let (n_out, k) = (67, 128);
2027 let n_blocks = n_out * k / Q4_0_BLOCK;
2028
2029 let data5 = synth_q5_0(n_blocks);
2030 let w5 = combs_formats::quants::dequantize_q5_0(&data5, n_out * k).unwrap();
2031 let q5 = Q50Weight::<WgpuRuntime>::from_gguf_bytes(&client, &data5, n_out, k).unwrap();
2032 assert_eq!(q5.vram_bytes(), n_blocks * 24);
2033
2034 let data8 = synth_q8_0(n_blocks);
2035 let w8 = combs_formats::quants::dequantize_q8_0(&data8, n_out * k).unwrap();
2036 let q8 = Q80Weight::<WgpuRuntime>::from_gguf_bytes(&client, &data8, n_out, k).unwrap();
2037 assert_eq!(q8.vram_bytes(), n_blocks * 36);
2038
2039 for m in [1usize, 3] {
2040 let x: Vec<f32> = (0..m * k)
2041 .map(|i| ((i * 7 % 13) as f32 - 6.0) / 8.0)
2042 .collect();
2043 let got5 = q5.matmul_host(&client, &x, m).unwrap();
2044 assert_close(&got5, &ref_matmul(&x, &w5, m, k, n_out), 1e-3, &format!("q5_0 m={m}"));
2045 let got8 = q8.matmul_host(&client, &x, m).unwrap();
2046 assert_close(&got8, &ref_matmul(&x, &w8, m, k, n_out), 1e-3, &format!("q8_0 m={m}"));
2047 }
2048 }
2049
2050 #[test]
2052 fn q5_q8_shape_validation() {
2053 if crate::skip_no_gpu() {
2054 return;
2055 }
2056 let device = Default::default();
2057 let client = WgpuRuntime::client(&device);
2058 assert!(repack_q5_0(&[0u8; 21]).is_err());
2059 assert!(repack_q8_0(&[0u8; 33]).is_err());
2060 assert!(Q50Weight::<WgpuRuntime>::from_gguf_bytes(&client, &synth_q5_0(2), 2, 31).is_err());
2061 assert!(Q80Weight::<WgpuRuntime>::from_gguf_bytes(&client, &synth_q8_0(2), 2, 64).is_err());
2062 }
2063
2064 #[test]
2066 fn k_quant_shape_validation() {
2067 if crate::skip_no_gpu() {
2068 return;
2069 }
2070 let device = Default::default();
2071 let client = WgpuRuntime::client(&device);
2072 assert!(repack_q4_k(&[0u8; 143]).is_err());
2073 assert!(repack_q6_k(&[0u8; 209]).is_err());
2074 assert!(
2076 Q4KWeight::<WgpuRuntime>::from_gguf_bytes(&client, &synth_q4_k(1), 1, 128).is_err()
2077 );
2078 assert!(
2079 Q6KWeight::<WgpuRuntime>::from_gguf_bytes(&client, &synth_q6_k(1), 1, 128).is_err()
2080 );
2081 }
2082
2083 fn assert_tiled_bit_identical(
2088 untiled: &[f32],
2089 tiled: &[f32],
2090 label: &str,
2091 ) {
2092 assert_eq!(untiled.len(), tiled.len(), "{label}: length mismatch");
2093 for (i, (u, t)) in untiled.iter().zip(tiled.iter()).enumerate() {
2094 assert_eq!(
2095 u.to_bits(),
2096 t.to_bits(),
2097 "{label} out[{i}]: untiled {u} vs tiled {t} — accumulation order drifted"
2098 );
2099 }
2100 }
2101
2102 #[test]
2107 fn tiled_q8_0_matmul_is_bit_identical() {
2108 if crate::skip_no_gpu() {
2109 return;
2110 }
2111 let (n_out, k) = (300, 320);
2112 let n_blocks = n_out * k / Q4_0_BLOCK;
2113 let data = synth_q8_0(n_blocks);
2114 let device = Default::default();
2115 let client = WgpuRuntime::client(&device);
2116 let w = Q80Weight::<WgpuRuntime>::from_gguf_bytes(&client, &data, n_out, k).unwrap();
2117 for m in [2usize, 3, 17, 256, 1024] {
2118 let x: Vec<f32> = (0..m * k)
2119 .map(|i| ((i * 11 % 29) as f32 - 14.0) / 16.0)
2120 .collect();
2121 let x_h = client.create_from_slice(f32::as_bytes(&x));
2122 let un_h = w.matmul_device_with(&client, x_h.clone(), m, false);
2123 let ti_h = w.matmul_device_with(&client, x_h, m, true);
2124 let un = f32::from_bytes(&client.read_one_unchecked(un_h)).to_vec();
2125 let ti = f32::from_bytes(&client.read_one_unchecked(ti_h)).to_vec();
2126 assert_tiled_bit_identical(&un, &ti, &format!("q8_0 m={m}"));
2127 }
2128 }
2129
2130 #[test]
2133 fn tiled_q4_k_matmul_is_bit_identical() {
2134 if crate::skip_no_gpu() {
2135 return;
2136 }
2137 let (n_out, k) = (300, 512);
2138 let n_sb = n_out * k / K_SUPERBLOCK;
2139 let data = synth_q4_k(n_sb);
2140 let device = Default::default();
2141 let client = WgpuRuntime::client(&device);
2142 let w = Q4KWeight::<WgpuRuntime>::from_gguf_bytes(&client, &data, n_out, k).unwrap();
2143 for m in [2usize, 3, 17, 256, 1024] {
2144 let x: Vec<f32> = (0..m * k)
2145 .map(|i| ((i * 13 % 31) as f32 - 15.0) / 16.0)
2146 .collect();
2147 let x_h = client.create_from_slice(f32::as_bytes(&x));
2148 let un_h = w.matmul_device_with(&client, x_h.clone(), m, false);
2149 let ti_h = w.matmul_device_with(&client, x_h, m, true);
2150 let un = f32::from_bytes(&client.read_one_unchecked(un_h)).to_vec();
2151 let ti = f32::from_bytes(&client.read_one_unchecked(ti_h)).to_vec();
2152 assert_tiled_bit_identical(&un, &ti, &format!("q4_k m={m}"));
2153 }
2154 }
2155
2156 #[test]
2158 fn shape_validation() {
2159 if crate::skip_no_gpu() {
2160 return;
2161 }
2162 let device = Default::default();
2163 let client = WgpuRuntime::client(&device);
2164 assert!(repack_q4_0(&[0u8; 17]).is_err());
2166 assert!(Q40Weight::<WgpuRuntime>::from_gguf_bytes(&client, &synth_q4_0(2), 2, 31).is_err());
2168 assert!(Q40Weight::<WgpuRuntime>::from_gguf_bytes(&client, &synth_q4_0(2), 2, 64).is_err());
2170 let w = Q40Weight::<WgpuRuntime>::from_gguf_bytes(&client, &synth_q4_0(2), 2, 32).unwrap();
2172 assert!(w.matmul_host(&client, &[0f32; 31], 1).is_err());
2173 }
2174}