diff --git a/examples/mnist-learn/main.cpp b/examples/mnist-learn/main.cpp index 203a50ea..3611918b 100644 --- a/examples/mnist-learn/main.cpp +++ b/examples/mnist-learn/main.cpp @@ -44,7 +44,7 @@ int main(int argc, char **argv) // Defines path to backend, on which to run a network. std::filesystem::path path_to_backend = - std::filesystem::path(argv[0]).parent_path() / "knp-cpu-single-threaded-backend"; + std::filesystem::path(argv[0]).parent_path() / "knp-cpu-multi-threaded-backend"; // Read data from corresponding files. auto spike_frames = read_spike_frames(argv[1]); diff --git a/knp/backends/cpu/cpu-library/include/knp/backends/cpu-library/impl/additive_stdp_impl.h b/knp/backends/cpu/cpu-library/include/knp/backends/cpu-library/impl/additive_stdp_impl.h index 6f385120..a60f90e3 100644 --- a/knp/backends/cpu/cpu-library/include/knp/backends/cpu-library/impl/additive_stdp_impl.h +++ b/knp/backends/cpu/cpu-library/include/knp/backends/cpu-library/impl/additive_stdp_impl.h @@ -28,6 +28,7 @@ #include +#include #include #include #include @@ -152,11 +153,12 @@ constexpr bool is_additive_stdp_synapse() template -void register_additive_stdp_spikes( +void register_additive_stdp_spikes_part( knp::core::Projection> &projection, - std::vector &all_messages) + std::vector &all_messages, uint64_t part_start, uint64_t part_end) { + if (part_start != 0) return; // Not much sense to parallelize this by projection, so it's calculated just once. SPDLOG_DEBUG("Calculating additive STDP delta synapse projection..."); using ProjectionType = typename std::decay_t; @@ -209,13 +211,15 @@ void register_additive_stdp_spikes( template -void update_projection_weights_additive_stdp( +void update_projection_weights_additive_stdp_part( knp::core::Projection> - &projection) + &projection, + uint64_t part_start, uint64_t part_end) { // Update projection parameters. - for (auto &proj : projection) + for (uint64_t i = part_start; i < std::min(projection.size(), part_end); ++i) { + auto &proj = projection[i]; SPDLOG_TRACE("Applying STDP rule..."); auto &rule = std::get(proj).rule_; const auto period = rule.tau_plus_ + rule.tau_minus_; @@ -238,19 +242,32 @@ template struct WeightUpdateSTDP> { using Synapse = synapse_traits::STDP; - static void init_projection( - knp::core::Projection &projection, std::vector &all_messages, knp::core::Step step) + + static void init_projection_part( + knp::core::Projection &projection, std::vector &all_messages, knp::core::Step step, + uint64_t part_start, uint64_t part_end) { - register_additive_stdp_spikes(projection, all_messages); + register_additive_stdp_spikes_part(projection, all_messages, part_start, part_end); } static void init_synapse(const knp::synapse_traits::synapse_parameters &projection, knp::core::Step step) { } + static void modify_weights_part(knp::core::Projection &projection, uint64_t part_start, uint64_t part_end) + { + update_projection_weights_additive_stdp_part(projection, part_start, part_end); + } + + static void init_projection( + knp::core::Projection &projection, std::vector &all_messages, knp::core::Step step) + { + init_projection_part(projection, all_messages, step, 0, projection.size()); + } + static void modify_weights(knp::core::Projection &projection) { - update_projection_weights_additive_stdp(projection); + modify_weights_part(projection, 0, projection.size()); } }; diff --git a/knp/backends/cpu/cpu-library/include/knp/backends/cpu-library/impl/base_stdp_impl.h b/knp/backends/cpu/cpu-library/include/knp/backends/cpu-library/impl/base_stdp_impl.h index 7f13e5e4..58fcf09f 100644 --- a/knp/backends/cpu/cpu-library/include/knp/backends/cpu-library/impl/base_stdp_impl.h +++ b/knp/backends/cpu/cpu-library/include/knp/backends/cpu-library/impl/base_stdp_impl.h @@ -4,24 +4,25 @@ * @kaspersky_support A. Vartenkov * @date 04.11.2023 * @license Apache 2.0 - * @copyright © 2024 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 + * @copyright © 2024 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. */ #pragma once #include #include +#include #include /** @@ -29,20 +30,30 @@ */ namespace knp::backends::cpu { - template struct WeightUpdateSTDP { - static void init_projection( + static void init_projection_part( const knp::core::Projection &projection, - const std::vector &messages, uint64_t step) + const std::vector &messages, uint64_t part_begin, uint64_t part_end, + uint64_t step) { } - static void init_synapse(const knp::synapse_traits::synapse_parameters &projection, uint64_t step) + static void init_synapse(knp::synapse_traits::synapse_parameters ¶ms, uint64_t step) {} + + static void modify_weights_part( + const knp::core::Projection &projection, uint64_t part_begin, uint64_t part_end) { } + static void init_projection( + const knp::core::Projection &projection, + const std::vector &messages, uint64_t step) + { + } + + static void modify_weights(const knp::core::Projection &projection) {} }; diff --git a/knp/backends/cpu/cpu-library/include/knp/backends/cpu-library/impl/blifat_population_impl.h b/knp/backends/cpu/cpu-library/include/knp/backends/cpu-library/impl/blifat_population_impl.h index d380de42..07dcf328 100644 --- a/knp/backends/cpu/cpu-library/include/knp/backends/cpu-library/impl/blifat_population_impl.h +++ b/knp/backends/cpu/cpu-library/include/knp/backends/cpu-library/impl/blifat_population_impl.h @@ -314,6 +314,17 @@ void calculate_neurons_post_input_state_part( } +/** + * Anything that works after the population has been calculated goes here. Mostly for STDP and other learning. + */ +template +void finalize_population( + knp::core::Population &population, const knp::core::messaging::SpikeMessage &message, + ProjectionContainer &projections, knp::core::Step step) +{ +} + + /** * @brief Process BLIFAT neuron population and return spiked neuron indexes. * @tparam BlifatLikeNeuron type of neuron which inference can be calculated the same as BLIFAT. diff --git a/knp/backends/cpu/cpu-library/include/knp/backends/cpu-library/impl/delta_synapse_projection_impl.h b/knp/backends/cpu/cpu-library/include/knp/backends/cpu-library/impl/delta_synapse_projection_impl.h index 9d8d973b..e586ec34 100644 --- a/knp/backends/cpu/cpu-library/include/knp/backends/cpu-library/impl/delta_synapse_projection_impl.h +++ b/knp/backends/cpu/cpu-library/include/knp/backends/cpu-library/impl/delta_synapse_projection_impl.h @@ -24,6 +24,7 @@ #include #include #include +#include #include @@ -132,15 +133,15 @@ template void calculate_projection_part_impl( knp::core::Projection &projection, const std::unordered_map &message_in_data, MessageQueue &future_messages, uint64_t step_n, - size_t part_start, size_t part_size, std::mutex &mutex) + uint64_t part_start, uint64_t part_size, std::mutex &mutex) { size_t part_end = std::min(part_start + part_size, projection.size()); std::vector> container; + WeightUpdateStdpMp::init_projection_part(projection, message_in_data, step_n); for (size_t synapse_index = part_start; synapse_index < part_end; ++synapse_index) { auto &synapse = projection[synapse_index]; // update_step(synapse.params_, step_n); - // TODO: Move update logic here too. auto iter = message_in_data.find(std::get(synapse)); if (iter == message_in_data.end()) { @@ -150,6 +151,7 @@ void calculate_projection_part_impl( // Add new impact. // The message is sent on step N - 1, received on step N. uint64_t key = std::get(synapse).delay_ + step_n - 1; + WeightUpdateStdpMp::init_synapse(std::get(synapse), step_n); knp::core::messaging::SynapticImpact impact{ synapse_index, std::get(synapse).weight_ * iter->second, @@ -183,6 +185,7 @@ void calculate_projection_part_impl( future_messages.insert(std::make_pair(value.first, message_out)); } } + WeightUpdateStdpMp::modify_weights_part(projection); } diff --git a/knp/backends/cpu/cpu-library/include/knp/backends/cpu-library/impl/synaptic_resource_stdp_impl.h b/knp/backends/cpu/cpu-library/include/knp/backends/cpu-library/impl/synaptic_resource_stdp_impl.h index e65973ba..14c8d96f 100644 --- a/knp/backends/cpu/cpu-library/include/knp/backends/cpu-library/impl/synaptic_resource_stdp_impl.h +++ b/knp/backends/cpu/cpu-library/include/knp/backends/cpu-library/impl/synaptic_resource_stdp_impl.h @@ -32,7 +32,9 @@ #include #include #include +#include #include +#include #include #include @@ -381,6 +383,40 @@ struct WeightUpdateSTDP +struct WeightUpdateStdpMp +{ + using Synapse = DeltaLikeSynapse; + static void init_projection_part( + const knp::core::Projection &projection, + const std::unordered_map &message_data, uint64_t step) + { + } + + static void init_synapse(knp::synapse_traits::synapse_parameters ¶ms, uint64_t step) {} + + static void modify_weights_part(const knp::core::Projection &projection) {} +}; + + +template +struct WeightUpdateStdpMp> +{ + using Synapse = synapse_traits::STDP; + static void init_projection_part( + const knp::core::Projection &projection, + const std::unordered_map &message_data, uint64_t step) + { + } + + static void init_synapse(knp::synapse_traits::synapse_parameters ¶ms, uint64_t step) + { + params.rule_.last_spike_step_ = step; + } + + static void modify_weights_part(const knp::core::Projection &projection) {} +}; + template void do_STDP_resource_plasticity( knp::core::Population> &population, @@ -402,4 +438,6 @@ void do_STDP_resource_plasticity( // 3. Renormalize resources if needed. knp::backends::cpu::renormalize_resource(working_projections, population, step); } + + } // namespace knp::backends::cpu diff --git a/knp/backends/cpu/cpu-multi-threaded-backend/CMakeLists.txt b/knp/backends/cpu/cpu-multi-threaded-backend/CMakeLists.txt index d2e9e5e6..06d37842 100644 --- a/knp/backends/cpu/cpu-multi-threaded-backend/CMakeLists.txt +++ b/knp/backends/cpu/cpu-multi-threaded-backend/CMakeLists.txt @@ -48,6 +48,7 @@ knp_add_library("${PROJECT_NAME}" BOTH impl/backend.cpp impl/get_network.cpp + impl/template_specs.cpp ${${PROJECT_NAME}_headers} ALIAS KNP::Backends::CPUMultiThreaded LINK_PRIVATE diff --git a/knp/backends/cpu/cpu-multi-threaded-backend/impl/backend.cpp b/knp/backends/cpu/cpu-multi-threaded-backend/impl/backend.cpp index 042d019d..e52322ac 100644 --- a/knp/backends/cpu/cpu-multi-threaded-backend/impl/backend.cpp +++ b/knp/backends/cpu/cpu-multi-threaded-backend/impl/backend.cpp @@ -21,6 +21,9 @@ #include #include +#include +#include +#include #include #include #include @@ -33,6 +36,7 @@ #include #include +#include #include #include @@ -153,10 +157,28 @@ std::vector MultiThreadedCPUBackend::calcula std::ref(pop), std::ref(message), neuron_index, population_part_size_, std::ref(ep_mutex_)); }, population); + } + } + calc_pool_->join(); + for (size_t pop_id = 0; pop_id < populations_.size(); ++pop_id) + { + auto &message = spike_container[pop_id]; + std::visit( + [this, &message](auto &pop) + { + using T = std::decay_t; + auto call_finalize = [](T &pop_ref, knp::core::messaging::SpikeMessage &message_ref, + ProjectionContainer &proj_ref, knp::core::Step step) + { + knp::backends::cpu::finalize_population( + pop_ref, message_ref, proj_ref, step); + }; + calc_pool_->post(call_finalize, std::ref(pop), std::ref(message), std::ref(projections_), get_step()); + }, + populations_[pop_id]); #if defined(_MSC_VER) # pragma warning(pop) #endif - } } calc_pool_->join(); return spike_container; @@ -394,5 +416,4 @@ MultiThreadedCPUBackend::ProjectionConstIterator MultiThreadedCPUBackend::end_pr BOOST_DLL_ALIAS(knp::backends::multi_threaded_cpu::MultiThreadedCPUBackend::create, create_knp_backend) - } // namespace knp::backends::multi_threaded_cpu diff --git a/knp/backends/cpu/cpu-multi-threaded-backend/impl/template_specs.cpp b/knp/backends/cpu/cpu-multi-threaded-backend/impl/template_specs.cpp new file mode 100644 index 00000000..2b8e4e87 --- /dev/null +++ b/knp/backends/cpu/cpu-multi-threaded-backend/impl/template_specs.cpp @@ -0,0 +1,63 @@ +/** + * @file template_specs.cpp + * @brief Multi-threaded CPU backend-specific template specializations. + * @kaspersky_support Vartenkov A. + * @date 11.06.2025 + * @license Apache 2.0 + * @copyright © 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 +#include +#include + + +namespace knp::backends::cpu +{ +template <> +void finalize_population< + knp::neuron_traits::SynapticResourceSTDPBLIFATNeuron, + multi_threaded_cpu::MultiThreadedCPUBackend::ProjectionContainer>( + knp::core::Population &population, + const knp::core::messaging::SpikeMessage &message, + multi_threaded_cpu::MultiThreadedCPUBackend::ProjectionContainer &projections, knp::core::Step step) +{ + using SynapseType = knp::synapse_traits::SynapticResourceSTDPDeltaSynapse; + std::vector *> working_projections; + constexpr uint64_t type_index = + boost::mp11::mp_find(); + + for (auto &projection : projections) + { + if (projection.arg_.index() != type_index) + { + continue; + } + + auto *projection_ptr = &(std::get(projection.arg_)); + if (projection_ptr->is_locked()) + { + continue; + } + + if (projection_ptr->get_postsynaptic() == population.get_uid()) + { + working_projections.push_back(projection_ptr); + } + } + + do_STDP_resource_plasticity( + population, working_projections, message, step); +} +} //namespace knp::backends::cpu diff --git a/knp/backends/cpu/cpu-multi-threaded-backend/include/knp/backends/cpu-multi-threaded/backend.h b/knp/backends/cpu/cpu-multi-threaded-backend/include/knp/backends/cpu-multi-threaded/backend.h index 9fd026d4..2ebd1578 100644 --- a/knp/backends/cpu/cpu-multi-threaded-backend/include/knp/backends/cpu-multi-threaded/backend.h +++ b/knp/backends/cpu/cpu-multi-threaded-backend/include/knp/backends/cpu-multi-threaded/backend.h @@ -78,12 +78,14 @@ class KNP_DECLSPEC MultiThreadedCPUBackend : public knp::core::Backend /** * @brief List of neuron types supported by the multi-threaded CPU backend. */ - using SupportedNeurons = boost::mp11::mp_list; + using SupportedNeurons = + boost::mp11::mp_list; /** * @brief List of synapse types supported by the multi-threaded CPU backend. */ - using SupportedSynapses = boost::mp11::mp_list; + using SupportedSynapses = + boost::mp11::mp_list; /** * @brief List of supported population types based on neuron types specified in `SupportedNeurons`. @@ -119,7 +121,6 @@ class KNP_DECLSPEC MultiThreadedCPUBackend : public knp::core::Backend struct ProjectionWrapper { ProjectionVariants arg_; - // cppcheck-suppress unusedStructMember std::unordered_map messages_; }; @@ -350,14 +351,13 @@ class KNP_DECLSPEC MultiThreadedCPUBackend : public knp::core::Backend void calculate_populations_pre_impact(); // Processing messages, one thread per population, probably very hard to go deeper unless atomic neuron params. void calculate_populations_impact(); + // Do STDP logic for populations that support it. One thread per population. + void do_STDP(); // Calculating post input changes and outputs. std::vector calculate_populations_post_impact(); - // cppcheck-suppress unusedStructMember PopulationContainer populations_; ProjectionContainer projections_; - // cppcheck-suppress unusedStructMember const size_t population_part_size_; - // cppcheck-suppress unusedStructMember const size_t projection_part_size_; std::unique_ptr calc_pool_; std::mutex ep_mutex_; diff --git a/knp/tests/backend/multi_thread_cpu_test.cpp b/knp/tests/backend/multi_thread_cpu_test.cpp index 29305511..a1205b90 100644 --- a/knp/tests/backend/multi_thread_cpu_test.cpp +++ b/knp/tests/backend/multi_thread_cpu_test.cpp @@ -117,6 +117,49 @@ TEST(MultiThreadCpuSuite, SmallestNetwork) } +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(input_uid, {in_channel_uid}); + endpoint.subscribe(out_channel_uid, {population.get_uid()}); + + std::vector results; + + backend._init(); + + for (knp::core::Step step = 0; step < 20; ++step) + { + // Send inputs on steps 0, 5, 10, 15. + send_messages_smallest_network(in_channel_uid, endpoint, step); + backend._step(); + if (receive_messages_smallest_network(out_channel_uid, endpoint)) results.push_back(step); + } + + // Spikes on steps "5n + 1" (input) and on "previous_spike_n + 6" (positive feedback loop). + const std::vector 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; diff --git a/knp/tests/common/generators.h b/knp/tests/common/generators.h index be6e39cb..710f09ff 100644 --- a/knp/tests/common/generators.h +++ b/knp/tests/common/generators.h @@ -35,7 +35,10 @@ namespace knp::testing { using DeltaProjection = knp::core::Projection; +using ResourceDeltaProjection = knp::core::Projection; using BLIFATPopulation = knp::core::Population; +using ResourceBlifatPopulation = knp::core::Population; + // Create an input projection. inline std::optional input_projection_gen(size_t /*index*/) // NOLINT @@ -49,10 +52,46 @@ inline std::optional synapse_generator(size_t /*index* return DeltaProjection::Synapse{{1.1, 6, knp::synapse_traits::OutputType::EXCITATORY}, 0, 0}; } +// Create an input resource projection +inline std::optional input_res_projection_gen(size_t /*index*/) // NOLINT +{ + knp::synapse_traits::synapse_parameters syn; + syn.rule_.w_max_ = 2.0; + syn.rule_.w_min_ = 1.0; + syn.rule_.synaptic_resource_ = 1.0; + syn.rule_.dopamine_plasticity_period_ = 5; + syn.weight_ = 1.5; + syn.delay_ = 1; + syn.output_type_ = synapse_traits::OutputType::EXCITATORY; + return ResourceDeltaProjection::Synapse{syn, 0, 0}; +} + +// Create a loop resource projection +inline std::optional loop_res_projection_gen(size_t /*index*/) // NOLINT +{ + knp::synapse_traits::synapse_parameters syn; + syn.rule_.w_max_ = 2.0; + syn.rule_.w_min_ = 1.0; + syn.rule_.synaptic_resource_ = 1.0; + syn.rule_.dopamine_plasticity_period_ = 5; + syn.weight_ = 1.5; + syn.delay_ = 6; + syn.output_type_ = synapse_traits::OutputType::EXCITATORY; + return ResourceDeltaProjection::Synapse{syn, 0, 0}; +} + // Create population. inline knp::neuron_traits::neuron_parameters neuron_generator(size_t) // NOLINT { return knp::neuron_traits::neuron_parameters{}; } + + +// Create resource population +inline auto neuron_res_generator(size_t) // NOLINT +{ + return knp::neuron_traits::neuron_parameters{}; +} + } // namespace knp::testing