1#![allow(unsafe_op_in_unsafe_fn)]
5use crate::thunk::*;
6
7#[allow(clippy::too_many_arguments)]
21pub fn dequant_matmul_int8(
22 x: &[f32], w_bytes: &[i8], scales: &[f32], zps: &[f32], out: &mut [f32], m: usize,
28 k: usize,
29 n: usize,
30 block_size: usize,
31 asym: bool,
32) {
33 let blocks_per_col = k.div_ceil(block_size);
34 if m > 1 {
41 let mut w_f = vec![0f32; k * n];
42 for p in 0..k {
43 let b = p / block_size;
44 for j in 0..n {
45 let z = if asym { zps[b * n + j] } else { 0.0 };
46 w_f[p * n + j] = (w_bytes[p * n + j] as f32 - z) * scales[b * n + j];
47 }
48 }
49 crate::blas::sgemm(x, &w_f, out, m, k, n);
50 return;
51 }
52 for i in 0..m {
53 let x_row = &x[i * k..i * k + k];
54 for j in 0..n {
55 let mut acc = 0f32;
62 for b in 0..blocks_per_col {
63 let s = scales[b * n + j];
64 let z = if asym { zps[b * n + j] } else { 0.0 };
65 let lo = b * block_size;
66 let hi = (lo + block_size).min(k);
67 let mut bacc = 0f32;
68 for p in lo..hi {
69 let q = w_bytes[p * n + j] as f32;
70 bacc += x_row[p] * (q - z);
71 }
72 acc += bacc * s;
73 }
74 out[i * n + j] = acc;
75 }
76 }
77}
78
79#[allow(clippy::too_many_arguments)]
91pub fn synth_matmul_codebook(
92 x: &[f32], indices: &[u8], codebook: &[f32], out: &mut [f32], m: usize,
97 k: usize,
98 n: usize,
99 entry_dim: usize,
100) {
101 let kb_per_row = k / entry_dim.max(1); if m > 1 {
106 let mut w_nk = vec![0f32; n * k];
112 for j in 0..n {
113 let wj = &mut w_nk[j * k..j * k + k];
114 let row = &indices[j * kb_per_row..j * kb_per_row + kb_per_row];
115 for (kb, &code) in row.iter().enumerate() {
116 let c = code as usize * entry_dim;
117 let base = kb * entry_dim;
118 wj[base..base + entry_dim].copy_from_slice(&codebook[c..c + entry_dim]);
119 }
120 }
121 crate::blas::sgemm_bt(x, &w_nk, out, m, k, n, 1.0);
122 return;
123 }
124 for i in 0..m {
127 let x_row = &x[i * k..i * k + k];
128 for j in 0..n {
129 let row = &indices[j * kb_per_row..j * kb_per_row + kb_per_row];
130 let mut acc = 0f32;
131 for (kb, &code) in row.iter().enumerate() {
132 let c = code as usize * entry_dim;
133 let cb = &codebook[c..c + entry_dim];
134 let base = kb * entry_dim;
135 for (t, &w) in cb.iter().enumerate() {
136 acc += x_row[base + t] * w;
137 }
138 }
139 out[i * n + j] = acc;
140 }
141 }
142}
143
144pub(crate) fn compile_synth_mat_mul(
147 node: &rlx_ir::Node,
148 graph: &Graph,
149 arena: &crate::arena::Arena,
150) -> Thunk {
151 let Op::SynthMatMul { kind } = &node.op else {
152 unreachable!()
153 };
154 let rlx_ir::SynthKind::Codebook {
155 entry_dim,
156 num_entries,
157 } = kind;
158 let n = node.shape.dim(node.shape.rank() - 1).unwrap_static();
159 let total = node.shape.num_elements().unwrap();
160 let m = total / n.max(1);
161 let x_total = graph.node(node.inputs[0]).shape.num_elements().unwrap();
162 let k = x_total / m.max(1);
163 Thunk::SynthMatMul {
164 x: node_offset(arena, node.inputs[0]),
165 indices: node_offset(arena, node.inputs[1]),
166 codebook: node_offset(arena, node.inputs[2]),
167 dst: node_offset(arena, node.id),
168 m: m as u32,
169 k: k as u32,
170 n: n as u32,
171 entry_dim: *entry_dim,
172 num_entries: *num_entries,
173 }
174}
175
176#[inline(always)]
178pub(crate) fn exec_synth_mat_mul(t: &Thunk, base: *mut u8) {
179 let Thunk::SynthMatMul {
180 x,
181 indices,
182 codebook,
183 dst,
184 m,
185 k,
186 n,
187 entry_dim,
188 num_entries,
189 } = t
190 else {
191 unreachable!()
192 };
193 let (m, k, n, d) = (*m as usize, *k as usize, *n as usize, *entry_dim as usize);
194 let kb_per_row = k / d.max(1);
195 unsafe {
196 let xs = sl(*x, base, m * k);
197 let idx = std::slice::from_raw_parts(base.add(*indices) as *const u8, n * kb_per_row);
198 let cb = sl(*codebook, base, *num_entries as usize * d);
199 let out = sl_mut(*dst, base, m * n);
200 synth_matmul_codebook(xs, idx, cb, out, m, k, n, d);
201 }
202}
203
204#[allow(clippy::too_many_arguments)]
205pub(crate) fn dequant_matmul_int4(
206 x: &[f32],
207 w_bytes: &[u8],
208 scales: &[f32],
209 zps: &[f32],
210 out: &mut [f32],
211 m: usize,
212 k: usize,
213 n: usize,
214 block_size: usize,
215 asym: bool,
216) {
217 let blocks_per_col = k.div_ceil(block_size);
218 if m > 1 {
221 let mut w_f = vec![0f32; k * n];
222 for p in 0..k {
223 let b = p / block_size;
224 for j in 0..n {
225 let elem = p * n + j;
226 let nibble = if elem & 1 == 0 {
227 w_bytes[elem / 2] & 0x0F
228 } else {
229 w_bytes[elem / 2] >> 4
230 };
231 let z = if asym { zps[b * n + j] } else { 0.0 };
232 w_f[elem] = (nibble as f32 - z) * scales[b * n + j];
233 }
234 }
235 crate::blas::sgemm(x, &w_f, out, m, k, n);
236 return;
237 }
238 for i in 0..m {
239 let x_row = &x[i * k..i * k + k];
240 for j in 0..n {
241 let mut acc = 0f32;
244 for b in 0..blocks_per_col {
245 let s = scales[b * n + j];
246 let z = if asym { zps[b * n + j] } else { 0.0 };
247 let lo = b * block_size;
248 let hi = (lo + block_size).min(k);
249 let mut bacc = 0f32;
250 for p in lo..hi {
251 let elem = p * n + j;
252 let byte_idx = elem / 2;
253 let nibble = if elem & 1 == 0 {
254 w_bytes[byte_idx] & 0x0F
255 } else {
256 w_bytes[byte_idx] >> 4
257 };
258 bacc += x_row[p] * (nibble as f32 - z);
259 }
260 acc += bacc * s;
261 }
262 out[i * n + j] = acc;
263 }
264 }
265}
266
267pub(crate) fn fp8_e4m3_to_f32(b: u8) -> f32 {
268 let sign = if b & 0x80 != 0 { -1.0 } else { 1.0 };
269 let exp = (b >> 3) & 0x0F;
270 let mant = b & 0x07;
271 if exp == 0 {
272 if mant == 0 {
273 return 0.0;
274 }
275 return sign * (mant as f32) * 2f32.powi(-9);
276 }
277 if exp == 0x0F {
278 return if mant == 0 {
279 sign * f32::INFINITY
280 } else {
281 f32::NAN
282 };
283 }
284 sign * (1.0 + mant as f32 / 8.0) * 2f32.powi(exp as i32 - 7)
285}
286
287pub(crate) fn fp8_e5m2_to_f32(b: u8) -> f32 {
288 let sign = if b & 0x80 != 0 { -1.0 } else { 1.0 };
289 let exp = (b >> 2) & 0x1F;
290 let mant = b & 0x03;
291 if exp == 0 {
292 if mant == 0 {
293 return 0.0;
294 }
295 return sign * (mant as f32) * 2f32.powi(-16);
296 }
297 if exp == 0x1F {
298 return if mant == 0 {
299 sign * f32::INFINITY
300 } else {
301 f32::NAN
302 };
303 }
304 sign * (1.0 + mant as f32 / 4.0) * 2f32.powi(exp as i32 - 15)
305}
306
307#[allow(clippy::too_many_arguments)]
308pub(crate) fn dequant_matmul_fp8(
309 x: &[f32],
310 w_bytes: &[u8],
311 scales: &[f32],
312 out: &mut [f32],
313 m: usize,
314 k: usize,
315 n: usize,
316 e5m2: bool,
317) {
318 let dequant = if e5m2 {
319 fp8_e5m2_to_f32
320 } else {
321 fp8_e4m3_to_f32
322 };
323 if m > 1 {
326 let mut w_f = vec![0f32; k * n];
327 for p in 0..k {
328 for j in 0..n {
329 let s = scales.get(j).copied().unwrap_or(1.0);
330 w_f[p * n + j] = dequant(w_bytes[p * n + j]) * s;
331 }
332 }
333 crate::blas::sgemm(x, &w_f, out, m, k, n);
334 return;
335 }
336 for i in 0..m {
337 let x_row = &x[i * k..i * k + k];
338 for j in 0..n {
339 let s = scales.get(j).copied().unwrap_or(1.0);
344 let mut acc = 0f32;
345 for p in 0..k {
346 let w = dequant(w_bytes[p * n + j]);
347 acc += x_row[p] * w;
348 }
349 out[i * n + j] = acc * s;
350 }
351 }
352}
353
354#[allow(clippy::too_many_arguments)]
355pub fn dequant_matmul_nvfp4(
356 x: &[f32],
357 w_bytes: &[u8],
358 scale_bytes: &[u8],
359 global_scale: f32,
360 out: &mut [f32],
361 m: usize,
362 k: usize,
363 n: usize,
364) {
365 use rlx_ir::{NVFP4_GROUP_SIZE, fp4_e2m1_to_f32, fp8_e4m3_scale_to_f32};
366 let gs = NVFP4_GROUP_SIZE;
367 let n_blocks = k.div_ceil(gs);
368 if m > 1 {
371 let mut w_f = vec![0f32; k * n];
372 for p in 0..k {
373 let b = p / gs;
374 for j in 0..n {
375 let scale = fp8_e4m3_scale_to_f32(scale_bytes[b * n + j]) * global_scale;
376 let elem = p * n + j;
377 let nibble = if elem & 1 == 0 {
378 w_bytes[elem / 2] & 0x0F
379 } else {
380 w_bytes[elem / 2] >> 4
381 };
382 w_f[elem] = fp4_e2m1_to_f32(nibble) * scale;
383 }
384 }
385 crate::blas::sgemm(x, &w_f, out, m, k, n);
386 return;
387 }
388 for i in 0..m {
389 let x_row = &x[i * k..i * k + k];
390 for j in 0..n {
391 let mut acc = 0f32;
395 for b in 0..n_blocks {
396 let scale = fp8_e4m3_scale_to_f32(scale_bytes[b * n + j]);
397 let lo = b * gs;
398 let hi = (lo + gs).min(k);
399 let mut bacc = 0f32;
400 for p in lo..hi {
401 let elem = p * n + j;
402 let byte_idx = elem / 2;
403 let nibble = if elem & 1 == 0 {
404 w_bytes[byte_idx] & 0x0F
405 } else {
406 w_bytes[byte_idx] >> 4
407 };
408 bacc += x_row[p] * fp4_e2m1_to_f32(nibble);
409 }
410 acc += bacc * scale;
411 }
412 out[i * n + j] = acc * global_scale;
413 }
414 }
415}
416
417#[allow(clippy::too_many_arguments)]
422pub fn dequant_matmul_mxfp4x2(
423 x: &[f32],
424 w_bytes: &[u8],
425 scale_bytes: &[u8],
426 group: usize,
427 out: &mut [f32],
428 m: usize,
429 k: usize,
430 n: usize,
431) {
432 use rlx_ir::fp4_e2m1_to_f32;
433 let plane = (k * n).div_ceil(2); let g = group.max(1);
435 let nblk = k.div_ceil(g);
436 let sbytes = nblk * n * 4; let rd_scale = |half: usize, idx: usize| -> f32 {
438 let o = half + idx * 4;
439 f32::from_le_bytes([
440 scale_bytes[o],
441 scale_bytes[o + 1],
442 scale_bytes[o + 2],
443 scale_bytes[o + 3],
444 ])
445 };
446 let nib = |plane_off: usize, elem: usize| -> u8 {
447 let b = w_bytes[plane_off + elem / 2];
448 if elem & 1 == 0 { b & 0x0F } else { b >> 4 }
449 };
450 for i in 0..m {
451 let x_row = &x[i * k..i * k + k];
452 for j in 0..n {
453 let mut acc = 0f32;
457 for b in 0..nblk {
458 let s0 = rd_scale(0, b * n + j);
459 let s1 = rd_scale(sbytes, b * n + j);
460 let lo = b * g;
461 let hi = (lo + g).min(k);
462 let mut acc0 = 0f32;
463 let mut acc1 = 0f32;
464 for p in lo..hi {
465 let elem = p * n + j;
466 let xv = x_row[p];
467 acc0 += xv * fp4_e2m1_to_f32(nib(0, elem));
468 acc1 += xv * fp4_e2m1_to_f32(nib(plane, elem));
469 }
470 acc += acc0 * s0 + acc1 * s1;
471 }
472 out[i * n + j] = acc;
473 }
474 }
475}
476
477#[inline]
487pub(crate) fn lowp_nblk(len: usize, layout: rlx_ir::ScaleLayout) -> usize {
488 match layout {
489 rlx_ir::ScaleLayout::PerTensor => 1,
490 _ => len.div_ceil(layout.block() as usize),
491 }
492}
493
494#[inline]
497pub(crate) fn lowp_snap_scale(layout: rlx_ir::ScaleLayout, s: f32) -> f32 {
498 use rlx_ir::lowp_codec;
499 match layout {
500 rlx_ir::ScaleLayout::PerTensor => s,
501 rlx_ir::ScaleLayout::BlockMxE8M0 { .. } => {
502 lowp_codec::e8m0_to_f32(lowp_codec::f32_to_e8m0(s))
503 }
504 rlx_ir::ScaleLayout::Nvfp4 { .. } => lowp_codec::decode(
505 rlx_ir::ScaledFormat::F8E4M3,
506 lowp_codec::encode(rlx_ir::ScaledFormat::F8E4M3, s),
507 ),
508 }
509}
510
511#[inline]
513pub(crate) fn lowp_scale_at(
514 layout: rlx_ir::ScaleLayout,
515 scales: &[f32],
516 free: usize,
517 contract: usize,
518 nblk: usize,
519) -> f32 {
520 match layout {
521 rlx_ir::ScaleLayout::PerTensor => scales.first().copied().unwrap_or(1.0),
522 _ => scales[free * nblk + contract / layout.block() as usize],
523 }
524}
525
526pub(crate) fn lowp_compute_scales(
529 x: &[f32],
530 fmt: rlx_ir::ScaledFormat,
531 layout: rlx_ir::ScaleLayout,
532 rows: usize,
533 cols: usize,
534) -> Vec<f32> {
535 let maxf = fmt.max_finite();
536 let to_scale = |amax: f32| if amax > 0.0 { amax / maxf } else { 1.0 };
537 match layout {
538 rlx_ir::ScaleLayout::PerTensor => {
539 let amax = x.iter().fold(0.0f32, |a, &v| a.max(v.abs()));
540 vec![to_scale(amax)]
541 }
542 _ => {
543 let block = layout.block() as usize;
544 let nblk = cols.div_ceil(block);
545 let mut out = vec![1.0f32; rows * nblk];
546 for r in 0..rows {
547 for b in 0..nblk {
548 let lo = b * block;
549 let hi = (lo + block).min(cols);
550 let mut amax = 0.0f32;
551 for c in lo..hi {
552 amax = amax.max(x[r * cols + c].abs());
553 }
554 out[r * nblk + b] = lowp_snap_scale(layout, to_scale(amax));
555 }
556 }
557 out
558 }
559 }
560}
561
562pub(crate) fn lowp_quantize(
565 x: &[f32],
566 scales: &[f32],
567 fmt: rlx_ir::ScaledFormat,
568 layout: rlx_ir::ScaleLayout,
569 rows: usize,
570 cols: usize,
571 out: &mut [u8],
572) {
573 let nblk = lowp_nblk(cols, layout);
574 for r in 0..rows {
575 for c in 0..cols {
576 let s = lowp_scale_at(layout, scales, r, c, nblk);
577 let v = if s != 0.0 { x[r * cols + c] / s } else { 0.0 };
578 out[r * cols + c] = rlx_ir::lowp_codec::encode(fmt, v);
579 }
580 }
581}
582
583#[allow(clippy::too_many_arguments)]
585pub(crate) fn lowp_scaled_matmul(
586 lhs: &[u8],
587 rhs: &[u8],
588 lhs_scales: &[f32],
589 rhs_scales: &[f32],
590 bias: Option<&[f32]>,
591 out: &mut [f32],
592 m: usize,
593 n: usize,
594 k: usize,
595 layout: rlx_ir::ScaleLayout,
596 lhs_fmt: rlx_ir::ScaledFormat,
597 rhs_fmt: rlx_ir::ScaledFormat,
598) {
599 use rlx_ir::lowp_codec::decode;
600 let nblk = lowp_nblk(k, layout);
601 let bs = match layout {
605 rlx_ir::ScaleLayout::PerTensor => k.max(1),
606 _ => layout.block() as usize,
607 };
608 for i in 0..m {
609 for j in 0..n {
610 let mut acc = 0f32;
611 for b in 0..nblk {
612 let lo = b * bs;
613 let hi = (lo + bs).min(k);
614 let a_scale = lowp_scale_at(layout, lhs_scales, i, lo, nblk);
615 let b_scale = lowp_scale_at(layout, rhs_scales, j, lo, nblk);
616 let mut bacc = 0f32;
617 for p in lo..hi {
618 bacc += decode(lhs_fmt, lhs[i * k + p]) * decode(rhs_fmt, rhs[j * k + p]);
619 }
620 acc += bacc * (a_scale * b_scale);
621 }
622 out[i * n + j] = acc + bias.map_or(0.0, |bb| bb[j]);
623 }
624 }
625}
626
627#[allow(clippy::too_many_arguments)]
635pub(crate) fn lowp_scaled_grouped_matmul(
636 input: &[u8],
637 weight: &[u8],
638 input_scales: &[f32],
639 weight_scales: &[f32],
640 expert_idx: &[f32],
641 bias: Option<&[f32]>,
642 out: &mut [f32],
643 m: usize,
644 n: usize,
645 k: usize,
646 num_experts: usize,
647 layout: rlx_ir::ScaleLayout,
648 lhs_fmt: rlx_ir::ScaledFormat,
649 rhs_fmt: rlx_ir::ScaledFormat,
650) {
651 use rlx_ir::lowp_codec::decode;
652 let nblk = lowp_nblk(k, layout);
653 let bs = match layout {
654 rlx_ir::ScaleLayout::PerTensor => k.max(1),
655 _ => layout.block() as usize,
656 };
657 for i in 0..m {
658 let e = expert_idx[i] as usize;
659 debug_assert!(
660 e < num_experts,
661 "scaled grouped expert_idx out of range: {e} >= {num_experts}"
662 );
663 let a_codes = &input[i * k..(i + 1) * k];
664 let w_codes = &weight[e * n * k..(e + 1) * n * k];
665 for j in 0..n {
666 let mut acc = 0f32;
667 for b in 0..nblk {
668 let lo = b * bs;
669 let hi = (lo + bs).min(k);
670 let a_scale = lowp_scale_at(layout, input_scales, i, lo, nblk);
671 let w_scale = lowp_scale_at(layout, weight_scales, e * n + j, lo, nblk);
672 let mut bacc = 0f32;
673 for p in lo..hi {
674 bacc += decode(lhs_fmt, a_codes[p]) * decode(rhs_fmt, w_codes[j * k + p]);
675 }
676 acc += bacc * (a_scale * w_scale);
677 }
678 out[i * n + j] = acc + bias.map_or(0.0, |bb| bb[e * n + j]);
679 }
680 }
681}
682
683pub(crate) fn lowp_dequantize(
686 codes: &[u8],
687 scales: &[f32],
688 fmt: rlx_ir::ScaledFormat,
689 layout: rlx_ir::ScaleLayout,
690 rows: usize,
691 cols: usize,
692 out: &mut [f32],
693) {
694 use rlx_ir::lowp_codec::decode;
695 let nblk = lowp_nblk(cols, layout);
696 for r in 0..rows {
697 for c in 0..cols {
698 let s = lowp_scale_at(layout, scales, r, c, nblk);
699 out[r * cols + c] = decode(fmt, codes[r * cols + c]) * s;
700 }
701 }
702}
703
704pub(crate) unsafe fn lowp_read_scales(
707 layout: rlx_ir::ScaleLayout,
708 base: *mut u8,
709 offset: usize,
710 n: usize,
711) -> Vec<f32> {
712 use rlx_ir::lowp_codec;
713 match layout {
714 rlx_ir::ScaleLayout::PerTensor => {
715 unsafe { std::slice::from_raw_parts(base.add(offset) as *const f32, n) }.to_vec()
716 }
717 rlx_ir::ScaleLayout::BlockMxE8M0 { .. } => {
718 let bytes = unsafe { std::slice::from_raw_parts(base.add(offset), n) };
719 bytes.iter().map(|&b| lowp_codec::e8m0_to_f32(b)).collect()
720 }
721 rlx_ir::ScaleLayout::Nvfp4 { .. } => {
722 let bytes = unsafe { std::slice::from_raw_parts(base.add(offset), n) };
723 bytes
724 .iter()
725 .map(|&b| lowp_codec::decode(rlx_ir::ScaledFormat::F8E4M3, b))
726 .collect()
727 }
728 }
729}
730
731#[allow(unused_variables)]
732pub(crate) fn compile_quantize(
733 node: &rlx_ir::Node,
734 graph: &Graph,
735 arena: &crate::arena::Arena,
736 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
737 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
738 rng: rlx_ir::RngOptions,
739) -> Thunk {
740 let Op::Quantize {
741 axis,
742 scales,
743 zero_points,
744 } = &node.op
745 else {
746 unreachable!()
747 };
748 {
749 let (chan_axis, chan_dim, inner) = quant_layout(&node.shape, *axis);
750 Thunk::Quantize {
751 x: node_offset(arena, node.inputs[0]),
752 q: node_offset(arena, node.id),
753 len: node.shape.num_elements().unwrap() as u32,
754 chan_axis: chan_axis as u32,
755 chan_dim: chan_dim as u32,
756 inner: inner as u32,
757 scales: scales.clone(),
758 zero_points: zero_points.clone(),
759 }
760 }
761}
762
763#[allow(unused_variables)]
764pub(crate) fn compile_fake_quantize(
765 node: &rlx_ir::Node,
766 graph: &Graph,
767 arena: &crate::arena::Arena,
768 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
769 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
770 rng: rlx_ir::RngOptions,
771) -> Thunk {
772 let Op::FakeQuantize {
773 bits,
774 axis,
775 ste,
776 scale_mode,
777 } = &node.op
778 else {
779 unreachable!()
780 };
781 {
782 let (chan_axis, chan_dim, inner) = quant_layout(&node.shape, *axis);
783 let state_off = match scale_mode {
784 rlx_ir::op::ScaleMode::PerBatch => None,
785 rlx_ir::op::ScaleMode::EMA { .. } | rlx_ir::op::ScaleMode::Fixed => {
786 debug_assert_eq!(
788 node.inputs.len(),
789 2,
790 "EMA/Fixed FakeQuantize needs a state input"
791 );
792 Some(node_offset(arena, node.inputs[1]))
793 }
794 };
795 Thunk::FakeQuantize {
796 x: node_offset(arena, node.inputs[0]),
797 out: node_offset(arena, node.id),
798 len: node.shape.num_elements().unwrap() as u32,
799 chan_axis: chan_axis as u32,
800 chan_dim: chan_dim as u32,
801 inner: inner as u32,
802 bits: *bits,
803 ste: *ste,
804 scale_mode: *scale_mode,
805 state_off,
806 }
807 }
808}
809
810#[allow(unused_variables)]
811pub(crate) fn compile_fake_quantize_l_s_q(
812 node: &rlx_ir::Node,
813 graph: &Graph,
814 arena: &crate::arena::Arena,
815 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
816 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
817 rng: rlx_ir::RngOptions,
818) -> Thunk {
819 let Op::FakeQuantizeLSQ { bits, axis } = &node.op else {
820 unreachable!()
821 };
822 {
823 let (chan_axis, chan_dim, inner) = quant_layout(&node.shape, *axis);
824 Thunk::FakeQuantizeLSQ {
825 x: node_offset(arena, node.inputs[0]),
826 scale_off: node_offset(arena, node.inputs[1]),
827 out: node_offset(arena, node.id),
828 len: node.shape.num_elements().unwrap() as u32,
829 chan_axis: chan_axis as u32,
830 chan_dim: chan_dim as u32,
831 inner: inner as u32,
832 bits: *bits,
833 }
834 }
835}
836
837#[allow(unused_variables)]
838pub(crate) fn compile_fake_quantize_l_s_q_backward_x(
839 node: &rlx_ir::Node,
840 graph: &Graph,
841 arena: &crate::arena::Arena,
842 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
843 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
844 rng: rlx_ir::RngOptions,
845) -> Thunk {
846 let Op::FakeQuantizeLSQBackwardX { bits, axis } = &node.op else {
847 unreachable!()
848 };
849 {
850 let (chan_axis, chan_dim, inner) = quant_layout(&node.shape, *axis);
851 Thunk::FakeQuantizeLSQBackwardX {
852 x: node_offset(arena, node.inputs[0]),
853 scale_off: node_offset(arena, node.inputs[1]),
854 dy: node_offset(arena, node.inputs[2]),
855 dx: node_offset(arena, node.id),
856 len: node.shape.num_elements().unwrap() as u32,
857 chan_axis: chan_axis as u32,
858 chan_dim: chan_dim as u32,
859 inner: inner as u32,
860 bits: *bits,
861 }
862 }
863}
864
865#[allow(unused_variables)]
866pub(crate) fn compile_fake_quantize_l_s_q_backward_scale(
867 node: &rlx_ir::Node,
868 graph: &Graph,
869 arena: &crate::arena::Arena,
870 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
871 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
872 rng: rlx_ir::RngOptions,
873) -> Thunk {
874 let Op::FakeQuantizeLSQBackwardScale { bits, axis } = &node.op else {
875 unreachable!()
876 };
877 {
878 let in_shape = &graph.node(node.inputs[0]).shape;
881 let (chan_axis, chan_dim, inner) = quant_layout(in_shape, *axis);
882 Thunk::FakeQuantizeLSQBackwardScale {
883 x: node_offset(arena, node.inputs[0]),
884 scale_off: node_offset(arena, node.inputs[1]),
885 dy: node_offset(arena, node.inputs[2]),
886 dscale: node_offset(arena, node.id),
887 len: in_shape.num_elements().unwrap() as u32,
888 chan_axis: chan_axis as u32,
889 chan_dim: chan_dim as u32,
890 inner: inner as u32,
891 bits: *bits,
892 }
893 }
894}
895
896#[allow(unused_variables)]
897pub(crate) fn compile_fake_quantize_backward(
898 node: &rlx_ir::Node,
899 graph: &Graph,
900 arena: &crate::arena::Arena,
901 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
902 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
903 rng: rlx_ir::RngOptions,
904) -> Thunk {
905 let Op::FakeQuantizeBackward { bits, axis, ste } = &node.op else {
906 unreachable!()
907 };
908 {
909 let (chan_axis, chan_dim, inner) = quant_layout(&node.shape, *axis);
910 Thunk::FakeQuantizeBackward {
911 x: node_offset(arena, node.inputs[0]),
912 dy: node_offset(arena, node.inputs[1]),
913 dx: node_offset(arena, node.id),
914 len: node.shape.num_elements().unwrap() as u32,
915 chan_axis: chan_axis as u32,
916 chan_dim: chan_dim as u32,
917 inner: inner as u32,
918 bits: *bits,
919 ste: *ste,
920 }
921 }
922}
923
924#[allow(unused_variables)]
925pub(crate) fn compile_dequantize(
926 node: &rlx_ir::Node,
927 graph: &Graph,
928 arena: &crate::arena::Arena,
929 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
930 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
931 rng: rlx_ir::RngOptions,
932) -> Thunk {
933 let Op::Dequantize {
934 axis,
935 scales,
936 zero_points,
937 } = &node.op
938 else {
939 unreachable!()
940 };
941 {
942 let (chan_axis, chan_dim, inner) = quant_layout(&node.shape, *axis);
943 Thunk::Dequantize {
944 q: node_offset(arena, node.inputs[0]),
945 x: node_offset(arena, node.id),
946 len: node.shape.num_elements().unwrap() as u32,
947 chan_axis: chan_axis as u32,
948 chan_dim: chan_dim as u32,
949 inner: inner as u32,
950 scales: scales.clone(),
951 zero_points: zero_points.clone(),
952 }
953 }
954}
955
956#[allow(unused_variables)]
957pub(crate) fn compile_q_mat_mul(
958 node: &rlx_ir::Node,
959 graph: &Graph,
960 arena: &crate::arena::Arena,
961 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
962 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
963 rng: rlx_ir::RngOptions,
964) -> Thunk {
965 let Op::QMatMul {
966 x_zp,
967 w_zp,
968 out_zp,
969 mult,
970 } = &node.op
971 else {
972 unreachable!()
973 };
974 {
975 let x_shape = &graph.node(node.inputs[0]).shape;
976 let w_shape = &graph.node(node.inputs[1]).shape;
977 let m = x_shape.dim(0).unwrap_static();
978 let k = x_shape.dim(1).unwrap_static();
979 let n = w_shape.dim(1).unwrap_static();
980 Thunk::QMatMul {
981 x: node_offset(arena, node.inputs[0]),
982 w: node_offset(arena, node.inputs[1]),
983 bias: node_offset(arena, node.inputs[2]),
984 out: node_offset(arena, node.id),
985 m: m as u32,
986 k: k as u32,
987 n: n as u32,
988 x_zp: *x_zp,
989 w_zp: *w_zp,
990 out_zp: *out_zp,
991 mult: *mult,
992 }
993 }
994}
995
996#[allow(unused_variables)]
997pub(crate) fn compile_q_conv2d(
998 node: &rlx_ir::Node,
999 graph: &Graph,
1000 arena: &crate::arena::Arena,
1001 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
1002 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
1003 rng: rlx_ir::RngOptions,
1004) -> Thunk {
1005 let Op::QConv2d {
1006 kernel_size,
1007 stride,
1008 padding,
1009 dilation,
1010 groups,
1011 x_zp,
1012 w_zp,
1013 out_zp,
1014 mult,
1015 } = &node.op
1016 else {
1017 unreachable!()
1018 };
1019 {
1020 let in_shape = &graph.node(node.inputs[0]).shape;
1021 let w_shape = &graph.node(node.inputs[1]).shape;
1022 let out_shape = &node.shape;
1023 if kernel_size.len() == 2
1024 && in_shape.rank() == 4
1025 && w_shape.rank() == 4
1026 && out_shape.rank() == 4
1027 {
1028 Thunk::QConv2d {
1029 x: node_offset(arena, node.inputs[0]),
1030 w: node_offset(arena, node.inputs[1]),
1031 bias: node_offset(arena, node.inputs[2]),
1032 out: node_offset(arena, node.id),
1033 n: in_shape.dim(0).unwrap_static() as u32,
1034 c_in: in_shape.dim(1).unwrap_static() as u32,
1035 h: in_shape.dim(2).unwrap_static() as u32,
1036 w_in: in_shape.dim(3).unwrap_static() as u32,
1037 c_out: out_shape.dim(1).unwrap_static() as u32,
1038 h_out: out_shape.dim(2).unwrap_static() as u32,
1039 w_out: out_shape.dim(3).unwrap_static() as u32,
1040 kh: kernel_size[0] as u32,
1041 kw: kernel_size[1] as u32,
1042 sh: stride.first().copied().unwrap_or(1) as u32,
1043 sw: stride.get(1).copied().unwrap_or(1) as u32,
1044 ph: padding.first().copied().unwrap_or(0) as u32,
1045 pw: padding.get(1).copied().unwrap_or(0) as u32,
1046 dh: dilation.first().copied().unwrap_or(1) as u32,
1047 dw: dilation.get(1).copied().unwrap_or(1) as u32,
1048 groups: *groups as u32,
1049 x_zp: *x_zp,
1050 w_zp: *w_zp,
1051 out_zp: *out_zp,
1052 mult: *mult,
1053 }
1054 } else {
1055 Thunk::Nop
1056 }
1057 }
1058}
1059
1060#[allow(unused_variables)]
1061pub(crate) fn compile_dequant_mat_mul(
1062 node: &rlx_ir::Node,
1063 graph: &Graph,
1064 arena: &crate::arena::Arena,
1065 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
1066 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
1067 rng: rlx_ir::RngOptions,
1068) -> Thunk {
1069 let Op::DequantMatMul { scheme } = &node.op else {
1070 unreachable!()
1071 };
1072 {
1073 use rlx_ir::quant::QuantScheme;
1074 let n = node.shape.dim(node.shape.rank() - 1).unwrap_static();
1075 let total = node.shape.num_elements().unwrap();
1076 let m = total / n.max(1);
1077 let x_total = graph.node(node.inputs[0]).shape.num_elements().unwrap();
1078 let k = x_total / m.max(1);
1079 if scheme.is_gguf() {
1080 Thunk::DequantMatMulGguf {
1081 x: node_offset(arena, node.inputs[0]),
1082 w_q: node_offset(arena, node.inputs[1]),
1083 dst: node_offset(arena, node.id),
1084 m: m as u32,
1085 k: k as u32,
1086 n: n as u32,
1087 scheme: *scheme,
1088 }
1089 } else {
1090 match scheme {
1091 QuantScheme::Nvfp4Block => Thunk::DequantMatMulNvfp4 {
1092 x: node_offset(arena, node.inputs[0]),
1093 w_q: node_offset(arena, node.inputs[1]),
1094 scale: node_offset(arena, node.inputs[2]),
1095 global_scale: node_offset(arena, node.inputs[3]),
1096 dst: node_offset(arena, node.id),
1097 m: m as u32,
1098 k: k as u32,
1099 n: n as u32,
1100 },
1101 QuantScheme::MxFp4x2Block { group_size } => Thunk::DequantMatMulMxFp4x2 {
1102 x: node_offset(arena, node.inputs[0]),
1103 w_q: node_offset(arena, node.inputs[1]),
1104 scale: node_offset(arena, node.inputs[2]),
1105 dst: node_offset(arena, node.id),
1106 m: m as u32,
1107 k: k as u32,
1108 n: n as u32,
1109 group: *group_size,
1110 },
1111 QuantScheme::Int4Block { block_size } => Thunk::DequantMatMulInt4 {
1112 x: node_offset(arena, node.inputs[0]),
1113 w_q: node_offset(arena, node.inputs[1]),
1114 scale: node_offset(arena, node.inputs[2]),
1115 zp: node_offset(arena, node.inputs[3]),
1116 dst: node_offset(arena, node.id),
1117 m: m as u32,
1118 k: k as u32,
1119 n: n as u32,
1120 block_size: *block_size,
1121 is_asymmetric: false,
1122 },
1123 QuantScheme::Fp8E4m3 => Thunk::DequantMatMulFp8 {
1124 x: node_offset(arena, node.inputs[0]),
1125 w_q: node_offset(arena, node.inputs[1]),
1126 scale: node_offset(arena, node.inputs[2]),
1127 dst: node_offset(arena, node.id),
1128 m: m as u32,
1129 k: k as u32,
1130 n: n as u32,
1131 e5m2: false,
1132 },
1133 QuantScheme::Fp8E5m2 => Thunk::DequantMatMulFp8 {
1134 x: node_offset(arena, node.inputs[0]),
1135 w_q: node_offset(arena, node.inputs[1]),
1136 scale: node_offset(arena, node.inputs[2]),
1137 dst: node_offset(arena, node.id),
1138 m: m as u32,
1139 k: k as u32,
1140 n: n as u32,
1141 e5m2: true,
1142 },
1143 QuantScheme::Int8Block { block_size } => Thunk::DequantMatMul {
1144 x: node_offset(arena, node.inputs[0]),
1145 w_q: node_offset(arena, node.inputs[1]),
1146 scale: node_offset(arena, node.inputs[2]),
1147 zp: node_offset(arena, node.inputs[3]),
1148 dst: node_offset(arena, node.id),
1149 m: m as u32,
1150 k: k as u32,
1151 n: n as u32,
1152 block_size: *block_size,
1153 is_asymmetric: false,
1154 },
1155 QuantScheme::Int8BlockAsym { block_size } => Thunk::DequantMatMul {
1156 x: node_offset(arena, node.inputs[0]),
1157 w_q: node_offset(arena, node.inputs[1]),
1158 scale: node_offset(arena, node.inputs[2]),
1159 zp: node_offset(arena, node.inputs[3]),
1160 dst: node_offset(arena, node.id),
1161 m: m as u32,
1162 k: k as u32,
1163 n: n as u32,
1164 block_size: *block_size,
1165 is_asymmetric: true,
1166 },
1167 QuantScheme::MlxAffine { .. }
1168 | QuantScheme::MlxMxfp4 { .. }
1169 | QuantScheme::MlxMxfp8 { .. } => Thunk::DequantMatMulMlx {
1170 x: node_offset(arena, node.inputs[0]),
1171 w_q: node_offset(arena, node.inputs[1]),
1172 scale: node_offset(arena, node.inputs[2]),
1173 zp: node_offset(arena, node.inputs[3]),
1174 dst: node_offset(arena, node.id),
1175 m: m as u32,
1176 k: k as u32,
1177 n: n as u32,
1178 scheme: *scheme,
1179 },
1180 other => panic!(
1181 "DequantMatMul on CPU supports Int8/Int4/FP8/NVFP4/MLX legacy or GGUF schemes; got {other}"
1182 ),
1183 }
1184 }
1185 }
1186}
1187
1188#[allow(unused_variables)]
1189pub(crate) fn compile_scaled_mat_mul(
1190 node: &rlx_ir::Node,
1191 graph: &Graph,
1192 arena: &crate::arena::Arena,
1193 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
1194 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
1195 rng: rlx_ir::RngOptions,
1196) -> Thunk {
1197 let Op::ScaledMatMul {
1198 lhs_format,
1199 rhs_format,
1200 scale_layout,
1201 has_bias,
1202 } = &node.op
1203 else {
1204 unreachable!()
1205 };
1206 {
1207 let n = node.shape.dim(node.shape.rank() - 1).unwrap_static();
1209 let total = node.shape.num_elements().unwrap();
1210 let m = total / n.max(1);
1211 let lhs_total = graph.node(node.inputs[0]).shape.num_elements().unwrap();
1212 let k = lhs_total / m.max(1);
1213 Thunk::ScaledMatMul {
1214 lhs: node_offset(arena, node.inputs[0]),
1215 rhs: node_offset(arena, node.inputs[1]),
1216 lhs_scale: node_offset(arena, node.inputs[2]),
1217 rhs_scale: node_offset(arena, node.inputs[3]),
1218 bias: if *has_bias {
1219 node_offset(arena, node.inputs[4])
1220 } else {
1221 0
1222 },
1223 dst: node_offset(arena, node.id),
1224 m: m as u32,
1225 k: k as u32,
1226 n: n as u32,
1227 lhs_fmt: *lhs_format,
1228 rhs_fmt: *rhs_format,
1229 layout: *scale_layout,
1230 has_bias: *has_bias,
1231 }
1232 }
1233}
1234
1235#[allow(unused_variables)]
1236pub(crate) fn compile_scaled_grouped_mat_mul(
1237 node: &rlx_ir::Node,
1238 graph: &Graph,
1239 arena: &crate::arena::Arena,
1240 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
1241 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
1242 rng: rlx_ir::RngOptions,
1243) -> Thunk {
1244 let Op::ScaledGroupedMatMul {
1245 lhs_format,
1246 rhs_format,
1247 scale_layout,
1248 has_bias,
1249 } = &node.op
1250 else {
1251 unreachable!()
1252 };
1253 {
1254 let in_shape = &graph.node(node.inputs[0]).shape;
1256 let w_shape = &graph.node(node.inputs[1]).shape;
1257 let m = in_shape.dim(in_shape.rank() - 2).unwrap_static();
1258 let k = in_shape.dim(in_shape.rank() - 1).unwrap_static();
1259 let num_experts = w_shape.dim(0).unwrap_static();
1260 let n = w_shape.dim(w_shape.rank() - 2).unwrap_static();
1261 Thunk::ScaledGroupedMatMul {
1262 input: node_offset(arena, node.inputs[0]),
1263 weight: node_offset(arena, node.inputs[1]),
1264 input_scale: node_offset(arena, node.inputs[2]),
1265 weight_scale: node_offset(arena, node.inputs[3]),
1266 expert_idx: node_offset(arena, node.inputs[4]),
1267 bias: if *has_bias {
1268 node_offset(arena, node.inputs[5])
1269 } else {
1270 0
1271 },
1272 dst: node_offset(arena, node.id),
1273 m: m as u32,
1274 k: k as u32,
1275 n: n as u32,
1276 num_experts: num_experts as u32,
1277 lhs_fmt: *lhs_format,
1278 rhs_fmt: *rhs_format,
1279 layout: *scale_layout,
1280 has_bias: *has_bias,
1281 }
1282 }
1283}
1284
1285#[allow(unused_variables)]
1286pub(crate) fn compile_scaled_quantize(
1287 node: &rlx_ir::Node,
1288 graph: &Graph,
1289 arena: &crate::arena::Arena,
1290 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
1291 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
1292 rng: rlx_ir::RngOptions,
1293) -> Thunk {
1294 let Op::ScaledQuantize {
1295 format,
1296 scale_layout,
1297 } = &node.op
1298 else {
1299 unreachable!()
1300 };
1301 {
1302 let xs = &graph.node(node.inputs[0]).shape;
1303 let cols = xs.dim(xs.rank() - 1).unwrap_static();
1304 let rows = xs.num_elements().unwrap() / cols.max(1);
1305 Thunk::ScaledQuantize {
1306 x: node_offset(arena, node.inputs[0]),
1307 scale: node_offset(arena, node.inputs[1]),
1308 dst: node_offset(arena, node.id),
1309 rows: rows as u32,
1310 cols: cols as u32,
1311 fmt: *format,
1312 layout: *scale_layout,
1313 }
1314 }
1315}
1316
1317#[allow(unused_variables)]
1318pub(crate) fn compile_scaled_quant_scale(
1319 node: &rlx_ir::Node,
1320 graph: &Graph,
1321 arena: &crate::arena::Arena,
1322 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
1323 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
1324 rng: rlx_ir::RngOptions,
1325) -> Thunk {
1326 let Op::ScaledQuantScale {
1327 format,
1328 scale_layout,
1329 } = &node.op
1330 else {
1331 unreachable!()
1332 };
1333 {
1334 let xs = &graph.node(node.inputs[0]).shape;
1335 let cols = xs.dim(xs.rank() - 1).unwrap_static();
1336 let rows = xs.num_elements().unwrap() / cols.max(1);
1337 Thunk::ScaledQuantScale {
1338 x: node_offset(arena, node.inputs[0]),
1339 dst: node_offset(arena, node.id),
1340 rows: rows as u32,
1341 cols: cols as u32,
1342 fmt: *format,
1343 layout: *scale_layout,
1344 }
1345 }
1346}
1347
1348#[allow(unused_variables)]
1349pub(crate) fn compile_scaled_dequantize(
1350 node: &rlx_ir::Node,
1351 graph: &Graph,
1352 arena: &crate::arena::Arena,
1353 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
1354 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
1355 rng: rlx_ir::RngOptions,
1356) -> Thunk {
1357 let Op::ScaledDequantize {
1358 format,
1359 scale_layout,
1360 } = &node.op
1361 else {
1362 unreachable!()
1363 };
1364 {
1365 let xs = &graph.node(node.inputs[0]).shape;
1366 let cols = xs.dim(xs.rank() - 1).unwrap_static();
1367 let rows = xs.num_elements().unwrap() / cols.max(1);
1368 Thunk::ScaledDequantize {
1369 codes: node_offset(arena, node.inputs[0]),
1370 scale: node_offset(arena, node.inputs[1]),
1371 dst: node_offset(arena, node.id),
1372 rows: rows as u32,
1373 cols: cols as u32,
1374 fmt: *format,
1375 layout: *scale_layout,
1376 }
1377 }
1378}
1379
1380#[allow(unused_variables)]
1381pub(crate) fn compile_dequant_grouped_mat_mul(
1382 node: &rlx_ir::Node,
1383 graph: &Graph,
1384 arena: &crate::arena::Arena,
1385 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
1386 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
1387 rng: rlx_ir::RngOptions,
1388) -> Thunk {
1389 let Op::DequantGroupedMatMul { scheme } = &node.op else {
1390 unreachable!()
1391 };
1392 {
1393 let in_shape = &graph.node(node.inputs[0]).shape;
1394 let w_shape = &graph.node(node.inputs[1]).shape;
1395 let m = in_shape.dim(in_shape.rank() - 2).unwrap_static();
1396 let k_dim = in_shape.dim(in_shape.rank() - 1).unwrap_static();
1397 let out_shape = &node.shape;
1398 let n = out_shape.dim(out_shape.rank() - 1).unwrap_static();
1399 let block_elems = scheme.gguf_block_size() as usize;
1400 let block_bytes = scheme.gguf_block_bytes() as usize;
1401 let slab_bytes = (k_dim * n) / block_elems * block_bytes;
1402 let total_bytes = w_shape.num_elements().unwrap();
1403 let num_experts = total_bytes / slab_bytes.max(1);
1404 Thunk::DequantGroupedMatMulGguf {
1405 input: node_offset(arena, node.inputs[0]),
1406 w_q: node_offset(arena, node.inputs[1]),
1407 expert_idx: node_offset(arena, node.inputs[2]),
1408 dst: node_offset(arena, node.id),
1409 m: m as u32,
1410 k_dim: k_dim as u32,
1411 n: n as u32,
1412 num_experts: num_experts as u32,
1413 scheme: *scheme,
1414 }
1415 }
1416}
1417
1418#[allow(unused_variables)]
1419pub(crate) fn compile_dequant_grouped_mat_mul_mlx(
1420 node: &rlx_ir::Node,
1421 graph: &Graph,
1422 arena: &crate::arena::Arena,
1423 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
1424 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
1425 rng: rlx_ir::RngOptions,
1426) -> Thunk {
1427 let Op::DequantGroupedMatMulMlx { scheme } = &node.op else {
1428 unreachable!()
1429 };
1430 let in_shape = &graph.node(node.inputs[0]).shape;
1431 let m = in_shape.dim(in_shape.rank() - 2).unwrap_static();
1432 let k_dim = in_shape.dim(in_shape.rank() - 1).unwrap_static();
1433 let n = node.shape.dim(node.shape.rank() - 1).unwrap_static();
1434 let scales_shape = &graph.node(node.inputs[2]).shape;
1436 let num_experts = scales_shape.dim(0).unwrap_static();
1437 let w_bytes = graph.node(node.inputs[1]).shape.num_elements().unwrap();
1438 let slab_bytes = w_bytes / num_experts.max(1);
1439 let scale_bf16 = graph.node(node.inputs[2]).shape.dtype() == rlx_ir::DType::BF16;
1440 Thunk::DequantGroupedMatMulMlx {
1441 input: node_offset(arena, node.inputs[0]),
1442 w_q: node_offset(arena, node.inputs[1]),
1443 scale: node_offset(arena, node.inputs[2]),
1444 zp: node_offset(arena, node.inputs[3]),
1445 expert_idx: node_offset(arena, node.inputs[4]),
1446 dst: node_offset(arena, node.id),
1447 m: m as u32,
1448 k_dim: k_dim as u32,
1449 n: n as u32,
1450 num_experts: num_experts as u32,
1451 slab_bytes: slab_bytes as u32,
1452 scheme: *scheme,
1453 scale_bf16,
1454 }
1455}
1456
1457pub(crate) fn exec_dequant_grouped_mat_mul_mlx(t: &Thunk, base: *mut u8) {
1459 let Thunk::DequantGroupedMatMulMlx {
1460 input,
1461 w_q,
1462 scale,
1463 zp,
1464 expert_idx,
1465 dst,
1466 m,
1467 k_dim,
1468 n,
1469 num_experts,
1470 slab_bytes,
1471 scheme,
1472 scale_bf16,
1473 } = t
1474 else {
1475 unreachable!()
1476 };
1477 unsafe {
1478 exec_dequant_grouped_mat_mul_mlx_inner(
1479 base,
1480 *input,
1481 *w_q,
1482 *scale,
1483 *zp,
1484 *expert_idx,
1485 *dst,
1486 *m as usize,
1487 *k_dim as usize,
1488 *n as usize,
1489 *num_experts as usize,
1490 *slab_bytes as usize,
1491 *scheme,
1492 *scale_bf16,
1493 );
1494 }
1495}
1496
1497#[allow(clippy::too_many_arguments)]
1500unsafe fn exec_dequant_grouped_mat_mul_mlx_inner(
1501 base: *mut u8,
1502 input: usize,
1503 w_q: usize,
1504 scale: usize,
1505 zp: usize,
1506 expert_idx: usize,
1507 dst: usize,
1508 m: usize,
1509 k: usize,
1510 n: usize,
1511 ne: usize,
1512 slab: usize,
1513 scheme: rlx_ir::quant::QuantScheme,
1514 scale_bf16: bool,
1515) {
1516 let gs = match scheme {
1521 rlx_ir::quant::QuantScheme::MlxAffine { group_size, .. }
1522 | rlx_ir::quant::QuantScheme::MlxMxfp4 { group_size } => group_size as usize,
1523 other => panic!("DequantGroupedMatMulMlx: expected MlxAffine/MlxMxfp4, got {other:?}"),
1524 };
1525 let n_groups = k / gs;
1526 let sb_per_expert = n * n_groups; unsafe {
1528 let inp = sl(input, base, m * k);
1529 let ids = sl(expert_idx, base, m);
1530 let out = sl_mut(dst, base, m * n);
1531 let wt = std::slice::from_raw_parts(base.add(w_q) as *const u8, ne * slab);
1532 let (scl, zpb, scl_b16, zpb_b16) = if scale_bf16 {
1535 let sb =
1536 std::slice::from_raw_parts(base.add(scale) as *const u8, ne * sb_per_expert * 2);
1537 let zb = std::slice::from_raw_parts(base.add(zp) as *const u8, ne * sb_per_expert * 2);
1538 (&[][..], &[][..], Some(sb), Some(zb))
1539 } else {
1540 (
1541 sl(scale, base, ne * sb_per_expert),
1542 sl(zp, base, ne * sb_per_expert),
1543 None,
1544 None,
1545 )
1546 };
1547 let bf16_slab = |bytes: &[u8], e: usize| -> Vec<f32> {
1548 bytes[e * sb_per_expert * 2..(e + 1) * sb_per_expert * 2]
1549 .chunks_exact(2)
1550 .map(|c| half::bf16::from_le_bytes([c[0], c[1]]).to_f32())
1551 .collect()
1552 };
1553 for r in 0..m {
1554 let e = (ids[r] as usize).min(ne.saturating_sub(1));
1555 let w_slab = &wt[e * slab..(e + 1) * slab];
1556 let (s_owned, b_owned);
1557 let (s_slab, b_slab): (&[f32], &[f32]) =
1558 if let (Some(sb), Some(zb)) = (scl_b16, zpb_b16) {
1559 s_owned = bf16_slab(sb, e);
1560 b_owned = bf16_slab(zb, e);
1561 (&s_owned, &b_owned)
1562 } else {
1563 (
1564 &scl[e * sb_per_expert..(e + 1) * sb_per_expert],
1565 &zpb[e * sb_per_expert..(e + 1) * sb_per_expert],
1566 )
1567 };
1568 let row = &inp[r * k..(r + 1) * k];
1569 let res = match scheme {
1570 rlx_ir::quant::QuantScheme::MlxAffine { bits, group_size } => {
1571 rlx_mlx_io::dequant_matvec_affine(
1579 row,
1580 w_slab,
1581 s_slab,
1582 b_slab,
1583 bits as u32,
1584 group_size,
1585 k,
1586 n,
1587 )
1588 }
1589 rlx_ir::quant::QuantScheme::MlxMxfp4 { group_size } => {
1590 rlx_mlx_io::dequant_matvec_mxfp4(row, w_slab, s_slab, group_size, k, n)
1595 }
1596 _ => unreachable!(),
1597 };
1598 match res {
1599 Ok(o) => out[r * n..(r + 1) * n].copy_from_slice(&o),
1600 Err(_) => out[r * n..(r + 1) * n].fill(0.0),
1601 }
1602 }
1603 }
1604}
1605
1606#[allow(clippy::too_many_arguments)]
1612pub fn dequant_grouped_matmul_affine_bt(
1613 x: &[f32],
1614 w_bytes: &[u8],
1615 scales: &[f32],
1616 biases: &[f32],
1617 idx: &[f32],
1618 out: &mut [f32],
1619 m: usize,
1620 k: usize,
1621 n: usize,
1622 num_experts: usize,
1623 bits: u32,
1624 group_size: usize,
1625) {
1626 use rayon::prelude::*;
1627 debug_assert_eq!(out.len(), m * n, "output buffer must be m×n");
1628 let slab = w_bytes.len() / num_experts.max(1);
1629 let n_groups = k / group_size.max(1);
1630 let sb = n * n_groups; out.par_chunks_mut(n).enumerate().for_each(|(r, out_row)| {
1638 let e = (idx[r] as usize).min(num_experts.saturating_sub(1));
1639 let w_slab = &w_bytes[e * slab..(e + 1) * slab];
1640 let s_slab = &scales[e * sb..(e + 1) * sb];
1641 let b_slab = &biases[e * sb..(e + 1) * sb];
1642 let row = &x[r * k..(r + 1) * k];
1643 match rlx_mlx_io::dequant_matvec_affine(
1646 row,
1647 w_slab,
1648 s_slab,
1649 b_slab,
1650 bits,
1651 group_size as u32,
1652 k,
1653 n,
1654 ) {
1655 Ok(o) => out_row.copy_from_slice(&o),
1656 Err(_) => out_row.fill(0.0),
1657 }
1658 });
1659}
1660
1661#[allow(clippy::too_many_arguments)]
1667pub fn dequant_grouped_matmul_mxfp4_bt(
1668 x: &[f32],
1669 w_bytes: &[u8],
1670 scales: &[f32],
1671 idx: &[f32],
1672 out: &mut [f32],
1673 m: usize,
1674 k: usize,
1675 n: usize,
1676 num_experts: usize,
1677 group_size: usize,
1678) {
1679 rlx_mlx_io::grouped_matmul_mxfp4_bt(
1685 x,
1686 w_bytes,
1687 scales,
1688 idx,
1689 out,
1690 m,
1691 k,
1692 n,
1693 num_experts,
1694 group_size,
1695 );
1696}
1697
1698#[allow(clippy::too_many_arguments)]
1702pub unsafe fn execute_dequant_grouped_matmul_mlx_f32(
1703 input: usize,
1704 w_q: usize,
1705 scale: usize,
1706 zp: usize,
1707 expert_idx: usize,
1708 dst: usize,
1709 m: usize,
1710 k: usize,
1711 n: usize,
1712 num_experts: usize,
1713 slab_bytes: usize,
1714 scheme: rlx_ir::quant::QuantScheme,
1715 scale_bf16: bool,
1716 base: *mut u8,
1717) {
1718 unsafe {
1719 exec_dequant_grouped_mat_mul_mlx_inner(
1720 base,
1721 input,
1722 w_q,
1723 scale,
1724 zp,
1725 expert_idx,
1726 dst,
1727 m,
1728 k,
1729 n,
1730 num_experts,
1731 slab_bytes,
1732 scheme,
1733 scale_bf16,
1734 );
1735 }
1736}
1737
1738#[allow(unused_variables)]
1739pub(crate) fn compile_dequant_mo_e_weights(
1740 node: &rlx_ir::Node,
1741 graph: &Graph,
1742 arena: &crate::arena::Arena,
1743 matmul_fold: &std::collections::HashMap<NodeId, (NodeId, bool, NodeId, bool)>,
1744 rng_shared: &std::sync::Arc<std::sync::RwLock<rlx_ir::RngOptions>>,
1745 rng: rlx_ir::RngOptions,
1746) -> Thunk {
1747 let Op::DequantMoEWeights { scheme } = &node.op else {
1748 unreachable!()
1749 };
1750 {
1751 let w_shape = &graph.node(node.inputs[0]).shape;
1752 let out_shape = &node.shape;
1753 let num_experts = out_shape.dim(0).unwrap_static();
1754 let k_dim = out_shape.dim(1).unwrap_static();
1755 let n = out_shape.dim(2).unwrap_static();
1756 let block_elems = scheme.gguf_block_size() as usize;
1757 let block_bytes = scheme.gguf_block_bytes() as usize;
1758 let slab_bytes = (k_dim * n) / block_elems * block_bytes;
1759 let total_bytes = w_shape.num_elements().unwrap();
1760 assert_eq!(
1761 total_bytes,
1762 num_experts * slab_bytes,
1763 "DequantMoEWeights packed bytes mismatch"
1764 );
1765 Thunk::DequantMoEWeightsGguf {
1766 w_q: node_offset(arena, node.inputs[0]),
1767 dst: node_offset(arena, node.id),
1768 k_dim: k_dim as u32,
1769 n: n as u32,
1770 num_experts: num_experts as u32,
1771 scheme: *scheme,
1772 }
1773 }
1774}
1775
1776#[inline(always)]
1777pub(crate) fn exec_dequant_mat_mul(t: &Thunk, base: *mut u8) {
1778 let Thunk::DequantMatMul {
1779 x,
1780 w_q,
1781 scale,
1782 zp,
1783 dst,
1784 m,
1785 k,
1786 n,
1787 block_size,
1788 is_asymmetric,
1789 } = t
1790 else {
1791 unreachable!()
1792 };
1793 {
1794 let (m, k, n, bs) = (*m as usize, *k as usize, *n as usize, *block_size as usize);
1795 let n_blocks = k.div_ceil(bs);
1796 unsafe {
1797 let xs = sl(*x, base, m * k);
1798 let w_bytes = std::slice::from_raw_parts(base.add(*w_q) as *const i8, k * n);
1799 let scales = sl(*scale, base, n_blocks * n);
1800 let zps = if *is_asymmetric {
1801 sl(*zp, base, n_blocks * n)
1802 } else {
1803 &[][..]
1804 };
1805 let out = sl_mut(*dst, base, m * n);
1806 #[cfg(rlx_cpu_amx_sme)]
1813 if n_blocks == 1
1814 && !*is_asymmetric
1815 && crate::intrinsics::apple_amx::sme::w8a8_dispatch_enabled()
1816 {
1817 use crate::intrinsics::apple_amx::sme;
1818 if m == 1 {
1819 let (xq, sx) = sme::quantize_i8_symmetric(xs);
1822 sme::qgemv_i8(&xq, sx, w_bytes, scales, out, k, n);
1823 return;
1824 } else if sme::worth_sme(m, k, n) {
1825 let (xq, sx) = sme::quantize_i8_symmetric(xs);
1826 sme::sme_qmatmul_i8_percol(&xq, sx, w_bytes, scales, out, m, k, n);
1827 return;
1828 }
1829 }
1830 dequant_matmul_int8(xs, w_bytes, scales, zps, out, m, k, n, bs, *is_asymmetric);
1831 }
1832 }
1833}
1834
1835#[inline(always)]
1836pub(crate) fn exec_dequant_mat_mul_gguf(t: &Thunk, base: *mut u8) {
1837 let Thunk::DequantMatMulGguf {
1838 x,
1839 w_q,
1840 dst,
1841 m,
1842 k,
1843 n,
1844 scheme,
1845 } = t
1846 else {
1847 unreachable!()
1848 };
1849 {
1850 let (m, k, n) = (*m as usize, *k as usize, *n as usize);
1851 let block_bytes = scheme.gguf_block_bytes() as usize;
1852 let block_elems = scheme.gguf_block_size() as usize;
1853 debug_assert!(
1854 block_bytes > 0 && block_elems > 0,
1855 "non-GGUF scheme in GGUF arm"
1856 );
1857 debug_assert!(
1858 (k * n).is_multiple_of(block_elems),
1859 "k*n={} not aligned to GGUF block size {}",
1860 k * n,
1861 block_elems
1862 );
1863 let total_bytes = (k * n) / block_elems * block_bytes;
1864 unsafe {
1865 let xs = sl(*x, base, m * k);
1866 let w_bytes_ptr = base.add(*w_q) as *const u8;
1867 let w_bytes = std::slice::from_raw_parts(w_bytes_ptr, total_bytes);
1868 let out = sl_mut(*dst, base, m * n);
1869 crate::gguf_matmul::gguf_matmul_bt_dispatch_at(
1870 xs, w_bytes, out, m, k, n, *scheme, *w_q,
1871 );
1872 }
1873 }
1874}
1875
1876#[inline(always)]
1877pub(crate) fn exec_dequant_mat_mul_int4(t: &Thunk, base: *mut u8) {
1878 let Thunk::DequantMatMulInt4 {
1879 x,
1880 w_q,
1881 scale,
1882 zp,
1883 dst,
1884 m,
1885 k,
1886 n,
1887 block_size,
1888 is_asymmetric,
1889 } = t
1890 else {
1891 unreachable!()
1892 };
1893 {
1894 let (m, k, n, bs) = (*m as usize, *k as usize, *n as usize, *block_size as usize);
1895 let n_blocks = k.div_ceil(bs);
1896 unsafe {
1897 let xs = sl(*x, base, m * k);
1898 let w_bytes =
1899 std::slice::from_raw_parts(base.add(*w_q) as *const u8, (k * n).div_ceil(2));
1900 let scales = sl(*scale, base, n_blocks * n);
1901 let zps = if *is_asymmetric {
1902 sl(*zp, base, n_blocks * n)
1903 } else {
1904 &[][..]
1905 };
1906 let out = sl_mut(*dst, base, m * n);
1907 dequant_matmul_int4(xs, w_bytes, scales, zps, out, m, k, n, bs, *is_asymmetric);
1908 }
1909 }
1910}
1911
1912#[inline(always)]
1913pub(crate) fn exec_dequant_mat_mul_fp8(t: &Thunk, base: *mut u8) {
1914 let Thunk::DequantMatMulFp8 {
1915 x,
1916 w_q,
1917 scale,
1918 dst,
1919 m,
1920 k,
1921 n,
1922 e5m2,
1923 } = t
1924 else {
1925 unreachable!()
1926 };
1927 {
1928 let (m, k, n) = (*m as usize, *k as usize, *n as usize);
1929 unsafe {
1930 let xs = sl(*x, base, m * k);
1931 let w_bytes = std::slice::from_raw_parts(base.add(*w_q) as *const u8, k * n);
1932 let scales = sl(*scale, base, n);
1933 let out = sl_mut(*dst, base, m * n);
1934 dequant_matmul_fp8(xs, w_bytes, scales, out, m, k, n, *e5m2);
1935 }
1936 }
1937}
1938
1939#[inline(always)]
1940pub(crate) fn exec_dequant_mat_mul_nvfp4(t: &Thunk, base: *mut u8) {
1941 let Thunk::DequantMatMulNvfp4 {
1942 x,
1943 w_q,
1944 scale,
1945 global_scale,
1946 dst,
1947 m,
1948 k,
1949 n,
1950 } = t
1951 else {
1952 unreachable!()
1953 };
1954 {
1955 let (m, k, n) = (*m as usize, *k as usize, *n as usize);
1956 let n_scale = k.div_ceil(rlx_ir::NVFP4_GROUP_SIZE) * n;
1957 unsafe {
1958 let xs = sl(*x, base, m * k);
1959 let w_bytes =
1960 std::slice::from_raw_parts(base.add(*w_q) as *const u8, (k * n).div_ceil(2));
1961 let scale_bytes = std::slice::from_raw_parts(base.add(*scale) as *const u8, n_scale);
1962 let gs = sl(*global_scale, base, 1)[0];
1963 let out = sl_mut(*dst, base, m * n);
1964 dequant_matmul_nvfp4(xs, w_bytes, scale_bytes, gs, out, m, k, n);
1965 }
1966 }
1967}
1968
1969pub(crate) fn exec_dequant_mat_mul_mxfp4x2(t: &Thunk, base: *mut u8) {
1970 let Thunk::DequantMatMulMxFp4x2 {
1971 x,
1972 w_q,
1973 scale,
1974 dst,
1975 m,
1976 k,
1977 n,
1978 group,
1979 } = t
1980 else {
1981 unreachable!()
1982 };
1983 let (m, k, n, group) = (*m as usize, *k as usize, *n as usize, *group as usize);
1984 let plane = (k * n).div_ceil(2);
1985 let nblk = k.div_ceil(group.max(1));
1986 unsafe {
1987 let xs = sl(*x, base, m * k);
1988 let w_bytes = std::slice::from_raw_parts(base.add(*w_q) as *const u8, 2 * plane);
1989 let scale_bytes =
1990 std::slice::from_raw_parts(base.add(*scale) as *const u8, 2 * nblk * n * 4);
1991 let out = sl_mut(*dst, base, m * n);
1992 dequant_matmul_mxfp4x2(xs, w_bytes, scale_bytes, group, out, m, k, n);
1993 }
1994}
1995
1996#[inline(always)]
1997pub(crate) fn exec_dequant_mat_mul_mlx(t: &Thunk, base: *mut u8) {
1998 let Thunk::DequantMatMulMlx {
1999 x,
2000 w_q,
2001 scale,
2002 zp,
2003 dst,
2004 m,
2005 k,
2006 n,
2007 scheme,
2008 } = t
2009 else {
2010 unreachable!()
2011 };
2012 unsafe {
2013 exec_dequant_mat_mul_mlx_inner(
2014 base,
2015 *x,
2016 *w_q,
2017 *scale,
2018 *zp,
2019 *dst,
2020 *m as usize,
2021 *k as usize,
2022 *n as usize,
2023 *scheme,
2024 );
2025 }
2026}
2027
2028pub(crate) unsafe fn exec_dequant_mat_mul_mlx_inner(
2030 base: *mut u8,
2031 x: usize,
2032 w_q: usize,
2033 scale: usize,
2034 zp: usize,
2035 dst: usize,
2036 m: usize,
2037 k: usize,
2038 n: usize,
2039 scheme: rlx_ir::quant::QuantScheme,
2040) {
2041 use rlx_ir::quant::QuantScheme;
2042 use rlx_mlx_io::{
2043 dequant_matmul_affine, dequant_mxfp4_f32, dequant_mxfp8_f32, pack_factor,
2044 validate_dequant_matmul_dims,
2045 };
2046 if let Err(e) = validate_dequant_matmul_dims(scheme, k, n, None) {
2047 panic!("DequantMatMulMlx: {e}");
2048 }
2049 let xs = unsafe { sl(x, base, m * k) };
2050 let out = unsafe { sl_mut(dst, base, m * n) };
2051 match scheme {
2052 QuantScheme::MlxAffine { bits, group_size } => {
2053 let gs = group_size as usize;
2054 let n_groups = k / gs;
2055 let pf = match pack_factor(bits as u32) {
2056 Ok(p) => p as usize,
2057 Err(e) => panic!("DequantMatMulMlx: {e}"),
2058 };
2059 let packs = gs / pf;
2060 let bpp = match bits {
2061 3 | 6 => 3,
2062 5 => 5,
2063 _ => 1,
2064 };
2065 let w_need = n * n_groups * packs * bpp;
2066 let w_bytes = unsafe { std::slice::from_raw_parts(base.add(w_q) as *const u8, w_need) };
2067 let scales = unsafe { sl(scale, base, n * n_groups) };
2068 let biases = unsafe { sl(zp, base, n * n_groups) };
2069 match dequant_matmul_affine(
2070 xs,
2071 w_bytes,
2072 scales,
2073 biases,
2074 bits as u32,
2075 group_size,
2076 m,
2077 k,
2078 n,
2079 ) {
2080 Ok(y) => out.copy_from_slice(&y),
2081 Err(e) => panic!("DequantMatMulMlx affine: {e}"),
2082 }
2083 }
2084 QuantScheme::MlxMxfp4 { group_size } => {
2085 let gs = group_size as usize;
2086 let n_groups = k / gs;
2087 let w_bytes =
2088 unsafe { std::slice::from_raw_parts(base.add(w_q) as *const u8, n * k / 2) };
2089 let scales_u8 =
2090 unsafe { std::slice::from_raw_parts(base.add(scale) as *const u8, n * n_groups) };
2091 let w_f = match dequant_mxfp4_f32(w_bytes, scales_u8, group_size, n, n_groups) {
2092 Ok(v) => v,
2093 Err(e) => panic!("DequantMatMulMlx mxfp4: {e}"),
2094 };
2095 matmul_x_wt(xs, &w_f, out, m, k, n);
2096 }
2097 QuantScheme::MlxMxfp8 { group_size } => {
2098 let gs = group_size as usize;
2099 let n_groups = k / gs;
2100 let w_bytes = unsafe { std::slice::from_raw_parts(base.add(w_q) as *const u8, n * k) };
2101 let scales_u8 =
2102 unsafe { std::slice::from_raw_parts(base.add(scale) as *const u8, n * n_groups) };
2103 let w_f = match dequant_mxfp8_f32(w_bytes, scales_u8, group_size, n, n_groups) {
2104 Ok(v) => v,
2105 Err(e) => panic!("DequantMatMulMlx mxfp8: {e}"),
2106 };
2107 matmul_x_wt(xs, &w_f, out, m, k, n);
2108 }
2109 other => panic!("DequantMatMulMlx: unexpected scheme {other}"),
2110 }
2111}
2112
2113#[inline]
2124fn matmul_x_wt(x: &[f32], w_nk: &[f32], out: &mut [f32], m: usize, k: usize, n: usize) {
2125 if m == 1 {
2126 crate::blas::sgemv_nn(w_nk, x, out, n, k, 1.0, 0.0);
2128 } else {
2129 crate::blas::sgemm_bt(x, w_nk, out, m, k, n, 1.0);
2131 }
2132}
2133
2134pub unsafe fn execute_dequant_matmul_mlx_f32(
2136 x: usize,
2137 w_q: usize,
2138 scale: usize,
2139 zp: usize,
2140 dst: usize,
2141 m: usize,
2142 k: usize,
2143 n: usize,
2144 scheme: rlx_ir::quant::QuantScheme,
2145 base: *mut u8,
2146) {
2147 unsafe {
2148 exec_dequant_mat_mul_mlx_inner(base, x, w_q, scale, zp, dst, m, k, n, scheme);
2149 }
2150}
2151
2152#[inline(always)]
2153pub(crate) fn exec_scaled_mat_mul(t: &Thunk, base: *mut u8) {
2154 let Thunk::ScaledMatMul {
2155 lhs,
2156 rhs,
2157 lhs_scale,
2158 rhs_scale,
2159 bias,
2160 dst,
2161 m,
2162 k,
2163 n,
2164 lhs_fmt,
2165 rhs_fmt,
2166 layout,
2167 has_bias,
2168 } = t
2169 else {
2170 unreachable!()
2171 };
2172 {
2173 let (m, k, n) = (*m as usize, *k as usize, *n as usize);
2174 let layout = *layout;
2175 let nblk = lowp_nblk(k, layout);
2176 let per_tensor = matches!(layout, rlx_ir::ScaleLayout::PerTensor);
2177 let n_lscale = if per_tensor { 1 } else { m * nblk };
2178 let n_rscale = if per_tensor { 1 } else { n * nblk };
2179 unsafe {
2180 let lhs_b = std::slice::from_raw_parts(base.add(*lhs) as *const u8, m * k);
2181 let rhs_b = std::slice::from_raw_parts(base.add(*rhs) as *const u8, n * k);
2182 let ls = lowp_read_scales(layout, base, *lhs_scale, n_lscale);
2183 let rs = lowp_read_scales(layout, base, *rhs_scale, n_rscale);
2184 let bias_s = if *has_bias {
2185 Some(sl(*bias, base, n))
2186 } else {
2187 None
2188 };
2189 let out = sl_mut(*dst, base, m * n);
2190 lowp_scaled_matmul(
2191 lhs_b, rhs_b, &ls, &rs, bias_s, out, m, n, k, layout, *lhs_fmt, *rhs_fmt,
2192 );
2193 }
2194 }
2195}
2196
2197pub(crate) fn exec_scaled_grouped_mat_mul(t: &Thunk, base: *mut u8) {
2198 let Thunk::ScaledGroupedMatMul {
2199 input,
2200 weight,
2201 input_scale,
2202 weight_scale,
2203 expert_idx,
2204 bias,
2205 dst,
2206 m,
2207 k,
2208 n,
2209 num_experts,
2210 lhs_fmt,
2211 rhs_fmt,
2212 layout,
2213 has_bias,
2214 } = t
2215 else {
2216 unreachable!()
2217 };
2218 unsafe {
2219 execute_scaled_grouped_matmul_f32(
2220 *input,
2221 *weight,
2222 *input_scale,
2223 *weight_scale,
2224 *expert_idx,
2225 *bias,
2226 *dst,
2227 *m as usize,
2228 *k as usize,
2229 *n as usize,
2230 *num_experts as usize,
2231 *has_bias,
2232 *lhs_fmt,
2233 *rhs_fmt,
2234 *layout,
2235 base,
2236 );
2237 }
2238}
2239
2240#[inline(always)]
2241pub(crate) fn exec_scaled_quantize(t: &Thunk, base: *mut u8) {
2242 let Thunk::ScaledQuantize {
2243 x,
2244 scale,
2245 dst,
2246 rows,
2247 cols,
2248 fmt,
2249 layout,
2250 } = t
2251 else {
2252 unreachable!()
2253 };
2254 {
2255 let (rows, cols) = (*rows as usize, *cols as usize);
2256 let layout = *layout;
2257 let nblk = lowp_nblk(cols, layout);
2258 let n_scale = if matches!(layout, rlx_ir::ScaleLayout::PerTensor) {
2259 1
2260 } else {
2261 rows * nblk
2262 };
2263 unsafe {
2264 let xs = sl(*x, base, rows * cols);
2265 let scales = lowp_read_scales(layout, base, *scale, n_scale);
2266 let out = std::slice::from_raw_parts_mut(base.add(*dst), rows * cols);
2267 lowp_quantize(xs, &scales, *fmt, layout, rows, cols, out);
2268 }
2269 }
2270}
2271
2272#[inline(always)]
2273pub(crate) fn exec_scaled_quant_scale(t: &Thunk, base: *mut u8) {
2274 let Thunk::ScaledQuantScale {
2275 x,
2276 dst,
2277 rows,
2278 cols,
2279 fmt,
2280 layout,
2281 } = t
2282 else {
2283 unreachable!()
2284 };
2285 {
2286 let (rows, cols) = (*rows as usize, *cols as usize);
2287 let layout = *layout;
2288 let nblk = lowp_nblk(cols, layout);
2289 unsafe {
2290 let xs = sl(*x, base, rows * cols);
2291 let scales = lowp_compute_scales(xs, *fmt, layout, rows, cols);
2292 match layout {
2293 rlx_ir::ScaleLayout::PerTensor => {
2294 sl_mut(*dst, base, 1)[0] = scales[0];
2295 }
2296 rlx_ir::ScaleLayout::BlockMxE8M0 { .. } => {
2297 let out = std::slice::from_raw_parts_mut(base.add(*dst), rows * nblk);
2298 for (o, &s) in out.iter_mut().zip(&scales) {
2299 *o = rlx_ir::lowp_codec::f32_to_e8m0(s);
2300 }
2301 }
2302 rlx_ir::ScaleLayout::Nvfp4 { .. } => {
2303 let out = std::slice::from_raw_parts_mut(base.add(*dst), rows * nblk);
2304 for (o, &s) in out.iter_mut().zip(&scales) {
2305 *o = rlx_ir::lowp_codec::encode(rlx_ir::ScaledFormat::F8E4M3, s);
2306 }
2307 }
2308 }
2309 }
2310 }
2311}
2312
2313#[inline(always)]
2314pub(crate) fn exec_scaled_dequantize(t: &Thunk, base: *mut u8) {
2315 let Thunk::ScaledDequantize {
2316 codes,
2317 scale,
2318 dst,
2319 rows,
2320 cols,
2321 fmt,
2322 layout,
2323 } = t
2324 else {
2325 unreachable!()
2326 };
2327 unsafe {
2328 execute_scaled_dequantize_f32(
2329 *codes,
2330 *scale,
2331 *dst,
2332 *rows as usize,
2333 *cols as usize,
2334 *fmt,
2335 *layout,
2336 base,
2337 );
2338 }
2339}
2340
2341#[inline(always)]
2342pub(crate) fn exec_dequant_grouped_mat_mul_gguf(t: &Thunk, base: *mut u8) {
2343 let Thunk::DequantGroupedMatMulGguf {
2344 input,
2345 w_q,
2346 expert_idx,
2347 dst,
2348 m,
2349 k_dim,
2350 n,
2351 num_experts,
2352 scheme,
2353 } = t
2354 else {
2355 unreachable!()
2356 };
2357 {
2358 let m = *m as usize;
2359 let k_dim = *k_dim as usize;
2360 let n = *n as usize;
2361 let num_experts = *num_experts as usize;
2362 let block_elems = scheme.gguf_block_size() as usize;
2363 let block_bytes = scheme.gguf_block_bytes() as usize;
2364 let slab_bytes = (k_dim * n) / block_elems * block_bytes;
2365 unsafe {
2366 let inp = sl(*input, base, m * k_dim);
2367 let wt =
2368 std::slice::from_raw_parts(base.add(*w_q) as *const u8, num_experts * slab_bytes);
2369 let ids = sl(*expert_idx, base, m);
2370 let out = sl_mut(*dst, base, m * n);
2371 crate::gguf_matmul::gguf_grouped_matmul_bt(
2372 inp,
2373 wt,
2374 ids,
2375 out,
2376 m,
2377 k_dim,
2378 n,
2379 num_experts,
2380 *scheme,
2381 );
2382 }
2383 }
2384}
2385
2386#[inline(always)]
2387pub(crate) fn exec_dequant_mo_e_weights_gguf(t: &Thunk, base: *mut u8) {
2388 let Thunk::DequantMoEWeightsGguf {
2389 w_q,
2390 dst,
2391 k_dim,
2392 n,
2393 num_experts,
2394 scheme,
2395 } = t
2396 else {
2397 unreachable!()
2398 };
2399 {
2400 let k_dim = *k_dim as usize;
2401 let n = *n as usize;
2402 let num_experts = *num_experts as usize;
2403 let block_elems = scheme.gguf_block_size() as usize;
2404 let block_bytes = scheme.gguf_block_bytes() as usize;
2405 let slab_bytes = (k_dim * n) / block_elems * block_bytes;
2406 unsafe {
2407 let wt =
2408 std::slice::from_raw_parts(base.add(*w_q) as *const u8, num_experts * slab_bytes);
2409 let out = sl_mut(*dst, base, num_experts * k_dim * n);
2410 crate::gguf_matmul::dequant_moe_weights_to_grouped_f32(
2411 wt,
2412 out,
2413 num_experts,
2414 k_dim,
2415 n,
2416 *scheme,
2417 );
2418 }
2419 }
2420}
2421
2422#[inline(always)]
2423pub(crate) fn exec_q_mat_mul(t: &Thunk, base: *mut u8) {
2424 let Thunk::QMatMul {
2425 x,
2426 w,
2427 bias,
2428 out,
2429 m,
2430 k,
2431 n,
2432 x_zp,
2433 w_zp,
2434 out_zp,
2435 mult,
2436 } = t
2437 else {
2438 unreachable!()
2439 };
2440 {
2441 let m = *m as usize;
2442 let k = *k as usize;
2443 let n = *n as usize;
2444 unsafe {
2445 let x_ptr = base.add(*x) as *const i8;
2446 let w_ptr = base.add(*w) as *const i8;
2447 let bias_ptr = base.add(*bias) as *const i32;
2448 let out_ptr = base.add(*out) as *mut i8;
2449 for mi in 0..m {
2450 for ni in 0..n {
2451 let mut acc: i32 = *bias_ptr.add(ni);
2452 for ki in 0..k {
2453 let xv = *x_ptr.add(mi * k + ki) as i32 - *x_zp;
2454 let wv = *w_ptr.add(ki * n + ni) as i32 - *w_zp;
2455 acc += xv * wv;
2456 }
2457 let r = (acc as f32 * *mult).round() as i32 + *out_zp;
2460 let r = r.clamp(-128, 127) as i8;
2461 *out_ptr.add(mi * n + ni) = r;
2462 }
2463 }
2464 }
2465 }
2466}
2467
2468#[inline(always)]
2469pub(crate) fn exec_quantize(t: &Thunk, base: *mut u8) {
2470 let Thunk::Quantize {
2471 x,
2472 q,
2473 len,
2474 chan_axis: _,
2475 chan_dim,
2476 inner,
2477 scales,
2478 zero_points,
2479 } = t
2480 else {
2481 unreachable!()
2482 };
2483 {
2484 let len = *len as usize;
2485 let chan_dim = *chan_dim as usize;
2486 let inner = *inner as usize;
2487 unsafe {
2488 let xs = sl(*x, base, len);
2489 let q_ptr = base.add(*q) as *mut i8;
2490 for i in 0..len {
2491 let c = if chan_dim == 1 {
2492 0
2493 } else {
2494 (i / inner) % chan_dim
2495 };
2496 let inv_scale = 1.0 / scales[c];
2497 let zp = zero_points[c];
2498 let v = (xs[i] * inv_scale).round() as i32 + zp;
2499 *q_ptr.add(i) = v.clamp(-128, 127) as i8;
2500 }
2501 }
2502 }
2503}
2504
2505#[inline(always)]
2506pub(crate) fn exec_dequantize(t: &Thunk, base: *mut u8) {
2507 let Thunk::Dequantize {
2508 q,
2509 x,
2510 len,
2511 chan_axis: _,
2512 chan_dim,
2513 inner,
2514 scales,
2515 zero_points,
2516 } = t
2517 else {
2518 unreachable!()
2519 };
2520 {
2521 let len = *len as usize;
2522 let chan_dim = *chan_dim as usize;
2523 let inner = *inner as usize;
2524 unsafe {
2525 let q_ptr = base.add(*q) as *const i8;
2526 let out = sl_mut(*x, base, len);
2527 for i in 0..len {
2528 let c = if chan_dim == 1 {
2529 0
2530 } else {
2531 (i / inner) % chan_dim
2532 };
2533 let scale = scales[c];
2534 let zp = zero_points[c];
2535 let qv = *q_ptr.add(i) as i32;
2536 out[i] = (qv - zp) as f32 * scale;
2537 }
2538 }
2539 }
2540}
2541
2542#[inline(always)]
2543pub(crate) fn exec_fake_quantize(t: &Thunk, base: *mut u8) {
2544 let Thunk::FakeQuantize {
2545 x,
2546 out,
2547 len,
2548 chan_axis: _,
2549 chan_dim,
2550 inner,
2551 bits,
2552 ste: _,
2553 scale_mode,
2554 state_off,
2555 } = t
2556 else {
2557 unreachable!()
2558 };
2559 {
2560 use rlx_ir::op::ScaleMode;
2561 let len = *len as usize;
2562 let chan_dim = *chan_dim as usize;
2563 let inner = *inner as usize;
2564 let q_max: f32 = match *bits {
2565 8 => 127.0,
2566 4 => 7.0,
2567 2 => 1.0,
2568 n => panic!("FakeQuantize: unsupported bits {n}"),
2569 };
2570 unsafe {
2571 let xs = sl(*x, base, len);
2572 let outs = sl_mut(*out, base, len);
2573
2574 let mut scale = vec![0f32; chan_dim];
2575 match scale_mode {
2576 ScaleMode::PerBatch => {
2577 let mut max_abs = vec![0f32; chan_dim];
2578 for i in 0..len {
2579 let c = if chan_dim == 1 {
2580 0
2581 } else {
2582 (i / inner) % chan_dim
2583 };
2584 let a = xs[i].abs();
2585 if a > max_abs[c] {
2586 max_abs[c] = a;
2587 }
2588 }
2589 for c in 0..chan_dim {
2590 scale[c] = (max_abs[c] / q_max).max(1e-12);
2591 }
2592 }
2593 ScaleMode::EMA { decay } => {
2594 let mut max_abs = vec![0f32; chan_dim];
2597 for i in 0..len {
2598 let c = if chan_dim == 1 {
2599 0
2600 } else {
2601 (i / inner) % chan_dim
2602 };
2603 let a = xs[i].abs();
2604 if a > max_abs[c] {
2605 max_abs[c] = a;
2606 }
2607 }
2608 let state = sl_mut(state_off.expect("EMA needs state_off"), base, chan_dim);
2609 for c in 0..chan_dim {
2610 let cur = (max_abs[c] / q_max).max(1e-12);
2611 let blended = if state[c] <= 0.0 {
2613 cur
2614 } else {
2615 *decay * state[c] + (1.0 - *decay) * cur
2616 };
2617 state[c] = blended;
2618 scale[c] = blended;
2619 }
2620 }
2621 ScaleMode::Fixed => {
2622 let state = sl(state_off.expect("Fixed needs state_off"), base, chan_dim);
2623 for c in 0..chan_dim {
2624 scale[c] = state[c].max(1e-12);
2625 }
2626 }
2627 }
2628
2629 for i in 0..len {
2630 let c = if chan_dim == 1 {
2631 0
2632 } else {
2633 (i / inner) % chan_dim
2634 };
2635 let s = scale[c];
2636 let qv = (xs[i] / s).round().clamp(-q_max, q_max);
2637 outs[i] = qv * s;
2638 }
2639 }
2640 }
2641}
2642
2643#[inline(always)]
2644pub(crate) fn exec_fake_quantize_l_s_q(t: &Thunk, base: *mut u8) {
2645 let Thunk::FakeQuantizeLSQ {
2646 x,
2647 scale_off,
2648 out,
2649 len,
2650 chan_axis: _,
2651 chan_dim,
2652 inner,
2653 bits,
2654 } = t
2655 else {
2656 unreachable!()
2657 };
2658 {
2659 let len = *len as usize;
2660 let chan_dim = *chan_dim as usize;
2661 let inner = *inner as usize;
2662 let q_max: f32 = match *bits {
2663 8 => 127.0,
2664 4 => 7.0,
2665 2 => 1.0,
2666 n => panic!("FakeQuantizeLSQ: bad bits {n}"),
2667 };
2668 unsafe {
2669 let xs = sl(*x, base, len);
2670 let scale = sl(*scale_off, base, chan_dim);
2671 let outs = sl_mut(*out, base, len);
2672 for i in 0..len {
2673 let c = if chan_dim == 1 {
2674 0
2675 } else {
2676 (i / inner) % chan_dim
2677 };
2678 let s = scale[c].max(1e-12);
2679 let qv = (xs[i] / s).round().clamp(-q_max, q_max);
2680 outs[i] = qv * s;
2681 }
2682 }
2683 }
2684}
2685
2686#[inline(always)]
2687pub(crate) fn exec_fake_quantize_l_s_q_backward_x(t: &Thunk, base: *mut u8) {
2688 let Thunk::FakeQuantizeLSQBackwardX {
2689 x,
2690 scale_off,
2691 dy,
2692 dx,
2693 len,
2694 chan_axis: _,
2695 chan_dim,
2696 inner,
2697 bits,
2698 } = t
2699 else {
2700 unreachable!()
2701 };
2702 {
2703 let len = *len as usize;
2704 let chan_dim = *chan_dim as usize;
2705 let inner = *inner as usize;
2706 let q_max: f32 = match *bits {
2707 8 => 127.0,
2708 4 => 7.0,
2709 2 => 1.0,
2710 n => panic!("FakeQuantizeLSQBackwardX: bad bits {n}"),
2711 };
2712 unsafe {
2713 let xs = sl(*x, base, len);
2714 let scale = sl(*scale_off, base, chan_dim);
2715 let dys = sl(*dy, base, len);
2716 let outs = sl_mut(*dx, base, len);
2717 for i in 0..len {
2719 let c = if chan_dim == 1 {
2720 0
2721 } else {
2722 (i / inner) % chan_dim
2723 };
2724 let z = xs[i] / scale[c].max(1e-12);
2725 outs[i] = if z.abs() <= q_max { dys[i] } else { 0.0 };
2726 }
2727 }
2728 }
2729}
2730
2731#[inline(always)]
2732pub(crate) fn exec_fake_quantize_l_s_q_backward_scale(t: &Thunk, base: *mut u8) {
2733 let Thunk::FakeQuantizeLSQBackwardScale {
2734 x,
2735 scale_off,
2736 dy,
2737 dscale,
2738 len,
2739 chan_axis: _,
2740 chan_dim,
2741 inner,
2742 bits,
2743 } = t
2744 else {
2745 unreachable!()
2746 };
2747 {
2748 let len = *len as usize;
2749 let chan_dim = *chan_dim as usize;
2750 let inner = *inner as usize;
2751 let q_max: f32 = match *bits {
2752 8 => 127.0,
2753 4 => 7.0,
2754 2 => 1.0,
2755 n => panic!("FakeQuantizeLSQBackwardScale: bad bits {n}"),
2756 };
2757 unsafe {
2758 let xs = sl(*x, base, len);
2759 let scale = sl(*scale_off, base, chan_dim);
2760 let dys = sl(*dy, base, len);
2761 let outs = sl_mut(*dscale, base, chan_dim);
2762 for v in outs.iter_mut() {
2763 *v = 0.0;
2764 }
2765 for i in 0..len {
2768 let c = if chan_dim == 1 {
2769 0
2770 } else {
2771 (i / inner) % chan_dim
2772 };
2773 let s = scale[c].max(1e-12);
2774 let z = xs[i] / s;
2775 let psi = if z.abs() <= q_max {
2776 -z + z.round()
2777 } else if z > 0.0 {
2778 q_max
2779 } else {
2780 -q_max
2781 };
2782 outs[c] += psi * dys[i];
2783 }
2784 }
2785 }
2786}
2787
2788#[inline(always)]
2789pub(crate) fn exec_fake_quantize_backward(t: &Thunk, base: *mut u8) {
2790 let Thunk::FakeQuantizeBackward {
2791 x,
2792 dy,
2793 dx,
2794 len,
2795 chan_axis: _,
2796 chan_dim,
2797 inner,
2798 bits,
2799 ste,
2800 } = t
2801 else {
2802 unreachable!()
2803 };
2804 {
2805 use rlx_ir::op::SteKind;
2806 let len = *len as usize;
2807 let chan_dim = *chan_dim as usize;
2808 let inner = *inner as usize;
2809 let q_max: f32 = match *bits {
2810 8 => 127.0,
2811 4 => 7.0,
2812 2 => 1.0,
2813 n => panic!("FakeQuantizeBackward: bad bits {n}"),
2814 };
2815 unsafe {
2816 let xs = sl(*x, base, len);
2817 let dys = sl(*dy, base, len);
2818 let outs = sl_mut(*dx, base, len);
2819
2820 let mut max_abs = vec![0f32; chan_dim];
2822 for i in 0..len {
2823 let c = if chan_dim == 1 {
2824 0
2825 } else {
2826 (i / inner) % chan_dim
2827 };
2828 let a = xs[i].abs();
2829 if a > max_abs[c] {
2830 max_abs[c] = a;
2831 }
2832 }
2833 let mut scale = vec![0f32; chan_dim];
2834 for c in 0..chan_dim {
2835 scale[c] = (max_abs[c] / q_max).max(1e-12);
2836 }
2837
2838 match *ste {
2839 SteKind::Identity => {
2840 outs.copy_from_slice(dys);
2842 }
2843 SteKind::ClippedIdentity => {
2844 for i in 0..len {
2847 let c = if chan_dim == 1 {
2848 0
2849 } else {
2850 (i / inner) % chan_dim
2851 };
2852 let bound = q_max * scale[c];
2853 outs[i] = if xs[i].abs() <= bound { dys[i] } else { 0.0 };
2854 }
2855 }
2856 SteKind::Tanh => {
2857 for i in 0..len {
2859 let c = if chan_dim == 1 {
2860 0
2861 } else {
2862 (i / inner) % chan_dim
2863 };
2864 let t = (xs[i] / scale[c]).tanh();
2865 outs[i] = dys[i] * (1.0 - t * t);
2866 }
2867 }
2868 SteKind::HardTanh => {
2869 for i in 0..len {
2871 let c = if chan_dim == 1 {
2872 0
2873 } else {
2874 (i / inner) % chan_dim
2875 };
2876 let bound = q_max * scale[c];
2877 let attenuation = (1.0 - (xs[i] / bound).abs()).max(0.0);
2878 outs[i] = dys[i] * attenuation;
2879 }
2880 }
2881 }
2882 }
2883 }
2884}
2885
2886pub unsafe fn execute_dequant_matmul_gguf_f32(
2888 x: usize,
2889 w_q: usize,
2890 dst: usize,
2891 m: usize,
2892 k: usize,
2893 n: usize,
2894 scheme: rlx_ir::quant::QuantScheme,
2895 base: *mut u8,
2896) {
2897 unsafe {
2898 let block_bytes = scheme.gguf_block_bytes() as usize;
2899 let block_elems = scheme.gguf_block_size() as usize;
2900 let total_bytes = (k * n) / block_elems * block_bytes;
2901 let xs = sl(x, base, m * k);
2902 let w_bytes = std::slice::from_raw_parts(base.add(w_q) as *const u8, total_bytes);
2903 let out = sl_mut(dst, base, m * n);
2904 crate::gguf_matmul::gguf_matmul_bt_dispatch_at(xs, w_bytes, out, m, k, n, scheme, w_q);
2905 }
2906}
2907
2908pub unsafe fn execute_dequant_grouped_matmul_gguf_f32(
2910 input: usize,
2911 w_q: usize,
2912 expert_idx: usize,
2913 dst: usize,
2914 m: usize,
2915 k: usize,
2916 n: usize,
2917 num_experts: usize,
2918 scheme: rlx_ir::quant::QuantScheme,
2919 base: *mut u8,
2920) {
2921 unsafe {
2922 let block_bytes = scheme.gguf_block_bytes() as usize;
2923 let block_elems = scheme.gguf_block_size() as usize;
2924 let slab_bytes = (k * n) / block_elems * block_bytes;
2925 let xs = sl(input, base, m * k);
2926 let w_bytes =
2927 std::slice::from_raw_parts(base.add(w_q) as *const u8, num_experts * slab_bytes);
2928 let ids = sl(expert_idx, base, m);
2929 let out = sl_mut(dst, base, m * n);
2930 crate::gguf_matmul::gguf_grouped_matmul_bt(
2931 xs,
2932 w_bytes,
2933 ids,
2934 out,
2935 m,
2936 k,
2937 n,
2938 num_experts,
2939 scheme,
2940 );
2941 }
2942}
2943
2944pub unsafe fn execute_dequant_matmul_int8_f32(
2946 x: usize,
2947 w_q: usize,
2948 scale: usize,
2949 zp: usize,
2950 dst: usize,
2951 m: usize,
2952 k: usize,
2953 n: usize,
2954 block_size: u32,
2955 is_asymmetric: bool,
2956 base: *mut u8,
2957) {
2958 let bs = block_size as usize;
2959 let n_blocks = k.div_ceil(bs);
2960 unsafe {
2961 let xs = sl(x, base, m * k);
2962 let w_bytes = std::slice::from_raw_parts(base.add(w_q) as *const i8, k * n);
2963 let scales = sl(scale, base, n_blocks * n);
2964 let zps = if is_asymmetric {
2965 sl(zp, base, n_blocks * n)
2966 } else {
2967 &[][..]
2968 };
2969 let out = sl_mut(dst, base, m * n);
2970 dequant_matmul_int8(xs, w_bytes, scales, zps, out, m, k, n, bs, is_asymmetric);
2971 }
2972}
2973
2974pub unsafe fn execute_dequant_matmul_int4_f32(
2976 x: usize,
2977 w_q: usize,
2978 scale: usize,
2979 zp: usize,
2980 dst: usize,
2981 m: usize,
2982 k: usize,
2983 n: usize,
2984 block_size: u32,
2985 is_asymmetric: bool,
2986 base: *mut u8,
2987) {
2988 let bs = block_size as usize;
2989 let n_blocks = k.div_ceil(bs);
2990 unsafe {
2991 let xs = sl(x, base, m * k);
2992 let w_bytes = std::slice::from_raw_parts(base.add(w_q) as *const u8, (k * n).div_ceil(2));
2993 let scales = sl(scale, base, n_blocks * n);
2994 let zps = if is_asymmetric {
2995 sl(zp, base, n_blocks * n)
2996 } else {
2997 &[][..]
2998 };
2999 let out = sl_mut(dst, base, m * n);
3000 dequant_matmul_int4(xs, w_bytes, scales, zps, out, m, k, n, bs, is_asymmetric);
3001 }
3002}
3003
3004pub unsafe fn execute_dequant_matmul_fp8_f32(
3006 x: usize,
3007 w_q: usize,
3008 scale: usize,
3009 dst: usize,
3010 m: usize,
3011 k: usize,
3012 n: usize,
3013 e5m2: bool,
3014 base: *mut u8,
3015) {
3016 unsafe {
3017 let xs = sl(x, base, m * k);
3018 let w_bytes = std::slice::from_raw_parts(base.add(w_q) as *const u8, k * n);
3019 let scales = sl(scale, base, n);
3020 let out = sl_mut(dst, base, m * n);
3021 dequant_matmul_fp8(xs, w_bytes, scales, out, m, k, n, e5m2);
3022 }
3023}
3024
3025#[allow(clippy::too_many_arguments)]
3028pub unsafe fn execute_dequant_matmul_mxfp4x2_f32(
3029 x: usize,
3030 w_q: usize,
3031 scale: usize,
3032 dst: usize,
3033 m: usize,
3034 k: usize,
3035 n: usize,
3036 group: usize,
3037 base: *mut u8,
3038) {
3039 let plane = (k * n).div_ceil(2);
3040 let nblk = k.div_ceil(group.max(1));
3041 unsafe {
3042 let xs = sl(x, base, m * k);
3043 let w_bytes = std::slice::from_raw_parts(base.add(w_q) as *const u8, 2 * plane);
3044 let scale_bytes =
3045 std::slice::from_raw_parts(base.add(scale) as *const u8, 2 * nblk * n * 4);
3046 let out = sl_mut(dst, base, m * n);
3047 dequant_matmul_mxfp4x2(xs, w_bytes, scale_bytes, group, out, m, k, n);
3048 }
3049}
3050
3051pub unsafe fn execute_dequant_matmul_nvfp4_f32(
3053 x: usize,
3054 w_q: usize,
3055 scale: usize,
3056 global_scale: usize,
3057 dst: usize,
3058 m: usize,
3059 k: usize,
3060 n: usize,
3061 base: *mut u8,
3062) {
3063 let n_scale = k.div_ceil(rlx_ir::NVFP4_GROUP_SIZE) * n;
3064 unsafe {
3065 let xs = sl(x, base, m * k);
3066 let w_bytes = std::slice::from_raw_parts(base.add(w_q) as *const u8, (k * n).div_ceil(2));
3067 let scale_bytes = std::slice::from_raw_parts(base.add(scale) as *const u8, n_scale);
3068 let gs = sl(global_scale, base, 1)[0];
3069 let out = sl_mut(dst, base, m * n);
3070 dequant_matmul_nvfp4(xs, w_bytes, scale_bytes, gs, out, m, k, n);
3071 }
3072}
3073
3074pub unsafe fn execute_scaled_quant_scale_f32(
3080 x: usize,
3081 dst: usize,
3082 rows: usize,
3083 cols: usize,
3084 fmt: rlx_ir::ScaledFormat,
3085 layout: rlx_ir::ScaleLayout,
3086 base: *mut u8,
3087) {
3088 unsafe {
3089 let xs = sl(x, base, rows * cols);
3090 let scales = lowp_compute_scales(xs, fmt, layout, rows, cols);
3091 let nblk = lowp_nblk(cols, layout);
3092 match layout {
3093 rlx_ir::ScaleLayout::PerTensor => {
3094 sl_mut(dst, base, 1)[0] = scales[0];
3095 }
3096 rlx_ir::ScaleLayout::BlockMxE8M0 { .. } => {
3097 let out = std::slice::from_raw_parts_mut(base.add(dst), rows * nblk);
3098 for (o, &s) in out.iter_mut().zip(&scales) {
3099 *o = rlx_ir::lowp_codec::f32_to_e8m0(s);
3100 }
3101 }
3102 rlx_ir::ScaleLayout::Nvfp4 { .. } => {
3103 let out = std::slice::from_raw_parts_mut(base.add(dst), rows * nblk);
3104 for (o, &s) in out.iter_mut().zip(&scales) {
3105 *o = rlx_ir::lowp_codec::encode(rlx_ir::ScaledFormat::F8E4M3, s);
3106 }
3107 }
3108 }
3109 }
3110}
3111
3112#[allow(clippy::too_many_arguments)]
3114pub unsafe fn execute_scaled_quantize_f32(
3115 x: usize,
3116 scale: usize,
3117 dst: usize,
3118 rows: usize,
3119 cols: usize,
3120 fmt: rlx_ir::ScaledFormat,
3121 layout: rlx_ir::ScaleLayout,
3122 base: *mut u8,
3123) {
3124 unsafe {
3125 let xs = sl(x, base, rows * cols);
3126 let nblk = lowp_nblk(cols, layout);
3127 let n_scale = if matches!(layout, rlx_ir::ScaleLayout::PerTensor) {
3128 1
3129 } else {
3130 rows * nblk
3131 };
3132 let scales = lowp_read_scales(layout, base, scale, n_scale);
3133 let out = std::slice::from_raw_parts_mut(base.add(dst), rows * cols);
3134 lowp_quantize(xs, &scales, fmt, layout, rows, cols, out);
3135 }
3136}
3137
3138#[allow(clippy::too_many_arguments)]
3141pub unsafe fn execute_scaled_dequantize_f32(
3142 codes: usize,
3143 scale: usize,
3144 dst: usize,
3145 rows: usize,
3146 cols: usize,
3147 fmt: rlx_ir::ScaledFormat,
3148 layout: rlx_ir::ScaleLayout,
3149 base: *mut u8,
3150) {
3151 unsafe {
3152 let nblk = lowp_nblk(cols, layout);
3153 let n_scale = if matches!(layout, rlx_ir::ScaleLayout::PerTensor) {
3154 1
3155 } else {
3156 rows * nblk
3157 };
3158 let cs = std::slice::from_raw_parts(base.add(codes), rows * cols);
3159 let scales = lowp_read_scales(layout, base, scale, n_scale);
3160 let out = std::slice::from_raw_parts_mut(base.add(dst) as *mut f32, rows * cols);
3161 lowp_dequantize(cs, &scales, fmt, layout, rows, cols, out);
3162 }
3163}
3164
3165#[allow(clippy::too_many_arguments)]
3167pub unsafe fn execute_scaled_matmul_f32(
3168 lhs: usize,
3169 rhs: usize,
3170 lhs_scale: usize,
3171 rhs_scale: usize,
3172 bias: usize,
3173 dst: usize,
3174 m: usize,
3175 k: usize,
3176 n: usize,
3177 has_bias: bool,
3178 lhs_fmt: rlx_ir::ScaledFormat,
3179 rhs_fmt: rlx_ir::ScaledFormat,
3180 layout: rlx_ir::ScaleLayout,
3181 base: *mut u8,
3182) {
3183 unsafe {
3184 let lhs_b = std::slice::from_raw_parts(base.add(lhs), m * k);
3185 let rhs_b = std::slice::from_raw_parts(base.add(rhs), n * k);
3186 let nblk = lowp_nblk(k, layout);
3187 let per_tensor = matches!(layout, rlx_ir::ScaleLayout::PerTensor);
3188 let n_l = if per_tensor { 1 } else { m * nblk };
3189 let n_r = if per_tensor { 1 } else { n * nblk };
3190 let ls = lowp_read_scales(layout, base, lhs_scale, n_l);
3191 let rs = lowp_read_scales(layout, base, rhs_scale, n_r);
3192 let bias_s = if has_bias {
3193 Some(sl(bias, base, n))
3194 } else {
3195 None
3196 };
3197 let out = sl_mut(dst, base, m * n);
3198 lowp_scaled_matmul(
3199 lhs_b, rhs_b, &ls, &rs, bias_s, out, m, n, k, layout, lhs_fmt, rhs_fmt,
3200 );
3201 }
3202}
3203
3204#[allow(clippy::too_many_arguments)]
3209pub unsafe fn execute_scaled_grouped_matmul_f32(
3210 input: usize,
3211 weight: usize,
3212 input_scale: usize,
3213 weight_scale: usize,
3214 expert_idx: usize,
3215 bias: usize,
3216 dst: usize,
3217 m: usize,
3218 k: usize,
3219 n: usize,
3220 num_experts: usize,
3221 has_bias: bool,
3222 lhs_fmt: rlx_ir::ScaledFormat,
3223 rhs_fmt: rlx_ir::ScaledFormat,
3224 layout: rlx_ir::ScaleLayout,
3225 base: *mut u8,
3226) {
3227 unsafe {
3228 let input_b = std::slice::from_raw_parts(base.add(input), m * k);
3229 let weight_b = std::slice::from_raw_parts(base.add(weight), num_experts * n * k);
3230 let nblk = lowp_nblk(k, layout);
3231 let per_tensor = matches!(layout, rlx_ir::ScaleLayout::PerTensor);
3232 let n_i = if per_tensor { 1 } else { m * nblk };
3233 let n_w = if per_tensor {
3234 1
3235 } else {
3236 num_experts * n * nblk
3237 };
3238 let is = lowp_read_scales(layout, base, input_scale, n_i);
3239 let ws = lowp_read_scales(layout, base, weight_scale, n_w);
3240 let ids = sl(expert_idx, base, m);
3241 let bias_s = if has_bias {
3242 Some(sl(bias, base, num_experts * n))
3243 } else {
3244 None
3245 };
3246 let out = sl_mut(dst, base, m * n);
3247 lowp_scaled_grouped_matmul(
3248 input_b,
3249 weight_b,
3250 &is,
3251 &ws,
3252 ids,
3253 bias_s,
3254 out,
3255 m,
3256 n,
3257 k,
3258 num_experts,
3259 layout,
3260 lhs_fmt,
3261 rhs_fmt,
3262 );
3263 }
3264}
3265
3266pub(crate) fn quant_layout(shape: &rlx_ir::Shape, axis: Option<usize>) -> (usize, usize, usize) {
3276 match axis {
3277 None => (0, 1, shape.num_elements().unwrap_or(0).max(1)),
3278 Some(d) => {
3279 let chan_dim = shape.dim(d).unwrap_static();
3280 let inner: usize = (d + 1..shape.rank())
3281 .map(|i| shape.dim(i).unwrap_static())
3282 .product::<usize>()
3283 .max(1);
3284 (d, chan_dim, inner)
3285 }
3286 }
3287}
3288
3289#[cfg(test)]
3290mod mxfp4x2_matmul_tests {
3291 use super::dequant_matmul_mxfp4x2;
3292 use rlx_ir::ScaledFormat;
3293 use rlx_ir::residual::{residual_dequantize, residual_quantize};
3294
3295 #[test]
3300 fn dequant_matmul_mxfp4x2_matches_residual_decode() {
3301 let (m, k, n) = (3usize, 32usize, 4usize);
3302 let x: Vec<f32> = (0..m * k).map(|i| ((i % 7) as f32 - 3.0) * 0.25).collect();
3303 let w: Vec<f32> = (0..k * n).map(|i| ((i % 11) as f32 - 5.0) * 0.3).collect(); let plane = (k * n).div_ceil(2);
3306 let mut w_bytes = vec![0u8; 2 * plane];
3307 let (mut s0, mut s1) = (vec![0f32; n], vec![0f32; n]);
3308 let mut w_dq = vec![0f32; k * n]; for j in 0..n {
3311 let col: Vec<f32> = (0..k).map(|p| w[p * n + j]).collect();
3312 let rb = residual_quantize(&col, ScaledFormat::F4E2M1, 2);
3313 s0[j] = rb.scales[0];
3314 s1[j] = rb.scales[1];
3315 let dq = residual_dequantize(&rb);
3316 for p in 0..k {
3317 let elem = p * n + j;
3318 let byte = elem / 2;
3319 let shift: u32 = if elem & 1 == 0 { 0 } else { 4 };
3320 let mask: u8 = 0x0Fu8 << shift;
3321 w_bytes[byte] = (w_bytes[byte] & !mask) | ((rb.codes[0][p] & 0x0F) << shift);
3322 w_bytes[plane + byte] =
3323 (w_bytes[plane + byte] & !mask) | ((rb.codes[1][p] & 0x0F) << shift);
3324 w_dq[elem] = dq[p];
3325 }
3326 }
3327
3328 let mut scale_bytes = Vec::with_capacity(2 * n * 4);
3329 for &s in &s0 {
3330 scale_bytes.extend_from_slice(&s.to_le_bytes());
3331 }
3332 for &s in &s1 {
3333 scale_bytes.extend_from_slice(&s.to_le_bytes());
3334 }
3335
3336 let mut out = vec![0f32; m * n];
3337 dequant_matmul_mxfp4x2(&x, &w_bytes, &scale_bytes, k, &mut out, m, k, n);
3338
3339 let mut want = vec![0f32; m * n];
3340 for i in 0..m {
3341 for j in 0..n {
3342 let mut acc = 0f32;
3343 for p in 0..k {
3344 acc += x[i * k + p] * w_dq[p * n + j];
3345 }
3346 want[i * n + j] = acc;
3347 }
3348 }
3349
3350 for (a, b) in out.iter().zip(&want) {
3351 assert!((a - b).abs() < 1e-4, "kernel {a} vs reference {b}");
3352 }
3353 }
3354}
3355
3356#[cfg(test)]
3357mod amx_matmul_bench {
3358 use super::matmul_x_wt;
3359 use std::time::Instant;
3360
3361 fn naive(x: &[f32], w: &[f32], out: &mut [f32], m: usize, k: usize, n: usize) {
3366 for i in 0..m {
3367 for j in 0..n {
3368 let mut a = 0f32;
3369 for p in 0..k {
3370 a += x[i * k + p] * w[j * k + p];
3371 }
3372 out[i * n + j] = a;
3373 }
3374 }
3375 }
3376
3377 #[test]
3378 fn amx_vs_scalar_dequant_matmul() {
3379 let k = 4096usize;
3380 println!("\n══ AMX (Accelerate) vs scalar matmul — MLX dequant-matmul inner, k={k} ══");
3381 println!(
3382 "{:>4} {:>6} {:>10} {:>10} {:>9} {:>10}",
3383 "m", "n", "scalar", "amx", "speedup", "rel|Δ|"
3384 );
3385 for (m, n) in [
3386 (1usize, 2048usize),
3387 (1, 4096),
3388 (5, 4096),
3389 (8, 2048),
3390 (16, 4096),
3391 ] {
3392 let x: Vec<f32> = (0..m * k).map(|i| (i as f32 * 0.7).sin() * 0.1).collect();
3393 let w: Vec<f32> = (0..n * k).map(|i| (i as f32 * 0.31).cos() * 0.1).collect();
3394 let mut o_naive = vec![0f32; m * n];
3395 let mut o_amx = vec![0f32; m * n];
3396 naive(&x, &w, &mut o_naive, m, k, n);
3397 matmul_x_wt(&x, &w, &mut o_amx, m, k, n);
3398 let reps = if m == 1 { 40 } else { 20 };
3399 let t = Instant::now();
3400 for _ in 0..reps {
3401 naive(&x, &w, &mut o_naive, m, k, n);
3402 }
3403 let tn = t.elapsed().as_secs_f64() * 1e3 / reps as f64;
3404 let t = Instant::now();
3405 for _ in 0..reps {
3406 matmul_x_wt(&x, &w, &mut o_amx, m, k, n);
3407 }
3408 let ta = t.elapsed().as_secs_f64() * 1e3 / reps as f64;
3409 let err = o_naive
3410 .iter()
3411 .zip(&o_amx)
3412 .map(|(a, b)| (a - b).abs())
3413 .fold(0f32, f32::max);
3414 let mag = o_naive.iter().map(|v| v.abs()).fold(1e-6, f32::max);
3415 println!(
3416 "{m:>4} {n:>6} {:>8.3}ms {:>8.3}ms {:>8.1}× {:>10.1e}",
3417 tn,
3418 ta,
3419 tn / ta,
3420 err / mag
3421 );
3422 assert!(
3423 err / mag < 1e-3,
3424 "AMX matmul must match scalar (rel {:.1e})",
3425 err / mag
3426 );
3427 }
3428 println!();
3429 }
3430
3431 #[test]
3435 fn amx_quant_matmul_schemes_m_gt_1() {
3436 use super::{
3437 dequant_matmul_fp8, dequant_matmul_int4, dequant_matmul_int8, dequant_matmul_nvfp4,
3438 };
3439 use rlx_ir::NVFP4_GROUP_SIZE;
3440 let (k, n, bs, mm) = (4096usize, 2048usize, 32usize, 8usize);
3441 let x: Vec<f32> = (0..mm * k)
3442 .map(|i| ((i * 7 % 23) as f32 - 11.0) * 0.03)
3443 .collect();
3444 let nb = k.div_ceil(bs);
3445 let run = |name: &str,
3448 amx: &dyn Fn(&[f32], usize, &mut [f32]),
3449 scalar_row: &dyn Fn(&[f32], &mut [f32])| {
3450 let mut got = vec![0f32; mm * n];
3451 amx(&x, mm, &mut got);
3452 let mut refr = vec![0f32; mm * n];
3453 for r in 0..mm {
3454 let mut row = vec![0f32; n];
3455 scalar_row(&x[r * k..r * k + k], &mut row);
3456 refr[r * n..r * n + n].copy_from_slice(&row);
3457 }
3458 let err = got
3459 .iter()
3460 .zip(&refr)
3461 .map(|(a, b)| (a - b).abs())
3462 .fold(0f32, f32::max);
3463 let mag = refr.iter().map(|v| v.abs()).fold(1e-6, f32::max);
3464 let reps = 10usize;
3465 let t = Instant::now();
3466 for _ in 0..reps {
3467 amx(&x, mm, &mut got);
3468 }
3469 let ta = t.elapsed().as_secs_f64() * 1e3 / reps as f64;
3470 let t = Instant::now();
3471 let mut tmp = vec![0f32; n];
3472 for _ in 0..reps {
3473 for r in 0..mm {
3474 scalar_row(&x[r * k..r * k + k], &mut tmp);
3475 }
3476 }
3477 let ts = t.elapsed().as_secs_f64() * 1e3 / reps as f64;
3478 println!(
3479 "{name:<8} m={mm} k={k} n={n}: scalar(row×m) {ts:>8.3}ms amx {ta:>7.3}ms {:>6.1}× rel|Δ| {:.1e}",
3480 ts / ta,
3481 err / mag
3482 );
3483 assert!(
3484 err / mag < 1e-3,
3485 "{name}: m>1 AMX must match row-by-row scalar (rel {:.1e})",
3486 err / mag
3487 );
3488 };
3489 println!("\n══ AMX vs scalar for int8/int4/fp8/nvfp4 dequant-matmul (m>1 GEMM) ══");
3490 let w8: Vec<i8> = (0..k * n)
3492 .map(|i| ((i * 31 % 251) as i32 - 125) as i8)
3493 .collect();
3494 let sc8: Vec<f32> = (0..nb * n)
3495 .map(|i| 0.008 + (i % 7) as f32 * 0.002)
3496 .collect();
3497 run(
3498 "int8",
3499 &|x, m, o| dequant_matmul_int8(x, &w8, &sc8, &[], o, m, k, n, bs, false),
3500 &|x, o| dequant_matmul_int8(x, &w8, &sc8, &[], o, 1, k, n, bs, false),
3501 );
3502 let w4: Vec<u8> = (0..k * n / 2).map(|i| (i * 37 % 256) as u8).collect();
3504 run(
3505 "int4",
3506 &|x, m, o| dequant_matmul_int4(x, &w4, &sc8, &[], o, m, k, n, bs, false),
3507 &|x, o| dequant_matmul_int4(x, &w4, &sc8, &[], o, 1, k, n, bs, false),
3508 );
3509 let wf8: Vec<u8> = (0..k * n).map(|i| (i * 53 % 256) as u8).collect();
3511 let scc: Vec<f32> = (0..n).map(|j| 0.01 + (j % 5) as f32 * 0.004).collect();
3512 run(
3513 "fp8",
3514 &|x, m, o| dequant_matmul_fp8(x, &wf8, &scc, o, m, k, n, false),
3515 &|x, o| dequant_matmul_fp8(x, &wf8, &scc, o, 1, k, n, false),
3516 );
3517 let gs = NVFP4_GROUP_SIZE;
3519 let nbg = k.div_ceil(gs);
3520 let wn4: Vec<u8> = (0..k * n / 2).map(|i| (i * 41 % 256) as u8).collect();
3521 let scn: Vec<u8> = (0..nbg * n).map(|i| (0x30 + (i % 40)) as u8).collect();
3522 run(
3523 "nvfp4",
3524 &|x, m, o| dequant_matmul_nvfp4(x, &wn4, &scn, 0.5, o, m, k, n),
3525 &|x, o| dequant_matmul_nvfp4(x, &wn4, &scn, 0.5, o, 1, k, n),
3526 );
3527 println!();
3528 }
3529}