From eaed3ee20320f94dc2779735565a2976bdfeb534 Mon Sep 17 00:00:00 2001 From: neworderofjamie Date: Tue, 20 Jul 2021 15:53:35 +0100 Subject: [PATCH 01/26] new variable access modes --- include/genn/genn/varAccess.h | 42 +++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/include/genn/genn/varAccess.h b/include/genn/genn/varAccess.h index cd42cdca1f..b0b37efd40 100644 --- a/include/genn/genn/varAccess.h +++ b/include/genn/genn/varAccess.h @@ -4,26 +4,40 @@ //---------------------------------------------------------------------------- // Enumerations //---------------------------------------------------------------------------- -//! Flags defining variable access models +//! Flags defining attributes of var access models +enum class VarAccessModeAttribute : unsigned int +{ + READ = (1 << 0), //! This variable can be read + WRITE = (1 << 1), //! This variable can be written + REDUCE = (1 << 2), //! This variable is a reduction target + SUM = (1 << 3), //! This variable's reduction operation is a summation + MAX = (1 << 4), //! This variable's reduction operation is a maximum +}; + +//! Supported combination of VarAccessModeAttribute enum class VarAccessMode : unsigned int { - READ_WRITE = (1 << 0), //! This variable is both read and written by the model - READ_ONLY = (1 << 1), //! This variable is only read by the model + READ_WRITE = static_cast(VarAccessModeAttribute::READ) | static_cast(VarAccessModeAttribute::WRITE), + READ_ONLY = static_cast(VarAccessModeAttribute::READ), + REDUCE_SUM = static_cast(VarAccessModeAttribute::REDUCE) | static_cast(VarAccessModeAttribute::SUM), + REDUCE_MAX = static_cast(VarAccessModeAttribute::REDUCE) | static_cast(VarAccessModeAttribute::MAX), }; //! Flags defining how variables should be duplicated across multiple batches enum class VarAccessDuplication : unsigned int { - DUPLICATE = (1 << 2), //! This variable should be duplicated in each batch - SHARED = (1 << 3), //! This variable should be shared between batches + DUPLICATE = (1 << 4), //! This variable should be duplicated in each batch + SHARED = (1 << 5), //! This variable should be shared between batches }; -//! Supported combinations of SynapticMatrixConnectivity and SynapticMatrixWeight +//! Supported combinations of VarAccessMode and VarAccessDuplication enum class VarAccess : unsigned int { READ_WRITE = static_cast(VarAccessMode::READ_WRITE) | static_cast(VarAccessDuplication::DUPLICATE), READ_ONLY = static_cast(VarAccessMode::READ_ONLY) | static_cast(VarAccessDuplication::SHARED), READ_ONLY_DUPLICATE = static_cast(VarAccessMode::READ_ONLY) | static_cast(VarAccessDuplication::DUPLICATE), + REDUCE_BATCH_SUM = static_cast(VarAccessMode::REDUCE_SUM) | static_cast(VarAccessDuplication::SHARED), + REDUCE_BATCH_MAX = static_cast(VarAccessMode::REDUCE_MAX) | static_cast(VarAccessDuplication::SHARED), }; //---------------------------------------------------------------------------- @@ -39,21 +53,25 @@ inline bool operator & (VarAccess type, VarAccessDuplication duplication) return (static_cast(type) & static_cast(duplication)) != 0; } -inline bool operator & (VarAccessDuplication a, VarAccessDuplication b) +inline bool operator & (VarAccess type, VarAccessModeAttribute modeAttribute) { - return (static_cast(a) & static_cast(b)) != 0; + return (static_cast(type) & static_cast(modeAttribute)) != 0; +} + +inline bool operator & (VarAccessMode mode, VarAccessModeAttribute modeAttribute) +{ + return (static_cast(mode) & static_cast(modeAttribute)) != 0; } //---------------------------------------------------------------------------- // Helpers //---------------------------------------------------------------------------- -// **THINK** these are kinda nasty as they can return things that aren't actually in the bit enums i.e. ORd together things inline VarAccessMode getVarAccessMode(VarAccess type) { - return static_cast(static_cast(type) & 0x5); + return static_cast(static_cast(type) & 0x1F); } inline VarAccessDuplication getVarAccessDuplication(VarAccess type) { - return static_cast(static_cast(type) & ~0x5); -} \ No newline at end of file + return static_cast(static_cast(type) & ~0x1F); +} From 8c1784644a1f38712c18f43f66aeadd924c9db79 Mon Sep 17 00:00:00 2001 From: neworderofjamie Date: Tue, 20 Jul 2021 15:54:37 +0100 Subject: [PATCH 02/26] removed strange usage of VarAccessDuplication & VarAccessDuplication operator --- src/genn/genn/code_generator/generateInit.cc | 4 ++-- src/genn/genn/code_generator/groupMerged.cc | 24 ++++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/genn/genn/code_generator/generateInit.cc b/src/genn/genn/code_generator/generateInit.cc index 9508d8ebec..bf5fdfa64f 100644 --- a/src/genn/genn/code_generator/generateInit.cc +++ b/src/genn/genn/code_generator/generateInit.cc @@ -23,7 +23,7 @@ void genVariableFill(CodeStream &os, const std::string &fieldName, const std::st VarAccessDuplication varDuplication, unsigned int batchSize, bool delay = false, unsigned int numDelaySlots = 1) { // Determine number of values to fill in each thread - const unsigned int numValues = ((varDuplication & VarAccessDuplication::SHARED) ? 1 : batchSize) * ((delay ? numDelaySlots : 1)); + const unsigned int numValues = ((varDuplication == VarAccessDuplication::SHARED) ? 1 : batchSize) * ((delay ? numDelaySlots : 1)); // If there's only one, don't generate a loop if(numValues == 1) { @@ -43,7 +43,7 @@ void genScalarFill(CodeStream &os, const std::string &fieldName, const std::stri VarAccessDuplication varDuplication, unsigned int batchSize, bool delay = false, unsigned int numDelaySlots = 1) { // Determine number of values to fill in each thread - const unsigned int numValues = ((varDuplication & VarAccessDuplication::SHARED) ? 1 : batchSize) * ((delay ? numDelaySlots : 1)); + const unsigned int numValues = ((varDuplication == VarAccessDuplication::SHARED) ? 1 : batchSize) * ((delay ? numDelaySlots : 1)); // If there's only one, don't generate a loop if(numValues == 1) { diff --git a/src/genn/genn/code_generator/groupMerged.cc b/src/genn/genn/code_generator/groupMerged.cc index 76decf729d..a3a20d0f9d 100644 --- a/src/genn/genn/code_generator/groupMerged.cc +++ b/src/genn/genn/code_generator/groupMerged.cc @@ -746,13 +746,13 @@ boost::uuids::detail::sha1::digest_type NeuronUpdateGroupMerged::getHashDigest() std::string NeuronUpdateGroupMerged::getVarIndex(unsigned int batchSize, VarAccessDuplication varDuplication, const std::string &index) { // **YUCK** there's a lot of duplication in these methods - do they belong elsewhere? - return ((varDuplication & VarAccessDuplication::SHARED || batchSize == 1) ? "" : "batchOffset + ") + index; + return ((varDuplication == VarAccessDuplication::SHARED || batchSize == 1) ? "" : "batchOffset + ") + index; } //-------------------------------------------------------------------------- std::string NeuronUpdateGroupMerged::getReadVarIndex(bool delay, unsigned int batchSize, VarAccessDuplication varDuplication, const std::string &index) { if(delay) { - return ((varDuplication & VarAccessDuplication::SHARED || batchSize == 1) ? "readDelayOffset + " : "readBatchDelayOffset + ") + index; + return ((varDuplication == VarAccessDuplication::SHARED || batchSize == 1) ? "readDelayOffset + " : "readBatchDelayOffset + ") + index; } else { return getVarIndex(batchSize, varDuplication, index); @@ -762,7 +762,7 @@ std::string NeuronUpdateGroupMerged::getReadVarIndex(bool delay, unsigned int ba std::string NeuronUpdateGroupMerged::getWriteVarIndex(bool delay, unsigned int batchSize, VarAccessDuplication varDuplication, const std::string &index) { if(delay) { - return ((varDuplication & VarAccessDuplication::SHARED || batchSize == 1) ? "writeDelayOffset + " : "writeBatchDelayOffset + ") + index; + return ((varDuplication == VarAccessDuplication::SHARED || batchSize == 1) ? "writeDelayOffset + " : "writeBatchDelayOffset + ") + index; } else { return getVarIndex(batchSize, varDuplication, index); @@ -1234,7 +1234,7 @@ std::string SynapseGroupMergedBase::getPostDenDelayIndex(unsigned int batchSize, //---------------------------------------------------------------------------- std::string SynapseGroupMergedBase::getPreVarIndex(bool delay, unsigned int batchSize, VarAccessDuplication varDuplication, const std::string &index) { - const bool singleBatch = (varDuplication & VarAccessDuplication::SHARED || batchSize == 1); + const bool singleBatch = (varDuplication == VarAccessDuplication::SHARED || batchSize == 1); if(delay) { return (singleBatch ? "preDelayOffset + " : "preBatchDelayOffset + ") + index; } @@ -1245,7 +1245,7 @@ std::string SynapseGroupMergedBase::getPreVarIndex(bool delay, unsigned int batc //-------------------------------------------------------------------------- std::string SynapseGroupMergedBase::getPostVarIndex(bool delay, unsigned int batchSize, VarAccessDuplication varDuplication, const std::string &index) { - const bool singleBatch = (varDuplication & VarAccessDuplication::SHARED || batchSize == 1); + const bool singleBatch = (varDuplication == VarAccessDuplication::SHARED || batchSize == 1); if(delay) { return (singleBatch ? "postDelayOffset + " : "postBatchDelayOffset + ") + index; } @@ -1256,7 +1256,7 @@ std::string SynapseGroupMergedBase::getPostVarIndex(bool delay, unsigned int bat //-------------------------------------------------------------------------- std::string SynapseGroupMergedBase::getPrePrevSpikeTimeIndex(bool delay, unsigned int batchSize, VarAccessDuplication varDuplication, const std::string &index) { - const bool singleBatch = (varDuplication & VarAccessDuplication::SHARED || batchSize == 1); + const bool singleBatch = (varDuplication == VarAccessDuplication::SHARED || batchSize == 1); if(delay) { return (singleBatch ? "prePrevSpikeTimeDelayOffset + " : "prePrevSpikeTimeBatchDelayOffset + ") + index; @@ -1268,7 +1268,7 @@ std::string SynapseGroupMergedBase::getPrePrevSpikeTimeIndex(bool delay, unsigne //-------------------------------------------------------------------------- std::string SynapseGroupMergedBase::getPostPrevSpikeTimeIndex(bool delay, unsigned int batchSize, VarAccessDuplication varDuplication, const std::string &index) { - const bool singleBatch = (varDuplication & VarAccessDuplication::SHARED || batchSize == 1); + const bool singleBatch = (varDuplication == VarAccessDuplication::SHARED || batchSize == 1); if(delay) { return (singleBatch ? "postPrevSpikeTimeDelayOffset + " : "postPrevSpikeTimeBatchDelayOffset + ") + index; @@ -1280,7 +1280,7 @@ std::string SynapseGroupMergedBase::getPostPrevSpikeTimeIndex(bool delay, unsign //-------------------------------------------------------------------------- std::string SynapseGroupMergedBase::getSynVarIndex(unsigned int batchSize, VarAccessDuplication varDuplication, const std::string &index) { - const bool singleBatch = (varDuplication & VarAccessDuplication::SHARED || batchSize == 1); + const bool singleBatch = (varDuplication == VarAccessDuplication::SHARED || batchSize == 1); return (singleBatch ? "" : "synBatchOffset + ") + index; } //---------------------------------------------------------------------------- @@ -1927,14 +1927,14 @@ boost::uuids::detail::sha1::digest_type CustomUpdateGroupMerged::getHashDigest() std::string CustomUpdateGroupMerged::getVarIndex(unsigned int batchSize, VarAccessDuplication varDuplication, const std::string &index) const { // If variable is shared, the batch size is one or this custom update isn't batched, batch offset isn't required - return ((varDuplication & VarAccessDuplication::SHARED || batchSize == 1 || !getArchetype().isBatched()) ? "" : "batchOffset + ") + index; + return ((varDuplication == VarAccessDuplication::SHARED || batchSize == 1 || !getArchetype().isBatched()) ? "" : "batchOffset + ") + index; } //---------------------------------------------------------------------------- std::string CustomUpdateGroupMerged::getVarRefIndex(bool delay, unsigned int batchSize, VarAccessDuplication varDuplication, const std::string &index) const { // If delayed, variable is shared, the batch size is one or this custom update isn't batched, batch delay offset isn't required if(delay) { - return ((varDuplication & VarAccessDuplication::SHARED || batchSize == 1 || !getArchetype().isBatched()) ? "delayOffset + " : "batchDelayOffset + ") + index; + return ((varDuplication == VarAccessDuplication::SHARED || batchSize == 1 || !getArchetype().isBatched()) ? "delayOffset + " : "batchDelayOffset + ") + index; } else { return getVarIndex(batchSize, varDuplication, index); @@ -1983,13 +1983,13 @@ boost::uuids::detail::sha1::digest_type CustomUpdateWUGroupMergedBase::getHashDi std::string CustomUpdateWUGroupMergedBase::getVarIndex(unsigned int batchSize, VarAccessDuplication varDuplication, const std::string &index) { // **YUCK** there's a lot of duplication in these methods - do they belong elsewhere? - return ((varDuplication & VarAccessDuplication::SHARED || batchSize == 1) ? "" : "batchOffset + ") + index; + return ((varDuplication == VarAccessDuplication::SHARED || batchSize == 1) ? "" : "batchOffset + ") + index; } //---------------------------------------------------------------------------- std::string CustomUpdateWUGroupMergedBase::getVarRefIndex(unsigned int batchSize, VarAccessDuplication varDuplication, const std::string &index) { // **YUCK** there's a lot of duplication in these methods - do they belong elsewhere? - return ((varDuplication & VarAccessDuplication::SHARED || batchSize == 1) ? "" : "batchOffset + ") + index; + return ((varDuplication == VarAccessDuplication::SHARED || batchSize == 1) ? "" : "batchOffset + ") + index; } //---------------------------------------------------------------------------- CustomUpdateWUGroupMergedBase::CustomUpdateWUGroupMergedBase(size_t index, const std::string &precision, const std::string &, const BackendBase &backend, From b36c24909036ae94669f4ddfe3bcc21ec50a10c9 Mon Sep 17 00:00:00 2001 From: neworderofjamie Date: Tue, 20 Jul 2021 16:24:27 +0100 Subject: [PATCH 03/26] finally tidied up makefile for unit tests --- tests/unit/Makefile | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/tests/unit/Makefile b/tests/unit/Makefile index 5fd4885545..8376deadf0 100644 --- a/tests/unit/Makefile +++ b/tests/unit/Makefile @@ -3,27 +3,39 @@ include ../../src/genn/MakefileCommon # Get simulate SpineML path i.e. directory of this Makefile UNIT_TEST_PATH :=$(GENN_DIR)/tests/unit +OBJECT_DIRECTORY :=$(OBJECT_DIRECTORY)/tests/unit -TEST_SOURCES := $(GTEST_DIR)/src/gtest-all.cc $(GTEST_DIR)/src/gtest_main.cc $(UNIT_TEST_PATH)/*.cc +SOURCES :=$(GTEST_DIR)/src/gtest-all.cc $(GTEST_DIR)/src/gtest_main.cc $(wildcard *.cc) +OBJECTS :=$(SOURCES:%.cc=$(OBJECT_DIRECTORY)/%.o) +DEPS :=$(OBJECTS:.o=.d) # Add compiler and linker flags to link libGeNN and pthreads -LDFLAGS += -L$(GENN_DIR)/lib -lgenn_single_threaded_cpu_backend$(GENN_PREFIX) -lgenn$(GENN_PREFIX) -lpthread +LDFLAGS += -L$(LIBRARY_DIRECTORY) -lgenn_single_threaded_cpu_backend$(GENN_PREFIX) -lgenn$(GENN_PREFIX) -lpthread CXXFLAGS += -I$(GENN_DIR)/include/genn/backends/single_threaded_cpu -I "$(GTEST_DIR)" -isystem "$(GTEST_DIR)/include" # Determine full path to generator and backend TEST :=$(UNIT_TEST_PATH)/test$(GENN_PREFIX) -.PHONY: all clean libgenn +.PHONY: all clean libgenn backend all: $(TEST) -$(TEST): $(TEST_SOURCES) libgenn - $(CXX) -std=c++11 $(CXXFLAGS) $(TEST_SOURCES) -o $@ $(LDFLAGS) +$(TEST): $(OBJECTS) libgenn backend + $(CXX) $(CXXFLAGS) $(OBJECTS) -o $@ $(LDFLAGS) -generator.d: ; +-include $(DEPS) + +$(OBJECT_DIRECTORY)/%.o: %.cc $(OBJECT_DIRECTORY)/%.d + mkdir -p $(@D) + $(CXX) -std=c++11 $(CXXFLAGS) -c -o $@ $< + +%.d: ; libgenn: if [ -w $(GENN_DIR)/lib ]; then $(MAKE) -C $(GENN_DIR)/src/genn/genn; fi; +backend: + if [ -w $(GENN_DIR)/lib ]; then $(MAKE) -C $(GENN_DIR)/src/genn/backends/single_threaded_cpu; fi; + clean: - rm -f $(TEST) *.d *.gcno + rm -f $(OBJECT_DIRECTORY)/*.o $(OBJECT_DIRECTORY)/*.d $(TEST) From 590bd79c54c1401996d8cebeb463fc2435dd09f8 Mon Sep 17 00:00:00 2001 From: neworderofjamie Date: Tue, 20 Jul 2021 16:34:13 +0100 Subject: [PATCH 04/26] fixed bug in var access modes --- include/genn/genn/varAccess.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/genn/genn/varAccess.h b/include/genn/genn/varAccess.h index b0b37efd40..035dd12fbc 100644 --- a/include/genn/genn/varAccess.h +++ b/include/genn/genn/varAccess.h @@ -26,8 +26,8 @@ enum class VarAccessMode : unsigned int //! Flags defining how variables should be duplicated across multiple batches enum class VarAccessDuplication : unsigned int { - DUPLICATE = (1 << 4), //! This variable should be duplicated in each batch - SHARED = (1 << 5), //! This variable should be shared between batches + DUPLICATE = (1 << 5), //! This variable should be duplicated in each batch + SHARED = (1 << 6), //! This variable should be shared between batches }; //! Supported combinations of VarAccessMode and VarAccessDuplication From ddf88a70f07fc6b769ed50c97c53a4c91d438d68 Mon Sep 17 00:00:00 2001 From: neworderofjamie Date: Tue, 20 Jul 2021 16:34:37 +0100 Subject: [PATCH 05/26] checks that reduction variables aren't added to models other than custom updates --- include/genn/genn/currentSource.h | 9 ++------- include/genn/genn/neuronGroup.h | 9 +-------- src/genn/genn/currentSource.cc | 19 ++++++++++++++++++- src/genn/genn/neuronGroup.cc | 18 ++++++++++++++++++ src/genn/genn/synapseGroup.cc | 23 ++++++++++++++++++++++- 5 files changed, 61 insertions(+), 17 deletions(-) diff --git a/include/genn/genn/currentSource.h b/include/genn/genn/currentSource.h index 43b6e277a7..a750669bfe 100644 --- a/include/genn/genn/currentSource.h +++ b/include/genn/genn/currentSource.h @@ -62,13 +62,8 @@ class GENN_EXPORT CurrentSource protected: CurrentSource(const std::string &name, const CurrentSourceModels::Base *currentSourceModel, const std::vector ¶ms, const std::vector &varInitialisers, - const NeuronGroupInternal *trgNeuronGroup, VarLocation defaultVarLocation, - VarLocation defaultExtraGlobalParamLocation) - : m_Name(name), m_CurrentSourceModel(currentSourceModel), m_Params(params), m_VarInitialisers(varInitialisers), - m_TrgNeuronGroup(trgNeuronGroup), m_VarLocation(varInitialisers.size(), defaultVarLocation), - m_ExtraGlobalParamLocation(currentSourceModel->getExtraGlobalParams().size(), defaultExtraGlobalParamLocation) - { - } + const NeuronGroupInternal *trgNeuronGroup, VarLocation defaultVarLocation, + VarLocation defaultExtraGlobalParamLocation); //------------------------------------------------------------------------ // Protected methods diff --git a/include/genn/genn/neuronGroup.h b/include/genn/genn/neuronGroup.h index f88380eecb..49801b3e62 100644 --- a/include/genn/genn/neuronGroup.h +++ b/include/genn/genn/neuronGroup.h @@ -198,14 +198,7 @@ class GENN_EXPORT NeuronGroup protected: NeuronGroup(const std::string &name, int numNeurons, const NeuronModels::Base *neuronModel, const std::vector ¶ms, const std::vector &varInitialisers, - VarLocation defaultVarLocation, VarLocation defaultExtraGlobalParamLocation) : - m_Name(name), m_NumNeurons(numNeurons), m_NeuronModel(neuronModel), m_Params(params), m_VarInitialisers(varInitialisers), - m_NumDelaySlots(1), m_VarQueueRequired(varInitialisers.size(), false), m_SpikeLocation(defaultVarLocation), m_SpikeEventLocation(defaultVarLocation), - m_SpikeTimeLocation(defaultVarLocation), m_PrevSpikeTimeLocation(defaultVarLocation), m_SpikeEventTimeLocation(defaultVarLocation), m_PrevSpikeEventTimeLocation(defaultVarLocation), - m_VarLocation(varInitialisers.size(), defaultVarLocation), m_ExtraGlobalParamLocation(neuronModel->getExtraGlobalParams().size(), defaultExtraGlobalParamLocation), - m_SpikeRecordingEnabled(false), m_SpikeEventRecordingEnabled(false) - { - } + VarLocation defaultVarLocation, VarLocation defaultExtraGlobalParamLocation); //------------------------------------------------------------------------ // Protected methods diff --git a/src/genn/genn/currentSource.cc b/src/genn/genn/currentSource.cc index fdd8659a74..8c04c53655 100644 --- a/src/genn/genn/currentSource.cc +++ b/src/genn/genn/currentSource.cc @@ -34,6 +34,23 @@ VarLocation CurrentSource::getExtraGlobalParamLocation(const std::string &varNam return m_ExtraGlobalParamLocation[getCurrentSourceModel()->getExtraGlobalParamIndex(varName)]; } //---------------------------------------------------------------------------- +CurrentSource::CurrentSource(const std::string &name, const CurrentSourceModels::Base *currentSourceModel, + const std::vector ¶ms, const std::vector &varInitialisers, + const NeuronGroupInternal *trgNeuronGroup, VarLocation defaultVarLocation, + VarLocation defaultExtraGlobalParamLocation) +: m_Name(name), m_CurrentSourceModel(currentSourceModel), m_Params(params), m_VarInitialisers(varInitialisers), + m_TrgNeuronGroup(trgNeuronGroup), m_VarLocation(varInitialisers.size(), defaultVarLocation), + m_ExtraGlobalParamLocation(currentSourceModel->getExtraGlobalParams().size(), defaultExtraGlobalParamLocation) +{ + // If any variables have a reduction access mode, give an error + const auto vars = getCurrentSourceModel()->getVars(); + if(std::any_of(vars.cbegin(), vars.cend(), + [](const Models::Base::Var &v){ return (v.access & VarAccessModeAttribute::REDUCE); })) + { + throw std::runtime_error("Current source models cannot include variables with REDUCE access modes - they are only supported by custom update models"); + } +} +//---------------------------------------------------------------------------- void CurrentSource::initDerivedParams(double dt) { auto derivedParams = getCurrentSourceModel()->getDerivedParams(); @@ -102,4 +119,4 @@ boost::uuids::detail::sha1::digest_type CurrentSource::getVarLocationHashDigest( Utils::updateHash(m_VarLocation, hash); Utils::updateHash(m_ExtraGlobalParamLocation, hash); return hash.get_digest(); -} \ No newline at end of file +} diff --git a/src/genn/genn/neuronGroup.cc b/src/genn/genn/neuronGroup.cc index cb4984cd69..6d225d95db 100644 --- a/src/genn/genn/neuronGroup.cc +++ b/src/genn/genn/neuronGroup.cc @@ -268,6 +268,24 @@ void NeuronGroup::injectCurrent(CurrentSourceInternal *src) m_CurrentSources.push_back(src); } //---------------------------------------------------------------------------- +NeuronGroup::NeuronGroup(const std::string &name, int numNeurons, const NeuronModels::Base *neuronModel, + const std::vector ¶ms, const std::vector &varInitialisers, + VarLocation defaultVarLocation, VarLocation defaultExtraGlobalParamLocation) +: m_Name(name), m_NumNeurons(numNeurons), m_NeuronModel(neuronModel), m_Params(params), m_VarInitialisers(varInitialisers), + m_NumDelaySlots(1), m_VarQueueRequired(varInitialisers.size(), false), m_SpikeLocation(defaultVarLocation), m_SpikeEventLocation(defaultVarLocation), + m_SpikeTimeLocation(defaultVarLocation), m_PrevSpikeTimeLocation(defaultVarLocation), m_SpikeEventTimeLocation(defaultVarLocation), m_PrevSpikeEventTimeLocation(defaultVarLocation), + m_VarLocation(varInitialisers.size(), defaultVarLocation), m_ExtraGlobalParamLocation(neuronModel->getExtraGlobalParams().size(), defaultExtraGlobalParamLocation), + m_SpikeRecordingEnabled(false), m_SpikeEventRecordingEnabled(false) +{ + // If any variables have a reduction access mode, give an error + const auto vars = getNeuronModel()->getVars(); + if(std::any_of(vars.cbegin(), vars.cend(), + [](const Models::Base::Var &v){ return (v.access & VarAccessModeAttribute::REDUCE); })) + { + throw std::runtime_error("Neuron models cannot include variables with REDUCE access modes - they are only supported by custom update models"); + } +} +//---------------------------------------------------------------------------- void NeuronGroup::checkNumDelaySlots(unsigned int requiredDelay) { if (requiredDelay >= getNumDelaySlots()) diff --git a/src/genn/genn/synapseGroup.cc b/src/genn/genn/synapseGroup.cc index 5f602c9aeb..548911cb31 100644 --- a/src/genn/genn/synapseGroup.cc +++ b/src/genn/genn/synapseGroup.cc @@ -396,7 +396,7 @@ bool SynapseGroup::isHostInitRNGRequired() const //---------------------------------------------------------------------------- bool SynapseGroup::isWUVarInitRequired() const { - // If this synapse group has per-synapse state variables and isn't a + // If this synapse group has per-synapse state variables and isn't a // weight sharing slave, return true if any of them have initialisation code which doesn't require a kernel if (!isWeightSharingSlave() && (getMatrixType() & SynapseMatrixWeight::INDIVIDUAL)) { return std::any_of(m_WUVarInitialisers.cbegin(), m_WUVarInitialisers.cend(), @@ -439,6 +439,27 @@ SynapseGroup::SynapseGroup(const std::string &name, SynapseMatrixType matrixType m_ConnectivityInitialiser(connectivityInitialiser), m_SparseConnectivityLocation(defaultSparseConnectivityLocation), m_ConnectivityExtraGlobalParamLocation(connectivityInitialiser.getSnippet()->getExtraGlobalParams().size(), defaultExtraGlobalParamLocation), m_PSModelTargetName(name) { + // If any variables have a reduction access mode, give an error + const auto wuVars = getWUModel()->getVars(); + const auto wuPreVars = getWUModel()->getPreVars(); + const auto wuPostVars = getWUModel()->getPostVars(); + if(std::any_of(wuVars.cbegin(), wuVars.cend(), + [](const Models::Base::Var &v){ return (v.access & VarAccessModeAttribute::REDUCE); }) + || std::any_of(wuPreVars.cbegin(), wuPreVars.cend(), + [](const Models::Base::Var &v){ return (v.access & VarAccessModeAttribute::REDUCE); }) + || std::any_of(wuPostVars.cbegin(), wuPostVars.cend(), + [](const Models::Base::Var &v){ return (v.access & VarAccessModeAttribute::REDUCE); })) + { + throw std::runtime_error("Weight update models cannot include variables with REDUCE access modes - they are only supported by custom update models"); + } + + const auto psmVars = getPSModel()->getVars(); + if(std::any_of(psmVars.cbegin(), psmVars.cend(), + [](const Models::Base::Var &v){ return (v.access & VarAccessModeAttribute::REDUCE); })) + { + throw std::runtime_error("Postsynaptic models cannot include variables with REDUCE access modes - they are only supported by custom update models"); + } + // If connectivity is procedural if(m_MatrixType & SynapseMatrixConnectivity::PROCEDURAL) { // If there's no row build code, give an error From 9f2301de496abe475e82640120af9be21a491ca3 Mon Sep 17 00:00:00 2001 From: neworderofjamie Date: Tue, 20 Jul 2021 16:48:41 +0100 Subject: [PATCH 06/26] incorporate reduction variable checks into validation framework --- include/genn/genn/currentSourceModels.h | 2 +- include/genn/genn/postsynapticModels.h | 2 +- src/genn/genn/currentSource.cc | 8 -------- src/genn/genn/currentSourceModels.cc | 14 ++++++++++++++ src/genn/genn/neuronGroup.cc | 8 -------- src/genn/genn/neuronModels.cc | 8 ++++++++ src/genn/genn/postsynapticModels.cc | 13 +++++++++++++ src/genn/genn/synapseGroup.cc | 22 ---------------------- src/genn/genn/weightUpdateModels.cc | 14 ++++++++++++++ 9 files changed, 51 insertions(+), 40 deletions(-) diff --git a/include/genn/genn/currentSourceModels.h b/include/genn/genn/currentSourceModels.h index 9a9c54b782..b9b87325bd 100644 --- a/include/genn/genn/currentSourceModels.h +++ b/include/genn/genn/currentSourceModels.h @@ -39,7 +39,7 @@ class GENN_EXPORT Base : public Models::Base boost::uuids::detail::sha1::digest_type getHashDigest() const; //! Validate names of parameters etc - using Models::Base::validate; + void validate() const; }; //---------------------------------------------------------------------------- diff --git a/include/genn/genn/postsynapticModels.h b/include/genn/genn/postsynapticModels.h index 3698da546f..e60453e37e 100644 --- a/include/genn/genn/postsynapticModels.h +++ b/include/genn/genn/postsynapticModels.h @@ -37,7 +37,7 @@ class GENN_EXPORT Base : public Models::Base boost::uuids::detail::sha1::digest_type getHashDigest() const; //! Validate names of parameters etc - using Models::Base::validate; + void validate() const; }; //---------------------------------------------------------------------------- diff --git a/src/genn/genn/currentSource.cc b/src/genn/genn/currentSource.cc index da6ef211c1..f31c3c3dc0 100644 --- a/src/genn/genn/currentSource.cc +++ b/src/genn/genn/currentSource.cc @@ -45,14 +45,6 @@ CurrentSource::CurrentSource(const std::string &name, const CurrentSourceModels: // Validate names Utils::validateVarPopName(name, "Current source"); getCurrentSourceModel()->validate(); - - // If any variables have a reduction access mode, give an error - const auto vars = getCurrentSourceModel()->getVars(); - if(std::any_of(vars.cbegin(), vars.cend(), - [](const Models::Base::Var &v){ return (v.access & VarAccessModeAttribute::REDUCE); })) - { - throw std::runtime_error("Current source models cannot include variables with REDUCE access modes - they are only supported by custom update models"); - } } //---------------------------------------------------------------------------- void CurrentSource::initDerivedParams(double dt) diff --git a/src/genn/genn/currentSourceModels.cc b/src/genn/genn/currentSourceModels.cc index 3ac2cd3ea9..a655dffb39 100644 --- a/src/genn/genn/currentSourceModels.cc +++ b/src/genn/genn/currentSourceModels.cc @@ -17,3 +17,17 @@ boost::uuids::detail::sha1::digest_type CurrentSourceModels::Base::getHashDigest Utils::updateHash(getInjectionCode(), hash); return hash.get_digest(); } +//---------------------------------------------------------------------------- +void CurrentSourceModels::Base::validate() const +{ + // Superclass + Models::Base::validate(); + + // If any variables have a reduction access mode, give an error + const auto vars = getVars(); + if(std::any_of(vars.cbegin(), vars.cend(), + [](const Models::Base::Var &v){ return (v.access & VarAccessModeAttribute::REDUCE); })) + { + throw std::runtime_error("Current source models cannot include variables with REDUCE access modes - they are only supported by custom update models"); + } +} diff --git a/src/genn/genn/neuronGroup.cc b/src/genn/genn/neuronGroup.cc index 4013f56c82..f2fba317e6 100644 --- a/src/genn/genn/neuronGroup.cc +++ b/src/genn/genn/neuronGroup.cc @@ -280,14 +280,6 @@ NeuronGroup::NeuronGroup(const std::string &name, int numNeurons, const NeuronMo // Validate names Utils::validateVarPopName(name, "Neuron group"); getNeuronModel()->validate(); - - // If any variables have a reduction access mode, give an error - const auto vars = getNeuronModel()->getVars(); - if(std::any_of(vars.cbegin(), vars.cend(), - [](const Models::Base::Var &v){ return (v.access & VarAccessModeAttribute::REDUCE); })) - { - throw std::runtime_error("Neuron models cannot include variables with REDUCE access modes - they are only supported by custom update models"); - } } //---------------------------------------------------------------------------- void NeuronGroup::checkNumDelaySlots(unsigned int requiredDelay) diff --git a/src/genn/genn/neuronModels.cc b/src/genn/genn/neuronModels.cc index 2ac61b923d..f8747737c1 100644 --- a/src/genn/genn/neuronModels.cc +++ b/src/genn/genn/neuronModels.cc @@ -38,4 +38,12 @@ void NeuronModels::Base::validate() const Models::Base::validate(); Utils::validateVecNames(getAdditionalInputVars(), "Additional input variable"); + + // If any variables have a reduction access mode, give an error + const auto vars = getVars(); + if(std::any_of(vars.cbegin(), vars.cend(), + [](const Models::Base::Var &v){ return (v.access & VarAccessModeAttribute::REDUCE); })) + { + throw std::runtime_error("Neuron models cannot include variables with REDUCE access modes - they are only supported by custom update models"); + } } diff --git a/src/genn/genn/postsynapticModels.cc b/src/genn/genn/postsynapticModels.cc index 5587387e43..7271bb673d 100644 --- a/src/genn/genn/postsynapticModels.cc +++ b/src/genn/genn/postsynapticModels.cc @@ -20,3 +20,16 @@ boost::uuids::detail::sha1::digest_type PostsynapticModels::Base::getHashDigest( Utils::updateHash(getSupportCode(), hash); return hash.get_digest(); } +//---------------------------------------------------------------------------- +void PostsynapticModels::Base::validate() const +{ + // Superclass + Models::Base::validate(); + + const auto vars = getVars(); + if(std::any_of(vars.cbegin(), vars.cend(), + [](const Models::Base::Var &v){ return (v.access & VarAccessModeAttribute::REDUCE); })) + { + throw std::runtime_error("Postsynaptic models cannot include variables with REDUCE access modes - they are only supported by custom update models"); + } +} diff --git a/src/genn/genn/synapseGroup.cc b/src/genn/genn/synapseGroup.cc index 713f70c2c7..a687c4ca64 100644 --- a/src/genn/genn/synapseGroup.cc +++ b/src/genn/genn/synapseGroup.cc @@ -439,28 +439,6 @@ SynapseGroup::SynapseGroup(const std::string &name, SynapseMatrixType matrixType m_ConnectivityInitialiser(connectivityInitialiser), m_SparseConnectivityLocation(defaultSparseConnectivityLocation), m_ConnectivityExtraGlobalParamLocation(connectivityInitialiser.getSnippet()->getExtraGlobalParams().size(), defaultExtraGlobalParamLocation), m_PSModelTargetName(name) { - - // If any variables have a reduction access mode, give an error - const auto wuVars = getWUModel()->getVars(); - const auto wuPreVars = getWUModel()->getPreVars(); - const auto wuPostVars = getWUModel()->getPostVars(); - if(std::any_of(wuVars.cbegin(), wuVars.cend(), - [](const Models::Base::Var &v){ return (v.access & VarAccessModeAttribute::REDUCE); }) - || std::any_of(wuPreVars.cbegin(), wuPreVars.cend(), - [](const Models::Base::Var &v){ return (v.access & VarAccessModeAttribute::REDUCE); }) - || std::any_of(wuPostVars.cbegin(), wuPostVars.cend(), - [](const Models::Base::Var &v){ return (v.access & VarAccessModeAttribute::REDUCE); })) - { - throw std::runtime_error("Weight update models cannot include variables with REDUCE access modes - they are only supported by custom update models"); - } - - const auto psmVars = getPSModel()->getVars(); - if(std::any_of(psmVars.cbegin(), psmVars.cend(), - [](const Models::Base::Var &v){ return (v.access & VarAccessModeAttribute::REDUCE); })) - { - throw std::runtime_error("Postsynaptic models cannot include variables with REDUCE access modes - they are only supported by custom update models"); - } - // Validate names Utils::validateVarPopName(name, "Synapse group"); getWUModel()->validate(); diff --git a/src/genn/genn/weightUpdateModels.cc b/src/genn/genn/weightUpdateModels.cc index 7738805f6c..8e2a96fdd0 100644 --- a/src/genn/genn/weightUpdateModels.cc +++ b/src/genn/genn/weightUpdateModels.cc @@ -44,4 +44,18 @@ void WeightUpdateModels::Base::validate() const Utils::validateVecNames(getPreVars(), "Presynaptic variable"); Utils::validateVecNames(getPostVars(), "Presynaptic variable"); + + // If any variables have a reduction access mode, give an error + const auto vars = getVars(); + const auto preVars = getPreVars(); + const auto postVars = getPostVars(); + if(std::any_of(vars.cbegin(), vars.cend(), + [](const Models::Base::Var &v){ return (v.access & VarAccessModeAttribute::REDUCE); }) + || std::any_of(preVars.cbegin(), preVars.cend(), + [](const Models::Base::Var &v){ return (v.access & VarAccessModeAttribute::REDUCE); }) + || std::any_of(postVars.cbegin(), postVars.cend(), + [](const Models::Base::Var &v){ return (v.access & VarAccessModeAttribute::REDUCE); })) + { + throw std::runtime_error("Weight update models cannot include variables with REDUCE access modes - they are only supported by custom update models"); + } } From 4f6242dc0ffe56b639881beec4107b7e6027e8c3 Mon Sep 17 00:00:00 2001 From: neworderofjamie Date: Wed, 21 Jul 2021 18:44:38 +0100 Subject: [PATCH 07/26] initial implementation of reduction operations --- .../genn/genn/code_generator/backendBase.h | 13 ++- .../genn/genn/code_generator/backendSIMT.h | 2 +- .../genn/genn/code_generator/codeGenUtils.h | 9 ++ include/genn/genn/customUpdate.h | 27 ++++- include/genn/genn/customUpdateInternal.h | 2 + include/genn/genn/gennUtils.h | 5 + src/genn/backends/cuda/optimiser.cc | 2 +- src/genn/genn/code_generator/backendBase.cc | 46 ++++++-- src/genn/genn/code_generator/backendSIMT.cc | 110 +++++++++++++----- src/genn/genn/code_generator/codeGenUtils.cc | 36 ++++++ .../code_generator/generateCustomUpdate.cc | 34 ++++-- src/genn/genn/customUpdate.cc | 5 +- src/genn/genn/gennUtils.cc | 6 + 13 files changed, 233 insertions(+), 64 deletions(-) diff --git a/include/genn/genn/code_generator/backendBase.h b/include/genn/genn/code_generator/backendBase.h index c26bde95b0..4a44cdb64c 100644 --- a/include/genn/genn/code_generator/backendBase.h +++ b/include/genn/genn/code_generator/backendBase.h @@ -483,6 +483,9 @@ class GENN_EXPORT BackendBase //! Get the size of the type size_t getSize(const std::string &type) const; + //! Get the lowest value of a type + const std::string &getLowestValue(const std::string &type) const; + //! Get the prefix for accessing the address of 'scalar' variables std::string getScalarAddressPrefix() const { @@ -500,9 +503,10 @@ class GENN_EXPORT BackendBase //-------------------------------------------------------------------------- // Protected API //-------------------------------------------------------------------------- - void addType(const std::string &type, size_t size) + void addType(const std::string &type, size_t size, const std::string &lowestValue = "") { - m_TypeBytes.emplace(type, size); + m_Types.emplace(std::piecewise_construct, std::forward_as_tuple(type), + std::forward_as_tuple(size, lowestValue)); } void setPointerBytes(size_t pointerBytes) @@ -523,8 +527,9 @@ class GENN_EXPORT BackendBase //! How large is a device pointer? E.g. on some AMD devices this != sizeof(char*) size_t m_PointerBytes; - //! Size of supported types in bytes - used for estimating memory usage - std::unordered_map m_TypeBytes; + //! Size of supported types in bytes and string containing their lowest value + //! used for estimating memory usage and for reduction operations + std::unordered_map> m_Types; //! Preferences const PreferencesBase &m_Preferences; diff --git a/include/genn/genn/code_generator/backendSIMT.h b/include/genn/genn/code_generator/backendSIMT.h index 44b7a268a1..575a770ace 100644 --- a/include/genn/genn/code_generator/backendSIMT.h +++ b/include/genn/genn/code_generator/backendSIMT.h @@ -212,7 +212,7 @@ class GENN_EXPORT BackendSIMT : public BackendBase size_t numInitializeThreads, size_t &idStart) const; //! Adds a type - both to backend base's list of sized types but also to device types set - void addDeviceType(const std::string &type, size_t size); + void addDeviceType(const std::string &type, size_t size, const std::string &maxValue = ""); //! Is type a a device only type? bool isDeviceType(const std::string &type) const; diff --git a/include/genn/genn/code_generator/codeGenUtils.h b/include/genn/genn/code_generator/codeGenUtils.h index 6ab622b651..16d426181f 100644 --- a/include/genn/genn/code_generator/codeGenUtils.h +++ b/include/genn/genn/code_generator/codeGenUtils.h @@ -74,6 +74,15 @@ GENN_EXPORT void genTypeRange(CodeStream &os, const std::string &precision, cons //-------------------------------------------------------------------------- GENN_EXPORT std::string ensureFtype(const std::string &oldcode, const std::string &type); +//-------------------------------------------------------------------------- +//! \brief Get the initial value to start reduction operations from +//-------------------------------------------------------------------------- +GENN_EXPORT std::string getReductionInitialValue(const BackendBase &backend, VarAccessMode access, const std::string &type); + +//-------------------------------------------------------------------------- +//! \brief Generate a reduction operation to reduce value into reduction +//-------------------------------------------------------------------------- +GENN_EXPORT std::string getReductionOperation(const std::string &reduction, const std::string &value, VarAccessMode access, const std::string &type); //-------------------------------------------------------------------------- /*! \brief This function checks for unknown variable definitions and returns a gennError if any are found diff --git a/include/genn/genn/customUpdate.h b/include/genn/genn/customUpdate.h index d8bed255c6..6c552332ab 100644 --- a/include/genn/genn/customUpdate.h +++ b/include/genn/genn/customUpdate.h @@ -55,7 +55,7 @@ class GENN_EXPORT CustomUpdateBase : m_Name(name), m_UpdateGroupName(updateGroupName), m_CustomUpdateModel(customUpdateModel), m_Params(params), m_VarInitialisers(varInitialisers), m_VarLocation(varInitialisers.size(), defaultVarLocation), m_ExtraGlobalParamLocation(customUpdateModel->getExtraGlobalParams().size(), defaultExtraGlobalParamLocation), - m_Batched(false) + m_Batched(false), m_Reduction(false) { // Validate names Utils::validateVarPopName(name, "Custom update"); @@ -81,6 +81,9 @@ class GENN_EXPORT CustomUpdateBase //! Is this custom update batched i.e. run in parallel across model batches bool isBatched() const { return m_Batched; } + //! Does this custom update perform a reduction i.e. reduce some variables from DUPLICATE to SHARED + bool isReduction() const { return m_Reduction; } + //! Updates hash with custom update /*! NOTE: this can only be called after model is finalized */ void updateHash(boost::uuids::detail::sha1 &hash) const; @@ -91,31 +94,42 @@ class GENN_EXPORT CustomUpdateBase boost::uuids::detail::sha1::digest_type getVarLocationHashDigest() const; - //! Helper function to determine whether a custom update should be batched + //! Helper function to determine whether a custom update should be batched or treated as a reduction template - void finalizeBatched(unsigned int batchSize, const std::vector &varRefs) + void finalize(unsigned int batchSize, const std::vector &varRefs) { // If model has batching at all, custom update should be batched // if targets of any variable references are duplicated + // **THINK** what about variables? if(batchSize > 1) { m_Batched = std::any_of(varRefs.cbegin(), varRefs.cend(), [](const R &v) { return (v.getVar().access & VarAccessDuplication::DUPLICATE); }); // If custom update is batched, check that any variable references to shared variables are read-only + // **THINK** what about variables? if(m_Batched) { const auto modelVarRefs = getCustomUpdateModel()->getVarRefs(); for(size_t i = 0; i < modelVarRefs.size(); i++) { if((varRefs.at(i).getVar().access & VarAccessDuplication::SHARED) && (modelVarRefs.at(i).access != VarAccessMode::READ_ONLY)) { - throw std::runtime_error("Variable references to SHARED variables in batched models must be read-only."); + throw std::runtime_error("Variable references to SHARED variables in batched custom updates must be read-only."); } } + + // The custom update is a reduction if any variables or references have the reduce access mode attribute + const auto vars = getCustomUpdateModel()->getVars(); + const auto varRefs = getCustomUpdateModel()->getVarRefs(); + m_Reduction = (std::any_of(vars.cbegin(), vars.cend(), + [](const Models::Base::Var &v) { return (v.access & VarAccessModeAttribute::REDUCE); }) + || std::any_of(varRefs.cbegin(), varRefs.cend(), + [](const Models::Base::VarRef &v) { return (v.access & VarAccessModeAttribute::REDUCE); })); } } - // Otherwise, update should not be batched + // Otherwise, update should not be batched and reductions are not required else { m_Batched = false; + m_Reduction = false; } } @@ -156,6 +170,9 @@ class GENN_EXPORT CustomUpdateBase //! Is this custom update batched i.e. run in parallel across model batches bool m_Batched; + + //! Does this custom update perform a reduction i.e. reduce some variables from DUPLICATE to SHARED + bool m_Reduction; }; //------------------------------------------------------------------------ diff --git a/include/genn/genn/customUpdateInternal.h b/include/genn/genn/customUpdateInternal.h index f64ecafb87..b6cf707c2b 100644 --- a/include/genn/genn/customUpdateInternal.h +++ b/include/genn/genn/customUpdateInternal.h @@ -23,6 +23,7 @@ class CustomUpdateInternal : public CustomUpdate using CustomUpdateBase::isInitRNGRequired; using CustomUpdateBase::isZeroCopyEnabled; using CustomUpdateBase::isBatched; + using CustomUpdateBase::isReduction; using CustomUpdateBase::getVarLocationHashDigest; using CustomUpdate::finalize; @@ -51,6 +52,7 @@ class CustomUpdateWUInternal : public CustomUpdateWU using CustomUpdateBase::isInitRNGRequired; using CustomUpdateBase::isZeroCopyEnabled; using CustomUpdateBase::isBatched; + using CustomUpdateBase::isReduction; using CustomUpdateBase::getVarLocationHashDigest; using CustomUpdateWU::finalize; diff --git a/include/genn/genn/gennUtils.h b/include/genn/genn/gennUtils.h index 290e69a9ab..04807ae3c4 100644 --- a/include/genn/genn/gennUtils.h +++ b/include/genn/genn/gennUtils.h @@ -49,6 +49,11 @@ GENN_EXPORT bool isTypePointer(const std::string &type); //-------------------------------------------------------------------------- GENN_EXPORT bool isTypePointerToPointer(const std::string &type); +//-------------------------------------------------------------------------- +//! \brief Function to determine whether a string containing a type is floating point +//-------------------------------------------------------------------------- +GENN_EXPORT bool isTypeFloatingPoint(const std::string &type); + //-------------------------------------------------------------------------- //! \brief Assuming type is a string containing a pointer type, function to return the underlying type //-------------------------------------------------------------------------- diff --git a/src/genn/backends/cuda/optimiser.cc b/src/genn/backends/cuda/optimiser.cc index 52c4d23897..fee76016ad 100644 --- a/src/genn/backends/cuda/optimiser.cc +++ b/src/genn/backends/cuda/optimiser.cc @@ -173,7 +173,7 @@ void calcGroupSizes(const CUDA::Preferences &preferences, const ModelSpecInterna else { customUpdateKernels.insert(c.second.getUpdateGroupName()); - const size_t numCopies = c.second.isBatched() ? model.getBatchSize() : 1; + const size_t numCopies = (c.second.isBatched() && !c.second.isReduction()) ? model.getBatchSize() : 1; if(sgInternal->getMatrixType() & SynapseMatrixConnectivity::SPARSE) { groupSizes[KernelCustomUpdate].push_back(numCopies * sgInternal->getSrcNeuronGroup()->getNumNeurons() * sgInternal->getMaxConnections()); } diff --git a/src/genn/genn/code_generator/backendBase.cc b/src/genn/genn/code_generator/backendBase.cc index bb5620fb74..84f7aec647 100644 --- a/src/genn/genn/code_generator/backendBase.cc +++ b/src/genn/genn/code_generator/backendBase.cc @@ -8,26 +8,32 @@ #include "code_generator/groupMerged.h" // Macro for simplifying defining type sizes -#define TYPE(T) {#T, sizeof(T)} +#define TYPE(T) {#T, {sizeof(T), std::to_string(std::numeric_limits::lowest())}} +#define FLOAT_TYPE(T) {#T, {sizeof(T), Utils::writePreciseString(std::numeric_limits::lowest())}} //-------------------------------------------------------------------------- // CodeGenerator::BackendBase //-------------------------------------------------------------------------- CodeGenerator::BackendBase::BackendBase(const std::string &scalarType, const PreferencesBase &preferences) -: m_PointerBytes(sizeof(char*)), m_TypeBytes{{TYPE(char), TYPE(wchar_t), TYPE(signed char), TYPE(short), +: m_PointerBytes(sizeof(char*)), m_Types{{TYPE(char), TYPE(wchar_t), TYPE(signed char), TYPE(short), TYPE(signed short), TYPE(short int), TYPE(signed short int), TYPE(int), TYPE(signed int), TYPE(long), TYPE(signed long), TYPE(long int), TYPE(signed long int), TYPE(long long), TYPE(signed long long), TYPE(long long int), TYPE(signed long long int), TYPE(unsigned char), TYPE(unsigned short), TYPE(unsigned short int), TYPE(unsigned), TYPE(unsigned int), TYPE(unsigned long), TYPE(unsigned long int), TYPE(unsigned long long), - TYPE(unsigned long long int), TYPE(float), TYPE(double), TYPE(long double), TYPE(bool), TYPE(intmax_t), - TYPE(uintmax_t), TYPE(int8_t), TYPE(uint8_t), TYPE(int16_t), TYPE(uint16_t), TYPE(int32_t), TYPE(uint32_t), - TYPE(int64_t), TYPE(uint64_t), TYPE(int_least8_t), TYPE(uint_least8_t), TYPE(int_least16_t), TYPE(uint_least16_t), - TYPE(int_least32_t), TYPE(uint_least32_t), TYPE(int_least64_t), TYPE(uint_least64_t), TYPE(int_fast8_t), - TYPE(uint_fast8_t), TYPE(int_fast16_t), TYPE(uint_fast16_t), TYPE(int_fast32_t), TYPE(uint_fast32_t), - TYPE(int_fast64_t), TYPE(uint_fast64_t)}}, m_Preferences(preferences) + TYPE(unsigned long long int), TYPE(bool), TYPE(intmax_t), TYPE(uintmax_t), TYPE(int8_t), TYPE(uint8_t), + TYPE(int16_t), TYPE(uint16_t), TYPE(int32_t), TYPE(uint32_t), TYPE(int64_t), TYPE(uint64_t), + TYPE(int_least8_t), TYPE(uint_least8_t), TYPE(int_least16_t), TYPE(uint_least16_t), TYPE(int_least32_t), + TYPE(uint_least32_t), TYPE(int_least64_t), TYPE(uint_least64_t), TYPE(int_fast8_t), TYPE(uint_fast8_t), + TYPE(int_fast16_t), TYPE(uint_fast16_t), TYPE(int_fast32_t), TYPE(uint_fast32_t), TYPE(int_fast64_t), + TYPE(uint_fast64_t), FLOAT_TYPE(float), FLOAT_TYPE(double), FLOAT_TYPE(long double)}}, m_Preferences(preferences) { // Add scalar type - addType("scalar", (scalarType == "float") ? sizeof(float) : sizeof(double)); + if(scalarType == "float") { + addType("scalar", sizeof(float), Utils::writePreciseString(std::numeric_limits::lowest())); + } + else { + addType("scalar", sizeof(double), Utils::writePreciseString(std::numeric_limits::lowest())); + } } //-------------------------------------------------------------------------- size_t CodeGenerator::BackendBase::getSize(const std::string &type) const @@ -39,18 +45,34 @@ size_t CodeGenerator::BackendBase::getSize(const std::string &type) const // Otherwise else { // If type isn't found in dictionary, give a warning and return 0 - const auto typeSize = m_TypeBytes.find(type); - if(typeSize == m_TypeBytes.cend()) { + const auto typeSizeLowest = m_Types.find(type); + if(typeSizeLowest == m_Types.cend()) { LOGW_CODE_GEN << "Unable to estimate size of type '" << type << "'"; return 0; } // Otherwise, return its size else { - return typeSize->second; + return typeSizeLowest->second.first; } } } //-------------------------------------------------------------------------- +const std::string &CodeGenerator::BackendBase::getLowestValue(const std::string &type) const +{ + assert(!Utils::isTypePointer(type)); + + // If type's found in dictionary and it has a lowest value + const auto typeSizeLowest = m_Types.find(type); + if(typeSizeLowest != m_Types.cend() && !typeSizeLowest->second.second.empty()) { + return typeSizeLowest->second.second; + } + // Otherwise, give warning and return empty string + else { + LOGW_CODE_GEN << "Unable to get lowest value for type '" << type << "'"; + return ""; + } +} +//-------------------------------------------------------------------------- bool CodeGenerator::BackendBase::areSixtyFourBitSynapseIndicesRequired(const SynapseGroupMergedBase &sg) const { // Loop through merged groups and calculate maximum number of synapses diff --git a/src/genn/genn/code_generator/backendSIMT.cc b/src/genn/genn/code_generator/backendSIMT.cc index e1e2c0759a..b859ef3e0d 100644 --- a/src/genn/genn/code_generator/backendSIMT.cc +++ b/src/genn/genn/code_generator/backendSIMT.cc @@ -20,13 +20,13 @@ size_t getNumMergedGroupThreads(const std::vector &groups, G getNumThreads) return std::accumulate( groups.cbegin(), groups.cend(), size_t{0}, [getNumThreads](size_t acc, const T &n) + { + return std::accumulate(n.getGroups().cbegin(), n.getGroups().cend(), acc, + [getNumThreads](size_t acc, std::reference_wrapper g) { - return std::accumulate(n.getGroups().cbegin(), n.getGroups().cend(), acc, - [getNumThreads](size_t acc, std::reference_wrapper g) - { - return acc + getNumThreads(g.get()); - }); + return acc + getNumThreads(g.get()); }); + }); } } @@ -167,7 +167,7 @@ size_t BackendSIMT::getNumInitialisationRNGStreams(const ModelSpecMerged &modelM size_t BackendSIMT::getPaddedNumCustomUpdateWUThreads(const CustomUpdateWUInternal &cg, unsigned int batchSize) const { const SynapseGroupInternal *sgInternal = static_cast(cg.getSynapseGroup()); - const size_t numCopies = cg.isBatched() ? batchSize : 1; + const size_t numCopies = (cg.isBatched() && !cg.isReduction()) ? batchSize : 1; if(sgInternal->getMatrixType() & SynapseMatrixConnectivity::SPARSE) { // **THINK** like for synapse dynamics kernels, this isn't really correct but correct value isn't known @@ -817,7 +817,7 @@ void BackendSIMT::genCustomUpdateKernel(CodeStream &os, const Substitutions &ker os, kernelSubs, modelMerged.getMergedCustomUpdateGroups(), idStart, [&modelMerged, this](const CustomUpdateInternal &cu) { - const unsigned int numCopies = cu.isBatched() ? modelMerged.getModel().getBatchSize() : 1; + const unsigned int numCopies = (cu.isBatched() && !cu.isReduction()) ? modelMerged.getModel().getBatchSize() : 1; return numCopies * padKernelSize(cu.getSize(), KernelCustomUpdate); }, [&updateGroup](const CustomUpdateGroupMerged &cg) { return (cg.getArchetype().getUpdateGroupName() == updateGroup); }, @@ -825,32 +825,86 @@ void BackendSIMT::genCustomUpdateKernel(CodeStream &os, const Substitutions &ker { const size_t blockSize = getKernelBlockSize(KernelCustomUpdate); - // If update is batched + // If update is a reduction Substitutions cuSubs(&popSubs); - if(cg.getArchetype().isBatched()) { - // Split ID into intra-batch ID and batch - // **TODO** fast-divide style optimisations here - os << "const unsigned int paddedSize = " << blockSize << " * ((group->size + " << blockSize << " - 1) / " << blockSize << ");" << std::endl; - os << "const unsigned int bid = " << cuSubs["id"] << " % paddedSize;" << std::endl; - os << "const unsigned int batch = " << cuSubs["id"] << " / paddedSize;" << std::endl; + if(cg.getArchetype().isReduction()) { + os << "// only do this for existing neurons" << std::endl; + os << "if(" << cuSubs["id"] << " < group->size)"; + { + CodeStream::Scope b(os); - // Replace id in substitution with intra-batch ID and add batch - cuSubs.addVarSubstitution("id", "bid", true); - cuSubs.addVarSubstitution("batch", "batch"); + // Loop through variables + std::vector> reductionTargets; + const auto *cm = cg.getArchetype().getCustomUpdateModel(); + for(const auto &v : cm->getVars()) { + // If variable is a reduction target, define variable initialised to correct initial value for reduction + if(v.access & VarAccessModeAttribute::REDUCE) { + os << v.type << " lr" << v.name << " = " << getReductionInitialValue(*this, getVarAccessMode(v.access), v.type) << ";" << std::endl; + reductionTargets.emplace_back(v.name, v.type, getVarAccessMode(v.access)); + } + } + + // Loop through variable references + for(const auto &v : cm->getVarRefs()) { + // If variable reference is a reduction target, define variable initialised to correct initial value for reduction + if(v.access & VarAccessModeAttribute::REDUCE) { + os << v.type << " lr" << v.name << " = " << getReductionInitialValue(*this, v.access, v.type) << ";" << std::endl; + reductionTargets.emplace_back(v.name, v.type, v.access); + } + } + + // Loop through batches + // **TODO** this naive approach is good for reduction when there are lots of neurons/synapses but, + // if this isn't the case (TF uses a threshold of 4096), we should do something smarter + os << "for(unsigned int batch = 0; batch < " << modelMerged.getModel().getBatchSize() << "; batch++)"; + { + CodeStream::Scope b(os); + cuSubs.addVarSubstitution("batch", "batch"); + + genCustomUpdateIndexCalculation(os, cg); + customUpdateHandler(os, cg, cuSubs); + + // Loop through reduction targets and generate reduction + for(const auto &r : reductionTargets) { + os << getReductionOperation("lr" + std::get<0>(r), "l" + std::get<0>(r), std::get<2>(r), std::get<1>(r)) << ";" << std::endl; + } + } + + // Loop through reduction targets + for(const auto &r : reductionTargets) { + os << "group->" << std::get<0>(r) << " = lr" << std::get<0>(r) << ";" << std::endl; + } + } } - // Otherwise, just substitute "batch" for 0 + // Otherwise else { - cuSubs.addVarSubstitution("batch", "0"); - } + if(cg.getArchetype().isBatched()) { + // Split ID into intra-batch ID and batch + // **TODO** fast-divide style optimisations here + os << "const unsigned int paddedSize = " << blockSize << " * ((group->size + " << blockSize << " - 1) / " << blockSize << ");" << std::endl; + os << "const unsigned int bid = " << cuSubs["id"] << " % paddedSize;" << std::endl; + os << "const unsigned int batch = " << cuSubs["id"] << " / paddedSize;" << std::endl; + + // Replace id in substitution with intra-batch ID and add batch + cuSubs.addVarSubstitution("id", "bid", true); + cuSubs.addVarSubstitution("batch", "batch"); + } + // Otherwise, just substitute "batch" for 0 + else { + cuSubs.addVarSubstitution("batch", "0"); + } - os << "// only do this for existing neurons" << std::endl; - os << "if(" << cuSubs["id"] << " < group->size)"; - { - CodeStream::Scope b(os); + os << "// only do this for existing neurons" << std::endl; + os << "if(" << cuSubs["id"] << " < group->size)"; + { + CodeStream::Scope b(os); - genCustomUpdateIndexCalculation(os, cg); - customUpdateHandler(os, cg, cuSubs); + genCustomUpdateIndexCalculation(os, cg); + customUpdateHandler(os, cg, cuSubs); + } } + + }); } //-------------------------------------------------------------------------- @@ -1510,9 +1564,9 @@ void BackendSIMT::genInitializeSparseKernel(CodeStream &os, const Substitutions }); } //-------------------------------------------------------------------------- -void BackendSIMT::addDeviceType(const std::string &type, size_t size) +void BackendSIMT::addDeviceType(const std::string &type, size_t size, const std::string &maxValue) { - addType(type, size); + addType(type, size, maxValue); m_DeviceTypes.emplace(type); } //-------------------------------------------------------------------------- diff --git a/src/genn/genn/code_generator/codeGenUtils.cc b/src/genn/genn/code_generator/codeGenUtils.cc index 0bbc545682..fb19c0e791 100644 --- a/src/genn/genn/code_generator/codeGenUtils.cc +++ b/src/genn/genn/code_generator/codeGenUtils.cc @@ -466,6 +466,42 @@ std::string ensureFtype(const std::string &oldcode, const std::string &type) return code; } +std::string getReductionInitialValue(const BackendBase &backend, VarAccessMode access, const std::string &type) +{ + // If reduction is a sum, initialise to zero + if(access & VarAccessModeAttribute::SUM) { + return "0"; + } + // Otherwise, reduction is a maximum operation, return lowest value for type + else if(access & VarAccessModeAttribute::MAX) { + return backend.getLowestValue(type); + } + else { + assert(false); + } +} + +std::string getReductionOperation(const std::string &reduction, const std::string &value, VarAccessMode access, const std::string &type) +{ + // If operation is sum, add output of custom update to sum + if(access & VarAccessModeAttribute::SUM) { + return reduction + " += " + value; + } + // Otherwise, if it's max + else if(access & VarAccessModeAttribute::MAX) { + // If type is floating point, generate fmax call + if(Utils::isTypeFloatingPoint(type)) { + return reduction + " = " + "fmax(" + reduction + ", " + value + ")"; + } + // Otherwise, generate max call + else { + return reduction + " = " + "max(" + reduction + ", " + value + ")"; + } + } + else { + assert(false); + } +} //-------------------------------------------------------------------------- /*! \brief This function checks for unknown variable definitions and returns a gennError if any are found */ diff --git a/src/genn/genn/code_generator/generateCustomUpdate.cc b/src/genn/genn/code_generator/generateCustomUpdate.cc index 477f1df97f..09274b3c35 100644 --- a/src/genn/genn/code_generator/generateCustomUpdate.cc +++ b/src/genn/genn/code_generator/generateCustomUpdate.cc @@ -34,28 +34,40 @@ void genCustomUpdate(CodeStream &os, Substitutions &baseSubs, const C &cg, const CustomUpdateModels::Base *cm = cg.getArchetype().getCustomUpdateModel(); const auto varRefs = cm->getVarRefs(); - // Read variables into registers + // Loop through variables for(const auto &v : cm->getVars()) { if(v.access & VarAccessMode::READ_ONLY) { os << "const "; } - os << v.type << " l" << v.name << " = " << "group->" << v.name << "["; - os << cg.getVarIndex(modelMerged.getModel().getBatchSize(), - getVarAccessDuplication(v.access), - updateSubs[index]); - os << "];" << std::endl; + os << v.type << " l" << v.name; + + // If this isn't a reduction, read value from memory + if(!(v.access & VarAccessModeAttribute::REDUCE)) { + os << " = group->" << v.name << "["; + os << cg.getVarIndex(modelMerged.getModel().getBatchSize(), + getVarAccessDuplication(v.access), + updateSubs[index]); + os << "]"; + } + os << ";" << std::endl; } - // Read variables references into registers + // Loop through variable references for(size_t i = 0; i < varRefs.size(); i++) { if(varRefs[i].access == VarAccessMode::READ_ONLY) { os << "const "; } - os << varRefs[i].type << " l" << varRefs[i].name << " = " << "group->" << varRefs[i].name << "["; - os << getVarRefIndex(cg.getArchetype().getVarReferences().at(i), - updateSubs[index]); - os << "];" << std::endl; + os << varRefs[i].type << " l" << varRefs[i].name; + + // If this isn't a reduction, read value from memory + if(!(varRefs[i].access & VarAccessModeAttribute::REDUCE)) { + os << " = " << "group->" << varRefs[i].name << "["; + os << getVarRefIndex(cg.getArchetype().getVarReferences().at(i), + updateSubs[index]); + os << "]"; + } + os << ";" << std::endl; } updateSubs.addVarNameSubstitution(cm->getVars(), "", "l"); diff --git a/src/genn/genn/customUpdate.cc b/src/genn/genn/customUpdate.cc index d0be7aa4a9..6ab1e7f1d9 100644 --- a/src/genn/genn/customUpdate.cc +++ b/src/genn/genn/customUpdate.cc @@ -69,6 +69,7 @@ void CustomUpdateBase::updateHash(boost::uuids::detail::sha1 &hash) const Utils::updateHash(getCustomUpdateModel()->getHashDigest(), hash); Utils::updateHash(getUpdateGroupName(), hash); Utils::updateHash(isBatched(), hash); + Utils::updateHash(isReduction(), hash); } //---------------------------------------------------------------------------- void CustomUpdateBase::updateInitHash(boost::uuids::detail::sha1 &hash) const @@ -137,7 +138,7 @@ void CustomUpdate::finalize(unsigned int batchSize) } // Determine whether custom update is batched - finalizeBatched(batchSize, m_VarReferences); + CustomUpdateBase::finalize(batchSize, m_VarReferences); } //---------------------------------------------------------------------------- boost::uuids::detail::sha1::digest_type CustomUpdate::getHashDigest() const @@ -223,7 +224,7 @@ CustomUpdateWU::CustomUpdateWU(const std::string &name, const std::string &updat //---------------------------------------------------------------------------- void CustomUpdateWU::finalize(unsigned int batchSize) { - finalizeBatched(batchSize, m_VarReferences); + CustomUpdateBase::finalize(batchSize, m_VarReferences); } //---------------------------------------------------------------------------- bool CustomUpdateWU::isTransposeOperation() const diff --git a/src/genn/genn/gennUtils.cc b/src/genn/genn/gennUtils.cc index 04e9c70ec6..532ce8288a 100644 --- a/src/genn/genn/gennUtils.cc +++ b/src/genn/genn/gennUtils.cc @@ -79,6 +79,12 @@ bool isTypePointerToPointer(const std::string &type) return (type[len - 1] == '*' && type[len - 2] == '*'); } //-------------------------------------------------------------------------- +bool isTypeFloatingPoint(const std::string &type) +{ + assert(!isTypePointer(type)); + return ((type == "float") || (type == "double") || (type == "half") || (type == "scalar")); +} +//-------------------------------------------------------------------------- std::string getUnderlyingType(const std::string &type) { // Check that type is a pointer type From 215928c8c7b41d58b6b3de15887284a300d933a9 Mon Sep 17 00:00:00 2001 From: neworderofjamie Date: Thu, 22 Jul 2021 13:21:29 +0100 Subject: [PATCH 08/26] fixed compiler warnings --- include/genn/genn/code_generator/backendBase.h | 2 +- src/genn/genn/code_generator/backendBase.cc | 2 +- src/genn/genn/code_generator/codeGenUtils.cc | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/include/genn/genn/code_generator/backendBase.h b/include/genn/genn/code_generator/backendBase.h index 4a44cdb64c..d29a04ed9f 100644 --- a/include/genn/genn/code_generator/backendBase.h +++ b/include/genn/genn/code_generator/backendBase.h @@ -484,7 +484,7 @@ class GENN_EXPORT BackendBase size_t getSize(const std::string &type) const; //! Get the lowest value of a type - const std::string &getLowestValue(const std::string &type) const; + std::string getLowestValue(const std::string &type) const; //! Get the prefix for accessing the address of 'scalar' variables std::string getScalarAddressPrefix() const diff --git a/src/genn/genn/code_generator/backendBase.cc b/src/genn/genn/code_generator/backendBase.cc index 84f7aec647..7d5202e943 100644 --- a/src/genn/genn/code_generator/backendBase.cc +++ b/src/genn/genn/code_generator/backendBase.cc @@ -57,7 +57,7 @@ size_t CodeGenerator::BackendBase::getSize(const std::string &type) const } } //-------------------------------------------------------------------------- -const std::string &CodeGenerator::BackendBase::getLowestValue(const std::string &type) const +std::string CodeGenerator::BackendBase::getLowestValue(const std::string &type) const { assert(!Utils::isTypePointer(type)); diff --git a/src/genn/genn/code_generator/codeGenUtils.cc b/src/genn/genn/code_generator/codeGenUtils.cc index fb19c0e791..eac36729ec 100644 --- a/src/genn/genn/code_generator/codeGenUtils.cc +++ b/src/genn/genn/code_generator/codeGenUtils.cc @@ -500,6 +500,7 @@ std::string getReductionOperation(const std::string &reduction, const std::strin } else { assert(false); + return ""; } } //-------------------------------------------------------------------------- From 8467025ff2cd3375136b974e85363f59e705ae97 Mon Sep 17 00:00:00 2001 From: neworderofjamie Date: Thu, 22 Jul 2021 13:21:58 +0100 Subject: [PATCH 09/26] initial implemention of weight reduction operations --- src/genn/genn/code_generator/backendSIMT.cc | 117 +++++++++++++------- 1 file changed, 79 insertions(+), 38 deletions(-) diff --git a/src/genn/genn/code_generator/backendSIMT.cc b/src/genn/genn/code_generator/backendSIMT.cc index b859ef3e0d..27c392d71d 100644 --- a/src/genn/genn/code_generator/backendSIMT.cc +++ b/src/genn/genn/code_generator/backendSIMT.cc @@ -28,6 +28,31 @@ size_t getNumMergedGroupThreads(const std::vector &groups, G getNumThreads) }); }); } +//----------------------------------------------------------------------- +template +std::vector> initReductionTargets(CodeStream &os, const BackendBase &backend, const G &cg) +{ + // Loop through variables + std::vector> reductionTargets; + const auto *cm = cg.getArchetype().getCustomUpdateModel(); + for(const auto &v : cm->getVars()) { + // If variable is a reduction target, define variable initialised to correct initial value for reduction + if(v.access & VarAccessModeAttribute::REDUCE) { + os << v.type << " lr" << v.name << " = " << getReductionInitialValue(backend, getVarAccessMode(v.access), v.type) << ";" << std::endl; + reductionTargets.emplace_back(v.name, v.type, getVarAccessMode(v.access)); + } + } + + // Loop through variable references + for(const auto &v : cm->getVarRefs()) { + // If variable reference is a reduction target, define variable initialised to correct initial value for reduction + if(v.access & VarAccessModeAttribute::REDUCE) { + os << v.type << " lr" << v.name << " = " << getReductionInitialValue(backend, v.access, v.type) << ";" << std::endl; + reductionTargets.emplace_back(v.name, v.type, v.access); + } + } + return reductionTargets; +} } //-------------------------------------------------------------------------- @@ -833,25 +858,8 @@ void BackendSIMT::genCustomUpdateKernel(CodeStream &os, const Substitutions &ker { CodeStream::Scope b(os); - // Loop through variables - std::vector> reductionTargets; - const auto *cm = cg.getArchetype().getCustomUpdateModel(); - for(const auto &v : cm->getVars()) { - // If variable is a reduction target, define variable initialised to correct initial value for reduction - if(v.access & VarAccessModeAttribute::REDUCE) { - os << v.type << " lr" << v.name << " = " << getReductionInitialValue(*this, getVarAccessMode(v.access), v.type) << ";" << std::endl; - reductionTargets.emplace_back(v.name, v.type, getVarAccessMode(v.access)); - } - } - - // Loop through variable references - for(const auto &v : cm->getVarRefs()) { - // If variable reference is a reduction target, define variable initialised to correct initial value for reduction - if(v.access & VarAccessModeAttribute::REDUCE) { - os << v.type << " lr" << v.name << " = " << getReductionInitialValue(*this, v.access, v.type) << ";" << std::endl; - reductionTargets.emplace_back(v.name, v.type, v.access); - } - } + // Initialise reduction targets + const auto reductionTargets = initReductionTargets(os, *this, cg); // Loop through batches // **TODO** this naive approach is good for reduction when there are lots of neurons/synapses but, @@ -925,26 +933,29 @@ void BackendSIMT::genCustomUpdateWUKernel(CodeStream &os, const Substitutions &k // Calculate number of threads for update os << "const unsigned int size = group->numSrcNeurons * group->rowStride;" << std::endl; - // If update is batched + // If update isn't a reduction Substitutions cuSubs(&popSubs); - if(cg.getArchetype().isBatched()) { - os << "const unsigned int paddedSize = " << blockSize << " * ((size + " << blockSize << " - 1) / " << blockSize << ");" << std::endl; - - // Split ID into intra-batch ID and batch - // **TODO** fast-divide style optimisations here - os << "const unsigned int bid = " << cuSubs["id"] << " % paddedSize;" << std::endl; - os << "const unsigned int batch = " << cuSubs["id"] << " / paddedSize;" << std::endl; - - // Replace id in substitution with intra-batch ID and add batch - cuSubs.addVarSubstitution("id", "bid", true); - cuSubs.addVarSubstitution("batch", "batch"); - - // Calculate batch offset - os << "const unsigned int batchOffset = size * batch;" << std::endl; - } - // Otherwise, just substitute "batch" for 0 - else { - cuSubs.addVarSubstitution("batch", "0"); + if(!cg.getArchetype().isReduction()) { + // If it's batched + if(cg.getArchetype().isBatched()) { + os << "const unsigned int paddedSize = " << blockSize << " * ((size + " << blockSize << " - 1) / " << blockSize << ");" << std::endl; + + // Split ID into intra-batch ID and batch + // **TODO** fast-divide style optimisations here + os << "const unsigned int bid = " << cuSubs["id"] << " % paddedSize;" << std::endl; + os << "const unsigned int batch = " << cuSubs["id"] << " / paddedSize;" << std::endl; + + // Replace id in substitution with intra-batch ID and add batch + cuSubs.addVarSubstitution("id", "bid", true); + cuSubs.addVarSubstitution("batch", "batch"); + + // Calculate batch offset + os << "const unsigned int batchOffset = size * batch;" << std::endl; + } + // Otherwise, just substitute "batch" for 0 + else { + cuSubs.addVarSubstitution("batch", "0"); + } } if(cg.getArchetype().getSynapseGroup()->getMatrixType() & SynapseMatrixConnectivity::SPARSE) { @@ -956,6 +967,20 @@ void BackendSIMT::genCustomUpdateWUKernel(CodeStream &os, const Substitutions &k { CodeStream::Scope b(os); + // Initialise reduction targets + const auto reductionTargets = (cg.getArchetype().isReduction() ? initReductionTargets(os, *this, cg) + : std::vector>{}); + + // If this is a reduction + if(cg.getArchetype().isReduction()) { + // Loop through batches + // **TODO** this naive approach is good for reduction when there are lots of neurons/synapses but, + // if this isn't the case (TF uses a threshold of 4096), we should do something smarter + os << "for(unsigned int batch = 0; batch < " << modelMerged.getModel().getBatchSize() << "; batch++)"; + os << CodeStream::OB(1); + cuSubs.addVarSubstitution("batch", "batch"); + } + if(cg.getArchetype().getSynapseGroup()->getMatrixType() & SynapseMatrixConnectivity::SPARSE) { // Determine synapse and presynaptic indices for this thread os << "const unsigned int s = group->synRemap[1 + " << cuSubs["id"] << "];" << std::endl; @@ -972,6 +997,22 @@ void BackendSIMT::genCustomUpdateWUKernel(CodeStream &os, const Substitutions &k } customUpdateWUHandler(os, cg, cuSubs); + + // If this is a reduction + if(cg.getArchetype().isReduction()) { + // Loop through reduction targets and generate reduction + for(const auto &r : reductionTargets) { + os << getReductionOperation("lr" + std::get<0>(r), "l" + std::get<0>(r), std::get<2>(r), std::get<1>(r)) << ";" << std::endl; + } + + // End for loop through batches + os << CodeStream::CB(1); + + // Loop through reduction targets + for(const auto &r : reductionTargets) { + os << "group->" << std::get<0>(r) << " = lr" << std::get<0>(r) << ";" << std::endl; + } + } } }); } From 27d676dcf84e84acfd3f506fd0c24d12c8609bb8 Mon Sep 17 00:00:00 2001 From: neworderofjamie Date: Thu, 22 Jul 2021 13:41:51 +0100 Subject: [PATCH 10/26] started feature test for reductions --- .../custom_update_reduction.sln | 30 +++++ .../custom_update_reduction.vcxproj | 63 ++++++++++ .../features/custom_update_reduction/model.cc | 110 ++++++++++++++++++ .../custom_update_reduction/runner_guid.txt | 1 + .../skip_SingleThreadedCPU | 0 .../features/custom_update_reduction/test.cc | 58 +++++++++ 6 files changed, 262 insertions(+) create mode 100644 tests/features/custom_update_reduction/custom_update_reduction.sln create mode 100644 tests/features/custom_update_reduction/custom_update_reduction.vcxproj create mode 100644 tests/features/custom_update_reduction/model.cc create mode 100644 tests/features/custom_update_reduction/runner_guid.txt create mode 100644 tests/features/custom_update_reduction/skip_SingleThreadedCPU create mode 100644 tests/features/custom_update_reduction/test.cc diff --git a/tests/features/custom_update_reduction/custom_update_reduction.sln b/tests/features/custom_update_reduction/custom_update_reduction.sln new file mode 100644 index 0000000000..24ade15603 --- /dev/null +++ b/tests/features/custom_update_reduction/custom_update_reduction.sln @@ -0,0 +1,30 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.30501.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "custom_update_reduction", "custom_update_reduction.vcxproj", "{80AF5494-0D9E-4015-8E2E-F69F27FA5E70}" + ProjectSection(ProjectDependencies) = postProject + {077CE370-2B04-43DE-B084-B210A0F55C} = {077CE370-2B04-43DE-B084-B210A0F55C} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "runner", "custom_update_reduction_CODE\runner.vcxproj", "{077CE370-2B04-43DE-B084-B210A0F55C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {80AF5494-0D9E-4015-8E2E-F69F27FA5E70}.Debug|x64.ActiveCfg = Debug|x64 + {80AF5494-0D9E-4015-8E2E-F69F27FA5E70}.Debug|x64.Build.0 = Debug|x64 + {80AF5494-0D9E-4015-8E2E-F69F27FA5E70}.Release|x64.ActiveCfg = Release|x64 + {80AF5494-0D9E-4015-8E2E-F69F27FA5E70}.Release|x64.Build.0 = Release|x64 + {077CE370-2B04-43DE-B084-B210A0F55C}.Debug|x64.ActiveCfg = Debug|x64 + {077CE370-2B04-43DE-B084-B210A0F55C}.Debug|x64.Build.0 = Debug|x64 + {077CE370-2B04-43DE-B084-B210A0F55C}.Release|x64.ActiveCfg = Release|x64 + {077CE370-2B04-43DE-B084-B210A0F55C}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/tests/features/custom_update_reduction/custom_update_reduction.vcxproj b/tests/features/custom_update_reduction/custom_update_reduction.vcxproj new file mode 100644 index 0000000000..5876255fee --- /dev/null +++ b/tests/features/custom_update_reduction/custom_update_reduction.vcxproj @@ -0,0 +1,63 @@ + + + + + Debug + x64 + + + Release + x64 + + + + {80AF5494-0D9E-4015-8E2E-F69F27FA5E70} + + + + + + + + + Application + true + $(DefaultPlatformToolset) + true + MultiByte + + + + + + + + + + ./ + $(Platform)\$(Configuration)\ + test + + + + Level3 + MaxSpeed + Disabled + true + true + true + custom_update_reduction_CODE;$(GTEST_DIR);$(GTEST_DIR)/include + _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING;_MBCS;%(PreprocessorDefinitions) + + + true + true + true + runner_Release.lib;%(AdditionalDependencies) + runner_Debug.lib;%(AdditionalDependencies) + + + + + + diff --git a/tests/features/custom_update_reduction/model.cc b/tests/features/custom_update_reduction/model.cc new file mode 100644 index 0000000000..b3620e201b --- /dev/null +++ b/tests/features/custom_update_reduction/model.cc @@ -0,0 +1,110 @@ +//-------------------------------------------------------------------------- +/*! \file custom_update_reduction/model.cc + +\brief model definition file that is part of the feature testing +suite of minimal models with known analytic outcomes that are used for continuous integration testing. +*/ +//-------------------------------------------------------------------------- + + +#include "modelSpec.h" + +class TestNeuron : public NeuronModels::Base +{ +public: + DECLARE_MODEL(TestNeuron, 0, 1); + + SET_VARS({{"V","scalar", VarAccess::READ_ONLY_DUPLICATE}}); +}; +IMPLEMENT_MODEL(TestNeuron); + +class TestWUM : public WeightUpdateModels::Base +{ +public: + DECLARE_WEIGHT_UPDATE_MODEL(TestWUM, 0, 1, 0, 0); + + SET_VARS({{"V", "scalar", VarAccess::READ_ONLY_DUPLICATE}}); +}; +IMPLEMENT_MODEL(TestWUM); + +class ReduceAdd : public CustomUpdateModels::Base +{ +public: + DECLARE_CUSTOM_UPDATE_MODEL(ReduceAdd, 0, 1, 1); + + SET_UPDATE_CODE("$(Sum) = $(V);\n"); + + SET_VARS({{"Sum", "scalar", VarAccess::REDUCE_BATCH_SUM}}); + SET_VAR_REFS({{"V", "scalar", VarAccessMode::READ_ONLY}}) +}; +IMPLEMENT_MODEL(ReduceAdd); + +class ReduceMax : public CustomUpdateModels::Base +{ +public: + DECLARE_CUSTOM_UPDATE_MODEL(ReduceMax, 0, 1, 1); + + SET_UPDATE_CODE("$(Max) = $(V);\n"); + + SET_VARS({{"Max", "scalar", VarAccess::REDUCE_BATCH_MAX}}); + SET_VAR_REFS({{"V", "scalar", VarAccessMode::READ_ONLY}}) +}; +IMPLEMENT_MODEL(ReduceMax); + +void modelDefinition(ModelSpec &model) +{ +#ifdef CL_HPP_TARGET_OPENCL_VERSION + if(std::getenv("OPENCL_DEVICE") != nullptr) { + GENN_PREFERENCES.deviceSelectMethod = DeviceSelect::MANUAL; + GENN_PREFERENCES.manualDeviceID = std::atoi(std::getenv("OPENCL_DEVICE")); + } + if(std::getenv("OPENCL_PLATFORM") != nullptr) { + GENN_PREFERENCES.manualPlatformID = std::atoi(std::getenv("OPENCL_PLATFORM")); + } +#endif + model.setDT(1.0); + model.setName("custom_update_reduction"); + model.setBatchSize(5); + + model.addNeuronPopulation("SpikeSource", 50, {}, {}); + auto *ng = model.addNeuronPopulation("Neuron", 50, {}, {uninitialisedVar()}); + /*auto *denseSG = model.addSynapsePopulation( + "Dense", SynapseMatrixType::DENSE_INDIVIDUALG, NO_DELAY, + "SpikeSource", "Neuron", + {}, {0.0, 0.0}, + {}, {}); + auto *sparseSG = model.addSynapsePopulation( + "Sparse", SynapseMatrixType::SPARSE_INDIVIDUALG, NO_DELAY, + "SpikeSource", "Neuron", + {}, {0.0, 0.0}, + {}, {}, + initConnectivity({0.1}));*/ + + //--------------------------------------------------------------------------- + // Custom updates + //--------------------------------------------------------------------------- + ReduceAdd::VarReferences neuronReduceAddVarReferences(createVarRef(ng, "V")); // V + model.addCustomUpdate("NeuronReduceAdd", "Test", + {}, {0.0}, neuronReduceAddVarReferences); + + ReduceMax::VarReferences neuronReduceMaxVarReferences(createVarRef(ng, "V")); // V + model.addCustomUpdate("NeuronReduceMax", "Test", + {}, {0.0}, neuronReduceMaxVarReferences); + + /*SetTimeBatch::WUVarReferences wumDenseDuplicateVarReferences(createWUVarRef(denseSG, "V")); // R + model.addCustomUpdate("WUMDenseDuplicateSetTime", "Test", + {}, {0.0}, wumDenseDuplicateVarReferences); + + SetTime::WUVarReferences wumDenseSharedVarReferences(createWUVarRef(denseSG, "U")); // R + model.addCustomUpdate("WUMDenseSharedSetTime", "Test", + {}, {0.0}, wumDenseSharedVarReferences); + + SetTimeBatch::WUVarReferences wumSparseDuplicateVarReferences(createWUVarRef(sparseSG, "V")); // R + model.addCustomUpdate("WUMSparseDuplicateSetTime", "Test", + {}, {0.0}, wumSparseDuplicateVarReferences); + + SetTime::WUVarReferences wumSparseSharedVarReferences(createWUVarRef(sparseSG, "U")); // R + model.addCustomUpdate("WUMSparseSharedSetTime", "Test", + {}, {0.0}, wumSparseSharedVarReferences);*/ + +} \ No newline at end of file diff --git a/tests/features/custom_update_reduction/runner_guid.txt b/tests/features/custom_update_reduction/runner_guid.txt new file mode 100644 index 0000000000..d04d942297 --- /dev/null +++ b/tests/features/custom_update_reduction/runner_guid.txt @@ -0,0 +1 @@ +077CE370-2B04-43DE-B084-B210A0F55C diff --git a/tests/features/custom_update_reduction/skip_SingleThreadedCPU b/tests/features/custom_update_reduction/skip_SingleThreadedCPU new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/features/custom_update_reduction/test.cc b/tests/features/custom_update_reduction/test.cc new file mode 100644 index 0000000000..73837084d7 --- /dev/null +++ b/tests/features/custom_update_reduction/test.cc @@ -0,0 +1,58 @@ +//-------------------------------------------------------------------------- +/*! \file custom_update_reduction/test.cc + +\brief Main test code that is part of the feature testing +suite of minimal models with known analytic outcomes that are used for continuous integration testing. +*/ +//-------------------------------------------------------------------------- +// Standard C++ includes +#include +#include + +// Google test includes +#include "gtest/gtest.h" + +// Auto-generated simulation code includess +#include "custom_update_reduction_CODE/definitions.h" + +// **NOTE** base-class for simulation tests must be +// included after auto-generated globals are includes +#include "../../utils/simulation_test.h" + +//---------------------------------------------------------------------------- +// SimTest +//---------------------------------------------------------------------------- +class SimTest : public SimulationTest +{ +public: + virtual void Init() + { + // Initialise variables to reduce + std::iota(&VNeuron[0], &VNeuron[50 * 5], 0); + } +}; + +template +void checkSparseVar(scalar *var, Predicate predicate) +{ + for(unsigned int i = 0; i < 50; i++) { + const unsigned int rowStart = maxRowLengthSparse * i; + const unsigned int rowLength = rowLengthSparse[i]; + ASSERT_TRUE(std::all_of(&var[rowStart], &var[rowStart + rowLength], predicate)); + } +} + +TEST_F(SimTest, CustomUpdateReduction) +{ + // Launch reduction + updateTest(); + + // Download reductions + pullSumNeuronReduceAddFromDevice(); + pullMaxNeuronReduceMaxFromDevice(); + + for(unsigned int i = 0; i < 50; i++) { + ASSERT_EQ(SumNeuronReduceAdd[i], (5 * (i + (4 * 50) + i)) / 2); + } +} + From 762319698af1ddaa341e4e2fb5258d323b90a41a Mon Sep 17 00:00:00 2001 From: neworderofjamie Date: Thu, 22 Jul 2021 14:41:24 +0100 Subject: [PATCH 11/26] fixed typos --- src/genn/genn/code_generator/backendSIMT.cc | 41 ++++++++++++--------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/src/genn/genn/code_generator/backendSIMT.cc b/src/genn/genn/code_generator/backendSIMT.cc index 27c392d71d..282004350c 100644 --- a/src/genn/genn/code_generator/backendSIMT.cc +++ b/src/genn/genn/code_generator/backendSIMT.cc @@ -37,6 +37,7 @@ std::vector> initReductionTa const auto *cm = cg.getArchetype().getCustomUpdateModel(); for(const auto &v : cm->getVars()) { // If variable is a reduction target, define variable initialised to correct initial value for reduction + // **NOTE** by not initialising this, compilers should emit a warning if user code doesn't set it to something if(v.access & VarAccessModeAttribute::REDUCE) { os << v.type << " lr" << v.name << " = " << getReductionInitialValue(backend, getVarAccessMode(v.access), v.type) << ";" << std::endl; reductionTargets.emplace_back(v.name, v.type, getVarAccessMode(v.access)); @@ -46,6 +47,7 @@ std::vector> initReductionTa // Loop through variable references for(const auto &v : cm->getVarRefs()) { // If variable reference is a reduction target, define variable initialised to correct initial value for reduction + // **NOTE** by not initialising this, compilers should emit a warning if user code doesn't set it to something if(v.access & VarAccessModeAttribute::REDUCE) { os << v.type << " lr" << v.name << " = " << getReductionInitialValue(backend, v.access, v.type) << ";" << std::endl; reductionTargets.emplace_back(v.name, v.type, v.access); @@ -878,9 +880,9 @@ void BackendSIMT::genCustomUpdateKernel(CodeStream &os, const Substitutions &ker } } - // Loop through reduction targets + // Loop through reduction targets and write reduced value back to memory for(const auto &r : reductionTargets) { - os << "group->" << std::get<0>(r) << " = lr" << std::get<0>(r) << ";" << std::endl; + os << "group->" << std::get<0>(r) << "[" << cuSubs["id"] << "] = lr" << std::get<0>(r) << ";" << std::endl; } } } @@ -967,6 +969,21 @@ void BackendSIMT::genCustomUpdateWUKernel(CodeStream &os, const Substitutions &k { CodeStream::Scope b(os); + if(cg.getArchetype().getSynapseGroup()->getMatrixType() & SynapseMatrixConnectivity::SPARSE) { + // Determine synapse and presynaptic indices for this thread + os << "const unsigned int s = group->synRemap[1 + " << cuSubs["id"] << "];" << std::endl; + + cuSubs.addVarSubstitution("id_pre", "(s / group->rowStride)"); + cuSubs.addVarSubstitution("id_post", "group->ind[s]"); + cuSubs.addVarSubstitution("id_syn", "s"); + } + else { + // **OPTIMIZE** we can do a fast constant divide optimization here and use the result to calculate the remainder + cuSubs.addVarSubstitution("id_pre", "(" + cuSubs["id"] + " / group->rowStride)"); + cuSubs.addVarSubstitution("id_post", "(" + cuSubs["id"] + " % group->rowStride)"); + cuSubs.addVarSubstitution("id_syn", cuSubs["id"]); + } + // Initialise reduction targets const auto reductionTargets = (cg.getArchetype().isReduction() ? initReductionTargets(os, *this, cg) : std::vector>{}); @@ -981,19 +998,9 @@ void BackendSIMT::genCustomUpdateWUKernel(CodeStream &os, const Substitutions &k cuSubs.addVarSubstitution("batch", "batch"); } - if(cg.getArchetype().getSynapseGroup()->getMatrixType() & SynapseMatrixConnectivity::SPARSE) { - // Determine synapse and presynaptic indices for this thread - os << "const unsigned int s = group->synRemap[1 + " << cuSubs["id"] << "];" << std::endl; - - cuSubs.addVarSubstitution("id_pre", "(s / group->rowStride)"); - cuSubs.addVarSubstitution("id_post", "group->ind[s]"); - cuSubs.addVarSubstitution("id_syn", "s"); - } - else { - // **OPTIMIZE** we can do a fast constant divide optimization here and use the result to calculate the remainder - cuSubs.addVarSubstitution("id_pre", "(" + cuSubs["id"] + " / group->rowStride)"); - cuSubs.addVarSubstitution("id_post", "(" + cuSubs["id"] + " % group->rowStride)"); - cuSubs.addVarSubstitution("id_syn", cuSubs["id"]); + // Calculate batch offset if required + if(cg.getArchetype().isBatched()) { + os << "const unsigned int batchOffset = size * batch;" << std::endl; } customUpdateWUHandler(os, cg, cuSubs); @@ -1008,9 +1015,9 @@ void BackendSIMT::genCustomUpdateWUKernel(CodeStream &os, const Substitutions &k // End for loop through batches os << CodeStream::CB(1); - // Loop through reduction targets + // Loop through reduction targets and write reduced value back to memory for(const auto &r : reductionTargets) { - os << "group->" << std::get<0>(r) << " = lr" << std::get<0>(r) << ";" << std::endl; + os << "group->" << std::get<0>(r) << "[" << cuSubs["id_syn"] << "] = lr" << std::get<0>(r) << ";" << std::endl; } } } From dccd48bc454da7a7d0269bdc94640857a0433441 Mon Sep 17 00:00:00 2001 From: neworderofjamie Date: Thu, 22 Jul 2021 14:54:53 +0100 Subject: [PATCH 12/26] complete feature test --- .../features/custom_update_reduction/model.cc | 63 ++++++------------- .../features/custom_update_reduction/test.cc | 53 ++++++++++++---- 2 files changed, 60 insertions(+), 56 deletions(-) diff --git a/tests/features/custom_update_reduction/model.cc b/tests/features/custom_update_reduction/model.cc index b3620e201b..1741e65180 100644 --- a/tests/features/custom_update_reduction/model.cc +++ b/tests/features/custom_update_reduction/model.cc @@ -27,29 +27,19 @@ class TestWUM : public WeightUpdateModels::Base }; IMPLEMENT_MODEL(TestWUM); -class ReduceAdd : public CustomUpdateModels::Base +class Reduce : public CustomUpdateModels::Base { public: - DECLARE_CUSTOM_UPDATE_MODEL(ReduceAdd, 0, 1, 1); + DECLARE_CUSTOM_UPDATE_MODEL(Reduce, 0, 2, 1); - SET_UPDATE_CODE("$(Sum) = $(V);\n"); + SET_UPDATE_CODE( + "$(Sum) = $(V);\n" + "$(Max) = $(V);\n"); - SET_VARS({{"Sum", "scalar", VarAccess::REDUCE_BATCH_SUM}}); + SET_VARS({{"Sum", "scalar", VarAccess::REDUCE_BATCH_SUM}, {"Max", "scalar", VarAccess::REDUCE_BATCH_MAX}}); SET_VAR_REFS({{"V", "scalar", VarAccessMode::READ_ONLY}}) }; -IMPLEMENT_MODEL(ReduceAdd); - -class ReduceMax : public CustomUpdateModels::Base -{ -public: - DECLARE_CUSTOM_UPDATE_MODEL(ReduceMax, 0, 1, 1); - - SET_UPDATE_CODE("$(Max) = $(V);\n"); - - SET_VARS({{"Max", "scalar", VarAccess::REDUCE_BATCH_MAX}}); - SET_VAR_REFS({{"V", "scalar", VarAccessMode::READ_ONLY}}) -}; -IMPLEMENT_MODEL(ReduceMax); +IMPLEMENT_MODEL(Reduce); void modelDefinition(ModelSpec &model) { @@ -68,43 +58,30 @@ void modelDefinition(ModelSpec &model) model.addNeuronPopulation("SpikeSource", 50, {}, {}); auto *ng = model.addNeuronPopulation("Neuron", 50, {}, {uninitialisedVar()}); - /*auto *denseSG = model.addSynapsePopulation( + auto *denseSG = model.addSynapsePopulation( "Dense", SynapseMatrixType::DENSE_INDIVIDUALG, NO_DELAY, "SpikeSource", "Neuron", - {}, {0.0, 0.0}, + {}, {uninitialisedVar()}, {}, {}); auto *sparseSG = model.addSynapsePopulation( "Sparse", SynapseMatrixType::SPARSE_INDIVIDUALG, NO_DELAY, "SpikeSource", "Neuron", - {}, {0.0, 0.0}, + {}, {uninitialisedVar()}, {}, {}, - initConnectivity({0.1}));*/ + initConnectivity({0.1})); //--------------------------------------------------------------------------- // Custom updates //--------------------------------------------------------------------------- - ReduceAdd::VarReferences neuronReduceAddVarReferences(createVarRef(ng, "V")); // V - model.addCustomUpdate("NeuronReduceAdd", "Test", - {}, {0.0}, neuronReduceAddVarReferences); - - ReduceMax::VarReferences neuronReduceMaxVarReferences(createVarRef(ng, "V")); // V - model.addCustomUpdate("NeuronReduceMax", "Test", - {}, {0.0}, neuronReduceMaxVarReferences); - - /*SetTimeBatch::WUVarReferences wumDenseDuplicateVarReferences(createWUVarRef(denseSG, "V")); // R - model.addCustomUpdate("WUMDenseDuplicateSetTime", "Test", - {}, {0.0}, wumDenseDuplicateVarReferences); + Reduce::VarReferences neuronReduceVarReferences(createVarRef(ng, "V")); // V + model.addCustomUpdate("NeuronReduce", "Test", + {}, {0.0, 0.0}, neuronReduceVarReferences); - SetTime::WUVarReferences wumDenseSharedVarReferences(createWUVarRef(denseSG, "U")); // R - model.addCustomUpdate("WUMDenseSharedSetTime", "Test", - {}, {0.0}, wumDenseSharedVarReferences); + Reduce::WUVarReferences wumDenseReduceVarReferences(createWUVarRef(denseSG, "V")); // V + model.addCustomUpdate("WUMDenseReduce", "Test", + {}, {0.0, 0.0}, wumDenseReduceVarReferences); - SetTimeBatch::WUVarReferences wumSparseDuplicateVarReferences(createWUVarRef(sparseSG, "V")); // R - model.addCustomUpdate("WUMSparseDuplicateSetTime", "Test", - {}, {0.0}, wumSparseDuplicateVarReferences); - - SetTime::WUVarReferences wumSparseSharedVarReferences(createWUVarRef(sparseSG, "U")); // R - model.addCustomUpdate("WUMSparseSharedSetTime", "Test", - {}, {0.0}, wumSparseSharedVarReferences);*/ - + Reduce::WUVarReferences wumSparseReduceVarReferences(createWUVarRef(sparseSG, "V")); // V + model.addCustomUpdate("WUMSparseReduce", "Test", + {}, {0.0, 0.0}, wumSparseReduceVarReferences); } \ No newline at end of file diff --git a/tests/features/custom_update_reduction/test.cc b/tests/features/custom_update_reduction/test.cc index 73837084d7..946397148b 100644 --- a/tests/features/custom_update_reduction/test.cc +++ b/tests/features/custom_update_reduction/test.cc @@ -28,31 +28,58 @@ class SimTest : public SimulationTest virtual void Init() { // Initialise variables to reduce - std::iota(&VNeuron[0], &VNeuron[50 * 5], 0); + std::iota(&VNeuron[0], &VNeuron[50 * 5], 0.0f); + std::iota(&VDense[0], &VDense[50 * 50 * 5], 0.0f); + + pullSparseConnectivityFromDevice(); + for(unsigned int b = 0; b < 5; b++) { + const unsigned int batchStart = (b * 50 * maxRowLengthSparse); + for(unsigned int i = 0; i < 50; i++) { + const unsigned rowStartIdx = batchStart + (i * maxRowLengthSparse); + for(unsigned int j = 0 ; j < rowLengthSparse[i]; j++) { + const unsigned int synIdx = rowStartIdx + j; + VSparse[synIdx] = (scalar)synIdx; + } + } + } } }; -template -void checkSparseVar(scalar *var, Predicate predicate) +int getIntegerRangeSum(int num, int first, int last) { - for(unsigned int i = 0; i < 50; i++) { - const unsigned int rowStart = maxRowLengthSparse * i; - const unsigned int rowLength = rowLengthSparse[i]; - ASSERT_TRUE(std::all_of(&var[rowStart], &var[rowStart + rowLength], predicate)); - } + return num * (first + last) / 2; } TEST_F(SimTest, CustomUpdateReduction) { // Launch reduction updateTest(); - + // Download reductions - pullSumNeuronReduceAddFromDevice(); - pullMaxNeuronReduceMaxFromDevice(); - + pullNeuronReduceStateFromDevice(); + pullWUMDenseReduceStateFromDevice(); + pullWUMSparseReduceStateFromDevice(); + + // Check neuron reductions + for(unsigned int i = 0; i < 50; i++) { + ASSERT_EQ(SumNeuronReduce[i], (float)getIntegerRangeSum(5, i, (4 * 50) + i)); + ASSERT_EQ(MaxNeuronReduce[i], (float)((4 * 50) + i)); + } + + // Check dense weight reductions + for(unsigned int i = 0; i < (50 * 50); i++) { + ASSERT_EQ(SumWUMDenseReduce[i], (float)getIntegerRangeSum(5, i, (4 * 50 * 50) + i)); + ASSERT_EQ(MaxWUMDenseReduce[i], (float)((4 * 50 * 50) + i)); + } + + // Check sparse weight reductions for(unsigned int i = 0; i < 50; i++) { - ASSERT_EQ(SumNeuronReduceAdd[i], (5 * (i + (4 * 50) + i)) / 2); + const unsigned rowStartIdx = i * maxRowLengthSparse; + for(unsigned int j = 0 ; j < rowLengthSparse[i]; j++) { + const unsigned int synIdx = rowStartIdx + j; + ASSERT_EQ(SumWUMSparseReduce[synIdx], (float)getIntegerRangeSum(5, synIdx, (4 * 50 * maxRowLengthSparse) + synIdx)); + ASSERT_EQ(MaxWUMSparseReduce[synIdx], (float)((4 * 50 * maxRowLengthSparse) + synIdx)); + } } } From b0bebdc4918a83a05eae038d00678bcbbb009bc4 Mon Sep 17 00:00:00 2001 From: neworderofjamie Date: Thu, 22 Jul 2021 15:44:08 +0100 Subject: [PATCH 13/26] fixed warning --- src/genn/genn/code_generator/codeGenUtils.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/genn/genn/code_generator/codeGenUtils.cc b/src/genn/genn/code_generator/codeGenUtils.cc index eac36729ec..63c189261c 100644 --- a/src/genn/genn/code_generator/codeGenUtils.cc +++ b/src/genn/genn/code_generator/codeGenUtils.cc @@ -478,6 +478,7 @@ std::string getReductionInitialValue(const BackendBase &backend, VarAccessMode a } else { assert(false); + return ""; } } From 31a6f7b56b311d0c43e607be9e6c134f3d2b1881 Mon Sep 17 00:00:00 2001 From: neworderofjamie Date: Thu, 22 Jul 2021 15:51:20 +0100 Subject: [PATCH 14/26] fixed issue with var access flags --- include/genn/genn/varAccess.h | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/include/genn/genn/varAccess.h b/include/genn/genn/varAccess.h index 035dd12fbc..ca894e9a62 100644 --- a/include/genn/genn/varAccess.h +++ b/include/genn/genn/varAccess.h @@ -5,20 +5,21 @@ // Enumerations //---------------------------------------------------------------------------- //! Flags defining attributes of var access models +//! **NOTE** Read-only and read-write are seperate flags rather than read and write so you can test mode & VarAccessMode::READ_ONLY enum class VarAccessModeAttribute : unsigned int { - READ = (1 << 0), //! This variable can be read - WRITE = (1 << 1), //! This variable can be written - REDUCE = (1 << 2), //! This variable is a reduction target - SUM = (1 << 3), //! This variable's reduction operation is a summation - MAX = (1 << 4), //! This variable's reduction operation is a maximum + READ_ONLY = (1 << 0), //! This variable is read only + READ_WRITE = (1 << 1), //! This variable is read-write + REDUCE = (1 << 2), //! This variable is a reduction target + SUM = (1 << 3), //! This variable's reduction operation is a summation + MAX = (1 << 4), //! This variable's reduction operation is a maximum }; //! Supported combination of VarAccessModeAttribute enum class VarAccessMode : unsigned int { - READ_WRITE = static_cast(VarAccessModeAttribute::READ) | static_cast(VarAccessModeAttribute::WRITE), - READ_ONLY = static_cast(VarAccessModeAttribute::READ), + READ_WRITE = static_cast(VarAccessModeAttribute::READ_WRITE), + READ_ONLY = static_cast(VarAccessModeAttribute::READ_ONLY), REDUCE_SUM = static_cast(VarAccessModeAttribute::REDUCE) | static_cast(VarAccessModeAttribute::SUM), REDUCE_MAX = static_cast(VarAccessModeAttribute::REDUCE) | static_cast(VarAccessModeAttribute::MAX), }; From 250052b203c0c43f02f24dfa5d8e6f01be89fda5 Mon Sep 17 00:00:00 2001 From: neworderofjamie Date: Thu, 22 Jul 2021 17:02:47 +0100 Subject: [PATCH 15/26] respect ``isBatched`` in ``CustomUpdateWUGroupMergedBase::getVarIndex`` and ``CustomUpdateWUGroupMergedBase::getVarRefIndex`` --- include/genn/genn/code_generator/groupMerged.h | 7 ++----- src/genn/genn/code_generator/groupMerged.cc | 8 ++++---- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/include/genn/genn/code_generator/groupMerged.h b/include/genn/genn/code_generator/groupMerged.h index ac43052545..296e96e644 100644 --- a/include/genn/genn/code_generator/groupMerged.h +++ b/include/genn/genn/code_generator/groupMerged.h @@ -1544,11 +1544,8 @@ class GENN_EXPORT CustomUpdateWUGroupMergedBase : public GroupMerged Date: Thu, 22 Jul 2021 17:24:48 +0100 Subject: [PATCH 16/26] fixed issue in PyGeNN --- pygenn/genn_model.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/pygenn/genn_model.py b/pygenn/genn_model.py index 60bb0ae4de..409ba51a3b 100644 --- a/pygenn/genn_model.py +++ b/pygenn/genn_model.py @@ -941,13 +941,13 @@ def create_wu_post_var_ref(sg, var_name): """ return (genn_wrapper.create_wupost_var_ref(sg.pop, var_name), sg) -def create_wu_var_ref(sg, var_name, tp_sg=None, tp_var_name=None): +def create_wu_var_ref(g, var_name, tp_sg=None, tp_var_name=None): """This helper function creates a Models::WUVarReference pointing to a weight update model variable for initialising variable references. Args: - sg -- SynapseGroup object + g -- SynapseGroup or CustomUpdate object var_name -- name of weight update model variable in synapse group to reference tp_sg -- (optional) SynapseGroup object to @@ -956,10 +956,15 @@ def create_wu_var_ref(sg, var_name, tp_sg=None, tp_var_name=None): model variable in tranpose synapse group to copy transpose to """ + + # If we're referencing a WU variable in a custom update, + # Use it's synapse group for the PyGeNN-level backreference + sg = g._synapse_group if isinstance(g, CustomUpdate) else g + if tp_sg is None: - return (genn_wrapper.create_wuvar_ref(sg.pop, var_name), sg) + return (genn_wrapper.create_wuvar_ref(g.pop, var_name), sg) else: - return (genn_wrapper.create_wuvar_ref(sg.pop, var_name, + return (genn_wrapper.create_wuvar_ref(g.pop, var_name, tp_sg.pop, tp_var_name), sg) From 321d49fb4dfe57b14b6f90eca6d6da91cae0f118 Mon Sep 17 00:00:00 2001 From: neworderofjamie Date: Thu, 5 Aug 2021 11:29:04 +0100 Subject: [PATCH 17/26] * added error message if you try and reduce into a duplicate variable * added test of error --- include/genn/genn/customUpdate.h | 12 +++++++++++- tests/unit/customUpdate.cc | 33 ++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/include/genn/genn/customUpdate.h b/include/genn/genn/customUpdate.h index 6c552332ab..8297bf9c7a 100644 --- a/include/genn/genn/customUpdate.h +++ b/include/genn/genn/customUpdate.h @@ -98,6 +98,17 @@ class GENN_EXPORT CustomUpdateBase template void finalize(unsigned int batchSize, const std::vector &varRefs) { + // Loop through variable references and check that no reduction targets reference duplicated variables + // **NOTE** whether model is batched or not is irrelevant + const auto modelVarRefs = getCustomUpdateModel()->getVarRefs(); + for(size_t i = 0; i < modelVarRefs.size(); i++) { + if((varRefs.at(i).getVar().access & VarAccessDuplication::DUPLICATE) + && (modelVarRefs.at(i).access & VarAccessModeAttribute::REDUCE)) + { + throw std::runtime_error("Reduction target variable reference must be to SHARED variables."); + } + } + // If model has batching at all, custom update should be batched // if targets of any variable references are duplicated // **THINK** what about variables? @@ -108,7 +119,6 @@ class GENN_EXPORT CustomUpdateBase // If custom update is batched, check that any variable references to shared variables are read-only // **THINK** what about variables? if(m_Batched) { - const auto modelVarRefs = getCustomUpdateModel()->getVarRefs(); for(size_t i = 0; i < modelVarRefs.size(); i++) { if((varRefs.at(i).getVar().access & VarAccessDuplication::SHARED) && (modelVarRefs.at(i).access != VarAccessMode::READ_ONLY)) diff --git a/tests/unit/customUpdate.cc b/tests/unit/customUpdate.cc index 7658f8c61a..c1ce9c4bd2 100644 --- a/tests/unit/customUpdate.cc +++ b/tests/unit/customUpdate.cc @@ -62,6 +62,17 @@ class Cont2 : public WeightUpdateModels::Base "$(addToInSyn, ($(g) + $(x)) * $(V_pre));\n"); }; IMPLEMENT_MODEL(Cont2); + +class Reduce : public CustomUpdateModels::Base +{ + DECLARE_CUSTOM_UPDATE_MODEL(Reduce, 0, 0, 2); + + SET_UPDATE_CODE("$(reduction) = $(var);\n"); + + SET_VAR_REFS({{"var", "scalar", VarAccessMode::READ_ONLY}, + {"reduction", "scalar", VarAccessMode::REDUCE_SUM}}); +}; +IMPLEMENT_MODEL(Reduce); } //-------------------------------------------------------------------------- // Tests @@ -267,6 +278,28 @@ TEST(CustomUpdates, BatchingWriteShared) NeuronModels::IzhikevichVariable::VarValues izkVarVals(0.0, 0.0, 0.02, 0.2, -65.0, 8.); auto *pop = model.addNeuronPopulation("Pop", 10, {}, izkVarVals); + // Create custom update which tries to create a read-write refernece to a (which isn't batched) + Reduce::VarReferences reduceVarReferences(createVarRef(pop, "V"), createVarRef(pop, "U")); + model.addCustomUpdate("Sum1", "CustomUpdate", + {}, {}, reduceVarReferences); + + try { + model.finalize(); + FAIL(); + } + catch(const std::runtime_error &) { + } +} +//-------------------------------------------------------------------------- +TEST(CustomUpdates, ReduceDuplicate) +{ + ModelSpecInternal model; + model.setBatchSize(5); + + // Add neuron and spike source (arbitrary choice of model with read_only variables) to model + NeuronModels::IzhikevichVariable::VarValues izkVarVals(0.0, 0.0, 0.02, 0.2, -65.0, 8.); + auto *pop = model.addNeuronPopulation("Pop", 10, {}, izkVarVals); + // Create custom update which tries to create a read-write refernece to a (which isn't batched) Sum2::VarValues sum2VarValues(1.0); Sum2::VarReferences sum2VarReferences(createVarRef(pop, "a"), createVarRef(pop, "V")); From 49351c9907cddbf0d30f7df82e942b61b430c595 Mon Sep 17 00:00:00 2001 From: neworderofjamie Date: Thu, 5 Aug 2021 12:07:07 +0100 Subject: [PATCH 18/26] Small refactor of ``CustomUpdateBase::isBatched`` and ``CustomUpdate::isReduction`` so they are set irrespective of actual batch size of model --- .../genn/genn/code_generator/backendBase.h | 2 +- include/genn/genn/customUpdate.h | 93 ++++++------------- include/genn/genn/customUpdateInternal.h | 1 - include/genn/genn/customUpdateModels.h | 3 + .../backends/single_threaded_cpu/backend.cc | 2 +- src/genn/genn/code_generator/backendBase.cc | 6 +- src/genn/genn/code_generator/backendSIMT.cc | 21 +++-- src/genn/genn/customUpdate.cc | 19 +--- src/genn/genn/customUpdateModels.cc | 11 +++ src/genn/genn/modelSpec.cc | 3 +- 10 files changed, 64 insertions(+), 97 deletions(-) diff --git a/include/genn/genn/code_generator/backendBase.h b/include/genn/genn/code_generator/backendBase.h index d29a04ed9f..f077787e20 100644 --- a/include/genn/genn/code_generator/backendBase.h +++ b/include/genn/genn/code_generator/backendBase.h @@ -518,7 +518,7 @@ class GENN_EXPORT BackendBase void genSynapseIndexCalculation(CodeStream &os, const SynapseGroupMergedBase &sg, unsigned int batchSize) const; - void genCustomUpdateIndexCalculation(CodeStream &os, const CustomUpdateGroupMerged &cu) const; + void genCustomUpdateIndexCalculation(CodeStream &os, const CustomUpdateGroupMerged &cu, unsigned int batchSize) const; private: //-------------------------------------------------------------------------- diff --git a/include/genn/genn/customUpdate.h b/include/genn/genn/customUpdate.h index 8297bf9c7a..d5dfb2d0ee 100644 --- a/include/genn/genn/customUpdate.h +++ b/include/genn/genn/customUpdate.h @@ -55,7 +55,7 @@ class GENN_EXPORT CustomUpdateBase : m_Name(name), m_UpdateGroupName(updateGroupName), m_CustomUpdateModel(customUpdateModel), m_Params(params), m_VarInitialisers(varInitialisers), m_VarLocation(varInitialisers.size(), defaultVarLocation), m_ExtraGlobalParamLocation(customUpdateModel->getExtraGlobalParams().size(), defaultExtraGlobalParamLocation), - m_Batched(false), m_Reduction(false) + m_Batched(false) { // Validate names Utils::validateVarPopName(name, "Custom update"); @@ -82,7 +82,7 @@ class GENN_EXPORT CustomUpdateBase bool isBatched() const { return m_Batched; } //! Does this custom update perform a reduction i.e. reduce some variables from DUPLICATE to SHARED - bool isReduction() const { return m_Reduction; } + bool isReduction() const { return getCustomUpdateModel()->isReduction(); } //! Updates hash with custom update /*! NOTE: this can only be called after model is finalized */ @@ -94,68 +94,40 @@ class GENN_EXPORT CustomUpdateBase boost::uuids::detail::sha1::digest_type getVarLocationHashDigest() const; - //! Helper function to determine whether a custom update should be batched or treated as a reduction - template - void finalize(unsigned int batchSize, const std::vector &varRefs) + //! Helper function to check if variable reference types match those specified in model + template + void checkVarReferences(const std::vector &varRefs) { - // Loop through variable references and check that no reduction targets reference duplicated variables - // **NOTE** whether model is batched or not is irrelevant const auto modelVarRefs = getCustomUpdateModel()->getVarRefs(); - for(size_t i = 0; i < modelVarRefs.size(); i++) { - if((varRefs.at(i).getVar().access & VarAccessDuplication::DUPLICATE) - && (modelVarRefs.at(i).access & VarAccessModeAttribute::REDUCE)) - { - throw std::runtime_error("Reduction target variable reference must be to SHARED variables."); - } - } - // If model has batching at all, custom update should be batched - // if targets of any variable references are duplicated - // **THINK** what about variables? - if(batchSize > 1) { - m_Batched = std::any_of(varRefs.cbegin(), varRefs.cend(), - [](const R &v) { return (v.getVar().access & VarAccessDuplication::DUPLICATE); }); - - // If custom update is batched, check that any variable references to shared variables are read-only - // **THINK** what about variables? - if(m_Batched) { - for(size_t i = 0; i < modelVarRefs.size(); i++) { - if((varRefs.at(i).getVar().access & VarAccessDuplication::SHARED) - && (modelVarRefs.at(i).access != VarAccessMode::READ_ONLY)) - { - throw std::runtime_error("Variable references to SHARED variables in batched custom updates must be read-only."); - } - } - - // The custom update is a reduction if any variables or references have the reduce access mode attribute - const auto vars = getCustomUpdateModel()->getVars(); - const auto varRefs = getCustomUpdateModel()->getVarRefs(); - m_Reduction = (std::any_of(vars.cbegin(), vars.cend(), - [](const Models::Base::Var &v) { return (v.access & VarAccessModeAttribute::REDUCE); }) - || std::any_of(varRefs.cbegin(), varRefs.cend(), - [](const Models::Base::VarRef &v) { return (v.access & VarAccessModeAttribute::REDUCE); })); - } - } - // Otherwise, update should not be batched and reductions are not required - else { - m_Batched = false; - m_Reduction = false; - } - } + // If target of any variable references is duplicated, custom update should be batched + m_Batched = std::any_of(varRefs.cbegin(), varRefs.cend(), + [](const V &v) { return (v.getVar().access & VarAccessDuplication::DUPLICATE); }); - //! Helper function to check if variable reference types match those specified in model - template - void checkVarReferenceTypes(const std::vector &varReferences) const - { // Loop through all variable references - const auto varRefs = getCustomUpdateModel()->getVarRefs(); - for(size_t i = 0; i < varReferences.size(); i++) { - const auto varRef = varReferences.at(i); + for(size_t i = 0; i < varRefs.size(); i++) { + const auto varRef = varRefs.at(i); + const auto modelVarRef = modelVarRefs.at(i); // Check types of variable references against those specified in model // **THINK** due to GeNN's current string-based type system this is rather conservative - if(varRef.getVar().type != varRefs.at(i).type) { - throw std::runtime_error("Incompatible type for variable reference '" + getCustomUpdateModel()->getVarRefs().at(i).name + "'"); + if(varRef.getVar().type != modelVarRef.type) { + throw std::runtime_error("Incompatible type for variable reference '" + modelVarRef.name + "'"); + } + + // Check that no reduction targets reference duplicated variables + if((varRef.getVar().access & VarAccessDuplication::DUPLICATE) + && (modelVarRef.access & VarAccessModeAttribute::REDUCE)) + { + throw std::runtime_error("Reduction target variable reference must be to SHARED variables."); + } + + // If custom update is batched, check that any variable references to shared variables are read-only + // **NOTE** if custom update isn't batched, it's totally fine to write to shared variables + if(m_Batched && (varRef.getVar().access & VarAccessDuplication::SHARED) + && (modelVarRef.access != VarAccessMode::READ_ONLY)) + { + throw std::runtime_error("Variable references to SHARED variables in batched custom updates must be read-only."); } } } @@ -180,9 +152,6 @@ class GENN_EXPORT CustomUpdateBase //! Is this custom update batched i.e. run in parallel across model batches bool m_Batched; - - //! Does this custom update perform a reduction i.e. reduce some variables from DUPLICATE to SHARED - bool m_Reduction; }; //------------------------------------------------------------------------ @@ -206,7 +175,7 @@ class GENN_EXPORT CustomUpdate : public CustomUpdateBase //------------------------------------------------------------------------ // Protected methods //------------------------------------------------------------------------ - void finalize(unsigned int batchSize); + void finalize(); //------------------------------------------------------------------------ // Protected const methods @@ -247,10 +216,6 @@ class GENN_EXPORT CustomUpdateWU : public CustomUpdateBase const std::vector &varInitialisers, const std::vector &varReferences, VarLocation defaultVarLocation, VarLocation defaultExtraGlobalParamLocation); - //------------------------------------------------------------------------ - // Protected methods - //------------------------------------------------------------------------ - void finalize(unsigned int batchSize); //------------------------------------------------------------------------ // Protected const methods diff --git a/include/genn/genn/customUpdateInternal.h b/include/genn/genn/customUpdateInternal.h index b6cf707c2b..564a980aeb 100644 --- a/include/genn/genn/customUpdateInternal.h +++ b/include/genn/genn/customUpdateInternal.h @@ -55,7 +55,6 @@ class CustomUpdateWUInternal : public CustomUpdateWU using CustomUpdateBase::isReduction; using CustomUpdateBase::getVarLocationHashDigest; - using CustomUpdateWU::finalize; using CustomUpdateWU::getHashDigest; using CustomUpdateWU::getInitHashDigest; using CustomUpdateWU::getSynapseGroup; diff --git a/include/genn/genn/customUpdateModels.h b/include/genn/genn/customUpdateModels.h index 066a0f8d68..37858189de 100644 --- a/include/genn/genn/customUpdateModels.h +++ b/include/genn/genn/customUpdateModels.h @@ -42,6 +42,9 @@ class GENN_EXPORT Base : public Models::Base //! Update hash from model boost::uuids::detail::sha1::digest_type getHashDigest() const; + //! Is this custom update a reduction operation? + bool isReduction() const; + //! Validate names of parameters etc void validate() const; }; diff --git a/src/genn/backends/single_threaded_cpu/backend.cc b/src/genn/backends/single_threaded_cpu/backend.cc index b8f15efec9..8ea9add012 100644 --- a/src/genn/backends/single_threaded_cpu/backend.cc +++ b/src/genn/backends/single_threaded_cpu/backend.cc @@ -509,7 +509,7 @@ void Backend::genCustomUpdate(CodeStream &os, const ModelSpecMerged &modelMerged // Get reference to group os << "const auto *group = &mergedCustomUpdateGroup" << c.getIndex() << "[g]; " << std::endl; - genCustomUpdateIndexCalculation(os, c); + genCustomUpdateIndexCalculation(os, c, 1); // Loop through group members os << "for(unsigned int i = 0; i < group->size; i++)"; diff --git a/src/genn/genn/code_generator/backendBase.cc b/src/genn/genn/code_generator/backendBase.cc index 7d5202e943..dd841ec690 100644 --- a/src/genn/genn/code_generator/backendBase.cc +++ b/src/genn/genn/code_generator/backendBase.cc @@ -188,10 +188,10 @@ void CodeGenerator::BackendBase::genSynapseIndexCalculation(CodeStream &os, cons } } //----------------------------------------------------------------------- -void CodeGenerator::BackendBase::genCustomUpdateIndexCalculation(CodeStream &os, const CustomUpdateGroupMerged &cu) const +void CodeGenerator::BackendBase::genCustomUpdateIndexCalculation(CodeStream &os, const CustomUpdateGroupMerged &cu, unsigned int batchSize) const { // If batching is enabled, calculate batch offset - if(cu.getArchetype().isBatched()) { + if(cu.getArchetype().isBatched() && batchSize > 1) { os << "const unsigned int batchOffset = group->size * batch;" << std::endl; } @@ -201,7 +201,7 @@ void CodeGenerator::BackendBase::genCustomUpdateIndexCalculation(CodeStream &os, os << "const unsigned int delayOffset = (*group->spkQuePtr * group->size);" << std::endl; // If batching is also enabled, calculate offset including delay and batch - if(cu.getArchetype().isBatched()) { + if(cu.getArchetype().isBatched() && batchSize > 1) { os << "const unsigned int batchDelayOffset = delayOffset + (batchOffset * " << cu.getArchetype().getDelayNeuronGroup()->getNumDelaySlots() << ");" << std::endl; } } diff --git a/src/genn/genn/code_generator/backendSIMT.cc b/src/genn/genn/code_generator/backendSIMT.cc index 282004350c..cf8decb4ff 100644 --- a/src/genn/genn/code_generator/backendSIMT.cc +++ b/src/genn/genn/code_generator/backendSIMT.cc @@ -851,6 +851,7 @@ void BackendSIMT::genCustomUpdateKernel(CodeStream &os, const Substitutions &ker [&modelMerged, this, customUpdateHandler](CodeStream &os, const CustomUpdateGroupMerged &cg, Substitutions &popSubs) { const size_t blockSize = getKernelBlockSize(KernelCustomUpdate); + const unsigned int batchSize = modelMerged.getModel().getBatchSize(); // If update is a reduction Substitutions cuSubs(&popSubs); @@ -866,12 +867,12 @@ void BackendSIMT::genCustomUpdateKernel(CodeStream &os, const Substitutions &ker // Loop through batches // **TODO** this naive approach is good for reduction when there are lots of neurons/synapses but, // if this isn't the case (TF uses a threshold of 4096), we should do something smarter - os << "for(unsigned int batch = 0; batch < " << modelMerged.getModel().getBatchSize() << "; batch++)"; + os << "for(unsigned int batch = 0; batch < " << batchSize << "; batch++)"; { CodeStream::Scope b(os); cuSubs.addVarSubstitution("batch", "batch"); - genCustomUpdateIndexCalculation(os, cg); + genCustomUpdateIndexCalculation(os, cg, batchSize); customUpdateHandler(os, cg, cuSubs); // Loop through reduction targets and generate reduction @@ -888,7 +889,7 @@ void BackendSIMT::genCustomUpdateKernel(CodeStream &os, const Substitutions &ker } // Otherwise else { - if(cg.getArchetype().isBatched()) { + if(cg.getArchetype().isBatched() && batchSize > 1) { // Split ID into intra-batch ID and batch // **TODO** fast-divide style optimisations here os << "const unsigned int paddedSize = " << blockSize << " * ((group->size + " << blockSize << " - 1) / " << blockSize << ");" << std::endl; @@ -909,7 +910,7 @@ void BackendSIMT::genCustomUpdateKernel(CodeStream &os, const Substitutions &ker { CodeStream::Scope b(os); - genCustomUpdateIndexCalculation(os, cg); + genCustomUpdateIndexCalculation(os, cg, batchSize); customUpdateHandler(os, cg, cuSubs); } } @@ -931,6 +932,7 @@ void BackendSIMT::genCustomUpdateWUKernel(CodeStream &os, const Substitutions &k [customUpdateWUHandler, &modelMerged, this](CodeStream &os, const CustomUpdateWUGroupMerged &cg, Substitutions &popSubs) { const size_t blockSize = getKernelBlockSize(KernelCustomUpdate); + const unsigned int batchSize = modelMerged.getModel().getBatchSize(); // Calculate number of threads for update os << "const unsigned int size = group->numSrcNeurons * group->rowStride;" << std::endl; @@ -939,7 +941,7 @@ void BackendSIMT::genCustomUpdateWUKernel(CodeStream &os, const Substitutions &k Substitutions cuSubs(&popSubs); if(!cg.getArchetype().isReduction()) { // If it's batched - if(cg.getArchetype().isBatched()) { + if(cg.getArchetype().isBatched() && batchSize > 1) { os << "const unsigned int paddedSize = " << blockSize << " * ((size + " << blockSize << " - 1) / " << blockSize << ");" << std::endl; // Split ID into intra-batch ID and batch @@ -993,13 +995,13 @@ void BackendSIMT::genCustomUpdateWUKernel(CodeStream &os, const Substitutions &k // Loop through batches // **TODO** this naive approach is good for reduction when there are lots of neurons/synapses but, // if this isn't the case (TF uses a threshold of 4096), we should do something smarter - os << "for(unsigned int batch = 0; batch < " << modelMerged.getModel().getBatchSize() << "; batch++)"; + os << "for(unsigned int batch = 0; batch < " << batchSize << "; batch++)"; os << CodeStream::OB(1); cuSubs.addVarSubstitution("batch", "batch"); } // Calculate batch offset if required - if(cg.getArchetype().isBatched()) { + if(cg.getArchetype().isBatched() && batchSize > 1) { os << "const unsigned int batchOffset = size * batch;" << std::endl; } @@ -1045,6 +1047,7 @@ void BackendSIMT::genCustomTransposeUpdateWUKernel(CodeStream &os, const Substit std::find_if(cg.getArchetype().getVarReferences().cbegin(), cg.getArchetype().getVarReferences().cend(), [](const Models::WUVarReference &v) { return v.getTransposeSynapseGroup() != nullptr; })); const std::string transposeVarName = cg.getArchetype().getCustomUpdateModel()->getVarRefs().at(transposeVarIdx).name; + const unsigned int batchSize = modelMerged.getModel().getBatchSize(); // To allow these kernels to be batched, we turn 2D grid into wide 1D grid of 2D block so calculate size os << "const unsigned int numXBlocks = (group->numTrgNeurons + " << (blockSize - 1) << ") / " << blockSize << ";" << std::endl; @@ -1053,7 +1056,7 @@ void BackendSIMT::genCustomTransposeUpdateWUKernel(CodeStream &os, const Substit os << "const unsigned int blockStart = " << popSubs["group_start_id"] << " / " << blockSize << ";" << std::endl; Substitutions synSubs(&popSubs); - if(cg.getArchetype().isBatched()) { + if(cg.getArchetype().isBatched() && batchSize > 1) { // If there's multiple batches we also need to know how many Y blocks and hence total blocks there are os << "const unsigned int numYBlocks = (group->numSrcNeurons + " << (blockSize - 1) << ") / " << blockSize << ";" << std::endl; os << "const unsigned int numBlocks = numXBlocks * numYBlocks;" << std::endl; @@ -1132,7 +1135,7 @@ void BackendSIMT::genCustomTransposeUpdateWUKernel(CodeStream &os, const Substit { CodeStream::Scope b(os); os << "group->" << transposeVarName << "Transpose["; - if(cg.getArchetype().isBatched()) { + if(cg.getArchetype().isBatched() && batchSize > 1) { os << "batchOffset + "; } os << "((y + j) * group->numSrcNeurons) + x] = shTile[" << getThreadID(0) << "][" << getThreadID(1) << " + j];" << std::endl; diff --git a/src/genn/genn/customUpdate.cc b/src/genn/genn/customUpdate.cc index 6ab1e7f1d9..d1bbd8943a 100644 --- a/src/genn/genn/customUpdate.cc +++ b/src/genn/genn/customUpdate.cc @@ -69,7 +69,6 @@ void CustomUpdateBase::updateHash(boost::uuids::detail::sha1 &hash) const Utils::updateHash(getCustomUpdateModel()->getHashDigest(), hash); Utils::updateHash(getUpdateGroupName(), hash); Utils::updateHash(isBatched(), hash); - Utils::updateHash(isReduction(), hash); } //---------------------------------------------------------------------------- void CustomUpdateBase::updateInitHash(boost::uuids::detail::sha1 &hash) const @@ -106,7 +105,7 @@ CustomUpdate::CustomUpdate(const std::string &name, const std::string &updateGro } // Check variable reference types - checkVarReferenceTypes(m_VarReferences); + checkVarReferences(m_VarReferences); // Give error if any sizes differ if(std::any_of(m_VarReferences.cbegin(), m_VarReferences.cend(), @@ -116,12 +115,8 @@ CustomUpdate::CustomUpdate(const std::string &name, const std::string &updateGro } } //---------------------------------------------------------------------------- -void CustomUpdate::finalize(unsigned int batchSize) +void CustomUpdate::finalize() { - // Because batch size might be set at any point and which neuron - // variables are queued is only calculated during Modelspec::finalize, - // these checks cannot be performed in the constructor - // If any variable references have delays auto delayRef = std::find_if(m_VarReferences.cbegin(), m_VarReferences.cend(), [](const Models::VarReference &v) { return v.getDelayNeuronGroup() != nullptr; }); @@ -136,9 +131,6 @@ void CustomUpdate::finalize(unsigned int batchSize) throw std::runtime_error("Referenced variables with delays in custom update '" + getName() + "' must all refer to same neuron group."); } } - - // Determine whether custom update is batched - CustomUpdateBase::finalize(batchSize, m_VarReferences); } //---------------------------------------------------------------------------- boost::uuids::detail::sha1::digest_type CustomUpdate::getHashDigest() const @@ -186,7 +178,7 @@ CustomUpdateWU::CustomUpdateWU(const std::string &name, const std::string &updat } // Check variable reference types - checkVarReferenceTypes(m_VarReferences); + checkVarReferences(m_VarReferences); // Give error if references point to different synapse groups // **NOTE** this could be relaxed for dense @@ -222,11 +214,6 @@ CustomUpdateWU::CustomUpdateWU(const std::string &name, const std::string &updat } } //---------------------------------------------------------------------------- -void CustomUpdateWU::finalize(unsigned int batchSize) -{ - CustomUpdateBase::finalize(batchSize, m_VarReferences); -} -//---------------------------------------------------------------------------- bool CustomUpdateWU::isTransposeOperation() const { // Transpose opetation is required if any variable references have a transpose diff --git a/src/genn/genn/customUpdateModels.cc b/src/genn/genn/customUpdateModels.cc index 2d882a744b..21d125d05f 100644 --- a/src/genn/genn/customUpdateModels.cc +++ b/src/genn/genn/customUpdateModels.cc @@ -18,6 +18,17 @@ boost::uuids::detail::sha1::digest_type CustomUpdateModels::Base::getHashDigest( return hash.get_digest(); } //---------------------------------------------------------------------------- +bool CustomUpdateModels::Base::isReduction() const +{ + // Return true if any variables or variable references have REDUCE flag in their access mode + const auto vars = getVars(); + const auto varRefs = getVarRefs(); + return (std::any_of(vars.cbegin(), vars.cend(), + [](const Models::Base::Var &v) { return (v.access & VarAccessModeAttribute::REDUCE); }) + || std::any_of(varRefs.cbegin(), varRefs.cend(), + [](const Models::Base::VarRef &v) { return (v.access & VarAccessModeAttribute::REDUCE); })); +} +//---------------------------------------------------------------------------- void CustomUpdateModels::Base::validate() const { // Superclass diff --git a/src/genn/genn/modelSpec.cc b/src/genn/genn/modelSpec.cc index 0c9d34b72b..0e3fc77f01 100644 --- a/src/genn/genn/modelSpec.cc +++ b/src/genn/genn/modelSpec.cc @@ -175,13 +175,12 @@ void ModelSpec::finalize() // Custom update groups for(auto &c : m_CustomUpdates) { - c.second.finalize(getBatchSize()); + c.second.finalize(); c.second.initDerivedParams(m_DT); } // Custom WUM update groups for(auto &c : m_CustomWUUpdates) { - c.second.finalize(getBatchSize()); c.second.initDerivedParams(m_DT); } From 1a09636bbac98e85fe6b3ff4bf08ec098b04efb6 Mon Sep 17 00:00:00 2001 From: neworderofjamie Date: Thu, 5 Aug 2021 12:07:43 +0100 Subject: [PATCH 19/26] updated tests to reflect that we can now detect some errors in ``ModelSpec::addCustomUpdate`` rather than only when finalizing model (always a good thing) --- tests/unit/customUpdate.cc | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/tests/unit/customUpdate.cc b/tests/unit/customUpdate.cc index c1ce9c4bd2..11e106f618 100644 --- a/tests/unit/customUpdate.cc +++ b/tests/unit/customUpdate.cc @@ -280,11 +280,9 @@ TEST(CustomUpdates, BatchingWriteShared) // Create custom update which tries to create a read-write refernece to a (which isn't batched) Reduce::VarReferences reduceVarReferences(createVarRef(pop, "V"), createVarRef(pop, "U")); - model.addCustomUpdate("Sum1", "CustomUpdate", - {}, {}, reduceVarReferences); - try { - model.finalize(); + model.addCustomUpdate("Sum1", "CustomUpdate", + {}, {}, reduceVarReferences); FAIL(); } catch(const std::runtime_error &) { @@ -303,11 +301,9 @@ TEST(CustomUpdates, ReduceDuplicate) // Create custom update which tries to create a read-write refernece to a (which isn't batched) Sum2::VarValues sum2VarValues(1.0); Sum2::VarReferences sum2VarReferences(createVarRef(pop, "a"), createVarRef(pop, "V")); - model.addCustomUpdate("Sum1", "CustomUpdate", - {}, sum2VarValues, sum2VarReferences); - try { - model.finalize(); + model.addCustomUpdate("Sum1", "CustomUpdate", + {}, sum2VarValues, sum2VarReferences); FAIL(); } catch(const std::runtime_error &) { From f1a0d5902d7db5163b5511e9c574cea6d1ab2caf Mon Sep 17 00:00:00 2001 From: neworderofjamie Date: Thu, 5 Aug 2021 12:23:35 +0100 Subject: [PATCH 20/26] moved some generic reduction-handling code down from ``BackendSIMT`` into ``BackendBase`` --- .../genn/genn/code_generator/backendBase.h | 45 +++++++++++++++++++ src/genn/genn/code_generator/backendSIMT.cc | 41 +++-------------- 2 files changed, 52 insertions(+), 34 deletions(-) diff --git a/include/genn/genn/code_generator/backendBase.h b/include/genn/genn/code_generator/backendBase.h index f077787e20..4652a28327 100644 --- a/include/genn/genn/code_generator/backendBase.h +++ b/include/genn/genn/code_generator/backendBase.h @@ -18,6 +18,7 @@ #include "codeStream.h" #include "gennExport.h" #include "gennUtils.h" +#include "varAccess.h" #include "variableMode.h" // Forward declarations @@ -500,6 +501,22 @@ class GENN_EXPORT BackendBase const T &getPreferences() const { return static_cast(m_Preferences); } protected: + //-------------------------------------------------------------------------- + // ReductionTarget + //-------------------------------------------------------------------------- + //! Simple struct to hold reduction targets + struct ReductionTarget + { + ReductionTarget(const std::string &n, const std::string &t, VarAccessMode a) + : name(n), type(t), access(a) + { + } + + const std::string name; + const std::string type; + const VarAccessMode access; + }; + //-------------------------------------------------------------------------- // Protected API //-------------------------------------------------------------------------- @@ -520,6 +537,34 @@ class GENN_EXPORT BackendBase void genCustomUpdateIndexCalculation(CodeStream &os, const CustomUpdateGroupMerged &cu, unsigned int batchSize) const; + template + std::vector genInitReductionTargets(CodeStream &os, const G &cg) const + { + // Loop through variables + std::vector reductionTargets; + const auto *cm = cg.getArchetype().getCustomUpdateModel(); + for(const auto &v : cm->getVars()) { + // If variable is a reduction target, define variable initialised to correct initial value for reduction + // **NOTE** by not initialising this, compilers should emit a warning if user code doesn't set it to something + if(v.access & VarAccessModeAttribute::REDUCE) { + os << v.type << " lr" << v.name << " = " << getReductionInitialValue(*this, getVarAccessMode(v.access), v.type) << ";" << std::endl; + reductionTargets.emplace_back(v.name, v.type, getVarAccessMode(v.access)); + } + } + + // Loop through variable references + for(const auto &v : cm->getVarRefs()) { + // If variable reference is a reduction target, define variable initialised to correct initial value for reduction + // **NOTE** by not initialising this, compilers should emit a warning if user code doesn't set it to something + if(v.access & VarAccessModeAttribute::REDUCE) { + os << v.type << " lr" << v.name << " = " << getReductionInitialValue(*this, v.access, v.type) << ";" << std::endl; + reductionTargets.emplace_back(v.name, v.type, v.access); + } + } + return reductionTargets; + } + + private: //-------------------------------------------------------------------------- // Members diff --git a/src/genn/genn/code_generator/backendSIMT.cc b/src/genn/genn/code_generator/backendSIMT.cc index cf8decb4ff..f7b2ae3fc8 100644 --- a/src/genn/genn/code_generator/backendSIMT.cc +++ b/src/genn/genn/code_generator/backendSIMT.cc @@ -28,33 +28,6 @@ size_t getNumMergedGroupThreads(const std::vector &groups, G getNumThreads) }); }); } -//----------------------------------------------------------------------- -template -std::vector> initReductionTargets(CodeStream &os, const BackendBase &backend, const G &cg) -{ - // Loop through variables - std::vector> reductionTargets; - const auto *cm = cg.getArchetype().getCustomUpdateModel(); - for(const auto &v : cm->getVars()) { - // If variable is a reduction target, define variable initialised to correct initial value for reduction - // **NOTE** by not initialising this, compilers should emit a warning if user code doesn't set it to something - if(v.access & VarAccessModeAttribute::REDUCE) { - os << v.type << " lr" << v.name << " = " << getReductionInitialValue(backend, getVarAccessMode(v.access), v.type) << ";" << std::endl; - reductionTargets.emplace_back(v.name, v.type, getVarAccessMode(v.access)); - } - } - - // Loop through variable references - for(const auto &v : cm->getVarRefs()) { - // If variable reference is a reduction target, define variable initialised to correct initial value for reduction - // **NOTE** by not initialising this, compilers should emit a warning if user code doesn't set it to something - if(v.access & VarAccessModeAttribute::REDUCE) { - os << v.type << " lr" << v.name << " = " << getReductionInitialValue(backend, v.access, v.type) << ";" << std::endl; - reductionTargets.emplace_back(v.name, v.type, v.access); - } - } - return reductionTargets; -} } //-------------------------------------------------------------------------- @@ -862,7 +835,7 @@ void BackendSIMT::genCustomUpdateKernel(CodeStream &os, const Substitutions &ker CodeStream::Scope b(os); // Initialise reduction targets - const auto reductionTargets = initReductionTargets(os, *this, cg); + const auto reductionTargets = genInitReductionTargets(os, cg); // Loop through batches // **TODO** this naive approach is good for reduction when there are lots of neurons/synapses but, @@ -877,13 +850,13 @@ void BackendSIMT::genCustomUpdateKernel(CodeStream &os, const Substitutions &ker // Loop through reduction targets and generate reduction for(const auto &r : reductionTargets) { - os << getReductionOperation("lr" + std::get<0>(r), "l" + std::get<0>(r), std::get<2>(r), std::get<1>(r)) << ";" << std::endl; + os << getReductionOperation("lr" + r.name, "l" + r.name, r.access, r.type) << ";" << std::endl; } } // Loop through reduction targets and write reduced value back to memory for(const auto &r : reductionTargets) { - os << "group->" << std::get<0>(r) << "[" << cuSubs["id"] << "] = lr" << std::get<0>(r) << ";" << std::endl; + os << "group->" << r.name << "[" << cuSubs["id"] << "] = lr" << r.name << ";" << std::endl; } } } @@ -987,8 +960,8 @@ void BackendSIMT::genCustomUpdateWUKernel(CodeStream &os, const Substitutions &k } // Initialise reduction targets - const auto reductionTargets = (cg.getArchetype().isReduction() ? initReductionTargets(os, *this, cg) - : std::vector>{}); + const auto reductionTargets = (cg.getArchetype().isReduction() ? genInitReductionTargets(os, cg) + : std::vector{}); // If this is a reduction if(cg.getArchetype().isReduction()) { @@ -1011,7 +984,7 @@ void BackendSIMT::genCustomUpdateWUKernel(CodeStream &os, const Substitutions &k if(cg.getArchetype().isReduction()) { // Loop through reduction targets and generate reduction for(const auto &r : reductionTargets) { - os << getReductionOperation("lr" + std::get<0>(r), "l" + std::get<0>(r), std::get<2>(r), std::get<1>(r)) << ";" << std::endl; + os << getReductionOperation("lr" + r.name, "l" + r.name, r.access, r.type) << ";" << std::endl; } // End for loop through batches @@ -1019,7 +992,7 @@ void BackendSIMT::genCustomUpdateWUKernel(CodeStream &os, const Substitutions &k // Loop through reduction targets and write reduced value back to memory for(const auto &r : reductionTargets) { - os << "group->" << std::get<0>(r) << "[" << cuSubs["id_syn"] << "] = lr" << std::get<0>(r) << ";" << std::endl; + os << "group->" << r.name << "[" << cuSubs["id_syn"] << "] = lr" << r.name << ";" << std::endl; } } } From ca7988a5d4526c3abfc9743520304ddbe627d960 Mon Sep 17 00:00:00 2001 From: neworderofjamie Date: Thu, 5 Aug 2021 12:32:04 +0100 Subject: [PATCH 21/26] new test for reductions with batch size 1 (tests fallback for single-threaded CPU) --- .../custom_update_reduction_batch_one.sln | 30 +++++++ .../custom_update_reduction_batch_one.vcxproj | 63 ++++++++++++++ .../model.cc | 86 +++++++++++++++++++ .../runner_guid.txt | 1 + .../custom_update_reduction_batch_one/test.cc | 77 +++++++++++++++++ 5 files changed, 257 insertions(+) create mode 100644 tests/features/custom_update_reduction_batch_one/custom_update_reduction_batch_one.sln create mode 100644 tests/features/custom_update_reduction_batch_one/custom_update_reduction_batch_one.vcxproj create mode 100644 tests/features/custom_update_reduction_batch_one/model.cc create mode 100644 tests/features/custom_update_reduction_batch_one/runner_guid.txt create mode 100644 tests/features/custom_update_reduction_batch_one/test.cc diff --git a/tests/features/custom_update_reduction_batch_one/custom_update_reduction_batch_one.sln b/tests/features/custom_update_reduction_batch_one/custom_update_reduction_batch_one.sln new file mode 100644 index 0000000000..95a3beeb1f --- /dev/null +++ b/tests/features/custom_update_reduction_batch_one/custom_update_reduction_batch_one.sln @@ -0,0 +1,30 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.30501.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "custom_update_reduction_batch_one", "custom_update_reduction_batch_one.vcxproj", "{12A8594C-DB0D-4CBC-A46B-1B95374C48BC}" + ProjectSection(ProjectDependencies) = postProject + {A8C1EE07-9BBE-4EAD-82B8-8EF27D6B4913} = {A8C1EE07-9BBE-4EAD-82B8-8EF27D6B4913} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "runner", "custom_update_reduction_batch_one_CODE\runner.vcxproj", "{A8C1EE07-9BBE-4EAD-82B8-8EF27D6B4913}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {12A8594C-DB0D-4CBC-A46B-1B95374C48BC}.Debug|x64.ActiveCfg = Debug|x64 + {12A8594C-DB0D-4CBC-A46B-1B95374C48BC}.Debug|x64.Build.0 = Debug|x64 + {12A8594C-DB0D-4CBC-A46B-1B95374C48BC}.Release|x64.ActiveCfg = Release|x64 + {12A8594C-DB0D-4CBC-A46B-1B95374C48BC}.Release|x64.Build.0 = Release|x64 + {A8C1EE07-9BBE-4EAD-82B8-8EF27D6B4913}.Debug|x64.ActiveCfg = Debug|x64 + {A8C1EE07-9BBE-4EAD-82B8-8EF27D6B4913}.Debug|x64.Build.0 = Debug|x64 + {A8C1EE07-9BBE-4EAD-82B8-8EF27D6B4913}.Release|x64.ActiveCfg = Release|x64 + {A8C1EE07-9BBE-4EAD-82B8-8EF27D6B4913}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/tests/features/custom_update_reduction_batch_one/custom_update_reduction_batch_one.vcxproj b/tests/features/custom_update_reduction_batch_one/custom_update_reduction_batch_one.vcxproj new file mode 100644 index 0000000000..6eecddef74 --- /dev/null +++ b/tests/features/custom_update_reduction_batch_one/custom_update_reduction_batch_one.vcxproj @@ -0,0 +1,63 @@ + + + + + Debug + x64 + + + Release + x64 + + + + {12A8594C-DB0D-4CBC-A46B-1B95374C48BC} + + + + + + + + + Application + true + $(DefaultPlatformToolset) + true + MultiByte + + + + + + + + + + ./ + $(Platform)\$(Configuration)\ + test + + + + Level3 + MaxSpeed + Disabled + true + true + true + custom_update_reduction_batch_one_CODE;$(GTEST_DIR);$(GTEST_DIR)/include + _SILENCE_TR1_NAMESPACE_DEPRECATION_WARNING;_MBCS;%(PreprocessorDefinitions) + + + true + true + true + runner_Release.lib;%(AdditionalDependencies) + runner_Debug.lib;%(AdditionalDependencies) + + + + + + diff --git a/tests/features/custom_update_reduction_batch_one/model.cc b/tests/features/custom_update_reduction_batch_one/model.cc new file mode 100644 index 0000000000..531fe3854e --- /dev/null +++ b/tests/features/custom_update_reduction_batch_one/model.cc @@ -0,0 +1,86 @@ +//-------------------------------------------------------------------------- +/*! \file custom_update_reduction_batch_one/model.cc + +\brief model definition file that is part of the feature testing +suite of minimal models with known analytic outcomes that are used for continuous integration testing. +*/ +//-------------------------------------------------------------------------- + + +#include "modelSpec.h" + +class TestNeuron : public NeuronModels::Base +{ +public: + DECLARE_MODEL(TestNeuron, 0, 1); + + SET_VARS({{"V","scalar", VarAccess::READ_ONLY_DUPLICATE}}); +}; +IMPLEMENT_MODEL(TestNeuron); + +class TestWUM : public WeightUpdateModels::Base +{ +public: + DECLARE_WEIGHT_UPDATE_MODEL(TestWUM, 0, 1, 0, 0); + + SET_VARS({{"V", "scalar", VarAccess::READ_ONLY_DUPLICATE}}); +}; +IMPLEMENT_MODEL(TestWUM); + +class Reduce : public CustomUpdateModels::Base +{ +public: + DECLARE_CUSTOM_UPDATE_MODEL(Reduce, 0, 2, 1); + + SET_UPDATE_CODE( + "$(Sum) = $(V);\n" + "$(Max) = $(V);\n"); + + SET_VARS({{"Sum", "scalar", VarAccess::REDUCE_BATCH_SUM}, {"Max", "scalar", VarAccess::REDUCE_BATCH_MAX}}); + SET_VAR_REFS({{"V", "scalar", VarAccessMode::READ_ONLY}}) +}; +IMPLEMENT_MODEL(Reduce); + +void modelDefinition(ModelSpec &model) +{ +#ifdef CL_HPP_TARGET_OPENCL_VERSION + if(std::getenv("OPENCL_DEVICE") != nullptr) { + GENN_PREFERENCES.deviceSelectMethod = DeviceSelect::MANUAL; + GENN_PREFERENCES.manualDeviceID = std::atoi(std::getenv("OPENCL_DEVICE")); + } + if(std::getenv("OPENCL_PLATFORM") != nullptr) { + GENN_PREFERENCES.manualPlatformID = std::atoi(std::getenv("OPENCL_PLATFORM")); + } +#endif + model.setDT(1.0); + model.setName("custom_update_reduction_batch_one"); + + model.addNeuronPopulation("SpikeSource", 50, {}, {}); + auto *ng = model.addNeuronPopulation("Neuron", 50, {}, {uninitialisedVar()}); + auto *denseSG = model.addSynapsePopulation( + "Dense", SynapseMatrixType::DENSE_INDIVIDUALG, NO_DELAY, + "SpikeSource", "Neuron", + {}, {uninitialisedVar()}, + {}, {}); + auto *sparseSG = model.addSynapsePopulation( + "Sparse", SynapseMatrixType::SPARSE_INDIVIDUALG, NO_DELAY, + "SpikeSource", "Neuron", + {}, {uninitialisedVar()}, + {}, {}, + initConnectivity({0.1})); + + //--------------------------------------------------------------------------- + // Custom updates + //--------------------------------------------------------------------------- + Reduce::VarReferences neuronReduceVarReferences(createVarRef(ng, "V")); // V + model.addCustomUpdate("NeuronReduce", "Test", + {}, {0.0, 0.0}, neuronReduceVarReferences); + + Reduce::WUVarReferences wumDenseReduceVarReferences(createWUVarRef(denseSG, "V")); // V + model.addCustomUpdate("WUMDenseReduce", "Test", + {}, {0.0, 0.0}, wumDenseReduceVarReferences); + + Reduce::WUVarReferences wumSparseReduceVarReferences(createWUVarRef(sparseSG, "V")); // V + model.addCustomUpdate("WUMSparseReduce", "Test", + {}, {0.0, 0.0}, wumSparseReduceVarReferences); +} \ No newline at end of file diff --git a/tests/features/custom_update_reduction_batch_one/runner_guid.txt b/tests/features/custom_update_reduction_batch_one/runner_guid.txt new file mode 100644 index 0000000000..fa486a206d --- /dev/null +++ b/tests/features/custom_update_reduction_batch_one/runner_guid.txt @@ -0,0 +1 @@ +A8C1EE07-9BBE-4EAD-82B8-8EF27D6B4913 diff --git a/tests/features/custom_update_reduction_batch_one/test.cc b/tests/features/custom_update_reduction_batch_one/test.cc new file mode 100644 index 0000000000..52356c5cc1 --- /dev/null +++ b/tests/features/custom_update_reduction_batch_one/test.cc @@ -0,0 +1,77 @@ +//-------------------------------------------------------------------------- +/*! \file custom_update_reduction_batch_one/test.cc + +\brief Main test code that is part of the feature testing +suite of minimal models with known analytic outcomes that are used for continuous integration testing. +*/ +//-------------------------------------------------------------------------- +// Standard C++ includes +#include +#include + +// Google test includes +#include "gtest/gtest.h" + +// Auto-generated simulation code includess +#include "custom_update_reduction_batch_one_CODE/definitions.h" + +// **NOTE** base-class for simulation tests must be +// included after auto-generated globals are includes +#include "../../utils/simulation_test.h" + +//---------------------------------------------------------------------------- +// SimTest +//---------------------------------------------------------------------------- +class SimTest : public SimulationTest +{ +public: + virtual void Init() + { + // Initialise variables to reduce + std::iota(&VNeuron[0], &VNeuron[50], 0.0f); + std::iota(&VDense[0], &VDense[50 * 50], 0.0f); + + pullSparseConnectivityFromDevice(); + for(unsigned int i = 0; i < 50; i++) { + const unsigned rowStartIdx = i * maxRowLengthSparse; + for(unsigned int j = 0 ; j < rowLengthSparse[i]; j++) { + const unsigned int synIdx = rowStartIdx + j; + VSparse[synIdx] = (scalar)synIdx; + } + } + } +}; + +TEST_F(SimTest, CustomUpdateReductionBatchOne) +{ + // Launch reduction + updateTest(); + + // Download reductions + pullNeuronReduceStateFromDevice(); + pullWUMDenseReduceStateFromDevice(); + pullWUMSparseReduceStateFromDevice(); + + // Check neuron reductions + for(unsigned int i = 0; i < 50; i++) { + ASSERT_EQ(SumNeuronReduce[i], (float)i); + ASSERT_EQ(MaxNeuronReduce[i], (float)i); + } + + // Check dense weight reductions + for(unsigned int i = 0; i < (50 * 50); i++) { + ASSERT_EQ(SumWUMDenseReduce[i], (float)i); + ASSERT_EQ(MaxWUMDenseReduce[i], (float)i); + } + + // Check sparse weight reductions + for(unsigned int i = 0; i < 50; i++) { + const unsigned rowStartIdx = i * maxRowLengthSparse; + for(unsigned int j = 0 ; j < rowLengthSparse[i]; j++) { + const unsigned int synIdx = rowStartIdx + j; + ASSERT_EQ(SumWUMSparseReduce[synIdx], synIdx); + ASSERT_EQ(MaxWUMSparseReduce[synIdx], synIdx); + } + } +} + From 4d6c790295d73aaed62222d5ae8c719654bcdb08 Mon Sep 17 00:00:00 2001 From: neworderofjamie Date: Thu, 5 Aug 2021 12:43:40 +0100 Subject: [PATCH 22/26] simplification --- src/genn/genn/code_generator/backendSIMT.cc | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/genn/genn/code_generator/backendSIMT.cc b/src/genn/genn/code_generator/backendSIMT.cc index f7b2ae3fc8..d73a9d4880 100644 --- a/src/genn/genn/code_generator/backendSIMT.cc +++ b/src/genn/genn/code_generator/backendSIMT.cc @@ -960,8 +960,7 @@ void BackendSIMT::genCustomUpdateWUKernel(CodeStream &os, const Substitutions &k } // Initialise reduction targets - const auto reductionTargets = (cg.getArchetype().isReduction() ? genInitReductionTargets(os, cg) - : std::vector{}); + const auto reductionTargets = genInitReductionTargets(os, cg); // If this is a reduction if(cg.getArchetype().isReduction()) { @@ -992,7 +991,7 @@ void BackendSIMT::genCustomUpdateWUKernel(CodeStream &os, const Substitutions &k // Loop through reduction targets and write reduced value back to memory for(const auto &r : reductionTargets) { - os << "group->" << r.name << "[" << cuSubs["id_syn"] << "] = lr" << r.name << ";" << std::endl; + os << "group->" << r.name << "[" << cuSubs["id_syn"] << "] = lr" << r.name << ";" << std::endl; } } } From cbca852a36655063e1e0b8ae0e624ad54d12be79 Mon Sep 17 00:00:00 2001 From: neworderofjamie Date: Thu, 5 Aug 2021 12:45:33 +0100 Subject: [PATCH 23/26] actually BackendSIMT is a better place for ``genInitReductionTargets`` --- .../genn/genn/code_generator/backendBase.h | 45 ------------------ .../genn/genn/code_generator/backendSIMT.h | 46 +++++++++++++++++++ 2 files changed, 46 insertions(+), 45 deletions(-) diff --git a/include/genn/genn/code_generator/backendBase.h b/include/genn/genn/code_generator/backendBase.h index 4652a28327..f077787e20 100644 --- a/include/genn/genn/code_generator/backendBase.h +++ b/include/genn/genn/code_generator/backendBase.h @@ -18,7 +18,6 @@ #include "codeStream.h" #include "gennExport.h" #include "gennUtils.h" -#include "varAccess.h" #include "variableMode.h" // Forward declarations @@ -501,22 +500,6 @@ class GENN_EXPORT BackendBase const T &getPreferences() const { return static_cast(m_Preferences); } protected: - //-------------------------------------------------------------------------- - // ReductionTarget - //-------------------------------------------------------------------------- - //! Simple struct to hold reduction targets - struct ReductionTarget - { - ReductionTarget(const std::string &n, const std::string &t, VarAccessMode a) - : name(n), type(t), access(a) - { - } - - const std::string name; - const std::string type; - const VarAccessMode access; - }; - //-------------------------------------------------------------------------- // Protected API //-------------------------------------------------------------------------- @@ -537,34 +520,6 @@ class GENN_EXPORT BackendBase void genCustomUpdateIndexCalculation(CodeStream &os, const CustomUpdateGroupMerged &cu, unsigned int batchSize) const; - template - std::vector genInitReductionTargets(CodeStream &os, const G &cg) const - { - // Loop through variables - std::vector reductionTargets; - const auto *cm = cg.getArchetype().getCustomUpdateModel(); - for(const auto &v : cm->getVars()) { - // If variable is a reduction target, define variable initialised to correct initial value for reduction - // **NOTE** by not initialising this, compilers should emit a warning if user code doesn't set it to something - if(v.access & VarAccessModeAttribute::REDUCE) { - os << v.type << " lr" << v.name << " = " << getReductionInitialValue(*this, getVarAccessMode(v.access), v.type) << ";" << std::endl; - reductionTargets.emplace_back(v.name, v.type, getVarAccessMode(v.access)); - } - } - - // Loop through variable references - for(const auto &v : cm->getVarRefs()) { - // If variable reference is a reduction target, define variable initialised to correct initial value for reduction - // **NOTE** by not initialising this, compilers should emit a warning if user code doesn't set it to something - if(v.access & VarAccessModeAttribute::REDUCE) { - os << v.type << " lr" << v.name << " = " << getReductionInitialValue(*this, v.access, v.type) << ";" << std::endl; - reductionTargets.emplace_back(v.name, v.type, v.access); - } - } - return reductionTargets; - } - - private: //-------------------------------------------------------------------------- // Members diff --git a/include/genn/genn/code_generator/backendSIMT.h b/include/genn/genn/code_generator/backendSIMT.h index 575a770ace..2756c31e85 100644 --- a/include/genn/genn/code_generator/backendSIMT.h +++ b/include/genn/genn/code_generator/backendSIMT.h @@ -7,6 +7,7 @@ // GeNN includes #include "gennExport.h" +#include "varAccess.h" // GeNN code generator includes #include "code_generator/backendBase.h" @@ -224,6 +225,23 @@ class GENN_EXPORT BackendSIMT : public BackendBase const KernelBlockSize &getKernelBlockSize() const { return m_KernelBlockSizes; } private: + //-------------------------------------------------------------------------- + // ReductionTarget + //-------------------------------------------------------------------------- + //! Simple struct to hold reduction targets + struct ReductionTarget + { + ReductionTarget(const std::string &n, const std::string &t, VarAccessMode a) + : name(n), type(t), access(a) + { + } + + const std::string name; + const std::string type; + const VarAccessMode access; + }; + + //-------------------------------------------------------------------------- // Type definitions //-------------------------------------------------------------------------- @@ -311,6 +329,34 @@ class GENN_EXPORT BackendSIMT : public BackendBase } } + + template + std::vector genInitReductionTargets(CodeStream &os, const G &cg) const + { + // Loop through variables + std::vector reductionTargets; + const auto *cm = cg.getArchetype().getCustomUpdateModel(); + for(const auto &v : cm->getVars()) { + // If variable is a reduction target, define variable initialised to correct initial value for reduction + // **NOTE** by not initialising this, compilers should emit a warning if user code doesn't set it to something + if(v.access & VarAccessModeAttribute::REDUCE) { + os << v.type << " lr" << v.name << " = " << getReductionInitialValue(*this, getVarAccessMode(v.access), v.type) << ";" << std::endl; + reductionTargets.emplace_back(v.name, v.type, getVarAccessMode(v.access)); + } + } + + // Loop through variable references + for(const auto &v : cm->getVarRefs()) { + // If variable reference is a reduction target, define variable initialised to correct initial value for reduction + // **NOTE** by not initialising this, compilers should emit a warning if user code doesn't set it to something + if(v.access & VarAccessModeAttribute::REDUCE) { + os << v.type << " lr" << v.name << " = " << getReductionInitialValue(*this, v.access, v.type) << ";" << std::endl; + reductionTargets.emplace_back(v.name, v.type, v.access); + } + } + return reductionTargets; + } + template void genParallelGroup(CodeStream &os, const Substitutions &kernelSubs, const std::vector &groups, size_t &idStart, S getPaddedSizeFunc, GroupHandler handler) const From 5fe2b8585126c3fb585d8e9c6c33bfed2db232fe Mon Sep 17 00:00:00 2001 From: neworderofjamie Date: Thu, 5 Aug 2021 12:55:24 +0100 Subject: [PATCH 24/26] implemented single-threaded CPU non-reduction --- .../backends/single_threaded_cpu/backend.h | 23 +++++++++++++++++++ .../backends/single_threaded_cpu/backend.cc | 7 ++++++ 2 files changed, 30 insertions(+) diff --git a/include/genn/backends/single_threaded_cpu/backend.h b/include/genn/backends/single_threaded_cpu/backend.h index 9f8ec684e5..7e15c8344b 100644 --- a/include/genn/backends/single_threaded_cpu/backend.h +++ b/include/genn/backends/single_threaded_cpu/backend.h @@ -7,6 +7,7 @@ // GeNN includes #include "backendExport.h" +#include "varAccess.h" // GeNN code generator includes #include "code_generator/backendBase.h" @@ -198,6 +199,28 @@ class BACKEND_EXPORT Backend : public BackendBase } } } + + //! Helper to generate code to copy reduced variables back to variables + /*! Because reduction operations are unnecessary in unbatched single-threaded CPU models so there's no need to actually reduce */ + template + void genWriteBackReductions(CodeStream &os, const G &cg, const std::string &idx) const + { + const auto *cm = cg.getArchetype().getCustomUpdateModel(); + for(const auto &v : cm->getVars()) { + // If variable is a reduction target, copy value from register straight back into global memory + if(v.access & VarAccessModeAttribute::REDUCE) { + os << "group->" << v.name << "[" << idx << "] = l" << v.name << ";" << std::endl; + } + } + + // Loop through variable references + for(const auto &v : cm->getVarRefs()) { + // If variable reference is a reduction target, copy value from register straight back into global memory + if(v.access & VarAccessModeAttribute::REDUCE) { + os << "group->" << v.name << "[" << idx<< "] = l" << v.name << ";" << std::endl; + } + } + } }; } // namespace SingleThreadedCPU } // namespace CodeGenerator diff --git a/src/genn/backends/single_threaded_cpu/backend.cc b/src/genn/backends/single_threaded_cpu/backend.cc index 8ea9add012..721f25f469 100644 --- a/src/genn/backends/single_threaded_cpu/backend.cc +++ b/src/genn/backends/single_threaded_cpu/backend.cc @@ -519,7 +519,11 @@ void Backend::genCustomUpdate(CodeStream &os, const ModelSpecMerged &modelMerged Substitutions popSubs(&funcSubs); popSubs.addVarSubstitution("id", "i"); + // Generate custom update customUpdateHandler(os, c, popSubs); + + // Write back reductions + genWriteBackReductions(os, c, popSubs["id"]); } } } @@ -576,6 +580,9 @@ void Backend::genCustomUpdate(CodeStream &os, const ModelSpecMerged &modelMerged // Call custom update handler customWUUpdateHandler(os, c, synSubs); + + // Write back reductions + genWriteBackReductions(os, c, synSubs["id_syn"]); } } } From 0191f8ff77b9784572e22ba75ebd089180cc9682 Mon Sep 17 00:00:00 2001 From: neworderofjamie Date: Thu, 5 Aug 2021 16:22:21 +0100 Subject: [PATCH 25/26] additional ``WUVarReference`` constructors and ``createWUVarRef`` wrappers to handle transposes involving custom WU update variablesadd additional error to prevent reduction and transpose operations being attempted simultaneously --- src/genn/genn/customUpdate.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/genn/genn/customUpdate.cc b/src/genn/genn/customUpdate.cc index d1bbd8943a..68f6ec0400 100644 --- a/src/genn/genn/customUpdate.cc +++ b/src/genn/genn/customUpdate.cc @@ -193,6 +193,11 @@ CustomUpdateWU::CustomUpdateWU(const std::string &name, const std::string &updat // If this is a transpose operation if(isTransposeOperation()) { + // Check that it isn't also a reduction + if(getCustomUpdateModel()->isReduction()) { + throw std::runtime_error("Custom updates cannot perform both transpose and reduction operations."); + } + // Give error if any of the variable references aren't dense // **NOTE** there's no reason NOT to implement sparse transpose if(std::any_of(m_VarReferences.cbegin(), m_VarReferences.cend(), From b45882fb2ad735519e813a2e18790ddd053fbffb Mon Sep 17 00:00:00 2001 From: neworderofjamie Date: Wed, 11 Aug 2021 12:12:24 +0100 Subject: [PATCH 26/26] fixed comments --- include/genn/genn/code_generator/backendSIMT.h | 2 -- src/genn/genn/code_generator/generateCustomUpdate.cc | 4 ++++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/include/genn/genn/code_generator/backendSIMT.h b/include/genn/genn/code_generator/backendSIMT.h index 2756c31e85..1c0de8db1e 100644 --- a/include/genn/genn/code_generator/backendSIMT.h +++ b/include/genn/genn/code_generator/backendSIMT.h @@ -338,7 +338,6 @@ class GENN_EXPORT BackendSIMT : public BackendBase const auto *cm = cg.getArchetype().getCustomUpdateModel(); for(const auto &v : cm->getVars()) { // If variable is a reduction target, define variable initialised to correct initial value for reduction - // **NOTE** by not initialising this, compilers should emit a warning if user code doesn't set it to something if(v.access & VarAccessModeAttribute::REDUCE) { os << v.type << " lr" << v.name << " = " << getReductionInitialValue(*this, getVarAccessMode(v.access), v.type) << ";" << std::endl; reductionTargets.emplace_back(v.name, v.type, getVarAccessMode(v.access)); @@ -348,7 +347,6 @@ class GENN_EXPORT BackendSIMT : public BackendBase // Loop through variable references for(const auto &v : cm->getVarRefs()) { // If variable reference is a reduction target, define variable initialised to correct initial value for reduction - // **NOTE** by not initialising this, compilers should emit a warning if user code doesn't set it to something if(v.access & VarAccessModeAttribute::REDUCE) { os << v.type << " lr" << v.name << " = " << getReductionInitialValue(*this, v.access, v.type) << ";" << std::endl; reductionTargets.emplace_back(v.name, v.type, v.access); diff --git a/src/genn/genn/code_generator/generateCustomUpdate.cc b/src/genn/genn/code_generator/generateCustomUpdate.cc index 09274b3c35..096d355340 100644 --- a/src/genn/genn/code_generator/generateCustomUpdate.cc +++ b/src/genn/genn/code_generator/generateCustomUpdate.cc @@ -42,6 +42,8 @@ void genCustomUpdate(CodeStream &os, Substitutions &baseSubs, const C &cg, os << v.type << " l" << v.name; // If this isn't a reduction, read value from memory + // **NOTE** by not initialising these variables for reductions, + // compilers SHOULD emit a warning if user code doesn't set it to something if(!(v.access & VarAccessModeAttribute::REDUCE)) { os << " = group->" << v.name << "["; os << cg.getVarIndex(modelMerged.getModel().getBatchSize(), @@ -61,6 +63,8 @@ void genCustomUpdate(CodeStream &os, Substitutions &baseSubs, const C &cg, os << varRefs[i].type << " l" << varRefs[i].name; // If this isn't a reduction, read value from memory + // **NOTE** by not initialising these variables for reductions, + // compilers SHOULD emit a warning if user code doesn't set it to something if(!(varRefs[i].access & VarAccessModeAttribute::REDUCE)) { os << " = " << "group->" << varRefs[i].name << "["; os << getVarRefIndex(cg.getArchetype().getVarReferences().at(i),