// Copyright (c) 2024 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_khop_sampler_kernel.h" #include #include #include #include #include #include "paddle/phi/backends/cpu/cpu_context.h" #include "paddle/phi/core/kernel_registry.h" namespace phi { template void SampleUniqueNeighbors(bidiiter begin, bidiiter end, int num_samples) { int left_num = std::distance(begin, end); std::random_device rd; std::mt19937 rng{rd()}; std::uniform_int_distribution dice_distribution( 0, std::numeric_limits::max()); for (int i = 0; i < num_samples; i++) { bidiiter r = begin; int random_step = dice_distribution(rng) % left_num; std::advance(r, random_step); std::swap(*begin, *r); ++begin; --left_num; } } template void SampleUniqueNeighborsWithEids(bidiiter src_begin, bidiiter src_end, bidiiter eid_begin, bidiiter eid_end, int num_samples) { int left_num = std::distance(src_begin, src_end); std::random_device rd; std::mt19937 rng{rd()}; std::uniform_int_distribution dice_distribution( 0, std::numeric_limits::max()); for (int i = 0; i < num_samples; i++) { bidiiter r1 = src_begin, r2 = eid_begin; int random_step = dice_distribution(rng) % left_num; std::advance(r1, random_step); std::advance(r2, random_step); std::swap(*src_begin, *r1); std::swap(*eid_begin, *r2); ++src_begin; ++eid_begin; --left_num; } } template void SampleNeighbors(const T* src, const T* dst_count, const T* src_eids, std::vector* inputs, std::vector* outputs, std::vector* output_counts, std::vector* outputs_eids, int k, int bs, bool is_first_layer, bool is_last_layer, bool return_eids) { // Allocate the memory of outputs // Collect the neighbors size std::vector> out_src_vec; std::vector> out_eids_vec; // `sample_cumsum_sizes` record the start position and end position after the // sample. std::vector sample_cumsum_sizes(bs + 1); int total_neighbors = 0; // `total_neighbors` the size of output after the sample sample_cumsum_sizes[0] = total_neighbors; for (int i = 0; i < bs; i++) { T node = inputs->data()[i]; T begin = dst_count[node]; T end = dst_count[node + 1]; int cap = end - begin; int sample_size = cap > k ? k : cap; total_neighbors += sample_size; sample_cumsum_sizes[i + 1] = total_neighbors; std::vector out_src; out_src.resize(cap); out_src_vec.emplace_back(out_src); if (return_eids) { std::vector out_eids; out_eids.resize(cap); out_eids_vec.emplace_back(out_eids); } } if (is_first_layer) { PADDLE_ENFORCE_GT( total_neighbors, 0, common::errors::InvalidArgument("The input nodes `X` should have at " "least one neighbors, but none of the " "input nodes have neighbors.")); } output_counts->resize(bs); outputs->resize(total_neighbors); if (return_eids) { outputs_eids->resize(total_neighbors); } #ifdef PADDLE_WITH_MKLML #pragma omp parallel for #endif // Sample the neighbour parallelism for (int i = 0; i < bs; i++) { T node = inputs->data()[i]; T begin = dst_count[node]; T end = dst_count[node + 1]; int cap = end - begin; if (k < cap) { std::copy(src + begin, src + end, out_src_vec[i].begin()); if (return_eids) { std::copy(src_eids + begin, src_eids + end, out_eids_vec[i].begin()); SampleUniqueNeighborsWithEids(out_src_vec[i].begin(), out_src_vec[i].end(), out_eids_vec[i].begin(), out_eids_vec[i].end(), k); } else { SampleUniqueNeighbors(out_src_vec[i].begin(), out_src_vec[i].end(), k); } *(output_counts->data() + i) = k; } else { std::copy(src + begin, src + end, out_src_vec[i].begin()); if (return_eids) { std::copy(src_eids + begin, src_eids + end, out_eids_vec[i].begin()); } *(output_counts->data() + i) = cap; } } #ifdef PADDLE_WITH_MKLML #pragma omp parallel for #endif // Copy the results parallelism for (int i = 0; i < bs; i++) { int sample_size = sample_cumsum_sizes[i + 1] - sample_cumsum_sizes[i]; std::copy(out_src_vec[i].begin(), out_src_vec[i].begin() + sample_size, outputs->data() + sample_cumsum_sizes[i]); if (return_eids) { std::copy(out_eids_vec[i].begin(), out_eids_vec[i].begin() + sample_size, outputs_eids->data() + sample_cumsum_sizes[i]); } } if (!is_last_layer) { std::sort(inputs->begin(), inputs->end()); std::vector outputs_sort(outputs->size()); std::copy(outputs->begin(), outputs->end(), outputs_sort.begin()); std::sort(outputs_sort.begin(), outputs_sort.end()); auto outputs_sort_end = std::unique(outputs_sort.begin(), outputs_sort.end()); outputs_sort.resize(std::distance(outputs_sort.begin(), outputs_sort_end)); std::vector unique_outputs(outputs_sort.size()); auto unique_outputs_end = std::set_difference(outputs_sort.begin(), outputs_sort.end(), inputs->begin(), inputs->end(), unique_outputs.begin()); inputs->resize(std::distance(unique_outputs.begin(), unique_outputs_end)); std::copy(unique_outputs.begin(), unique_outputs_end, inputs->begin()); } } template void GraphKhopSamplerKernel(const Context& dev_ctx, const DenseTensor& row, const DenseTensor& col_ptr, const DenseTensor& x, const optional& eids, const std::vector& sample_sizes, bool return_eids, DenseTensor* out_src, DenseTensor* out_dst, DenseTensor* sample_index, DenseTensor* reindex_x, DenseTensor* out_eids) { // 1. Get sample neighbors operators' inputs. auto row_dims = row.dims(); auto row_dims_lens = row_dims.size(); auto col_dims = col_ptr.dims(); auto col_dims_lens = col_dims.size(); auto x_dims = x.dims(); auto x_dims_lens = x_dims.size(); for (int i = 0; i < row_dims_lens; i++) { PADDLE_ENFORCE_NE( row_dims[i], 0, common::errors::InvalidArgument("The size of Row(X) should not be 0.")); } for (int i = 0; i < col_dims_lens; i++) { PADDLE_ENFORCE_NE(col_dims[i], 0, common::errors::InvalidArgument( "The size of Col_Ptr(X) should not be 0.")); } for (int i = 0; i < x_dims_lens; i++) { PADDLE_ENFORCE_NE(x_dims[i], 0, common::errors::InvalidArgument( "The size of Input_Node(X) should not be 0.")); } const T* src_data = row.data(); const T* dst_count_data = col_ptr.data(); const T* p_vertices = x.data(); int bs = static_cast(x.dims()[0]); // 2. Get unique input nodes(X). std::vector inputs(bs); std::copy(p_vertices, p_vertices + bs, inputs.begin()); auto unique_inputs_end = std::unique(inputs.begin(), inputs.end()); inputs.resize(std::distance(inputs.begin(), unique_inputs_end)); // 3. Sample neighbors. We should distinguish w/o "Eids". std::vector outputs; std::vector output_counts; std::vector outputs_eids; std::vector> dst_vec; dst_vec.emplace_back(inputs); std::vector> outputs_vec; std::vector> output_counts_vec; std::vector> outputs_eids_vec; int num_layers = sample_sizes.size(); bool is_last_layer = false, is_first_layer = true; if (return_eids) { const T* src_eids_data = eids.get_ptr()->data(); for (int i = 0; i < num_layers; i++) { if (i == num_layers - 1) { is_last_layer = true; } if (inputs.size() == 0) { break; } if (i > 0) { dst_vec.emplace_back(inputs); is_first_layer = false; } SampleNeighbors(src_data, dst_count_data, src_eids_data, &inputs, &outputs, &output_counts, &outputs_eids, sample_sizes[i], bs, is_first_layer, is_last_layer, return_eids); outputs_vec.emplace_back(outputs); output_counts_vec.emplace_back(output_counts); outputs_eids_vec.emplace_back(outputs_eids); } } else { for (int i = 0; i < num_layers; i++) { if (i == num_layers - 1) { is_last_layer = true; } if (inputs.size() == 0) { break; } if (i > 0) { is_first_layer = false; dst_vec.emplace_back(inputs); } SampleNeighbors(src_data, dst_count_data, nullptr, &inputs, &outputs, &output_counts, &outputs_eids, sample_sizes[i], bs, is_first_layer, is_last_layer, return_eids); outputs_vec.emplace_back(outputs); output_counts_vec.emplace_back(output_counts); outputs_eids_vec.emplace_back(outputs_eids); } } // 4. Concat intermediate sample results. int64_t unique_dst_size = 0, src_size = 0; for (int i = 0; i < num_layers; i++) { unique_dst_size += dst_vec[i].size(); src_size += outputs_vec[i].size(); } std::vector unique_dst_merge(unique_dst_size); std::vector src_merge(src_size); std::vector dst_sample_counts_merge(unique_dst_size); auto unique_dst_merge_ptr = unique_dst_merge.begin(); auto src_merge_ptr = src_merge.begin(); auto dst_sample_counts_merge_ptr = dst_sample_counts_merge.begin(); // TODO(daisiming): We may try to use std::move in the future. for (int i = 0; i < num_layers; i++) { if (i == 0) { unique_dst_merge_ptr = std::copy( dst_vec[i].begin(), dst_vec[i].end(), unique_dst_merge.begin()); src_merge_ptr = std::copy( outputs_vec[i].begin(), outputs_vec[i].end(), src_merge.begin()); dst_sample_counts_merge_ptr = std::copy(output_counts_vec[i].begin(), output_counts_vec[i].end(), dst_sample_counts_merge.begin()); } else { unique_dst_merge_ptr = std::copy(dst_vec[i].begin(), dst_vec[i].end(), unique_dst_merge_ptr); src_merge_ptr = std::copy( outputs_vec[i].begin(), outputs_vec[i].end(), src_merge_ptr); dst_sample_counts_merge_ptr = std::copy(output_counts_vec[i].begin(), output_counts_vec[i].end(), dst_sample_counts_merge_ptr); } } // 5. Return eids results. if (return_eids) { std::vector eids_merge(src_size); auto eids_merge_ptr = eids_merge.begin(); for (int i = 0; i < num_layers; i++) { if (i == 0) { eids_merge_ptr = std::copy(outputs_eids_vec[i].begin(), outputs_eids_vec[i].end(), eids_merge.begin()); } else { eids_merge_ptr = std::copy(outputs_eids_vec[i].begin(), outputs_eids_vec[i].end(), eids_merge_ptr); } } out_eids->Resize({static_cast(eids_merge.size())}); T* out_eids_data = dev_ctx.template Alloc(out_eids); std::copy(eids_merge.begin(), eids_merge.end(), out_eids_data); } int64_t num_sample_edges = std::accumulate(dst_sample_counts_merge.begin(), dst_sample_counts_merge.end(), static_cast(0)); PADDLE_ENFORCE_EQ( src_merge.size(), num_sample_edges, common::errors::PreconditionNotMet( "Number of sample edges mismatch, the sample kernel has error.")); // 6. Reindex edges. std::unordered_map node_map; std::vector unique_nodes; size_t reindex_id = 0; for (size_t i = 0; i < unique_dst_merge.size(); i++) { T node = unique_dst_merge[i]; unique_nodes.emplace_back(node); node_map[node] = reindex_id++; } for (size_t i = 0; i < src_merge.size(); i++) { T node = src_merge[i]; if (node_map.find(node) == node_map.end()) { unique_nodes.emplace_back(node); node_map[node] = reindex_id++; } src_merge[i] = node_map[node]; } std::vector dst_merge(src_merge.size()); size_t cnt = 0; for (size_t i = 0; i < unique_dst_merge.size(); i++) { for (T j = 0; j < dst_sample_counts_merge[i]; j++) { T node = unique_dst_merge[i]; dst_merge[cnt++] = node_map[node]; } } // 7. Get Reindex_X for input nodes. reindex_x->Resize({static_cast(bs)}); T* p_reindex_x = dev_ctx.template Alloc(reindex_x); for (int i = 0; i < bs; i++) { p_reindex_x[i] = node_map[p_vertices[i]]; } // 8. Get operator's outputs. sample_index->Resize({static_cast(unique_nodes.size())}); out_src->Resize({static_cast(src_merge.size()), 1}); out_dst->Resize({static_cast(src_merge.size()), 1}); T* p_sample_index = dev_ctx.template Alloc(sample_index); T* p_out_src = dev_ctx.template Alloc(out_src); T* p_out_dst = dev_ctx.template Alloc(out_dst); std::copy(unique_nodes.begin(), unique_nodes.end(), p_sample_index); std::copy(src_merge.begin(), src_merge.end(), p_out_src); std::copy(dst_merge.begin(), dst_merge.end(), p_out_dst); } } // namespace phi PD_REGISTER_KERNEL(graph_khop_sampler, CPU, ALL_LAYOUT, phi::GraphKhopSamplerKernel, int, int64_t) { kernel->OutputAt(2).SetDataType(phi::DataType::INT32); }