// Copyright (c) 2026 PaddlePaddle Authors. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #if defined(PADDLE_WITH_CUDA) #include #include #include #include "gtest/gtest.h" #include "paddle/phi/backends/gpu/gpu_info.h" #include "paddle/phi/common/bfloat16.h" #include "paddle/phi/common/complex.h" #include "paddle/phi/common/float16.h" // Helper: allocate three same-sized device buffers, copy host data in, // invoke a kernel via |fn|, copy results back, synchronize, then free. // |fn| receives (d_a, d_b, d_c); it must not free them. template static void runOnDevice(const std::vector& h_a, const std::vector& h_b, std::vector* h_c, Fn fn) { size_t bytes = h_a.size() * sizeof(T); T *d_a = nullptr, *d_b = nullptr, *d_c = nullptr; ASSERT_EQ(cudaMalloc(&d_a, bytes), cudaSuccess); ASSERT_EQ(cudaMalloc(&d_b, bytes), cudaSuccess); ASSERT_EQ(cudaMalloc(&d_c, bytes), cudaSuccess); ASSERT_EQ(cudaMemcpy(d_a, h_a.data(), bytes, cudaMemcpyHostToDevice), cudaSuccess); ASSERT_EQ(cudaMemcpy(d_b, h_b.data(), bytes, cudaMemcpyHostToDevice), cudaSuccess); ASSERT_EQ(cudaMemcpy(d_c, h_c->data(), bytes, cudaMemcpyHostToDevice), cudaSuccess); fn(d_a, d_b, d_c); ASSERT_EQ(cudaMemcpy(h_c->data(), d_c, bytes, cudaMemcpyDeviceToHost), cudaSuccess); ASSERT_EQ(cudaDeviceSynchronize(), cudaSuccess); cudaFree(d_a); cudaFree(d_b); cudaFree(d_c); } // Runs 2x2 no-transpose gemm: C = alpha*A*B + beta*C and checks the result. // // Column-major layout: // A: col0={1,3}, col1={2,4} => logical A = [[1,2],[3,4]] // B: col0={5,7}, col1={6,8} => logical B = [[5,6],[7,8]] // A*B = [[19,22],[43,50]] stored col-major: col0={19,43}, col1={22,50} template > class GemmTester { public: static constexpr int64_t N = 2; static double toDouble(T val) { return static_cast(val); } void Run() { std::vector h_a = {T(1), T(3), T(2), T(4)}; std::vector h_b = {T(5), T(7), T(6), T(8)}; std::vector h_c(N * N, T(0)); MathT alpha = static_cast(1); MathT beta = static_cast(0); runOnDevice(h_a, h_b, &h_c, [&](T* d_a, T* d_b, T* d_c) { at::cuda::blas::gemm( 'N', 'N', N, N, N, alpha, d_a, N, d_b, N, beta, d_c, N); }); EXPECT_NEAR(toDouble(h_c[0]), 19.0, 1e-2); // C(0,0) EXPECT_NEAR(toDouble(h_c[1]), 43.0, 1e-2); // C(1,0) EXPECT_NEAR(toDouble(h_c[2]), 22.0, 1e-2); // C(0,1) EXPECT_NEAR(toDouble(h_c[3]), 50.0, 1e-2); // C(1,1) } // transA='T': C = alpha * A^T * B + beta * C // A^T = [[1,3],[2,4]], A^T * B = [[26,30],[38,44]] void RunTransA() { std::vector h_a = {T(1), T(3), T(2), T(4)}; std::vector h_b = {T(5), T(7), T(6), T(8)}; std::vector h_c(N * N, T(0)); MathT alpha = static_cast(1); MathT beta = static_cast(0); runOnDevice(h_a, h_b, &h_c, [&](T* d_a, T* d_b, T* d_c) { at::cuda::blas::gemm( 'T', 'N', N, N, N, alpha, d_a, N, d_b, N, beta, d_c, N); }); EXPECT_NEAR(toDouble(h_c[0]), 26.0, 1e-2); EXPECT_NEAR(toDouble(h_c[1]), 38.0, 1e-2); EXPECT_NEAR(toDouble(h_c[2]), 30.0, 1e-2); EXPECT_NEAR(toDouble(h_c[3]), 44.0, 1e-2); } }; TEST(CUDABlasTest, GemmDouble) { GemmTester t; t.Run(); } TEST(CUDABlasTest, GemmDoubleTransA) { GemmTester t; t.RunTransA(); } TEST(CUDABlasTest, GemmFloat) { GemmTester t; t.Run(); } TEST(CUDABlasTest, GemmFloatTransA) { GemmTester t; t.RunTransA(); } TEST(CUDABlasTest, GemmFloatTransALowercase) { constexpr int64_t N = 2; std::vector h_a = {1.F, 3.F, 2.F, 4.F}; std::vector h_b = {5.F, 7.F, 6.F, 8.F}; std::vector h_c(N * N, 0.F); float alpha = 1.F; float beta = 0.F; runOnDevice(h_a, h_b, &h_c, [&](float* d_a, float* d_b, float* d_c) { at::cuda::blas::gemm( 't', 'n', N, N, N, alpha, d_a, N, d_b, N, beta, d_c, N); }); EXPECT_NEAR(h_c[0], 26.0f, 1e-3f); EXPECT_NEAR(h_c[1], 38.0f, 1e-3f); EXPECT_NEAR(h_c[2], 30.0f, 1e-3f); EXPECT_NEAR(h_c[3], 44.0f, 1e-3f); } TEST(CUDABlasTest, GemmComplexDouble) { GemmTester> t; t.Run(); } TEST(CUDABlasTest, GemmComplexFloat) { GemmTester> t; t.Run(); } TEST(CUDABlasTest, GemmHalf) { GemmTester t; t.Run(); } TEST(CUDABlasTest, GemmBFloat16) { GemmTester t; t.Run(); } // to_cublas_op 'C'/'c' path: C = A^H * I = A^H (conjugate-transpose of A). // // A stored col-major: col0={1+i,2+2i}, col1={3+3i,4+4i} // A^H stored col-major: col0={1-i,3-3i}, col1={2-2i,4-4i} TEST(CUDABlasTest, GemmComplexFloatConjTrans) { constexpr int64_t N = 2; using T = c10::complex; std::vector h_a = {T(1, 1), T(2, 2), T(3, 3), T(4, 4)}; std::vector h_b = {T(1, 0), T(0, 0), T(0, 0), T(1, 0)}; // identity std::vector h_c(N * N, T(0, 0)); float alpha = 1.0f; float beta = 0.0f; runOnDevice(h_a, h_b, &h_c, [&](T* d_a, T* d_b, T* d_c) { at::cuda::blas::gemm( 'C', 'N', N, N, N, alpha, d_a, N, d_b, N, beta, d_c, N); }); EXPECT_NEAR(h_c[0].real, 1.0f, 1e-3f); EXPECT_NEAR(h_c[0].imag, -1.0f, 1e-3f); EXPECT_NEAR(h_c[1].real, 3.0f, 1e-3f); EXPECT_NEAR(h_c[1].imag, -3.0f, 1e-3f); EXPECT_NEAR(h_c[2].real, 2.0f, 1e-3f); EXPECT_NEAR(h_c[2].imag, -2.0f, 1e-3f); EXPECT_NEAR(h_c[3].real, 4.0f, 1e-3f); EXPECT_NEAR(h_c[3].imag, -4.0f, 1e-3f); } // Same as above but uses lowercase 'c'/'n' to exercise that switch-case branch. TEST(CUDABlasTest, GemmComplexDoubleConjTransLower) { constexpr int64_t N = 2; using T = c10::complex; std::vector h_a = {T(1, 1), T(2, 2), T(3, 3), T(4, 4)}; std::vector h_b = {T(1, 0), T(0, 0), T(0, 0), T(1, 0)}; std::vector h_c(N * N, T(0, 0)); double alpha = 1.0; double beta = 0.0; runOnDevice(h_a, h_b, &h_c, [&](T* d_a, T* d_b, T* d_c) { at::cuda::blas::gemm( 'c', 'n', N, N, N, alpha, d_a, N, d_b, N, beta, d_c, N); }); EXPECT_NEAR(h_c[0].real, 1.0, 1e-6); EXPECT_NEAR(h_c[0].imag, -1.0, 1e-6); EXPECT_NEAR(h_c[1].real, 3.0, 1e-6); EXPECT_NEAR(h_c[1].imag, -3.0, 1e-6); } TEST(CUDABlasTest, GemmInvalidTransposeThrows) { constexpr int64_t N = 1; double alpha = 1.0; double beta = 0.0; EXPECT_THROW(at::cuda::blas::gemm('X', 'N', N, N, N, alpha, static_cast(nullptr), N, static_cast(nullptr), N, beta, static_cast(nullptr), N), std::exception); } #endif // PADDLE_WITH_CUDA