#![allow(missing_docs)]
use cubecl::prelude::*;
use cubecl_utils_rs::prelude::*;
use crate::gpu::{WORKGROUP_64, WORKGROUP_128, WORKGROUP_256};
fn spmm_workgroup(s_width: usize) -> u32 {
match s_width {
0..=64 => WORKGROUP_64,
65..=128 => WORKGROUP_128,
_ => WORKGROUP_256,
}
}
use crate::gpu::linalg::sparse_gpu::GpuCompressedSparseData;
use crate::prelude::*;
#[cube(launch_unchecked)]
pub fn spmm_csr_forward<S: Float, A: Float>(
indptr: &Tensor<u32>,
indices: &Tensor<u32>,
values: &Tensor<S>,
x: &Tensor<A>,
correction: &Tensor<A>,
row_offsets: &Tensor<A>,
x_sum: &Tensor<A>,
y: &mut Tensor<A>,
n_rows: u32,
s_width: u32,
#[comptime] wg_size: u32,
) {
let row = CUBE_POS_Y * CUBE_COUNT_X + CUBE_POS_X;
if row >= n_rows {
terminate!();
}
let tx = UNIT_POS_X;
let seg_start = indptr[row as usize];
let seg_end = indptr[(row + 1u32) as usize];
let m_row = row_offsets[row as usize];
let mut col = tx;
while col < s_width {
let mut acc = A::new(0.0);
let mut idx = seg_start;
while idx < seg_end {
let j = indices[idx as usize];
let v = A::cast_from(values[idx as usize]);
acc += v * x[j as usize * s_width as usize + col as usize];
idx += 1u32;
}
acc -= correction[col as usize];
acc -= m_row * x_sum[col as usize];
y[row as usize * s_width as usize + col as usize] = acc;
col += wg_size;
}
}
#[cube(launch_unchecked)]
pub fn spmm_csc_transpose<S: Float, A: Float>(
indptr: &Tensor<u32>,
indices: &Tensor<u32>,
values: &Tensor<S>,
q: &Tensor<A>,
q_sum: &Tensor<A>,
mu: &Tensor<A>,
sigma: &Tensor<A>,
m_dot_q: &Tensor<A>,
z: &mut Tensor<A>,
m_rows: u32,
s_width: u32,
#[comptime] wg_size: u32,
) {
let row = CUBE_POS_Y * CUBE_COUNT_X + CUBE_POS_X;
if row >= m_rows {
terminate!();
}
let tx = UNIT_POS_X;
let seg_start = indptr[row as usize];
let seg_end = indptr[(row + 1u32) as usize];
let mu_j = mu[row as usize];
let sigma_j = sigma[row as usize];
let mut col = tx;
while col < s_width {
let mut acc = A::new(0.0);
let mut idx = seg_start;
while idx < seg_end {
let i = indices[idx as usize];
let v = A::cast_from(values[idx as usize]);
acc += v * q[i as usize * s_width as usize + col as usize];
idx += 1u32;
}
acc -= mu_j * q_sum[col as usize];
acc -= m_dot_q[col as usize];
acc /= sigma_j;
z[row as usize * s_width as usize + col as usize] = acc;
col += wg_size;
}
}
#[cube(launch_unchecked)]
pub fn dense_column_sum<A: Float>(
matrix: &Tensor<A>,
out: &mut Tensor<A>,
n_rows: u32,
s_width: u32,
#[comptime] wg_size: u32,
) {
let col = CUBE_POS_Y * CUBE_COUNT_X + CUBE_POS_X;
if col >= s_width {
terminate!();
}
let tx = UNIT_POS_X;
let mut acc = A::new(0.0);
let mut i = tx;
while i < n_rows {
acc += matrix[i as usize * s_width as usize + col as usize];
i += wg_size;
}
let mut shared = SharedMemory::<A>::new(WORKGROUP_128 as usize);
shared[tx as usize] = acc;
sync_cube();
if tx < 64u32 {
let other = shared[(tx + 64u32) as usize];
shared[tx as usize] += other;
}
sync_cube();
if tx < 32u32 {
let other = shared[(tx + 32u32) as usize];
shared[tx as usize] += other;
}
sync_cube();
if tx < 16u32 {
let other = shared[(tx + 16u32) as usize];
shared[tx as usize] += other;
}
sync_cube();
if tx < 8u32 {
let other = shared[(tx + 8u32) as usize];
shared[tx as usize] += other;
}
sync_cube();
if tx < 4u32 {
let other = shared[(tx + 4u32) as usize];
shared[tx as usize] += other;
}
sync_cube();
if tx < 2u32 {
let other = shared[(tx + 2u32) as usize];
shared[tx as usize] += other;
}
sync_cube();
if tx < 1u32 {
let other = shared[(tx + 1u32) as usize];
shared[tx as usize] += other;
}
sync_cube();
if tx == 0u32 {
out[col as usize] = shared[0];
}
}
#[cube(launch_unchecked)]
pub fn dense_column_weighted_sum<A: Float>(
weights: &Tensor<A>,
matrix: &Tensor<A>,
out: &mut Tensor<A>,
n_rows: u32,
s_width: u32,
#[comptime] wg_size: u32,
) {
let col = CUBE_POS_Y * CUBE_COUNT_X + CUBE_POS_X;
if col >= s_width {
terminate!();
}
let tx = UNIT_POS_X;
let mut acc = A::new(0.0);
let mut i = tx;
while i < n_rows {
acc += weights[i as usize] * matrix[i as usize * s_width as usize + col as usize];
i += wg_size;
}
let mut shared = SharedMemory::<A>::new(WORKGROUP_128 as usize);
shared[tx as usize] = acc;
sync_cube();
if tx < 64u32 {
let other = shared[(tx + 64u32) as usize];
shared[tx as usize] += other;
}
sync_cube();
if tx < 32u32 {
let other = shared[(tx + 32u32) as usize];
shared[tx as usize] += other;
}
sync_cube();
if tx < 16u32 {
let other = shared[(tx + 16u32) as usize];
shared[tx as usize] += other;
}
sync_cube();
if tx < 8u32 {
let other = shared[(tx + 8u32) as usize];
shared[tx as usize] += other;
}
sync_cube();
if tx < 4u32 {
let other = shared[(tx + 4u32) as usize];
shared[tx as usize] += other;
}
sync_cube();
if tx < 2u32 {
let other = shared[(tx + 2u32) as usize];
shared[tx as usize] += other;
}
sync_cube();
if tx < 1u32 {
let other = shared[(tx + 1u32) as usize];
shared[tx as usize] += other;
}
sync_cube();
if tx == 0u32 {
out[col as usize] = shared[0];
}
}
#[allow(clippy::too_many_arguments)]
pub fn launch_spmm_csr_forward<R, S, A>(
sparse: &GpuCompressedSparseData<R, S>,
x: &GpuTensor<R, A>,
correction: &GpuTensor<R, A>,
row_offsets: &GpuTensor<R, A>,
x_sum: &GpuTensor<R, A>,
y: &GpuTensor<R, A>,
s_width: usize,
client: &ComputeClient<R>,
) -> Result<(), BixverseErrors>
where
R: Runtime,
S: Float + cubecl::CubeElement,
A: Float + cubecl::CubeElement,
{
if !sparse.cs_type.is_csr() {
return Err(BixverseErrors::SparseLayoutMismatch {
expected: CompressedSparseFormat::Csr,
got: sparse.cs_type,
});
}
let (n, _m) = sparse.shape;
let limits = GpuLimits::from_client(client);
let (gx, gy) = grid_2d(n as u32, &limits)?;
let count = CubeCount::Static(gx, gy, 1);
macro_rules! dispatch {
($wg:expr) => {
unsafe {
spmm_csr_forward::launch_unchecked::<S, A, R>(
client,
count,
CubeDim::new_1d($wg),
sparse.indptr.clone().into_tensor_arg(),
sparse.indices.clone().into_tensor_arg(),
sparse.values.clone().into_tensor_arg(),
x.clone().into_tensor_arg(),
correction.clone().into_tensor_arg(),
row_offsets.clone().into_tensor_arg(),
x_sum.clone().into_tensor_arg(),
y.clone().into_tensor_arg(),
n as u32,
s_width as u32,
$wg,
);
}
};
}
match spmm_workgroup(s_width) {
WORKGROUP_64 => dispatch!(WORKGROUP_64),
WORKGROUP_128 => dispatch!(WORKGROUP_128),
_ => dispatch!(WORKGROUP_256),
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub fn launch_spmm_csc_transpose<R, S, A>(
sparse: &GpuCompressedSparseData<R, S>,
q: &GpuTensor<R, A>,
q_sum: &GpuTensor<R, A>,
mu: &GpuTensor<R, A>,
sigma: &GpuTensor<R, A>,
m_dot_q: &GpuTensor<R, A>,
z: &GpuTensor<R, A>,
s_width: usize,
client: &ComputeClient<R>,
) -> Result<(), BixverseErrors>
where
R: Runtime,
S: Float + cubecl::CubeElement,
A: Float + cubecl::CubeElement,
{
if !sparse.cs_type.is_csc() {
return Err(BixverseErrors::SparseLayoutMismatch {
expected: CompressedSparseFormat::Csc,
got: sparse.cs_type,
});
}
let (_n, m) = sparse.shape;
let limits = GpuLimits::from_client(client);
let (gx, gy) = grid_2d(m as u32, &limits)?;
let count = CubeCount::Static(gx, gy, 1);
macro_rules! dispatch {
($wg:expr) => {
unsafe {
spmm_csc_transpose::launch_unchecked::<S, A, R>(
client,
count,
CubeDim::new_1d($wg),
sparse.indptr.clone().into_tensor_arg(),
sparse.indices.clone().into_tensor_arg(),
sparse.values.clone().into_tensor_arg(),
q.clone().into_tensor_arg(),
q_sum.clone().into_tensor_arg(),
mu.clone().into_tensor_arg(),
sigma.clone().into_tensor_arg(),
m_dot_q.clone().into_tensor_arg(),
z.clone().into_tensor_arg(),
m as u32,
s_width as u32,
$wg,
);
}
};
}
match spmm_workgroup(s_width) {
WORKGROUP_64 => dispatch!(WORKGROUP_64),
WORKGROUP_128 => dispatch!(WORKGROUP_128),
_ => dispatch!(WORKGROUP_256),
}
Ok(())
}
pub fn launch_dense_column_sum<R, A>(
matrix: &GpuTensor<R, A>,
out: &GpuTensor<R, A>,
n_rows: usize,
s_width: usize,
client: &ComputeClient<R>,
) -> Result<(), BixverseErrors>
where
R: Runtime,
A: Float + cubecl::CubeElement,
{
let limits = GpuLimits::from_client(client);
let (gx, gy) = grid_2d(s_width as u32, &limits)?;
unsafe {
dense_column_sum::launch_unchecked::<A, R>(
client,
CubeCount::Static(gx, gy, 1),
CubeDim::new_1d(WORKGROUP_128),
matrix.clone().into_tensor_arg(),
out.clone().into_tensor_arg(),
n_rows as u32,
s_width as u32,
WORKGROUP_128,
);
}
Ok(())
}
pub fn launch_dense_column_weighted_sum<R, A>(
weights: &GpuTensor<R, A>,
matrix: &GpuTensor<R, A>,
out: &GpuTensor<R, A>,
n_rows: usize,
s_width: usize,
client: &ComputeClient<R>,
) -> Result<(), BixverseErrors>
where
R: Runtime,
A: Float + cubecl::CubeElement,
{
let limits = GpuLimits::from_client(client);
let (gx, gy) = grid_2d(s_width as u32, &limits)?;
unsafe {
dense_column_weighted_sum::launch_unchecked::<A, R>(
client,
CubeCount::Static(gx, gy, 1),
CubeDim::new_1d(WORKGROUP_128),
weights.clone().into_tensor_arg(),
matrix.clone().into_tensor_arg(),
out.clone().into_tensor_arg(),
n_rows as u32,
s_width as u32,
WORKGROUP_128,
);
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use cubecl::wgpu::{WgpuDevice, WgpuRuntime};
fn try_device() -> Option<WgpuDevice> {
let device = WgpuDevice::DefaultDevice;
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
WgpuRuntime::client(&device);
}))
.ok()
.map(|_| device)
}
fn dense_to_csr(a: &[f32], n: usize, m: usize) -> (Vec<f32>, Vec<u32>, Vec<u32>) {
let mut values = Vec::new();
let mut indices = Vec::new();
let mut indptr = vec![0u32];
for i in 0..n {
for j in 0..m {
let v = a[i * m + j];
if v != 0.0 {
values.push(v);
indices.push(j as u32);
}
}
indptr.push(values.len() as u32);
}
(values, indices, indptr)
}
fn dense_to_csc(a: &[f32], n: usize, m: usize) -> (Vec<f32>, Vec<u32>, Vec<u32>) {
let mut values = Vec::new();
let mut indices = Vec::new();
let mut indptr = vec![0u32];
for j in 0..m {
for i in 0..n {
let v = a[i * m + j];
if v != 0.0 {
values.push(v);
indices.push(i as u32);
}
}
indptr.push(values.len() as u32);
}
(values, indices, indptr)
}
#[allow(clippy::too_many_arguments)]
fn cpu_spmm_csr_forward(
a: &[f32],
x: &[f32],
correction: &[f32],
row_offsets: &[f32],
x_sum: &[f32],
n: usize,
m: usize,
s: usize,
) -> Vec<f32> {
let mut y = vec![0.0f32; n * s];
for i in 0..n {
for col in 0..s {
let mut acc = 0.0f32;
for j in 0..m {
acc += a[i * m + j] * x[j * s + col];
}
y[i * s + col] = acc - correction[col] - row_offsets[i] * x_sum[col];
}
}
y
}
#[allow(clippy::too_many_arguments)]
fn cpu_spmm_csc_transpose(
a: &[f32],
q: &[f32],
q_sum: &[f32],
mu: &[f32],
sigma: &[f32],
m_dot_q: &[f32],
n: usize,
m: usize,
s: usize,
) -> Vec<f32> {
let mut z = vec![0.0f32; m * s];
for j in 0..m {
for col in 0..s {
let mut acc = 0.0f32;
for i in 0..n {
acc += a[i * m + j] * q[i * s + col];
}
z[j * s + col] = (acc - mu[j] * q_sum[col] - m_dot_q[col]) / sigma[j];
}
}
z
}
fn cpu_column_sum(matrix: &[f32], n_rows: usize, s: usize) -> Vec<f32> {
let mut out = vec![0.0f32; s];
for i in 0..n_rows {
for col in 0..s {
out[col] += matrix[i * s + col];
}
}
out
}
fn cpu_column_weighted_sum(w: &[f32], matrix: &[f32], n_rows: usize, s: usize) -> Vec<f32> {
let mut out = vec![0.0f32; s];
for i in 0..n_rows {
for col in 0..s {
out[col] += w[i] * matrix[i * s + col];
}
}
out
}
fn assert_vec_close(got: &[f32], want: &[f32], tol: f32) {
assert_eq!(got.len(), want.len());
for j in 0..got.len() {
assert!(
(got[j] - want[j]).abs() < tol,
"elem {}: {} != {} (diff {})",
j,
got[j],
want[j],
(got[j] - want[j]).abs()
);
}
}
fn run_column_sum(
matrix: &[f32],
n_rows: usize,
s_width: usize,
device: &WgpuDevice,
) -> Vec<f32> {
let client = WgpuRuntime::client(device);
let m_gpu =
GpuTensor::<WgpuRuntime, f32>::from_slice(matrix, vec![n_rows, s_width], &client)
.unwrap();
let out_gpu = GpuTensor::<WgpuRuntime, f32>::from_slice(
&vec![0.0f32; s_width],
vec![s_width],
&client,
)
.unwrap();
launch_dense_column_sum(&m_gpu, &out_gpu, n_rows, s_width, &client).unwrap();
out_gpu.read(&client).unwrap()
}
fn run_column_weighted_sum(
weights: &[f32],
matrix: &[f32],
n_rows: usize,
s_width: usize,
device: &WgpuDevice,
) -> Vec<f32> {
let client = WgpuRuntime::client(device);
let w_gpu =
GpuTensor::<WgpuRuntime, f32>::from_slice(weights, vec![n_rows], &client).unwrap();
let m_gpu =
GpuTensor::<WgpuRuntime, f32>::from_slice(matrix, vec![n_rows, s_width], &client)
.unwrap();
let out_gpu = GpuTensor::<WgpuRuntime, f32>::from_slice(
&vec![0.0f32; s_width],
vec![s_width],
&client,
)
.unwrap();
launch_dense_column_weighted_sum(&w_gpu, &m_gpu, &out_gpu, n_rows, s_width, &client)
.unwrap();
out_gpu.read(&client).unwrap()
}
#[allow(clippy::too_many_arguments)]
fn run_spmm_csr_forward(
a: &[f32],
x: &[f32],
correction: &[f32],
row_offsets: &[f32],
x_sum: &[f32],
n: usize,
m: usize,
s: usize,
device: &WgpuDevice,
) -> Vec<f32> {
let client = WgpuRuntime::client(device);
let (values, indices, indptr) = dense_to_csr(a, n, m);
let sparse = GpuCompressedSparseData::<WgpuRuntime, f32>::from_parts(
&values,
&indices,
&indptr,
CompressedSparseFormat::Csr,
(n, m),
&client,
)
.unwrap();
let x_gpu = GpuTensor::<WgpuRuntime, f32>::from_slice(x, vec![m, s], &client).unwrap();
let corr_gpu =
GpuTensor::<WgpuRuntime, f32>::from_slice(correction, vec![s], &client).unwrap();
let row_offsets_gpu =
GpuTensor::<WgpuRuntime, f32>::from_slice(row_offsets, vec![n], &client).unwrap();
let x_sum_gpu = GpuTensor::<WgpuRuntime, f32>::from_slice(x_sum, vec![s], &client).unwrap();
let y_gpu =
GpuTensor::<WgpuRuntime, f32>::from_slice(&vec![0.0f32; n * s], vec![n, s], &client)
.unwrap();
launch_spmm_csr_forward(
&sparse,
&x_gpu,
&corr_gpu,
&row_offsets_gpu,
&x_sum_gpu,
&y_gpu,
s,
&client,
)
.unwrap();
y_gpu.read(&client).unwrap()
}
#[allow(clippy::too_many_arguments)]
fn run_spmm_csc_transpose(
a: &[f32],
q: &[f32],
q_sum: &[f32],
mu: &[f32],
sigma: &[f32],
m_dot_q: &[f32],
n: usize,
m: usize,
s: usize,
device: &WgpuDevice,
) -> Vec<f32> {
let client = WgpuRuntime::client(device);
let (values, indices, indptr) = dense_to_csc(a, n, m);
let sparse = GpuCompressedSparseData::<WgpuRuntime, f32>::from_parts(
&values,
&indices,
&indptr,
CompressedSparseFormat::Csc,
(n, m),
&client,
)
.unwrap();
let q_gpu = GpuTensor::<WgpuRuntime, f32>::from_slice(q, vec![n, s], &client).unwrap();
let qsum_gpu = GpuTensor::<WgpuRuntime, f32>::from_slice(q_sum, vec![s], &client).unwrap();
let mu_gpu = GpuTensor::<WgpuRuntime, f32>::from_slice(mu, vec![m], &client).unwrap();
let sigma_gpu = GpuTensor::<WgpuRuntime, f32>::from_slice(sigma, vec![m], &client).unwrap();
let m_dot_q_gpu =
GpuTensor::<WgpuRuntime, f32>::from_slice(m_dot_q, vec![s], &client).unwrap();
let z_gpu =
GpuTensor::<WgpuRuntime, f32>::from_slice(&vec![0.0f32; m * s], vec![m, s], &client)
.unwrap();
launch_spmm_csc_transpose(
&sparse,
&q_gpu,
&qsum_gpu,
&mu_gpu,
&sigma_gpu,
&m_dot_q_gpu,
&z_gpu,
s,
&client,
)
.unwrap();
z_gpu.read(&client).unwrap()
}
#[test]
fn test_dense_column_sum() {
let Some(device) = try_device() else { return };
let (n, s) = (250, 16);
let matrix: Vec<f32> = (0..n * s)
.map(|i| ((i * 7 + 3) % 23) as f32 * 0.1)
.collect();
let got = run_column_sum(&matrix, n, s, &device);
let want = cpu_column_sum(&matrix, n, s);
assert_vec_close(&got, &want, 1e-3);
}
#[test]
fn test_dense_column_weighted_sum() {
let Some(device) = try_device() else { return };
let (n, s) = (250, 16);
let matrix: Vec<f32> = (0..n * s)
.map(|i| ((i * 7 + 3) % 23) as f32 * 0.1)
.collect();
let weights: Vec<f32> = (0..n).map(|i| ((i * 11 + 5) % 17) as f32 * 0.05).collect();
let got = run_column_weighted_sum(&weights, &matrix, n, s, &device);
let want = cpu_column_weighted_sum(&weights, &matrix, n, s);
assert_vec_close(&got, &want, 1e-3);
}
#[test]
fn test_spmm_csr_forward() {
let Some(device) = try_device() else { return };
let (n, m, s) = (100, 60, 16);
let a: Vec<f32> = (0..n * m)
.map(|i| {
if i % 5 == 0 {
((i * 7 + 3) % 23) as f32 * 0.2
} else {
0.0
}
})
.collect();
let x: Vec<f32> = (0..m * s)
.map(|i| ((i * 11 + 5) % 19) as f32 * 0.1)
.collect();
let correction: Vec<f32> = (0..s).map(|i| (i as f32) * 0.05).collect();
let row_offsets = vec![0.0f32; n];
let x_sum = vec![0.0f32; s];
let got = run_spmm_csr_forward(&a, &x, &correction, &row_offsets, &x_sum, n, m, s, &device);
let want = cpu_spmm_csr_forward(&a, &x, &correction, &row_offsets, &x_sum, n, m, s);
assert_vec_close(&got, &want, 1e-3);
}
#[test]
fn test_spmm_csc_transpose() {
let Some(device) = try_device() else { return };
let (n, m, s) = (120, 50, 16);
let a: Vec<f32> = (0..n * m)
.map(|i| {
if i % 5 == 0 {
((i * 7 + 3) % 23) as f32 * 0.2
} else {
0.0
}
})
.collect();
let q: Vec<f32> = (0..n * s)
.map(|i| ((i * 11 + 5) % 19) as f32 * 0.1)
.collect();
let q_sum = cpu_column_sum(&q, n, s);
let mu: Vec<f32> = (0..m).map(|j| ((j * 13 + 1) % 11) as f32 * 0.1).collect();
let sigma: Vec<f32> = (0..m)
.map(|j| 0.5 + ((j * 5 + 2) % 7) as f32 * 0.1)
.collect();
let m_dot_q = vec![0.0f32; s];
let got = run_spmm_csc_transpose(&a, &q, &q_sum, &mu, &sigma, &m_dot_q, n, m, s, &device);
let want = cpu_spmm_csc_transpose(&a, &q, &q_sum, &mu, &sigma, &m_dot_q, n, m, s);
assert_vec_close(&got, &want, 1e-3);
}
#[test]
fn test_spmm_csr_forward_empty_row() {
let Some(device) = try_device() else { return };
let (n, m, s) = (8, 4, 4);
let mut a = vec![0.0f32; n * m];
a[1] = 1.5;
a[7 * m + 3] = -2.0;
let x: Vec<f32> = (0..m * s).map(|i| (i + 1) as f32 * 0.1).collect();
let correction: Vec<f32> = (0..s).map(|i| (i + 1) as f32 * 0.25).collect();
let row_offsets = vec![0.0f32; n];
let x_sum = vec![0.0f32; s];
let got = run_spmm_csr_forward(&a, &x, &correction, &row_offsets, &x_sum, n, m, s, &device);
let want = cpu_spmm_csr_forward(&a, &x, &correction, &row_offsets, &x_sum, n, m, s);
assert_vec_close(&got, &want, 1e-6);
for i in 1..7 {
for col in 0..s {
assert!(
(got[i * s + col] + correction[col]).abs() < 1e-6,
"empty row {} col {} not equal to -correction",
i,
col
);
}
}
}
#[test]
fn test_spmm_csr_forward_layout_mismatch() {
let Some(device) = try_device() else { return };
let client = WgpuRuntime::client(&device);
let (n, m, s) = (4, 3, 2);
let mut a = vec![0.0f32; n * m];
a[0] = 1.0;
let (values, indices, indptr) = dense_to_csc(&a, n, m);
let sparse = GpuCompressedSparseData::<WgpuRuntime, f32>::from_parts(
&values,
&indices,
&indptr,
CompressedSparseFormat::Csc,
(n, m),
&client,
)
.unwrap();
let x_gpu =
GpuTensor::<WgpuRuntime, f32>::from_slice(&vec![0.0f32; m * s], vec![m, s], &client)
.unwrap();
let corr_gpu =
GpuTensor::<WgpuRuntime, f32>::from_slice(&vec![0.0f32; s], vec![s], &client).unwrap();
let y_gpu =
GpuTensor::<WgpuRuntime, f32>::from_slice(&vec![0.0f32; n * s], vec![n, s], &client)
.unwrap();
let res = launch_spmm_csr_forward(
&sparse,
&x_gpu,
&corr_gpu,
&GpuTensor::<WgpuRuntime, f32>::from_slice(&vec![0.0f32; n], vec![n], &client).unwrap(),
&GpuTensor::<WgpuRuntime, f32>::from_slice(&vec![0.0f32; s], vec![s], &client).unwrap(),
&y_gpu,
s,
&client,
);
assert!(matches!(
res,
Err(BixverseErrors::SparseLayoutMismatch { .. })
));
}
#[test]
fn test_spmm_csc_transpose_layout_mismatch() {
let Some(device) = try_device() else { return };
let client = WgpuRuntime::client(&device);
let (n, m, s) = (4, 3, 2);
let mut a = vec![0.0f32; n * m];
a[0] = 1.0;
let (values, indices, indptr) = dense_to_csr(&a, n, m);
let sparse = GpuCompressedSparseData::<WgpuRuntime, f32>::from_parts(
&values,
&indices,
&indptr,
CompressedSparseFormat::Csr,
(n, m),
&client,
)
.unwrap();
let q_gpu =
GpuTensor::<WgpuRuntime, f32>::from_slice(&vec![0.0f32; n * s], vec![n, s], &client)
.unwrap();
let qsum_gpu =
GpuTensor::<WgpuRuntime, f32>::from_slice(&vec![0.0f32; s], vec![s], &client).unwrap();
let mu_gpu =
GpuTensor::<WgpuRuntime, f32>::from_slice(&vec![0.0f32; m], vec![m], &client).unwrap();
let sigma_gpu =
GpuTensor::<WgpuRuntime, f32>::from_slice(&vec![1.0f32; m], vec![m], &client).unwrap();
let z_gpu =
GpuTensor::<WgpuRuntime, f32>::from_slice(&vec![0.0f32; m * s], vec![m, s], &client)
.unwrap();
let res = launch_spmm_csc_transpose(
&sparse,
&q_gpu,
&qsum_gpu,
&mu_gpu,
&sigma_gpu,
&GpuTensor::<WgpuRuntime, f32>::from_slice(&vec![0.0f32; s], vec![s], &client).unwrap(),
&z_gpu,
s,
&client,
);
assert!(matches!(
res,
Err(BixverseErrors::SparseLayoutMismatch { .. })
));
}
}