blob: 560e0b20fec95fff96f2df042d4b9f27256e9546 [file]
// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Copyright (C) 2026 Rasmus Munk Larsen <rmlarsen@gmail.com>
//
// This Source Code Form is subject to the terms of the Mozilla
// Public License v. 2.0. If a copy of the MPL was not distributed
// with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// SPDX-License-Identifier: MPL-2.0
// cuBLAS-specific support types. Generic CUDA runtime utilities (DeviceBuffer,
// cuda_data_type) live in GpuSupport.h.
#ifndef EIGEN_GPU_CUBLAS_SUPPORT_H
#define EIGEN_GPU_CUBLAS_SUPPORT_H
// IWYU pragma: private
#include "./InternalHeaderCheck.h"
#include "./GpuSupport.h"
#include <cublas_v2.h>
#include <cublasLt.h>
#include <cstring>
#include <utility>
namespace Eigen {
namespace gpu {
namespace internal {
#define EIGEN_CUBLAS_CHECK(expr) \
do { \
cublasStatus_t _s = (expr); \
eigen_assert(_s == CUBLAS_STATUS_SUCCESS && "cuBLAS call failed"); \
} while (0)
constexpr cublasOperation_t to_cublas_op(GpuOp op) {
switch (op) {
case GpuOp::Trans:
return CUBLAS_OP_T;
case GpuOp::ConjTrans:
return CUBLAS_OP_C;
default:
return CUBLAS_OP_N;
}
}
// cuBLAS 12.0 added `_64` entry points taking int64_t dimensions across the
// whole wrapped surface, so one wrapper body serves both:
// EIGEN_CUBLAS_FN(cublasSgemm)(..., to_blas_dim(m), ...) becomes
// cublasSgemm_64(..., m, ...) there (the plain `_64` names are cublas_v2.h
// aliases for the real `_v2_64` symbols) and cublasSgemm(..., to_blas_int(m),
// ...) on older cuBLAS. to_blas_dim is separate from to_blas_int, which must
// keep narrowing for the cuSOLVER and cuFFT sites that have no `_64` API.
#if defined(CUBLAS_VERSION) && CUBLAS_VERSION >= 120000
#define EIGEN_CUBLAS_FN(name) name##_64
inline int64_t to_blas_dim(int64_t v) { return v; }
#else
#define EIGEN_CUBLAS_FN(name) name
inline int to_blas_dim(int64_t v) { return to_blas_int(v); }
#endif
// RAII cuBLAS / cuBLASLt handles; the ownership flag supports handles borrowed from a gpu::Context.
struct CublasHandleDeleter {
bool owns = true;
void operator()(cublasHandle_t h) const noexcept {
if (owns && h) (void)cublasDestroy(h);
}
};
using UniqueCublasHandle = std::unique_ptr<std::remove_pointer_t<cublasHandle_t>, CublasHandleDeleter>;
struct CublasLtHandleDeleter {
void operator()(cublasLtHandle_t h) const noexcept {
if (h) (void)cublasLtDestroy(h);
}
};
using UniqueCublasLtHandle = std::unique_ptr<std::remove_pointer_t<cublasLtHandle_t>, CublasLtHandleDeleter>;
// cublasLtMatmul takes a compute type separate from the data type, which selects
// the precision policy:
// - Default: tensor-core algorithms via the cublasLtMatmul heuristics. For
// double, cuBLAS may use Ozaki emulation on sm_80+ tensor cores.
// - EIGEN_CUDA_TF32: TF32 for float (~2x faster, 10-bit mantissa).
// - EIGEN_NO_CUDA_TENSOR_OPS: pedantic compute types, no tensor cores, for
// bit-exact reproducibility.
// Real and complex of the same precision share a compute type, so the
// specializations below just dispatch to a precision tag.
namespace cuda_compute_type_detail {
#if defined(EIGEN_NO_CUDA_TENSOR_OPS)
constexpr cublasComputeType_t kFloat = CUBLAS_COMPUTE_32F_PEDANTIC;
constexpr cublasComputeType_t kDouble = CUBLAS_COMPUTE_64F_PEDANTIC;
#elif defined(EIGEN_CUDA_TF32)
constexpr cublasComputeType_t kFloat = CUBLAS_COMPUTE_32F_FAST_TF32;
constexpr cublasComputeType_t kDouble = CUBLAS_COMPUTE_64F;
#else
constexpr cublasComputeType_t kFloat = CUBLAS_COMPUTE_32F;
constexpr cublasComputeType_t kDouble = CUBLAS_COMPUTE_64F;
#endif
} // namespace cuda_compute_type_detail
template <typename Scalar>
struct cuda_compute_type;
template <>
struct cuda_compute_type<float> {
static constexpr cublasComputeType_t value = cuda_compute_type_detail::kFloat;
};
template <>
struct cuda_compute_type<double> {
static constexpr cublasComputeType_t value = cuda_compute_type_detail::kDouble;
};
template <>
struct cuda_compute_type<std::complex<float>> {
static constexpr cublasComputeType_t value = cuda_compute_type_detail::kFloat;
};
template <>
struct cuda_compute_type<std::complex<double>> {
static constexpr cublasComputeType_t value = cuda_compute_type_detail::kDouble;
};
#define EIGEN_CUBLASLT_CHECK(expr) \
do { \
cublasStatus_t _s = (expr); \
eigen_assert(_s == CUBLAS_STATUS_SUCCESS && "cuBLASLt call failed"); \
} while (0)
// Maximum workspace the heuristic is allowed to consider. This is a preference
// ceiling, not an allocation — actual allocation matches the selected algorithm.
// Override at compile time via EIGEN_CUDA_CUBLASLT_MAX_WORKSPACE_BYTES.
#ifndef EIGEN_CUDA_CUBLASLT_MAX_WORKSPACE_BYTES
#define EIGEN_CUDA_CUBLASLT_MAX_WORKSPACE_BYTES (32 * 1024 * 1024) // 32 MB
#endif
static constexpr size_t kCublasLtMaxWorkspaceBytes = EIGEN_CUDA_CUBLASLT_MAX_WORKSPACE_BYTES;
// Algorithm hint for the cublasGemmEx fallback path.
constexpr cublasGemmAlgo_t cuda_gemm_algo() {
#ifdef EIGEN_NO_CUDA_TENSOR_OPS
return CUBLAS_GEMM_DEFAULT;
#else
return CUBLAS_GEMM_DEFAULT_TENSOR_OP;
#endif
}
// Per-call descriptor creation and heuristic lookup cost 5-35% of total GEMM time
// for small and medium matrices, so plans are cached by shape. Typical workloads
// (CG iteration, chained solves) touch only 1-3 distinct shapes, hence the small
// capacity.
static constexpr std::size_t kCublasLtPlanCacheCapacity = 8;
struct CublasLtPlanKey {
int64_t m, n, k;
int64_t lda, ldb, ldc;
cudaDataType_t dtype;
cublasOperation_t transA, transB;
bool operator==(const CublasLtPlanKey& o) const {
return m == o.m && n == o.n && k == o.k && lda == o.lda && ldb == o.ldb && ldc == o.ldc && dtype == o.dtype &&
transA == o.transA && transB == o.transB;
}
};
struct CublasLtPlanKeyHash {
std::size_t operator()(const CublasLtPlanKey& k) const noexcept {
// boost-style hash_combine: mix each field into the rolling hash.
auto mix = [](std::size_t a, std::size_t b) { return a ^ (b + 0x9e3779b97f4a7c15ULL + (a << 6) + (a >> 2)); };
std::size_t r = std::hash<int64_t>{}(k.m);
r = mix(r, std::hash<int64_t>{}(k.n));
r = mix(r, std::hash<int64_t>{}(k.k));
r = mix(r, std::hash<int64_t>{}(k.lda));
r = mix(r, std::hash<int64_t>{}(k.ldb));
r = mix(r, std::hash<int64_t>{}(k.ldc));
r = mix(r, std::hash<int>{}(static_cast<int>(k.dtype)));
r = mix(r, std::hash<int>{}(static_cast<int>(k.transA)));
r = mix(r, std::hash<int>{}(static_cast<int>(k.transB)));
return r;
}
};
// A cached cuBLASLt matmul plan: the descriptor, three matrix layouts, and the
// heuristic-selected algorithm. Destruction releases all four cuBLASLt handles,
// so LruCache eviction is enough to manage entry lifetime. Move-only, since
// copying would alias the handles and double-destroy them.
class CublasLtPlanEntry {
public:
// If the heuristic returns no usable algorithm, use_cublaslt stays false and
// the caller takes the cublasGemmEx fallback path. `max_workspace_bytes` is the
// heuristic's ceiling — see gpu::Context::setCublasLtMaxWorkspaceBytes().
CublasLtPlanEntry(cublasLtHandle_t lt_handle, const CublasLtPlanKey& key, cublasComputeType_t compute,
cudaDataType_t alpha_type, std::size_t max_workspace_bytes) {
EIGEN_CUBLASLT_CHECK(cublasLtMatmulDescCreate(&matmul_desc, compute, alpha_type));
EIGEN_CUBLASLT_CHECK(
cublasLtMatmulDescSetAttribute(matmul_desc, CUBLASLT_MATMUL_DESC_TRANSA, &key.transA, sizeof(key.transA)));
EIGEN_CUBLASLT_CHECK(
cublasLtMatmulDescSetAttribute(matmul_desc, CUBLASLT_MATMUL_DESC_TRANSB, &key.transB, sizeof(key.transB)));
// Layout dimensions are the physical (rows, cols) of the column-major operand;
// the leading dimension is the actual stride between columns (lda/ldb/ldc),
// which may exceed the active row count (e.g., a thin view of a wider buffer).
const int64_t a_rows = (key.transA == CUBLAS_OP_N) ? key.m : key.k;
const int64_t a_cols = (key.transA == CUBLAS_OP_N) ? key.k : key.m;
const int64_t b_rows = (key.transB == CUBLAS_OP_N) ? key.k : key.n;
const int64_t b_cols = (key.transB == CUBLAS_OP_N) ? key.n : key.k;
EIGEN_CUBLASLT_CHECK(cublasLtMatrixLayoutCreate(&layout_A, key.dtype, a_rows, a_cols, key.lda));
EIGEN_CUBLASLT_CHECK(cublasLtMatrixLayoutCreate(&layout_B, key.dtype, b_rows, b_cols, key.ldb));
EIGEN_CUBLASLT_CHECK(cublasLtMatrixLayoutCreate(&layout_C, key.dtype, key.m, key.n, key.ldc));
cublasLtMatmulPreference_t preference = nullptr;
EIGEN_CUBLASLT_CHECK(cublasLtMatmulPreferenceCreate(&preference));
EIGEN_CUBLASLT_CHECK(cublasLtMatmulPreferenceSetAttribute(preference, CUBLASLT_MATMUL_PREF_MAX_WORKSPACE_BYTES,
&max_workspace_bytes, sizeof(max_workspace_bytes)));
cublasLtMatmulHeuristicResult_t result;
int returned_results = 0;
cublasStatus_t heuristic_status = cublasLtMatmulAlgoGetHeuristic(
lt_handle, matmul_desc, layout_A, layout_B, layout_C, layout_C, preference, 1, &result, &returned_results);
EIGEN_CUBLASLT_CHECK(cublasLtMatmulPreferenceDestroy(preference));
// cublasLtMatmulAlgoGetHeuristic can return CUBLAS_STATUS_SUCCESS overall while
// marking individual results NOT_SUPPORTED via result.state, so gate on both.
if (heuristic_status == CUBLAS_STATUS_SUCCESS && returned_results > 0 && result.state == CUBLAS_STATUS_SUCCESS) {
algo = result.algo;
workspace_size = result.workspaceSize;
use_cublaslt = true;
}
}
~CublasLtPlanEntry() { destroy(); }
CublasLtPlanEntry(const CublasLtPlanEntry&) = delete;
CublasLtPlanEntry& operator=(const CublasLtPlanEntry&) = delete;
CublasLtPlanEntry(CublasLtPlanEntry&& o) noexcept
: matmul_desc(o.matmul_desc),
layout_A(o.layout_A),
layout_B(o.layout_B),
layout_C(o.layout_C),
algo(o.algo),
workspace_size(o.workspace_size),
use_cublaslt(o.use_cublaslt) {
o.matmul_desc = nullptr;
o.layout_A = o.layout_B = o.layout_C = nullptr;
o.use_cublaslt = false;
}
CublasLtPlanEntry& operator=(CublasLtPlanEntry&& o) noexcept {
if (this != &o) {
destroy();
matmul_desc = o.matmul_desc;
layout_A = o.layout_A;
layout_B = o.layout_B;
layout_C = o.layout_C;
algo = o.algo;
workspace_size = o.workspace_size;
use_cublaslt = o.use_cublaslt;
o.matmul_desc = nullptr;
o.layout_A = o.layout_B = o.layout_C = nullptr;
o.use_cublaslt = false;
}
return *this;
}
cublasLtMatmulDesc_t matmul_desc = nullptr;
cublasLtMatrixLayout_t layout_A = nullptr;
cublasLtMatrixLayout_t layout_B = nullptr;
cublasLtMatrixLayout_t layout_C = nullptr;
cublasLtMatmulAlgo_t algo{};
std::size_t workspace_size = 0;
bool use_cublaslt = false;
private:
void destroy() noexcept {
if (layout_C) cublasLtMatrixLayoutDestroy(layout_C);
if (layout_B) cublasLtMatrixLayoutDestroy(layout_B);
if (layout_A) cublasLtMatrixLayoutDestroy(layout_A);
if (matmul_desc) cublasLtMatmulDescDestroy(matmul_desc);
}
};
using CublasLtPlanCache = Eigen::internal::LruCache<CublasLtPlanKey, CublasLtPlanEntry, CublasLtPlanKeyHash>;
// cublasLtMatmul with a shape-keyed plan cache, falling back to cublasGemmEx for
// shapes and types the cublasLt heuristic cannot serve. Dimensions are 64-bit on
// the cublasLt path. `workspace` grows monotonically to the selected algorithm's
// requirement; neither it nor `plan_cache` is thread-safe, so all calls sharing
// them must run on one stream.
template <typename Scalar>
void cublaslt_gemm(cublasLtHandle_t lt_handle, cublasHandle_t cublas_handle, cublasOperation_t transA,
cublasOperation_t transB, int64_t m, int64_t n, int64_t k, const Scalar* alpha, const Scalar* A,
int64_t lda, const Scalar* B, int64_t ldb, const Scalar* beta, Scalar* C, int64_t ldc,
DeviceBuffer& workspace, CublasLtPlanCache& plan_cache, std::size_t max_workspace_bytes,
cudaStream_t stream) {
constexpr cudaDataType_t dtype = cuda_data_type<Scalar>::value;
constexpr cublasComputeType_t compute = cuda_compute_type<Scalar>::value;
constexpr cudaDataType_t alpha_type = cuda_data_type<Scalar>::value;
// The key carries the leading dimensions so that strided views — e.g. SVD's
// thin VT/U slices — get distinct cache entries.
const CublasLtPlanKey key{m, n, k, lda, ldb, ldc, dtype, transA, transB};
CublasLtPlanEntry* entry = plan_cache.find(key);
if (!entry) {
entry = plan_cache.insert(key, CublasLtPlanEntry(lt_handle, key, compute, alpha_type, max_workspace_bytes));
}
if (entry->use_cublaslt) {
const size_t needed = entry->workspace_size;
if (needed > workspace.size()) {
// Sync only when freeing an existing buffer that may be in use.
if (workspace.get()) EIGEN_CUDA_RUNTIME_CHECK(cudaStreamSynchronize(stream));
workspace = DeviceBuffer(needed);
}
EIGEN_CUBLASLT_CHECK(cublasLtMatmul(lt_handle, entry->matmul_desc, alpha, A, entry->layout_A, B, entry->layout_B,
beta, C, entry->layout_C, C, entry->layout_C, &entry->algo, workspace.get(),
needed, stream));
} else {
// Fallback: cublasGemmEx for shapes/types that cublasLt cannot handle.
EIGEN_CUBLAS_CHECK(EIGEN_CUBLAS_FN(cublasGemmEx)(
cublas_handle, transA, transB, to_blas_dim(m), to_blas_dim(n), to_blas_dim(k), alpha, A, dtype,
to_blas_dim(lda), B, dtype, to_blas_dim(ldb), beta, C, dtype, to_blas_dim(ldc), compute, cuda_gemm_algo()));
}
}
// cuBLAS exposes one entry point per scalar type (Sgemm, Dgemm, ...). The
// cublasX* overloads below recover a type-generic interface over them.
inline cublasStatus_t cublasXgemm(cublasHandle_t h, cublasOperation_t transA, cublasOperation_t transB, int64_t m,
int64_t n, int64_t k, const float* alpha, const float* A, int64_t lda, const float* B,
int64_t ldb, const float* beta, float* C, int64_t ldc) {
return EIGEN_CUBLAS_FN(cublasSgemm)(h, transA, transB, to_blas_dim(m), to_blas_dim(n), to_blas_dim(k), alpha, A,
to_blas_dim(lda), B, to_blas_dim(ldb), beta, C, to_blas_dim(ldc));
}
inline cublasStatus_t cublasXgemm(cublasHandle_t h, cublasOperation_t transA, cublasOperation_t transB, int64_t m,
int64_t n, int64_t k, const double* alpha, const double* A, int64_t lda,
const double* B, int64_t ldb, const double* beta, double* C, int64_t ldc) {
return EIGEN_CUBLAS_FN(cublasDgemm)(h, transA, transB, to_blas_dim(m), to_blas_dim(n), to_blas_dim(k), alpha, A,
to_blas_dim(lda), B, to_blas_dim(ldb), beta, C, to_blas_dim(ldc));
}
static_assert(sizeof(cuComplex) == sizeof(std::complex<float>), "cuComplex and std::complex<float> layout mismatch");
static_assert(sizeof(cuDoubleComplex) == sizeof(std::complex<double>),
"cuDoubleComplex and std::complex<double> layout mismatch");
// Complex alpha/beta are type-punned from std::complex<T>* to
// cuComplex*/cuDoubleComplex*. reinterpret_cast violates strict aliasing here:
// once inlined, clang/MSVC no longer see a read through the original type and
// elide the caller's store, which segfaults. std::memcpy is the standard-blessed
// pun. Device array pointers (A, B, C) are never dereferenced by the host
// compiler, so reinterpret_cast is safe for them.
inline cublasStatus_t cublasXgemm(cublasHandle_t h, cublasOperation_t transA, cublasOperation_t transB, int64_t m,
int64_t n, int64_t k, const std::complex<float>* alpha, const std::complex<float>* A,
int64_t lda, const std::complex<float>* B, int64_t ldb,
const std::complex<float>* beta, std::complex<float>* C, int64_t ldc) {
cuComplex a, b;
std::memcpy(&a, alpha, sizeof(a));
std::memcpy(&b, beta, sizeof(b));
return EIGEN_CUBLAS_FN(cublasCgemm)(h, transA, transB, to_blas_dim(m), to_blas_dim(n), to_blas_dim(k), &a,
reinterpret_cast<const cuComplex*>(A), to_blas_dim(lda),
reinterpret_cast<const cuComplex*>(B), to_blas_dim(ldb), &b,
reinterpret_cast<cuComplex*>(C), to_blas_dim(ldc));
}
inline cublasStatus_t cublasXgemm(cublasHandle_t h, cublasOperation_t transA, cublasOperation_t transB, int64_t m,
int64_t n, int64_t k, const std::complex<double>* alpha,
const std::complex<double>* A, int64_t lda, const std::complex<double>* B,
int64_t ldb, const std::complex<double>* beta, std::complex<double>* C, int64_t ldc) {
cuDoubleComplex a, b;
std::memcpy(&a, alpha, sizeof(a));
std::memcpy(&b, beta, sizeof(b));
return EIGEN_CUBLAS_FN(cublasZgemm)(h, transA, transB, to_blas_dim(m), to_blas_dim(n), to_blas_dim(k), &a,
reinterpret_cast<const cuDoubleComplex*>(A), to_blas_dim(lda),
reinterpret_cast<const cuDoubleComplex*>(B), to_blas_dim(ldb), &b,
reinterpret_cast<cuDoubleComplex*>(C), to_blas_dim(ldc));
}
inline cublasStatus_t cublasXtrsm(cublasHandle_t h, cublasSideMode_t side, cublasFillMode_t uplo,
cublasOperation_t trans, cublasDiagType_t diag, int64_t m, int64_t n,
const float* alpha, const float* A, int64_t lda, float* B, int64_t ldb) {
return EIGEN_CUBLAS_FN(cublasStrsm)(h, side, uplo, trans, diag, to_blas_dim(m), to_blas_dim(n), alpha, A,
to_blas_dim(lda), B, to_blas_dim(ldb));
}
inline cublasStatus_t cublasXtrsm(cublasHandle_t h, cublasSideMode_t side, cublasFillMode_t uplo,
cublasOperation_t trans, cublasDiagType_t diag, int64_t m, int64_t n,
const double* alpha, const double* A, int64_t lda, double* B, int64_t ldb) {
return EIGEN_CUBLAS_FN(cublasDtrsm)(h, side, uplo, trans, diag, to_blas_dim(m), to_blas_dim(n), alpha, A,
to_blas_dim(lda), B, to_blas_dim(ldb));
}
inline cublasStatus_t cublasXtrsm(cublasHandle_t h, cublasSideMode_t side, cublasFillMode_t uplo,
cublasOperation_t trans, cublasDiagType_t diag, int64_t m, int64_t n,
const std::complex<float>* alpha, const std::complex<float>* A, int64_t lda,
std::complex<float>* B, int64_t ldb) {
cuComplex a;
std::memcpy(&a, alpha, sizeof(a));
return EIGEN_CUBLAS_FN(cublasCtrsm)(h, side, uplo, trans, diag, to_blas_dim(m), to_blas_dim(n), &a,
reinterpret_cast<const cuComplex*>(A), to_blas_dim(lda),
reinterpret_cast<cuComplex*>(B), to_blas_dim(ldb));
}
inline cublasStatus_t cublasXtrsm(cublasHandle_t h, cublasSideMode_t side, cublasFillMode_t uplo,
cublasOperation_t trans, cublasDiagType_t diag, int64_t m, int64_t n,
const std::complex<double>* alpha, const std::complex<double>* A, int64_t lda,
std::complex<double>* B, int64_t ldb) {
cuDoubleComplex a;
std::memcpy(&a, alpha, sizeof(a));
return EIGEN_CUBLAS_FN(cublasZtrsm)(h, side, uplo, trans, diag, to_blas_dim(m), to_blas_dim(n), &a,
reinterpret_cast<const cuDoubleComplex*>(A), to_blas_dim(lda),
reinterpret_cast<cuDoubleComplex*>(B), to_blas_dim(ldb));
}
// SYMM: real → symm, complex → hemm.
inline cublasStatus_t cublasXsymm(cublasHandle_t h, cublasSideMode_t side, cublasFillMode_t uplo, int64_t m, int64_t n,
const float* alpha, const float* A, int64_t lda, const float* B, int64_t ldb,
const float* beta, float* C, int64_t ldc) {
return EIGEN_CUBLAS_FN(cublasSsymm)(h, side, uplo, to_blas_dim(m), to_blas_dim(n), alpha, A, to_blas_dim(lda), B,
to_blas_dim(ldb), beta, C, to_blas_dim(ldc));
}
inline cublasStatus_t cublasXsymm(cublasHandle_t h, cublasSideMode_t side, cublasFillMode_t uplo, int64_t m, int64_t n,
const double* alpha, const double* A, int64_t lda, const double* B, int64_t ldb,
const double* beta, double* C, int64_t ldc) {
return EIGEN_CUBLAS_FN(cublasDsymm)(h, side, uplo, to_blas_dim(m), to_blas_dim(n), alpha, A, to_blas_dim(lda), B,
to_blas_dim(ldb), beta, C, to_blas_dim(ldc));
}
inline cublasStatus_t cublasXsymm(cublasHandle_t h, cublasSideMode_t side, cublasFillMode_t uplo, int64_t m, int64_t n,
const std::complex<float>* alpha, const std::complex<float>* A, int64_t lda,
const std::complex<float>* B, int64_t ldb, const std::complex<float>* beta,
std::complex<float>* C, int64_t ldc) {
cuComplex a, b;
std::memcpy(&a, alpha, sizeof(a));
std::memcpy(&b, beta, sizeof(b));
return EIGEN_CUBLAS_FN(cublasChemm)(
h, side, uplo, to_blas_dim(m), to_blas_dim(n), &a, reinterpret_cast<const cuComplex*>(A), to_blas_dim(lda),
reinterpret_cast<const cuComplex*>(B), to_blas_dim(ldb), &b, reinterpret_cast<cuComplex*>(C), to_blas_dim(ldc));
}
inline cublasStatus_t cublasXsymm(cublasHandle_t h, cublasSideMode_t side, cublasFillMode_t uplo, int64_t m, int64_t n,
const std::complex<double>* alpha, const std::complex<double>* A, int64_t lda,
const std::complex<double>* B, int64_t ldb, const std::complex<double>* beta,
std::complex<double>* C, int64_t ldc) {
cuDoubleComplex a, b;
std::memcpy(&a, alpha, sizeof(a));
std::memcpy(&b, beta, sizeof(b));
return EIGEN_CUBLAS_FN(cublasZhemm)(h, side, uplo, to_blas_dim(m), to_blas_dim(n), &a,
reinterpret_cast<const cuDoubleComplex*>(A), to_blas_dim(lda),
reinterpret_cast<const cuDoubleComplex*>(B), to_blas_dim(ldb), &b,
reinterpret_cast<cuDoubleComplex*>(C), to_blas_dim(ldc));
}
// GEAM: C = alpha * op(A) + beta * op(B).
inline cublasStatus_t cublasXgeam(cublasHandle_t h, cublasOperation_t transA, cublasOperation_t transB, int64_t m,
int64_t n, const float* alpha, const float* A, int64_t lda, const float* beta,
const float* B, int64_t ldb, float* C, int64_t ldc) {
return EIGEN_CUBLAS_FN(cublasSgeam)(h, transA, transB, to_blas_dim(m), to_blas_dim(n), alpha, A, to_blas_dim(lda),
beta, B, to_blas_dim(ldb), C, to_blas_dim(ldc));
}
inline cublasStatus_t cublasXgeam(cublasHandle_t h, cublasOperation_t transA, cublasOperation_t transB, int64_t m,
int64_t n, const double* alpha, const double* A, int64_t lda, const double* beta,
const double* B, int64_t ldb, double* C, int64_t ldc) {
return EIGEN_CUBLAS_FN(cublasDgeam)(h, transA, transB, to_blas_dim(m), to_blas_dim(n), alpha, A, to_blas_dim(lda),
beta, B, to_blas_dim(ldb), C, to_blas_dim(ldc));
}
inline cublasStatus_t cublasXgeam(cublasHandle_t h, cublasOperation_t transA, cublasOperation_t transB, int64_t m,
int64_t n, const std::complex<float>* alpha, const std::complex<float>* A,
int64_t lda, const std::complex<float>* beta, const std::complex<float>* B,
int64_t ldb, std::complex<float>* C, int64_t ldc) {
cuComplex a, b;
std::memcpy(&a, alpha, sizeof(a));
std::memcpy(&b, beta, sizeof(b));
return EIGEN_CUBLAS_FN(cublasCgeam)(
h, transA, transB, to_blas_dim(m), to_blas_dim(n), &a, reinterpret_cast<const cuComplex*>(A), to_blas_dim(lda),
&b, reinterpret_cast<const cuComplex*>(B), to_blas_dim(ldb), reinterpret_cast<cuComplex*>(C), to_blas_dim(ldc));
}
inline cublasStatus_t cublasXgeam(cublasHandle_t h, cublasOperation_t transA, cublasOperation_t transB, int64_t m,
int64_t n, const std::complex<double>* alpha, const std::complex<double>* A,
int64_t lda, const std::complex<double>* beta, const std::complex<double>* B,
int64_t ldb, std::complex<double>* C, int64_t ldc) {
cuDoubleComplex a, b;
std::memcpy(&a, alpha, sizeof(a));
std::memcpy(&b, beta, sizeof(b));
return EIGEN_CUBLAS_FN(cublasZgeam)(h, transA, transB, to_blas_dim(m), to_blas_dim(n), &a,
reinterpret_cast<const cuDoubleComplex*>(A), to_blas_dim(lda), &b,
reinterpret_cast<const cuDoubleComplex*>(B), to_blas_dim(ldb),
reinterpret_cast<cuDoubleComplex*>(C), to_blas_dim(ldc));
}
// SYRK: real → syrk, complex → herk.
inline cublasStatus_t cublasXsyrk(cublasHandle_t h, cublasFillMode_t uplo, cublasOperation_t trans, int64_t n,
int64_t k, const float* alpha, const float* A, int64_t lda, const float* beta,
float* C, int64_t ldc) {
return EIGEN_CUBLAS_FN(cublasSsyrk)(h, uplo, trans, to_blas_dim(n), to_blas_dim(k), alpha, A, to_blas_dim(lda), beta,
C, to_blas_dim(ldc));
}
inline cublasStatus_t cublasXsyrk(cublasHandle_t h, cublasFillMode_t uplo, cublasOperation_t trans, int64_t n,
int64_t k, const double* alpha, const double* A, int64_t lda, const double* beta,
double* C, int64_t ldc) {
return EIGEN_CUBLAS_FN(cublasDsyrk)(h, uplo, trans, to_blas_dim(n), to_blas_dim(k), alpha, A, to_blas_dim(lda), beta,
C, to_blas_dim(ldc));
}
inline cublasStatus_t cublasXsyrk(cublasHandle_t h, cublasFillMode_t uplo, cublasOperation_t trans, int64_t n,
int64_t k, const float* alpha, const std::complex<float>* A, int64_t lda,
const float* beta, std::complex<float>* C, int64_t ldc) {
return EIGEN_CUBLAS_FN(cublasCherk)(h, uplo, trans, to_blas_dim(n), to_blas_dim(k), alpha,
reinterpret_cast<const cuComplex*>(A), to_blas_dim(lda), beta,
reinterpret_cast<cuComplex*>(C), to_blas_dim(ldc));
}
inline cublasStatus_t cublasXsyrk(cublasHandle_t h, cublasFillMode_t uplo, cublasOperation_t trans, int64_t n,
int64_t k, const double* alpha, const std::complex<double>* A, int64_t lda,
const double* beta, std::complex<double>* C, int64_t ldc) {
return EIGEN_CUBLAS_FN(cublasZherk)(h, uplo, trans, to_blas_dim(n), to_blas_dim(k), alpha,
reinterpret_cast<const cuDoubleComplex*>(A), to_blas_dim(lda), beta,
reinterpret_cast<cuDoubleComplex*>(C), to_blas_dim(ldc));
}
// SCAL: x = alpha * x, with real alpha even for complex x (Csscal/Zdscal), as
// needed by the intrinsically real 1/n inverse-FFT scaling.
inline cublasStatus_t cublasXscal(cublasHandle_t h, int64_t n, const float* alpha, float* x, int64_t incx) {
return EIGEN_CUBLAS_FN(cublasSscal)(h, to_blas_dim(n), alpha, x, to_blas_dim(incx));
}
inline cublasStatus_t cublasXscal(cublasHandle_t h, int64_t n, const double* alpha, double* x, int64_t incx) {
return EIGEN_CUBLAS_FN(cublasDscal)(h, to_blas_dim(n), alpha, x, to_blas_dim(incx));
}
inline cublasStatus_t cublasXscal(cublasHandle_t h, int64_t n, const float* alpha, std::complex<float>* x,
int64_t incx) {
return EIGEN_CUBLAS_FN(cublasCsscal)(h, to_blas_dim(n), alpha, reinterpret_cast<cuComplex*>(x), to_blas_dim(incx));
}
inline cublasStatus_t cublasXscal(cublasHandle_t h, int64_t n, const double* alpha, std::complex<double>* x,
int64_t incx) {
return EIGEN_CUBLAS_FN(cublasZdscal)(h, to_blas_dim(n), alpha, reinterpret_cast<cuDoubleComplex*>(x),
to_blas_dim(incx));
}
// By-value alpha, for callers holding the scale as a scalar rather than a host
// pointer (e.g. inverse-FFT 1/n normalization).
inline cublasStatus_t cublasXscal(cublasHandle_t h, int64_t n, float alpha, float* x, int64_t incx) {
return EIGEN_CUBLAS_FN(cublasSscal)(h, to_blas_dim(n), &alpha, x, to_blas_dim(incx));
}
inline cublasStatus_t cublasXscal(cublasHandle_t h, int64_t n, double alpha, double* x, int64_t incx) {
return EIGEN_CUBLAS_FN(cublasDscal)(h, to_blas_dim(n), &alpha, x, to_blas_dim(incx));
}
inline cublasStatus_t cublasXscal(cublasHandle_t h, int64_t n, float alpha, std::complex<float>* x, int64_t incx) {
return EIGEN_CUBLAS_FN(cublasCsscal)(h, to_blas_dim(n), &alpha, reinterpret_cast<cuComplex*>(x), to_blas_dim(incx));
}
inline cublasStatus_t cublasXscal(cublasHandle_t h, int64_t n, double alpha, std::complex<double>* x, int64_t incx) {
return EIGEN_CUBLAS_FN(cublasZdscal)(h, to_blas_dim(n), &alpha, reinterpret_cast<cuDoubleComplex*>(x),
to_blas_dim(incx));
}
// DGMM: C = A * diag(x) (side=RIGHT) or C = diag(x) * A (side=LEFT), applying a
// diagonal scaling without materialising diag(x). cuBLAS documents C == A as
// safe.
inline cublasStatus_t cublasXdgmm(cublasHandle_t h, cublasSideMode_t side, int64_t m, int64_t n, const float* A,
int64_t lda, const float* x, int64_t incx, float* C, int64_t ldc) {
return EIGEN_CUBLAS_FN(cublasSdgmm)(h, side, to_blas_dim(m), to_blas_dim(n), A, to_blas_dim(lda), x,
to_blas_dim(incx), C, to_blas_dim(ldc));
}
inline cublasStatus_t cublasXdgmm(cublasHandle_t h, cublasSideMode_t side, int64_t m, int64_t n, const double* A,
int64_t lda, const double* x, int64_t incx, double* C, int64_t ldc) {
return EIGEN_CUBLAS_FN(cublasDdgmm)(h, side, to_blas_dim(m), to_blas_dim(n), A, to_blas_dim(lda), x,
to_blas_dim(incx), C, to_blas_dim(ldc));
}
inline cublasStatus_t cublasXdgmm(cublasHandle_t h, cublasSideMode_t side, int64_t m, int64_t n,
const std::complex<float>* A, int64_t lda, const std::complex<float>* x, int64_t incx,
std::complex<float>* C, int64_t ldc) {
return EIGEN_CUBLAS_FN(cublasCdgmm)(h, side, to_blas_dim(m), to_blas_dim(n), reinterpret_cast<const cuComplex*>(A),
to_blas_dim(lda), reinterpret_cast<const cuComplex*>(x), to_blas_dim(incx),
reinterpret_cast<cuComplex*>(C), to_blas_dim(ldc));
}
inline cublasStatus_t cublasXdgmm(cublasHandle_t h, cublasSideMode_t side, int64_t m, int64_t n,
const std::complex<double>* A, int64_t lda, const std::complex<double>* x,
int64_t incx, std::complex<double>* C, int64_t ldc) {
return EIGEN_CUBLAS_FN(cublasZdgmm)(h, side, to_blas_dim(m), to_blas_dim(n),
reinterpret_cast<const cuDoubleComplex*>(A), to_blas_dim(lda),
reinterpret_cast<const cuDoubleComplex*>(x), to_blas_dim(incx),
reinterpret_cast<cuDoubleComplex*>(C), to_blas_dim(ldc));
}
// The BLAS-1 wrappers below honour whichever pointer mode the caller set on the
// handle; under CUBLAS_POINTER_MODE_DEVICE the dot/nrm2 result pointers must
// address device memory.
// dot: result = x^T * y (real) or x^H * y (complex, conjugating x).
inline cublasStatus_t cublasXdot(cublasHandle_t h, int64_t n, const float* x, int64_t incx, const float* y,
int64_t incy, float* result) {
return EIGEN_CUBLAS_FN(cublasSdot)(h, to_blas_dim(n), x, to_blas_dim(incx), y, to_blas_dim(incy), result);
}
inline cublasStatus_t cublasXdot(cublasHandle_t h, int64_t n, const double* x, int64_t incx, const double* y,
int64_t incy, double* result) {
return EIGEN_CUBLAS_FN(cublasDdot)(h, to_blas_dim(n), x, to_blas_dim(incx), y, to_blas_dim(incy), result);
}
inline cublasStatus_t cublasXdot(cublasHandle_t h, int64_t n, const std::complex<float>* x, int64_t incx,
const std::complex<float>* y, int64_t incy, std::complex<float>* result) {
return EIGEN_CUBLAS_FN(cublasCdotc)(h, to_blas_dim(n), reinterpret_cast<const cuComplex*>(x), to_blas_dim(incx),
reinterpret_cast<const cuComplex*>(y), to_blas_dim(incy),
reinterpret_cast<cuComplex*>(result));
}
inline cublasStatus_t cublasXdot(cublasHandle_t h, int64_t n, const std::complex<double>* x, int64_t incx,
const std::complex<double>* y, int64_t incy, std::complex<double>* result) {
return EIGEN_CUBLAS_FN(cublasZdotc)(h, to_blas_dim(n), reinterpret_cast<const cuDoubleComplex*>(x), to_blas_dim(incx),
reinterpret_cast<const cuDoubleComplex*>(y), to_blas_dim(incy),
reinterpret_cast<cuDoubleComplex*>(result));
}
// nrm2: result = ||x||_2, always real.
inline cublasStatus_t cublasXnrm2(cublasHandle_t h, int64_t n, const float* x, int64_t incx, float* result) {
return EIGEN_CUBLAS_FN(cublasSnrm2)(h, to_blas_dim(n), x, to_blas_dim(incx), result);
}
inline cublasStatus_t cublasXnrm2(cublasHandle_t h, int64_t n, const double* x, int64_t incx, double* result) {
return EIGEN_CUBLAS_FN(cublasDnrm2)(h, to_blas_dim(n), x, to_blas_dim(incx), result);
}
inline cublasStatus_t cublasXnrm2(cublasHandle_t h, int64_t n, const std::complex<float>* x, int64_t incx,
float* result) {
return EIGEN_CUBLAS_FN(cublasScnrm2)(h, to_blas_dim(n), reinterpret_cast<const cuComplex*>(x), to_blas_dim(incx),
result);
}
inline cublasStatus_t cublasXnrm2(cublasHandle_t h, int64_t n, const std::complex<double>* x, int64_t incx,
double* result) {
return EIGEN_CUBLAS_FN(cublasDznrm2)(h, to_blas_dim(n), reinterpret_cast<const cuDoubleComplex*>(x),
to_blas_dim(incx), result);
}
// axpy: y += alpha * x.
inline cublasStatus_t cublasXaxpy(cublasHandle_t h, int64_t n, const float* alpha, const float* x, int64_t incx,
float* y, int64_t incy) {
return EIGEN_CUBLAS_FN(cublasSaxpy)(h, to_blas_dim(n), alpha, x, to_blas_dim(incx), y, to_blas_dim(incy));
}
inline cublasStatus_t cublasXaxpy(cublasHandle_t h, int64_t n, const double* alpha, const double* x, int64_t incx,
double* y, int64_t incy) {
return EIGEN_CUBLAS_FN(cublasDaxpy)(h, to_blas_dim(n), alpha, x, to_blas_dim(incx), y, to_blas_dim(incy));
}
inline cublasStatus_t cublasXaxpy(cublasHandle_t h, int64_t n, const std::complex<float>* alpha,
const std::complex<float>* x, int64_t incx, std::complex<float>* y, int64_t incy) {
cuComplex a;
std::memcpy(&a, alpha, sizeof(a));
return EIGEN_CUBLAS_FN(cublasCaxpy)(h, to_blas_dim(n), &a, reinterpret_cast<const cuComplex*>(x), to_blas_dim(incx),
reinterpret_cast<cuComplex*>(y), to_blas_dim(incy));
}
inline cublasStatus_t cublasXaxpy(cublasHandle_t h, int64_t n, const std::complex<double>* alpha,
const std::complex<double>* x, int64_t incx, std::complex<double>* y, int64_t incy) {
cuDoubleComplex a;
std::memcpy(&a, alpha, sizeof(a));
return EIGEN_CUBLAS_FN(cublasZaxpy)(h, to_blas_dim(n), &a, reinterpret_cast<const cuDoubleComplex*>(x),
to_blas_dim(incx), reinterpret_cast<cuDoubleComplex*>(y), to_blas_dim(incy));
}
// SCAL with complex alpha (Cscal/Zscal); the real-alpha forms are above.
inline cublasStatus_t cublasXscal(cublasHandle_t h, int64_t n, const std::complex<float>* alpha, std::complex<float>* x,
int64_t incx) {
cuComplex a;
std::memcpy(&a, alpha, sizeof(a));
return EIGEN_CUBLAS_FN(cublasCscal)(h, to_blas_dim(n), &a, reinterpret_cast<cuComplex*>(x), to_blas_dim(incx));
}
inline cublasStatus_t cublasXscal(cublasHandle_t h, int64_t n, const std::complex<double>* alpha,
std::complex<double>* x, int64_t incx) {
cuDoubleComplex a;
std::memcpy(&a, alpha, sizeof(a));
return EIGEN_CUBLAS_FN(cublasZscal)(h, to_blas_dim(n), &a, reinterpret_cast<cuDoubleComplex*>(x), to_blas_dim(incx));
}
// copy: y = x.
inline cublasStatus_t cublasXcopy(cublasHandle_t h, int64_t n, const float* x, int64_t incx, float* y, int64_t incy) {
return EIGEN_CUBLAS_FN(cublasScopy)(h, to_blas_dim(n), x, to_blas_dim(incx), y, to_blas_dim(incy));
}
inline cublasStatus_t cublasXcopy(cublasHandle_t h, int64_t n, const double* x, int64_t incx, double* y, int64_t incy) {
return EIGEN_CUBLAS_FN(cublasDcopy)(h, to_blas_dim(n), x, to_blas_dim(incx), y, to_blas_dim(incy));
}
inline cublasStatus_t cublasXcopy(cublasHandle_t h, int64_t n, const std::complex<float>* x, int64_t incx,
std::complex<float>* y, int64_t incy) {
return EIGEN_CUBLAS_FN(cublasCcopy)(h, to_blas_dim(n), reinterpret_cast<const cuComplex*>(x), to_blas_dim(incx),
reinterpret_cast<cuComplex*>(y), to_blas_dim(incy));
}
inline cublasStatus_t cublasXcopy(cublasHandle_t h, int64_t n, const std::complex<double>* x, int64_t incx,
std::complex<double>* y, int64_t incy) {
return EIGEN_CUBLAS_FN(cublasZcopy)(h, to_blas_dim(n), reinterpret_cast<const cuDoubleComplex*>(x), to_blas_dim(incx),
reinterpret_cast<cuDoubleComplex*>(y), to_blas_dim(incy));
}
} // namespace internal
} // namespace gpu
} // namespace Eigen
#endif // EIGEN_GPU_CUBLAS_SUPPORT_H