our_knp/examples/knp/tests/backend/multi_thread_cpu_test.cpp
int 8f43927575 Initial import: KNP experiments (examples, viz, cats-dogs, reports)
examples/ is based on KasperskyLab/knp at 55470ed6 with local changes
to the mnist-learn altai/blifat models, cuda-backend CMake and model_loader.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:17:15 +03:00

209 lines
7.3 KiB
C++

/**
* @file multi_thread_cpu_test.cpp
* @brief Multi-threaded backend test.
* @kaspersky_support Vartenkov An.
* @date 07.04.2023
* @license Apache 2.0
* @copyright © 2024-2025 AO Kaspersky Lab
*
* 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 <knp/backends/cpu-multi-threaded/backend.h>
#include <knp/backends/thread_pool/thread_pool_context.h>
#include <knp/backends/thread_pool/thread_pool_executor.h>
#include <knp/core/population.h>
#include <knp/core/projection.h>
#include <generators.h>
#include <spdlog/spdlog.h>
#include <tests_common.h>
#include <tests_messaging_common.h>
#include <functional>
#include <vector>
using Population = knp::backends::multi_threaded_cpu::MultiThreadedCPUBackend::PopulationVariants;
using Projection = knp::backends::multi_threaded_cpu::MultiThreadedCPUBackend::ProjectionVariants;
namespace knp::testing
{
class MTestingBack : public knp::backends::multi_threaded_cpu::MultiThreadedCPUBackend
{
public:
MTestingBack() = default;
void _init() override { knp::backends::multi_threaded_cpu::MultiThreadedCPUBackend::_init(); }
};
} // namespace knp::testing
TEST(MultiThreadCpuSuite, SmallestNetwork)
{
// Create a single-neuron neural network: input -> input_projection -> population <=> loop_projection.
namespace kt = knp::testing;
kt::MTestingBack backend;
kt::BLIFATPopulation population{kt::neuron_generator, 1};
Projection loop_projection =
kt::DeltaProjection{population.get_uid(), population.get_uid(), kt::synapse_generator, 1};
Projection input_projection =
kt::DeltaProjection{knp::core::UID{false}, population.get_uid(), kt::input_projection_gen, 1};
knp::core::UID input_uid = std::visit([](const auto &proj) { return proj.get_uid(); }, input_projection);
backend.load_populations({population});
backend.load_projections({input_projection, loop_projection});
auto endpoint = backend.get_message_bus().create_endpoint();
knp::core::UID in_channel_uid;
knp::core::UID out_channel_uid;
// Create input and output.
backend.subscribe<knp::core::messaging::SpikeMessage>(input_uid, {in_channel_uid});
endpoint.subscribe<knp::core::messaging::SpikeMessage>(out_channel_uid, {population.get_uid()});
std::vector<knp::core::Step> results;
backend._init();
for (knp::core::Step step = 0; step < 20; ++step)
{
// Send inputs on steps 0, 5, 10, 15.
knp::testing::internal::send_messages_smallest_network(in_channel_uid, endpoint, step);
backend._step();
auto out = knp::testing::internal::receive_messages_smallest_network(out_channel_uid, endpoint);
if (!out.empty()) results.push_back(step);
}
// Spikes on steps "5n + 1" (input) and on "previous_spike_n + 6" (positive feedback loop).
const std::vector<knp::core::Step> expected_results = {1, 6, 7, 11, 12, 13, 16, 17, 18, 19};
ASSERT_EQ(results, expected_results);
}
TEST(MultiThreadCpuSuite, SmallestResourceNetwork)
{
// Create a single-neuron neural network: input -> input_projection -> population <=> loop_projection.
namespace kt = knp::testing;
kt::MTestingBack backend;
kt::ResourceBlifatPopulation population{kt::neuron_res_generator, 1};
auto loop_projection =
kt::ResourceDeltaProjection{population.get_uid(), population.get_uid(), kt::loop_res_projection_gen, 1};
auto input_projection =
kt::ResourceDeltaProjection{knp::core::UID{false}, population.get_uid(), kt::input_res_projection_gen, 1};
knp::core::UID input_uid = input_projection.get_uid();
backend.load_populations({population});
backend.load_projections({input_projection, loop_projection});
auto endpoint = backend.get_message_bus().create_endpoint();
knp::core::UID in_channel_uid;
knp::core::UID out_channel_uid;
// Create input and output.
backend.subscribe<knp::core::messaging::SpikeMessage>(input_uid, {in_channel_uid});
endpoint.subscribe<knp::core::messaging::SpikeMessage>(out_channel_uid, {population.get_uid()});
std::vector<knp::core::Step> results;
backend._init();
for (knp::core::Step step = 0; step < 20; ++step)
{
// Send inputs on steps 0, 5, 10, 15.
knp::testing::internal::send_messages_smallest_network(in_channel_uid, endpoint, step);
backend._step();
auto out = knp::testing::internal::receive_messages_smallest_network(out_channel_uid, endpoint);
if (!out.empty()) results.push_back(step);
}
// Spikes on steps "5n + 1" (input) and on "previous_spike_n + 6" (positive feedback loop).
const std::vector<knp::core::Step> expected_results = {1, 6, 7, 11, 12, 13, 16, 17, 18, 19};
ASSERT_EQ(results, expected_results);
}
TEST(MultiThreadCpuSuite, NeuronsGettingTest)
{
const knp::testing::MTestingBack backend;
auto s_neurons = backend.get_supported_neurons();
ASSERT_LE(s_neurons.size(), boost::mp11::mp_size<knp::neuron_traits::AllNeurons>());
ASSERT_EQ(s_neurons[0], "BLIFATNeuron");
}
TEST(MultiThreadCpuSuite, SynapsesGettingTest)
{
const knp::testing::MTestingBack backend;
auto s_synapses = backend.get_supported_synapses();
ASSERT_LE(s_synapses.size(), boost::mp11::mp_size<knp::synapse_traits::AllSynapses>());
ASSERT_EQ(s_synapses[0], "DeltaSynapse");
}
void fibonacci(const uint64_t begin, uint64_t iterations, uint64_t *result)
{
// This function calculates last 3 digits of "begin * Fibonacci(iterations)".
uint64_t prev[] = {begin, 0};
const uint64_t divider = 1000; // 10^N, used so that only N last digits remain.
for (uint64_t i = 0; i < iterations; ++i)
{
const auto tmp = (prev[0] + prev[1]) % divider;
prev[1] = prev[0];
prev[0] = tmp;
}
*result = prev[0];
}
void batch(
knp::backends::cpu_executors::ThreadPoolContext &pool, uint64_t iterations,
const std::vector<uint64_t> &start_values, std::vector<uint64_t> &result)
{
knp::backends::cpu_executors::ThreadPoolExecutor const executor(pool);
result.resize(start_values.size(), 0);
for (size_t i = 0; i < start_values.size(); ++i)
{
boost::asio::post(executor, std::bind(fibonacci, start_values[i], iterations, &result[i]));
}
}
TEST(MultiThreadCpuSuite, ThreadPoolTest)
{
knp::backends::cpu_executors::ThreadPoolContext pool;
std::vector<uint64_t> result;
const int num_iterations = 10; // Corresponding Fibonacci number is 89.
batch(pool, num_iterations, {2, 4, 5, 7, 9}, result);
ASSERT_EQ(result.size(), 5); // All threads are finished.
ASSERT_EQ(result[0], 178); // The results are correct (89 * 2).
ASSERT_EQ(result[1], 356);
// Check that pool is reusable.
batch(pool, num_iterations, {7, 5, 5, 7, 9, 11, 8, 7}, result);
ASSERT_EQ(result.size(), 8);
ASSERT_EQ(result[0], 623);
ASSERT_EQ(result[1], 445);
ASSERT_EQ(result[0], result[7]); // Delayed tasks should give the same results as the first ones.
}