diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 791cd73..32c33a6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,10 @@ name: Continuous integration jobs: ci: - runs-on: self-hosted + runs-on: ${{ matrix.runner }} + strategy: + matrix: + runner: [orin, a40] steps: - name: checkout code uses: actions/checkout@v4 diff --git a/CMakeLists.txt b/CMakeLists.txt index 5d39ae0..4b87d7b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,35 +2,49 @@ # GPUtils # ==================================================================== cmake_minimum_required(VERSION 3.20 FATAL_ERROR) + +if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.29") + cmake_policy(SET CMP0135 NEW) +endif() + +# Set C++ version and SM architecture +if (NOT DEFINED CPPVERSION) + set(CPPVERSION 20) # A40: 20, Orin: 17 +endif() +if (NOT DEFINED SM_ARCH) + set(SM_ARCH 86)# A40: 86, Orin: 87 +endif() + + project(GPUtils - DESCRIPTION "Easy use of vectors and matrices on GPGPU devices." - HOMEPAGE_URL "https://github.com/GPUEngineering/GPUtils" - LANGUAGES CXX -) + DESCRIPTION "Easy use of vectors and matrices on GPGPU devices." + HOMEPAGE_URL "https://github.com/GPUEngineering/GPUtils" + LANGUAGES CXX + ) # ---- set(CMAKE_CUDA_SEPARABLE_COMPILATION ON) # required for calling cuda kernels from cuda kernels -set(CMAKE_CUDA_COMPILER "/usr/local/cuda-12.3/bin/nvcc") -set(CMAKE_CUDA_ARCHITECTURES 86) -set(CMAKE_CUDA_STANDARD 20) -set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CUDA_FLAGS "-std=c++20") -set(CUDA_NVCC_FLAGS ${CUDA_NVCC_FLAGS}; -std=c++20) +set(CMAKE_CUDA_COMPILER "/usr/local/cuda/bin/nvcc") +set(CMAKE_CUDA_ARCHITECTURES ${SM_ARCH}) +set(CMAKE_CUDA_STANDARD ${CPPVERSION}) +set(CMAKE_CXX_STANDARD ${CPPVERSION}) +set(CMAKE_CUDA_FLAGS "-std=c++${CPPVERSION}") +set(CUDA_NVCC_FLAGS ${CUDA_NVCC_FLAGS}; "-std=c++${CPPVERSION}") enable_language(CUDA) # ---- add_library(device_compiler_flags INTERFACE) -target_compile_features(device_compiler_flags INTERFACE cxx_std_20) +target_compile_features(device_compiler_flags INTERFACE cxx_std_${CPPVERSION}) set(CMAKE_CXX_EXTENSIONS OFF) # ---- add_library(developer_flags INTERFACE) set(cxx_flags -Wall) -set(cuda_flags -arch=sm_60 -std=c++20 -Xcompiler=-Wall -Xcudafe=--display_error_number -g) +set(cuda_flags -arch=sm_${SM_ARCH} -std=c++${CPPVERSION} -Xcompiler=-Wall -Xcudafe=--display_error_number -g) target_compile_options(developer_flags - INTERFACE - # flags for CXX builds - $<$:${cxx_flags}> - # flags for CUDA builds - $<$:${cuda_flags}> -) + INTERFACE + # flags for CXX builds + $<$:${cxx_flags}> + # flags for CUDA builds + $<$:${cuda_flags}> + ) target_link_libraries(device_compiler_flags INTERFACE $) # ---- @@ -40,21 +54,21 @@ target_link_libraries(device_compiler_flags INTERFACE $ -void CholeskyBatchFactoriser::factorise() { +inline void CholeskyBatchFactoriser::factorise() { if (m_factorisationDone) return; DTensor ptrA = m_matrix->pointersToMatrices(); gpuErrChk(cusolverDnDpotrfBatched(Session::getInstance().cuSolverHandle(), @@ -1340,7 +1340,7 @@ void CholeskyBatchFactoriser::factorise() { } template<> -void CholeskyBatchFactoriser::factorise() { +inline void CholeskyBatchFactoriser::factorise() { if (m_factorisationDone) return; DTensor ptrA = m_matrix->pointersToMatrices(); gpuErrChk(cusolverDnSpotrfBatched(Session::getInstance().cuSolverHandle(), @@ -1354,8 +1354,11 @@ void CholeskyBatchFactoriser::factorise() { } template<> -void CholeskyBatchFactoriser::solve(DTensor &b) { +inline void CholeskyBatchFactoriser::solve(DTensor &b) { if (!m_factorisationDone) throw std::logic_error("[CholeskyBatchSolve] no factor to solve with"); + if (m_numRows != b.numRows() || m_numMats != b.numMats()) { + throw std::invalid_argument("[CholeskyBatchSolve] A and b incompatible"); + } if (b.numCols() != 1) throw std::invalid_argument("[CholeskyBatchSolve] only supports `b` with one column"); DTensor ptrA = m_matrix->pointersToMatrices(); DTensor ptrB = b.pointersToMatrices(); @@ -1372,8 +1375,11 @@ void CholeskyBatchFactoriser::solve(DTensor &b) { } template<> -void CholeskyBatchFactoriser::solve(DTensor &b) { +inline void CholeskyBatchFactoriser::solve(DTensor &b) { if (!m_factorisationDone) throw std::logic_error("[CholeskyBatchSolve] no factor to solve with"); + if (m_numRows != b.numRows() || m_numMats != b.numMats()) { + throw std::invalid_argument("[CholeskyBatchSolve] A and b incompatible"); + } if (b.numCols() != 1) throw std::invalid_argument("[CholeskyBatchSolve] only supports `b` with one column"); DTensor ptrA = m_matrix->pointersToMatrices(); DTensor ptrB = b.pointersToMatrices(); diff --git a/test/testTensor.cu b/test/testTensor.cu index 5291c68..20ddc1b 100644 --- a/test/testTensor.cu +++ b/test/testTensor.cu @@ -24,7 +24,7 @@ protected: * Zero Tensor (Constructor) * --------------------------------------- */ -template +TEMPLATE_WITH_TYPE_T void tensorConstructionZero() { DTensor zero(2, 3, 4, true); EXPECT_EQ(2, zero.numRows()); @@ -46,7 +46,7 @@ TEST_F(TensorTest, tensorConstructionZero) { * Row- and column-major data * --------------------------------------- */ -template +TEMPLATE_WITH_TYPE_T void tensorConstructionStorageMode() { size_t rows = 3; size_t cols = 2; @@ -98,7 +98,7 @@ TEST_F(TensorTest, tensorConstructionStorageMode) { * Move constructor * --------------------------------------- */ -template +TEMPLATE_WITH_TYPE_T void tensorMoveConstructor() { DTensor zero(2, 3, 4, true); DTensor x(std::move(zero)); @@ -118,7 +118,7 @@ TEST_F(TensorTest, tensorMoveConstructor) { * Constructor * --------------------------------------- */ -template +TEMPLATE_WITH_TYPE_T void tensorConstructionFromVector() { std::vector data = TENSOR_DATA_234A; DTensor tenz(data, 2, 3, 4); @@ -138,7 +138,7 @@ TEST_F(TensorTest, tensorConstructionFromVector) { * Tensor: Copy constructor * --------------------------------------- */ -template +TEMPLATE_WITH_TYPE_T void tensorCopyConstructor() { std::vector data = TENSOR_DATA_234A; DTensor tenz(data, 2, 3, 4); @@ -164,7 +164,7 @@ TEST_F(TensorTest, tensorCopyConstructor) { * axis = 2 (matrices) * --------------------------------------- */ -template +TEMPLATE_WITH_TYPE_T void tensorSlicingConstructorAxis2() { std::vector data = TENSOR_DATA_234A; DTensor tens(data, 2, 3, 4); @@ -186,7 +186,7 @@ TEST_F(TensorTest, tensorSlicingConstructorAxis2) { * axis = 1 (columns) * --------------------------------------- */ -template +TEMPLATE_WITH_TYPE_T void tensorSlicingConstructorAxis1() { std::vector data = TENSOR_DATA_234A; DTensor tenz(data, 2, 3, 4); @@ -211,7 +211,7 @@ TEST_F(TensorTest, tensorSlicingConstructorAxis1) { * axis = 0 (columns) * --------------------------------------- */ -template +TEMPLATE_WITH_TYPE_T void tensorSlicingConstructorAxis0() { std::vector data = TENSOR_DATA_234A; DTensor tenz(data, 2, 3, 4); @@ -235,7 +235,7 @@ TEST_F(TensorTest, tensorSlicingConstructorAxis0) { * Tensor: Upload data * --------------------------------------- */ -template +TEMPLATE_WITH_TYPE_T void tensorUpload() { std::vector data = TENSOR_DATA_234A; DTensor tenz(2, 3, 4); @@ -258,7 +258,7 @@ TEST_F(TensorTest, tensorUpload) { * Tensor: deviceCopyTo * --------------------------------------- */ -template +TEMPLATE_WITH_TYPE_T void tensorDeviceCopyTo() { std::vector data = TENSOR_DATA_234A; DTensor tenz(data, 2, 3, 4); @@ -282,7 +282,7 @@ TEST_F(TensorTest, tensorDeviceCopyTo) { * Tensor: Frobenius dot product * --------------------------------------- */ -template +TEMPLATE_WITH_TYPE_T void tensorDotF(T epsilon) { // as vectors std::vector dataA = TENSOR_DATA_234A; @@ -307,7 +307,7 @@ TEST_F(TensorTest, tensorDotF) { * Tensor: Frobenius norm * --------------------------------------- */ -template +TEMPLATE_WITH_TYPE_T void tensorNormF(T epsilon) { std::vector data = TENSOR_DATA_234A; DTensor tenz(data, 2, 3, 4); @@ -324,7 +324,7 @@ TEST_F(TensorTest, tensorNormF) { * all elements * --------------------------------------- */ -template +TEMPLATE_WITH_TYPE_T void tensorSumAbs() { std::vector data = TENSOR_DATA_234A; DTensor tenz(data, 2, 3, 4); @@ -341,7 +341,7 @@ TEST_F(TensorTest, tensorNormFtensorSumAbs) { * e.g., t(2, 3, 4) * --------------------------------------- */ -template +TEMPLATE_WITH_TYPE_T void tensorBracketOperator() { std::vector data = TENSOR_DATA_234A; DTensor tenz(data, 2, 3, 4); @@ -360,7 +360,7 @@ TEST_F(TensorTest, tensorBracketOperator) { * Tensor assignment operator * --------------------------------------- */ -template +TEMPLATE_WITH_TYPE_T void tensorAssignmentOperator() { std::vector data = TENSOR_DATA_234A; DTensor tenz(data, 2, 3, 4); @@ -382,7 +382,7 @@ TEST_F(TensorTest, tensorAssignmentOperator) { * Tensor times-equals scalar * --------------------------------------- */ -template +TEMPLATE_WITH_TYPE_T void tensorTimesEqualsScalar() { std::vector data = TENSOR_DATA_234A; std::vector dataTimes3 = {3, 6, 9, 12, 15, 18, 21, 24, 27, 24, 21, 30, 15, 12, 9, 6, 3, -3, 12, 9, 12, 9, 12, @@ -403,7 +403,7 @@ TEST_F(TensorTest, tensorTimesEqualsScalar) { * Scalar times tensor * --------------------------------------- */ -template +TEMPLATE_WITH_TYPE_T void tensorTimesScalar() { std::vector data = TENSOR_DATA_234A; std::vector dataTimes3 = {3, 6, 9, 12, 15, 18, 21, 24, 27, 24, 21, 30, 15, 12, 9, 6, 3, -3, 12, 9, 12, 9, 12, @@ -424,7 +424,7 @@ TEST_F(TensorTest, tensorTimesScalar) { * Tensor plus-equals tensor * --------------------------------------- */ -template +TEMPLATE_WITH_TYPE_T void tensorPlusEqualsTensor() { std::vector dataA = TENSOR_DATA_234A; std::vector dataB = TENSOR_DATA_234B; @@ -446,7 +446,7 @@ TEST_F(TensorTest, tensorPlusEqualsTensor) { * Tensor minus-equals tensor * --------------------------------------- */ -template +TEMPLATE_WITH_TYPE_T void tensorMinusEqualsTensor() { std::vector dataA = TENSOR_DATA_234A; std::vector dataB = TENSOR_DATA_234B; @@ -468,7 +468,7 @@ TEST_F(TensorTest, tensorMinusEqualsTensor) { * Tensor + Tensor * --------------------------------------- */ -template +TEMPLATE_WITH_TYPE_T void tensorPlusTensor() { std::vector dataA = TENSOR_DATA_234A; std::vector dataB = TENSOR_DATA_234B; @@ -490,7 +490,7 @@ TEST_F(TensorTest, tensorPlusTensor) { * Tensor - Tensor * --------------------------------------- */ -template +TEMPLATE_WITH_TYPE_T void tensorMinusTensor() { std::vector dataA = TENSOR_DATA_234A; std::vector dataB = TENSOR_DATA_234B; @@ -512,7 +512,7 @@ TEST_F(TensorTest, tensorMinusTensor) { * Tensor: pointers to matrices (on device) * --------------------------------------- */ -template +TEMPLATE_WITH_TYPE_T void tensorPointersToMatrices() { std::vector dataA = TENSOR_DATA_234A; DTensor A(dataA, 2, 3, 4); @@ -536,7 +536,7 @@ TEST_F(TensorTest, tensorPointersToMatrices) { * Tensor: C = AB * --------------------------------------- */ -template +TEMPLATE_WITH_TYPE_T void tensorAddAB() { std::vector aData = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, @@ -563,7 +563,7 @@ TEST_F(TensorTest, tensorAddAB) { * Tensor: getRows * --------------------------------------- */ -template +TEMPLATE_WITH_TYPE_T void tensorGetRows() { std::vector aData = {10.5, 25.0, 60.0, -21.0, 720.0, -1.0, @@ -595,7 +595,7 @@ TEST_F(TensorTest, tensorGetRows) { * Tensor: transpose * --------------------------------------- */ -template +TEMPLATE_WITH_TYPE_T void tensorTranspose() { std::vector aData = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; DTensor A(aData, 3, 2, 2); @@ -629,7 +629,7 @@ protected: * Tensor: Least squares * --------------------------------------- */ -template +TEMPLATE_WITH_TYPE_T void tensorLeastSquares1(T epsilon) { // TODO test with tall matrices too std::vector aData = {1, 2, @@ -672,8 +672,8 @@ protected: * and matrix rank * --------------------------------------- */ -template -requires std::floating_point +TEMPLATE_WITH_TYPE_T +TEMPLATE_CONSTRAINT_REQUIRES_FPX void singularValuesComputation(float epsilon) { std::vector bData = {1, 6, 6, 6, 6, 6, 6, 6, 2, 7, 7, 7, 7, 7, 7, 7, @@ -699,8 +699,8 @@ TEST_F(SvdTest, singularValuesComputation) { * Singular values - memory mgmt * --------------------------------------- */ -template -requires std::floating_point +TEMPLATE_WITH_TYPE_T +TEMPLATE_CONSTRAINT_REQUIRES_FPX void singularValuesMemory(float epsilon) { std::vector bData = {1, 6, 6, 6, 6, 6, 6, 6, 2, 7, 7, 7, 7, 7, 7, 7, @@ -731,8 +731,8 @@ TEST_F(SvdTest, singularValuesMemory) { /* --------------------------------------- * SVD with multiple matrices * --------------------------------------- */ -template -requires std::floating_point +TEMPLATE_WITH_TYPE_T +TEMPLATE_CONSTRAINT_REQUIRES_FPX void singularValuesMultipleMatrices(float epsilon) { std::vector aData = {1, 2, 3, 4, 5, 6, 1, 1, 1, 2, 2, 2, 0, 0, 0, 0, 0, 1}; DTensor A(aData, 3, 2, 3); @@ -779,8 +779,8 @@ TEST_F(SvdTest, singularValuesMultipleMatrices) { * SVD for rank computation of multiple * matrices * --------------------------------------- */ -template -requires std::floating_point +TEMPLATE_WITH_TYPE_T +TEMPLATE_CONSTRAINT_REQUIRES_FPX void singularValuesRankMultipleMatrices(float epsilon) { std::vector aData = {1, 4, 7, 10, 2, 5, 8, 11, 3, 6, 9, 0, 1, 4, 7, 10, 2, 5, 8, 11, 3, 6, 9, 12, @@ -815,8 +815,8 @@ protected: * Cholesky factorisation * --------------------------------------- */ -template -requires std::floating_point +TEMPLATE_WITH_TYPE_T +TEMPLATE_CONSTRAINT_REQUIRES_FPX void choleskyFactorisation(T epsilon) { std::vector aData = {10.0, 2.0, 3.0, 2.0, 20.0, -1.0, @@ -838,8 +838,8 @@ TEST_F(CholeskyTest, choleskyFactorisation) { * Cholesky factorisation: solve system * --------------------------------------- */ -template -requires std::floating_point +TEMPLATE_WITH_TYPE_T +TEMPLATE_CONSTRAINT_REQUIRES_FPX void choleskyFactorisationSolution(T epsilon) { std::vector aData = {10.0, 2.0, 3.0, 2.0, 20.0, -1.0, @@ -874,8 +874,8 @@ TEST_F(CholeskyTest, choleskyFactorisationSolution) { * Batched Cholesky factorisation * --------------------------------------- */ -template -requires std::floating_point +TEMPLATE_WITH_TYPE_T +TEMPLATE_CONSTRAINT_REQUIRES_FPX void choleskyBatchFactorisation(T epsilon) { std::vector aData = {10.0, 2.0, 3.0, 2.0, 20.0, -1.0, @@ -906,8 +906,8 @@ TEST_F(CholeskyTest, choleskyBatchFactorisation) { * Batched Cholesky solve * --------------------------------------- */ -template -requires std::floating_point +TEMPLATE_WITH_TYPE_T +TEMPLATE_CONSTRAINT_REQUIRES_FPX void choleskyBatchFactorSolve(T epsilon) { std::vector aData = {10.0, 2.0, 3.0, 2.0, 20.0, -1.0, @@ -947,8 +947,8 @@ TEST_F(CholeskyTest, choleskyBatchFactorSolve) { * Batched Cholesky solve (factor provided) * --------------------------------------- */ -template -requires std::floating_point +TEMPLATE_WITH_TYPE_T +TEMPLATE_CONSTRAINT_REQUIRES_FPX void choleskyBatchSolve(T epsilon) { std::vector aData = {10.0, 2.0, 3.0, 2.0, 20.0, -1.0, @@ -1007,8 +1007,8 @@ protected: * Basic nullspace test * --------------------------------------- */ -template -requires std::floating_point +TEMPLATE_WITH_TYPE_T +TEMPLATE_CONSTRAINT_REQUIRES_FPX void computeNullspaceTensor(T epsilon) { std::vector aData = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 7, 8, 9, @@ -1048,8 +1048,8 @@ TEST_F(NullspaceTest, computeNullspaceTensor) { * Nullspace is trivial * --------------------------------------- */ -template -requires std::floating_point +TEMPLATE_WITH_TYPE_T +TEMPLATE_CONSTRAINT_REQUIRES_FPX void computeNullspaceTrivial(T epsilon) { std::vector data{4, 5, 7, 4, 1, 8, @@ -1072,8 +1072,8 @@ TEST_F(NullspaceTest, computeNullspaceTrivial) { * Project onto nullspace * --------------------------------------- */ -template -requires std::floating_point +TEMPLATE_WITH_TYPE_T +TEMPLATE_CONSTRAINT_REQUIRES_FPX void projectOnNullspaceTensor(T epsilon) { // offline size_t m = 3;