Files
paddlepaddle--paddle/paddle/phi/kernels/cpu/graph_sample_neighbors_kernel.cc
T
2026-07-13 12:40:42 +08:00

231 lines
7.8 KiB
C++

// 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_sample_neighbors_kernel.h"
#include <vector>
#include "paddle/phi/backends/cpu/cpu_context.h"
#include "paddle/phi/core/kernel_registry.h"
namespace phi {
template <class bidiiter>
void SampleUniqueNeighbors(
bidiiter begin,
bidiiter end,
int num_samples,
std::mt19937& rng, // NOLINT
std::uniform_int_distribution<int>& dice_distribution) { // NOLINT
int left_num = std::distance(begin, end);
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 <class bidiiter>
void SampleUniqueNeighborsWithEids(
bidiiter src_begin,
bidiiter src_end,
bidiiter eid_begin,
bidiiter eid_end,
int num_samples,
std::mt19937& rng, // NOLINT
std::uniform_int_distribution<int>& dice_distribution) { // NOLINT
int left_num = std::distance(src_begin, src_end);
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 <typename T>
void SampleNeighbors(const T* row,
const T* col_ptr,
const T* eids,
const T* input,
std::vector<T>* output,
std::vector<int>* output_count,
std::vector<T>* output_eids,
int sample_size,
int bs,
bool return_eids) {
std::vector<std::vector<T>> out_src_vec;
std::vector<std::vector<T>> out_eids_vec;
// `sample_cumsum_sizes` record the start position and end position
// after sampling.
std::vector<int> sample_cumsum_sizes(bs + 1);
// `total_neighbors` the size of output after sample.
int total_neighbors = 0;
sample_cumsum_sizes[0] = total_neighbors;
for (int i = 0; i < bs; i++) {
T node = input[i];
int cap = col_ptr[node + 1] - col_ptr[node];
int k = cap > sample_size ? sample_size : cap;
total_neighbors += k;
sample_cumsum_sizes[i + 1] = total_neighbors;
std::vector<T> out_src;
out_src.resize(cap);
out_src_vec.emplace_back(out_src);
if (return_eids) {
std::vector<T> out_eids;
out_eids.resize(cap);
out_eids_vec.emplace_back(out_eids);
}
}
output_count->resize(bs);
output->resize(total_neighbors);
if (return_eids) {
output_eids->resize(total_neighbors);
}
std::random_device rd;
std::mt19937 rng{rd()};
std::uniform_int_distribution<int> dice_distribution(
0, std::numeric_limits<int>::max());
#ifdef PADDLE_WITH_MKLML
#pragma omp parallel for
#endif
// Sample the neighbors in parallelism.
for (int i = 0; i < bs; i++) {
T node = input[i];
T begin = col_ptr[node], end = col_ptr[node + 1];
int cap = end - begin;
if (sample_size < cap) {
std::copy(row + begin, row + end, out_src_vec[i].begin());
if (return_eids) {
std::copy(eids + begin, 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(),
sample_size,
rng,
dice_distribution);
} else {
SampleUniqueNeighbors(out_src_vec[i].begin(),
out_src_vec[i].end(),
sample_size,
rng,
dice_distribution);
}
*(output_count->data() + i) = sample_size;
} else {
std::copy(row + begin, row + end, out_src_vec[i].begin());
if (return_eids) {
std::copy(eids + begin, eids + end, out_eids_vec[i].begin());
}
*(output_count->data() + i) = cap;
}
}
#ifdef PADDLE_WITH_MKLML
#pragma omp parallel for
#endif
// Copy the results parallelism
for (int i = 0; i < bs; i++) {
int k = sample_cumsum_sizes[i + 1] - sample_cumsum_sizes[i];
std::copy(out_src_vec[i].begin(),
out_src_vec[i].begin() + k,
output->data() + sample_cumsum_sizes[i]);
if (return_eids) {
std::copy(out_eids_vec[i].begin(),
out_eids_vec[i].begin() + k,
output_eids->data() + sample_cumsum_sizes[i]);
}
}
}
template <typename T, typename Context>
void GraphSampleNeighborsKernel(const Context& dev_ctx,
const DenseTensor& row,
const DenseTensor& col_ptr,
const DenseTensor& x,
const optional<DenseTensor>& eids,
const optional<DenseTensor>& perm_buffer,
int sample_size,
bool return_eids,
bool flag_perm_buffer,
DenseTensor* out,
DenseTensor* out_count,
DenseTensor* out_eids) {
const T* row_data = row.data<T>();
const T* col_ptr_data = col_ptr.data<T>();
const T* x_data = x.data<T>();
int bs = static_cast<int>(x.dims()[0]);
std::vector<T> output;
std::vector<int> output_count;
if (return_eids) {
const T* eids_data = eids.get_ptr()->data<T>();
std::vector<T> output_eids;
SampleNeighbors<T>(row_data,
col_ptr_data,
eids_data,
x_data,
&output,
&output_count,
&output_eids,
sample_size,
bs,
return_eids);
out_eids->Resize({static_cast<int>(output_eids.size())});
T* out_eids_data = dev_ctx.template Alloc<T>(out_eids);
std::copy(output_eids.begin(), output_eids.end(), out_eids_data);
} else {
SampleNeighbors<T>(row_data,
col_ptr_data,
nullptr,
x_data,
&output,
&output_count,
nullptr,
sample_size,
bs,
return_eids);
}
out->Resize({static_cast<int>(output.size())});
T* out_data = dev_ctx.template Alloc<T>(out);
std::copy(output.begin(), output.end(), out_data);
out_count->Resize({bs});
int* out_count_data = dev_ctx.template Alloc<int>(out_count);
std::copy(output_count.begin(), output_count.end(), out_count_data);
}
} // namespace phi
PD_REGISTER_KERNEL(graph_sample_neighbors,
CPU,
ALL_LAYOUT,
phi::GraphSampleNeighborsKernel,
int,
int64_t) {
kernel->OutputAt(1).SetDataType(phi::DataType::INT32);
}