#![cfg(all(feature = "vulkan", feature = "cpu"))]
use rlx_ir::quant::QuantScheme;
use rlx_ir::{DType, Dim, Graph, Op, Shape};
use rlx_runtime::{Device, Session};
mod common;
const QK_K: usize = 256;
fn stack(scheme: QuantScheme, experts: usize, k: usize, n: usize) -> Vec<u8> {
let block_bytes = scheme.gguf_block_bytes() as usize;
let blocks_per_row = k / QK_K;
let mut packed = vec![0u8; experts * n * blocks_per_row * block_bytes];
let mut at = 0usize;
for e in 0..experts {
let amp = 1.0 + e as f32;
for row in 0..n {
for b in 0..blocks_per_row {
let src: Vec<f32> = (0..QK_K)
.map(|i| {
let t = (e * 811 + row * 149 + b * 23 + i) as f32;
(t * 0.041).sin() * amp
})
.collect();
let dst = &mut packed[at..at + block_bytes];
match scheme {
QuantScheme::GgufQ4K => rlx_gguf::quantize::quantize_q4_k_block(&src, dst),
QuantScheme::GgufQ6K => rlx_gguf::quantize::quantize_q6_k_block(&src, dst),
other => panic!("unsupported in this fixture: {other:?}"),
}
at += block_bytes;
}
}
}
packed
}
fn build(scheme: QuantScheme, experts: usize, k: usize, n: usize, packed_len: usize) -> Graph {
let mut g = Graph::new("moe_w");
let w = g.input("w", Shape::new(&[packed_len], DType::U8));
let y = g.add_node(
Op::DequantMoEWeights { scheme },
vec![w],
Shape::from_dims(
&[Dim::Static(experts), Dim::Static(k), Dim::Static(n)],
DType::F32,
),
);
g.set_outputs(vec![y]);
g
}
fn run(device: Device, scheme: QuantScheme, experts: usize, k: usize, n: usize) -> Vec<f32> {
let packed = stack(scheme, experts, k, n);
let bytes = Session::new(device)
.compile(build(scheme, experts, k, n, packed.len()))
.run_typed(&[("w", &packed, DType::U8)])
.remove(0)
.0;
bytes
.chunks_exact(4)
.map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]]))
.collect()
}
fn exact(what: &str, got: &[f32], want: &[f32]) {
assert_eq!(got.len(), want.len(), "{what}: length");
for (i, (a, b)) in got.iter().zip(want).enumerate() {
assert_eq!(
a.to_bits(),
b.to_bits(),
"{what}: element {i} — vulkan {a} vs cpu {b}"
);
}
}
#[test]
fn q4k_matches_cpu_bit_exactly() {
let _gpu = common::serialize_gpu();
if common::skip_unless_available(Device::Vulkan, "vulkan") {
return;
}
for (experts, k, n) in [(3usize, 256usize, 4usize), (2, 512, 3)] {
let cpu = run(Device::Cpu, QuantScheme::GgufQ4K, experts, k, n);
let vk = run(Device::Vulkan, QuantScheme::GgufQ4K, experts, k, n);
exact(&format!("q4k E={experts} K={k} N={n}"), &vk, &cpu);
}
}
#[test]
fn q6k_matches_cpu_bit_exactly() {
let _gpu = common::serialize_gpu();
if common::skip_unless_available(Device::Vulkan, "vulkan") {
return;
}
for (experts, k, n) in [(3usize, 256usize, 4usize), (2, 512, 3)] {
let cpu = run(Device::Cpu, QuantScheme::GgufQ6K, experts, k, n);
let vk = run(Device::Vulkan, QuantScheme::GgufQ6K, experts, k, n);
exact(&format!("q6k E={experts} K={k} N={n}"), &vk, &cpu);
}
}
#[test]
fn single_expert_still_transposes() {
let _gpu = common::serialize_gpu();
if common::skip_unless_available(Device::Vulkan, "vulkan") {
return;
}
let cpu = run(Device::Cpu, QuantScheme::GgufQ4K, 1, 256, 5);
let vk = run(Device::Vulkan, QuantScheme::GgufQ4K, 1, 256, 5);
exact("q4k single expert", &vk, &cpu);
}