Files
2026-07-13 12:40:42 +08:00

413 lines
16 KiB
Plaintext

// 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.
#include "paddle/phi/kernels/graph_reindex_kernel.h"
#include <thrust/copy.h>
#include <thrust/device_vector.h>
#include <thrust/reduce.h>
#include <thrust/scan.h>
#include <thrust/sequence.h>
#include "paddle/phi/backends/gpu/gpu_context.h"
#include "paddle/phi/backends/gpu/gpu_primitives.h"
#include "paddle/phi/common/memory_utils.h"
#include "paddle/phi/core/kernel_registry.h"
#include "paddle/phi/kernels/funcs/cub.h"
#include "paddle/phi/kernels/gpu/graph_reindex_funcs.h"
namespace phi {
constexpr int WARP_SIZE = 32;
const int CUDA_NUM_THREADS = 512;
inline int GET_BLOCKS(const int N) {
return (N + CUDA_NUM_THREADS - 1) / CUDA_NUM_THREADS;
}
template <typename T>
__global__ void InitializeHashTable(T* tensor, int len) {
CUDA_KERNEL_LOOP(idx, len) { tensor[idx] = -1; }
}
template <typename T, typename Context>
std::shared_ptr<Allocation> FillHashTable(const Context& dev_ctx,
const T* input,
int num_input,
int64_t len_hashtable,
T* keys,
int* values,
int* key_index,
int* final_nodes_len) {
const auto place = dev_ctx.GetPlace();
int block = 1024;
int max_grid_dimx = dev_ctx.GetCUDAMaxGridDimSize()[0];
int grid_tmp = (num_input + block - 1) / block;
int grid = grid_tmp < max_grid_dimx ? grid_tmp : max_grid_dimx;
// Insert data into keys and values.
BuildHashTable<T><<<grid, block, 0, dev_ctx.stream()>>>(
input, num_input, len_hashtable, keys, key_index);
// Get item index count.
thrust::device_vector<int> item_count(num_input + 1, 0);
GetItemIndexCount<T><<<grid, block, 0, dev_ctx.stream()>>>(
input,
thrust::raw_pointer_cast(item_count.data()),
num_input,
len_hashtable,
keys,
key_index);
thrust::exclusive_scan(
item_count.begin(), item_count.end(), item_count.begin());
int total_unique_items = item_count[num_input];
auto unique_items =
memory_utils::AllocShared(place, total_unique_items * sizeof(T));
T* unique_items_data = reinterpret_cast<T*>(unique_items->ptr());
*final_nodes_len = total_unique_items;
// Get unique items
FillUniqueItems<T><<<grid, block, 0, dev_ctx.stream()>>>(
input,
num_input,
len_hashtable,
unique_items_data,
thrust::raw_pointer_cast(item_count.data()),
keys,
values,
key_index);
return unique_items;
}
template <typename T, typename Context>
void FillBufferHashTable(const Context& dev_ctx,
const T* input,
int num_input,
thrust::device_vector<T>* unique_items,
int* values,
int* key_index) {
int block = 1024;
int max_grid_dimx = dev_ctx.GetCUDAMaxGridDimSize()[0];
int grid_tmp = (num_input + block - 1) / block;
int grid = grid_tmp < max_grid_dimx ? grid_tmp : max_grid_dimx;
// Insert data.
BuildHashTable<T>
<<<grid, block, 0, dev_ctx.stream()>>>(input, num_input, key_index);
// Get item index count.
thrust::device_vector<int> item_count(num_input + 1, 0);
GetItemIndexCount<T><<<grid, block, 0, dev_ctx.stream()>>>(
input, thrust::raw_pointer_cast(item_count.data()), num_input, key_index);
thrust::exclusive_scan(
item_count.begin(), item_count.end(), item_count.begin());
size_t total_unique_items = item_count[num_input];
unique_items->resize(total_unique_items);
// Get unique items
FillUniqueItems<T><<<grid, block, 0, dev_ctx.stream()>>>(
input,
num_input,
thrust::raw_pointer_cast(unique_items->data()),
thrust::raw_pointer_cast(item_count.data()),
values,
key_index);
}
template <typename T, typename Context>
void ResetBufferHashTable(const Context& dev_ctx,
const T* input,
int num_input,
thrust::device_vector<T>* unique_items,
int* values,
int* key_index) {
int block = 1024;
int max_grid_dimx = dev_ctx.GetCUDAMaxGridDimSize()[0];
int grid_tmp = (unique_items->size() + block - 1) / block;
int grid = grid_tmp < max_grid_dimx ? grid_tmp : max_grid_dimx;
ResetHashTable<T><<<grid, block, 0, dev_ctx.stream()>>>(
thrust::raw_pointer_cast(unique_items->data()),
unique_items->size(),
key_index,
values);
}
template <typename T, typename Context>
void ReindexSrc(const Context& dev_ctx,
T* edges_src,
T* keys,
int* values,
int64_t num_edges,
int64_t table_size) {
// Fill outputs with reindex result.
int block = 1024;
int max_grid_dimx = dev_ctx.GetCUDAMaxGridDimSize()[0];
int grid_tmp = (num_edges + block - 1) / block;
int grid = grid_tmp < max_grid_dimx ? grid_tmp : max_grid_dimx;
ReindexSrcOutput<T><<<grid, block, 0, dev_ctx.stream()>>>(
edges_src, num_edges, table_size, keys, values);
}
template <typename T, typename Context>
void Reindex(const Context& dev_ctx,
const T* inputs,
thrust::device_ptr<T> src_outputs,
thrust::device_vector<T>* out_nodes,
int num_inputs,
int num_edges) {
out_nodes->resize(num_inputs + num_edges);
thrust::copy(inputs, inputs + num_inputs, out_nodes->begin());
thrust::copy(
src_outputs, src_outputs + num_edges, out_nodes->begin() + num_inputs);
// Fill hash table
int64_t num = out_nodes->size();
int64_t log_num = 1 << static_cast<size_t>(1 + std::log2(num >> 1));
int64_t table_size = log_num << 1;
auto keys = memory_utils::Alloc(dev_ctx.GetPlace(), table_size * sizeof(T));
auto values =
memory_utils::Alloc(dev_ctx.GetPlace(), table_size * sizeof(int));
auto key_index =
memory_utils::Alloc(dev_ctx.GetPlace(), table_size * sizeof(int));
T* keys_ptr = reinterpret_cast<T*>(keys->ptr());
int* values_ptr = reinterpret_cast<int*>(values->ptr());
int* key_index_ptr = reinterpret_cast<int*>(key_index->ptr());
InitializeHashTable<T>
<<<GET_BLOCKS(table_size), CUDA_NUM_THREADS, 0, dev_ctx.stream()>>>(
keys_ptr, table_size);
InitializeHashTable<int>
<<<GET_BLOCKS(table_size), CUDA_NUM_THREADS, 0, dev_ctx.stream()>>>(
values_ptr, table_size);
InitializeHashTable<int>
<<<GET_BLOCKS(table_size), CUDA_NUM_THREADS, 0, dev_ctx.stream()>>>(
key_index_ptr, table_size);
int unique_len = 0;
std::shared_ptr<Allocation> unique_items =
FillHashTable<T, Context>(dev_ctx,
thrust::raw_pointer_cast(out_nodes->data()),
out_nodes->size(),
table_size,
keys_ptr,
values_ptr,
key_index_ptr,
&unique_len);
out_nodes->resize(unique_len);
T* unique_items_data = reinterpret_cast<T*>(unique_items->ptr());
thrust::copy(thrust::device_pointer_cast(unique_items_data),
thrust::device_pointer_cast(unique_items_data) + unique_len,
out_nodes->begin());
ReindexSrc<T, Context>(dev_ctx,
thrust::raw_pointer_cast(src_outputs),
keys_ptr,
values_ptr,
num_edges,
table_size);
}
template <typename T, typename Context>
void BufferReindex(const Context& dev_ctx,
const T* inputs,
thrust::device_ptr<T> src_outputs,
thrust::device_vector<T>* out_nodes,
int num_inputs,
int* hashtable_value,
int* hashtable_index,
int num_edges) {
out_nodes->resize(num_inputs + num_edges);
thrust::copy(inputs, inputs + num_inputs, out_nodes->begin());
thrust::copy(
src_outputs, src_outputs + num_edges, out_nodes->begin() + num_inputs);
thrust::device_vector<T> unique_nodes;
unique_nodes.clear();
// Fill hash table
FillBufferHashTable<T, Context>(dev_ctx,
thrust::raw_pointer_cast(out_nodes->data()),
out_nodes->size(),
&unique_nodes,
hashtable_value,
hashtable_index);
out_nodes->resize(unique_nodes.size());
thrust::copy(unique_nodes.begin(), unique_nodes.end(), out_nodes->begin());
// Fill outputs with reindex result.
int block = 1024;
int max_grid_dimx = dev_ctx.GetCUDAMaxGridDimSize()[0];
int grid_tmp = (num_edges + block - 1) / block;
int grid = grid_tmp < max_grid_dimx ? grid_tmp : max_grid_dimx;
ReindexSrcOutput<T><<<grid, block, 0, dev_ctx.stream()>>>(
thrust::raw_pointer_cast(src_outputs), num_edges, hashtable_value);
ResetBufferHashTable<T, Context>(dev_ctx,
thrust::raw_pointer_cast(out_nodes->data()),
out_nodes->size(),
&unique_nodes,
hashtable_value,
hashtable_index);
}
template <typename T, int BLOCK_WARPS, int TILE_SIZE>
__global__ void GetDstEdgeCUDAKernel(const int64_t num_rows,
const int* in_rows,
const int* dst_counts,
const int* dst_ptr,
T* dst_outputs) {
assert(blockDim.x == WARP_SIZE);
assert(blockDim.y == BLOCK_WARPS);
int64_t out_row = static_cast<int64_t>(blockIdx.x) * TILE_SIZE +
static_cast<int64_t>(threadIdx.y);
const int64_t last_row =
min(static_cast<int64_t>(blockIdx.x + 1) * TILE_SIZE, num_rows);
while (out_row < last_row) {
const int row = in_rows[out_row];
const int dst_sample_size = dst_counts[out_row];
const int out_row_start = dst_ptr[out_row];
for (int idx = threadIdx.x; idx < dst_sample_size; idx += WARP_SIZE) {
dst_outputs[out_row_start + idx] = row;
}
out_row += BLOCK_WARPS;
}
}
template <typename T, typename Context>
void ReindexDst(const Context& dev_ctx,
T* reindex_dst_data,
int* scan_dst_data,
const int* count_data,
int num_edge_types,
int node_len) {
constexpr int BLOCK_WARPS = 128 / WARP_SIZE;
constexpr int TILE_SIZE = BLOCK_WARPS * 16;
const dim3 block(WARP_SIZE, BLOCK_WARPS);
const dim3 grid((node_len + TILE_SIZE - 1) / TILE_SIZE);
int begin = 0, count_i = 0;
thrust::device_vector<int> dst_ptr(node_len + 1, 0);
for (int i = 0; i < num_edge_types; i++) {
thrust::inclusive_scan(
thrust::device_pointer_cast(count_data) + i * node_len,
thrust::device_pointer_cast(count_data) + (i + 1) * node_len,
dst_ptr.begin() + 1);
GetDstEdgeCUDAKernel<T, BLOCK_WARPS, TILE_SIZE>
<<<grid, block, 0, dev_ctx.stream()>>>(
node_len,
scan_dst_data,
count_data + i * node_len,
thrust::raw_pointer_cast(dst_ptr.data()),
reindex_dst_data + begin);
#ifdef PADDLE_WITH_HIP
hipMemcpy(&count_i,
thrust::raw_pointer_cast(dst_ptr.data()) + node_len,
sizeof(int),
hipMemcpyDeviceToHost);
#else
cudaMemcpy(&count_i,
thrust::raw_pointer_cast(dst_ptr.data()) + node_len,
sizeof(int),
cudaMemcpyDeviceToHost);
#endif
begin += count_i;
}
}
template <typename T, typename Context>
void GraphReindexKernel(const Context& dev_ctx,
const DenseTensor& x,
const DenseTensor& neighbors,
const DenseTensor& count,
const optional<DenseTensor>& hashtable_value,
const optional<DenseTensor>& hashtable_index,
DenseTensor* reindex_src,
DenseTensor* reindex_dst,
DenseTensor* out_nodes) {
bool flag_buffer_hashtable =
hashtable_value.is_initialized() && hashtable_index.is_initialized();
const T* x_data = x.data<T>();
const T* neighbors_data = neighbors.data<T>();
const int* count_data = count.data<int>();
int64_t bs = x.dims()[0];
PADDLE_ENFORCE_NE(
0,
bs,
errors::InvalidArgument("The first of dims should not be equal to 0."));
int64_t num_edges = neighbors.dims()[0];
reindex_src->Resize({num_edges});
T* reindex_src_data = dev_ctx.template Alloc<T>(reindex_src);
thrust::device_ptr<T> src_outputs(reindex_src_data);
thrust::device_vector<T> unique_nodes;
thrust::copy(neighbors_data, neighbors_data + num_edges, src_outputs);
if (flag_buffer_hashtable) {
// Here we directly use buffer tensor to act as a hash table.
DenseTensor hashtable_value_out(hashtable_value->type());
const auto* ph_value = hashtable_value.get_ptr();
hashtable_value_out.ShareDataWith(*ph_value);
DenseTensor hashtable_index_out(hashtable_index->type());
const auto* ph_index = hashtable_index.get_ptr();
hashtable_index_out.ShareDataWith(*ph_index);
int* hashtable_value_data =
dev_ctx.template Alloc<int>(&hashtable_value_out);
int* hashtable_index_data =
dev_ctx.template Alloc<int>(&hashtable_index_out);
BufferReindex<T, Context>(dev_ctx,
x_data,
src_outputs,
&unique_nodes,
bs,
hashtable_value_data,
hashtable_index_data,
num_edges);
} else {
Reindex<T, Context>(
dev_ctx, x_data, src_outputs, &unique_nodes, bs, num_edges);
}
// Get reindex dst edge.
// Add support for multi-type edges reindex.
int64_t num_ac_count = count.dims()[0];
int64_t num_edge_types = num_ac_count / bs;
// TODO(large-tensor): downstream functors may still use int
thrust::device_vector<int> unique_dst_reindex(bs);
thrust::sequence(unique_dst_reindex.begin(), unique_dst_reindex.end());
reindex_dst->Resize({num_edges});
T* reindex_dst_data = dev_ctx.template Alloc<T>(reindex_dst);
ReindexDst<T, Context>(dev_ctx,
reindex_dst_data,
thrust::raw_pointer_cast(unique_dst_reindex.data()),
count_data,
num_edge_types,
bs);
// TODO(large-tensor): Resize not support int64
PADDLE_ENFORCE_LE_INT_MAX(unique_nodes.size(), "unique_nodes.size()");
out_nodes->Resize({static_cast<int>(unique_nodes.size())});
T* out_nodes_data = dev_ctx.template Alloc<T>(out_nodes);
thrust::copy(unique_nodes.begin(), unique_nodes.end(), out_nodes_data);
}
} // namespace phi
PD_REGISTER_KERNEL(
graph_reindex, GPU, ALL_LAYOUT, phi::GraphReindexKernel, int, int64_t) {}