// Speed check: a convolution-like feature layer built from AltAI neurons in KNP. // usage: bench_conv // K prototypes × positions (6×6×3 patches of a 32×32×3 image with the given stride) AltAI neurons; random integer // weights; random input spikes (pixel rate ~0.4 per active step); 15 steps per image, 10 of them active. #include #include #include #include #include #include #include #include using DeltaProjection = knp::core::Projection; using AltAIPopulation = knp::core::Population; constexpr int side = 32, patch = 6, channels = 3, steps_per_image = 15, active_steps = 10; int main(int argc, const char *const argv[]) { const int k = std::stoi(argv[1]), stride = std::stoi(argv[2]), images = std::stoi(argv[3]); const int per_side = (side - patch) / stride + 1, positions = per_side * per_side; const size_t neurons = static_cast(k) * positions, fan_in = patch * patch * channels; AltAIPopulation population( [](size_t) { knp::neuron_traits::neuron_parameters p; p.activation_threshold_ = 1000; p.negative_activation_threshold_ = 0; p.potential_reset_value_ = 0; return p; }, neurons); std::mt19937 rng(1); std::uniform_int_distribution weight(-60, 60); std::vector w(fan_in * k); for (auto &v : w) v = weight(rng); // Synapse index → (neuron, patch element); the source is the input channel index of that pixel. DeltaProjection input_projection( knp::core::UID{false}, population.get_uid(), [&](size_t index) -> std::optional { const size_t neuron = index / fan_in, e = index % fan_in; const int proto = static_cast(neuron / positions), pos = static_cast(neuron % positions); const int py = (pos / per_side) * stride, px = (pos % per_side) * stride; const int c = static_cast(e / (patch * patch)), dy = static_cast(e % (patch * patch)) / patch, dx = static_cast(e % patch); const size_t source = static_cast(c * side * side + (py + dy) * side + px + dx); return DeltaProjection::Synapse{ {static_cast(w[proto * fan_in + e]), 1, knp::synapse_traits::OutputType::EXCITATORY}, source, neuron}; }, neurons * fan_in); const knp::core::UID input_uid = input_projection.get_uid(), output_uid = population.get_uid(); knp::framework::Network network; network.add_population(std::move(population)); network.add_projection(std::move(input_projection)); knp::core::UID in_channel, out_channel; knp::framework::Model model(std::move(network)); model.add_input_channel(in_channel, input_uid); model.add_output_channel(out_channel, output_uid); std::bernoulli_distribution fire(0.4); auto input_gen = [&](knp::core::Step step) -> knp::core::messaging::SpikeData { knp::core::messaging::SpikeData s; if (step % steps_per_image >= active_steps) return s; for (size_t i = 0; i < side * side * channels; ++i) if (fire(rng)) s.push_back(i); return s; }; knp::framework::BackendLoader loader; knp::framework::ModelExecutor executor(model, loader.load(argv[4]), {{in_channel, input_gen}}); auto &out = executor.get_loader().get_output_channel(out_channel); const auto t0 = std::chrono::steady_clock::now(); executor.start([&](size_t step) { return step < static_cast(images * steps_per_image); }); const double sec = std::chrono::duration(std::chrono::steady_clock::now() - t0).count(); size_t spikes = 0; for (const auto &m : out.update()) spikes += m.neuron_indexes_.size(); std::cout << "K=" << k << " stride=" << stride << " neurons=" << neurons << " synapses=" << neurons * fan_in << " images=" << images << " seconds=" << sec << " per_image=" << sec / images << " output_spikes_per_image=" << spikes / images << std::endl; }