#include "ops_matrix.h"
#include "types.h"
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#ifdef USE_BLAS
#include <cblas.h>
#endif
#define MATMUL_OP(TYPE, TYPE_SUFFIX) \
void matmul_##TYPE_SUFFIX(const void *lhs_ptr, const void *rhs_ptr, void *output_ptr, \
const size_t *metadata) { \
const TYPE *lhs = (const TYPE *)lhs_ptr; \
const TYPE *rhs = (const TYPE *)rhs_ptr; \
TYPE *output = (TYPE *)output_ptr; \
\
const size_t num_els = metadata[0]; \
const size_t lhs_ndim = metadata[1]; \
const size_t rhs_ndim = metadata[2]; \
const size_t batch_ndim = metadata[3]; \
\
const size_t *lhs_shape = metadata + 4; \
const size_t *rhs_shape = lhs_shape + lhs_ndim; \
const size_t *batch_shape = rhs_shape + rhs_ndim; \
const size_t *lhs_strides = batch_shape + batch_ndim; \
const size_t *rhs_strides = lhs_strides + lhs_ndim; \
const size_t lhs_offset = *(rhs_strides + rhs_ndim); \
const size_t rhs_offset = *(rhs_strides + rhs_ndim + 1); \
const size_t M = *(rhs_strides + rhs_ndim + 2); \
const size_t K = *(rhs_strides + rhs_ndim + 3); \
const size_t N = *(rhs_strides + rhs_ndim + 4); \
\
size_t lhs_batch_ndim = lhs_ndim - 2; \
size_t rhs_batch_ndim = rhs_ndim - 2; \
\
\
bool is_contiguous = (lhs_strides[lhs_ndim - 1] == 1 && rhs_strides[rhs_ndim - 1] == 1 && \
lhs_strides[lhs_ndim - 2] == K && rhs_strides[rhs_ndim - 2] == N && \
lhs_offset == 0 && rhs_offset == 0); \
\
if (is_contiguous && batch_ndim == 0) { \
\
for (size_t i = 0; i < M; i++) { \
for (size_t j = 0; j < N; j++) { \
TYPE sum = 0; \
for (size_t k = 0; k < K; k++) { \
sum += lhs[i * K + k] * rhs[k * N + j]; \
} \
output[i * N + j] = sum; \
} \
} \
} else { \
\
for (size_t idx = 0; idx < num_els; idx++) { \
size_t mn = idx % (M * N); \
size_t batch_idx = idx / (M * N); \
size_t i = mn / N; \
size_t j = mn % N; \
\
size_t batch_indices[16]; \
size_t temp = batch_idx; \
for (int d = (int)batch_ndim - 1; d >= 0; d--) { \
batch_indices[d] = temp % batch_shape[d]; \
temp /= batch_shape[d]; \
} \
\
size_t lhs_batch_indices[16]; \
for (size_t d = 0; d < lhs_batch_ndim; d++) { \
size_t batch_dim_idx = batch_ndim - lhs_batch_ndim + d; \
lhs_batch_indices[d] = (lhs_shape[d] == 1) ? 0 : batch_indices[batch_dim_idx]; \
} \
\
size_t rhs_batch_indices[16]; \
for (size_t d = 0; d < rhs_batch_ndim; d++) { \
size_t batch_dim_idx = batch_ndim - rhs_batch_ndim + d; \
rhs_batch_indices[d] = (rhs_shape[d] == 1) ? 0 : batch_indices[batch_dim_idx]; \
} \
\
TYPE sum = 0; \
for (size_t k = 0; k < K; k++) { \
size_t lhs_idx = lhs_offset; \
for (size_t d = 0; d < lhs_batch_ndim; d++) { \
lhs_idx += lhs_batch_indices[d] * lhs_strides[d]; \
} \
lhs_idx += i * lhs_strides[lhs_ndim - 2]; \
lhs_idx += k * lhs_strides[lhs_ndim - 1]; \
\
size_t rhs_idx = rhs_offset; \
for (size_t d = 0; d < rhs_batch_ndim; d++) { \
rhs_idx += rhs_batch_indices[d] * rhs_strides[d]; \
} \
rhs_idx += k * rhs_strides[rhs_ndim - 2]; \
rhs_idx += j * rhs_strides[rhs_ndim - 1]; \
\
sum += lhs[lhs_idx] * rhs[rhs_idx]; \
} \
\
output[idx] = sum; \
} \
} \
}
MATMUL_OP(f32_t, f32_fallback)
MATMUL_OP(f64_t, f64_fallback)
#ifdef USE_BLAS
void matmul_f32(const void *lhs_ptr, const void *rhs_ptr, void *output_ptr,
const size_t *metadata) {
const f32_t *lhs = (const f32_t *)lhs_ptr;
const f32_t *rhs = (const f32_t *)rhs_ptr;
f32_t *output = (f32_t *)output_ptr;
const size_t lhs_ndim = metadata[1];
const size_t rhs_ndim = metadata[2];
const size_t batch_ndim = metadata[3];
const size_t *lhs_shape = metadata + 4;
const size_t *rhs_shape = lhs_shape + lhs_ndim;
const size_t *batch_shape = rhs_shape + rhs_ndim;
const size_t *lhs_strides = batch_shape + batch_ndim;
const size_t *rhs_strides = lhs_strides + lhs_ndim;
const size_t lhs_offset = *(rhs_strides + rhs_ndim);
const size_t rhs_offset = *(rhs_strides + rhs_ndim + 1);
const size_t M = *(rhs_strides + rhs_ndim + 2);
const size_t K = *(rhs_strides + rhs_ndim + 3);
const size_t N = *(rhs_strides + rhs_ndim + 4);
lhs += lhs_offset;
rhs += rhs_offset;
bool lhs_row_major = (lhs_strides[lhs_ndim - 1] == 1);
bool lhs_col_major = (lhs_strides[lhs_ndim - 2] == 1);
bool rhs_row_major = (rhs_strides[rhs_ndim - 1] == 1);
bool rhs_col_major = (rhs_strides[rhs_ndim - 2] == 1);
if ((lhs_row_major || lhs_col_major) && (rhs_row_major || rhs_col_major)) {
CBLAS_TRANSPOSE trans_lhs = lhs_row_major ? CblasNoTrans : CblasTrans;
CBLAS_TRANSPOSE trans_rhs = rhs_row_major ? CblasNoTrans : CblasTrans;
size_t lda = lhs_row_major ? lhs_strides[lhs_ndim - 2] : lhs_strides[lhs_ndim - 1];
size_t ldb = rhs_row_major ? rhs_strides[rhs_ndim - 2] : rhs_strides[rhs_ndim - 1];
size_t ldc = N;
if (batch_ndim == 0) {
cblas_sgemm(CblasRowMajor, trans_lhs, trans_rhs, M, N, K, 1.0f, lhs, lda, rhs, ldb,
0.0f, output, ldc);
} else {
size_t total_batches = 1;
for (size_t i = 0; i < batch_ndim; i++) {
total_batches *= batch_shape[i];
}
size_t out_batch_stride = M * N;
for (size_t batch_idx = 0; batch_idx < total_batches; batch_idx++) {
size_t idx = batch_idx;
size_t lhs_batch_offset = 0;
size_t rhs_batch_offset = 0;
for (size_t i = batch_ndim; i > 0; i--) {
size_t dim_idx = idx % batch_shape[i - 1];
idx /= batch_shape[i - 1];
size_t lhs_dim_idx = (lhs_shape[lhs_ndim - 2 - i] == 1) ? 0 : dim_idx;
size_t rhs_dim_idx = (rhs_shape[rhs_ndim - 2 - i] == 1) ? 0 : dim_idx;
lhs_batch_offset += lhs_dim_idx * lhs_strides[lhs_ndim - 2 - i];
rhs_batch_offset += rhs_dim_idx * rhs_strides[rhs_ndim - 2 - i];
}
const f32_t *lhs_batch = lhs + lhs_batch_offset;
const f32_t *rhs_batch = rhs + rhs_batch_offset;
f32_t *out_batch = output + batch_idx * out_batch_stride;
cblas_sgemm(CblasRowMajor, trans_lhs, trans_rhs, M, N, K, 1.0f, lhs_batch, lda,
rhs_batch, ldb, 0.0f, out_batch, ldc);
}
}
} else {
matmul_f32_fallback(lhs_ptr, rhs_ptr, output_ptr, metadata);
}
}
void matmul_f64(const void *lhs_ptr, const void *rhs_ptr, void *output_ptr,
const size_t *metadata) {
const f64_t *lhs = (const f64_t *)lhs_ptr;
const f64_t *rhs = (const f64_t *)rhs_ptr;
f64_t *output = (f64_t *)output_ptr;
const size_t lhs_ndim = metadata[1];
const size_t rhs_ndim = metadata[2];
const size_t batch_ndim = metadata[3];
const size_t *lhs_shape = metadata + 4;
const size_t *rhs_shape = lhs_shape + lhs_ndim;
const size_t *batch_shape = rhs_shape + rhs_ndim;
const size_t *lhs_strides = batch_shape + batch_ndim;
const size_t *rhs_strides = lhs_strides + lhs_ndim;
const size_t lhs_offset = *(rhs_strides + rhs_ndim);
const size_t rhs_offset = *(rhs_strides + rhs_ndim + 1);
const size_t M = *(rhs_strides + rhs_ndim + 2);
const size_t K = *(rhs_strides + rhs_ndim + 3);
const size_t N = *(rhs_strides + rhs_ndim + 4);
lhs += lhs_offset;
rhs += rhs_offset;
bool lhs_row_major = (lhs_strides[lhs_ndim - 1] == 1);
bool lhs_col_major = (lhs_strides[lhs_ndim - 2] == 1);
bool rhs_row_major = (rhs_strides[rhs_ndim - 1] == 1);
bool rhs_col_major = (rhs_strides[rhs_ndim - 2] == 1);
if ((lhs_row_major || lhs_col_major) && (rhs_row_major || rhs_col_major)) {
CBLAS_TRANSPOSE trans_lhs = lhs_row_major ? CblasNoTrans : CblasTrans;
CBLAS_TRANSPOSE trans_rhs = rhs_row_major ? CblasNoTrans : CblasTrans;
size_t lda = lhs_row_major ? lhs_strides[lhs_ndim - 2] : lhs_strides[lhs_ndim - 1];
size_t ldb = rhs_row_major ? rhs_strides[rhs_ndim - 2] : rhs_strides[rhs_ndim - 1];
size_t ldc = N;
if (batch_ndim == 0) {
cblas_dgemm(CblasRowMajor, trans_lhs, trans_rhs, M, N, K, 1.0, lhs, lda, rhs, ldb, 0.0,
output, ldc);
} else {
size_t total_batches = 1;
for (size_t i = 0; i < batch_ndim; i++) {
total_batches *= batch_shape[i];
}
size_t out_batch_stride = M * N;
for (size_t batch_idx = 0; batch_idx < total_batches; batch_idx++) {
size_t idx = batch_idx;
size_t lhs_batch_offset = 0;
size_t rhs_batch_offset = 0;
for (size_t i = batch_ndim; i > 0; i--) {
size_t dim_idx = idx % batch_shape[i - 1];
idx /= batch_shape[i - 1];
size_t lhs_dim_idx = (lhs_shape[lhs_ndim - 2 - i] == 1) ? 0 : dim_idx;
size_t rhs_dim_idx = (rhs_shape[rhs_ndim - 2 - i] == 1) ? 0 : dim_idx;
lhs_batch_offset += lhs_dim_idx * lhs_strides[lhs_ndim - 2 - i];
rhs_batch_offset += rhs_dim_idx * rhs_strides[rhs_ndim - 2 - i];
}
const f64_t *lhs_batch = lhs + lhs_batch_offset;
const f64_t *rhs_batch = rhs + rhs_batch_offset;
f64_t *out_batch = output + batch_idx * out_batch_stride;
cblas_dgemm(CblasRowMajor, trans_lhs, trans_rhs, M, N, K, 1.0, lhs_batch, lda,
rhs_batch, ldb, 0.0, out_batch, ldc);
}
}
} else {
matmul_f64_fallback(lhs_ptr, rhs_ptr, output_ptr, metadata);
}
}
#else
void matmul_f32(const void *lhs_ptr, const void *rhs_ptr, void *output_ptr,
const size_t *metadata) {
matmul_f32_fallback(lhs_ptr, rhs_ptr, output_ptr, metadata);
}
void matmul_f64(const void *lhs_ptr, const void *rhs_ptr, void *output_ptr,
const size_t *metadata) {
matmul_f64_fallback(lhs_ptr, rhs_ptr, output_ptr, metadata);
}
#endif
MATMUL_OP(f8e4m3_t, f8e4m3)
MATMUL_OP(f8e5m2_t, f8e5m2)
MATMUL_OP(bf16_t, bf16)
MATMUL_OP(f16_t, f16)
MATMUL_OP(int8_t, i8)
MATMUL_OP(int16_t, i16)
MATMUL_OP(int32_t, i32)
MATMUL_OP(int64_t, i64)
MATMUL_OP(uint8_t, u8)
MATMUL_OP(uint16_t, u16)
MATMUL_OP(uint32_t, u32)
MATMUL_OP(uint64_t, u64)
#define DOT_OP(TYPE, TYPE_SUFFIX) \
void dot_##TYPE_SUFFIX(const void *lhs_ptr, const void *rhs_ptr, void *output_ptr, \
const size_t *metadata) { \
const TYPE *lhs = (const TYPE *)lhs_ptr; \
const TYPE *rhs = (const TYPE *)rhs_ptr; \
TYPE *output = (TYPE *)output_ptr; \
\
const size_t M = metadata[0]; \
const size_t K = metadata[1]; \
const size_t N = metadata[2]; \
const size_t lhs_stride_m = metadata[3]; \
const size_t lhs_stride_k = metadata[4]; \
const size_t rhs_stride_k = metadata[5]; \
const size_t rhs_stride_n = metadata[6]; \
const size_t lhs_offset = metadata[7]; \
const size_t rhs_offset = metadata[8]; \
\
\
memset(output, 0, M * N * sizeof(TYPE)); \
\
\
const size_t BLOCK_M = 32; \
const size_t BLOCK_N = 32; \
const size_t BLOCK_K = 256; \
const size_t REG_M = 4; \
const size_t REG_N = 4; \
\
\
bool is_contiguous = \
(lhs_stride_k == 1 && rhs_stride_n == 1 && lhs_stride_m == K && rhs_stride_k == N); \
\
if (is_contiguous && lhs_offset == 0 && rhs_offset == 0) { \
\
for (size_t ii = 0; ii < M; ii += BLOCK_M) { \
size_t i_end = (ii + BLOCK_M < M) ? (ii + BLOCK_M) : M; \
for (size_t kk = 0; kk < K; kk += BLOCK_K) { \
size_t k_end = (kk + BLOCK_K < K) ? (kk + BLOCK_K) : K; \
for (size_t jj = 0; jj < N; jj += BLOCK_N) { \
size_t j_end = (jj + BLOCK_N < N) ? (jj + BLOCK_N) : N; \
\
for (size_t i = ii; i < i_end; i += REG_M) { \
size_t i_reg_end = (i + REG_M < i_end) ? (i + REG_M) : i_end; \
for (size_t j = jj; j < j_end; j += REG_N) { \
size_t j_reg_end = (j + REG_N < j_end) ? (j + REG_N) : j_end; \
\
TYPE acc[4][4] = {{0}}; \
for (size_t ir = 0; ir < (i_reg_end - i); ir++) { \
for (size_t jr = 0; jr < (j_reg_end - j); jr++) { \
acc[ir][jr] = output[(i + ir) * N + (j + jr)]; \
} \
} \
\
for (size_t k = kk; k < k_end; k++) { \
for (size_t ir = 0; ir < (i_reg_end - i); ir++) { \
TYPE a_val = lhs[(i + ir) * K + k]; \
for (size_t jr = 0; jr < (j_reg_end - j); jr++) { \
acc[ir][jr] += a_val * rhs[k * N + (j + jr)]; \
} \
} \
} \
\
for (size_t ir = 0; ir < (i_reg_end - i); ir++) { \
for (size_t jr = 0; jr < (j_reg_end - j); jr++) { \
output[(i + ir) * N + (j + jr)] = acc[ir][jr]; \
} \
} \
} \
} \
} \
} \
} \
} else { \
\
for (size_t ii = 0; ii < M; ii += BLOCK_M) { \
size_t i_end = (ii + BLOCK_M < M) ? (ii + BLOCK_M) : M; \
for (size_t kk = 0; kk < K; kk += BLOCK_K) { \
size_t k_end = (kk + BLOCK_K < K) ? (kk + BLOCK_K) : K; \
for (size_t jj = 0; jj < N; jj += BLOCK_N) { \
size_t j_end = (jj + BLOCK_N < N) ? (jj + BLOCK_N) : N; \
for (size_t i = ii; i < i_end; i++) { \
for (size_t k = kk; k < k_end; k++) { \
size_t lhs_idx = lhs_offset + i * lhs_stride_m + k * lhs_stride_k; \
TYPE a_val = lhs[lhs_idx]; \
for (size_t j = jj; j < j_end; j++) { \
size_t rhs_idx = \
rhs_offset + k * rhs_stride_k + j * rhs_stride_n; \
output[i * N + j] += a_val * rhs[rhs_idx]; \
} \
} \
} \
} \
} \
} \
} \
}
DOT_OP(f32_t, f32_fallback)
DOT_OP(f64_t, f64_fallback)
#ifdef USE_BLAS
void dot_f32(const void *lhs_ptr, const void *rhs_ptr, void *output_ptr, const size_t *metadata) {
const f32_t *lhs = (const f32_t *)lhs_ptr;
const f32_t *rhs = (const f32_t *)rhs_ptr;
f32_t *output = (f32_t *)output_ptr;
const size_t M = metadata[0];
const size_t K = metadata[1];
const size_t N = metadata[2];
const size_t lhs_stride_m = metadata[3];
const size_t lhs_stride_k = metadata[4];
const size_t rhs_stride_k = metadata[5];
const size_t rhs_stride_n = metadata[6];
const size_t lhs_offset = metadata[7];
const size_t rhs_offset = metadata[8];
lhs += lhs_offset;
rhs += rhs_offset;
bool lhs_row_major = (lhs_stride_k == 1);
bool lhs_col_major = (lhs_stride_m == 1);
bool rhs_row_major = (rhs_stride_n == 1);
bool rhs_col_major = (rhs_stride_k == 1);
if ((lhs_row_major || lhs_col_major) && (rhs_row_major || rhs_col_major)) {
CBLAS_TRANSPOSE trans_lhs = lhs_row_major ? CblasNoTrans : CblasTrans;
CBLAS_TRANSPOSE trans_rhs = rhs_row_major ? CblasNoTrans : CblasTrans;
size_t lda = lhs_row_major ? lhs_stride_m : lhs_stride_k;
size_t ldb = rhs_row_major ? rhs_stride_k : rhs_stride_n;
cblas_sgemm(CblasRowMajor, trans_lhs, trans_rhs, M, N, K, 1.0f, lhs, lda, rhs, ldb, 0.0f,
output, N);
} else {
dot_f32_fallback(lhs_ptr, rhs_ptr, output_ptr, metadata);
}
}
void dot_f64(const void *lhs_ptr, const void *rhs_ptr, void *output_ptr, const size_t *metadata) {
const f64_t *lhs = (const f64_t *)lhs_ptr;
const f64_t *rhs = (const f64_t *)rhs_ptr;
f64_t *output = (f64_t *)output_ptr;
const size_t M = metadata[0];
const size_t K = metadata[1];
const size_t N = metadata[2];
const size_t lhs_stride_m = metadata[3];
const size_t lhs_stride_k = metadata[4];
const size_t rhs_stride_k = metadata[5];
const size_t rhs_stride_n = metadata[6];
const size_t lhs_offset = metadata[7];
const size_t rhs_offset = metadata[8];
lhs += lhs_offset;
rhs += rhs_offset;
bool lhs_row_major = (lhs_stride_k == 1);
bool lhs_col_major = (lhs_stride_m == 1);
bool rhs_row_major = (rhs_stride_n == 1);
bool rhs_col_major = (rhs_stride_k == 1);
if ((lhs_row_major || lhs_col_major) && (rhs_row_major || rhs_col_major)) {
CBLAS_TRANSPOSE trans_lhs = lhs_row_major ? CblasNoTrans : CblasTrans;
CBLAS_TRANSPOSE trans_rhs = rhs_row_major ? CblasNoTrans : CblasTrans;
size_t lda = lhs_row_major ? lhs_stride_m : lhs_stride_k;
size_t ldb = rhs_row_major ? rhs_stride_k : rhs_stride_n;
cblas_dgemm(CblasRowMajor, trans_lhs, trans_rhs, M, N, K, 1.0, lhs, lda, rhs, ldb, 0.0,
output, N);
} else {
dot_f64_fallback(lhs_ptr, rhs_ptr, output_ptr, metadata);
}
}
#else
void dot_f32(const void *lhs_ptr, const void *rhs_ptr, void *output_ptr, const size_t *metadata) {
dot_f32_fallback(lhs_ptr, rhs_ptr, output_ptr, metadata);
}
void dot_f64(const void *lhs_ptr, const void *rhs_ptr, void *output_ptr, const size_t *metadata) {
dot_f64_fallback(lhs_ptr, rhs_ptr, output_ptr, metadata);
}
#endif
DOT_OP(f8e4m3_t, f8e4m3)
DOT_OP(f8e5m2_t, f8e5m2)
DOT_OP(bf16_t, bf16)
DOT_OP(f16_t, f16)
DOT_OP(int8_t, i8)
DOT_OP(int16_t, i16)
DOT_OP(int32_t, i32)
DOT_OP(int64_t, i64)
DOT_OP(uint8_t, u8)
DOT_OP(uint16_t, u16)
DOT_OP(uint32_t, u32)
DOT_OP(uint64_t, u64)