use super::{GgmlDType, QStorage};
use crate::quantized::k_quants::GgmlType;
use crate::{backend::BackendDevice, cuda_backend::WrapErr};
use crate::{builder_arg as barg, CudaDevice, CudaStorage, Result};
use half::f16;
use cudarc::driver::{CudaSlice, CudaStream, CudaView, DevicePtr, PushKernelArg, SyncOnDrop};
#[derive(Clone, Debug)]
struct PaddedCudaSlice {
inner: CudaSlice<u8>,
len: usize,
}
#[derive(Clone, Debug)]
pub struct QCudaStorage {
data: PaddedCudaSlice,
dtype: GgmlDType,
device: CudaDevice,
}
pub(crate) static FORCE_DMMV: std::sync::atomic::AtomicBool =
std::sync::atomic::AtomicBool::new(false);
pub fn set_force_dmmv(f: bool) {
FORCE_DMMV.store(f, std::sync::atomic::Ordering::Relaxed)
}
pub(crate) static FAST_MMQ: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
static FAST_MMQ_INIT: std::sync::Once = std::sync::Once::new();
pub fn set_fast_mmq(f: bool) {
FAST_MMQ_INIT.call_once(|| {});
FAST_MMQ.store(f, std::sync::atomic::Ordering::Relaxed);
}
pub(crate) fn fast_mmq_enabled() -> bool {
FAST_MMQ_INIT.call_once(|| {
let env = matches!(
std::env::var("CUDA_FAST_MMQ").as_deref(),
Ok("1") | Ok("true") | Ok("TRUE")
);
FAST_MMQ.store(env, std::sync::atomic::Ordering::Relaxed);
});
FAST_MMQ.load(std::sync::atomic::Ordering::Relaxed)
}
pub const WARP_SIZE: usize = 32;
pub const MMQ_X_Q4_0_AMPERE: usize = 4;
pub const MMQ_Y_Q4_0_AMPERE: usize = 32;
pub const NWARPS_Q4_0_AMPERE: usize = 4;
pub const GGML_CUDA_MMV_X: usize = 32;
pub const GGML_CUDA_MMV_Y: usize = 1;
pub const CUDA_QUANTIZE_BLOCK_SIZE: usize = 256;
pub const CUDA_DEQUANTIZE_BLOCK_SIZE: usize = 256;
pub const MATRIX_ROW_PADDING: usize = 512;
fn ceil_div(p: usize, q: usize) -> usize {
p.div_ceil(q)
}
fn pad(p: usize, q: usize) -> usize {
ceil_div(p, q) * q
}
fn quantize_q8_1(
src: &CudaView<f32>,
dst: &mut CudaSlice<u8>,
k: usize,
ky: usize,
dev: &CudaDevice,
) -> Result<()> {
let kx_padded = pad(k, MATRIX_ROW_PADDING);
let num_blocks = ceil_div(kx_padded, CUDA_QUANTIZE_BLOCK_SIZE);
let total_rows = ky;
let q8_1_block_size = GgmlDType::Q8_1.block_size();
let q8_1_type_size = GgmlDType::Q8_1.type_size();
let num_blocks_per_row = kx_padded / q8_1_block_size;
let dst_row_size_bytes = num_blocks_per_row * q8_1_type_size;
const CHUNK_SIZE: usize = 65535; let func = dev.get_or_load_func("quantize_q8_1", &hanzo_kernels::QUANTIZED)?;
let mut rows_processed = 0;
while rows_processed < total_rows {
let remaining_rows = total_rows - rows_processed;
let rows_in_chunk = std::cmp::min(CHUNK_SIZE, remaining_rows);
let src_start_elem = rows_processed * k;
let src_num_elems = rows_in_chunk * k;
let src_chunk = src.slice(src_start_elem..(src_start_elem + src_num_elems));
let dst_start_byte = rows_processed * dst_row_size_bytes;
let dst_num_bytes = rows_in_chunk * dst_row_size_bytes;
let dst_chunk = dst.slice(dst_start_byte..(dst_start_byte + dst_num_bytes));
let cfg = cudarc::driver::LaunchConfig {
grid_dim: (num_blocks as u32, rows_in_chunk as u32, 1),
block_dim: (CUDA_QUANTIZE_BLOCK_SIZE as u32, 1, 1),
shared_mem_bytes: 0,
};
let mut builder = func.builder();
builder.arg(&src_chunk);
builder.arg(&dst_chunk);
barg!(builder, k as i32, kx_padded as i32);
unsafe { builder.launch(cfg) }.w()?;
rows_processed += rows_in_chunk;
}
Ok(())
}
fn dequantize_f32(
data: &PaddedCudaSlice,
dtype: GgmlDType,
elem_count: usize,
dev: &CudaDevice,
) -> Result<CudaStorage> {
let nb = elem_count.div_ceil(256);
let (kernel_name, is_k, block_dim, num_blocks) = match dtype {
GgmlDType::Q4_0 => ("dequantize_block_q4_0_f32", false, 32, nb),
GgmlDType::Q4_1 => ("dequantize_block_q4_1_f32", false, 32, nb),
GgmlDType::Q5_0 => (
"dequantize_block_q5_0_f32",
false,
CUDA_DEQUANTIZE_BLOCK_SIZE,
ceil_div(elem_count, 2 * CUDA_DEQUANTIZE_BLOCK_SIZE),
),
GgmlDType::Q5_1 => (
"dequantize_block_q5_1_f32",
false,
CUDA_DEQUANTIZE_BLOCK_SIZE,
ceil_div(elem_count, 2 * CUDA_DEQUANTIZE_BLOCK_SIZE),
),
GgmlDType::Q8_0 => ("dequantize_block_q8_0_f32", false, 32, nb),
GgmlDType::Q2K => ("dequantize_block_q2_K_f32", true, 64, nb),
GgmlDType::Q3K => ("dequantize_block_q3_K_f32", true, 64, nb),
GgmlDType::Q4K => ("dequantize_block_q4_K_f32", true, 32, nb),
GgmlDType::Q5K => ("dequantize_block_q5_K_f32", true, 64, nb),
GgmlDType::Q6K => ("dequantize_block_q6_K_f32", true, 64, nb),
GgmlDType::Q8K => ("dequantize_block_q8_K_f32", true, 32, nb),
_ => crate::bail!("unsupported dtype for dequantize {dtype:?}"),
};
let func = dev.get_or_load_func(kernel_name, &hanzo_kernels::QUANTIZED)?;
let dst = unsafe { dev.alloc::<f32>(elem_count)? };
let cfg = cudarc::driver::LaunchConfig {
grid_dim: (num_blocks as u32, 1, 1),
block_dim: (block_dim as u32, 1, 1),
shared_mem_bytes: 0,
};
if is_k {
let mut builder = func.builder();
builder.arg(&data.inner);
builder.arg(&dst);
unsafe { builder.launch(cfg) }.w()?;
} else {
let nb32 = match dtype {
GgmlDType::Q5_0 | GgmlDType::Q5_1 => elem_count,
_ => elem_count / 32,
};
let mut builder = func.builder();
builder.arg(&data.inner);
builder.arg(&dst);
barg!(builder, nb32 as i32);
unsafe { builder.launch(cfg) }.w()?;
}
Ok(CudaStorage::wrap_cuda_slice(dst, dev.clone()))
}
fn dequantize_f16(
data: &PaddedCudaSlice,
dtype: GgmlDType,
elem_count: usize,
dev: &CudaDevice,
) -> Result<CudaStorage> {
let nb = elem_count.div_ceil(256);
let (kernel_name, is_k, block_dim, num_blocks) = match dtype {
GgmlDType::Q4_0 => ("dequantize_block_q4_0_f16", false, 32, nb),
GgmlDType::Q4_1 => ("dequantize_block_q4_1_f16", false, 32, nb),
GgmlDType::Q5_0 => (
"dequantize_block_q5_0_f16",
false,
CUDA_DEQUANTIZE_BLOCK_SIZE,
ceil_div(elem_count, 2 * CUDA_DEQUANTIZE_BLOCK_SIZE),
),
GgmlDType::Q5_1 => (
"dequantize_block_q5_1_f16",
false,
CUDA_DEQUANTIZE_BLOCK_SIZE,
ceil_div(elem_count, 2 * CUDA_DEQUANTIZE_BLOCK_SIZE),
),
GgmlDType::Q8_0 => ("dequantize_block_q8_0_f16", false, 32, nb),
GgmlDType::Q2K => ("dequantize_block_q2_K_f16", true, 64, nb),
GgmlDType::Q3K => ("dequantize_block_q3_K_f16", true, 64, nb),
GgmlDType::Q4K => ("dequantize_block_q4_K_f16", true, 32, nb),
GgmlDType::Q5K => ("dequantize_block_q5_K_f16", true, 64, nb),
GgmlDType::Q6K => ("dequantize_block_q6_K_f16", true, 64, nb),
GgmlDType::Q8K => ("dequantize_block_q8_K_f16", true, 32, nb),
_ => crate::bail!("unsupported dtype for dequantize {dtype:?}"),
};
let func = dev.get_or_load_func(kernel_name, &hanzo_kernels::QUANTIZED)?;
let dst = unsafe { dev.alloc::<f16>(elem_count)? };
let cfg = cudarc::driver::LaunchConfig {
grid_dim: (num_blocks as u32, 1, 1),
block_dim: (block_dim as u32, 1, 1),
shared_mem_bytes: 0,
};
if is_k {
let mut builder = func.builder();
builder.arg(&data.inner);
builder.arg(&dst);
unsafe { builder.launch(cfg) }.w()?;
} else {
let nb32 = match dtype {
GgmlDType::Q5_0 | GgmlDType::Q5_1 => elem_count,
_ => elem_count / 32,
};
let mut builder = func.builder();
builder.arg(&data.inner);
builder.arg(&dst);
barg!(builder, nb32 as i32);
unsafe { builder.launch(cfg) }.w()?;
}
Ok(CudaStorage::wrap_cuda_slice(dst, dev.clone()))
}
fn dequantize_mul_mat_vec(
data: &PaddedCudaSlice,
y: &CudaView<f32>,
dtype: GgmlDType,
ncols: usize,
nrows: usize,
dev: &CudaDevice,
) -> Result<CudaStorage> {
let data_elems = data.len / dtype.type_size() * dtype.block_size();
if data_elems < ncols * nrows {
crate::bail!("unexpected data size {}, ncols {ncols} {nrows}", data_elems)
}
if y.len() != ncols {
crate::bail!("unexpected y size {}, ncols {ncols} {nrows}", y.len())
}
let kernel_name = match dtype {
GgmlDType::Q4_0 => "dequantize_mul_mat_vec_q4_0_cuda",
GgmlDType::Q4_1 => "dequantize_mul_mat_vec_q4_1_cuda",
GgmlDType::Q5_0 => "dequantize_mul_mat_vec_q5_0_cuda",
GgmlDType::Q5_1 => "dequantize_mul_mat_vec_q5_1_cuda",
GgmlDType::Q8_0 => "dequantize_mul_mat_vec_q8_0_cuda",
GgmlDType::Q2K => "dequantize_mul_mat_vec_q2_k",
GgmlDType::Q3K => "dequantize_mul_mat_vec_q3_k",
GgmlDType::Q4K => "dequantize_mul_mat_vec_q4_k",
GgmlDType::Q5K => "dequantize_mul_mat_vec_q5_k",
GgmlDType::Q6K => "dequantize_mul_mat_vec_q6_k",
_ => crate::bail!("unsupported dtype for quantized matmul {dtype:?}"),
};
let func = dev.get_or_load_func(kernel_name, &hanzo_kernels::QUANTIZED)?;
let dst = unsafe { dev.alloc::<f32>(nrows)? };
let block_num_y = ceil_div(nrows, GGML_CUDA_MMV_Y);
let cfg = cudarc::driver::LaunchConfig {
grid_dim: (block_num_y as u32, 1, 1),
block_dim: (WARP_SIZE as u32, GGML_CUDA_MMV_Y as u32, 1),
shared_mem_bytes: 0,
};
let mut builder = func.builder();
builder.arg(&data.inner);
builder.arg(y);
builder.arg(&dst);
barg!(builder, ncols as i32, nrows as i32);
unsafe { builder.launch(cfg) }.w()?;
Ok(CudaStorage::wrap_cuda_slice(dst, dev.clone()))
}
pub fn moe_route(
logits: &CudaView<f32>,
ntok: usize,
n_experts: usize,
topk: usize,
norm: bool,
dev: &CudaDevice,
) -> Result<(CudaStorage, CudaStorage)> {
const MOE_ROUTE_MAX_E: usize = 256;
const MOE_ROUTE_MAX_K: usize = 32;
if n_experts > MOE_ROUTE_MAX_E {
crate::bail!("moe_route: n_experts {n_experts} exceeds kernel max {MOE_ROUTE_MAX_E}");
}
if topk > MOE_ROUTE_MAX_K {
crate::bail!("moe_route: topk {topk} exceeds kernel max {MOE_ROUTE_MAX_K}");
}
if logits.len() != ntok * n_experts {
crate::bail!(
"moe_route: logits len {} != ntok*n_experts {}",
logits.len(),
ntok * n_experts
);
}
let func = dev.get_or_load_func("moe_route", &hanzo_kernels::QUANTIZED)?;
let ids = unsafe { dev.alloc::<u32>(ntok * topk)? };
let w = unsafe { dev.alloc::<f32>(ntok * topk)? };
let cfg = cudarc::driver::LaunchConfig {
grid_dim: (ntok as u32, 1, 1),
block_dim: (64, 1, 1),
shared_mem_bytes: 0,
};
let mut builder = func.builder();
barg!(
builder,
ntok as i32,
n_experts as i32,
topk as i32,
i32::from(norm)
);
builder.arg(logits);
builder.arg(&ids);
builder.arg(&w);
unsafe { builder.launch(cfg) }.w()?;
Ok((
CudaStorage::wrap_cuda_slice(ids, dev.clone()),
CudaStorage::wrap_cuda_slice(w, dev.clone()),
))
}
fn mul_mat_vec_via_q8_1(
data: &PaddedCudaSlice,
y: &CudaView<f32>,
dtype: GgmlDType,
ncols: usize,
nrows: usize,
b_size: usize,
dev: &CudaDevice,
) -> Result<CudaStorage> {
let data_elems = data.len / dtype.type_size() * dtype.block_size();
if data_elems < ncols * nrows {
crate::bail!("unexpected data size {}, ncols {ncols} {nrows}", data_elems)
}
if y.len() != ncols * b_size {
crate::bail!("unexpected y size {}, ncols {ncols} {nrows}", y.len())
}
if b_size == 0 || b_size > 8 {
crate::bail!("only bsize between 1 and 8 are supported, got {b_size}")
}
let ncols_padded = pad(ncols, MATRIX_ROW_PADDING);
let y_size_in_bytes =
b_size * ncols_padded * GgmlDType::Q8_1.type_size() / GgmlDType::Q8_1.block_size();
let mut y_q8_1 = dev.alloc_zeros::<u8>(y_size_in_bytes)?;
quantize_q8_1(y, &mut y_q8_1, ncols, b_size, dev)?;
let kernel_name = match dtype {
GgmlDType::Q4_0 => "mul_mat_vec_q4_0_q8_1_cuda",
GgmlDType::Q4_1 => "mul_mat_vec_q4_1_q8_1_cuda",
GgmlDType::Q5_0 => "mul_mat_vec_q5_0_q8_1_cuda",
GgmlDType::Q5_1 => "mul_mat_vec_q5_1_q8_1_cuda",
GgmlDType::Q8_0 => "mul_mat_vec_q8_0_q8_1_cuda",
GgmlDType::Q2K => "mul_mat_vec_q2_K_q8_1_cuda",
GgmlDType::Q3K => "mul_mat_vec_q3_K_q8_1_cuda",
GgmlDType::Q4K => "mul_mat_vec_q4_K_q8_1_cuda",
GgmlDType::Q5K => "mul_mat_vec_q5_K_q8_1_cuda",
GgmlDType::Q6K => "mul_mat_vec_q6_K_q8_1_cuda",
_ => crate::bail!("unsupported dtype for quantized matmul {dtype:?}"),
};
let kernel_name = format!("{kernel_name}{b_size}");
let func = dev.get_or_load_func(&kernel_name, &hanzo_kernels::QUANTIZED)?;
let dst = dev.alloc_zeros::<f32>(nrows * b_size)?;
let (nblocks, nwarps) = match b_size {
1 => (nrows as u32, 4),
2..=4 => ((nrows as u32).div_ceil(2), 4),
5..=8 => ((nrows as u32).div_ceil(2), 2),
_ => crate::bail!("unexpected bsize {b_size}"),
};
let cfg = cudarc::driver::LaunchConfig {
grid_dim: (nblocks, 1, 1),
block_dim: (WARP_SIZE as u32, nwarps, 1),
shared_mem_bytes: 0,
};
let mut builder = func.builder();
builder.arg(&data.inner);
builder.arg(&y_q8_1);
builder.arg(&dst);
barg!(
builder,
ncols as i32,
nrows as i32,
ncols_padded as i32,
nrows as i32
);
unsafe { builder.launch(cfg) }.w()?;
Ok(CudaStorage::wrap_cuda_slice(dst, dev.clone()))
}
fn iquant_dp4a_suffix(dtype: GgmlDType) -> Option<&'static str> {
Some(match dtype {
GgmlDType::IQ2_XXS => "iq2xxs",
GgmlDType::IQ2_XS => "iq2xs",
GgmlDType::IQ2_S => "iq2s",
GgmlDType::IQ3_XXS => "iq3xxs",
GgmlDType::IQ3_S => "iq3s",
GgmlDType::IQ1_S => "iq1_s",
GgmlDType::IQ1_M => "iq1_m",
GgmlDType::IQ4_XS => "iq4xs",
GgmlDType::TQ2_0 => "tq2_0",
GgmlDType::TQ1_0 => "tq1_0",
_ => return None,
})
}
#[allow(clippy::too_many_arguments)]
fn mul_mat_vec_iquant_dp4a(
data: &PaddedCudaSlice,
y: &CudaView<f32>,
dtype: GgmlDType,
ncols: usize,
nrows: usize,
b_size: usize,
dev: &CudaDevice,
) -> Result<CudaStorage> {
let suffix = iquant_dp4a_suffix(dtype)
.ok_or_else(|| crate::Error::Msg(format!("no i-quant dp4a kernel for {dtype:?}")).bt())?;
if ncols % 256 != 0 {
crate::bail!("i-quant ncols {ncols} must be a multiple of 256");
}
if b_size == 0 || b_size > 8 {
crate::bail!("only bsize between 1 and 8 are supported, got {b_size}");
}
if y.len() != ncols * b_size {
crate::bail!("unexpected y size {}, ncols {ncols} bsize {b_size}", y.len());
}
let nblk32 = ncols / 32;
let mut xq = dev.alloc_zeros::<u8>(b_size * ncols)?;
let mut xd = dev.alloc_zeros::<f16>(b_size * nblk32)?;
{
let func = dev.get_or_load_func("iq_quantize_q8_f32", &hanzo_kernels::IQUANT_MMVQ)?;
let nwarp = (b_size * nblk32) as u32;
let threads = 256u32; let blocks = nwarp.div_ceil(threads / WARP_SIZE as u32);
let cfg = cudarc::driver::LaunchConfig {
grid_dim: (blocks, 1, 1),
block_dim: (threads, 1, 1),
shared_mem_bytes: 0,
};
let mut builder = func.builder();
barg!(builder, b_size as i32, ncols as i32);
builder.arg(y);
builder.arg(&xq);
builder.arg(&xd);
unsafe { builder.launch(cfg) }.w()?;
}
let kernel_name = format!("qmatvec_dp4a_{suffix}_f32");
let func = dev.get_or_load_func(&kernel_name, &hanzo_kernels::IQUANT_MMVQ)?;
let dst = dev.alloc_zeros::<f32>(nrows * b_size)?;
const ROWS_PER_BLOCK: u32 = 8; for bi in 0..b_size {
let xq_b = xq.slice(bi * ncols..(bi + 1) * ncols);
let xd_b = xd.slice(bi * nblk32..(bi + 1) * nblk32);
let dst_b = dst.slice(bi * nrows..(bi + 1) * nrows);
let cfg = cudarc::driver::LaunchConfig {
grid_dim: ((nrows as u32).div_ceil(ROWS_PER_BLOCK), 1, 1),
block_dim: (ROWS_PER_BLOCK * WARP_SIZE as u32, 1, 1),
shared_mem_bytes: 0,
};
let mut builder = func.builder();
barg!(builder, nrows as i32, ncols as i32);
builder.arg(&data.inner);
builder.arg(&xq_b);
builder.arg(&xd_b);
builder.arg(&dst_b);
unsafe { builder.launch(cfg) }.w()?;
}
Ok(CudaStorage::wrap_cuda_slice(dst, dev.clone()))
}
#[allow(clippy::too_many_arguments)]
fn mul_mat_via_q8_1(
data: &PaddedCudaSlice,
y: &CudaView<f32>,
dtype: GgmlDType,
x_rows: usize,
x_cols: usize,
y_rows: usize,
y_cols: usize,
dev: &CudaDevice,
) -> Result<CudaStorage> {
let data_elems = data.len / dtype.type_size() * dtype.block_size();
if data_elems < x_rows * x_cols {
crate::bail!("unexpected lhs size {}, {x_rows} {x_cols}", data_elems)
}
if y.len() != y_rows * y_cols {
crate::bail!("unexpected y size {}, {y_rows} {y_cols}", y.len())
}
if x_cols != y_rows {
crate::bail!("unexpected x/y size {x_rows} {x_cols} {y_rows} {y_cols}")
}
let k = x_cols;
let k_padded = pad(k, MATRIX_ROW_PADDING);
let y_size_in_bytes =
k_padded * y_cols * GgmlDType::Q8_1.type_size() / GgmlDType::Q8_1.block_size();
let mut y_q8_1 = dev.alloc_zeros::<u8>(y_size_in_bytes)?;
quantize_q8_1(y, &mut y_q8_1, k, y_cols, dev)?;
let (kernel_name, mmq_x, mmq_y) = match dtype {
GgmlDType::Q4_0 => ("mul_mat_q4_0", 64, 128),
GgmlDType::Q4_1 => ("mul_mat_q4_1", 64, 128),
GgmlDType::Q5_0 => ("mul_mat_q5_0", 128, 64),
GgmlDType::Q5_1 => ("mul_mat_q5_1", 128, 64),
GgmlDType::Q8_0 => ("mul_mat_q8_0", 128, 64),
GgmlDType::Q2K => ("mul_mat_q2_K", 64, 128),
GgmlDType::Q3K => ("mul_mat_q3_K", 128, 128),
GgmlDType::Q4K => ("mul_mat_q4_K", 64, 128),
GgmlDType::Q5K => ("mul_mat_q5_K", 64, 128),
GgmlDType::Q6K => ("mul_mat_q6_K", 64, 64),
_ => crate::bail!("unsupported dtype for quantized matmul {dtype:?}"),
};
let func = dev.get_or_load_func(kernel_name, &hanzo_kernels::QUANTIZED)?;
let dst = dev.alloc_zeros::<f32>(x_rows * y_cols)?;
let cfg = cudarc::driver::LaunchConfig {
grid_dim: (
ceil_div(x_rows, mmq_y) as u32,
ceil_div(y_cols, mmq_x) as u32,
1,
),
block_dim: (WARP_SIZE as u32, 4, 1),
shared_mem_bytes: 0,
};
let mut builder = func.builder();
builder.arg( &data.inner);
builder.arg( &y_q8_1);
builder.arg( &dst);
barg!(
builder,
x_cols as i32,
x_rows as i32,
y_cols as i32,
k_padded as i32,
x_rows as i32
);
unsafe { builder.launch(cfg) }.w()?;
Ok(CudaStorage::wrap_cuda_slice(dst, dev.clone()))
}
fn indexed_moe_kernel_name(dtype: GgmlDType) -> Option<&'static str> {
Some(match dtype {
GgmlDType::Q2K => "indexed_moe_forward_q2k_q8_1",
GgmlDType::Q3K => "indexed_moe_forward_q3k_q8_1",
GgmlDType::Q4K => "indexed_moe_forward_q4k_q8_1",
GgmlDType::Q5K => "indexed_moe_forward_q5k_q8_1",
GgmlDType::Q6K => "indexed_moe_forward_q6k_q8_1",
GgmlDType::Q8_0 => "indexed_moe_forward_q8_0_q8_1",
GgmlDType::Q4_0 => "indexed_moe_forward_q4_0_q8_1",
GgmlDType::Q4_1 => "indexed_moe_forward_q4_1_q8_1",
GgmlDType::Q5_0 => "indexed_moe_forward_q5_0_q8_1",
GgmlDType::Q5_1 => "indexed_moe_forward_q5_1_q8_1",
_ => return None,
})
}
fn indexed_moe_forward_fused_q8_1_input(
weight: &CudaView<u8>,
w_shape: &crate::Shape, w_dtype: GgmlDType,
input: &CudaSlice<f32>,
in_shape: &crate::Shape, ids: &CudaView<u32>,
idx_shape: &crate::Shape, dev: &CudaDevice,
) -> Result<(CudaStorage, crate::Shape)> {
let (_, n, k) = w_shape.dims3()?;
let batch = in_shape.dims()[0];
let input_dim1 = in_shape.dims()[1];
let n_slots = idx_shape.elem_count();
assert!(
n_slots % batch == 0,
"ids count {n_slots} not a multiple of batch {batch}"
);
let topk = n_slots / batch;
if batch > 1 {
if let Some(res) = super::fast_mmq::indexed_moe_grouped(
weight, w_shape, w_dtype, input, in_shape, ids, idx_shape, dev,
)? {
return Ok(res);
}
}
let total_rows = batch * input_dim1;
let k_padded = pad(k, MATRIX_ROW_PADDING);
let q8_1_block_size = GgmlDType::Q8_1.block_size();
let q8_1_type_size = GgmlDType::Q8_1.type_size();
let num_blocks_per_row = k_padded / q8_1_block_size;
let dst_row_size_bytes = num_blocks_per_row * q8_1_type_size;
let y_size_in_bytes = total_rows * dst_row_size_bytes;
let mut input_quant = dev.alloc_zeros::<u8>(y_size_in_bytes)?;
let input_view = input.slice(0..);
quantize_q8_1(&input_view, &mut input_quant, k, total_rows, dev)?;
let outsize = batch * topk * n;
let out = dev.alloc_zeros::<f32>(outsize)?;
let kernel_name = match indexed_moe_kernel_name(w_dtype) {
Some(name) => name,
None => crate::bail!("unsupported dtype for indexed_moe_forward {w_dtype:?}"),
};
let func = dev.get_or_load_func(kernel_name, &hanzo_kernels::QUANTIZED)?;
let nblocks = n as u32;
let nwarps = 4u32;
let cfg = cudarc::driver::LaunchConfig {
grid_dim: (nblocks, batch as u32, topk as u32),
block_dim: (WARP_SIZE as u32, nwarps, 1),
shared_mem_bytes: 0,
};
let mut builder = func.builder();
builder.arg(weight);
builder.arg(&input_quant);
builder.arg(ids);
builder.arg(&out);
barg!(
builder,
n as i32,
k as i32,
batch as i32,
topk as i32,
k_padded as i32,
input_dim1 as i32
);
unsafe { builder.launch(cfg) }.w()?;
let mut out_shape = in_shape.dims().to_vec();
out_shape.pop();
out_shape.push(n);
out_shape[1] = topk;
Ok((
CudaStorage::wrap_cuda_slice(out, dev.clone()),
out_shape.into(),
))
}
impl QCudaStorage {
pub fn supports_indexed_moe(dtype: GgmlDType) -> bool {
indexed_moe_kernel_name(dtype).is_some()
}
pub fn indexed_moe_forward(
&self,
self_shape: &crate::Shape, input: &CudaStorage, input_l: &crate::Layout,
ids: &CudaStorage, ids_l: &crate::Layout,
) -> Result<(CudaStorage, crate::Shape)> {
if Self::supports_indexed_moe(self.dtype()) {
let input_storage = input.as_cuda_slice::<f32>()?;
let ids_storage = ids.as_cuda_slice::<u32>()?;
indexed_moe_forward_fused_q8_1_input(
&self.data.inner.slice(0..),
self_shape, self.dtype(),
&input_storage,
input_l.shape(), &ids_storage.slice(0..),
ids_l.shape(), &self.device,
)
} else {
crate::bail!(
"The given quantized dtype {:?} is not supported for indexed_moe_forward!",
self.dtype()
);
}
}
pub fn supports_iquant_moe(dtype: GgmlDType) -> bool {
iquant_dp4a_suffix(dtype).is_some()
}
pub fn moe_iquant_dp4a(
&self,
x_flat: &CudaView<f32>,
ids: &CudaView<u32>,
nrows: usize,
n: usize,
k: usize,
) -> Result<CudaStorage> {
let suffix = iquant_dp4a_suffix(self.dtype)
.ok_or_else(|| crate::Error::Msg(format!("no i-quant MoE kernel for {:?}", self.dtype)).bt())?;
if k % 256 != 0 {
crate::bail!("i-quant MoE ncols {k} must be a multiple of 256");
}
if x_flat.len() != nrows * k {
crate::bail!("unexpected x_flat size {}, nrows {nrows} k {k}", x_flat.len());
}
let dev = self.device();
let nblk32 = k / 32;
let mut xq = dev.alloc_zeros::<u8>(nrows * k)?;
let mut xd = dev.alloc_zeros::<f16>(nrows * nblk32)?;
{
let func = dev.get_or_load_func("iq_quantize_q8_f32", &hanzo_kernels::IQUANT_MMVQ)?;
let nwarp = (nrows * nblk32) as u32;
let threads = 256u32;
let blocks = nwarp.div_ceil(threads / WARP_SIZE as u32);
let cfg = cudarc::driver::LaunchConfig {
grid_dim: (blocks, 1, 1),
block_dim: (threads, 1, 1),
shared_mem_bytes: 0,
};
let mut builder = func.builder();
barg!(builder, nrows as i32, k as i32);
builder.arg(x_flat);
builder.arg(&xq);
builder.arg(&xd);
unsafe { builder.launch(cfg) }.w()?;
}
let kernel_name = format!("moe_qmatvec_dp4a_{suffix}_f32");
let func = dev.get_or_load_func(&kernel_name, &hanzo_kernels::IQUANT_MMVQ)?;
let out = dev.alloc_zeros::<f32>(nrows * n)?;
const ROWS_PER_BLOCK: u32 = 8;
let cfg = cudarc::driver::LaunchConfig {
grid_dim: ((n as u32).div_ceil(ROWS_PER_BLOCK), nrows as u32, 1),
block_dim: (ROWS_PER_BLOCK * WARP_SIZE as u32, 1, 1),
shared_mem_bytes: 0,
};
let mut builder = func.builder();
barg!(builder, n as i32, k as i32, nrows as i32);
builder.arg(&self.data.inner);
builder.arg(ids);
builder.arg(&xq);
builder.arg(&xd);
builder.arg(&out);
unsafe { builder.launch(cfg) }.w()?;
Ok(CudaStorage::wrap_cuda_slice(out, dev.clone()))
}
pub fn moe_iquant_qmmq(
&self,
w_shape: &crate::Shape,
input: &CudaSlice<f32>,
in_shape: &crate::Shape,
ids: &CudaView<u32>,
idx_shape: &crate::Shape,
) -> Result<Option<(CudaStorage, crate::Shape)>> {
super::fast_mmq::indexed_moe_grouped(
&self.data.inner.slice(0..),
w_shape,
self.dtype,
input,
in_shape,
ids,
idx_shape,
self.device(),
)
}
pub fn zeros(device: &CudaDevice, el_count: usize, dtype: GgmlDType) -> Result<Self> {
let size_in_bytes = ceil_div(el_count, dtype.block_size()) * dtype.type_size();
let padded_size_in_bytes =
ceil_div(el_count + MATRIX_ROW_PADDING, dtype.block_size()) * dtype.type_size();
let inner = device.alloc_zeros::<u8>(padded_size_in_bytes)?;
Ok(QCudaStorage {
data: PaddedCudaSlice {
inner,
len: size_in_bytes,
},
device: device.clone(),
dtype,
})
}
pub fn dtype(&self) -> GgmlDType {
self.dtype
}
pub fn device(&self) -> &CudaDevice {
&self.device
}
pub fn dequantize(&self, elem_count: usize) -> Result<CudaStorage> {
fn deq<T: GgmlType>(buffer: &[u8], n: usize, dst: &mut [f32]) {
let slice = unsafe { std::slice::from_raw_parts(buffer.as_ptr() as *const T, n) };
let vec = slice.to_vec();
T::to_float(&vec, dst)
}
let fast_kernel = matches!(
self.dtype,
GgmlDType::Q4_0
| GgmlDType::Q4_1
| GgmlDType::Q5_0
| GgmlDType::Q5_1
| GgmlDType::Q8_0
| GgmlDType::Q2K
| GgmlDType::Q3K
| GgmlDType::Q4K
| GgmlDType::Q5K
| GgmlDType::Q6K
| GgmlDType::Q8K
);
if fast_kernel {
return dequantize_f32(&self.data, self.dtype, elem_count, self.device());
}
let buffer = self
.device
.clone_dtoh(&self.data.inner.slice(..self.data.len))?;
let mut out = vec![0.0; elem_count];
let block_len = elem_count / self.dtype.block_size();
match self.dtype {
GgmlDType::F32 => deq::<f32>(&buffer, block_len, &mut out),
GgmlDType::F16 => deq::<half::f16>(&buffer, block_len, &mut out),
GgmlDType::BF16 => deq::<half::bf16>(&buffer, block_len, &mut out),
GgmlDType::I32 => deq::<i32>(&buffer, block_len, &mut out),
GgmlDType::Q4_0 => deq::<crate::quantized::BlockQ4_0>(&buffer, block_len, &mut out),
GgmlDType::Q4_1 => deq::<crate::quantized::BlockQ4_1>(&buffer, block_len, &mut out),
GgmlDType::Q5_0 => deq::<crate::quantized::BlockQ5_0>(&buffer, block_len, &mut out),
GgmlDType::Q5_1 => deq::<crate::quantized::BlockQ5_1>(&buffer, block_len, &mut out),
GgmlDType::Q8_0 => deq::<crate::quantized::BlockQ8_0>(&buffer, block_len, &mut out),
GgmlDType::Q8_1 => deq::<crate::quantized::BlockQ8_1>(&buffer, block_len, &mut out),
GgmlDType::Q2K => deq::<crate::quantized::BlockQ2K>(&buffer, block_len, &mut out),
GgmlDType::Q3K => deq::<crate::quantized::BlockQ3K>(&buffer, block_len, &mut out),
GgmlDType::Q4K => deq::<crate::quantized::BlockQ4K>(&buffer, block_len, &mut out),
GgmlDType::Q5K => deq::<crate::quantized::BlockQ5K>(&buffer, block_len, &mut out),
GgmlDType::Q6K => deq::<crate::quantized::BlockQ6K>(&buffer, block_len, &mut out),
GgmlDType::Q8K => deq::<crate::quantized::BlockQ8K>(&buffer, block_len, &mut out),
GgmlDType::IQ4_NL => deq::<crate::quantized::BlockIQ4nl>(&buffer, block_len, &mut out),
GgmlDType::IQ4_XS => deq::<crate::quantized::BlockIQ4xs>(&buffer, block_len, &mut out),
GgmlDType::MXFP4 => deq::<crate::quantized::BlockMXFP4>(&buffer, block_len, &mut out),
GgmlDType::IQ2_XXS => {
deq::<crate::quantized::iq_quants::BlockIQ2xxs>(&buffer, block_len, &mut out)
}
GgmlDType::IQ2_XS => {
deq::<crate::quantized::iq_quants::BlockIQ2xs>(&buffer, block_len, &mut out)
}
GgmlDType::IQ3_XXS => {
deq::<crate::quantized::iq_quants::BlockIQ3xxs>(&buffer, block_len, &mut out)
}
GgmlDType::IQ1_S => {
deq::<crate::quantized::iq_quants::BlockIQ1s>(&buffer, block_len, &mut out)
}
GgmlDType::IQ3_S => {
deq::<crate::quantized::iq_quants::BlockIQ3s>(&buffer, block_len, &mut out)
}
GgmlDType::IQ2_S => {
deq::<crate::quantized::iq_quants::BlockIQ2s>(&buffer, block_len, &mut out)
}
GgmlDType::IQ1_M => {
deq::<crate::quantized::iq_quants::BlockIQ1m>(&buffer, block_len, &mut out)
}
GgmlDType::TQ1_0 => {
deq::<crate::quantized::iq_quants::BlockTQ1_0>(&buffer, block_len, &mut out)
}
GgmlDType::TQ2_0 => {
deq::<crate::quantized::iq_quants::BlockTQ2_0>(&buffer, block_len, &mut out)
}
GgmlDType::NVFP4 => {
deq::<crate::quantized::iq_quants::BlockNVFP4>(&buffer, block_len, &mut out)
}
GgmlDType::Q1_0 => {
deq::<crate::quantized::iq_quants::BlockQ1_0>(&buffer, block_len, &mut out)
}
}
self.device
.storage_from_cpu_storage(&crate::CpuStorage::F32(out))
}
pub fn dequantize_f16(&self, elem_count: usize) -> Result<CudaStorage> {
dequantize_f16(&self.data, self.dtype, elem_count, self.device())
}
pub fn quantize(&mut self, src: &CudaStorage) -> Result<()> {
let src = match &src.slice {
crate::cuda_backend::CudaStorageSlice::F32(data) => self.device.clone_dtoh(data)?,
_ => crate::bail!("only f32 can be quantized"),
};
let src_len = src.len();
let src = crate::Storage::Cpu(crate::CpuStorage::F32(src));
let mut qcpu_storage = crate::Device::Cpu.qzeros(src_len, self.dtype)?;
qcpu_storage.quantize(&src)?;
let data = qcpu_storage.data()?;
let padded_len =
data.len() + MATRIX_ROW_PADDING * self.dtype.type_size() / self.dtype.block_size();
let mut inner = unsafe { self.device.alloc::<u8>(padded_len)? };
self.device
.memcpy_htod(&data[..], &mut inner.slice_mut(..data.len()))?;
self.data = PaddedCudaSlice {
inner,
len: data.len(),
};
Ok(())
}
pub fn quantize_imatrix(
&mut self,
src: &CudaStorage,
imatrix_weights: &[f32],
n_per_row: usize,
) -> Result<()> {
let src = match &src.slice {
crate::cuda_backend::CudaStorageSlice::F32(data) => self.device.clone_dtoh(data)?,
_ => crate::bail!("only f32 can be quantized"),
};
let src_len = src.len();
let src = crate::Storage::Cpu(crate::CpuStorage::F32(src));
let mut qcpu_storage = crate::Device::Cpu.qzeros(src_len, self.dtype)?;
qcpu_storage.quantize_imatrix(&src, imatrix_weights, n_per_row)?;
let data = qcpu_storage.data()?;
let padded_len =
data.len() + MATRIX_ROW_PADDING * self.dtype.type_size() / self.dtype.block_size();
let mut inner = unsafe { self.device.alloc::<u8>(padded_len)? };
self.device
.memcpy_htod(&data[..], &mut inner.slice_mut(..data.len()))?;
self.data = PaddedCudaSlice {
inner,
len: data.len(),
};
Ok(())
}
pub fn quantize_imatrix_onto(
&mut self,
src: &crate::CpuStorage,
imatrix_weights: &[f32],
n_per_row: usize,
) -> Result<()> {
let src_len = src.as_slice::<f32>()?.len();
let mut qcpu_storage = crate::Device::Cpu.qzeros(src_len, self.dtype)?;
if let QStorage::Cpu(storage) = &mut qcpu_storage {
storage.from_float_imatrix(src.as_slice::<f32>()?, imatrix_weights, n_per_row);
} else {
unreachable!()
}
let data = qcpu_storage.data()?;
let padded_len =
data.len() + MATRIX_ROW_PADDING * self.dtype.type_size() / self.dtype.block_size();
let mut inner = unsafe { self.device.alloc::<u8>(padded_len)? };
self.device
.memcpy_htod(&data[..], &mut inner.slice_mut(..data.len()))?;
self.data = PaddedCudaSlice {
inner,
len: data.len(),
};
Ok(())
}
pub fn quantize_onto(&mut self, src: &crate::CpuStorage) -> Result<()> {
let src_len = src.as_slice::<f32>()?.len();
let mut qcpu_storage = crate::Device::Cpu.qzeros(src_len, self.dtype)?;
if let QStorage::Cpu(storage) = &mut qcpu_storage {
storage.from_float(src.as_slice::<f32>()?);
} else {
unreachable!()
}
let data = qcpu_storage.data()?;
let padded_len =
data.len() + MATRIX_ROW_PADDING * self.dtype.type_size() / self.dtype.block_size();
let mut inner = unsafe { self.device.alloc::<u8>(padded_len)? };
self.device
.memcpy_htod(&data[..], &mut inner.slice_mut(..data.len()))?;
self.data = PaddedCudaSlice {
inner,
len: data.len(),
};
Ok(())
}
pub fn storage_size_in_bytes(&self) -> usize {
self.data.len
}
pub fn fwd(
&self,
self_shape: &crate::Shape,
storage: &CudaStorage,
layout: &crate::Layout,
) -> Result<(CudaStorage, crate::Shape)> {
let max_bm = if FORCE_DMMV.load(std::sync::atomic::Ordering::Relaxed) {
1
} else {
8
};
let use_vec_kernel = match layout.shape().dims() {
[b, m, _k] => b * m <= max_bm,
[b, _k] => *b <= max_bm,
_ => false,
};
if fast_mmq_enabled() && !FORCE_DMMV.load(std::sync::atomic::Ordering::Relaxed) {
if use_vec_kernel {
if let Some(out) = super::fast_mmvq::try_fwd(self, self_shape, storage, layout)? {
return Ok(out);
}
} else if let Some(out) = super::fast_mmq::try_fwd(self, self_shape, storage, layout)? {
return Ok(out);
}
}
if !Self::has_native_q8_1_matmul(self.dtype) {
if iquant_dp4a_suffix(self.dtype).is_some() {
if use_vec_kernel {
return self.mul_mat_vec_iquant(self_shape, storage, layout);
}
if let Some(out) = super::fast_mmq::try_fwd(self, self_shape, storage, layout)? {
return Ok(out);
}
}
return self.dequantize_matmul_dense(self_shape, storage, layout);
}
if use_vec_kernel {
self.dequantize_matmul_vec(self_shape, storage, layout)
} else {
self.dequantize_matmul(self_shape, storage, layout)
}
}
pub fn data(&self) -> Result<Vec<u8>> {
let mut out = vec![0u8; self.data.len];
self.device
.memcpy_dtoh(&self.data.inner.slice(..self.data.len), &mut out)?;
Ok(out)
}
pub fn device_ptr(&self) -> Result<*const u8> {
Ok(self.data.inner.device_ptr(self.data.inner.stream()).0 as *const u8)
}
pub fn device_ptr_with_guard<'a>(
&'a self,
stream: &'a CudaStream,
) -> Result<(*const u8, SyncOnDrop<'a>)> {
let (ptr, guard) = self.data.inner.device_ptr(stream);
Ok((ptr as *const u8, guard))
}
}
impl QCudaStorage {
fn dequantize_matmul_vec(
&self,
self_shape: &crate::Shape,
rhs: &CudaStorage,
rhs_l: &crate::Layout,
) -> Result<(CudaStorage, crate::Shape)> {
let (nrows, ncols) = self_shape.dims2()?;
let rhs = rhs.as_cuda_slice::<f32>()?;
let rhs = match rhs_l.contiguous_offsets() {
Some((o1, o2)) => rhs.slice(o1..o2),
None => Err(crate::Error::RequiresContiguous { op: "dmmv" }.bt())?,
};
let (b_size, k) = match rhs_l.shape().dims() {
[b, m, k] => (b * m, *k),
[b, k] => (*b, *k),
_ => crate::bail!("unexpected rhs shape in dmmv {:?}", rhs_l.shape()),
};
if ncols != k {
crate::bail!("mismatch on matmul dim {self_shape:?} {:?}", rhs_l.shape())
}
let out = if FORCE_DMMV.load(std::sync::atomic::Ordering::Relaxed) {
dequantize_mul_mat_vec(&self.data, &rhs, self.dtype, ncols, nrows, self.device())?
} else {
mul_mat_vec_via_q8_1(
&self.data,
&rhs,
self.dtype,
ncols,
nrows,
b_size,
self.device(),
)?
};
let mut out_shape = rhs_l.shape().dims().to_vec();
out_shape.pop();
out_shape.push(nrows);
Ok((out, out_shape.into()))
}
fn mul_mat_vec_iquant(
&self,
self_shape: &crate::Shape,
rhs: &CudaStorage,
rhs_l: &crate::Layout,
) -> Result<(CudaStorage, crate::Shape)> {
let (nrows, ncols) = self_shape.dims2()?;
let rhs = rhs.as_cuda_slice::<f32>()?;
let rhs = match rhs_l.contiguous_offsets() {
Some((o1, o2)) => rhs.slice(o1..o2),
None => Err(crate::Error::RequiresContiguous { op: "iq-mmvq" }.bt())?,
};
let (b_size, k) = match rhs_l.shape().dims() {
[b, m, k] => (b * m, *k),
[b, k] => (*b, *k),
_ => crate::bail!("unexpected rhs shape in iq-mmvq {:?}", rhs_l.shape()),
};
if ncols != k {
crate::bail!("mismatch on matmul dim {self_shape:?} {:?}", rhs_l.shape())
}
let out =
mul_mat_vec_iquant_dp4a(&self.data, &rhs, self.dtype, ncols, nrows, b_size, self.device())?;
let mut out_shape = rhs_l.shape().dims().to_vec();
out_shape.pop();
out_shape.push(nrows);
Ok((out, out_shape.into()))
}
fn has_native_q8_1_matmul(dtype: GgmlDType) -> bool {
matches!(
dtype,
GgmlDType::Q4_0
| GgmlDType::Q4_1
| GgmlDType::Q5_0
| GgmlDType::Q5_1
| GgmlDType::Q8_0
| GgmlDType::Q2K
| GgmlDType::Q3K
| GgmlDType::Q4K
| GgmlDType::Q5K
| GgmlDType::Q6K
)
}
fn dequantize_matmul_dense(
&self,
self_shape: &crate::Shape,
storage: &CudaStorage,
layout: &crate::Layout,
) -> Result<(CudaStorage, crate::Shape)> {
use crate::backend::BackendStorage;
let (n, k) = self_shape.dims2()?;
let (b, m, k2) = match layout.shape().dims() {
&[b, m, k2] => (b, m, k2),
&[m, k2] => (1, m, k2),
s => crate::bail!("unexpected shape for input {s:?}"),
};
if k2 != k {
crate::bail!("mismatch on matmul dim {self_shape:?} {:?}", layout.shape())
}
let data_f32 = self.dequantize(n * k)?;
let rhs_l = crate::Layout::new((k, n).into(), vec![1, k], 0).broadcast_as((b, k, n))?;
let out = storage.matmul(&data_f32, (b, m, n, k), layout, &rhs_l)?;
let mut out_shape = layout.shape().dims().to_vec();
out_shape.pop();
out_shape.push(n);
Ok((out, out_shape.into()))
}
fn dequantize_matmul(
&self,
self_shape: &crate::Shape,
storage: &CudaStorage,
layout: &crate::Layout,
) -> Result<(CudaStorage, crate::Shape)> {
if FORCE_DMMV.load(std::sync::atomic::Ordering::Relaxed) {
return self.dequantize_matmul_dense(self_shape, storage, layout);
}
let (n, k) = self_shape.dims2()?;
let (b, m, k2) = match layout.shape().dims() {
&[b, m, k2] => (b, m, k2),
&[m, k2] => (1, m, k2),
s => crate::bail!("unexpected shape for input {s:?}"),
};
if k2 != k {
crate::bail!("mismatch on matmul dim {self_shape:?} {:?}", layout.shape())
}
let storage = storage.as_cuda_slice::<f32>()?;
let storage = match layout.contiguous_offsets() {
Some((o1, o2)) => storage.slice(o1..o2),
None => Err(crate::Error::RequiresContiguous {
op: "quantized-matmul",
}
.bt())?,
};
let out = mul_mat_via_q8_1(
&self.data,
&storage,
self.dtype,
n,
k,
k,
b * m,
self.device(),
)?;
let mut out_shape = layout.shape().dims().to_vec();
out_shape.pop();
out_shape.push(n);
Ok((out, out_shape.into()))
}
}
pub fn load_quantized<T: super::GgmlType + Send + Sync + 'static>(
device: &CudaDevice,
data: &[T],
) -> Result<super::QStorage> {
let data = unsafe {
std::slice::from_raw_parts(data.as_ptr() as *const u8, core::mem::size_of_val(data))
};
let dtype = T::DTYPE;
let padded_len = data.len() + MATRIX_ROW_PADDING * dtype.type_size() / dtype.block_size();
let mut inner = device.alloc_zeros::<u8>(padded_len)?;
device.memcpy_htod(data, &mut inner.slice_mut(..data.len()))?;
Ok(QStorage::Cuda(QCudaStorage {
data: PaddedCudaSlice {
inner,
len: data.len(),
},
device: device.clone(),
dtype,
}))
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn cuda_quantize_q8_1() -> Result<()> {
let dev = CudaDevice::new(0)?;
let el = 256;
let el_padded = pad(el, MATRIX_ROW_PADDING);
let y_size_in_bytes =
el_padded * GgmlDType::Q8_1.type_size() / GgmlDType::Q8_1.block_size();
let mut y_q8_1 = unsafe { dev.alloc::<u8>(y_size_in_bytes)? };
let vs: Vec<f32> = (0..el).map(|v| v as f32).collect();
let y = dev.clone_htod(&vs)?;
quantize_q8_1(&y.as_view(), &mut y_q8_1, el, 1, &dev)?;
Ok(())
}
#[test]
fn cuda_mmv_q8_1() -> Result<()> {
let dev = CudaDevice::new(0)?;
let ncols = 256;
let vs: Vec<f32> = (0..ncols).map(|v| v as f32).collect();
let y = dev.clone_htod(&vs)?;
let mut xs = QCudaStorage::zeros(&dev, ncols, GgmlDType::Q4_0)?;
xs.quantize(&CudaStorage::wrap_cuda_slice(y.clone(), dev.clone()))?;
let cuda_storage = mul_mat_vec_via_q8_1(
&xs.data,
&y.as_view(),
GgmlDType::Q4_0,
ncols,
1,
1,
&dev,
)?;
let vs = cuda_storage.as_cuda_slice::<f32>()?;
let vs = dev.clone_dtoh(&vs.as_view())?;
assert_eq!(vs.len(), 1);
assert_eq!(vs[0], 5561664.5);
let cuda_storage = dequantize_mul_mat_vec(
&xs.data,
&y.as_view(),
GgmlDType::Q4_0,
ncols,
1,
&dev,
)?;
let vs = cuda_storage.as_cuda_slice::<f32>()?;
let vs = dev.clone_dtoh(&vs.as_view())?;
assert_eq!(vs.len(), 1);
assert_eq!(vs[0], 5561851.0);
Ok(())
}
#[test]
fn cuda_mm_q8_1() -> Result<()> {
let dev = CudaDevice::new(0)?;
let ncols = 256;
let vs: Vec<f32> = (0..ncols * 4).map(|v| v as f32 / 4.).collect();
let y = dev.clone_htod(&vs)?;
let mut xs = QCudaStorage::zeros(&dev, ncols * 4, GgmlDType::Q4_0)?;
xs.quantize(&CudaStorage::wrap_cuda_slice(y.clone(), dev.clone()))?;
let cuda_storage = mul_mat_via_q8_1(
&xs.data,
&y.as_view(),
GgmlDType::Q4_0,
4,
ncols,
ncols,
4,
&dev,
)?;
let vs = cuda_storage.as_cuda_slice::<f32>()?;
let vs = dev.clone_dtoh(&vs.as_view())?;
assert_eq!(vs.len(), 16);
assert_eq!(vs[0], 347604.0);
assert_eq!(vs[1], 888153.06);
assert_eq!(vs[4], 869780.7);
assert_eq!(vs[5], 2483145.0);
assert_eq!(vs[11], 9407368.0);
assert_eq!(vs[14], 9470856.0);
assert_eq!(vs[15], 13138824.0);
Ok(())
}
#[test]
fn cuda_mm_q8_1_pad() -> Result<()> {
let dev = CudaDevice::new(0)?;
let (x_rows, ncols, y_cols) = (4, 16, 2048);
let vs: Vec<f32> = (0..ncols * y_cols).map(|v| v as f32 / 256.).collect();
let y = dev.clone_htod(&vs)?;
let mut xs = QCudaStorage::zeros(&dev, ncols * x_rows, GgmlDType::Q4_0)?;
xs.quantize(&CudaStorage::wrap_cuda_slice(y.clone(), dev.clone()))?;
let cuda_storage = mul_mat_via_q8_1(
&xs.data,
&y.as_view(),
GgmlDType::Q4_0,
x_rows,
ncols,
ncols,
y_cols,
&dev,
)?;
let vs = cuda_storage.as_cuda_slice::<f32>()?;
let _vs = dev.clone_dtoh(&vs.as_view())?;
Ok(())
}
#[test]
fn cuda_indexed_moe_legacy_32block() -> Result<()> {
let dev = CudaDevice::new(0)?;
let (e, n, k) = (3usize, 8usize, 256usize);
let (batch, topk) = (2usize, 2usize);
let ids: Vec<u32> = vec![0, 1, 2, 0];
let mut st: u32 = 0x1234_5678;
let mut nextf = || -> f32 {
st = st.wrapping_mul(1664525).wrapping_add(1013904223);
((st >> 8) as f32 / (1u32 << 24) as f32) - 0.5 };
let w: Vec<f32> = (0..e * n * k).map(|_| nextf() * 0.5).collect();
let inp: Vec<f32> = (0..batch * k).map(|_| nextf()).collect();
for dtype in [
GgmlDType::Q4_0,
GgmlDType::Q4_1,
GgmlDType::Q5_0,
GgmlDType::Q5_1,
GgmlDType::Q8_0,
GgmlDType::Q2K,
GgmlDType::Q3K,
GgmlDType::Q4K,
GgmlDType::Q5K,
GgmlDType::Q6K,
] {
let mut bank = QCudaStorage::zeros(&dev, e * n * k, dtype)?;
let w_cuda = CudaStorage::wrap_cuda_slice(dev.clone_htod(&w)?, dev.clone());
bank.quantize(&w_cuda)?;
let deq = bank.dequantize(e * n * k)?;
let w_deq = dev.clone_dtoh(&deq.as_cuda_slice::<f32>()?.as_view())?;
let input = CudaStorage::wrap_cuda_slice(dev.clone_htod(&inp)?, dev.clone());
let input_l = crate::Layout::contiguous((batch, 1usize, k));
let ids_storage = CudaStorage::wrap_cuda_slice(dev.clone_htod(&ids)?, dev.clone());
let ids_l = crate::Layout::contiguous((batch, topk));
let self_shape: crate::Shape = (e, n, k).into();
let (out_storage, out_shape) =
bank.indexed_moe_forward(&self_shape, &input, &input_l, &ids_storage, &ids_l)?;
assert_eq!(out_shape.dims().to_vec(), vec![batch, topk, n]);
let got = dev.clone_dtoh(&out_storage.as_cuda_slice::<f32>()?.as_view())?;
let mut max_abs = 0f32;
let mut max_err = 0f32;
for b in 0..batch {
for t in 0..topk {
let expert = ids[b * topk + t] as usize;
for j in 0..n {
let mut acc = 0f32;
for i in 0..k {
acc += w_deq[expert * n * k + j * k + i] * inp[b * k + i];
}
let g = got[(b * topk + t) * n + j];
max_abs = max_abs.max(acc.abs());
max_err = max_err.max((g - acc).abs());
}
}
}
let tol = 0.05 * max_abs + 1e-3;
assert!(
max_err <= tol,
"{dtype:?}: indexed-MoE max_err {max_err} > tol {tol} (max_abs {max_abs})"
);
}
Ok(())
}
#[test]
fn cuda_iquant_load_and_dense_matmul() -> Result<()> {
let dev = CudaDevice::new(0)?;
let cuda_dev = crate::Device::Cuda(dev.clone());
let (m, n, k) = (2usize, 4usize, 256usize); let inp: Vec<f32> = (0..m * k).map(|i| (i % 17) as f32 * 0.05 - 0.4).collect();
for dtype in [
GgmlDType::IQ2_XXS,
GgmlDType::IQ2_XS,
GgmlDType::IQ2_S,
GgmlDType::IQ3_XXS,
GgmlDType::IQ3_S,
GgmlDType::IQ1_S,
GgmlDType::IQ1_M,
GgmlDType::TQ1_0,
GgmlDType::TQ2_0,
GgmlDType::NVFP4,
GgmlDType::Q1_0,
] {
let nbytes = (n * k / dtype.block_size()) * dtype.type_size();
let bytes: Vec<u8> = (0..nbytes)
.map(|i| (i.wrapping_mul(37).wrapping_add(11) & 0x3F) as u8)
.collect();
let qcuda = match QStorage::from_data(std::borrow::Cow::Owned(bytes), &cuda_dev, dtype)? {
QStorage::Cuda(s) => s,
_ => unreachable!("from_data on a CUDA device must yield CUDA storage"),
};
let w_dev = qcuda.dequantize(n * k)?;
let w_host = dev.clone_dtoh(&w_dev.as_cuda_slice::<f32>()?.as_view())?;
let input = CudaStorage::wrap_cuda_slice(dev.clone_htod(&inp)?, dev.clone());
let input_l = crate::Layout::contiguous((m, k));
let self_shape: crate::Shape = (n, k).into();
let (out, out_shape) = qcuda.fwd(&self_shape, &input, &input_l)?;
assert_eq!(out_shape.dims().to_vec(), vec![m, n]);
let got = dev.clone_dtoh(&out.as_cuda_slice::<f32>()?.as_view())?;
let mut max_abs = 0f32;
let mut max_err = 0f32;
for r in 0..m {
for j in 0..n {
let mut acc = 0f32;
for i in 0..k {
acc += w_host[j * k + i] * inp[r * k + i];
}
let g = got[r * n + j];
assert!(g.is_finite(), "{dtype:?}: non-finite CUDA output {g}");
max_abs = max_abs.max(acc.abs());
max_err = max_err.max((g - acc).abs());
}
}
let tol = 1e-3 * max_abs + 1e-4;
assert!(
max_err <= tol,
"{dtype:?}: i-quant CUDA dense matmul max_err {max_err} > tol {tol} (max_abs {max_abs})"
);
}
Ok(())
}
#[test]
fn cuda_dense_qmmq_prefill_vs_dequant_and_oracle() -> Result<()> {
let dev = CudaDevice::new(0)?;
let cuda_dev = crate::Device::Cuda(dev.clone());
let (m, n, k) = (16usize, 128usize, 512usize);
let mut st: u32 = 0x9E37_79B9;
let mut nextf = || -> f32 {
st = st.wrapping_mul(1664525).wrapping_add(1013904223);
((st >> 8) as f32 / (1u32 << 24) as f32) - 0.5
};
let inp: Vec<f32> = (0..m * k).map(|_| nextf()).collect();
let wf: Vec<f32> = (0..n * k).map(|_| nextf() * 0.5).collect();
for dtype in [GgmlDType::Q4K, GgmlDType::Q6K, GgmlDType::Q8_0] {
let qcpu = crate::Device::Cpu.qzeros(n * k, dtype)?;
let qcpu = match qcpu {
QStorage::Cpu(mut storage) => {
storage.from_float(&wf);
QStorage::Cpu(storage)
}
_ => unreachable!(),
};
let bytes = qcpu.data()?.into_owned();
let qcuda = match QStorage::from_data(std::borrow::Cow::Owned(bytes), &cuda_dev, dtype)? {
QStorage::Cuda(s) => s,
_ => unreachable!(),
};
let w_dev = qcuda.dequantize(n * k)?;
let w_host = dev.clone_dtoh(&w_dev.as_cuda_slice::<f32>()?.as_view())?;
let input = CudaStorage::wrap_cuda_slice(dev.clone_htod(&inp)?, dev.clone());
let input_l = crate::Layout::contiguous((m, k));
let self_shape: crate::Shape = (n, k).into();
let (q_out, q_shape) = crate::quantized::fast_mmq::try_fwd(&qcuda, &self_shape, &input, &input_l)?
.expect("fast_mmq::try_fwd must support q8_1 dense prefill");
assert_eq!(q_shape.dims().to_vec(), vec![m, n]);
let q_got = dev.clone_dtoh(&q_out.as_cuda_slice::<f32>()?.as_view())?;
let (d_out, d_shape) = qcuda.dequantize_matmul_dense(&self_shape, &input, &input_l)?;
assert_eq!(d_shape.dims().to_vec(), vec![m, n]);
let d_got = dev.clone_dtoh(&d_out.as_cuda_slice::<f32>()?.as_view())?;
let mut max_abs = 0f32;
let mut err_q = 0f32;
let mut err_d = 0f32;
let mut err_qd = 0f32;
for r in 0..m {
for j in 0..n {
let mut acc = 0f32;
for i in 0..k {
acc += w_host[j * k + i] * inp[r * k + i];
}
let gq = q_got[r * n + j];
let gd = d_got[r * n + j];
assert!(gq.is_finite() && gd.is_finite(), "{dtype:?}: non-finite output");
max_abs = max_abs.max(acc.abs());
err_q = err_q.max((gq - acc).abs());
err_d = err_d.max((gd - acc).abs());
err_qd = err_qd.max((gq - gd).abs());
}
}
let tol_q = 0.05 * max_abs + 1e-3;
let tol_d = 1e-3 * max_abs + 1e-4;
assert!(err_q <= tol_q, "{dtype:?}: DENSE QMMQ vs oracle err {err_q} > tol {tol_q} (max_abs {max_abs})");
assert!(err_d <= tol_d, "{dtype:?}: DEQUANT-DENSE vs oracle err {err_d} > tol {tol_d} (max_abs {max_abs})");
assert!(err_qd <= tol_q, "{dtype:?}: QMMQ vs DEQUANT-DENSE err {err_qd} > tol {tol_q} (max_abs {max_abs})");
eprintln!("[DENSE_QMMQ] {dtype:?}: max_abs={max_abs:.4} err_qmmq={err_q:.5} err_dequant={err_d:.6} err_qmmq_vs_dequant={err_qd:.5}");
}
Ok(())
}
#[test]
fn cuda_indexed_moe_grouped_prefill_vs_oracle() -> Result<()> {
let dev = CudaDevice::new(0)?;
let (e, n, k) = (8usize, 64usize, 512usize);
let (batch, topk) = (40usize, 4usize);
let mut sr: u32 = 0xC0FF_EE11;
let ids: Vec<u32> = (0..batch * topk)
.map(|_| {
sr = sr.wrapping_mul(1664525).wrapping_add(1013904223);
let ex = (sr >> 9) % (e as u32);
if ex == 5 {
4
} else {
ex
}
})
.collect();
let mut st: u32 = 0x1234_5678;
let mut nextf = || -> f32 {
st = st.wrapping_mul(1664525).wrapping_add(1013904223);
((st >> 8) as f32 / (1u32 << 24) as f32) - 0.5
};
let w: Vec<f32> = (0..e * n * k).map(|_| nextf() * 0.5).collect();
let inp: Vec<f32> = (0..batch * k).map(|_| nextf()).collect();
let inp1: Vec<f32> = inp[0..k].to_vec();
let ids1: Vec<u32> = ids[0..topk].to_vec();
for dtype in [GgmlDType::Q4K, GgmlDType::Q6K, GgmlDType::Q8_0] {
let mut bank = QCudaStorage::zeros(&dev, e * n * k, dtype)?;
bank.quantize(&CudaStorage::wrap_cuda_slice(dev.clone_htod(&w)?, dev.clone()))?;
let deq = bank.dequantize(e * n * k)?;
let w_deq = dev.clone_dtoh(&deq.as_cuda_slice::<f32>()?.as_view())?;
let self_shape: crate::Shape = (e, n, k).into();
let input = CudaStorage::wrap_cuda_slice(dev.clone_htod(&inp)?, dev.clone());
let input_l = crate::Layout::contiguous((batch, 1usize, k));
let ids_storage = CudaStorage::wrap_cuda_slice(dev.clone_htod(&ids)?, dev.clone());
let ids_l = crate::Layout::contiguous((batch, topk));
let (g_st, g_sh) =
bank.indexed_moe_forward(&self_shape, &input, &input_l, &ids_storage, &ids_l)?;
assert_eq!(g_sh.dims().to_vec(), vec![batch, topk, n]);
let got_g = dev.clone_dtoh(&g_st.as_cuda_slice::<f32>()?.as_view())?;
let mut max_abs = 0f32;
let mut err_g = 0f32;
for b in 0..batch {
for t in 0..topk {
let ex = ids[b * topk + t] as usize;
for j in 0..n {
let mut acc = 0f32;
for i in 0..k {
acc += w_deq[ex * n * k + j * k + i] * inp[b * k + i];
}
max_abs = max_abs.max(acc.abs());
err_g = err_g.max((got_g[(b * topk + t) * n + j] - acc).abs());
}
}
}
let tol = 0.05 * max_abs + 1e-3;
assert!(
err_g <= tol,
"{dtype:?}: GROUPED prefill vs oracle err {err_g} > tol {tol} (max_abs {max_abs})"
);
let input1 = CudaStorage::wrap_cuda_slice(dev.clone_htod(&inp1)?, dev.clone());
let input1_l = crate::Layout::contiguous((1usize, 1usize, k));
let ids1_storage = CudaStorage::wrap_cuda_slice(dev.clone_htod(&ids1)?, dev.clone());
let ids1_l = crate::Layout::contiguous((1usize, topk));
let (p_st, _) =
bank.indexed_moe_forward(&self_shape, &input1, &input1_l, &ids1_storage, &ids1_l)?;
let got_p = dev.clone_dtoh(&p_st.as_cuda_slice::<f32>()?.as_view())?;
let mut err_p = 0f32;
for t in 0..topk {
let ex = ids1[t] as usize;
for j in 0..n {
let mut acc = 0f32;
for i in 0..k {
acc += w_deq[ex * n * k + j * k + i] * inp1[i];
}
err_p = err_p.max((got_p[t * n + j] - acc).abs());
}
}
assert!(
err_p <= tol,
"{dtype:?}: PER-SLOT decode vs oracle err {err_p} > tol {tol}"
);
}
Ok(())
}
}