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

416 lines
14 KiB
Text

/**
* @file backend_impl.cu
* @brief CUDABackendImpl backend class implementation.
* @kaspersky_support Artiom N.
* @date 24.02.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 <knp/devices/gpu_cuda.h>
#include <knp/meta/assert_helpers.h>
#include <knp/meta/stringify.h>
#include <knp/meta/variant_helpers.h>
#include <spdlog/spdlog.h>
#include <limits>
#include <vector>
#include <boost/mp11.hpp>
#include <algorithm>
#include "backend_impl.cuh"
#include "projection.cuh"
#include "population.cuh"
#include "cuda_lib/fast_error_check.cuh"
#include "cuda_lib/get_blocks_config.cuh"
#include "cuda_lib/printf.cuh"
#include "cuda_lib/register_all.cuh"
#include "cuda_lib/vector.cuh"
#include "cuda_bus/messaging.cuh"
#include <thrust/binary_search.h>
#include <thrust/sort.h>
namespace knp::backends::gpu::cuda
{
// helper type for the visitor.
template<class... Ts>
struct overloaded : Ts ...
{
using Ts::operator()...;
};
// explicit deduction guide.
template<class... Ts>
overloaded(Ts...) -> overloaded<Ts...>;
template <>
PopulationVariants gpu_extract<PopulationVariants>(const PopulationVariants *);
template <>
void gpu_insert<PopulationVariants>(const PopulationVariants &, PopulationVariants *);
template <>
ProjectionVariants gpu_extract<ProjectionVariants>(const ProjectionVariants *);
template <>
void gpu_insert<ProjectionVariants>(const ProjectionVariants &, ProjectionVariants *);
namespace detail
{
template <class Variant, class Instance>
__global__ void make_variant_kernel(Variant *result, Instance *source)
{
new (result) Variant(*source);
}
}
template<class TypeVariant, size_t index>
TypeVariant extract_by_index(const void *type_ptr)
{
return gpu_extract<boost::mp11::mp_at_c<TypeVariant, index>>(
reinterpret_cast<const boost::mp11::mp_at_c<TypeVariant, index> *>(type_ptr));
}
// TODO: Make a template, it's also used for messages.
template<typename T>
__host__ __device__ void get_kernel(const T *var, int *type, const void **val)
{
int type_val = var->index();
static_assert(::cuda::std::variant_size<T>() == 1, "Incorrect variant size!");
switch (type_val)
{
case 0:
*val = ::cuda::std::get_if<0>(var);
break;
default:
*val = nullptr;
}
*type = type_val;
}
__global__ void get_population_kernel(const PopulationVariants *var, int *type, const void **pop)
{
get_kernel(var, type, pop);
}
__global__ void get_projection_kernel(const ProjectionVariants *var, int *type, const void **proj)
{
get_kernel(var, type, proj);
}
template<>
void gpu_insert<PopulationVariants>(const PopulationVariants &cpu_source, PopulationVariants *gpu_target)
{
::cuda::std::visit([gpu_target](const auto &val)
{
using ValueType = std::decay_t<decltype(val)>;
ValueType *buffer;
call_and_check(cudaMalloc(&buffer, sizeof(ValueType)));
gpu_insert(val, buffer);
device_lib::make_variant_kernel<<<1, 1>>>(gpu_target, buffer);
call_and_check(cudaFree(buffer));
}, cpu_source);
}
template<class T>
__global__ void get_uids_kernel(const T *data, size_t size, cuda::UID *result)
{
const size_t index = blockIdx.x * blockDim.x + threadIdx.x;
if (index >= size) return;
result[index] = ::cuda::std::visit([](auto &v) { return v.uid_; }, data[index]);
}
template<class VectorData>
device_lib::CUDAVector<cuda::UID> get_uids(const device_lib::CUDAVector<VectorData> &entities)
{
device_lib::CUDAVector<cuda::UID> result(entities.size());
if (entities.size() != 0)
{
auto [num_blocks, num_threads] = device_lib::get_blocks_config(entities.size());
get_uids_kernel<<<num_blocks, num_threads>>>(entities.data(), entities.size(), result.data());
}
return result;
}
template<class VectorData>
device_lib::CUDAVector<cuda::UID> get_uids_std(const std::vector<VectorData> &entities)
{
device_lib::CUDAVector<cuda::UID> result;
result.reserve(entities.size());
for (size_t i = 0; i < entities.size(); ++i)
{
::cuda::std::visit([&result](const auto &entity)
{
result.push_back(entity.uid_);
}, entities[i]);
}
return result;
}
void CUDABackendImpl::calculate_projections(StepIndex step)
{
// Calculate projections.
device_lib::CUDAVector<cuda::UID> projection_uids = get_uids_std(device_projections_);
if (!device_projections_.size()) return;
std::vector<std::vector<device_lib::LongIndex>> projection_messages;
projection_messages.reserve(device_projections_.size());
for (size_t i = 0; i < device_projections_.size(); ++i)
{
const std::vector<device_lib::LongIndex> message_ids
= device_message_bus_.unload_messages<cuda::SpikeMessage>(projection_uids.copy_at(i));
projection_messages.push_back(message_ids);
}
assert(device_projections_.size() == projection_messages.size());
for (size_t i = 0; i < device_projections_.size(); ++i)
{
::cuda::std::visit([this, &projection_messages, step, i](auto &projection)
{
calculate_projection(projection, device_message_bus_, projection_messages[i], step);
}, device_projections_[i]);
}
cudaDeviceSynchronize();
}
void CUDABackendImpl::load_populations(const knp::backends::gpu::CUDABackend::PopulationContainer &populations)
{
SPDLOG_DEBUG("Loading populations [{}]...", populations.size());
device_populations_.clear();
device_populations_.reserve(populations.size());
for (const auto &population : populations)
{
::std::visit([this](auto &arg)
{
using CPUPopulationType = std::decay_t<decltype(arg)>;
auto pop = CUDAPopulation<typename CPUPopulationType::PopulationNeuronType>(arg);
device_populations_.push_back(pop);
}, population);
}
SPDLOG_DEBUG("All populations loaded.");
}
void CUDABackendImpl::load_projections(const knp::backends::gpu::CUDABackend::ProjectionContainer &projections)
{
SPDLOG_DEBUG("Loading projections [{}]...", projections.size());
CUDA_FAST_ERROR_CHECK("Starting to load, already an error: {}");
device_projections_.clear();
CUDA_FAST_ERROR_CHECK("Cleared device projections: {}");
device_projections_.reserve(projections.size());
CUDA_FAST_ERROR_CHECK("Reserving device projections: {}");
for (const auto &projection : projections)
{
::std::visit([this](auto &arg)
{
using CPUProjectionType = std::decay_t<decltype(arg)>;
auto proj = CUDAProjection<typename CPUProjectionType::ProjectionSynapseType>{arg};
SPDLOG_DEBUG("Pushing back a projection, size before: {}, pointer before: {}, capacity {}",
device_projections_.size(), reinterpret_cast<void *>(device_projections_.data()),
device_projections_.capacity());
device_projections_.push_back(proj);
CUDA_FAST_ERROR_CHECK("Pushed back {}");
SPDLOG_DEBUG("Pushed back: size after: {}, pointer after: {}, capacity {}", device_projections_.size(),
reinterpret_cast<void *>(device_projections_.data()), device_projections_.capacity());
}, projection);
}
SPDLOG_DEBUG("All projections loaded.");
}
void CUDABackendImpl::init()
{
SPDLOG_DEBUG("Initializing CUDABackendImpl...");
// knp::backends::cpu::init(projections_, get_message_endpoint());
for (size_t i = 0; i < device_projections_.size(); ++i)
{
const auto [pre_uid, post_uid, this_uid] = ::cuda::std::visit([](auto &proj)
{
return std::make_tuple(proj.presynaptic_uid_, proj.postsynaptic_uid_, proj.uid_);
}, device_projections_[i]);
if (!cuda::empty_uid(pre_uid)) this->device_message_bus_.subscribe_gpu<cuda::SpikeMessage>(this_uid, {pre_uid});
if (!cuda::empty_uid(post_uid))
{
this->device_message_bus_.subscribe_gpu<cuda::SynapticImpactMessage>(post_uid, {this_uid});
}
}
SPDLOG_DEBUG("Initialization finished.");
}
void CUDABackendImpl::calculate_populations(StepIndex step)
{
// device_message_bus_.clear<SpikeMessage>();
for (auto &population : device_populations_)
{
::cuda::std::visit([this, step](auto &pop)
{
auto spikes = calculate_population(pop, device_message_bus_, step);
if (!spikes.empty())
{
cuda::SpikeMessage message{MessageHeader{pop.uid_, step, false}, std::move(spikes)};
SPDLOG_DEBUG("Population {} created a message with {} spikes", std::string(to_cpu_uid(pop.uid_)),
message.neuron_indexes_.size());
device_message_bus_.send_message(std::move(message));
}
else
{
SPDLOG_DEBUG("No spike messages were sent from population {}", ::std::string(to_cpu_uid(pop.uid_)));
}
}, population);
}
}
__host__ uint64_t CUDABackendImpl::route_projection_messages(StepIndex step)
{
using MessageVector = device_lib::CUDAVector<cuda::MessageVariant>;
device_lib::LongIndex sent_message_counter = 0;
device_message_bus_.clear();
for (size_t i = 0; i < device_projections_.size(); ++i)
{
::cuda::std::visit([this, &sent_message_counter](auto &proj)
{
if (proj.message_buf_.impacts_.size())
{
device_message_bus_.send_message(std::move(proj.message_buf_));
// cudaDeviceSynchronize();
proj.message_buf_.impacts_.clear();
++sent_message_counter;
}
}, device_projections_[i]);
}
cudaDeviceSynchronize();
SPDLOG_DEBUG("Projections sent {} messages", sent_message_counter);
return sent_message_counter;
}
__host__ CUDABackendImpl::PopulationIterator CUDABackendImpl::begin_populations()
{
return PopulationIterator{device_populations_.begin()};
}
__host__ CUDABackendImpl::PopulationConstIterator CUDABackendImpl::begin_populations() const
{
return {device_populations_.cbegin()};
}
__host__ CUDABackendImpl::PopulationIterator CUDABackendImpl::end_populations()
{
return PopulationIterator{device_populations_.end()};
}
__host__ CUDABackendImpl::PopulationConstIterator CUDABackendImpl::end_populations() const
{
return device_populations_.cend();
}
__host__ CUDABackendImpl::ProjectionIterator CUDABackendImpl::begin_projections()
{
return ProjectionIterator{device_projections_.begin()};
}
__host__ CUDABackendImpl::ProjectionConstIterator CUDABackendImpl::begin_projections() const
{
return device_projections_.cbegin();
}
__host__ CUDABackendImpl::ProjectionIterator CUDABackendImpl::end_projections()
{
return ProjectionIterator{device_projections_.end()};
}
__host__ CUDABackendImpl::ProjectionConstIterator CUDABackendImpl::end_projections() const
{
return device_projections_.cend();
}
__global__ void get_spike_message_data(device_lib::CUDAVectorView<cuda::MessageVariant> all_messages,
device_lib::LongIndex msg_index, device_lib::LongIndex *size, const SpikeIndex **data_pointer)
{
constexpr size_t spike_message_index = boost::mp11::mp_find<cuda::MessageVariant, cuda::SpikeMessage>();
auto &message_var = all_messages.data_[msg_index];
if (message_var.index() != spike_message_index)
{
*size = 0;
*data_pointer = nullptr;
PRINTF_DEBUG("Pointer (no msg): %p, size: %lu\n", *data_pointer, *size);
return;
}
*data_pointer = ::cuda::std::get<cuda::SpikeMessage>(message_var).neuron_indexes_.data();
*size = ::cuda::std::get<cuda::SpikeMessage>(message_var).neuron_indexes_.size();
#ifdef DEBUG
printf("Pointer: %p, size: %lu\n", *data_pointer, *size);
for (size_t i = 0; i < *size; ++i)
{
printf("%u ", ::cuda::std::get<cuda::SpikeMessage>(message_var).neuron_indexes_.data()[i]);
}
printf("\n");
#endif // DEBUG
}
} // namespace knp::backends::gpu::cuda
// TODO: Replace with a loop macro.
REGISTER_CUDA_VECTOR_TYPE(knp::backends::gpu::cuda::PopulationVariants);
REGISTER_CUDA_VECTOR_TYPE(knp::backends::gpu::cuda::ProjectionVariants);
REGISTER_CUDA_VECTOR_TYPE(knp::backends::gpu::cuda::SynapticImpact);
REGISTER_CUDA_VECTOR_TYPE(knp::backends::gpu::cuda::CUDAPopulation<knp::neuron_traits::BLIFATNeuron>);
REGISTER_CUDA_VECTOR_TYPE(knp::backends::gpu::cuda::CUDAProjection<knp::synapse_traits::DeltaSynapse>);
REGISTER_CUDA_VECTOR_TYPE(knp::backends::gpu::cuda::CUDAProjection<knp::synapse_traits::DeltaSynapse>::Synapse);
REGISTER_CUDA_VECTOR_TYPE(knp::backends::gpu::cuda::CUDAPopulation<knp::neuron_traits::BLIFATNeuron>::NeuronParameters);
REGISTER_CUDA_VECTOR_TYPE(knp::backends::gpu::cuda::device_lib::CUDAVector<unsigned long long>);
REGISTER_CUDA_VECTOR_TYPE(unsigned int);
REGISTER_CUDA_VECTOR_TYPE(unsigned long long);
REGISTER_CUDA_VECTOR_TYPE(knp::backends::gpu::cuda::Subscription);
REGISTER_CUDA_VECTOR_TYPE(knp::backends::gpu::cuda::MessageVariant);
REGISTER_CUDA_VECTOR_TYPE(knp::backends::gpu::cuda::UID);