Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 26 additions & 7 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ env:

jobs:
cpu-tests:
name: CPU Unit Tests
name: CPU Unit Tests (${{ matrix.blas }})
strategy:
fail-fast: false
matrix:
blas: [OpenBLAS, MKL]
if: |
github.event_name != 'issue_comment' ||
(
Expand All @@ -28,7 +32,7 @@ jobs:
!startsWith(github.event.comment.body, '/runbenchmark')
)
concurrency:
group: cpu-tests-${{ github.event.issue.number || github.ref }}
group: cpu-tests-${{ matrix.blas }}-${{ github.event.issue.number || github.ref }}
cancel-in-progress: true
runs-on: ubuntu-latest
timeout-minutes: 30
Expand All @@ -38,10 +42,19 @@ jobs:
with:
ref: ${{ github.event_name == 'issue_comment' && format('refs/pull/{0}/merge', github.event.issue.number) || github.ref }}

- name: Install OpenBLAS
- name: Install ${{ matrix.blas }}
run: |
sudo apt-get update
sudo apt-get install -y libopenblas-dev
if [ "${{ matrix.blas }}" = MKL ]; then
wget -qO- https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS.PUB \
| gpg --dearmor | sudo tee /usr/share/keyrings/oneapi-archive-keyring.gpg > /dev/null
echo "deb [signed-by=/usr/share/keyrings/oneapi-archive-keyring.gpg] https://apt.repos.intel.com/oneapi all main" \
| sudo tee /etc/apt/sources.list.d/oneAPI.list
sudo apt-get update
sudo apt-get install -y intel-oneapi-mkl-devel
else
sudo apt-get update
sudo apt-get install -y libopenblas-dev
fi

- name: Cache FetchContent dependencies
uses: actions/cache@v4
Expand All @@ -51,27 +64,33 @@ jobs:
restore-keys: cmake-deps-cpu-

- name: Configure
shell: bash
run: |
cmake -B build -S . \
-DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} \
-DSOFIEBLAS_BUILD_TESTS=ON \
-DSOFIEBLAS_BUILD_BENCHMARKS=ON \
-DSOFIEBLAS_ENABLE_CUDA=OFF \
-DSOFIEBLAS_ENABLE_HIP=OFF \
"-DFETCHCONTENT_BASE_DIR=${{ env.DEPS_CACHE }}"
-DCPU_BLAS_LIB=${{ matrix.blas }} \
"-DFETCHCONTENT_BASE_DIR=${{ env.DEPS_CACHE }}" | tee configure.log
grep -q "using CPU BLAS library ${{ matrix.blas }}" configure.log

- name: Build
run: cmake --build build -j"$(nproc)"

- name: Run tests
working-directory: build
env:
LD_LIBRARY_PATH: /opt/intel/oneapi/mkl/latest/lib/intel64:/opt/intel/oneapi/mkl/latest/lib
MKL_THREADING_LAYER: SEQUENTIAL
run: ctest --output-on-failure -j"$(nproc)" -R '\.cpu(\.|$)'

- name: Upload test log
if: always()
uses: actions/upload-artifact@v4
with:
name: cpu-test-log-${{ github.run_id }}
name: cpu-test-log-${{ matrix.blas }}-${{ github.run_id }}
path: build/Testing/Temporary/LastTest.log
if-no-files-found: ignore

Expand Down
3 changes: 2 additions & 1 deletion tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ if(SOFIEBLAS_CUDA_ENABLED)
)

target_compile_definitions(test_cuda PRIVATE ALPAKA_ACC_GPU_CUDA_ENABLED)
target_include_directories(test_cuda PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(test_cuda PRIVATE
sofieBLAS::sofieBLAS alpaka::alpaka CUDA::cudart CUDA::cublas CUDA::cublasLt)

Expand All @@ -51,7 +52,7 @@ if(SOFIEBLAS_HIP_ENABLED)
target_compile_options(test_hip PRIVATE ${CXXFLAGS} ${CXX_HOST_FLAGS})

target_compile_definitions(test_hip PRIVATE ALPAKA_ACC_GPU_HIP_ENABLED)
target_include_directories(test_hip PRIVATE ${ROCM_BASE}/include)
target_include_directories(test_hip PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${ROCM_BASE}/include)
target_link_directories(test_hip PRIVATE ${ROCM_BASE}/lib)
target_link_libraries(test_hip PRIVATE
sofieBLAS::sofieBLAS alpaka::alpaka hipblaslt hipblas amdhip64)
Expand Down
220 changes: 220 additions & 0 deletions tests/cpu/unit_test.tpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
// CPU backend tests, included from test.cc

static void runCpuTests() {
Comment thread
sanjibansg marked this conversation as resolved.
std::cout << "\n=== CPU Tests ===\n";

alpaka::PlatformCpu platform{};
auto dev = alpaka::getDevByIdx(platform, 0u);
alpaka::Queue<alpaka::DevCpu, alpaka::Blocking> queue{dev};
sofieBLAS<alpaka::TagCpuSerial> blas(queue);

constexpr int M = 4, N = 3, K = 5;

// Allocate host buffers
auto hA = alpaka::allocBuf<float, Idx>(dev, static_cast<Idx>(M * K));
auto hB = alpaka::allocBuf<float, Idx>(dev, static_cast<Idx>(K * N));
auto hC = alpaka::allocBuf<float, Idx>(dev, static_cast<Idx>(M * N));
auto hBias = alpaka::allocBuf<float, Idx>(dev, static_cast<Idx>(M * N));

float *A = alpaka::getPtrNative(hA);
float *B = alpaka::getPtrNative(hB);
float *C = alpaka::getPtrNative(hC);
float *bias = alpaka::getPtrNative(hBias);

fillSeq(A, M * K);
fillSeq(B, K * N, 1.f, 0.5f);
fillSeq(bias, M * N, 0.1f, 0.1f);

std::vector<float> ref(M * N);

// --- matmul NN ---
fillVal(C, M * N, 0.f);
blas.matmul('N', 'N', M, N, K, 1.f, hA, hB, 0.f, hC);
std::copy(C, C + M * N, ref.data());
std::fill(ref.begin(), ref.end(), 0.f);
refMatmul(ref.data(), A, B, M, N, K, 1.f, 0.f, false, false);
checkClose(C, ref.data(), M * N, "cpu::matmul NN");

// --- matmul TN (A^T: K×M physical → M×K logical) ---
{
auto hAt = alpaka::allocBuf<float, Idx>(dev, static_cast<Idx>(K * M));
float *At = alpaka::getPtrNative(hAt);
fillSeq(At, K * M);
fillVal(C, M * N, 0.f);
blas.matmul('T', 'N', M, N, K, 1.f, hAt, hB, 0.f, hC);
std::fill(ref.begin(), ref.end(), 0.f);
refMatmul(ref.data(), At, B, M, N, K, 1.f, 0.f, true, false);
checkClose(C, ref.data(), M * N, "cpu::matmul TN");
}

// --- matmul NT ---
{
auto hBt = alpaka::allocBuf<float, Idx>(dev, static_cast<Idx>(N * K));
float *Bt = alpaka::getPtrNative(hBt);
fillSeq(Bt, N * K, 1.f, 0.5f);
fillVal(C, M * N, 0.f);
blas.matmul('N', 'T', M, N, K, 1.f, hA, hBt, 0.f, hC);
std::fill(ref.begin(), ref.end(), 0.f);
refMatmul(ref.data(), A, Bt, M, N, K, 1.f, 0.f, false, true);
checkClose(C, ref.data(), M * N, "cpu::matmul NT");
}

// --- matmul TT ---
{
auto hAt = alpaka::allocBuf<float, Idx>(dev, static_cast<Idx>(K * M));
auto hBt = alpaka::allocBuf<float, Idx>(dev, static_cast<Idx>(N * K));
float *At = alpaka::getPtrNative(hAt);
float *Bt = alpaka::getPtrNative(hBt);
fillSeq(At, K * M);
fillSeq(Bt, N * K, 1.f, 0.5f);
fillVal(C, M * N, 0.f);
blas.matmul('T', 'T', M, N, K, 1.f, hAt, hBt, 0.f, hC);
std::fill(ref.begin(), ref.end(), 0.f);
refMatmul(ref.data(), At, Bt, M, N, K, 1.f, 0.f, true, true);
checkClose(C, ref.data(), M * N, "cpu::matmul TT");
}

// --- matmul: alpha scaling ---
fillVal(C, M * N, 0.f);
blas.matmul('N', 'N', M, N, K, 2.5f, hA, hB, 0.f, hC);
std::fill(ref.begin(), ref.end(), 0.f);
refMatmul(ref.data(), A, B, M, N, K, 2.5f, 0.f, false, false);
checkClose(C, ref.data(), M * N, "cpu::matmul alpha=2.5");

// --- matmul: beta accumulation ---
fillSeq(C, M * N, 10.f); // pre-fill C
blas.matmul('N', 'N', M, N, K, 1.f, hA, hB, 0.5f, hC);
{
std::vector<float> C0(M * N);
fillSeq(C0.data(), M * N, 10.f);
refMatmul(ref.data(), A, B, M, N, K, 1.f, 0.5f, false, false);
std::copy(C0.begin(), C0.end(), ref.data());
refMatmul(ref.data(), A, B, M, N, K, 1.f, 0.5f, false, false);
}
checkClose(C, ref.data(), M * N, "cpu::matmul beta=0.5");

// --- gemm NN (beta=0, no prior accumulation) ---
fillVal(C, M * N, 0.f);
fillSeq(bias, M * N, 0.1f, 0.1f);
blas.gemm('N', 'N', M, N, K, 1.f, hA, hB, 0.f, hBias, hC);
std::fill(ref.begin(), ref.end(), 0.f);
refGemm(ref.data(), A, B, bias, M, N, K, 1.f, 0.f, false, false);
checkClose(C, ref.data(), M * N, "cpu::gemm NN beta=0");

// --- gemm NN (beta=1 accumulation) ---
fillVal(C, M * N, 0.f);
blas.gemm('N', 'N', M, N, K, 1.f, hA, hB, 1.f, hBias, hC);
std::fill(ref.begin(), ref.end(), 0.f);
refGemm(ref.data(), A, B, bias, M, N, K, 1.f, 1.f, false, false);
checkClose(C, ref.data(), M * N, "cpu::gemm NN beta=1");

// --- gemm TN ---
{
auto hAt = alpaka::allocBuf<float, Idx>(dev, static_cast<Idx>(K * M));
float *At = alpaka::getPtrNative(hAt);
fillSeq(At, K * M);
fillVal(C, M * N, 0.f);
blas.gemm('T', 'N', M, N, K, 1.f, hAt, hB, 0.f, hBias, hC);
std::fill(ref.begin(), ref.end(), 0.f);
refGemm(ref.data(), At, B, bias, M, N, K, 1.f, 0.f, true, false);
checkClose(C, ref.data(), M * N, "cpu::gemm TN");
}

// --- gemmrelu: all-positive matmul result stays unchanged ---
{
// A and B with positive values ensure result is positive before bias
auto hAp = alpaka::allocBuf<float, Idx>(dev, static_cast<Idx>(M * K));
auto hBp = alpaka::allocBuf<float, Idx>(dev, static_cast<Idx>(K * N));
auto hBiasp = alpaka::allocBuf<float, Idx>(dev, static_cast<Idx>(M * N));
float *Ap = alpaka::getPtrNative(hAp);
float *Bp = alpaka::getPtrNative(hBp);
float *biasp = alpaka::getPtrNative(hBiasp);
fillSeq(Ap, M * K, 0.1f, 0.1f);
fillSeq(Bp, K * N, 0.1f, 0.1f);
fillVal(biasp, M * N, 0.f);
fillVal(C, M * N, 0.f);
blas.gemmrelu('N', 'N', M, N, K, 1.f, hAp, hBp, 0.f, hBiasp, hC);
std::fill(ref.begin(), ref.end(), 0.f);
refGemmRelu(ref.data(), Ap, Bp, biasp, M, N, K, 1.f, 0.f, false, false);
checkClose(C, ref.data(), M * N, "cpu::gemmrelu all-positive");
}

// --- gemmrelu: negative values clamped to zero ---
{
// Use alpha=-1 to force negative results
auto hBiasz = alpaka::allocBuf<float, Idx>(dev, static_cast<Idx>(M * N));
fillVal(alpaka::getPtrNative(hBiasz), M * N, 0.f);
fillVal(C, M * N, 0.f);
blas.gemmrelu('N', 'N', M, N, K, -1.f, hA, hB, 0.f, hBiasz, hC);
std::fill(ref.begin(), ref.end(), 0.f);
refGemmRelu(ref.data(), A, B, alpaka::getPtrNative(hBiasz), M, N, K, -1.f,
0.f, false, false);
checkClose(C, ref.data(), M * N, "cpu::gemmrelu alpha=-1 (clamped)");
}

// --- gemmrelu with bias ---
fillVal(C, M * N, 0.f);
fillSeq(bias, M * N, -5.f, 2.f);
blas.gemmrelu('N', 'N', M, N, K, 1.f, hA, hB, 0.f, hBias, hC);
std::fill(ref.begin(), ref.end(), 0.f);
refGemmRelu(ref.data(), A, B, bias, M, N, K, 1.f, 0.f, false, false);
checkClose(C, ref.data(), M * N, "cpu::gemmrelu with mixed bias");

// --- gemmgelu NN ---
fillVal(C, M * N, 0.f);
fillVal(bias, M * N, 0.f);
blas.gemmgelu('N', 'N', M, N, K, 1.f, hA, hB, 0.f, hBias, hC);
std::fill(ref.begin(), ref.end(), 0.f);
refGemmGelu(ref.data(), A, B, bias, M, N, K, 1.f, 0.f, false, false);
checkClose(C, ref.data(), M * N, "cpu::gemmgelu NN");

// --- gemmgelu with bias ---
fillVal(C, M * N, 0.f);
fillSeq(bias, M * N, -2.f, 0.5f);
blas.gemmgelu('N', 'N', M, N, K, 1.f, hA, hB, 0.f, hBias, hC);
std::fill(ref.begin(), ref.end(), 0.f);
refGemmGelu(ref.data(), A, B, bias, M, N, K, 1.f, 0.f, false, false);
checkClose(C, ref.data(), M * N, "cpu::gemmgelu with bias");

// --- gemmgelu TN ---
{
auto hAt = alpaka::allocBuf<float, Idx>(dev, static_cast<Idx>(K * M));
float *At = alpaka::getPtrNative(hAt);
fillSeq(At, K * M);
fillVal(C, M * N, 0.f);
fillVal(bias, M * N, 0.f);
blas.gemmgelu('T', 'N', M, N, K, 1.f, hAt, hB, 0.f, hBias, hC);
std::fill(ref.begin(), ref.end(), 0.f);
refGemmGelu(ref.data(), At, B, bias, M, N, K, 1.f, 0.f, true, false);
checkClose(C, ref.data(), M * N, "cpu::gemmgelu TN");
}

// --- edge: zero matrix ---
{
auto hZ = alpaka::allocBuf<float, Idx>(dev, static_cast<Idx>(M * K));
fillVal(alpaka::getPtrNative(hZ), M * K, 0.f);
fillVal(C, M * N, 99.f);
fillVal(bias, M * N, 0.f);
blas.matmul('N', 'N', M, N, K, 1.f, hZ, hB, 0.f, hC);
std::fill(ref.begin(), ref.end(), 0.f);
checkClose(C, ref.data(), M * N, "cpu::matmul zero-A");
}

// --- edge: identity-like (square, known result) ---
{
constexpr int S = 3;
auto hI = alpaka::allocBuf<float, Idx>(dev, static_cast<Idx>(S * S));
auto hX = alpaka::allocBuf<float, Idx>(dev, static_cast<Idx>(S * S));
auto hY = alpaka::allocBuf<float, Idx>(dev, static_cast<Idx>(S * S));
float *I = alpaka::getPtrNative(hI);
float *X = alpaka::getPtrNative(hX);
float *Y = alpaka::getPtrNative(hY);
fillVal(I, S * S, 0.f);
for (int i = 0; i < S; ++i)
I[i * S + i] = 1.f;
fillSeq(X, S * S);
fillVal(Y, S * S, 0.f);
blas.matmul('N', 'N', S, S, S, 1.f, hI, hX, 0.f, hY);
checkClose(Y, X, S * S, "cpu::matmul identity×X=X");
}
}
Loading
Loading