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

270 lines
8.8 KiB
C++

// 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/weighted_sample_neighbors_kernel.h"
#include <cmath>
#include <queue>
#include <vector>
#include "paddle/phi/backends/cpu/cpu_context.h"
#include "paddle/phi/core/kernel_registry.h"
namespace phi {
template <typename T>
struct GraphWeightedNode {
T node_id;
float weight_key;
T eid;
GraphWeightedNode() {
node_id = 0;
weight_key = 0;
eid = 0;
}
GraphWeightedNode(T node_id, float weight_key, T eid = 0)
: node_id(node_id), weight_key(weight_key), eid(eid) {}
GraphWeightedNode(const GraphWeightedNode<T>& other) : weight_key(0) {
if (this != &other) {
this->node_id = other.node_id;
this->weight_key = other.weight_key;
this->eid = other.eid;
}
}
GraphWeightedNode& operator=(const GraphWeightedNode<T>& other) {
if (this != &other) {
this->node_id = other.node_id;
this->weight_key = other.weight_key;
this->eid = other.eid;
return *this;
}
return *this;
}
friend bool operator>(const GraphWeightedNode<T>& n1,
const GraphWeightedNode<T>& n2) {
return n1.weight_key > n2.weight_key;
}
};
template <typename T>
void SampleWeightedNeighbors(
std::vector<T>& out_src, // NOLINT
const std::vector<float>& out_weight,
std::vector<T>& out_eids, // NOLINT
int sample_size,
std::mt19937& rng, // NOLINT
std::uniform_real_distribution<float>& dice_distribution, // NOLINT
bool return_eids) {
std::priority_queue<GraphWeightedNode<T>,
std::vector<GraphWeightedNode<T>>,
std::greater<GraphWeightedNode<T>>> // NOLINT
min_heap;
for (size_t i = 0; i < out_src.size(); i++) {
float weight_key = log2(dice_distribution(rng)) * (1 / out_weight[i]);
if (static_cast<int>(i) < sample_size) {
if (!return_eids) {
min_heap.push(GraphWeightedNode<T>(out_src[i], weight_key));
} else {
min_heap.push(
GraphWeightedNode<T>(out_src[i], weight_key, out_eids[i]));
}
} else {
const GraphWeightedNode<T>& small = min_heap.top();
GraphWeightedNode<T> cmp;
if (!return_eids) {
cmp = GraphWeightedNode<T>(out_src[i], weight_key);
} else {
cmp = GraphWeightedNode<T>(out_src[i], weight_key, out_eids[i]);
}
bool flag = cmp > small;
if (flag) {
min_heap.pop();
min_heap.push(cmp);
}
}
}
int cnt = 0;
while (!min_heap.empty()) {
const GraphWeightedNode<T>& tmp = min_heap.top();
out_src[cnt] = tmp.node_id;
if (return_eids) {
out_eids[cnt] = tmp.eid;
}
cnt++;
min_heap.pop();
}
}
template <typename T>
void SampleNeighbors(const T* row,
const T* col_ptr,
const float* edge_weight,
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<float>> out_weight_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);
std::vector<float> out_weight;
out_weight.resize(cap);
out_weight_vec.emplace_back(out_weight);
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_real_distribution<float> dice_distribution(0, 1);
#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) { // sample_size < neighbor_len
std::copy(row + begin, row + end, out_src_vec[i].begin());
std::copy(
edge_weight + begin, edge_weight + end, out_weight_vec[i].begin());
if (return_eids) {
std::copy(eids + begin, eids + end, out_eids_vec[i].begin());
}
SampleWeightedNeighbors(out_src_vec[i],
out_weight_vec[i],
out_eids_vec[i],
sample_size,
rng,
dice_distribution,
return_eids);
*(output_count->data() + i) = sample_size;
} else { // sample_size >= neighbor_len, directly copy
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 WeightedSampleNeighborsKernel(const Context& dev_ctx,
const DenseTensor& row,
const DenseTensor& col_ptr,
const DenseTensor& edge_weight,
const DenseTensor& x,
const optional<DenseTensor>& eids,
int sample_size,
bool return_eids,
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 float* weights_data = edge_weight.data<float>();
const T* x_data = x.data<T>();
const T* eids_data =
(eids.get_ptr() == nullptr ? nullptr : eids.get_ptr()->data<T>());
int bs = static_cast<int>(x.dims()[0]);
std::vector<T> output;
std::vector<int> output_count;
std::vector<T> output_eids;
SampleNeighbors<T>(row_data,
col_ptr_data,
weights_data,
eids_data,
x_data,
&output,
&output_count,
&output_eids,
sample_size,
bs,
return_eids);
if (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);
}
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(weighted_sample_neighbors,
CPU,
ALL_LAYOUT,
phi::WeightedSampleNeighborsKernel,
int,
int64_t) {}