// 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/core/kernel_registry.h" #include "paddle/phi/kernels/funcs/math_function.h" namespace phi { template bool DistPairDescend(std::tuple pair1, std::tuple pair2) { return std::get<2>(pair1) > std::get<2>(pair2); } // The match_indices must be initialized to -1 at first. // The match_dist must be initialized to 0 at first. template void BipartiteMatch(const DenseTensor& dist, int* match_indices, T* match_dist) { PADDLE_ENFORCE_EQ( dist.dims().size(), 2, common::errors::InvalidArgument("The rank of dist must be 2.")); int64_t row = dist.dims()[0]; int64_t col = dist.dims()[1]; auto* dist_data = dist.data(); // Test result: When row==130 the speed of these two methods almost the same if (row >= 130) { std::vector> match_pair; for (int64_t i = 0; i < row; ++i) { for (int64_t j = 0; j < col; ++j) { match_pair.push_back(std::make_tuple(i, j, dist_data[i * col + j])); } } std::sort(match_pair.begin(), match_pair.end(), DistPairDescend); std::vector row_indices(row, -1); int64_t idx = 0; for (int64_t k = 0; k < row * col; ++k) { int64_t i = std::get<0>(match_pair[k]); int64_t j = std::get<1>(match_pair[k]); T dist = std::get<2>(match_pair[k]); if (idx >= row) { break; } if (match_indices[j] == -1 && row_indices[i] == -1 && dist > 0) { match_indices[j] = static_cast(i); row_indices[i] = static_cast(j); match_dist[j] = dist; idx += 1; } } } else { constexpr T kEPS = static_cast(1e-6); std::vector row_pool; for (int i = 0; i < row; ++i) { row_pool.push_back(i); } while (!row_pool.empty()) { int max_idx = -1; int max_row_idx = -1; T max_dist = -1; for (int64_t j = 0; j < col; ++j) { if (match_indices[j] != -1) { continue; } for (auto m : row_pool) { // distance is 0 between m-th row and j-th column if (dist_data[m * col + j] < kEPS) { continue; } if (dist_data[m * col + j] > max_dist) { max_idx = static_cast(j); max_row_idx = m; max_dist = dist_data[m * col + j]; } } } if (max_idx == -1) { // Cannot find good match. break; } else { PADDLE_ENFORCE_EQ( match_indices[max_idx], -1, common::errors::InvalidArgument( "The match_indices must be initialized to -1 at [%d].", max_idx)); match_indices[max_idx] = max_row_idx; match_dist[max_idx] = max_dist; // Erase the row index. row_pool.erase( std::find(row_pool.begin(), row_pool.end(), max_row_idx)); } } } } template void ArgMaxMatch(const DenseTensor& dist, int* match_indices, T* match_dist, T overlap_threshold) { constexpr T kEPS = static_cast(1e-6); int64_t row = dist.dims()[0]; int64_t col = dist.dims()[1]; auto* dist_data = dist.data(); for (int64_t j = 0; j < col; ++j) { if (match_indices[j] != -1) { // the j-th column has been matched to one entity. continue; } int max_row_idx = -1; T max_dist = -1; for (int i = 0; i < row; ++i) { T dist = dist_data[i * col + j]; if (dist < kEPS) { // distance is 0 between m-th row and j-th column continue; } if (dist >= overlap_threshold && dist > max_dist) { max_row_idx = i; max_dist = dist; } } if (max_row_idx != -1) { PADDLE_ENFORCE_EQ( match_indices[j], -1, common::errors::InvalidArgument( "The match_indices must be initialized to -1 at [%d].", j)); match_indices[j] = max_row_idx; match_dist[j] = max_dist; } } } template void BipartiteMatchKernel(const Context& dev_ctx, const DenseTensor& dist_mat_in, const std::string& match_type, float dist_threshold, DenseTensor* col_to_row_match_indices, DenseTensor* col_to_row_match_dist) { auto* dist_mat = &dist_mat_in; auto* match_indices = col_to_row_match_indices; auto* match_dist = col_to_row_match_dist; auto col = dist_mat->dims()[1]; int64_t n = dist_mat->lod().empty() ? 1 : static_cast(dist_mat->lod().back().size() - 1); if (!dist_mat->lod().empty()) { PADDLE_ENFORCE_EQ( dist_mat->lod().size(), 1UL, common::errors::InvalidArgument("Only support 1 level of LoD.")); } match_indices->Resize({n, col}); dev_ctx.template Alloc(match_indices); match_dist->Resize({n, col}); dev_ctx.template Alloc(match_dist); funcs::SetConstant iset; iset(dev_ctx, match_indices, static_cast(-1)); funcs::SetConstant tset; tset(dev_ctx, match_dist, static_cast(0)); int* indices = match_indices->data(); T* dist = match_dist->data(); auto type = match_type; auto threshold = dist_threshold; if (n == 1) { BipartiteMatch(*dist_mat, indices, dist); if (type == "per_prediction") { ArgMaxMatch(*dist_mat, indices, dist, threshold); } } else { auto lod = dist_mat->lod().back(); for (size_t i = 0; i < lod.size() - 1; ++i) { if (lod[i + 1] > lod[i]) { DenseTensor one_ins = dist_mat->Slice(static_cast(lod[i]), static_cast(lod[i + 1])); BipartiteMatch(one_ins, indices + i * col, dist + i * col); if (type == "per_prediction") { ArgMaxMatch(one_ins, indices + i * col, dist + i * col, threshold); } } } } } } // namespace phi PD_REGISTER_KERNEL(bipartite_match, CPU, ALL_LAYOUT, phi::BipartiteMatchKernel, float, double) { kernel->OutputAt(0).SetDataType(phi::DataType::INT32); }