our_knp/altai-objects/gpu/cuda_parity.cpp

95 lines
5.9 KiB
C++

// Per-step conformance against the actual KNP CPU backend, including export and pause/resume.
#include <knp/framework/model_executor.h>
#include <knp/framework/network.h>
#include <knp/neuron-traits/altai_lif.h>
#include <knp/synapse-traits/delta.h>
#include <cstring>
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <vector>
using P=knp::core::Population<knp::neuron_traits::AltAILIF>;
using R=knp::core::Projection<knp::synapse_traits::DeltaSynapse>;
using N=knp::neuron_traits::neuron_parameters<knp::neuron_traits::AltAILIF>;
using Type=knp::synapse_traits::OutputType;
struct Trace {std::vector<std::string> states;std::vector<std::string> spikes;};
unsigned bits(float v) {unsigned result;std::memcpy(&result,&v,4);return result;}
Trace run(const char *backend_path,bool split) {
P a([](size_t i){N n;n.activation_threshold_=3+i;n.negative_activation_threshold_=i%2?7:0;
n.potential_=i?static_cast<float>(static_cast<int>(i%5)-2):-0.F;
n.potential_reset_value_=i%3;n.potential_leak_=static_cast<int>(i%3)-1;
n.activity_time_=i%4==0?0:(i%4==1?-2:(i%4==2?1:INT64_MAX));
n.is_diff_=i%2;n.is_reset_=i%3!=0;n.saturate_=i%2;n.leak_rev_=i%2;n.do_not_save_=i%5==0;
return n;},11);
P b([](size_t i){N n;n.activation_threshold_=5+i;n.negative_activation_threshold_=0;return n;},4);
auto auid=a.get_uid(),buid=b.get_uid();
R input(knp::core::UID{false},auid,[](size_t j)->std::optional<R::Synapse>{
unsigned d=j%4==0?64:(j%4==1?7:(j%4==2?2:1));
return R::Synapse{{static_cast<float>(static_cast<int>(j%9)-4),d,Type::EXCITATORY},j%600,j/600};},11*600);
R bias(knp::core::UID{false},auid,[](size_t j)->std::optional<R::Synapse>{
return R::Synapse{{2,2,Type::INHIBITORY_CURRENT},0,j};},11);
R output(auid,buid,[](size_t j)->std::optional<R::Synapse>{
return R::Synapse{{static_cast<float>(static_cast<int>(j%7)-3),static_cast<unsigned>(1+j%2),Type::EXCITATORY},j%11,j/11};},44);
R recurrent(auid,auid,[](size_t j)->std::optional<R::Synapse>{
return R::Synapse{{-2,2,Type::EXCITATORY},j,(j+1)%11};},11);
auto input_uid=input.get_uid(),bias_uid=bias.get_uid();
knp::framework::Network net;
net.add_population(std::move(a));net.add_population(std::move(b));
net.add_projection(std::move(input));net.add_projection(std::move(bias));net.add_projection(std::move(output));net.add_projection(std::move(recurrent));
knp::framework::Model model(std::move(net));
knp::core::UID channel,outa,outb;
model.add_input_channel(channel,input_uid);model.add_input_channel(channel,bias_uid);
model.add_output_channel(outa,auid);model.add_output_channel(outb,buid);
auto generator=[](knp::core::Step step){knp::core::messaging::SpikeData data;
if(step<30) {for(unsigned i=0;i<600;++i) if((i+step)%7==0) data.push_back(i);
if(step%5==0) {data.push_back(0);data.push_back(0);}}
return data;};
knp::framework::BackendLoader loader;
knp::framework::ModelExecutor executor(model,loader.load(backend_path),{{channel,generator}});
Trace trace;
auto snapshot=[&](size_t step) {
std::ostringstream row;row<<step;
auto data=executor.get_backend()->get_network_data();
auto &it=data.population_range.first;auto &end=data.population_range.second;
while(*it!=*end) {auto variant=**it;const auto &pop=std::get<P>(variant);
for(const auto &n:pop) row<<','<<bits(n.potential_)<<':'<<bits(n.pre_impact_potential_)<<':'<<n.activity_time_;
++(*it);}
trace.states.push_back(row.str());
};
size_t last=static_cast<size_t>(-1);
auto execute=[&](size_t limit) {executor.start([&](size_t step){if(step!=last){snapshot(step);last=step;}return step<limit;});executor.stop();};
if(split) execute(17);
execute(100);
for(auto uid:{outa,outb}) {
std::ostringstream stream;
for(const auto &m:executor.get_loader().get_output_channel(uid).update()) {
stream<<m.header_.send_time_<<':';for(auto i:m.neuron_indexes_) stream<<i<<',';stream<<';';}
trace.spikes.push_back(stream.str());
}
return trace;
}
void rejected(const char *gpu, int scenario) {
P p([](size_t){N n;n.activation_threshold_=2;n.negative_activation_threshold_=0;return n;},1);
R r(knp::core::UID{false},p.get_uid(),[&](size_t)->std::optional<R::Synapse>{
return R::Synapse{{scenario==0?0.5F:(scenario==2?65535.F:1.F),static_cast<unsigned>(scenario==1?65:1),Type::EXCITATORY},0,0};},scenario==2?257:1);
auto pid=p.get_uid(),rid=r.get_uid();knp::framework::Network net;net.add_population(std::move(p));net.add_projection(std::move(r));
knp::framework::Model model(std::move(net));knp::core::UID input;model.add_input_channel(input,rid);
bool failed=false;
try {knp::framework::BackendLoader loader;knp::framework::ModelExecutor executor(model,loader.load(gpu),{{input,[](knp::core::Step){return knp::core::messaging::SpikeData{0};}}});executor.start([](size_t step){return step<3;});}
catch(const std::exception &e) {failed=true;std::cout<<"Expected rejection "<<scenario<<": "<<e.what()<<'\n';}
if(!failed) throw std::runtime_error("Unsupported network was silently accepted");
}
int main(int argc,char **argv) try {
if(argc!=3) return 2;
for(bool split:{false,true}) {
auto cpu=run(argv[1],split),gpu=run(argv[2],split);
if(cpu.states!=gpu.states) {
for(size_t i=0;i<std::min(cpu.states.size(),gpu.states.size());++i) if(cpu.states[i]!=gpu.states[i]) {
std::cerr<<"CPU "<<cpu.states[i]<<"\nGPU "<<gpu.states[i]<<'\n';break;}
throw std::runtime_error("Per-step neuron state mismatch");}
if(cpu.spikes!=gpu.spikes) throw std::runtime_error("Per-step spike mismatch");
std::cout<<"Exact CPU/CUDA parity: "<<cpu.states.size()<<" snapshots, split="<<split<<'\n';
}
for(int i=0;i<3;++i) rejected(argv[2],i);
std::cout<<"CUDA_PARITY_OK\n";
} catch(const std::exception &e) {std::cerr<<e.what()<<'\n';return 1;}