95 lines
5.9 KiB
C++
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;}
|