chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:40:42 +08:00
commit e25996e7db
15472 changed files with 3536181 additions and 0 deletions
@@ -0,0 +1,84 @@
/* Copyright (c) 2022 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. */
#pragma once
#include <stdint.h>
#include "paddle/common/ddim.h"
namespace phi {
namespace funcs {
namespace sparse {
inline const DDim InferDenseDims(const DDim& x_dims,
const int64_t sparse_dim,
const int64_t non_zero_num) {
auto dense_dim = x_dims.size() - sparse_dim;
DDim values_dims;
if (dense_dim > 0) {
std::vector<int64_t> dense_dim_vec(dense_dim + 1);
dense_dim_vec[0] = non_zero_num;
memcpy(&dense_dim_vec[1],
x_dims.Get() + sparse_dim,
dense_dim * sizeof(x_dims[0]));
values_dims = make_ddim(dense_dim_vec);
} else {
values_dims = make_ddim({non_zero_num});
}
return values_dims;
}
template <typename IntT>
inline const IntT HOSTDEVICE IndicesToIndex(const IntT* indices,
const IntT* sparse_offsets,
const int64_t non_zero_num,
const int64_t sparse_dim,
const int i) {
IntT index = 0;
for (IntT j = 0; j < sparse_dim; j++) {
index += indices[j * non_zero_num + i] * sparse_offsets[j];
}
return index;
}
template <typename IntT>
inline void HOSTDEVICE FlattenIndices(const IntT* indices,
const IntT* sparse_offsets,
const int64_t non_zero_num,
const int64_t sparse_dim,
const int start,
const int stride,
IntT* out) {
for (int i = start; i < non_zero_num; i += stride) {
out[i] =
IndicesToIndex(indices, sparse_offsets, non_zero_num, sparse_dim, i);
}
}
// 1. indices.dims().size() == 2
template <typename IntT>
inline void CalcOffsetsPerDim(const DDim& dims,
const int64_t sparse_dim,
std::vector<IntT>* offsets) {
IntT offset = 1;
for (IntT i = sparse_dim - 1; i >= 0; i--) {
(*offsets)[i] = offset;
offset *= dims[i];
}
}
} // namespace sparse
} // namespace funcs
} // namespace phi
@@ -0,0 +1,264 @@
/* Copyright (c) 2022 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. */
#pragma once
#include "paddle/common/ddim.h"
#include "paddle/phi/core/kmap_cache.h"
#include "paddle/phi/core/tensor_utils.h"
#include "paddle/phi/kernels/empty_kernel.h"
#if !defined(PADDLE_WITH_CUDA) || !defined(PADDLE_WITH_CUSTOM_DEVICE)
#include "paddle/phi/kernels/funcs/sparse/convolution_blas.h"
#endif
namespace phi {
namespace funcs {
namespace sparse {
struct Dims4D {
int dims[4];
Dims4D(const int batch, const int x, const int y, const int z) {
dims[0] = batch;
dims[1] = z;
dims[2] = y;
dims[3] = x;
}
HOSTDEVICE const int& operator[](int i) const { return dims[i]; }
};
// Judge whether the current position x is in (lower, upper)
template <typename IntT = int>
inline HOSTDEVICE bool Check(const IntT& x,
const int& kx,
const int& pad,
const int& stride,
const int dilation,
const int kdim,
const int xdim) {
const IntT lower = x - dilation * kx + pad;
const IntT upper = x + (kdim - kx - 1) * dilation - pad;
return (lower >= 0 && lower % stride == 0 && upper < xdim);
}
// Check whether the current position(x, y, z) is legal:
// Judge the minimum and maximum values at each latitude
template <typename IntT = int>
inline HOSTDEVICE bool Check(const Dims4D& dims,
const Dims4D& kernel_dims,
const Dims4D& paddings,
const Dims4D& dilations,
const Dims4D& strides,
const IntT x,
const IntT y,
const IntT z,
const int kx,
const int ky,
const int kz) {
bool x_valid = Check(
x, kx, paddings[3], strides[3], dilations[3], kernel_dims[3], dims[3]);
bool y_valid = Check(
y, ky, paddings[2], strides[2], dilations[2], kernel_dims[2], dims[2]);
bool z_valid = Check(
z, kz, paddings[1], strides[1], dilations[1], kernel_dims[1], dims[1]);
return (x_valid && y_valid && z_valid);
}
template <typename Dim, typename IntT = int>
inline HOSTDEVICE IntT PointToIndex(const IntT& batch,
const IntT& x,
const IntT& y,
const IntT& z,
const Dim& dims) {
return batch * dims[1] * dims[2] * dims[3] + z * dims[2] * dims[3] +
y * dims[3] + x;
}
// TODO(zhangkaihuo): use division and multiply to optimize
// modulo operation
template <typename Dim, typename IntT = int>
inline HOSTDEVICE void IndexToPoint(
const IntT index, const Dim& dims, IntT* batch, IntT* x, IntT* y, IntT* z) {
IntT n = index;
*x = n % dims[3];
n /= dims[3];
*y = n % dims[2];
n /= dims[2];
*z = n % dims[1];
n /= dims[1];
*batch = n;
}
inline void GetOutShape(const DDim& x_dims,
const std::vector<int>& kernel_sizes,
const std::vector<int>& paddings,
const std::vector<int>& dilations,
const std::vector<int>& strides,
DDim* out_dims) {
const bool is2D = out_dims->size() == 4 ? true : false;
if (is2D) {
PADDLE_ENFORCE_EQ(x_dims.size(),
4,
common::errors::InvalidArgument(
"the shape of x should be (N, H, W, C)"));
PADDLE_ENFORCE_EQ(kernel_sizes.size(),
4,
common::errors::InvalidArgument(
"the shape of kernel should be (H, W, C, OC)"));
// infer out shape
(*out_dims)[0] = x_dims[0];
(*out_dims)[3] = kernel_sizes[3];
for (int i = 1; i < 3; i++) {
(*out_dims)[i] = (x_dims[i] + 2 * paddings[i - 1] -
dilations[i - 1] * (kernel_sizes[i - 1] - 1) - 1) /
strides[i - 1] +
1;
}
} else {
PADDLE_ENFORCE_EQ(x_dims.size(),
5,
common::errors::InvalidArgument(
"the shape of x should be (N, D, H, W, C)"));
PADDLE_ENFORCE_EQ(kernel_sizes.size(),
5,
common::errors::InvalidArgument(
"the shape of kernel should be (D, H, W, C, OC)"));
// infer out shape
(*out_dims)[0] = x_dims[0];
(*out_dims)[4] = kernel_sizes[4];
for (int i = 1; i < 4; i++) {
(*out_dims)[i] = (x_dims[i] + 2 * paddings[i - 1] -
dilations[i - 1] * (kernel_sizes[i - 1] - 1) - 1) /
strides[i - 1] +
1;
}
}
}
inline void ResetSubmKernelSizeAndStrides(const DDim& kernel_dims,
std::vector<int>* paddings,
std::vector<int>* strides) {
for (uint64_t i = 0; i < paddings->size(); i++) {
(*paddings)[i] = kernel_dims[i] / 2;
(*strides)[i] = 1;
}
}
inline const std::vector<int> PoolResetKernel(
const std::vector<int>& kernel_sizes,
const int in_channels,
const int out_channels) {
std::vector<int> res(kernel_sizes);
res.resize(5);
res[3] = in_channels;
res[4] = out_channels;
return res;
}
template <typename T>
inline void PrefixSum(const T* counter, T* offsets, const int n) {
T offset = 0;
for (int i = 0; i < n; i++) {
offsets[i] = offset;
offset += counter[i];
}
offsets[n] = offset;
}
template <typename IntT>
inline const IntT* GetRulebookPtr(const SparseCooTensor& coo,
const DenseTensor& rulebook,
const std::string& key,
int* rulebook_len) {
if (!key.empty()) {
const auto* indices_pairs = coo.IndicesPairs(key);
if (indices_pairs != nullptr) {
const DenseTensor& tmp_rulebook = indices_pairs->first;
*rulebook_len = tmp_rulebook.dims()[1];
return tmp_rulebook.data<IntT>();
}
}
*rulebook_len = rulebook.dims()[1];
return rulebook.data<IntT>();
}
inline const int* GetCounterPtr(const SparseCooTensor& coo,
const DenseTensor& counter,
const std::string& key) {
if (!key.empty()) {
const auto* indices_pairs = coo.IndicesPairs(key);
if (indices_pairs != nullptr) {
return indices_pairs->second.data<int>();
}
}
return counter.data<int>();
}
template <typename T, typename IntT, typename Context>
inline const IntT* PrepareSubm(const Context& dev_ctx,
const SparseCooTensor& x,
const std::string& key,
const DDim& out_dims,
SparseCooTensor* out,
int* counter,
int* offsets,
int* rulebook_len,
bool* need_product_rulebook) {
const auto* indices_pairs = x.IndicesPairs(key);
if (indices_pairs != nullptr) {
*need_product_rulebook = false;
const DenseTensor& rulebook = indices_pairs->first;
const int64_t counter_size = indices_pairs->second.numel();
memcpy(
counter, indices_pairs->second.data<int>(), counter_size * sizeof(int));
out->SetIndicesDict(x.GetIndicesDict());
*rulebook_len = rulebook.dims()[1];
DenseTensor out_indices = EmptyLike<IntT>(dev_ctx, x.non_zero_indices());
DenseTensor out_values = EmptyLike<T>(dev_ctx, x.non_zero_elements());
phi::Copy(
dev_ctx, x.non_zero_indices(), dev_ctx.GetPlace(), false, &out_indices);
out->SetMember(out_indices, out_values, out_dims, false);
PrefixSum<int>(counter, offsets, counter_size);
return rulebook.data<IntT>();
}
return nullptr;
}
template <typename Context>
inline void SaveToTable(const Context& dev_ctx,
const SparseCooTensor& x,
const std::string& key,
const DenseTensor& in_rulebook,
const DenseTensor& h_counter,
SparseCooTensor* out,
DenseTensor* out_rulebook,
DenseTensor* counter) {
out->SetIndicesDict(x.GetIndicesDict());
if (!key.empty()) {
out->SaveIndicesPairs(key, std::make_pair(in_rulebook, h_counter));
} else {
*out_rulebook = in_rulebook;
counter->Resize({h_counter.numel()});
int* counter_ptr = dev_ctx.template HostAlloc<int>(counter);
memcpy(counter_ptr, h_counter.data<int>(), h_counter.numel() * sizeof(int));
}
}
} // namespace sparse
} // namespace funcs
} // namespace phi
@@ -0,0 +1,70 @@
/* Copyright (c) 2022 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. */
#pragma once
#include "paddle/common/ddim.h"
#include "paddle/phi/core/kmap_cache.h"
#include "paddle/phi/core/tensor_utils.h"
#include "paddle/phi/kernels/empty_kernel.h"
#include "paddle/phi/kernels/funcs/blas/blas.h"
namespace phi {
namespace funcs {
namespace sparse {
template <typename T, typename Context>
inline void SubmPreProcess(const Context& dev_ctx,
const SparseCooTensor& x,
const DenseTensor& kernel,
const DenseTensor& out_grad,
const int in_channels,
const int out_channels,
const int half_kernel_size,
DenseTensor* kernel_grad,
DenseTensor* x_grad) {
auto blas = funcs::GetBlas<Context, T>(dev_ctx);
const bool is_params_freezing = kernel_grad == nullptr;
if (!is_params_freezing) {
T* d_kernel_ptr = kernel_grad->data<T>();
blas.GEMM(CblasTrans,
CblasNoTrans,
x.non_zero_elements().dims()[1],
out_grad.dims()[1],
x.non_zero_elements().dims()[0],
static_cast<T>(1),
x.non_zero_elements().data<T>(),
out_grad.data<T>(),
static_cast<T>(0),
d_kernel_ptr + half_kernel_size * in_channels * out_channels);
}
// call gemm: d_x = out_grad * transpose(kernel)
// (n, out_channels) * (out_channels, in_channels)
T* x_grad_ptr = x_grad->data<T>();
blas.GEMM(CblasNoTrans,
CblasTrans,
out_grad.dims()[0],
in_channels,
out_grad.dims()[1],
static_cast<T>(1),
out_grad.data<T>(),
kernel.data<T>() + half_kernel_size * in_channels * out_channels,
static_cast<T>(0),
x_grad_ptr);
}
} // namespace sparse
} // namespace funcs
} // namespace phi
@@ -0,0 +1,61 @@
/* Copyright (c) 2022 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. */
#pragma once
#include "paddle/phi/kernels/funcs/sparse/flatten_indices.h"
namespace phi {
namespace funcs {
namespace sparse {
template <typename IntT>
__global__ void FlattenIndicesKernel(const IntT* indices,
const IntT* sparse_offsets,
const int64_t non_zero_num,
const int64_t sparse_dim,
IntT* out) {
int64_t tid =
static_cast<int64_t>(threadIdx.x) +
static_cast<int64_t>(blockIdx.x) * static_cast<int64_t>(blockDim.x);
funcs::sparse::FlattenIndices<IntT>(indices,
sparse_offsets,
non_zero_num,
sparse_dim,
tid,
gridDim.x * blockDim.x,
out);
}
template <typename IntT>
__global__ void IndexToCoordinateKernel(const IntT* index,
const Dim<DDim::kMaxRank> dims,
const int64_t non_zero_num,
const int64_t sparse_dim,
IntT* indices) {
int64_t tid =
static_cast<int64_t>(threadIdx.x) +
static_cast<int64_t>(blockIdx.x) * static_cast<int64_t>(blockDim.x);
IndexToCoordinate(index,
dims,
non_zero_num,
sparse_dim,
tid,
gridDim.x * blockDim.x,
indices);
}
} // namespace sparse
} // namespace funcs
} // namespace phi
@@ -0,0 +1,94 @@
/* Copyright (c) 2022 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. */
#pragma once
#include <stdint.h>
#include "paddle/common/ddim.h"
namespace phi {
namespace funcs {
namespace sparse {
template <typename IntT>
inline const IntT HOSTDEVICE CoordinateToIndex(const IntT* indices,
const IntT* sparse_offsets,
const int64_t non_zero_num,
const int64_t sparse_dim,
const int i) {
IntT index = 0;
for (IntT j = 0; j < sparse_dim; j++) {
index += indices[j * non_zero_num + i] * sparse_offsets[j];
}
return index;
}
template <typename IntT>
inline void HOSTDEVICE FlattenIndices(const IntT* indices,
const IntT* sparse_offsets,
const int64_t non_zero_num,
const int64_t sparse_dim,
const int64_t start,
const int64_t stride,
IntT* out) {
for (int64_t i = start; i < non_zero_num; i += stride) {
out[i] =
CoordinateToIndex(indices, sparse_offsets, non_zero_num, sparse_dim, i);
}
}
// 1. indices.dims().size() == 2
template <typename IntT>
inline void CalcOffsetsPerDim(const DDim& dims,
const int64_t sparse_dim,
IntT* offsets) {
IntT offset = 1;
for (IntT i = sparse_dim - 1; i >= 0; i--) {
offsets[i] = offset;
offset *= dims[i];
}
}
template <typename IntT>
inline void HOSTDEVICE IndexToCoordinate(const IntT index,
const Dim<DDim::kMaxRank>& dims,
const int64_t non_zero_num,
const int64_t sparse_dim,
const int indices_offset,
IntT* indices) {
IntT tmp_index = index;
for (int j = sparse_dim - 1; j >= 0; j--) {
indices[j * non_zero_num + indices_offset] = tmp_index % dims[j];
tmp_index /= dims[j];
}
}
template <typename IntT>
inline void HOSTDEVICE IndexToCoordinate(const IntT* index,
const Dim<DDim::kMaxRank>& dims,
const int64_t non_zero_num,
const int64_t sparse_dim,
const int64_t start,
const int64_t stride,
IntT* indices) {
for (int64_t i = start; i < non_zero_num; i += stride) {
IntT tmp_index = index[i];
IndexToCoordinate(tmp_index, dims, non_zero_num, sparse_dim, i, indices);
}
}
} // namespace sparse
} // namespace funcs
} // namespace phi
@@ -0,0 +1,162 @@
/* Copyright (c) 2022 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. */
#pragma once
#include "paddle/phi/backends/gpu/gpu_context.h"
#include "paddle/phi/backends/gpu/gpu_launch_config.h"
#include "paddle/phi/kernels/funcs/aligned_vector.h"
#define VecBytes 16
namespace phi {
namespace funcs {
namespace sparse {
/**
* brief: scatter add
* input: the inputs
* unique_value: refer to UpdateIndexKernel notes
* out_index: the output feature index
* non_zero_num: the number of output features
* rulebook_len: the length of rulebook
* channels: the output channel size
* out: the outputs
**/
template <typename T, int VecSize>
__global__ void ScatterKernel(const T* input,
const int* unique_value,
const int* out_index,
const int non_zero_num,
const int rulebook_len,
const int channels,
T* out) {
int64_t tid =
static_cast<int64_t>(threadIdx.x) +
static_cast<int64_t>(blockIdx.x) * static_cast<int64_t>(blockDim.x);
const int vec_channels = channels / VecSize;
using LoadT = AlignedVector<T, VecSize>;
using StoreT = AlignedVector<T, VecSize>;
for (int i = tid; i < non_zero_num * vec_channels;
i += gridDim.x * blockDim.x) {
int indices_i = i / vec_channels;
int channels_i = i - indices_i * vec_channels;
int start = unique_value[indices_i];
int end = indices_i == non_zero_num - 1 ? rulebook_len
: unique_value[indices_i + 1];
// max(end-start) = kernel_size
StoreT sums = {static_cast<T>(0)};
for (int j = start; j < end; j++) {
const int out_feature_i = out_index[j];
LoadT vec_in;
Load<T, VecSize>(input + out_feature_i * channels + channels_i * VecSize,
&vec_in);
#pragma unroll
for (int k = 0; k < VecSize; k++) {
sums[k] += vec_in[k];
}
}
Store<T, VecSize>(sums, out + indices_i * channels + channels_i * VecSize);
}
}
// scatter's index has been grouped in advance
// index_counts record the count of each group
// index_groups save the index of each group
template <typename T, int VecSize>
__global__ void ScatterKernelV2(const T* input,
const int* index_counts,
const int* index_groups,
const int non_zero_num,
const int kernel_size,
const int channels,
const int buffer_counts,
T* out) {
int64_t tid =
static_cast<int64_t>(threadIdx.x) +
static_cast<int64_t>(blockIdx.x) * static_cast<int64_t>(blockDim.x);
const int vec_channels = channels / VecSize;
using LoadT = AlignedVector<T, VecSize>;
using StoreT = AlignedVector<T, VecSize>;
for (int i = tid; i < non_zero_num * vec_channels;
i += gridDim.x * blockDim.x) {
int indices_i = i / vec_channels;
int channels_i = i - indices_i * vec_channels;
StoreT sums = {static_cast<T>(0)};
Load<T, VecSize>(out + indices_i * channels + channels_i * VecSize, &sums);
for (int it = 0; it < buffer_counts; it++) {
int len = index_counts[indices_i + it * non_zero_num];
const int group_offset = it * kernel_size * non_zero_num;
for (int j = 0; j < len; j++) {
const int out_feature_i =
index_groups[indices_i * kernel_size + j + group_offset];
LoadT vec_in;
Load<T, VecSize>(
input + out_feature_i * channels + channels_i * VecSize, &vec_in);
#pragma unroll
for (int k = 0; k < VecSize; k++) {
sums[k] += vec_in[k];
}
}
}
Store<T, VecSize>(sums, out + indices_i * channels + channels_i * VecSize);
}
}
template <typename T>
void ScatterV2(const GPUContext& dev_ctx,
const T* input,
const int* index_counts,
const int* index_groups,
const int non_zero_num,
const int kernel_size,
const int channels,
const int buffer_counts,
T* output) {
const int VecSize = VecBytes / sizeof(T);
if (channels % VecSize == 0) {
auto config = phi::backends::gpu::GetGpuLaunchConfig1D(
dev_ctx, non_zero_num * channels / VecSize, 1);
ScatterKernelV2<T, VecSize><<<config.block_per_grid.x,
config.thread_per_block.x,
0,
dev_ctx.stream()>>>(input,
index_counts,
index_groups,
non_zero_num,
kernel_size,
channels,
buffer_counts,
output);
} else {
auto config = phi::backends::gpu::GetGpuLaunchConfig1D(
dev_ctx, non_zero_num * channels, 1);
ScatterKernelV2<T, 1><<<config.block_per_grid.x,
config.thread_per_block.x,
0,
dev_ctx.stream()>>>(input,
index_counts,
index_groups,
non_zero_num,
kernel_size,
channels,
buffer_counts,
output);
}
}
} // namespace sparse
} // namespace funcs
} // namespace phi
@@ -0,0 +1,203 @@
/* Copyright (c) 2023 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. */
#pragma once
#include "paddle/phi/backends/gpu/cuda/cuda_graph_with_memory_pool.h"
namespace phi {
namespace funcs {
namespace sparse {
/* Given the indices of a sparse tensor, return a vector of offsets
for the entries in the equivalent dense tensor. */
template <typename IntT, typename Context>
inline DenseTensor GetOffsets(const Context& dev_ctx,
const DenseTensor& indices,
const std::vector<IntT>& sizes,
const IntT dim) {
#ifdef __HIPCC__
const auto& policy = thrust::hip::par.on(dev_ctx.stream());
#else
const auto& policy = thrust::cuda::par.on(dev_ctx.stream());
#endif
auto ndim = indices.dims()[0];
auto nnz = indices.dims()[1];
std::vector<IntT> host_strides(ndim, 1);
if (ndim > 1) {
for (IntT i = ndim - 2; i >= 0; i--) {
host_strides[i] = host_strides[i + 1] * (i + 1 == dim ? 1 : sizes[i + 1]);
}
}
const IntArray strides_shape(vectorize<IntT>(indices.dims()));
DenseTensor strides = Empty<IntT>(dev_ctx, strides_shape);
auto strides_ptr = strides.data<IntT>();
#if defined(__NVCC__) || defined(__HIPCC__)
const IntT* stable_st =
phi::backends::gpu::RestoreHostMemIfCapturingCUDAGraph(
host_strides.data(), host_strides.size());
#else
const IntT* stable_st = host_strides.data();
#endif
memory_utils::Copy(dev_ctx.GetPlace(),
strides_ptr,
CPUPlace(),
stable_st,
sizeof(IntT) * host_strides.size(),
dev_ctx.stream());
DenseTensor offsets = Empty<IntT>(dev_ctx, {nnz});
auto indices_ptr = indices.data<IntT>();
thrust::transform(
policy,
thrust::make_counting_iterator(IntT(0)),
thrust::make_counting_iterator(IntT(nnz)),
thrust::device_ptr<IntT>(offsets.data<IntT>()),
[strides_ptr, indices_ptr, nnz, dim, ndim] __device__(IntT x) {
IntT pool_index = 0;
for (IntT j = 0; j < ndim; j++) {
if (j != dim) {
auto indice_cur_ptr = indices_ptr + j * nnz + x;
auto stride = strides_ptr[j];
pool_index += stride * (*indice_cur_ptr);
}
}
return pool_index;
});
return offsets;
}
/* Return pools of indices that align with the given dimension and the
corresponding max values for each pool. */
template <typename T,
typename IntT,
typename Context,
bool requireMxRows = true>
std::tuple<DenseTensor, DenseTensor, DenseTensor, DenseTensor> ComputePoolMax(
const Context& dev_ctx,
const DenseTensor& indices,
const DenseTensor& values,
const std::vector<IntT>& sizes,
IntT nvalues,
const IntT dim) {
#ifdef __HIPCC__
const auto& policy = thrust::hip::par.on(dev_ctx.stream());
#else
const auto& policy = thrust::cuda::par.on(dev_ctx.stream());
#endif
using thrust_ptr = thrust::device_ptr<IntT>;
auto nnz = indices.dims()[1];
DenseTensor offsets =
funcs::sparse::GetOffsets<IntT, Context>(dev_ctx, indices, sizes, dim);
auto offsets_ptr = offsets.data<IntT>();
DenseTensor sorted_indices = Empty<IntT>(dev_ctx, {nnz});
thrust_ptr sorted_indices_thrust_ptr(sorted_indices.data<IntT>());
thrust::sequence(
policy, sorted_indices_thrust_ptr, sorted_indices_thrust_ptr + nnz, 0);
/* sort indices corresponding to offsets */
thrust::sort(policy,
sorted_indices_thrust_ptr,
sorted_indices_thrust_ptr + nnz,
[offsets_ptr] __device__(IntT x, IntT y) {
return offsets_ptr[x] < offsets_ptr[y];
});
DenseTensor pool_sizes = Empty<IntT>(dev_ctx, {nnz});
/* reduce the elements which are grouped by pool index,
returns all the pool indexes with unique offset value for each. */
auto new_end =
thrust::reduce_by_key(policy,
sorted_indices_thrust_ptr,
sorted_indices_thrust_ptr + nnz,
thrust::make_constant_iterator(IntT(1)),
thrust::make_discard_iterator(),
thrust_ptr(pool_sizes.data<IntT>()),
[offsets_ptr] __device__(IntT x, IntT y) {
return offsets_ptr[x] == offsets_ptr[y];
});
auto new_sz =
thrust::distance(thrust_ptr(pool_sizes.data<IntT>()), new_end.second);
pool_sizes.Resize({new_sz});
DenseTensor pool_offsets;
pool_offsets.Resize({new_sz});
dev_ctx.template Alloc<T>(&pool_offsets);
phi::Copy(dev_ctx, pool_sizes, dev_ctx.GetPlace(), false, &pool_offsets);
/* accumulate value for each pool index */
thrust_ptr pool_offsets_thrust_ptr(pool_offsets.data<IntT>());
thrust::exclusive_scan(policy,
pool_offsets_thrust_ptr,
pool_offsets_thrust_ptr + new_sz,
pool_offsets_thrust_ptr);
DenseTensor mx_buffer;
if (requireMxRows) {
mx_buffer = phi::Full<T>(
dev_ctx, {new_sz * nvalues}, -std::numeric_limits<T>::infinity());
auto mx_buffer_ptr = mx_buffer.data<T>();
auto pool_sizes_ptr = pool_sizes.data<IntT>();
auto sorted_indices_ptr = sorted_indices.data<IntT>();
auto pool_offsets_ptr = pool_offsets.data<IntT>();
auto values_ptr = values.data<T>();
/* calculate max value in each pool. */
thrust::for_each(policy,
thrust::make_counting_iterator(IntT(0)),
thrust::make_counting_iterator(IntT(new_sz)),
[sorted_indices_ptr,
pool_sizes_ptr,
pool_offsets_ptr,
mx_buffer_ptr,
values_ptr,
nvalues] __device__(IntT index) {
IntT curr_pool_size = pool_sizes_ptr[index];
auto mx_row = mx_buffer_ptr + index * nvalues;
IntT offset = pool_offsets_ptr[index];
for (IntT p = 0; p < curr_pool_size; p++) {
IntT i = *(sorted_indices_ptr + offset + p);
for (IntT j = 0; j < nvalues; j++) {
auto value_tmp = *(values_ptr);
mx_row[j] = std::max(mx_row[j], value_tmp);
}
}
});
}
return std::make_tuple(sorted_indices, pool_offsets, pool_sizes, mx_buffer);
}
inline int GetNumThreads(int nElem) {
#if defined(PADLDE_WITH_ROCM)
int threadSizes[5] = {16, 32, 64, 128, 256};
#else
int threadSizes[5] = {32, 64, 128, 256, 512};
#endif
for (int i = 0; i != 5; ++i) {
if (nElem <= threadSizes[i]) {
return threadSizes[i];
}
}
return threadSizes[4];
}
} // namespace sparse
} // namespace funcs
} // namespace phi
+88
View File
@@ -0,0 +1,88 @@
/* Copyright (c) 2023 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. */
#pragma once
#include "paddle/common/ddim.h"
#include "paddle/phi/core/tensor_utils.h"
namespace phi {
namespace funcs {
namespace sparse {
template <typename IntT>
inline void GetPoolsSoftmax(const DenseTensor& indices,
const std::vector<IntT>& sizes,
const int dim,
std::map<IntT, std::vector<IntT>>* pools) {
auto ndim = indices.dims()[0];
auto nnz = indices.dims()[1];
std::vector<IntT> strides(ndim, 1);
if (ndim > 1) {
for (IntT i = ndim - 2; i >= 0; i--) {
strides[i] = strides[i + 1] * (i + 1 == dim ? 1 : sizes[i + 1]);
}
}
auto* indices_data = indices.data<IntT>();
for (IntT i = 0; i < nnz; i++) {
IntT pool_index = 0;
for (IntT j = 0; j < ndim; j++) {
if (j == dim) continue;
pool_index += strides[j] * indices_data[j * nnz + i];
}
if (pools->find(pool_index) == pools->end()) {
std::vector<IntT> vec;
(*pools)[pool_index] = vec;
}
(*pools)[pool_index].push_back(i);
}
}
template <typename IntT>
inline std::vector<IntT> GetOffsets(const DenseTensor& indices,
const std::vector<IntT>& sizes,
const int dim) {
auto ndim = indices.dims()[0];
auto nnz = indices.dims()[1];
std::vector<IntT> offsets(nnz);
std::vector<IntT> strides(ndim, 1);
auto indices_ptr = indices.data<IntT>();
if (ndim > 1) {
for (IntT i = ndim - 2; i >= 0; i--) {
strides[i] = strides[i + 1] * sizes[i + 1];
}
}
for (int i = 0; i < nnz; i++) {
IntT acc = 0;
for (int j = 0; j < ndim; j++) {
auto indices_cur = indices_ptr + j * nnz + i;
auto stride = strides[j];
if (j != dim) {
acc += stride * (*indices_cur);
}
}
offsets[i] = acc;
}
return offsets;
}
} // namespace sparse
} // namespace funcs
} // namespace phi
@@ -0,0 +1,115 @@
// Copyright (c) 2018 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.
#pragma once
#include "paddle/phi/core/dense_tensor.h"
#include "paddle/phi/core/device_context.h"
#include "paddle/phi/core/sparse_coo_tensor.h"
#include "paddle/phi/core/sparse_csr_tensor.h"
namespace phi {
namespace funcs {
namespace sparse {
template <typename DeviceContext>
class SparseBlas {
public:
explicit SparseBlas(const DeviceContext& dev_ctx) : dev_ctx_(dev_ctx) {}
template <typename T, typename TensorType>
void SPMM(bool transa,
bool transb,
T alpha,
const TensorType& mat_a,
const DenseTensor& mat_b,
T beta,
DenseTensor* mat_out) const;
template <typename T, typename TensorType>
void SPMV(bool transa,
T alpha,
const TensorType& mat_a,
const DenseTensor& vec_x,
T beta,
DenseTensor* vec_out) const;
template <typename T, typename TensorType>
void SDDMM(bool transa,
bool transb,
T alpha,
const DenseTensor& mat_a,
const DenseTensor& mat_b,
T beta,
TensorType* mat_out) const;
template <typename T>
void SPGEMM(bool transa,
bool transb,
T alpha,
const SparseCsrTensor& mat_a,
const SparseCsrTensor& mat_b,
T beta,
SparseCsrTensor* mat_out) const;
private:
const DeviceContext& dev_ctx_;
};
template <typename DeviceContext, typename T>
class SparseBlasT : private SparseBlas<DeviceContext> {
public:
using SparseBlas<DeviceContext>::SparseBlas;
template <typename... ARGS>
void SPMM(ARGS... args) const {
Base()->template SPMM<T>(args...);
}
template <typename... ARGS>
void SPMV(ARGS... args) const {
Base()->template SPMV<T>(args...);
}
template <typename... ARGS>
void SDDMM(ARGS... args) const {
Base()->template SDDMM<T>(args...);
}
template <typename... ARGS>
void SPGEMM(ARGS... args) const {
Base()->template SPGEMM<T>(args...);
}
private:
const SparseBlas<DeviceContext>* Base() const {
return static_cast<const SparseBlas<DeviceContext>*>(this);
}
};
template <typename DeviceContext, typename T>
inline SparseBlasT<DeviceContext, T> GetSparseBlas(
const DeviceContext& dev_ctx) {
return SparseBlasT<DeviceContext, T>(dev_ctx);
}
} // namespace sparse
} // namespace funcs
} // namespace phi
#if defined(PADDLE_WITH_CUDA)
#include "paddle/phi/kernels/funcs/sparse/sparse_blas_impl.cu.h"
#endif
#if defined(PADDLE_WITH_HIP) && HIP_VERSION >= 402
#include "paddle/phi/kernels/funcs/sparse/sparse_blas_impl.hip.h"
#endif
@@ -0,0 +1,839 @@
// Copyright (c) 2018 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.
#pragma once
#include "glog/logging.h"
#include "paddle/common/ddim.h"
#include "paddle/phi/backends/dynload/cusparse.h"
#ifdef PADDLE_WITH_CUDA
#include "paddle/phi/backends/gpu/cuda/cuda_graph_with_memory_pool.h"
#endif
#include "paddle/phi/backends/gpu/gpu_context.h"
#include "paddle/phi/common/memory_utils.h"
#include "paddle/phi/core/dense_tensor.h"
#include "paddle/phi/core/enforce.h"
#include "paddle/phi/core/sparse_coo_tensor.h"
#include "paddle/phi/core/sparse_csr_tensor.h"
#include "paddle/phi/core/visit_type.h"
#include "paddle/phi/kernels/cast_kernel.h"
#include "paddle/phi/kernels/concat_kernel.h"
#include "paddle/phi/kernels/empty_kernel.h"
namespace phi {
namespace funcs {
namespace sparse {
template <typename T>
cudaDataType_t GetGpuDataType() {
if (std::is_same<T, float>::value) {
return CUDA_R_32F;
} else if (std::is_same<T, double>::value) {
return CUDA_R_64F;
} else if (std::is_same<T, phi::float16>::value) {
return CUDA_R_16F;
}
}
template <typename T>
cusparseIndexType_t GetCusparseIndexType() {
if (std::is_same<T, int32_t>::value) {
return CUSPARSE_INDEX_32I;
} else if (std::is_same<T, int64_t>::value) {
return CUSPARSE_INDEX_64I;
}
}
inline cusparseOperation_t GetTransposeOperation(const bool trans) {
if (trans) {
return CUSPARSE_OPERATION_TRANSPOSE;
} else {
return CUSPARSE_OPERATION_NON_TRANSPOSE;
}
}
inline cusparseSpMMAlg_t GetSpMMAlgorithm(const SparseCsrTensor& x) {
// TODO(zhouwei): will change to 'CUSPARSE_SPMM_CSR_ALG2' when support batch
return CUSPARSE_SPMM_CSR_ALG2;
}
inline cusparseSpMMAlg_t GetSpMMAlgorithm(const SparseCooTensor& x) {
return CUSPARSE_SPMM_ALG_DEFAULT;
}
/************* SPARSE MATRIX DESCRIPTOR (COO/CSR) ************/
template <typename T, typename IntT>
inline void CreateCsrDescriptor(const SparseCsrTensor& x,
const GPUContext& dev_ctx,
cusparseSpMatDescr_t* descriptor) {
std::vector<int64_t> xdim_vec = vectorize(x.dims());
auto x_ndims = xdim_vec.size();
PADDLE_ENFORCE_GE(
x_ndims,
2,
common::errors::InvalidArgument("the dim size of SparseCsrTensor must be "
"greater than or equal to 2."));
int64_t M = xdim_vec[x_ndims - 2];
int64_t N = xdim_vec[x_ndims - 1];
int batch_size = 1;
for (int i = 0; i < x_ndims - 2; i++) {
batch_size *= xdim_vec[i];
}
PADDLE_ENFORCE_EQ(x.non_zero_crows().numel(),
batch_size * (M + 1),
common::errors::PreconditionNotMet(
"the length of SparseCsrTensor crows is not right."));
const IntT* crows_data = x.non_zero_crows().data<IntT>();
const IntT* cols_data = x.non_zero_cols().data<IntT>();
const T* values_data = x.non_zero_elements().data<T>();
int64_t batch_nnz = x.nnz() / batch_size;
cudaDataType_t gpu_type = GetGpuDataType<T>();
cusparseIndexType_t index_type = GetCusparseIndexType<IntT>();
dev_ctx.CusparseCall([&](cusparseHandle_t handle) {
phi::dynload::cusparseCreateCsr(descriptor,
M,
N,
batch_nnz,
const_cast<IntT*>(crows_data),
const_cast<IntT*>(cols_data),
const_cast<T*>(values_data),
index_type,
index_type,
CUSPARSE_INDEX_BASE_ZERO,
gpu_type);
});
if (batch_size > 1) {
#if CUDA_VERSION >= 11080
dev_ctx.CusparseCall([&](cusparseHandle_t handle) {
phi::dynload::cusparseCsrSetStridedBatch(
*descriptor, batch_size, M + 1, batch_nnz);
});
#else
PADDLE_THROW(common::errors::Unimplemented(
"Batch Sparse matmul use 'cusparseCsrSetStridedBatch', which is "
"supported from CUDA 11.8"));
#endif
}
}
template <typename T, typename IntT>
inline void CreateCooDescriptor(const SparseCooTensor& x,
const GPUContext& dev_ctx,
cusparseSpMatDescr_t* descriptor) {
std::vector<int64_t> xdim_vec = vectorize(x.dims());
auto x_ndims = xdim_vec.size();
PADDLE_ENFORCE_GE(
x_ndims,
2,
common::errors::InvalidArgument("the dim size of SparseCsrTensor must be "
"greater than or equal to 2."));
int64_t M = xdim_vec[x_ndims - 2];
int64_t N = xdim_vec[x_ndims - 1];
int batch_size = 1;
for (int i = 0; i < x_ndims - 2; i++) {
batch_size *= xdim_vec[i];
}
int64_t nnz = x.nnz();
const IntT* indices_data = x.non_zero_indices().data<IntT>();
const T* values_data = x.non_zero_elements().data<T>();
auto rows_data = indices_data + (x_ndims - 2) * nnz;
auto cols_data = indices_data + (x_ndims - 1) * nnz;
int64_t batch_nnz = nnz / batch_size;
cudaDataType_t gpu_type = GetGpuDataType<T>();
cusparseIndexType_t index_type = GetCusparseIndexType<IntT>();
dev_ctx.CusparseCall([&](cusparseHandle_t handle) {
phi::dynload::cusparseCreateCoo(descriptor,
M,
N,
batch_nnz,
const_cast<IntT*>(rows_data),
const_cast<IntT*>(cols_data),
const_cast<T*>(values_data),
index_type,
CUSPARSE_INDEX_BASE_ZERO,
gpu_type);
});
if (batch_size > 1) {
#if CUDA_VERSION >= 11080
dev_ctx.CusparseCall([&](cusparseHandle_t handle) {
phi::dynload::cusparseCooSetStridedBatch(
*descriptor, batch_size, batch_nnz);
});
#else
PADDLE_THROW(common::errors::Unimplemented(
"Batch Sparse matmul use 'cusparseCooSetStridedBatch', which is "
"supported from CUDA 11.8"));
#endif
}
}
template <typename T>
class CuSparseSpMatDescriptor {
public:
explicit CuSparseSpMatDescriptor(const SparseCsrTensor& x,
const GPUContext& dev_ctx)
: dev_ctx_(dev_ctx) {
PD_VISIT_BASE_INTEGRAL_TYPES(
x.non_zero_crows().dtype(), "Csr CuSparseSpMatDescriptor", ([&] {
CreateCsrDescriptor<T, data_t>(x, dev_ctx_, &descriptor_);
}));
VLOG(6) << "Create csr cusparseSpMatDescr_t " << &descriptor_;
}
explicit CuSparseSpMatDescriptor(const SparseCooTensor& x,
const GPUContext& dev_ctx)
: dev_ctx_(dev_ctx) {
PD_VISIT_BASE_INTEGRAL_TYPES(
x.non_zero_indices().dtype(), "Coo CuSparseSpMatDescriptor", ([&] {
CreateCooDescriptor<T, data_t>(x, dev_ctx_, &descriptor_);
}));
VLOG(6) << "Create coo cusparseSpMatDescr_t " << &descriptor_;
}
~CuSparseSpMatDescriptor() {
dev_ctx_.CusparseCall([&](cusparseHandle_t handle) {
phi::dynload::cusparseDestroySpMat(descriptor_);
});
VLOG(6) << "Destroy cusparseSpMatDescr_t " << &descriptor_;
}
const cusparseSpMatDescr_t& descriptor() const { return descriptor_; }
private:
const GPUContext& dev_ctx_;
cusparseSpMatDescr_t descriptor_;
};
/************* DENSE MATRIX DESCRIPTOR ************/
template <typename T>
class CuSparseDnMatDescriptor {
public:
explicit CuSparseDnMatDescriptor(const DenseTensor& x,
const GPUContext& dev_ctx)
: dev_ctx_(dev_ctx) {
std::vector<int64_t> xdim_vec = vectorize(x.dims());
auto x_ndims = xdim_vec.size();
PADDLE_ENFORCE_GE(
x_ndims,
2,
common::errors::InvalidArgument("the dim size of DenseTensor must be "
"greater than or equal to 2."));
int64_t M = xdim_vec[x_ndims - 2];
int64_t N = xdim_vec[x_ndims - 1];
int batch_size = 1;
for (int i = 0; i < x_ndims - 2; i++) {
batch_size *= xdim_vec[i];
}
const T* x_data = x.data<T>();
cudaDataType_t gpu_type = GetGpuDataType<T>();
dev_ctx_.CusparseCall([&](cusparseHandle_t handle) {
phi::dynload::cusparseCreateDnMat(&descriptor_,
M,
N,
N,
const_cast<T*>(x_data),
gpu_type,
CUSPARSE_ORDER_ROW);
});
PADDLE_ENFORCE_EQ(x.numel(), batch_size * M * N);
if (batch_size > 1) {
#if CUDA_VERSION >= 11080
dev_ctx_.CusparseCall([&](cusparseHandle_t handle) {
phi::dynload::cusparseDnMatSetStridedBatch(
descriptor_, batch_size, M * N);
});
#else
PADDLE_THROW(common::errors::Unimplemented(
"Batch Sparse matmul use 'cusparseDnMatSetStridedBatch', which is "
"supported from CUDA 11.8"));
#endif
}
VLOG(6) << "Create cusparseDnMatDescr_t " << &descriptor_;
}
~CuSparseDnMatDescriptor() {
dev_ctx_.CusparseCall([&](cusparseHandle_t handle) {
phi::dynload::cusparseDestroyDnMat(descriptor_);
});
VLOG(6) << "Destroy cusparseDnMatDescr_t " << &descriptor_;
}
const cusparseDnMatDescr_t& descriptor() const { return descriptor_; }
private:
const GPUContext& dev_ctx_;
cusparseDnMatDescr_t descriptor_;
};
/************* DENSE VECTOR DESCRIPTOR ************/
template <typename T>
class CuSparseDnVecDescriptor {
public:
explicit CuSparseDnVecDescriptor(const DenseTensor& x,
const GPUContext& dev_ctx)
: dev_ctx_(dev_ctx) {
std::vector<int64_t> xdim_vec = vectorize(x.dims());
auto x_ndims = xdim_vec.size();
PADDLE_ENFORCE_GE(x_ndims,
1,
common::errors::InvalidArgument(
"the dim size of Vec must be equal to 1."));
const T* x_data = x.data<T>();
cudaDataType_t gpu_type = GetGpuDataType<T>();
dev_ctx_.CusparseCall([&](cusparseHandle_t handle) {
phi::dynload::cusparseCreateDnVec(
&descriptor_, x.numel(), const_cast<T*>(x_data), gpu_type);
});
VLOG(6) << "Create cusparseDnVecDescr_t " << &descriptor_;
}
~CuSparseDnVecDescriptor() {
dev_ctx_.CusparseCall([&](cusparseHandle_t handle) {
phi::dynload::cusparseDestroyDnVec(descriptor_);
});
VLOG(6) << "Destroy cusparseDnVecDescr_t " << &descriptor_;
}
const cusparseDnVecDescr_t& descriptor() const { return descriptor_; }
private:
const GPUContext& dev_ctx_;
cusparseDnVecDescr_t descriptor_;
};
/************* SPARSE*DENSE->DENSE MATMUL ************/
template <>
template <typename T, typename TensorType>
void SparseBlas<GPUContext>::SPMM(bool transa,
bool transb,
T alpha,
const TensorType& mat_a,
const DenseTensor& mat_b,
T beta,
DenseTensor* mat_out) const {
auto a_descriptor = CuSparseSpMatDescriptor<T>(mat_a, dev_ctx_);
auto b_descriptor = CuSparseDnMatDescriptor<T>(mat_b, dev_ctx_);
auto out_descriptor = CuSparseDnMatDescriptor<T>(*mat_out, dev_ctx_);
cudaDataType_t gpu_type = GetGpuDataType<T>();
size_t buffer_size = 0;
dev_ctx_.CusparseCall([&](cusparseHandle_t handle) {
phi::dynload::cusparseSpMM_bufferSize(handle,
GetTransposeOperation(transa),
GetTransposeOperation(transb),
&alpha,
a_descriptor.descriptor(),
b_descriptor.descriptor(),
&beta,
out_descriptor.descriptor(),
gpu_type,
GetSpMMAlgorithm(mat_a),
&buffer_size);
});
phi::Allocator::AllocationPtr tmp_buffer = phi::memory_utils::Alloc(
dev_ctx_.GetPlace(),
buffer_size,
phi::Stream(reinterpret_cast<phi::StreamId>(dev_ctx_.stream())));
void* tmp_buffer_ptr = tmp_buffer->ptr();
dev_ctx_.CusparseCall([&](cusparseHandle_t handle) {
phi::dynload::cusparseSpMM(handle,
GetTransposeOperation(transa),
GetTransposeOperation(transb),
&alpha,
a_descriptor.descriptor(),
b_descriptor.descriptor(),
&beta,
out_descriptor.descriptor(),
gpu_type,
GetSpMMAlgorithm(mat_a),
tmp_buffer_ptr);
});
}
/************* SPARSE*DENSE->DENSE MV ************/
template <>
template <typename T, typename TensorType>
void SparseBlas<GPUContext>::SPMV(bool transa,
T alpha,
const TensorType& mat_a,
const DenseTensor& vec_x,
T beta,
DenseTensor* vec_out) const {
auto a_descriptor = CuSparseSpMatDescriptor<T>(mat_a, dev_ctx_);
auto x_descriptor = CuSparseDnVecDescriptor<T>(vec_x, dev_ctx_);
auto out_descriptor = CuSparseDnVecDescriptor<T>(*vec_out, dev_ctx_);
cudaDataType_t gpu_type = GetGpuDataType<T>();
size_t buffer_size = 0;
dev_ctx_.CusparseCall([&](cusparseHandle_t handle) {
phi::dynload::cusparseSpMV_bufferSize(handle,
GetTransposeOperation(transa),
&alpha,
a_descriptor.descriptor(),
x_descriptor.descriptor(),
&beta,
out_descriptor.descriptor(),
gpu_type,
CUSPARSE_SPMV_ALG_DEFAULT,
&buffer_size);
});
phi::Allocator::AllocationPtr tmp_buffer = phi::memory_utils::Alloc(
dev_ctx_.GetPlace(),
buffer_size,
phi::Stream(reinterpret_cast<phi::StreamId>(dev_ctx_.stream())));
void* tmp_buffer_ptr = tmp_buffer->ptr();
dev_ctx_.CusparseCall([&](cusparseHandle_t handle) {
phi::dynload::cusparseSpMV(handle,
GetTransposeOperation(transa),
&alpha,
a_descriptor.descriptor(),
x_descriptor.descriptor(),
&beta,
out_descriptor.descriptor(),
gpu_type,
CUSPARSE_SPMV_ALG_DEFAULT,
tmp_buffer_ptr);
});
}
/************* DENSE*DENSE->SPARSE MATMUL ************/
template <>
template <typename T, typename TensorType>
void SparseBlas<GPUContext>::SDDMM(bool transa,
bool transb,
T alpha,
const DenseTensor& mat_a,
const DenseTensor& mat_b,
T beta,
TensorType* mat_out) const {
auto a_descriptor = CuSparseDnMatDescriptor<T>(mat_a, dev_ctx_);
auto b_descriptor = CuSparseDnMatDescriptor<T>(mat_b, dev_ctx_);
auto out_descriptor = CuSparseSpMatDescriptor<T>(*mat_out, dev_ctx_);
cudaDataType_t gpu_type = GetGpuDataType<T>();
size_t buffer_size = 0;
dev_ctx_.CusparseCall([&](cusparseHandle_t handle) {
phi::dynload::cusparseSDDMM_bufferSize(handle,
GetTransposeOperation(transa),
GetTransposeOperation(transb),
&alpha,
a_descriptor.descriptor(),
b_descriptor.descriptor(),
&beta,
out_descriptor.descriptor(),
gpu_type,
CUSPARSE_SDDMM_ALG_DEFAULT,
&buffer_size);
});
phi::Allocator::AllocationPtr tmp_buffer = phi::memory_utils::Alloc(
dev_ctx_.GetPlace(),
buffer_size,
phi::Stream(reinterpret_cast<phi::StreamId>(dev_ctx_.stream())));
void* tmp_buffer_ptr = tmp_buffer->ptr();
dev_ctx_.CusparseCall([&](cusparseHandle_t handle) {
phi::dynload::cusparseSDDMM_preprocess(handle,
GetTransposeOperation(transa),
GetTransposeOperation(transb),
&alpha,
a_descriptor.descriptor(),
b_descriptor.descriptor(),
&beta,
out_descriptor.descriptor(),
gpu_type,
CUSPARSE_SDDMM_ALG_DEFAULT,
tmp_buffer_ptr);
});
dev_ctx_.CusparseCall([&](cusparseHandle_t handle) {
phi::dynload::cusparseSDDMM(handle,
GetTransposeOperation(transa),
GetTransposeOperation(transb),
&alpha,
a_descriptor.descriptor(),
b_descriptor.descriptor(),
&beta,
out_descriptor.descriptor(),
gpu_type,
CUSPARSE_SDDMM_ALG_DEFAULT,
tmp_buffer_ptr);
});
}
/************* SPARSE*SPARSE->SPARSE MATMUL ************/
template <typename T>
__global__ void GetCsrBatchNnz(const int32_t* crow_data,
int64_t rows,
int32_t* batch_nnz) {
int64_t i = static_cast<int64_t>(threadIdx.x);
batch_nnz[i] = crow_data[(i + 1) * (rows + 1) - 1];
}
template <>
template <typename T>
void SparseBlas<GPUContext>::SPGEMM(bool transa,
bool transb,
T alpha,
const SparseCsrTensor& mat_a,
const SparseCsrTensor& mat_b,
T beta,
SparseCsrTensor* mat_out) const {
DenseTensor* mat_out_crows = mat_out->mutable_crows();
DenseTensor* mat_out_cols = mat_out->mutable_cols();
DenseTensor* mat_out_values = mat_out->mutable_values();
MetaTensor out_crows_meta(mat_out_crows);
out_crows_meta.set_dtype(DataType::INT32);
out_crows_meta.set_dims(mat_a.crows().dims());
dev_ctx_.template Alloc<int32_t>(mat_out_crows);
std::vector<int64_t> a_dim_vec = vectorize(mat_a.dims());
auto a_ndims = a_dim_vec.size();
const int64_t a_rows = a_dim_vec[a_ndims - 2];
const int64_t a_cols = a_dim_vec[a_ndims - 1];
int a_batch_size = 1;
for (int i = 0; i < a_ndims - 2; i++) {
a_batch_size *= a_dim_vec[i];
}
std::vector<int64_t> b_dim_vec = vectorize(mat_b.dims());
auto b_ndims = b_dim_vec.size();
const int64_t b_rows = b_dim_vec[b_ndims - 2];
const int64_t b_cols = b_dim_vec[b_ndims - 1];
// cusparseSpGEMM only support 32-bit indices.
const int32_t *a_crows_data = nullptr, *a_cols_data = nullptr,
*b_crows_data = nullptr, *b_cols_data = nullptr;
std::shared_ptr<DenseTensor> a_crows_int = nullptr, a_cols_int = nullptr,
b_crows_int = nullptr, b_cols_int = nullptr;
if (mat_a.crows().dtype() == DataType::INT32) {
a_crows_data = mat_a.crows().data<int32_t>();
a_cols_data = mat_a.cols().data<int32_t>();
} else {
a_crows_int = std::make_shared<DenseTensor>();
a_cols_int = std::make_shared<DenseTensor>();
MetaTensor crows_meta(a_crows_int.get());
crows_meta.set_dims(mat_a.crows().dims());
MetaTensor cols_meta(a_cols_int.get());
cols_meta.set_dims(mat_a.cols().dims());
CastKernel<int64_t>(
dev_ctx_, mat_a.crows(), DataType::INT32, a_crows_int.get());
CastKernel<int64_t>(
dev_ctx_, mat_a.cols(), DataType::INT32, a_cols_int.get());
a_crows_data = a_crows_int->data<int32_t>();
a_cols_data = a_cols_int->data<int32_t>();
}
if (mat_b.crows().dtype() == DataType::INT32) {
b_crows_data = mat_b.crows().data<int32_t>();
b_cols_data = mat_b.cols().data<int32_t>();
} else {
b_crows_int = std::make_shared<DenseTensor>();
b_cols_int = std::make_shared<DenseTensor>();
MetaTensor crows_meta(b_crows_int.get());
crows_meta.set_dims(mat_b.crows().dims());
MetaTensor cols_meta(b_cols_int.get());
cols_meta.set_dims(mat_b.cols().dims());
CastKernel<int64_t>(
dev_ctx_, mat_b.crows(), DataType::INT32, b_crows_int.get());
CastKernel<int64_t>(
dev_ctx_, mat_b.cols(), DataType::INT32, b_cols_int.get());
b_crows_data = b_crows_int->data<int32_t>();
b_cols_data = b_cols_int->data<int32_t>();
}
const T* a_values_data = mat_a.values().data<T>();
const T* b_values_data = mat_b.values().data<T>();
const int32_t* out_crows_data = mat_out->crows().data<int32_t>();
const int batch_size = a_batch_size;
std::vector<int32_t> a_batch_nnz_vec(batch_size);
std::vector<int32_t> b_batch_nnz_vec(batch_size);
if (batch_size == 1) {
a_batch_nnz_vec[0] = mat_a.nnz();
b_batch_nnz_vec[0] = mat_b.nnz();
} else {
phi::Allocator::AllocationPtr tmp_buffer = phi::memory_utils::Alloc(
dev_ctx_.GetPlace(),
batch_size * sizeof(int32_t),
phi::Stream(reinterpret_cast<phi::StreamId>(dev_ctx_.stream())));
void* tmp_buffer_ptr = tmp_buffer->ptr();
GetCsrBatchNnz<T><<<1, batch_size, 0, dev_ctx_.stream()>>>(
a_crows_data, a_rows, static_cast<int32_t*>(tmp_buffer_ptr));
#ifdef PADDLE_WITH_CUDA
PADDLE_ENFORCE_EQ(
phi::backends::gpu::IsCUDAGraphCapturing(),
false,
common::errors::InvalidArgument(
"SparseBlas CsrMM does not support CUDA Graph capture: async D2H "
"copy to local vectors 'a_batch_nnz_vec' / 'b_batch_nnz_vec' will "
"bake the destination addresses into the graph; on replay the "
"vectors are re-created at different addresses, causing "
"dangling-pointer writes."));
#endif
phi::backends::gpu::GpuMemcpyAsync(a_batch_nnz_vec.data(),
tmp_buffer_ptr,
batch_size * sizeof(int32_t),
gpuMemcpyDeviceToHost,
dev_ctx_.stream());
GetCsrBatchNnz<T><<<1, batch_size, 0, dev_ctx_.stream()>>>(
b_crows_data, b_rows, static_cast<int32_t*>(tmp_buffer_ptr));
phi::backends::gpu::GpuMemcpyAsync(b_batch_nnz_vec.data(),
tmp_buffer_ptr,
batch_size * sizeof(int32_t),
gpuMemcpyDeviceToHost,
dev_ctx_.stream());
}
std::vector<DenseTensor> out_batch_cols_vec(batch_size);
std::vector<DenseTensor> out_batch_values_vec(batch_size);
cudaDataType_t gpu_type = GetGpuDataType<T>();
const int32_t* a_batch_crows_data = a_crows_data;
const int32_t* a_batch_cols_data = a_cols_data;
const T* a_batch_values_data = a_values_data;
const int32_t* b_batch_crows_data = b_crows_data;
const int32_t* b_batch_cols_data = b_cols_data;
const T* b_batch_values_data = b_values_data;
const int32_t* out_batch_crows_data = out_crows_data;
for (int i = 0; i < batch_size; ++i) {
int32_t a_batch_nnz = a_batch_nnz_vec[i];
int32_t b_batch_nnz = b_batch_nnz_vec[i];
cusparseSpMatDescr_t a_batch_desc, b_batch_desc, out_batch_desc;
dev_ctx_.CusparseCall([&](cusparseHandle_t handle) {
phi::dynload::cusparseCreateCsr(&a_batch_desc,
a_rows,
a_cols,
a_batch_nnz,
const_cast<int32_t*>(a_batch_crows_data),
const_cast<int32_t*>(a_batch_cols_data),
const_cast<T*>(a_batch_values_data),
CUSPARSE_INDEX_32I,
CUSPARSE_INDEX_32I,
CUSPARSE_INDEX_BASE_ZERO,
gpu_type);
});
dev_ctx_.CusparseCall([&](cusparseHandle_t handle) {
phi::dynload::cusparseCreateCsr(&b_batch_desc,
b_rows,
b_cols,
b_batch_nnz,
const_cast<int32_t*>(b_batch_crows_data),
const_cast<int32_t*>(b_batch_cols_data),
const_cast<T*>(b_batch_values_data),
CUSPARSE_INDEX_32I,
CUSPARSE_INDEX_32I,
CUSPARSE_INDEX_BASE_ZERO,
gpu_type);
});
dev_ctx_.CusparseCall([&](cusparseHandle_t handle) {
phi::dynload::cusparseCreateCsr(&out_batch_desc,
a_rows,
b_cols,
0,
nullptr,
nullptr,
nullptr,
CUSPARSE_INDEX_32I,
CUSPARSE_INDEX_32I,
CUSPARSE_INDEX_BASE_ZERO,
gpu_type);
});
size_t buffer_a_size = 0, buffer_b_size = 0;
cusparseSpGEMMDescr_t spgemm_desc;
dev_ctx_.CusparseCall([&](cusparseHandle_t handle) {
phi::dynload::cusparseSpGEMM_createDescr(&spgemm_desc);
});
dev_ctx_.CusparseCall([&](cusparseHandle_t handle) {
phi::dynload::cusparseSpGEMM_workEstimation(handle,
GetTransposeOperation(transa),
GetTransposeOperation(transb),
&alpha,
a_batch_desc,
b_batch_desc,
&beta,
out_batch_desc,
gpu_type,
CUSPARSE_SPGEMM_DEFAULT,
spgemm_desc,
&buffer_a_size,
nullptr);
});
phi::Allocator::AllocationPtr tmp_buffer_a = phi::memory_utils::Alloc(
dev_ctx_.GetPlace(),
buffer_a_size,
phi::Stream(reinterpret_cast<phi::StreamId>(dev_ctx_.stream())));
void* tmp_buffer_a_ptr = tmp_buffer_a->ptr();
dev_ctx_.CusparseCall([&](cusparseHandle_t handle) {
phi::dynload::cusparseSpGEMM_workEstimation(handle,
GetTransposeOperation(transa),
GetTransposeOperation(transb),
&alpha,
a_batch_desc,
b_batch_desc,
&beta,
out_batch_desc,
gpu_type,
CUSPARSE_SPGEMM_DEFAULT,
spgemm_desc,
&buffer_a_size,
tmp_buffer_a_ptr);
});
dev_ctx_.CusparseCall([&](cusparseHandle_t handle) {
phi::dynload::cusparseSpGEMM_compute(handle,
GetTransposeOperation(transa),
GetTransposeOperation(transb),
&alpha,
a_batch_desc,
b_batch_desc,
&beta,
out_batch_desc,
gpu_type,
CUSPARSE_SPGEMM_DEFAULT,
spgemm_desc,
&buffer_b_size,
nullptr);
});
phi::Allocator::AllocationPtr tmp_buffer_b = phi::memory_utils::Alloc(
dev_ctx_.GetPlace(),
buffer_b_size,
phi::Stream(reinterpret_cast<phi::StreamId>(dev_ctx_.stream())));
void* tmp_buffer_b_ptr = tmp_buffer_b->ptr();
dev_ctx_.CusparseCall([&](cusparseHandle_t handle) {
phi::dynload::cusparseSpGEMM_compute(handle,
GetTransposeOperation(transa),
GetTransposeOperation(transb),
&alpha,
a_batch_desc,
b_batch_desc,
&beta,
out_batch_desc,
gpu_type,
CUSPARSE_SPGEMM_DEFAULT,
spgemm_desc,
&buffer_b_size,
tmp_buffer_b_ptr);
});
int64_t out_num_crows, out_num_cols, out_num_values;
dev_ctx_.CusparseCall([&](cusparseHandle_t handle) {
phi::dynload::cusparseSpMatGetSize(
out_batch_desc, &out_num_crows, &out_num_cols, &out_num_values);
});
out_batch_cols_vec[i].Resize(common::make_dim(out_num_values));
dev_ctx_.template Alloc<int32_t>(&out_batch_cols_vec[i]);
out_batch_values_vec[i].Resize(common::make_dim(out_num_values));
dev_ctx_.template Alloc<T>(&out_batch_values_vec[i]);
dev_ctx_.CusparseCall([&](cusparseHandle_t handle) {
phi::dynload::cusparseCsrSetPointers(
out_batch_desc,
const_cast<int32_t*>(out_batch_crows_data),
const_cast<int32_t*>(out_batch_cols_vec[i].data<int32_t>()),
const_cast<T*>(out_batch_values_vec[i].data<T>()));
});
dev_ctx_.CusparseCall([&](cusparseHandle_t handle) {
phi::dynload::cusparseSpGEMM_copy(handle,
GetTransposeOperation(transa),
GetTransposeOperation(transb),
&alpha,
a_batch_desc,
b_batch_desc,
&beta,
out_batch_desc,
gpu_type,
CUSPARSE_SPGEMM_DEFAULT,
spgemm_desc);
});
dev_ctx_.CusparseCall([&](cusparseHandle_t handle) {
phi::dynload::cusparseSpGEMM_destroyDescr(spgemm_desc);
});
a_batch_crows_data += a_rows + 1;
a_batch_cols_data += a_batch_nnz;
a_batch_values_data += a_batch_nnz;
b_batch_crows_data += b_rows + 1;
b_batch_cols_data += b_batch_nnz;
b_batch_values_data += b_batch_nnz;
out_batch_crows_data += a_rows + 1;
}
if (batch_size == 1) {
*(mat_out->mutable_cols()) = std::move(out_batch_cols_vec[0]);
*(mat_out->mutable_values()) = std::move(out_batch_values_vec[0]);
} else {
std::vector<const DenseTensor*> cols_vec, values_vec;
for (int i = 0; i < batch_size; ++i) {
cols_vec.push_back(&out_batch_cols_vec[i]);
values_vec.push_back(&out_batch_values_vec[i]);
}
phi::ConcatKernel<int32_t>(dev_ctx_, cols_vec, 0, mat_out->mutable_cols());
phi::ConcatKernel<T>(dev_ctx_, values_vec, 0, mat_out->mutable_values());
}
if (mat_a.crows().dtype() == DataType::INT64 ||
mat_b.crows().dtype() == DataType::INT64) {
CastKernel<int32_t>(
dev_ctx_, *mat_out_crows, DataType::INT64, mat_out_crows);
CastKernel<int32_t>(dev_ctx_, *mat_out_cols, DataType::INT64, mat_out_cols);
}
}
} // namespace sparse
} // namespace funcs
} // namespace phi
@@ -0,0 +1,405 @@
// Copyright (c) 2023 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.
#pragma once
#include "paddle/common/ddim.h"
#include "paddle/phi/backends/dynload/rocsparse.h"
#include "paddle/phi/backends/gpu/gpu_context.h"
#include "paddle/phi/common/data_type.h"
#include "paddle/phi/common/memory_utils.h"
#include "paddle/phi/core/dense_tensor.h"
#include "paddle/phi/core/enforce.h"
#include "paddle/phi/core/sparse_coo_tensor.h"
#include "paddle/phi/core/sparse_csr_tensor.h"
#include "paddle/phi/core/visit_type.h"
namespace phi {
namespace funcs {
namespace sparse {
template <typename IntT>
rocsparse_indextype GetGpuIndexType() {
if (std::is_same<IntT, int32_t>::value) {
return rocsparse_indextype_i32;
} else if (std::is_same<IntT, int64_t>::value) {
return rocsparse_indextype_i64;
}
}
template <typename T>
rocsparse_datatype GetGpuDataType() {
if (std::is_same<T, float>::value) {
return rocsparse_datatype_f32_r;
} else if (std::is_same<T, double>::value) {
return rocsparse_datatype_f64_r;
}
}
inline rocsparse_operation GetTransposeOperation(const bool trans) {
if (trans) {
return rocsparse_operation_transpose;
} else {
return rocsparse_operation_none;
}
}
template <typename TensorType>
inline rocsparse_spmm_alg GetSpMMAlgorithm(const TensorType& x) {
return rocsparse_spmm_alg_default;
}
/************* SPARSE MATRIX DESCRIPTOR (COO/CSR) ************/
template <typename T, typename IntT>
inline void CreateCsrDescriptor(const SparseCsrTensor& x,
const GPUContext& dev_ctx,
rocsparse_spmat_descr* descriptor) {
std::vector<int64_t> xdim_vec = vectorize(x.dims());
auto x_ndims = xdim_vec.size();
PADDLE_ENFORCE_GE(
x_ndims,
2,
common::errors::InvalidArgument("the dim size of SparseCsrTensor must be "
"greater than or equal to 2."));
int64_t M = xdim_vec[x_ndims - 2];
int64_t N = xdim_vec[x_ndims - 1];
int batch_size = 1;
for (int i = 0; i < x_ndims - 2; i++) {
batch_size *= xdim_vec[i];
}
PADDLE_ENFORCE_EQ(x.non_zero_crows().numel(),
batch_size * (M + 1),
common::errors::PreconditionNotMet(
"the length of SparseCsrTensor crows is not right."));
const IntT* crows_data = x.non_zero_crows().data<IntT>();
const IntT* cols_data = x.non_zero_cols().data<IntT>();
const T* values_data = x.non_zero_elements().data<T>();
int64_t batch_nnz = x.nnz() / batch_size;
rocsparse_indextype itype = GetGpuIndexType<int64_t>();
rocsparse_indextype jtype = GetGpuIndexType<int64_t>();
rocsparse_datatype ttype = GetGpuDataType<T>();
dev_ctx.CusparseCall([&](rocsparse_handle handle) {
phi::dynload::rocsparse_create_csr_descr(descriptor,
M,
N,
batch_nnz,
const_cast<IntT*>(crows_data),
const_cast<IntT*>(cols_data),
const_cast<T*>(values_data),
itype,
jtype,
rocsparse_index_base_zero,
ttype);
});
if (batch_size > 1) {
// TODO(umiswing): Add batch sparse matmul support for ROCM after 5.2.0
PADDLE_THROW(common::errors::Unimplemented(
"Batch Sparse matmul use 'rocsparse_coo_set_strided_batch', which is "
"supported from ROCM 5.2.0"));
}
}
template <typename T, typename IntT>
inline void CreateCooDescriptor(const SparseCooTensor& x,
const GPUContext& dev_ctx,
rocsparse_spmat_descr* descriptor) {
std::vector<int64_t> xdim_vec = vectorize(x.dims());
auto x_ndims = xdim_vec.size();
PADDLE_ENFORCE_GE(
x_ndims,
2,
common::errors::InvalidArgument("the dim size of SparseCooTensor must be "
"greater than or equal to 2."));
int64_t M = xdim_vec[x_ndims - 2];
int64_t N = xdim_vec[x_ndims - 1];
int batch_size = 1;
for (int i = 0; i < x_ndims - 2; i++) {
batch_size *= xdim_vec[i];
}
int64_t nnz = x.nnz();
const IntT* indices_data = x.non_zero_indices().data<IntT>();
const T* values_data = x.non_zero_elements().data<T>();
auto rows_data = indices_data + (x_ndims - 2) * nnz;
auto cols_data = indices_data + (x_ndims - 1) * nnz;
int64_t batch_nnz = nnz / batch_size;
rocsparse_indextype itype = GetGpuIndexType<int64_t>();
rocsparse_datatype ttype = GetGpuDataType<T>();
dev_ctx.CusparseCall([&](rocsparse_handle handle) {
phi::dynload::rocsparse_create_coo_descr(descriptor,
M,
N,
batch_nnz,
const_cast<IntT*>(rows_data),
const_cast<IntT*>(cols_data),
const_cast<T*>(values_data),
itype,
rocsparse_index_base_zero,
ttype);
});
if (batch_size > 1) {
// TODO(umiswing): Add batch sparse matmul support for ROCM after 5.2.0
PADDLE_THROW(common::errors::Unimplemented(
"Batch Sparse matmul use 'rocsparse_coo_set_strided_batch', which is "
"supported from ROCM 5.2.0"));
}
}
template <typename T>
class RocSparseSpMatDescriptor {
public:
explicit RocSparseSpMatDescriptor(const SparseCsrTensor& x,
const GPUContext& dev_ctx)
: dev_ctx_(dev_ctx) {
PD_VISIT_BASE_INTEGRAL_TYPES(
x.non_zero_crows().dtype(), "Csr RocSparseSpMatDescriptor", ([&] {
CreateCsrDescriptor<T, data_t>(x, dev_ctx_, &descriptor_);
}));
VLOG(6) << "Create csr rocsparse_spmat_descr " << &descriptor_;
}
explicit RocSparseSpMatDescriptor(const SparseCooTensor& x,
const GPUContext& dev_ctx)
: dev_ctx_(dev_ctx) {
PD_VISIT_BASE_INTEGRAL_TYPES(
x.non_zero_indices().dtype(), "Coo RocSparseSpMatDescriptor", ([&] {
CreateCooDescriptor<T, data_t>(x, dev_ctx_, &descriptor_);
}));
VLOG(6) << "Create coo rocsparse_spmat_descr " << &descriptor_;
}
~RocSparseSpMatDescriptor() {
dev_ctx_.CusparseCall([&](rocsparse_handle handle) {
phi::dynload::rocsparse_destroy_spmat_descr(descriptor_);
});
VLOG(6) << "Destroy roscparse_spmat_descr " << &descriptor_;
}
const rocsparse_spmat_descr& descriptor() const { return descriptor_; }
private:
const GPUContext& dev_ctx_;
rocsparse_spmat_descr descriptor_;
};
/************* DENSE MATRIX DESCRIPTOR ************/
template <typename T>
class RocSparseDnMatDescriptor {
public:
explicit RocSparseDnMatDescriptor(const DenseTensor& x,
const GPUContext& dev_ctx)
: dev_ctx_(dev_ctx) {
std::vector<int64_t> xdim_vec = vectorize(x.dims());
auto x_ndims = xdim_vec.size();
PADDLE_ENFORCE_GE(
x_ndims,
2,
common::errors::InvalidArgument("the dim size of DenseTensor must be "
"greater than or equal to 2."));
int64_t M = xdim_vec[x_ndims - 2];
int64_t N = xdim_vec[x_ndims - 1];
int batch_size = 1;
for (int i = 0; i < x_ndims - 2; i++) {
batch_size *= xdim_vec[i];
}
const T* x_data = x.data<T>();
rocsparse_datatype ttype = GetGpuDataType<T>();
dev_ctx.CusparseCall([&](rocsparse_handle handle) {
phi::dynload::rocsparse_create_dnmat_descr(&descriptor_,
M,
N,
N,
const_cast<T*>(x_data),
ttype,
rocsparse_order_row);
});
PADDLE_ENFORCE_EQ(
x.numel(),
batch_size * M * N,
common::errors::InvalidArgument("The number of elements in DenseTensor "
"must equals to batch_size * M * N."));
if (batch_size > 1) {
// TODO(umiswing): Add batch sparse matmul support for ROCM after 5.2.0
PADDLE_THROW(common::errors::Unimplemented(
"Batch Sparse matmul use 'rocsparse_dnmat_set_strided_batch', which "
"is supported from ROCM 5.2.0"));
}
VLOG(6) << "Create cusparseDnMatDescr_t " << &descriptor_;
}
~RocSparseDnMatDescriptor() {
dev_ctx_.CusparseCall([&](rocsparse_handle handle) {
phi::dynload::rocsparse_destroy_dnmat_descr(descriptor_);
});
VLOG(6) << "Destroy rocsparse_dnmat_descr " << &descriptor_;
}
const rocsparse_dnmat_descr& descriptor() const { return descriptor_; }
private:
const GPUContext& dev_ctx_;
rocsparse_dnmat_descr descriptor_;
};
/************* SPARSE*DENSE->DENSE MATMUL ************/
template <>
template <typename T, typename TensorType>
void SparseBlas<GPUContext>::SPMM(bool transa,
bool transb,
T alpha,
const TensorType& mat_a,
const DenseTensor& mat_b,
T beta,
DenseTensor* mat_out) const {
auto a_descriptor = RocSparseSpMatDescriptor<T>(mat_a, dev_ctx_);
auto b_descriptor = RocSparseDnMatDescriptor<T>(mat_b, dev_ctx_);
auto out_descriptor = RocSparseDnMatDescriptor<T>(*mat_out, dev_ctx_);
rocsparse_datatype ttype = GetGpuDataType<T>();
size_t buffer_size = 0;
// Query SpMM buffer
dev_ctx_.CusparseCall([&](rocsparse_handle handle) {
phi::dynload::rocsparse_spmm(handle,
GetTransposeOperation(transa),
GetTransposeOperation(transb),
&alpha,
a_descriptor.descriptor(),
b_descriptor.descriptor(),
&beta,
out_descriptor.descriptor(),
ttype,
GetSpMMAlgorithm(mat_a),
rocsparse_spmm_stage_buffer_size,
&buffer_size,
nullptr);
});
// Allocate buffer
phi::Allocator::AllocationPtr tmp_buffer = phi::memory_utils::Alloc(
dev_ctx_.GetPlace(),
buffer_size,
phi::Stream(reinterpret_cast<phi::StreamId>(dev_ctx_.stream())));
void* tmp_buffer_ptr = tmp_buffer->ptr();
// Preprocess data
dev_ctx_.CusparseCall([&](rocsparse_handle handle) {
phi::dynload::rocsparse_spmm(handle,
GetTransposeOperation(transa),
GetTransposeOperation(transb),
&alpha,
a_descriptor.descriptor(),
b_descriptor.descriptor(),
&beta,
out_descriptor.descriptor(),
ttype,
GetSpMMAlgorithm(mat_a),
rocsparse_spmm_stage_preprocess,
&buffer_size,
tmp_buffer_ptr);
});
// Performs the actual SpMM computation
dev_ctx_.CusparseCall([&](rocsparse_handle handle) {
phi::dynload::rocsparse_spmm(handle,
GetTransposeOperation(transa),
GetTransposeOperation(transb),
&alpha,
a_descriptor.descriptor(),
b_descriptor.descriptor(),
&beta,
out_descriptor.descriptor(),
ttype,
GetSpMMAlgorithm(mat_a),
rocsparse_spmm_stage_compute,
&buffer_size,
tmp_buffer_ptr);
});
}
/************* DENSE*DENSE->SPARSE MATMUL ************/
#if HIP_VERSION >= 403
template <>
template <typename T, typename TensorType>
void SparseBlas<GPUContext>::SDDMM(bool transa,
bool transb,
T alpha,
const DenseTensor& mat_a,
const DenseTensor& mat_b,
T beta,
TensorType* mat_out) const {
auto a_descriptor = RocSparseDnMatDescriptor<T>(mat_a, dev_ctx_);
auto b_descriptor = RocSparseDnMatDescriptor<T>(mat_b, dev_ctx_);
auto out_descriptor = RocSparseSpMatDescriptor<T>(*mat_out, dev_ctx_);
rocsparse_datatype gpu_type = GetGpuDataType<T>();
size_t buffer_size = 0;
dev_ctx_.CusparseCall([&](rocsparse_handle handle) {
phi::dynload::rocsparse_sddmm_buffer_size(handle,
GetTransposeOperation(transa),
GetTransposeOperation(transb),
&alpha,
a_descriptor.descriptor(),
b_descriptor.descriptor(),
&beta,
out_descriptor.descriptor(),
gpu_type,
rocsparse_sddmm_alg_default,
&buffer_size);
});
phi::Allocator::AllocationPtr tmp_buffer = phi::memory_utils::Alloc(
dev_ctx_.GetPlace(),
buffer_size,
phi::Stream(reinterpret_cast<phi::StreamId>(dev_ctx_.stream())));
void* tmp_buffer_ptr = tmp_buffer->ptr();
dev_ctx_.CusparseCall([&](rocsparse_handle handle) {
phi::dynload::rocsparse_sddmm_preprocess(handle,
GetTransposeOperation(transa),
GetTransposeOperation(transb),
&alpha,
a_descriptor.descriptor(),
b_descriptor.descriptor(),
&beta,
out_descriptor.descriptor(),
gpu_type,
rocsparse_sddmm_alg_default,
tmp_buffer_ptr);
});
dev_ctx_.CusparseCall([&](rocsparse_handle handle) {
phi::dynload::rocsparse_sddmm(handle,
GetTransposeOperation(transa),
GetTransposeOperation(transb),
&alpha,
a_descriptor.descriptor(),
b_descriptor.descriptor(),
&beta,
out_descriptor.descriptor(),
gpu_type,
rocsparse_sddmm_alg_default,
tmp_buffer_ptr);
});
}
#endif
} // namespace sparse
} // namespace funcs
} // namespace phi
@@ -0,0 +1,44 @@
/* Copyright (c) 2022 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. */
#pragma once
namespace phi {
namespace funcs {
namespace sparse {
// brief: calculation the distance between start and end
template <typename T>
__global__ void DistanceKernel(const T* start, const T* end, T* distance) {
if (threadIdx.x == 0) {
*distance = end - start;
}
}
inline __device__ bool SetBits(const int value, int* ptr) {
const int index = value >> 5;
const int mask = 1 << (value & 31);
const int old = atomicOr(ptr + index, mask);
return (mask & old) != 0;
}
inline __device__ bool TestBits(const int value, const int* ptr) {
const int index = value >> 5;
const int mask = 1 << (value & 31);
return (mask & ptr[index]) != 0;
}
} // namespace sparse
} // namespace funcs
} // namespace phi