gallery_server_client_s190js/docs/face-knn-source/src/knn_opencl.cc
2026-10-26 11:00:14 +01:00

484 lines
10 KiB
C++

#include "knn_opencl.h"
#include <algorithm>
#include <chrono>
#include <cmath>
#include <limits>
#include <stdexcept>
#include <string>
#include <vector>
namespace face_knn {
SearchOutput search_opencl_impl(
const std::vector<float>& database,
const std::vector<float>& queries,
std::size_t database_count,
std::size_t query_count,
const SearchOptions& options);
namespace {
bool better_neighbor(
const Neighbor& left,
const Neighbor& right)
{
if (left.score != right.score) {
return left.score > right.score;
}
return left.index < right.index;
}
void validate_finite_values(
const std::vector<float>& values,
const char* name)
{
for (
std::size_t index = 0;
index < values.size();
++index
) {
if (!std::isfinite(values[index])) {
throw std::invalid_argument(
std::string(name)
+ " contiene un valore non finito"
);
}
}
}
std::uint32_t effective_top_k(
std::size_t database_count,
std::uint32_t requested_top_k)
{
return std::min(
requested_top_k,
static_cast<std::uint32_t>(
database_count
)
);
}
} // namespace
void validate_input(
const std::vector<float>& database,
const std::vector<float>& queries,
std::size_t database_count,
std::size_t query_count,
const SearchOptions& options)
{
if (options.dimension == 0) {
throw std::invalid_argument(
"dimension deve essere maggiore di zero"
);
}
if (options.top_k == 0) {
throw std::invalid_argument(
"top_k deve essere maggiore di zero"
);
}
if (options.lanes_per_query == 0) {
throw std::invalid_argument(
"lanes_per_query deve essere maggiore di zero"
);
}
if (options.chunk_size == 0) {
throw std::invalid_argument(
"chunk_size deve essere maggiore di zero"
);
}
if (database_count == 0) {
throw std::invalid_argument(
"database_count deve essere maggiore di zero"
);
}
if (query_count == 0) {
throw std::invalid_argument(
"query_count deve essere maggiore di zero"
);
}
if (
database_count
> std::numeric_limits<
std::uint32_t
>::max()
) {
throw std::invalid_argument(
"database_count supera uint32"
);
}
const std::size_t expected_database =
database_count
* static_cast<std::size_t>(
options.dimension
);
const std::size_t expected_queries =
query_count
* static_cast<std::size_t>(
options.dimension
);
if (database.size() != expected_database) {
throw std::invalid_argument(
"dimensione database incoerente"
);
}
if (queries.size() != expected_queries) {
throw std::invalid_argument(
"dimensione query incoerente"
);
}
validate_finite_values(
database,
"database"
);
validate_finite_values(
queries,
"queries"
);
}
SearchOutput search_cpu(
const std::vector<float>& database,
const std::vector<float>& queries,
std::size_t database_count,
std::size_t query_count,
const SearchOptions& options)
{
validate_input(
database,
queries,
database_count,
query_count,
options
);
const std::uint32_t result_top_k =
effective_top_k(
database_count,
options.top_k
);
SearchOutput output;
output.query_count = query_count;
output.database_count = database_count;
output.dimension = options.dimension;
output.top_k = result_top_k;
output.backend = "cpu";
output.device_name = "CPU";
output.neighbors.resize(
query_count
* static_cast<std::size_t>(
result_top_k
)
);
const auto started_at =
std::chrono::steady_clock::now();
for (
std::size_t query_index = 0;
query_index < query_count;
++query_index
) {
const float* query =
&queries[
query_index
* options.dimension
];
std::vector<Neighbor> candidates;
candidates.reserve(database_count);
for (
std::size_t database_index = 0;
database_index < database_count;
++database_index
) {
const float* database_vector =
&database[
database_index
* options.dimension
];
float score = 0.0f;
for (
std::uint32_t component = 0;
component < options.dimension;
++component
) {
score +=
database_vector[component]
* query[component];
}
candidates.push_back({
score,
static_cast<std::uint32_t>(
database_index
),
});
}
if (
result_top_k
< candidates.size()
) {
std::nth_element(
candidates.begin(),
candidates.begin()
+ result_top_k,
candidates.end(),
better_neighbor
);
}
std::sort(
candidates.begin(),
candidates.begin()
+ result_top_k,
better_neighbor
);
const std::size_t output_offset =
query_index
* result_top_k;
for (
std::uint32_t position = 0;
position < result_top_k;
++position
) {
output.neighbors[
output_offset + position
] = candidates[position];
}
}
const auto completed_at =
std::chrono::steady_clock::now();
output.elapsed_ms =
std::chrono::duration<
double,
std::milli
>(
completed_at - started_at
).count();
return output;
}
SearchOutput search_opencl(
const std::vector<float>& database,
const std::vector<float>& queries,
std::size_t database_count,
std::size_t query_count,
const SearchOptions& options)
{
return search_opencl_impl(
database,
queries,
database_count,
query_count,
options
);
}
std::vector<Neighbor> query_neighbors(
const SearchOutput& output,
std::size_t query_index)
{
if (query_index >= output.query_count) {
throw std::out_of_range(
"query_index fuori intervallo"
);
}
const std::size_t begin =
query_index
* output.top_k;
const std::size_t end =
begin
+ output.top_k;
if (end > output.neighbors.size()) {
throw std::runtime_error(
"layout SearchOutput incoerente"
);
}
return {
output.neighbors.begin() + begin,
output.neighbors.begin() + end,
};
}
std::vector<Neighbor> exclude_neighbor(
const std::vector<Neighbor>& neighbors,
std::uint32_t excluded_index)
{
std::vector<Neighbor> filtered;
filtered.reserve(neighbors.size());
for (const Neighbor& neighbor : neighbors) {
if (neighbor.index == invalid_index) {
continue;
}
if (neighbor.index == excluded_index) {
continue;
}
filtered.push_back(neighbor);
}
return filtered;
}
ValidationResult compare_outputs(
const SearchOutput& expected,
const SearchOutput& actual,
float score_tolerance,
float recognition_threshold)
{
if (score_tolerance < 0.0f) {
throw std::invalid_argument(
"score_tolerance non valida"
);
}
if (
expected.query_count
!= actual.query_count
|| expected.database_count
!= actual.database_count
|| expected.dimension
!= actual.dimension
|| expected.top_k
!= actual.top_k
|| expected.neighbors.size()
!= actual.neighbors.size()
) {
throw std::invalid_argument(
"Output kNN non confrontabili"
);
}
ValidationResult result;
result.positions =
expected.neighbors.size();
for (
std::size_t index = 0;
index < result.positions;
++index
) {
const Neighbor& expected_neighbor =
expected.neighbors[index];
const Neighbor& actual_neighbor =
actual.neighbors[index];
if (
expected_neighbor.index
== actual_neighbor.index
) {
result.exact_index_matches += 1;
}
const float error = std::fabs(
expected_neighbor.score
- actual_neighbor.score
);
result.maximum_score_error =
std::max(
result.maximum_score_error,
error
);
}
for (
std::size_t query_index = 0;
query_index < expected.query_count;
++query_index
) {
const auto expected_neighbors =
query_neighbors(
expected,
query_index
);
const auto actual_neighbors =
query_neighbors(
actual,
query_index
);
const bool expected_recognized =
!expected_neighbors.empty()
&& expected_neighbors.front().score
>= recognition_threshold;
const bool actual_recognized =
!actual_neighbors.empty()
&& actual_neighbors.front().score
>= recognition_threshold;
if (
expected_recognized
== actual_recognized
) {
result.decision_matches += 1;
}
}
result.exact_indices =
result.exact_index_matches
== result.positions;
result.decisions_equal =
result.decision_matches
== expected.query_count;
if (
result.maximum_score_error
> score_tolerance
) {
result.decisions_equal = false;
}
return result;
}
} // namespace face_knn