diff --git a/unit_tests/operator/Test_MetaOperator.cpp b/unit_tests/operator/Test_MetaOperator.cpp index de720f5bc7e3c18bcd725c676d458f30344d3b1a..7b0b80d816eba8000e782e0e5238c2550dd4eed9 100644 --- a/unit_tests/operator/Test_MetaOperator.cpp +++ b/unit_tests/operator/Test_MetaOperator.cpp @@ -745,160 +745,105 @@ TEST_CASE("[cpu/operator] MetaOperator", "[MetaOperator][CPU]") { REQUIRE( approxEq<float>(*(fc2Op->getOutput(0)), *(expectedOutputfc2ts2))); } +} +TEST_CASE("[cpu/operator] MetaOperator", "[Leaky][CPU]") { SECTION("Leaky(forward)") { std::random_device rd; std::mt19937 gen(rd()); - std::uniform_real_distribution<float> valueDist( - 0.1f, - 1.1f); // Random float distribution between 0 and 1 - std::uniform_int_distribution<std::size_t> dimSizeDist(std::size_t(2), - std::size_t(4)); - std::uniform_int_distribution<std::size_t> nbDimsDist(std::size_t(3), - std::size_t(3)); + std::uniform_real_distribution<float> valueDist(0.1f,1.1f); + std::uniform_int_distribution<std::size_t> dimSizeDist(2,4); + std::uniform_int_distribution<std::size_t> nbDimsDist(3,3); // fixed to 3. std::uniform_int_distribution<int> boolDist(0, 1); std::uniform_real_distribution<float> betaDist(0,1); + std::uniform_real_distribution<float> thresholDist(0.1,3); - const std::size_t nbDims = nbDimsDist(gen); - Log::info("Nbdims : {}", nbDims); - std::vector<std::size_t> dims; - for (std::size_t i = 0; i < nbDims; ++i) { - dims.push_back(dimSizeDist(gen)); - } - Log::info("timesteps : {}", dims[0]); - Log::info("dimensions : "); - for (auto dim : dims) { - Log::info("{}", dim); - } - - const auto nbTimeSteps = dims[0]; const auto beta = betaDist(gen); + const auto threshold = thresholDist(gen); + const auto nbDims = nbDimsDist(gen); + std::vector<std::size_t> dims(nbDims); + std::generate(dims.begin(), dims.end(), [&]() { return dimSizeDist(gen); }); + const auto nbTimeSteps = dims[0]; - auto myLeaky = Leaky(nbTimeSteps, beta, 1.0, LeakyReset::Subtraction, "leaky"); - auto op = - std::static_pointer_cast<MetaOperator_Op>(myLeaky->getOperator()); - // auto stack = Stack(2); - auto mem_rec = Stack(nbTimeSteps, "mem_rec"); - auto spk_rec = Stack(nbTimeSteps, "spk_rec"); - auto pop = Pop("popinput"); - - // Here we test LSTM as it is was flatten in the graph. - // We just borrow its micro-graph into our larger myGraph graph. - auto myGraph = std::make_shared<GraphView>(); - - pop->addChild(op->getMicroGraph()->getOrderedInputs()[0].first, 0, 0); - // 0 for mem 1 for stack - op->getMicroGraph()->getOrderedOutputs()[1].first->addChild(mem_rec, - 0, - 0); - op->getMicroGraph()->getOrderedOutputs()[0].first->addChild(spk_rec, - 0, - 0); - for (auto node : op->getMicroGraph()->getOrderedOutputs()) { - Log::info("name of output {}", node.first->name()); - } - - myGraph->add(pop); - myGraph->add(op->getMicroGraph()); - myGraph->add(mem_rec); - myGraph->add(spk_rec); - myGraph->save("mg", true, true); - - // 3 outputs - REQUIRE(myLeaky->nbInputs() == 3); - REQUIRE(myLeaky->inputCategory(0) == InputCategory::Data); - // Two spikes connected to nothing, + the Add node real output - REQUIRE(myLeaky->nbOutputs() == 4); - - std::shared_ptr<Tensor> myInput = std::make_shared<Tensor>( - Array3D<float, 2, 3, 2>{{{{1.0, 2.0}, {3.0, 4.0}, {5.0, 6.0}}, - {{2.0, 3.0}, {4.0, 5.0}, {6.0, 7.0}}}}); - - // std::shared_ptr<Tensor> expectedOutput = std::make_shared<Tensor>( - // Array3D<float, 2, 3, 2>{{{{1.0, 2.0}, {3.0, 4.0}, {5.0, 6.0}}, - // {{2.0, 3.0}, {4.0, 5.0}, - // {6.0, 7.0}}}}); - - // Generate input - std::shared_ptr<Tensor> T0 = std::make_shared<Tensor>(); - T0->setDataType(DataType::Float32); - T0->setBackend("cpu"); + auto leakyNode = Leaky(nbTimeSteps, beta, threshold, LeakyReset::Subtraction, "leaky"); + auto leakyOp = std::static_pointer_cast<MetaOperator_Op>(leakyNode->getOperator()); + auto memoryRecord = Stack(nbTimeSteps, "mem_rec"); + auto spikeRecord = Stack(nbTimeSteps, "spk_rec"); + auto popNode = Pop("input"); - std::shared_ptr<Tensor> expectedOutput = std::make_shared<Tensor>(); - expectedOutput->setDataType(DataType::Float32); - expectedOutput->setBackend("cpu"); + REQUIRE(leakyNode->nbInputs() == 3); + REQUIRE(leakyNode->inputCategory(0) == InputCategory::Data); + REQUIRE(leakyNode->nbOutputs() == 4); const auto nb_elements = std::accumulate(dims.cbegin(), dims.cend(), std::size_t(1), std::multiplies<std::size_t>()); - float *input = new float[nb_elements]; - float *result = new float[nb_elements]; + const auto nbElementsPerTimeStep = nb_elements / dims[0]; - for (std::size_t i = 0; i < nb_elements; ++i) { - input[i] = valueDist(gen); - } - T0->resize(dims); - T0->getImpl()->setRawPtr(input, nb_elements); - T0->print(); - // Elements popped at each time step - auto nbElementsPerTimeStep = nb_elements / dims[0]; + // Compute the expected result using ad-hoc implementation // Init - for (int i = 0; i < nbElementsPerTimeStep; ++i) { - result[i] = input[i]; - } - - // Reccurence - for (int i = 1; i < dims[0]; ++i) { - auto offset = nbElementsPerTimeStep * i; - auto prev = nbElementsPerTimeStep * (i - 1); - for (int j = 0; j < nbElementsPerTimeStep; ++j) { - auto reset = (result[prev + j] > 1.0 ? 1 : 0); - result[offset + j] = - result[prev + j] * beta + input[offset + j] - reset; + auto *input = new float[nb_elements]; + std::generate_n(input, nb_elements, [&]() { return valueDist(gen); }); + auto *result = new float[nb_elements]; + std::copy(input, input + nbElementsPerTimeStep, result); + + // Recurrence calculation for each timestep + for (int timestep = 1; timestep < nbTimeSteps; ++timestep) { + const auto currentOffset = nbElementsPerTimeStep * timestep; + const auto previousOffset = nbElementsPerTimeStep * (timestep - 1); + + for (int element = 0; element < nbElementsPerTimeStep; ++element) { + const auto previousValue = result[previousOffset + element]; + const auto resetValue = (previousValue > threshold) ? threshold : 0; + + result[currentOffset + element] = + previousValue * beta + input[currentOffset + element] - resetValue; } } + auto expectedOutput = std::make_shared<Tensor>(DataType::Float32); + expectedOutput->setBackend("cpu"); expectedOutput->resize(dims); expectedOutput->getImpl()->setRawPtr(result, nb_elements); - Log::info("Expected ouptut : "); - expectedOutput->print(); - std::shared_ptr<Tensor> myInit = - std::make_shared<Tensor>(Array2D<float, 3, 3>{ - {{0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}}}); - auto initMemdims = - std::vector<std::size_t>(dims.begin() + 1, dims.end()); - Log::info("dimensions : "); - for (auto dim : initMemdims) { - Log::info("{}", dim); - } - std::shared_ptr<Tensor> myInitW = std::make_shared<Tensor>( - Array2D<float, 3, 2>{{{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}}); + // Compute the real result using our operator implemenation + auto inputTensor = std::make_shared<Tensor>(DataType::Float32); + inputTensor->setBackend("cpu"); + inputTensor->resize(dims); + inputTensor->getImpl()->setRawPtr(input, nb_elements); - std::shared_ptr<Tensor> myInitR = - std::make_shared<Tensor>(initMemdims); - myInitR->setDataType(DataType::Float32); - myInitR->setBackend("cpu"); - uniformFiller<float>(myInitR, 0, 0); + auto memoryInit = std::make_shared<Tensor>(DataType::Float32); + memoryInit->setBackend("cpu"); + memoryInit->resize(std::vector<std::size_t>(dims.begin() + 1, dims.end())); + memoryInit->zeros(); + auto memoryInitNode = Producer(memoryInit); - pop->getOperator()->associateInput(0, T0); - op->associateInput(1, myInitR); - op->associateInput(2, myInitR); + popNode->getOperator()->associateInput(0, inputTensor); + popNode->addChild(leakyNode,0, 0); + memoryInitNode->addChild(leakyNode, 0, 1); + memoryInitNode->addChild(leakyNode, 0, 2); + leakyNode->addChild(memoryRecord, 1, 0); + leakyNode->addChild(spikeRecord, 0, 0); - myGraph->compile("cpu", DataType::Float32); + auto g = std::make_shared<GraphView>(); + g->add({popNode, leakyNode, memoryRecord, spikeRecord, memoryInitNode}); + g->setDataType(DataType::Float32); + g->setBackend("cpu"); - auto scheduler = SequentialScheduler(myGraph); + auto scheduler = SequentialScheduler(g); REQUIRE_NOTHROW(scheduler.generateScheduling()); REQUIRE_NOTHROW(scheduler.forward(true)); + // Compare expected output with actual output auto memOp = - std::static_pointer_cast<OperatorTensor>(spk_rec->getOperator()); + std::static_pointer_cast<OperatorTensor>(spikeRecord->getOperator()); + //memOp->getOutput(0)->print(); REQUIRE(approxEq<float>(*(memOp->getOutput(0)), *(expectedOutput))); } }