our_knp/examples/knp/backends/gpu/cuda-backend/impl/populations_impl.cu
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

201 lines
7.8 KiB
Text

/**
* @file populations_impl.cu
* @brief Contains functions for population calculation.
* @kaspersky_support A. Vartenkov
* @date 17.08.2026
* @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 "population.cuh"
#include "populations_impl.cuh"
#include <knp/core/population.h>
/**
* @brief Namespace for CUDA backend.
*/
namespace knp::backends::gpu::cuda
{
using BlifatParams = knp::neuron_traits::neuron_parameters<knp::neuron_traits::BLIFATNeuron>;
__global__ void calculate_neurons_pre_impact(device_lib::CUDAVectorMutableView<BlifatParams> neurons,
StepIndex current_step)
{
const size_t neuron_index = blockIdx.x * blockDim.x + threadIdx.x;
if (neuron_index >= neurons.size_) return;
BlifatParams &neuron = neurons.data_[neuron_index];
++neuron.n_time_steps_since_last_firing_;
neuron.dynamic_threshold_ *= neuron.threshold_decay_;
neuron.postsynaptic_trace_ *= neuron.postsynaptic_trace_decay_;
neuron.inhibitory_conductance_ *= neuron.inhibitory_conductance_decay_;
/*
if constexpr (has_dopamine_plasticity<BlifatLikeNeuron>())
{
neuron.dopamine_value_ = 0.0;
neuron.is_being_forced_ = false;
}
*/
if (neuron.bursting_phase_ && !--neuron.bursting_phase_)
{
neuron.potential_ = neuron.potential_ * neuron.potential_decay_ + neuron.reflexive_weight_;
}
else
{
neuron.potential_ *= neuron.potential_decay_;
}
neuron.pre_impact_potential_ = neuron.potential_;
}
__global__ void calculate_neurons_impacts(device_lib::CUDAVectorMutableView<BlifatParams> neurons,
device_lib::CUDAVectorView<SynapticImpact> impacts)
{
const size_t impact_index = blockIdx.x * blockDim.x + threadIdx.x;
if (impact_index >= impacts.size_) return;
const SynapticImpact &impact = impacts.data_[impact_index];
if (impact.postsynaptic_neuron_index_ >= neurons.size_) return;
auto &neuron = neurons.data_[impact.postsynaptic_neuron_index_];
switch (impact.synapse_type_)
{
case knp::synapse_traits::OutputType::EXCITATORY:
atomicAdd(&neuron.potential_, impact.impact_value_);
break;
case knp::synapse_traits::OutputType::INHIBITORY_CURRENT:
atomicAdd(&neuron.potential_, -impact.impact_value_);
break;
case knp::synapse_traits::OutputType::INHIBITORY_CONDUCTANCE:
atomicAdd(&neuron.inhibitory_conductance_, impact.impact_value_);
break;
case knp::synapse_traits::OutputType::DOPAMINE:
atomicAdd(&neuron.dopamine_value_, impact.impact_value_);
break;
case knp::synapse_traits::OutputType::BLOCKING:
neuron.total_blocking_period_ = static_cast<unsigned int>(impact.impact_value_);
break;
}
}
__host__ void calculate_neurons_impacts_all(device_lib::CUDAVectorMutableView<BlifatParams> neurons,
const std::vector<SynapticImpactMessage> &messages_all,
std::vector<device_lib::LongIndex> message_ids)
{
for (const auto &msg_id : message_ids)
{
const auto &msg = messages_all[msg_id];
auto [num_blocks, num_threads] = device_lib::get_blocks_config(msg.impacts_.size());
calculate_neurons_impacts<<<num_blocks, num_threads>>>(neurons, msg.impacts_.view());
}
cudaDeviceSynchronize();
}
__global__ void calculate_neurons_post_impact(device_lib::CUDAVectorMutableView<BlifatParams> neurons,
SpikeIndex *spike_buffer, SpikeIndex *size_counter)
{
const size_t neuron_index = blockIdx.x * blockDim.x + threadIdx.x;
bool spike = false;
neuron_traits::neuron_parameters <neuron_traits::BLIFATNeuron> &neuron = neurons.data_[neuron_index];
if (neuron.total_blocking_period_ <= 0)
{
// TODO: Make it more readable, don't be afraid to use if operators.
// Restore potential that the neuron had before impacts.
neuron.potential_ = neuron.pre_impact_potential_;
bool was_negative = neuron.total_blocking_period_ < 0;
// If it is negative, increase by 1.
neuron.total_blocking_period_ += was_negative;
// If it is now zero, but was negative before, increase it to max, else leave it as is.
neuron.total_blocking_period_ +=
std::numeric_limits<int64_t>::max() * ((neuron.total_blocking_period_ == 0) && was_negative);
}
else
{
neuron.total_blocking_period_ -= 1;
}
if (neuron.inhibitory_conductance_ < 1.0)
{
neuron.potential_ -=
(neuron.potential_ - neuron.reversal_inhibitory_potential_) *
neuron.inhibitory_conductance_;
}
else
{
neuron.potential_ = neuron.reversal_inhibitory_potential_;
}
if ((neuron.n_time_steps_since_last_firing_ > neuron.absolute_refractory_period_) &&
(neuron.potential_ >= neuron.activation_threshold_ + neuron.dynamic_threshold_))
{
// Spike.
neuron.dynamic_threshold_ += neuron.threshold_increment_;
neuron.postsynaptic_trace_ += neuron.postsynaptic_trace_increment_;
neuron.potential_ = neuron.potential_reset_value_;
neuron.bursting_phase_ = neuron.bursting_period_;
neuron.n_time_steps_since_last_firing_ = 0;
spike = true;
}
if (neuron.potential_ < neuron.min_potential_)
{
neuron.potential_ = neuron.min_potential_;
}
if (spike)
{
SpikeIndex counter = atomicAdd(size_counter, 1);
spike_buffer[counter] = neuron_index;
}
}
device_lib::CUDAVector<SpikeIndex> calculate_population(
CUDAPopulation<knp::neuron_traits::BLIFATNeuron> &population, const CUDAMessageBus& device_message_bus,
StepIndex step)
{
auto [num_blocks_neuro, num_threads_neuro] = device_lib::get_blocks_config(population.neurons_.size());
calculate_neurons_pre_impact<<<num_blocks_neuro, num_threads_neuro>>>(population.neurons_.mut_view(), step);
std::vector<device_lib::LongIndex> message_ids
= device_message_bus.unload_messages<cuda::SynapticImpactMessage>(population.uid_);
if (!message_ids.empty())
{
SPDLOG_DEBUG("Running calculate impacts on {} messages", message_ids.size());
calculate_neurons_impacts_all(population.neurons_.mut_view(),
device_message_bus.all_messages<SynapticImpactMessage>(),
message_ids);
}
SpikeIndex *output;
cudaMalloc(&output, sizeof(SpikeIndex) * population.neurons_.size());
SpikeIndex *counter;
cudaMalloc(&counter, sizeof(SpikeIndex));
cudaMemset(counter, 0, sizeof(SpikeIndex));
calculate_neurons_post_impact<<<num_blocks_neuro, num_threads_neuro>>>(population.neurons_.mut_view(), output,
counter);
SpikeIndex out_size = 0;
cudaMemcpy(&out_size, counter, sizeof(SpikeIndex), cudaMemcpyDeviceToHost);
// Capacity would be "out_size" while the ptr is larger, but that doesn't matter as the pointer is freed as a whole.
device_lib::CUDAVector<SpikeIndex> result{output, out_size};
cudaFree(counter);
return result;
}
} // namespace knp::backends::gpu::cuda