// 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. #include "paddle/phi/kernels/sparse/unary_kernel.h" #include "paddle/phi/backends/gpu/gpu_context.h" #include "paddle/phi/backends/gpu/gpu_launch_config.h" #include "paddle/phi/backends/gpu/gpu_primitives.h" #include "paddle/phi/common/scalar.h" #include "paddle/phi/core/kernel_registry.h" #include "paddle/phi/core/visit_type.h" #include "paddle/phi/kernels/cast_kernel.h" #include "paddle/phi/kernels/cum_kernel.h" #include "paddle/phi/kernels/empty_kernel.h" #include "paddle/phi/kernels/funcs/elementwise_base.h" #include "paddle/phi/kernels/index_select_kernel.h" #include "paddle/phi/kernels/reduce_sum_kernel.h" #include "paddle/phi/kernels/reshape_kernel.h" #include "paddle/phi/kernels/sparse/empty_kernel.h" #include "paddle/phi/kernels/sparse/sparse_utils_kernel.h" namespace phi { namespace sparse { template __global__ void SumCooCudaKernel(const IntT* x_indices_data, const T* x_values_data, const int64_t x_nnz, const int64_t dense_dim, const int64_t sparse_dim, const int64_t axis, const bool keep_dim, IntT* out_indices_data, T* out_values_data) { CUDA_KERNEL_LOOP_TYPE(index_i, x_nnz, int64_t) { int64_t i = 0; for (int j = 0; j < dense_dim; ++j) { out_values_data[j + index_i * dense_dim] = 0; } int64_t _index_j_ = static_cast(blockIdx.y) * blockDim.y + threadIdx.y; for (auto index_j = _index_j_; index_j < x_nnz; index_j += static_cast(blockDim.y) * gridDim.y) { // Determine whether the index_i and index_j elements have the same // indices in all dimensions except for the specified axis dimension. bool same = true; for (int j = 0; j < sparse_dim + !keep_dim; ++j) { if (j != axis && x_indices_data[index_i + j * x_nnz] != x_indices_data[index_j + j * x_nnz]) { same = false; break; } } if (same) { for (int j = 0; j < dense_dim; ++j) { CudaAtomicAdd(&out_values_data[j + index_i * dense_dim], x_values_data[j + index_j * dense_dim]); } } } if (_index_j_ != 0) { return; } if (keep_dim) { for (int j = 0; j < sparse_dim; ++j) { if (j == axis) { out_indices_data[index_i + j * x_nnz] = 0; } else { out_indices_data[index_i + j * x_nnz] = x_indices_data[index_i + j * x_nnz]; } } return; } for (int j = 0; j < sparse_dim; ++j) { // out_indices_data [sparse_dim, x.nnz()] int64_t x_indices_data_offset; if (j < axis) { x_indices_data_offset = index_i + j * x_nnz; } else { x_indices_data_offset = index_i + (j + 1) * x_nnz; } out_indices_data[index_i + j * x_nnz] = x_indices_data[x_indices_data_offset]; } } } __global__ void SumAllCsrCudaKernel(int64_t* out_crows_data, int64_t* out_cols_data) { CUDA_KERNEL_LOOP_TYPE(index, 2, int64_t) { out_crows_data[index] = index; if (index == 0) { out_cols_data[0] = 0; } } } template __global__ void SumCsr2DCudaKernel(const int64_t* x_crows_data, const T* x_values_data, const int64_t x_dim0, int64_t* out_crows_data, int64_t* out_cols_data, T* out_values_data) { CUDA_KERNEL_LOOP_TYPE(index, x_dim0 + 1, int64_t) { out_crows_data[index] = index; if (index != x_dim0) { out_cols_data[index] = 0; T sum_value = 0; for (auto j = x_crows_data[index]; j < x_crows_data[index + 1]; ++j) { sum_value += x_values_data[j]; } out_values_data[index] = sum_value; } } } template __global__ void SumCsr3DCudaKernel(const int64_t* x_crows_data, const T* x_values_data, const int64_t x_dim0, const int64_t x_dim1, const int64_t* batch_nnz_data, int64_t* out_crows_data, int64_t* out_cols_data, T* out_values_data) { { CUDA_KERNEL_LOOP_TYPE(index, x_dim0 * x_dim1, int64_t) { out_cols_data[index] = 0; } } CUDA_KERNEL_LOOP_TYPE(index, x_dim0 * (x_dim1 + 1), int64_t) { int64_t batch = index / (x_dim1 + 1); int64_t number = index % (x_dim1 + 1); out_crows_data[index] = number; if (number != x_dim1) { T sum_value = 0; int64_t x_values_data_offset; if (batch == 0) { x_values_data_offset = 0; } else { x_values_data_offset = batch_nnz_data[batch - 1]; } for (int64_t j = x_crows_data[index]; j < x_crows_data[index + 1]; ++j) { sum_value += x_values_data[j + x_values_data_offset]; } // `index - batch` would never exceed x_dim0 * x_dim1. out_values_data[index - batch] = sum_value; } } } template void SumCooGPU0Kernel(const Context& dev_ctx, const SparseCooTensor& x, const IntArray& axis, DataType dtype, bool keep_dim, SparseCooTensor* out) { auto sparse_dim = x.sparse_dim(); // create out sparse tensor const auto& x_dims = x.dims(); const auto& x_indices = x.indices(); const auto& x_values = x.values(); DDim out_dims; DenseTensor out_indices; DenseTensor out_values; if (keep_dim) { out_dims = make_ddim(std::vector(x_dims.size(), 1)); out_indices = Empty(dev_ctx, {sparse_dim, 1}); } else { out_dims = make_ddim({1}); out_indices = Empty(dev_ctx, {1, 1}); } funcs::SetConstant set_out_indices; set_out_indices(dev_ctx, &out_indices, static_cast(0)); out_values = phi::Sum(dev_ctx, x.values(), {}, dtype, keep_dim); out->SetMember(out_indices, out_values, out_dims, x.coalesced()); } template void SumCooGPU1Kernel(const Context& dev_ctx, const SparseCooTensor& x, const IntArray& axis, DataType dtype, bool keep_dim, SparseCooTensor* out) { auto sparse_dim = x.sparse_dim(); // create out sparse tensor const auto& x_dims = x.dims(); const auto& x_indices = x.indices(); const auto& x_values = x.values(); DDim out_dims; DenseTensor out_indices; DenseTensor out_values; auto n_dim = x.dims().size(); auto dim = axis[0] < 0 ? x_dims.size() + axis[0] : axis[0]; std::vector dims; for (int i = 0; i < n_dim; ++i) { if (i != dim) { dims.emplace_back(x.dims()[i]); } else if (keep_dim || (dim < sparse_dim && sparse_dim == 1)) { dims.emplace_back(1); } } out_dims = make_ddim(dims); if (dim >= sparse_dim) { out_indices = x_indices; dim = dim - sparse_dim + 1; out_values = phi::Sum(dev_ctx, x.values(), {dim}, dtype, keep_dim); out->SetMember(out_indices, out_values, out_dims, x.coalesced()); return; } // Ensure the sparse_dim is not less than 1. if (sparse_dim == 1) { keep_dim = true; } // if axis in sparse_dim and keep_dim, sparse_dim will be reduced. if (!keep_dim) { sparse_dim -= 1; } std::vector out_values_dims; out_values_dims.push_back(x.nnz()); for (auto i = 1; i < x.values().dims().size(); ++i) { out_values_dims.push_back(static_cast(x.values().dims()[i])); } int64_t dense_dim = std::accumulate(out_values_dims.begin() + 1, out_values_dims.end(), 1, std::multiplies()); out_indices = Empty(dev_ctx, {sparse_dim, x.nnz()}); out_values = Empty(dev_ctx, out_values_dims); const auto* x_indices_data = x_indices.data(); const auto* x_values_data = x_values.data(); auto* out_indices_data = out_indices.data(); auto* out_values_data = out_values.data(); auto config = backends::gpu::GetGpuLaunchConfig2D(dev_ctx, x.nnz(), x.nnz()); SumCooCudaKernel<<>>(x_indices_data, x_values_data, x.nnz(), dense_dim, sparse_dim, dim, keep_dim, out_indices_data, out_values_data); if (dtype != phi::DataType::UNDEFINED && dtype != x.dtype()) { out_values = Cast(dev_ctx, out_values, dtype); } out->SetMember(out_indices, out_values, out_dims, x.coalesced()); } template void SumCooKernel(const Context& dev_ctx, const SparseCooTensor& x, const IntArray& axis, DataType dtype, bool keep_dim, SparseCooTensor* out) { const size_t n_dim = axis.size(); if (n_dim == 0) { PD_VISIT_BASE_INTEGRAL_TYPES(x.indices().dtype(), "SumCooGPUKernel", ([&] { SumCooGPU0Kernel( dev_ctx, x, axis, dtype, keep_dim, out); })); } else { PD_VISIT_BASE_INTEGRAL_TYPES(x.indices().dtype(), "SumCooGPUKernel", ([&] { SumCooGPU1Kernel( dev_ctx, x, axis, dtype, keep_dim, out); })); } } template void SumCsr0Kernel(const Context& dev_ctx, const SparseCsrTensor& x, const IntArray& axis, DataType dtype, bool keep_dim, SparseCsrTensor* out) { auto x_dim0 = x.dims()[0]; auto x_dim1 = x.dims()[1]; const auto& x_crows = x.crows(); const auto& x_values = x.values(); const auto* x_crows_data = x_crows.data(); const auto* x_values_data = x_values.data(); DenseTensor out_crows, out_cols, out_values; DDim out_dims; if (keep_dim && x.dims().size() == 3) { out_dims = make_ddim({1, 1, 1}); } else { out_dims = make_ddim({1, 1}); } out_crows = Empty(dev_ctx, {2}); // crows = [0, 1] out_cols = Empty(dev_ctx, {1}); // crows = [0] auto* out_crows_data = out_crows.data(); auto* out_cols_data = out_cols.data(); auto config = backends::gpu::GetGpuLaunchConfig1D(dev_ctx, 2, 1); SumAllCsrCudaKernel<<>>(out_crows_data, out_cols_data); out_values = phi::Sum(dev_ctx, x.values(), {}, dtype, true); out->SetMember(out_crows, out_cols, out_values, out_dims); } template void SumCsr1Kernel(const Context& dev_ctx, const SparseCsrTensor& x, const IntArray& axis, DataType dtype, bool keep_dim, SparseCsrTensor* out) { auto x_dim0 = x.dims()[0]; auto x_dim1 = x.dims()[1]; const auto& x_crows = x.crows(); const auto& x_values = x.values(); const auto* x_crows_data = x_crows.data(); const auto* x_values_data = x_values.data(); DenseTensor out_crows, out_cols, out_values; DDim out_dims; out_crows = EmptyLike(dev_ctx, x.crows()); auto* out_crows_data = out_crows.data(); if (x.dims().size() == 2) { out_cols = Empty(dev_ctx, {x_dim0}); out_values = Empty(dev_ctx, {x_dim0}); auto* out_cols_data = out_cols.data(); auto* out_values_data = out_values.data(); out_dims = make_ddim({x_dim0, 1}); auto config = backends::gpu::GetGpuLaunchConfig1D(dev_ctx, x_dim0 + 1, 1); SumCsr2DCudaKernel<<>>(x_crows_data, x_values_data, x_dim0, out_crows_data, out_cols_data, out_values_data); } else { out_cols = Empty(dev_ctx, {x_dim0 * x_dim1}); out_values = Empty(dev_ctx, {x_dim0 * x_dim1}); auto* out_cols_data = out_cols.data(); auto* out_values_data = out_values.data(); if (keep_dim) { out_dims = make_ddim({x_dim0, x_dim1, 1}); } else { out_dims = make_ddim({x_dim0, x_dim1}); } DenseTensor x_crows_reshape = Reshape(dev_ctx, x_crows, {x_dim0, x_dim1 + 1}); DenseTensor last_indices = Empty(dev_ctx, {1}); funcs::SetConstant set_constant; set_constant(dev_ctx, &last_indices, static_cast(x_dim1)); DenseTensor x_crows_last = Empty(dev_ctx, {x_dim0, 1}); IndexSelectKernel( dev_ctx, x_crows_reshape, last_indices, 1, &x_crows_last); DenseTensor batch_nnz = Empty(dev_ctx, {x_dim0, 1}); CumsumKernel( dev_ctx, x_crows_last, Scalar(0), false, false, false, &batch_nnz); auto* batch_nnz_data = batch_nnz.data(); auto config = backends::gpu::GetGpuLaunchConfig1D( dev_ctx, x.dims()[0] * (x.dims()[1] + 1), 1); SumCsr3DCudaKernel<<>>(x_crows_data, x_values_data, x_dim0, x_dim1, batch_nnz_data, out_crows_data, out_cols_data, out_values_data); } if (dtype != phi::DataType::UNDEFINED && dtype != x.dtype()) { out_values = Cast(dev_ctx, out_values, dtype); } out->SetMember(out_crows, out_cols, out_values, out_dims); } template void SumCsrKernel(const Context& dev_ctx, const SparseCsrTensor& x, const IntArray& axis, DataType dtype, bool keep_dim, SparseCsrTensor* out) { size_t n_dim = axis.size(); if (n_dim == 0) { SumCsr0Kernel(dev_ctx, x, axis, dtype, keep_dim, out); } else { PADDLE_ENFORCE_EQ(axis[0], -1, common::errors::Unimplemented( "`axis` of SumCsrKernel only support None or -1 now." "More number will be supported in the future.")); SumCsr1Kernel(dev_ctx, x, axis, dtype, keep_dim, out); } } } // namespace sparse } // namespace phi PD_REGISTER_KERNEL(sum_coo, GPU, ALL_LAYOUT, phi::sparse::SumCooKernel, float, double, int, int64_t) { kernel->OutputAt(0).SetDataType(phi::DataType::UNDEFINED); } PD_REGISTER_KERNEL(sum_csr, GPU, ALL_LAYOUT, phi::sparse::SumCsrKernel, float, double, int, int64_t) { kernel->OutputAt(0).SetDataType(phi::DataType::UNDEFINED); }