diff --git a/include/genn/backends/single_threaded_cpu/backend.h b/include/genn/backends/single_threaded_cpu/backend.h index 83065ff25b..4867bbc576 100644 --- a/include/genn/backends/single_threaded_cpu/backend.h +++ b/include/genn/backends/single_threaded_cpu/backend.h @@ -94,7 +94,8 @@ class BACKEND_EXPORT Backend : public BackendBase const Substitutions &kernelSubs, Handler handler) const override; virtual void genSparseSynapseVariableRowInit(CodeStream &os, const Substitutions &kernelSubs, Handler handler) const override; virtual void genDenseSynapseVariableRowInit(CodeStream &os, const Substitutions &kernelSubs, Handler handler) const override; - virtual void genKernelSynapseVariableInit(CodeStream &os, const SynapseKernelInitGroupMerged &sg, const Substitutions &kernelSubs, Handler handler) const final; + virtual void genKernelSynapseVariableInit(CodeStream &os, const SynapseInitGroupMerged &sg, const Substitutions &kernelSubs, Handler handler) const final; + virtual void genKernelCustomUpdateVariableInit(CodeStream &os, const CustomWUUpdateInitGroupMerged &cu, const Substitutions &kernelSubs, Handler handler) const final; virtual void genVariablePush(CodeStream &os, const std::string &type, const std::string &name, VarLocation loc, bool autoInitialized, size_t count) const override; virtual void genVariablePull(CodeStream &os, const std::string &type, const std::string &name, VarLocation loc, size_t count) const override; diff --git a/include/genn/genn/code_generator/backendBase.h b/include/genn/genn/code_generator/backendBase.h index 6d07ac2fb7..9c77473ea4 100644 --- a/include/genn/genn/code_generator/backendBase.h +++ b/include/genn/genn/code_generator/backendBase.h @@ -40,12 +40,11 @@ namespace CodeGenerator class CustomUpdateTransposeWUGroupMerged; class NeuronInitGroupMerged; class CustomUpdateInitGroupMerged; - class CustomWUUpdateDenseInitGroupMerged; + class CustomWUUpdateInitGroupMerged; class CustomWUUpdateSparseInitGroupMerged; class SynapseConnectivityInitGroupMerged; - class SynapseDenseInitGroupMerged; + class SynapseInitGroupMerged; class SynapseSparseInitGroupMerged; - class SynapseKernelInitGroupMerged; } @@ -282,7 +281,8 @@ class GENN_EXPORT BackendBase const Substitutions &kernelSubs, Handler handler) const = 0; virtual void genSparseSynapseVariableRowInit(CodeStream &os, const Substitutions &kernelSubs, Handler handler) const = 0; virtual void genDenseSynapseVariableRowInit(CodeStream &os, const Substitutions &kernelSubs, Handler handler) const = 0; - virtual void genKernelSynapseVariableInit(CodeStream &os, const SynapseKernelInitGroupMerged &sg, const Substitutions &kernelSubs, Handler handler) const = 0; + virtual void genKernelSynapseVariableInit(CodeStream &os, const SynapseInitGroupMerged &sg, const Substitutions &kernelSubs, Handler handler) const = 0; + virtual void genKernelCustomUpdateVariableInit(CodeStream &os, const CustomWUUpdateInitGroupMerged &cu, const Substitutions &kernelSubs, Handler handler) const = 0; //! Generate code for pushing a variable to the 'device' virtual void genVariablePush(CodeStream &os, const std::string &type, const std::string &name, VarLocation loc, bool autoInitialized, size_t count) const = 0; diff --git a/include/genn/genn/code_generator/backendSIMT.h b/include/genn/genn/code_generator/backendSIMT.h index 9e1e0c6e30..72ac098edb 100644 --- a/include/genn/genn/code_generator/backendSIMT.h +++ b/include/genn/genn/code_generator/backendSIMT.h @@ -134,7 +134,8 @@ class GENN_EXPORT BackendSIMT : public BackendBase genSynapseVariableRowInit(os, kernelSubs, handler); } - virtual void genKernelSynapseVariableInit(CodeStream &os, const SynapseKernelInitGroupMerged &sg, const Substitutions &kernelSubs, Handler handler) const final; + virtual void genKernelSynapseVariableInit(CodeStream &os, const SynapseInitGroupMerged &sg, const Substitutions &kernelSubs, Handler handler) const final; + virtual void genKernelCustomUpdateVariableInit(CodeStream &os, const CustomWUUpdateInitGroupMerged &cu, const Substitutions &kernelSubs, Handler handler) const final; //! Should 'scalar' variables be implemented on device or can host variables be used directly? virtual bool isDeviceScalarRequired() const final { return true; } @@ -165,6 +166,8 @@ class GENN_EXPORT BackendSIMT : public BackendBase static size_t getNumPostsynapticUpdateThreads(const SynapseGroupInternal &sg); static size_t getNumSynapseDynamicsThreads(const SynapseGroupInternal &sg); static size_t getNumConnectivityInitThreads(const SynapseGroupInternal &sg); + static size_t getNumInitThreads(const SynapseGroupInternal &sg); + static size_t getNumInitThreads(const CustomUpdateWUInternal &cg); //! Register a new presynaptic update strategy /*! This function should be called with strategies in ascending order of preference */ @@ -321,6 +324,14 @@ class GENN_EXPORT BackendSIMT : public BackendBase } + template + void genParallelGroup(CodeStream &os, const Substitutions &kernelSubs, const std::vector &groups, size_t &idStart, + S getPaddedSizeFunc, GroupHandler handler) const + { + genParallelGroup(os, kernelSubs, groups, idStart, getPaddedSizeFunc, + [](const T &) { return true; }, handler); + } + template std::vector genInitReductionTargets(CodeStream &os, const G &cg) const { @@ -345,13 +356,138 @@ class GENN_EXPORT BackendSIMT : public BackendBase } return reductionTargets; } + + // Helper function to generate kernel code to initialise variables associated with synapse group or custom WU update with dense/kernel connectivity + template + void genSynapseVarInit(CodeStream &os, const ModelSpecMerged &modelMerged, const G &g, Substitutions &popSubs, + bool initRNGRequired, bool kernel, size_t kernelDimensions) const + { + os << "if(" << popSubs["id"] << " < "; + + // If synapse group has kernel weights, check ID against product of kernel dimensions + if (kernel) { + // Loop through kernel dimensions and multiply together + os << "("; + for (size_t i = 0; i < kernelDimensions; i++) { + os << g.getKernelSize(i); + if (i != (kernelDimensions - 1)) { + os << " * "; + } + } + os << ")"; + } + // Otherwise, against number of postsynaptic neurons + else { + os << "group->numTrgNeurons"; + } + os << ")"; + { + CodeStream::Scope b(os); + + // If an RNG is required for initialisation, + // make copy of global phillox RNG and skip ahead by thread id + // **NOTE** not LOCAL id + if(initRNGRequired) { + genGlobalRNGSkipAhead(os, popSubs, "id"); + } - template - void genParallelGroup(CodeStream &os, const Substitutions &kernelSubs, const std::vector &groups, size_t &idStart, - S getPaddedSizeFunc, GroupHandler handler) const + // If synapse group has kernel weights + if (kernel) { + // Loop through kernel dimensions to generate seperate indices + for (size_t i = 0; i < kernelDimensions; i++) { + os << "const unsigned int kernelID" << i << " = (" << popSubs["id"]; + + // If this isn't the last dimension + if (i < (kernelDimensions - 1)) { + // Loop backwards through other kernel and generate code to divide by product of subsequent dimensions + os << " / ("; + for (size_t j = (kernelDimensions - 1); j > i; j--) { + os << g.getKernelSize(j); + + if (j != (i + 1)) { + os << " * "; + } + } + os << ")"; + } + os << ")"; + + // If this isn't the first dimension, take modulus of kernel size + if (i > 0) { + os << " % " << g.getKernelSize(i); + } + + os << ";" << std::endl; + + // Add substitution + popSubs.addVarSubstitution("id_kernel_" + std::to_string(i), "kernelID" + std::to_string(i)); + } + } + // Otherwise, just substitute postsynaptic index + else { + popSubs.addVarSubstitution("id_post", popSubs["id"]); + } + + // Generate init code + g.generateInit(*this, os, modelMerged, popSubs); + } + } + + // Helper function to generate kernel code to initialise variables associated with synapse group or custom WU update with sparse connectivity + template + void genSparseSynapseVarInit(CodeStream &os, const ModelSpecMerged &modelMerged, const G &g, Substitutions &popSubs, + bool varInitRequired, GroupHandler handler) const { - genParallelGroup(os, kernelSubs, groups, idStart, getPaddedSizeFunc, - [](const T &) { return true; }, handler); + // Calculate how many blocks rows need to be processed in (in order to store row lengths in shared memory) + const size_t blockSize = getKernelBlockSize(KernelInitializeSparse); + os << "const unsigned int numBlocks = (group->numSrcNeurons + " << blockSize << " - 1) / " << blockSize << ";" << std::endl; + + os << "unsigned int idx = " << popSubs["id"] << ";" << std::endl; + + // Loop through blocks + os << "for(unsigned int r = 0; r < numBlocks; r++)"; + { + CodeStream::Scope b(os); + + // Calculate number of rows to process in this block + os << "const unsigned numRowsInBlock = (r == (numBlocks - 1))"; + os << " ? ((group->numSrcNeurons - 1) % " << blockSize << ") + 1"; + os << " : " << blockSize << ";" << std::endl; + + // Use threads to copy block of sparse structure into shared memory + genSharedMemBarrier(os); + os << "if (" << getThreadID() << " < numRowsInBlock)"; + { + CodeStream::Scope b(os); + os << "shRowLength[" << getThreadID() << "] = group->rowLength[(r * " << blockSize << ") + " << getThreadID() << "];" << std::endl; + } + genSharedMemBarrier(os); + + // Loop through rows + os << "for(unsigned int i = 0; i < numRowsInBlock; i++)"; + { + CodeStream::Scope b(os); + + // If there is a synapse for this thread to initialise + os << "if(" << popSubs["id"] << " < shRowLength[i])"; + { + CodeStream::Scope b(os); + + // Generate initialisation code + if(varInitRequired) { + popSubs.addVarSubstitution("id_pre", "((r * " + std::to_string(blockSize) + ") + i)"); + popSubs.addVarSubstitution("id_post", "group->ind[idx]"); + g.generateInit(*this, os, modelMerged, popSubs); + } + + // Call handler + handler(os, g, popSubs); + } + + // If matrix is ragged, advance index to next row by adding stride + os << "idx += group->rowStride;" << std::endl; + } + } } void genEmitSpike(CodeStream &os, const Substitutions &subs, const std::string &suffix, bool recordingEnabled) const; diff --git a/include/genn/genn/code_generator/codeGenUtils.h b/include/genn/genn/code_generator/codeGenUtils.h index a754205b1d..60711f87f3 100644 --- a/include/genn/genn/code_generator/codeGenUtils.h +++ b/include/genn/genn/code_generator/codeGenUtils.h @@ -147,4 +147,53 @@ void neuronSubstitutionsInSynapticCode(CodeGenerator::Substitutions &substitutio // Substitute extra global parameters from neuron model substitutions.addVarNameSubstitution(nm->getExtraGlobalParams(), sourceSuffix, "group->", destSuffix); } + +template +bool isKernelSizeHeterogeneous(const G *group, size_t dimensionIndex, K getKernelSizeFn) +{ + // Get size of this kernel dimension for archetype + const unsigned archetypeValue = getKernelSizeFn(group->getArchetype()).at(dimensionIndex); + + // Return true if any of the other groups have a different value + return std::any_of(group->getGroups().cbegin(), group->getGroups().cend(), + [archetypeValue, dimensionIndex, getKernelSizeFn] + (const typename G::GroupInternal& g) + { + return (getKernelSizeFn(g).at(dimensionIndex) != archetypeValue); + }); +} + +template +std::string getKernelSize(const G *group, size_t dimensionIndex, K getKernelSizeFn) +{ + // If kernel size if heterogeneous in this dimension, return group structure entry + if (isKernelSizeHeterogeneous(group, dimensionIndex, getKernelSizeFn)) { + return "group->kernelSize" + std::to_string(dimensionIndex); + } + // Otherwise, return literal + else { + return std::to_string(getKernelSizeFn(group->getArchetype()).at(dimensionIndex)); + } +} + +template +void genKernelIndex(const G *group, std::ostream &os, const CodeGenerator::Substitutions &subs, + K getKernelSizeFn) +{ + // Loop through kernel dimensions to calculate array index + const auto &kernelSize = getKernelSizeFn(group->getArchetype()); + for (size_t i = 0; i < kernelSize.size(); i++) { + os << "(" << subs["id_kernel_" + std::to_string(i)]; + // Loop through remainining dimensions of kernel and multiply + for (size_t j = i + 1; j < kernelSize.size(); j++) { + os << " * " << getKernelSize(group, j, getKernelSizeFn); + } + os << ")"; + + // If this isn't the last dimension, add + + if (i != (kernelSize.size() - 1)) { + os << " + "; + } + } +} } // namespace CodeGenerator diff --git a/include/genn/genn/code_generator/customUpdateGroupMerged.h b/include/genn/genn/code_generator/customUpdateGroupMerged.h index 07f6488ed9..796852e258 100644 --- a/include/genn/genn/code_generator/customUpdateGroupMerged.h +++ b/include/genn/genn/code_generator/customUpdateGroupMerged.h @@ -1,6 +1,7 @@ #pragma once // GeNN code generator includes +#include "code_generator/codeGenUtils.h" #include "code_generator/groupMerged.h" //---------------------------------------------------------------------------- @@ -61,10 +62,33 @@ class GENN_EXPORT CustomUpdateWUGroupMergedBase : public GroupMergedkernelSizeXXX) + std::string getKernelSize(size_t dimensionIndex) const + { + return CodeGenerator::getKernelSize(this, dimensionIndex, getGroupKernelSize); + } + + //! Generate an index into a kernel based on the id_kernel_XXX variables in subs + void genKernelIndex(std::ostream& os, const CodeGenerator::Substitutions& subs) const + { + return CodeGenerator::genKernelIndex(this, os, subs, getGroupKernelSize); + } + protected: CustomUpdateWUGroupMergedBase(size_t index, const std::string &precision, const std::string &, const BackendBase &backend, const std::vector> &groups); +private: + static const std::vector& getGroupKernelSize(const CustomUpdateWUInternal& g) + { + return g.getSynapseGroup()->getKernelSize(); + } }; // ---------------------------------------------------------------------------- diff --git a/include/genn/genn/code_generator/groupMerged.h b/include/genn/genn/code_generator/groupMerged.h index 3b730305f4..2caada1795 100644 --- a/include/genn/genn/code_generator/groupMerged.h +++ b/include/genn/genn/code_generator/groupMerged.h @@ -1049,13 +1049,22 @@ class GENN_EXPORT SynapseGroupMergedBase : public GroupMergedkernelSizeXXX) - std::string getKernelSize(size_t dimensionIndex) const; + std::string getKernelSize(size_t dimensionIndex) const + { + return CodeGenerator::getKernelSize(this, dimensionIndex, getGroupKernelSize); + } //! Generate an index into a kernel based on the id_kernel_XXX variables in subs - void genKernelIndex(std::ostream &os, const CodeGenerator::Substitutions &subs) const; + void genKernelIndex(std::ostream& os, const CodeGenerator::Substitutions& subs) const + { + return CodeGenerator::genKernelIndex(this, os, subs, getGroupKernelSize); + } std::string getPreSlot(unsigned int batchSize) const; std::string getPostSlot(unsigned int batchSize) const; @@ -1113,9 +1122,8 @@ class GENN_EXPORT SynapseGroupMergedBase : public GroupMerged& getGroupKernelSize(const SynapseGroupInternal& g) + { + return g.getKernelSize(); + } + //------------------------------------------------------------------------ // Members //------------------------------------------------------------------------ diff --git a/include/genn/genn/code_generator/initGroupMerged.h b/include/genn/genn/code_generator/initGroupMerged.h index 22c512c863..a5a95af53b 100644 --- a/include/genn/genn/code_generator/initGroupMerged.h +++ b/include/genn/genn/code_generator/initGroupMerged.h @@ -95,19 +95,19 @@ class GENN_EXPORT NeuronInitGroupMerged : public NeuronGroupMergedBase //---------------------------------------------------------------------------- -// CodeGenerator::SynapseDenseInitGroupMerged +// CodeGenerator::SynapseInitGroupMerged //---------------------------------------------------------------------------- -class GENN_EXPORT SynapseDenseInitGroupMerged : public SynapseGroupMergedBase +class GENN_EXPORT SynapseInitGroupMerged : public SynapseGroupMergedBase { public: - SynapseDenseInitGroupMerged(size_t index, const std::string &precision, const std::string &timePrecision, const BackendBase &backend, + SynapseInitGroupMerged(size_t index, const std::string &precision, const std::string &timePrecision, const BackendBase &backend, const std::vector> &groups) - : SynapseGroupMergedBase(index, precision, timePrecision, backend, SynapseGroupMergedBase::Role::DenseInit, "", groups) + : SynapseGroupMergedBase(index, precision, timePrecision, backend, SynapseGroupMergedBase::Role::Init, "", groups) {} boost::uuids::detail::sha1::digest_type getHashDigest() const { - return SynapseGroupMergedBase::getHashDigest(SynapseGroupMergedBase::Role::DenseInit); + return SynapseGroupMergedBase::getHashDigest(SynapseGroupMergedBase::Role::Init); } void generateRunner(const BackendBase &backend, CodeStream &definitionsInternal, @@ -158,39 +158,6 @@ class GENN_EXPORT SynapseSparseInitGroupMerged : public SynapseGroupMergedBase static const std::string name; }; -//---------------------------------------------------------------------------- -// CodeGenerator::SynapseKernelInitGroupMerged -//---------------------------------------------------------------------------- -class GENN_EXPORT SynapseKernelInitGroupMerged : public SynapseGroupMergedBase -{ -public: - SynapseKernelInitGroupMerged(size_t index, const std::string &precision, const std::string &timePrecision, const BackendBase &backend, - const std::vector> &groups) - : SynapseGroupMergedBase(index, precision, timePrecision, backend, SynapseGroupMergedBase::Role::KernelInit, "", groups) - {} - - boost::uuids::detail::sha1::digest_type getHashDigest() const - { - return SynapseGroupMergedBase::getHashDigest(SynapseGroupMergedBase::Role::KernelInit); - } - - void generateRunner(const BackendBase &backend, CodeStream &definitionsInternal, - CodeStream &definitionsInternalFunc, CodeStream &definitionsInternalVar, - CodeStream &runnerVarDecl, CodeStream &runnerMergedStructAlloc) const - { - generateRunnerBase(backend, definitionsInternal, definitionsInternalFunc, definitionsInternalVar, - runnerVarDecl, runnerMergedStructAlloc, name); - } - - void generateInit(const BackendBase &backend, CodeStream &os, const ModelSpecMerged &modelMerged, Substitutions &popSubs) const; - - //---------------------------------------------------------------------------- - // Static constants - //---------------------------------------------------------------------------- - static const std::string name; -}; - - // ---------------------------------------------------------------------------- // CodeGenerator::SynapseConnectivityInitGroupMerged //---------------------------------------------------------------------------- @@ -357,13 +324,13 @@ class GENN_EXPORT CustomUpdateInitGroupMerged : public CustomUpdateInitGroupMerg // ---------------------------------------------------------------------------- -// CodeGenerator::CustomWUUpdateDenseInitGroupMerged +// CodeGenerator::CustomWUUpdateInitGroupMerged //---------------------------------------------------------------------------- -class GENN_EXPORT CustomWUUpdateDenseInitGroupMerged : public CustomUpdateInitGroupMergedBase +class GENN_EXPORT CustomWUUpdateInitGroupMerged : public CustomUpdateInitGroupMergedBase { public: - CustomWUUpdateDenseInitGroupMerged(size_t index, const std::string &precision, const std::string &, const BackendBase &backend, - const std::vector> &groups); + CustomWUUpdateInitGroupMerged(size_t index, const std::string &precision, const std::string &, const BackendBase &backend, + const std::vector> &groups); //---------------------------------------------------------------------------- // Public API @@ -380,10 +347,37 @@ class GENN_EXPORT CustomWUUpdateDenseInitGroupMerged : public CustomUpdateInitGr void generateInit(const BackendBase &backend, CodeStream &os, const ModelSpecMerged &modelMerged, Substitutions &popSubs) const; + //! Is kernel size heterogeneous in this dimension? + bool isKernelSizeHeterogeneous(size_t dimensionIndex) const + { + return CodeGenerator::isKernelSizeHeterogeneous(this, dimensionIndex, getGroupKernelSize); + } + + //! Get expression for kernel size in dimension (may be literal or group->kernelSizeXXX) + std::string getKernelSize(size_t dimensionIndex) const + { + return CodeGenerator::getKernelSize(this, dimensionIndex, getGroupKernelSize); + } + + //! Generate an index into a kernel based on the id_kernel_XXX variables in subs + void genKernelIndex(std::ostream &os, const CodeGenerator::Substitutions &subs) const + { + return CodeGenerator::genKernelIndex(this, os, subs, getGroupKernelSize); + } + //---------------------------------------------------------------------------- // Static constants //---------------------------------------------------------------------------- static const std::string name; + +private: + //---------------------------------------------------------------------------- + // Private static methods + //---------------------------------------------------------------------------- + static const std::vector &getGroupKernelSize(const CustomUpdateWUInternal &g) + { + return g.getSynapseGroup()->getKernelSize(); + } }; // ---------------------------------------------------------------------------- @@ -415,5 +409,4 @@ class GENN_EXPORT CustomWUUpdateSparseInitGroupMerged : public CustomUpdateInitG //---------------------------------------------------------------------------- static const std::string name; }; - } // namespace CodeGenerator diff --git a/include/genn/genn/code_generator/modelSpecMerged.h b/include/genn/genn/code_generator/modelSpecMerged.h index 53f5fdad3e..6b35a46b3d 100644 --- a/include/genn/genn/code_generator/modelSpecMerged.h +++ b/include/genn/genn/code_generator/modelSpecMerged.h @@ -119,19 +119,16 @@ class GENN_EXPORT ModelSpecMerged const std::vector &getMergedCustomUpdateInitGroups() const { return m_MergedCustomUpdateInitGroups; } //! Get merged custom updategroups with dense connectivity which require initialisation - const std::vector &getMergedCustomWUUpdateDenseInitGroups() const { return m_MergedCustomWUUpdateDenseInitGroups; } + const std::vector &getMergedCustomWUUpdateInitGroups() const { return m_MergedCustomWUUpdateInitGroups; } //! Get merged synapse groups with dense connectivity which require initialisation - const std::vector &getMergedSynapseDenseInitGroups() const{ return m_MergedSynapseDenseInitGroups; } + const std::vector &getMergedSynapseInitGroups() const{ return m_MergedSynapseInitGroups; } //! Get merged synapse groups which require connectivity initialisation const std::vector &getMergedSynapseConnectivityInitGroups() const{ return m_MergedSynapseConnectivityInitGroups; } //! Get merged synapse groups with sparse connectivity which require initialisation const std::vector &getMergedSynapseSparseInitGroups() const{ return m_MergedSynapseSparseInitGroups; } - - //! Get merged synapse groups with kernel connectivity which require initialisation - const std::vector &getMergedSynapseKernelInitGroups() const{ return m_MergedSynapseKernelInitGroups; } //! Get merged custom update groups with sparse connectivity which require initialisation const std::vector &getMergedCustomWUUpdateSparseInitGroups() const { return m_MergedCustomWUUpdateSparseInitGroups; } @@ -169,11 +166,10 @@ class GENN_EXPORT ModelSpecMerged void genMergedSynapseDynamicsGroupStructs(CodeStream &os, const BackendBase &backend) const { genMergedStructures(os, backend, m_MergedSynapseDynamicsGroups); } void genMergedNeuronInitGroupStructs(CodeStream &os, const BackendBase &backend) const { genMergedStructures(os, backend, m_MergedNeuronInitGroups); } void genMergedCustomUpdateInitGroupStructs(CodeStream &os, const BackendBase &backend) const { genMergedStructures(os, backend, m_MergedCustomUpdateInitGroups); } - void genMergedCustomWUUpdateDenseInitGroupStructs(CodeStream &os, const BackendBase &backend) const { genMergedStructures(os, backend, m_MergedCustomWUUpdateDenseInitGroups); } - void genMergedSynapseDenseInitGroupStructs(CodeStream &os, const BackendBase &backend) const { genMergedStructures(os, backend, m_MergedSynapseDenseInitGroups); } + void genMergedCustomWUUpdateInitGroupStructs(CodeStream &os, const BackendBase &backend) const { genMergedStructures(os, backend, m_MergedCustomWUUpdateInitGroups); } + void genMergedSynapseInitGroupStructs(CodeStream &os, const BackendBase &backend) const { genMergedStructures(os, backend, m_MergedSynapseInitGroups); } void genMergedSynapseConnectivityInitGroupStructs(CodeStream &os, const BackendBase &backend) const { genMergedStructures(os, backend, m_MergedSynapseConnectivityInitGroups); } void genMergedSynapseSparseInitGroupStructs(CodeStream &os, const BackendBase &backend) const { genMergedStructures(os, backend, m_MergedSynapseSparseInitGroups); } - void genMergedSynapseKernelInitGroupStructs(CodeStream &os, const BackendBase &backend) const { genMergedStructures(os, backend, m_MergedSynapseKernelInitGroups); } void genMergedCustomWUUpdateSparseInitGroupStructs(CodeStream &os, const BackendBase &backend) const { genMergedStructures(os, backend, m_MergedCustomWUUpdateSparseInitGroups); } void genMergedNeuronSpikeQueueUpdateStructs(CodeStream &os, const BackendBase &backend) const{ genMergedStructures(os, backend, m_MergedNeuronSpikeQueueUpdateGroups); } void genMergedNeuronPrevSpikeTimeUpdateStructs(CodeStream &os, const BackendBase &backend) const{ genMergedStructures(os, backend, m_MergedNeuronPrevSpikeTimeUpdateGroups); } @@ -389,20 +385,17 @@ class GENN_EXPORT ModelSpecMerged //! Merged custom update groups which require initialisation std::vector m_MergedCustomUpdateInitGroups; - //! Merged custom update groups with dense connectivity which require initialisation - std::vector m_MergedCustomWUUpdateDenseInitGroups; + //! Merged custom update weight update groups which require initialisation + std::vector m_MergedCustomWUUpdateInitGroups; //! Merged synapse groups with dense connectivity which require initialisation - std::vector m_MergedSynapseDenseInitGroups; + std::vector m_MergedSynapseInitGroups; //! Merged synapse groups which require connectivity initialisation std::vector m_MergedSynapseConnectivityInitGroups; //! Merged synapse groups with sparse connectivity which require initialisation std::vector m_MergedSynapseSparseInitGroups; - - //! Merged synapse groups with kernel connectivity which require initialisation - std::vector m_MergedSynapseKernelInitGroups; //! Merged custom update groups with sparse connectivity which require initialisation std::vector m_MergedCustomWUUpdateSparseInitGroups; diff --git a/src/genn/backends/cuda/backend.cc b/src/genn/backends/cuda/backend.cc index a0fda62d27..88d3fe85fc 100644 --- a/src/genn/backends/cuda/backend.cc +++ b/src/genn/backends/cuda/backend.cc @@ -857,22 +857,20 @@ void Backend::genInit(CodeStream &os, const ModelSpecMerged &modelMerged, // Generate struct definitions modelMerged.genMergedNeuronInitGroupStructs(os, *this); - modelMerged.genMergedCustomUpdateInitGroupStructs(os, *this); - modelMerged.genMergedCustomWUUpdateDenseInitGroupStructs(os, *this); - modelMerged.genMergedSynapseDenseInitGroupStructs(os, *this); - modelMerged.genMergedSynapseKernelInitGroupStructs(os, *this); + modelMerged.genMergedSynapseInitGroupStructs(os, *this); modelMerged.genMergedSynapseConnectivityInitGroupStructs(os, *this); modelMerged.genMergedSynapseSparseInitGroupStructs(os, *this); + modelMerged.genMergedCustomUpdateInitGroupStructs(os, *this); + modelMerged.genMergedCustomWUUpdateInitGroupStructs(os, *this); modelMerged.genMergedCustomWUUpdateSparseInitGroupStructs(os, *this); // Generate arrays of merged structs and functions to push them genMergedStructArrayPush(os, modelMerged.getMergedNeuronInitGroups()); - genMergedStructArrayPush(os, modelMerged.getMergedCustomUpdateInitGroups()); - genMergedStructArrayPush(os, modelMerged.getMergedCustomWUUpdateDenseInitGroups()); - genMergedStructArrayPush(os, modelMerged.getMergedSynapseDenseInitGroups()); - genMergedStructArrayPush(os, modelMerged.getMergedSynapseKernelInitGroups()); + genMergedStructArrayPush(os, modelMerged.getMergedSynapseInitGroups()); genMergedStructArrayPush(os, modelMerged.getMergedSynapseConnectivityInitGroups()); genMergedStructArrayPush(os, modelMerged.getMergedSynapseSparseInitGroups()); + genMergedStructArrayPush(os, modelMerged.getMergedCustomUpdateInitGroups()); + genMergedStructArrayPush(os, modelMerged.getMergedCustomWUUpdateInitGroups()); genMergedStructArrayPush(os, modelMerged.getMergedCustomWUUpdateSparseInitGroups()); // Generate preamble @@ -885,10 +883,9 @@ void Backend::genInit(CodeStream &os, const ModelSpecMerged &modelMerged, genMergedKernelDataStructures( os, totalConstMem, modelMerged.getMergedNeuronInitGroups(), [this](const NeuronGroupInternal &ng){ return padKernelSize(ng.getNumNeurons(), KernelInitialize); }, + modelMerged.getMergedSynapseInitGroups(), [this](const SynapseGroupInternal &sg){ return padKernelSize(getNumInitThreads(sg), KernelInitialize); }, modelMerged.getMergedCustomUpdateInitGroups(), [this](const CustomUpdateInternal &cg) { return padKernelSize(cg.getSize(), KernelInitialize); }, - modelMerged.getMergedCustomWUUpdateDenseInitGroups(), [this](const CustomUpdateWUInternal &cg){ return padKernelSize(cg.getSynapseGroup()->getTrgNeuronGroup()->getNumNeurons(), KernelInitialize); }, - modelMerged.getMergedSynapseDenseInitGroups(), [this](const SynapseGroupInternal &sg){ return padKernelSize(sg.getTrgNeuronGroup()->getNumNeurons(), KernelInitialize); }, - modelMerged.getMergedSynapseKernelInitGroups(), [this](const SynapseGroupInternal &sg){ return padKernelSize(sg.getKernelSizeFlattened(), KernelInitialize); }, + modelMerged.getMergedCustomWUUpdateInitGroups(), [this](const CustomUpdateWUInternal &cg){ return padKernelSize(getNumInitThreads(cg), KernelInitialize); }, modelMerged.getMergedSynapseConnectivityInitGroups(), [this](const SynapseGroupInternal &sg){ return padKernelSize(getNumConnectivityInitThreads(sg), KernelInitialize); }); // Generate data structure for accessing merged groups from within sparse initialisation kernel diff --git a/src/genn/backends/opencl/backend.cc b/src/genn/backends/opencl/backend.cc index 9a2d968f46..af04a65152 100644 --- a/src/genn/backends/opencl/backend.cc +++ b/src/genn/backends/opencl/backend.cc @@ -1081,10 +1081,9 @@ void Backend::genInit(CodeStream &os, const ModelSpecMerged &modelMerged, os << "cl::Kernel " << KernelNames[KernelInitialize] << ";" << std::endl; os << "cl::Kernel " << KernelNames[KernelInitializeSparse] << ";" << std::endl; genMergedStructPreamble(os, modelMerged, modelMerged.getMergedNeuronInitGroups()); + genMergedStructPreamble(os, modelMerged, modelMerged.getMergedSynapseInitGroups()); genMergedStructPreamble(os, modelMerged, modelMerged.getMergedCustomUpdateInitGroups()); - genMergedStructPreamble(os, modelMerged, modelMerged.getMergedCustomWUUpdateDenseInitGroups()); - genMergedStructPreamble(os, modelMerged, modelMerged.getMergedSynapseDenseInitGroups()); - genMergedStructPreamble(os, modelMerged, modelMerged.getMergedSynapseKernelInitGroups()); + genMergedStructPreamble(os, modelMerged, modelMerged.getMergedCustomWUUpdateInitGroups()); genMergedStructPreamble(os, modelMerged, modelMerged.getMergedSynapseConnectivityInitGroups()); genMergedStructPreamble(os, modelMerged, modelMerged.getMergedSynapseSparseInitGroups()); genMergedStructPreamble(os, modelMerged, modelMerged.getMergedCustomWUUpdateSparseInitGroups()); @@ -1108,10 +1107,9 @@ void Backend::genInit(CodeStream &os, const ModelSpecMerged &modelMerged, // Generate struct definitions modelMerged.genMergedNeuronInitGroupStructs(initializeKernels, *this); + modelMerged.genMergedSynapseInitGroupStructs(initializeKernels, *this); modelMerged.genMergedCustomUpdateInitGroupStructs(initializeKernels, *this); - modelMerged.genMergedCustomWUUpdateDenseInitGroupStructs(initializeKernels, *this); - modelMerged.genMergedSynapseDenseInitGroupStructs(initializeKernels, *this); - modelMerged.genMergedSynapseKernelInitGroupStructs(initializeKernels, *this); + modelMerged.genMergedCustomWUUpdateInitGroupStructs(initializeKernels, *this); modelMerged.genMergedSynapseConnectivityInitGroupStructs(initializeKernels, *this); modelMerged.genMergedSynapseSparseInitGroupStructs(initializeKernels, *this); modelMerged.genMergedCustomWUUpdateSparseInitGroupStructs(initializeKernels, *this); @@ -1121,10 +1119,9 @@ void Backend::genInit(CodeStream &os, const ModelSpecMerged &modelMerged, genMergedKernelDataStructures( initializeKernels, modelMerged.getMergedNeuronInitGroups(), [this](const NeuronGroupInternal &ng) { return padKernelSize(ng.getNumNeurons(), KernelInitialize); }, + modelMerged.getMergedSynapseInitGroups(), [this](const SynapseGroupInternal &sg) { return padKernelSize(getNumInitThreads(sg), KernelInitialize); }, modelMerged.getMergedCustomUpdateInitGroups(), [this](const CustomUpdateInternal &cg) { return padKernelSize(cg.getSize(), KernelInitialize); }, - modelMerged.getMergedCustomWUUpdateDenseInitGroups(), [this](const CustomUpdateWUInternal &cg) { return padKernelSize(cg.getSynapseGroup()->getTrgNeuronGroup()->getNumNeurons(), KernelInitialize); }, - modelMerged.getMergedSynapseDenseInitGroups(), [this](const SynapseGroupInternal &sg) { return padKernelSize(sg.getTrgNeuronGroup()->getNumNeurons(), KernelInitialize); }, - modelMerged.getMergedSynapseKernelInitGroups(), [this](const SynapseGroupInternal &sg) { return padKernelSize(sg.getKernelSizeFlattened(), KernelInitialize); }, + modelMerged.getMergedCustomWUUpdateInitGroups(), [this](const CustomUpdateWUInternal &cg) { return padKernelSize(getNumInitThreads(cg), KernelInitialize); }, modelMerged.getMergedSynapseConnectivityInitGroups(), [this](const SynapseGroupInternal &sg) { return padKernelSize(getNumConnectivityInitThreads(sg), KernelInitialize); }); // Generate data structure for accessing merged groups from within sparse initialisation kernel @@ -1136,10 +1133,9 @@ void Backend::genInit(CodeStream &os, const ModelSpecMerged &modelMerged, // Generate kernels used to populate merged structs genMergedStructBuildKernels(initializeKernels, modelMerged, modelMerged.getMergedNeuronInitGroups()); + genMergedStructBuildKernels(initializeKernels, modelMerged, modelMerged.getMergedSynapseInitGroups()); genMergedStructBuildKernels(initializeKernels, modelMerged, modelMerged.getMergedCustomUpdateInitGroups()); - genMergedStructBuildKernels(initializeKernels, modelMerged, modelMerged.getMergedCustomWUUpdateDenseInitGroups()); - genMergedStructBuildKernels(initializeKernels, modelMerged, modelMerged.getMergedSynapseDenseInitGroups()); - genMergedStructBuildKernels(initializeKernels, modelMerged, modelMerged.getMergedSynapseKernelInitGroups()); + genMergedStructBuildKernels(initializeKernels, modelMerged, modelMerged.getMergedCustomWUUpdateInitGroups()); genMergedStructBuildKernels(initializeKernels, modelMerged, modelMerged.getMergedSynapseConnectivityInitGroups()); genMergedStructBuildKernels(initializeKernels, modelMerged, modelMerged.getMergedSynapseSparseInitGroups()); genMergedStructBuildKernels(initializeKernels, modelMerged, modelMerged.getMergedCustomWUUpdateSparseInitGroups()); @@ -1149,18 +1145,17 @@ void Backend::genInit(CodeStream &os, const ModelSpecMerged &modelMerged, bool globalRNGRequired = isGlobalDeviceRNGRequired(modelMerged); bool additionalParamsRequired = globalRNGRequired || shouldUseSubBufferAllocations(); const bool anyCustomUpdateInitGroups = !modelMerged.getMergedCustomUpdateInitGroups().empty(); - const bool anyCustomWUUpdateDenseInitGroups = !modelMerged.getMergedCustomWUUpdateDenseInitGroups().empty(); - const bool anyDenseInitGroups = !modelMerged.getMergedSynapseDenseInitGroups().empty(); - const bool anyKernelInitGroups = !modelMerged.getMergedSynapseKernelInitGroups().empty(); + const bool anyCustomWUUpdateInitGroups = !modelMerged.getMergedCustomWUUpdateInitGroups().empty(); + const bool anySynapseInitGroups = !modelMerged.getMergedSynapseInitGroups().empty(); const bool anyConnectivityInitGroups = !modelMerged.getMergedSynapseConnectivityInitGroups().empty(); genMergedGroupKernelParams(initializeKernels, modelMerged.getMergedNeuronInitGroups(), - anyCustomUpdateInitGroups || anyCustomWUUpdateDenseInitGroups || anyDenseInitGroups || anyKernelInitGroups || anyConnectivityInitGroups || additionalParamsRequired); + anySynapseInitGroups || anyCustomUpdateInitGroups || anyCustomWUUpdateInitGroups || anyConnectivityInitGroups || additionalParamsRequired); + genMergedGroupKernelParams(initializeKernels, modelMerged.getMergedSynapseInitGroups(), + anyCustomUpdateInitGroups || anyCustomWUUpdateInitGroups || anyConnectivityInitGroups || additionalParamsRequired); genMergedGroupKernelParams(initializeKernels, modelMerged.getMergedCustomUpdateInitGroups(), - anyCustomWUUpdateDenseInitGroups || anyDenseInitGroups || anyKernelInitGroups || anyConnectivityInitGroups || additionalParamsRequired); - genMergedGroupKernelParams(initializeKernels, modelMerged.getMergedCustomWUUpdateDenseInitGroups(), - anyDenseInitGroups || anyKernelInitGroups || anyConnectivityInitGroups || additionalParamsRequired); - genMergedGroupKernelParams(initializeKernels, modelMerged.getMergedSynapseDenseInitGroups(), anyKernelInitGroups || anyConnectivityInitGroups || additionalParamsRequired); - genMergedGroupKernelParams(initializeKernels, modelMerged.getMergedSynapseKernelInitGroups(), anyConnectivityInitGroups || additionalParamsRequired); + anyCustomWUUpdateInitGroups || anyConnectivityInitGroups || additionalParamsRequired); + genMergedGroupKernelParams(initializeKernels, modelMerged.getMergedCustomWUUpdateInitGroups(), + anyConnectivityInitGroups || additionalParamsRequired); genMergedGroupKernelParams(initializeKernels, modelMerged.getMergedSynapseConnectivityInitGroups(), additionalParamsRequired); if(globalRNGRequired) { initializeKernels << "__global clrngPhilox432HostStream *d_rng"; @@ -1233,10 +1228,9 @@ void Backend::genInit(CodeStream &os, const ModelSpecMerged &modelMerged, os << "// Configure merged struct building kernels" << std::endl; genMergedStructBuild(os, modelMerged, modelMerged.getMergedNeuronInitGroups(), "initializeProgram"); + genMergedStructBuild(os, modelMerged, modelMerged.getMergedSynapseInitGroups(), "initializeProgram"); genMergedStructBuild(os, modelMerged, modelMerged.getMergedCustomUpdateInitGroups(), "initializeProgram"); - genMergedStructBuild(os, modelMerged, modelMerged.getMergedCustomWUUpdateDenseInitGroups(), "initializeProgram"); - genMergedStructBuild(os, modelMerged, modelMerged.getMergedSynapseDenseInitGroups(), "initializeProgram"); - genMergedStructBuild(os, modelMerged, modelMerged.getMergedSynapseKernelInitGroups(), "initializeProgram"); + genMergedStructBuild(os, modelMerged, modelMerged.getMergedCustomWUUpdateInitGroups(), "initializeProgram"); genMergedStructBuild(os, modelMerged, modelMerged.getMergedSynapseConnectivityInitGroups(), "initializeProgram"); genMergedStructBuild(os, modelMerged, modelMerged.getMergedSynapseSparseInitGroups(), "initializeProgram"); genMergedStructBuild(os, modelMerged, modelMerged.getMergedCustomWUUpdateSparseInitGroups(), "initializeProgram"); @@ -1248,10 +1242,9 @@ void Backend::genInit(CodeStream &os, const ModelSpecMerged &modelMerged, os << "CHECK_OPENCL_ERRORS_POINTER(" << KernelNames[KernelInitialize] << " = cl::Kernel(initializeProgram, \"" << KernelNames[KernelInitialize] << "\", &error));" << std::endl; size_t start = 0; setMergedGroupKernelParams(os, KernelNames[KernelInitialize], modelMerged.getMergedNeuronInitGroups(), start); + setMergedGroupKernelParams(os, KernelNames[KernelInitialize], modelMerged.getMergedSynapseInitGroups(), start); setMergedGroupKernelParams(os, KernelNames[KernelInitialize], modelMerged.getMergedCustomUpdateInitGroups(), start); - setMergedGroupKernelParams(os, KernelNames[KernelInitialize], modelMerged.getMergedCustomWUUpdateDenseInitGroups(), start); - setMergedGroupKernelParams(os, KernelNames[KernelInitialize], modelMerged.getMergedSynapseDenseInitGroups(), start); - setMergedGroupKernelParams(os, KernelNames[KernelInitialize], modelMerged.getMergedSynapseKernelInitGroups(), start); + setMergedGroupKernelParams(os, KernelNames[KernelInitialize], modelMerged.getMergedCustomWUUpdateInitGroups(), start); setMergedGroupKernelParams(os, KernelNames[KernelInitialize], modelMerged.getMergedSynapseConnectivityInitGroups(), start); if(shouldUseSubBufferAllocations()) { os << "CHECK_OPENCL_ERRORS(" << KernelNames[KernelInitialize] << ".setArg(" << (start + (globalRNGRequired ? 1 : 0)) << ", d_staticBuffer));" << std::endl; @@ -1311,9 +1304,9 @@ void Backend::genInit(CodeStream &os, const ModelSpecMerged &modelMerged, CodeStream::Scope b(os); genKernelDimensions(os, KernelInitialize, idInitStart, 1); if(globalRNGRequired) { - const size_t numInitGroups = (modelMerged.getMergedNeuronInitGroups().size() + modelMerged.getMergedCustomUpdateInitGroups().size() + - modelMerged.getMergedCustomWUUpdateDenseInitGroups().size() + modelMerged.getMergedSynapseDenseInitGroups().size() + - modelMerged.getMergedSynapseKernelInitGroups().size() + modelMerged.getMergedSynapseConnectivityInitGroups().size()); + const size_t numInitGroups = (modelMerged.getMergedNeuronInitGroups().size() + modelMerged.getMergedSynapseInitGroups().size() + + modelMerged.getMergedCustomUpdateInitGroups().size() + modelMerged.getMergedCustomWUUpdateInitGroups().size() + + modelMerged.getMergedSynapseConnectivityInitGroups().size()); os << "CHECK_OPENCL_ERRORS(" << KernelNames[KernelInitialize] << ".setArg(" << numInitGroups << ", d_rng));" << std::endl; } diff --git a/src/genn/backends/single_threaded_cpu/backend.cc b/src/genn/backends/single_threaded_cpu/backend.cc index 335f23b4e2..790d107674 100644 --- a/src/genn/backends/single_threaded_cpu/backend.cc +++ b/src/genn/backends/single_threaded_cpu/backend.cc @@ -71,7 +71,49 @@ const std::vector &getFunctionTemplates(const s { return (precision == "double") ? cpuDoublePrecisionFunctions : cpuSinglePrecisionFunctions; } +//----------------------------------------------------------------------- +template +void genKernelIteration(CodeStream &os, const G &g, size_t numKernelDims, const Substitutions &kernelSubs, BackendBase::Handler handler) +{ + Substitutions varSubs(&kernelSubs); + + // Define recursive function to generate nested kernel initialisation loops + // **NOTE** this is a std::function as type of auto lambda couldn't be determined inside for recursive call + std::function generateRecursive = + [&handler, &os, &g, &varSubs, &generateRecursive, numKernelDims] + (size_t depth) + { + // Loop through this kernel dimensions + const std::string idxVar = "k" + std::to_string(depth); + os << "for(unsigned int " << idxVar << " = 0; " << idxVar << " < " << g.getKernelSize(depth) << "; " << idxVar << "++)"; + { + CodeStream::Scope b(os); + + // Add substitution for this kernel index + varSubs.addVarSubstitution("id_kernel_" + std::to_string(depth), idxVar); + + // If we've recursed through all dimensions + if (depth == (numKernelDims - 1)) { + // Generate kernel index and use as "synapse" index + // **TODO** rename + os << "const unsigned int kernelInd = "; + g.genKernelIndex(os, varSubs); + os << ";" << std::endl; + varSubs.addVarSubstitution("id_syn", "kernelInd"); + + // Call handler + handler(os, varSubs); + } + // Otherwise, recurse + else { + generateRecursive(depth + 1); + } + } + }; + // Generate loops through kernel indices recursively + generateRecursive(0); +} } //-------------------------------------------------------------------------- @@ -550,45 +592,60 @@ void Backend::genCustomUpdate(CodeStream &os, const ModelSpecMerged &modelMerged // Get reference to group os << "const auto *group = &mergedCustomUpdateWUGroup" << c.getIndex() << "[g]; " << std::endl; - // Loop through presynaptic neurons - os << "for(unsigned int i = 0; i < group->numSrcNeurons; i++)"; - { - // If this synapse group has sparse connectivity, loop through length of this row - CodeStream::Scope b(os); - if(c.getArchetype().getSynapseGroup()->getMatrixType() & SynapseMatrixConnectivity::SPARSE) { - os << "for(unsigned int s = 0; s < group->rowLength[i]; s++)"; - } - // Otherwise, if it's dense, loop through each postsynaptic neuron - else if(c.getArchetype().getSynapseGroup()->getMatrixType() & SynapseMatrixConnectivity::DENSE) { - os << "for (unsigned int j = 0; j < group->numTrgNeurons; j++)"; - } - else { - throw std::runtime_error("Only DENSE and SPARSE format connectivity can be used for custom updates"); - } + const SynapseGroupInternal *sg = c.getArchetype().getSynapseGroup(); + if (sg->getMatrixType() & SynapseMatrixWeight::KERNEL) { + genKernelIteration(os, c, c.getArchetype().getSynapseGroup()->getKernelSize().size(), funcSubs, + [&c, &modelMerged, this] + (CodeStream &os, Substitutions &subs) + { + // Call custom update handler + c.generateCustomUpdate(*this, os, modelMerged, subs); + + // Write back reductions + genWriteBackReductions(os, c, subs["id_syn"]); + }); + } + else { + // Loop through presynaptic neurons + os << "for(unsigned int i = 0; i < group->numSrcNeurons; i++)"; { + // If this synapse group has sparse connectivity, loop through length of this row CodeStream::Scope b(os); - - Substitutions synSubs(&funcSubs); - if(c.getArchetype().getSynapseGroup()->getMatrixType() & SynapseMatrixConnectivity::SPARSE) { - // Calculate index of synapse and use it to look up postsynaptic index - os << "const unsigned int n = (i * group->rowStride) + s;" << std::endl; - os << "const unsigned int j = group->ind[n];" << std::endl; - - synSubs.addVarSubstitution("id_syn", "n"); + if (sg->getMatrixType() & SynapseMatrixConnectivity::SPARSE) { + os << "for(unsigned int s = 0; s < group->rowLength[i]; s++)"; + } + // Otherwise, if it's dense, loop through each postsynaptic neuron + else if (sg->getMatrixType() & SynapseMatrixConnectivity::DENSE) { + os << "for (unsigned int j = 0; j < group->numTrgNeurons; j++)"; } else { - synSubs.addVarSubstitution("id_syn", "(i * group->numTrgNeurons) + j"); + throw std::runtime_error("Only DENSE and SPARSE format connectivity can be used for custom updates"); + } + { + CodeStream::Scope b(os); + + Substitutions synSubs(&funcSubs); + if (sg->getMatrixType() & SynapseMatrixConnectivity::SPARSE) { + // Calculate index of synapse and use it to look up postsynaptic index + os << "const unsigned int n = (i * group->rowStride) + s;" << std::endl; + os << "const unsigned int j = group->ind[n];" << std::endl; + + synSubs.addVarSubstitution("id_syn", "n"); + } + else { + synSubs.addVarSubstitution("id_syn", "(i * group->numTrgNeurons) + j"); + } + + // Add pre and postsynaptic indices to substitutions + synSubs.addVarSubstitution("id_pre", "i"); + synSubs.addVarSubstitution("id_post", "j"); + + // Call custom update handler + c.generateCustomUpdate(*this, os, modelMerged, synSubs); + + // Write back reductions + genWriteBackReductions(os, c, synSubs["id_syn"]); } - - // Add pre and postsynaptic indices to substitutions - synSubs.addVarSubstitution("id_pre", "i"); - synSubs.addVarSubstitution("id_post", "j"); - - // Call custom update handler - c.generateCustomUpdate(*this, os, modelMerged, synSubs); - - // Write back reductions - genWriteBackReductions(os, c, synSubs["id_syn"]); } } } @@ -661,20 +718,18 @@ void Backend::genInit(CodeStream &os, const ModelSpecMerged &modelMerged, // Generate struct definitions modelMerged.genMergedNeuronInitGroupStructs(os, *this); + modelMerged.genMergedSynapseInitGroupStructs(os, *this); modelMerged.genMergedCustomUpdateInitGroupStructs(os, *this); - modelMerged.genMergedCustomWUUpdateDenseInitGroupStructs(os, *this); - modelMerged.genMergedSynapseDenseInitGroupStructs(os, *this); - modelMerged.genMergedSynapseKernelInitGroupStructs(os, *this); + modelMerged.genMergedCustomWUUpdateInitGroupStructs(os, *this); modelMerged.genMergedSynapseConnectivityInitGroupStructs(os, *this); modelMerged.genMergedSynapseSparseInitGroupStructs(os, *this); modelMerged.genMergedCustomWUUpdateSparseInitGroupStructs(os, *this); // Generate arrays of merged structs and functions to set them genMergedStructArrayPush(os, modelMerged.getMergedNeuronInitGroups()); + genMergedStructArrayPush(os, modelMerged.getMergedSynapseInitGroups()); genMergedStructArrayPush(os, modelMerged.getMergedCustomUpdateInitGroups()); - genMergedStructArrayPush(os, modelMerged.getMergedCustomWUUpdateDenseInitGroups()); - genMergedStructArrayPush(os, modelMerged.getMergedSynapseDenseInitGroups()); - genMergedStructArrayPush(os, modelMerged.getMergedSynapseKernelInitGroups()); + genMergedStructArrayPush(os, modelMerged.getMergedCustomWUUpdateInitGroups()); genMergedStructArrayPush(os, modelMerged.getMergedSynapseConnectivityInitGroups()); genMergedStructArrayPush(os, modelMerged.getMergedSynapseSparseInitGroups()); genMergedStructArrayPush(os, modelMerged.getMergedCustomWUUpdateSparseInitGroups()); @@ -698,7 +753,7 @@ void Backend::genInit(CodeStream &os, const ModelSpecMerged &modelMerged, } os << "// ------------------------------------------------------------------------" << std::endl; - os << "// Local neuron groups" << std::endl; + os << "// Neuron groups" << std::endl; for(const auto &n : modelMerged.getMergedNeuronInitGroups()) { CodeStream::Scope b(os); os << "// merged neuron init group " << n.getIndex() << std::endl; @@ -712,73 +767,57 @@ void Backend::genInit(CodeStream &os, const ModelSpecMerged &modelMerged, n.generateInit(*this, os, modelMerged, popSubs); } } - + os << "// ------------------------------------------------------------------------" << std::endl; - os << "// Custom update groups" << std::endl; - for(const auto &c : modelMerged.getMergedCustomUpdateInitGroups()) { + os << "// Synapse groups" << std::endl; + for(const auto &s : modelMerged.getMergedSynapseInitGroups()) { CodeStream::Scope b(os); - os << "// merged custom update group " << c.getIndex() << std::endl; - os << "for(unsigned int g = 0; g < " << c.getGroups().size() << "; g++)"; + os << "// merged synapse init group " << s.getIndex() << std::endl; + os << "for(unsigned int g = 0; g < " << s.getGroups().size() << "; g++)"; { CodeStream::Scope b(os); // Get reference to group - os << "const auto *group = &mergedCustomUpdateInitGroup" < generateRecursive =\ - [&handler, &kernelSize, &os, &sg, &varSubs, &generateRecursive](size_t depth) - { - // Loop through this kernel dimensions - const std::string idxVar = "k" + std::to_string(depth); - os << "for(unsigned int " << idxVar << " = 0; " << idxVar << " < " << sg.getKernelSize(depth) << "; " << idxVar << "++)"; - { - CodeStream::Scope b(os); - - // Add substitution for this kernel index - varSubs.addVarSubstitution("id_kernel_" + std::to_string(depth), idxVar); - - // If we've recursed through all dimensions - if(depth == (kernelSize.size() - 1)) { - // Generate kernel index and use as "synapse" index - // **TODO** rename - os << "const unsigned int kernelInd = "; - sg.genKernelIndex(os, varSubs); - os << ";" << std::endl; - varSubs.addVarSubstitution("id_syn", "kernelInd"); - - // Call handler - handler(os, varSubs); - } - // Otherwise, recurse - else { - generateRecursive(depth + 1); - } - } - }; - - // Generate loops through kernel indices recursively - generateRecursive(0); + genKernelIteration(os, sg, sg.getArchetype().getKernelSize().size(), kernelSubs, handler); +} +//-------------------------------------------------------------------------- +void Backend::genKernelCustomUpdateVariableInit(CodeStream &os, const CustomWUUpdateInitGroupMerged &cu, const Substitutions &kernelSubs, Handler handler) const +{ + genKernelIteration(os, cu, cu.getArchetype().getSynapseGroup()->getKernelSize().size(), kernelSubs, handler); } //-------------------------------------------------------------------------- void Backend::genVariablePush(CodeStream&, const std::string&, const std::string&, VarLocation, bool, size_t) const diff --git a/src/genn/genn/code_generator/backendSIMT.cc b/src/genn/genn/code_generator/backendSIMT.cc index a31904a462..6b0906ac5b 100644 --- a/src/genn/genn/code_generator/backendSIMT.cc +++ b/src/genn/genn/code_generator/backendSIMT.cc @@ -28,7 +28,7 @@ size_t getNumMergedGroupThreads(const std::vector &groups, G getNumThreads) }); }); } -} +} // Anonymous namespace //-------------------------------------------------------------------------- // CodeGenerator::BackendSIMT @@ -82,7 +82,7 @@ void BackendSIMT::genVariableInit(CodeStream &os, const std::string &, const std handler(os, varSubs); } //-------------------------------------------------------------------------- -void BackendSIMT::genKernelSynapseVariableInit(CodeStream &os, const SynapseKernelInitGroupMerged &, const Substitutions &kernelSubs, Handler handler) const +void BackendSIMT::genKernelSynapseVariableInit(CodeStream &os, const SynapseInitGroupMerged &, const Substitutions &kernelSubs, Handler handler) const { // Variable should already be provided via parallelism assert(kernelSubs.hasVarSubstitution("id")); @@ -93,6 +93,17 @@ void BackendSIMT::genKernelSynapseVariableInit(CodeStream &os, const SynapseKern handler(os, varSubs); } //-------------------------------------------------------------------------- +void BackendSIMT::genKernelCustomUpdateVariableInit(CodeStream &os, const CustomWUUpdateInitGroupMerged&, const Substitutions &kernelSubs, Handler handler) const +{ + // Variable should already be provided via parallelism + assert(kernelSubs.hasVarSubstitution("id")); + + Substitutions varSubs(&kernelSubs); + varSubs.addVarSubstitution("id_syn", varSubs["id"]); + + handler(os, varSubs); +} +//-------------------------------------------------------------------------- bool BackendSIMT::isGlobalHostRNGRequired(const ModelSpecMerged &modelMerged) const { // Host RNG is required if any synapse groups require a host initialization RNG @@ -144,32 +155,25 @@ size_t BackendSIMT::getNumInitialisationRNGStreams(const ModelSpecMerged &modelM return padKernelSize(cg.getSize(), KernelInitialize); }); - // Add on total number of threads used for custom WU update groups with dense connectivity - numInitThreads += getNumMergedGroupThreads(modelMerged.getMergedCustomWUUpdateDenseInitGroups(), + // Add on total number of threads used for custom WU update initialization + numInitThreads += getNumMergedGroupThreads(modelMerged.getMergedCustomWUUpdateInitGroups(), [this](const CustomUpdateWUInternal &cg) { - return padKernelSize(cg.getSynapseGroup()->getTrgNeuronGroup()->getNumNeurons(), KernelInitialize); - }); - - // Add on total number of threads used for dense synapse initialisation - numInitThreads += getNumMergedGroupThreads(modelMerged.getMergedSynapseDenseInitGroups(), - [this](const SynapseGroupInternal &sg) - { - return padKernelSize(sg.getTrgNeuronGroup()->getNumNeurons(), KernelInitialize); + return padKernelSize(getNumInitThreads(cg), KernelInitialize); }); - // Add on total number of threads used for kernel synapse initialisation - numInitThreads += getNumMergedGroupThreads(modelMerged.getMergedSynapseKernelInitGroups(), + // Add on total number of threads used for synapse initialisation + numInitThreads += getNumMergedGroupThreads(modelMerged.getMergedSynapseInitGroups(), [this](const SynapseGroupInternal &sg) { - return padKernelSize(sg.getKernelSizeFlattened(), KernelInitialize); + return padKernelSize(getNumInitThreads(sg), KernelInitialize); }); // Add on total number of threads used for synapse connectivity initialisation numInitThreads += getNumMergedGroupThreads(modelMerged.getMergedSynapseConnectivityInitGroups(), [this](const SynapseGroupInternal &sg) { - return padKernelSize(sg.getSrcNeuronGroup()->getNumNeurons(), KernelInitialize); + return padKernelSize(getNumConnectivityInitThreads(sg), KernelInitialize); }); // Add on total number of threads used for sparse synapse initialisation @@ -199,13 +203,11 @@ size_t BackendSIMT::getPaddedNumCustomUpdateWUThreads(const CustomUpdateWUIntern const SynapseGroupInternal *sgInternal = static_cast(cg.getSynapseGroup()); 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 - return numCopies * padKernelSize((size_t)sgInternal->getSrcNeuronGroup()->getNumNeurons() * sgInternal->getMaxConnections(), - KernelCustomUpdate); + if(sgInternal->getMatrixType() & SynapseMatrixWeight::KERNEL) { + return numCopies * padKernelSize(sgInternal->getKernelSizeFlattened(), KernelCustomUpdate); } else { - return numCopies * padKernelSize((size_t)sgInternal->getSrcNeuronGroup()->getNumNeurons() * sgInternal->getTrgNeuronGroup()->getNumNeurons(), + return numCopies * padKernelSize((size_t)sgInternal->getSrcNeuronGroup()->getNumNeurons() * sgInternal->getMaxConnections(), KernelCustomUpdate); } } @@ -263,6 +265,26 @@ size_t BackendSIMT::getNumConnectivityInitThreads(const SynapseGroupInternal &sg } } //-------------------------------------------------------------------------- +size_t BackendSIMT::getNumInitThreads(const SynapseGroupInternal &sg) +{ + if (sg.getMatrixType() & SynapseMatrixWeight::KERNEL) { + return sg.getKernelSizeFlattened(); + } + else { + return sg.getTrgNeuronGroup()->getNumNeurons(); + } +} +//-------------------------------------------------------------------------- +size_t BackendSIMT::getNumInitThreads(const CustomUpdateWUInternal &cg) +{ + if (cg.getSynapseGroup()->getMatrixType() & SynapseMatrixWeight::KERNEL) { + return cg.getSynapseGroup()->getKernelSizeFlattened(); + } + else { + return cg.getSynapseGroup()->getTrgNeuronGroup()->getNumNeurons(); + } +} +//-------------------------------------------------------------------------- void BackendSIMT::addPresynapticUpdateStrategy(PresynapticUpdateStrategySIMT::Base *strategy) { s_PresynapticUpdateStrategies.push_back(strategy); @@ -944,11 +966,25 @@ void BackendSIMT::genCustomUpdateWUKernel(CodeStream &os, const Substitutions &k [&updateGroup](const CustomUpdateWUGroupMerged &cg) { return (cg.getArchetype().getUpdateGroupName() == updateGroup); }, [&modelMerged, this](CodeStream &os, const CustomUpdateWUGroupMerged &cg, Substitutions &popSubs) { + const SynapseGroupInternal *sg = cg.getArchetype().getSynapseGroup(); 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; + // Calculate size of each batch to update + if (sg->getMatrixType() & SynapseMatrixWeight::KERNEL) { + // Loop through kernel dimensions and multiply together + os << "const unsigned int size = "; + for (size_t i = 0; i < sg->getKernelSize().size(); i++) { + os << cg.getKernelSize(i); + if (i != (sg->getKernelSize().size() - 1)) { + os << " * "; + } + } + os << ";" << std::endl; + } + else { + os << "const unsigned int size = group->numSrcNeurons * group->rowStride;" << std::endl; + } // If update isn't a reduction Substitutions cuSubs(&popSubs); @@ -975,32 +1011,34 @@ void BackendSIMT::genCustomUpdateWUKernel(CodeStream &os, const Substitutions &k } } - if(cg.getArchetype().getSynapseGroup()->getMatrixType() & SynapseMatrixConnectivity::SPARSE) { - os << "if (" << cuSubs["id"] << " < (group->numSrcNeurons * group->rowStride))"; - } - else { - os << "if (" << cuSubs["id"] << " < size)"; - } + // if this isn't a padding thread + os << "if (" << cuSubs["id"] << " < size)"; { CodeStream::Scope b(os); - if(cg.getArchetype().getSynapseGroup()->getMatrixType() & SynapseMatrixConnectivity::SPARSE) { - // **OPTIMIZE * *we can do a fast constant divide optimization here and use the result to calculate the remainder - os << "const unsigned int row = " << cuSubs["id"] << " / group->rowStride;" << std::endl; - os << "const unsigned int col = " << cuSubs["id"] << " % group->rowStride;" << std::endl; - - cuSubs.addVarSubstitution("id_pre", "row"); - cuSubs.addVarSubstitution("id_post", "group->ind[" + cuSubs["id"] + "]"); + if (sg->getMatrixType() & SynapseMatrixWeight::KERNEL) { cuSubs.addVarSubstitution("id_syn", cuSubs["id"]); - - os << "if(col < group->rowLength[row])"; - os << CodeStream::OB(2); + cuSubs.addVarSubstitution("id_kernel", cuSubs["id"]); } 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"]); + if (sg->getMatrixType() & SynapseMatrixConnectivity::SPARSE) { + // **OPTIMIZE * *we can do a fast constant divide optimization here and use the result to calculate the remainder + os << "const unsigned int row = " << cuSubs["id"] << " / group->rowStride;" << std::endl; + os << "const unsigned int col = " << cuSubs["id"] << " % group->rowStride;" << std::endl; + + cuSubs.addVarSubstitution("id_pre", "row"); + cuSubs.addVarSubstitution("id_post", "group->ind[" + cuSubs["id"] + "]"); + cuSubs.addVarSubstitution("id_syn", cuSubs["id"]); + + os << "if(col < group->rowLength[row])"; + os << CodeStream::OB(2); + } + 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 @@ -1039,7 +1077,7 @@ void BackendSIMT::genCustomUpdateWUKernel(CodeStream &os, const Substitutions &k } } - if (cg.getArchetype().getSynapseGroup()->getMatrixType() & SynapseMatrixConnectivity::SPARSE) { + if (sg->getMatrixType() & SynapseMatrixConnectivity::SPARSE) { os << CodeStream::CB(2); } } @@ -1210,6 +1248,18 @@ void BackendSIMT::genInitializeKernel(CodeStream &os, const Substitutions &kerne }); os << std::endl; + os << "// ------------------------------------------------------------------------" << std::endl; + os << "// Synapse groups" << std::endl; + genParallelGroup( + os, kernelSubs, modelMerged.getMergedSynapseInitGroups(), idStart, + [this](const SynapseGroupInternal &sg) { return padKernelSize(getNumInitThreads(sg), KernelInitialize); }, + [&modelMerged, this](CodeStream &os, const SynapseInitGroupMerged &sg, Substitutions &popSubs) + { + genSynapseVarInit(os, modelMerged, sg, popSubs, sg.getArchetype().isWUInitRNGRequired(), + (sg.getArchetype().getMatrixType() & SynapseMatrixWeight::KERNEL), sg.getArchetype().getKernelSize().size()); + }); + os << std::endl; + os << "// ------------------------------------------------------------------------" << std::endl; os << "// Custom update groups" << std::endl; genParallelGroup( @@ -1235,113 +1285,15 @@ void BackendSIMT::genInitializeKernel(CodeStream &os, const Substitutions &kerne os << std::endl; os << "// ------------------------------------------------------------------------" << std::endl; - os << "// Custom WU update groups with dense connectivity" << std::endl; - genParallelGroup( - os, kernelSubs, modelMerged.getMergedCustomWUUpdateDenseInitGroups(), idStart, - [this](const CustomUpdateWUInternal &cg) { return padKernelSize(cg.getSynapseGroup()->getTrgNeuronGroup()->getNumNeurons(), KernelInitialize); }, - [&modelMerged, this](CodeStream &os, const CustomWUUpdateDenseInitGroupMerged &cg, Substitutions &popSubs) - { - os << "// only do this for existing postsynaptic neurons" << std::endl; - os << "if(" << popSubs["id"] << " < group->numTrgNeurons)"; - { - CodeStream::Scope b(os); - // If this post synapse requires an RNG for initialisation, - // make copy of global phillox RNG and skip ahead by thread id - // **NOTE** not LOCAL id - if(cg.getArchetype().isInitRNGRequired()) { - genGlobalRNGSkipAhead(os, popSubs, "id"); - } - - popSubs.addVarSubstitution("id_post", popSubs["id"]); - cg.generateInit(*this, os, modelMerged, popSubs); - } - }); - os << std::endl; - - os << "// ------------------------------------------------------------------------" << std::endl; - os << "// Synapse groups with dense connectivity" << std::endl; - genParallelGroup( - os, kernelSubs, modelMerged.getMergedSynapseDenseInitGroups(), idStart, - [this](const SynapseGroupInternal &sg) { return padKernelSize(sg.getTrgNeuronGroup()->getNumNeurons(), KernelInitialize); }, - [&modelMerged, this](CodeStream &os, const SynapseDenseInitGroupMerged &sg, Substitutions &popSubs) - { - os << "// only do this for existing postsynaptic neurons" << std::endl; - os << "if(" << popSubs["id"] << " < group->numTrgNeurons)"; - { - CodeStream::Scope b(os); - // If this post synapse requires an RNG for initialisation, - // make copy of global phillox RNG and skip ahead by thread id - // **NOTE** not LOCAL id - if(sg.getArchetype().isWUInitRNGRequired()) { - genGlobalRNGSkipAhead(os, popSubs, "id"); - } - - popSubs.addVarSubstitution("id_post", popSubs["id"]); - sg.generateInit(*this, os, modelMerged, popSubs); - } - }); - os << std::endl; - - os << "// ------------------------------------------------------------------------" << std::endl; - os << "// Synapse groups with kernel connectivity" << std::endl; - genParallelGroup( - os, kernelSubs, modelMerged.getMergedSynapseKernelInitGroups(), idStart, - [this](const SynapseGroupInternal &sg) { return padKernelSize(sg.getKernelSizeFlattened(), KernelInitialize); }, - [&modelMerged, this](CodeStream &os, const SynapseKernelInitGroupMerged &sg, Substitutions &popSubs) + os << "// Custom WU update groups" << std::endl; + genParallelGroup( + os, kernelSubs, modelMerged.getMergedCustomWUUpdateInitGroups(), idStart, + [this](const CustomUpdateWUInternal &cg) { return padKernelSize(getNumInitThreads(cg), KernelInitialize); }, + [&modelMerged, this](CodeStream &os, const CustomWUUpdateInitGroupMerged &cg, Substitutions &popSubs) { - os << "// only do this for existing kernel entries" << std::endl; - os << "if(" << popSubs["id"] << " < ("; - const auto &kernelSize = sg.getArchetype().getKernelSize(); - - // Loop through kernel dimensions and multiply together - for(size_t i = 0; i < kernelSize.size(); i++) { - os << sg.getKernelSize(i); - - if(i != (kernelSize.size() - 1)) { - os << " * "; - } - } - os << "))"; - { - CodeStream::Scope b(os); - // If this post synapse requires an RNG for initialisation, - // make copy of global phillox RNG and skip ahead by thread id - // **NOTE** not LOCAL id - if(sg.getArchetype().isWUInitRNGRequired()) { - genGlobalRNGSkipAhead(os, popSubs, "id"); - } - - // Loop through kernel dimensions to generate seperate indices - for(size_t i = 0; i < kernelSize.size(); i++) { - os << "const unsigned int kernelID" << i << " = (" << popSubs["id"]; - - // If this isn't the last dimension - if(i < (kernelSize.size() - 1)) { - // Loop backwards through other kernel and generate code to divide by product of subsequent dimensions - os << " / ("; - for(size_t j = (kernelSize.size() - 1); j > i; j--) { - os << sg.getKernelSize(j); - - if(j != (i + 1)) { - os << " * "; - } - } - os << ")"; - } - os << ")"; - - // If this isn't the first dimension, take modulus of kernel size - if (i > 0) { - os << " % " << sg.getKernelSize(i); - } - - os << ";" << std::endl; - - // Add substitution - popSubs.addVarSubstitution("id_kernel_" + std::to_string(i), "kernelID" + std::to_string(i)); - } - sg.generateInit(*this, os, modelMerged, popSubs); - } + const SynapseGroup *sg = cg.getArchetype().getSynapseGroup(); + genSynapseVarInit(os, modelMerged, cg, popSubs, cg.getArchetype().isInitRNGRequired(), + (sg->getMatrixType() & SynapseMatrixWeight::KERNEL), sg->getKernelSize().size()); }); os << std::endl; @@ -1517,71 +1469,29 @@ void BackendSIMT::genInitializeSparseKernel(CodeStream &os, const Substitutions genGlobalRNGSkipAhead(os, popSubs, std::to_string(numInitializeThreads) + " + id"); } - // Calculate how many blocks rows need to be processed in (in order to store row lengths in shared memory) - const size_t blockSize = getKernelBlockSize(KernelInitializeSparse); - os << "const unsigned int numBlocks = (group->numSrcNeurons + " << blockSize << " - 1) / " << blockSize << ";" << std::endl; - - os << "unsigned int idx = " << popSubs["id"] << ";" << std::endl; - - // Loop through blocks - os << "for(unsigned int r = 0; r < numBlocks; r++)"; - { - CodeStream::Scope b(os); - - // Calculate number of rows to process in this block - os << "const unsigned numRowsInBlock = (r == (numBlocks - 1))"; - os << " ? ((group->numSrcNeurons - 1) % " << blockSize << ") + 1"; - os << " : " << blockSize << ";" << std::endl; - - // Use threads to copy block of sparse structure into shared memory - genSharedMemBarrier(os); - os << "if (" << getThreadID() << " < numRowsInBlock)"; + // Generate sparse synapse variable initialisation code + genSparseSynapseVarInit( + os, modelMerged, sg, popSubs, sg.getArchetype().isWUVarInitRequired(), + [this](CodeStream &os, const SynapseSparseInitGroupMerged &sg, Substitutions&) { - CodeStream::Scope b(os); - os << "shRowLength[" << getThreadID() << "] = group->rowLength[(r * " << blockSize << ") + " << getThreadID() << "];" << std::endl; - } - genSharedMemBarrier(os); - - // Loop through rows - os << "for(unsigned int i = 0; i < numRowsInBlock; i++)"; - { - CodeStream::Scope b(os); - - // If there is a synapse for this thread to initialise - os << "if(" << popSubs["id"] << " < shRowLength[i])"; - { + // If postsynaptic learning is required + if(!sg.getArchetype().getWUModel()->getLearnPostCode().empty()) { CodeStream::Scope b(os); - // Generate sparse initialisation code - if(sg.getArchetype().isWUVarInitRequired()) { - popSubs.addVarSubstitution("id_pre", "((r * " + std::to_string(blockSize) + ") + i)"); - popSubs.addVarSubstitution("id_post", "group->ind[idx]"); - sg.generateInit(*this, os, modelMerged, popSubs); - } - - // If postsynaptic learning is required - if(!sg.getArchetype().getWUModel()->getLearnPostCode().empty()) { - CodeStream::Scope b(os); + // Extract index of synapse's postsynaptic target + os << "const unsigned int postIndex = group->ind[idx];" << std::endl; - // Extract index of synapse's postsynaptic target - os << "const unsigned int postIndex = group->ind[idx];" << std::endl; + // Atomically increment length of column of connectivity associated with this target + // **NOTE** this returns previous length i.e. where to insert new entry + os << "const unsigned int colLocation = " << getAtomic("unsigned int") << "(&group->colLength[postIndex], 1);" << std::endl; - // Atomically increment length of column of connectivity associated with this target - // **NOTE** this returns previous length i.e. where to insert new entry - os << "const unsigned int colLocation = " << getAtomic("unsigned int") << "(&group->colLength[postIndex], 1);" << std::endl; + // From this calculate index into column-major matrix + os << "const unsigned int colMajorIndex = (postIndex * group->colStride) + colLocation;" << std::endl; - // From this calculate index into column-major matrix - os << "const unsigned int colMajorIndex = (postIndex * group->colStride) + colLocation;" << std::endl; - - // Add remapping entry at this location poining back to row-major index - os << "group->remap[colMajorIndex] = idx;" << std::endl; - } + // Add remapping entry at this location poining back to row-major index + os << "group->remap[colMajorIndex] = idx;" << std::endl; } - - // If matrix is ragged, advance index to next row by adding stride - os << "idx += group->rowStride;" << std::endl; - } - } + }); }); // Initialise weight update variables for synapse groups with sparse connectivity @@ -1595,53 +1505,11 @@ void BackendSIMT::genInitializeSparseKernel(CodeStream &os, const Substitutions if(cg.getArchetype().isInitRNGRequired()) { genGlobalRNGSkipAhead(os, popSubs, std::to_string(numInitializeThreads) + " + id"); } - - // Calculate how many blocks rows need to be processed in (in order to store row lengths in shared memory) - const size_t blockSize = getKernelBlockSize(KernelInitializeSparse); - os << "const unsigned int numBlocks = (group->numSrcNeurons + " << blockSize << " - 1) / " << blockSize << ";" << std::endl; - - os << "unsigned int idx = " << popSubs["id"] << ";" << std::endl; - - // Loop through blocks - os << "for(unsigned int r = 0; r < numBlocks; r++)"; - { - CodeStream::Scope b(os); - - // Calculate number of rows to process in this block - os << "const unsigned numRowsInBlock = (r == (numBlocks - 1))"; - os << " ? ((group->numSrcNeurons - 1) % " << blockSize << ") + 1"; - os << " : " << blockSize << ";" << std::endl; - - // Use threads to copy block of sparse structure into shared memory - genSharedMemBarrier(os); - os << "if (" << getThreadID() << " < numRowsInBlock)"; - { - CodeStream::Scope b(os); - os << "shRowLength[" << getThreadID() << "] = group->rowLength[(r * " << blockSize << ") + " << getThreadID() << "];" << std::endl; - } - - genSharedMemBarrier(os); - - // Loop through rows - os << "for(unsigned int i = 0; i < numRowsInBlock; i++)"; - { - CodeStream::Scope b(os); - - // If there is a synapse for this thread to initialise - os << "if(" << popSubs["id"] << " < shRowLength[i])"; - { - CodeStream::Scope b(os); - - // Generate sparse initialisation code - popSubs.addVarSubstitution("id_pre", "((r * " + std::to_string(blockSize) + ") + i)"); - popSubs.addVarSubstitution("id_post", "group->ind[idx]"); - cg.generateInit(*this, os, modelMerged, popSubs); - } - - // If matrix is ragged, advance index to next row by adding stride - os << "idx += group->rowStride;" << std::endl; - } - } + + // Generate sparse synapse variable initialisation code + genSparseSynapseVarInit( + os, modelMerged, cg, popSubs, true, + [](CodeStream&, const CustomWUUpdateSparseInitGroupMerged&, Substitutions&){}); }); } //-------------------------------------------------------------------------- diff --git a/src/genn/genn/code_generator/customUpdateGroupMerged.cc b/src/genn/genn/code_generator/customUpdateGroupMerged.cc index 8f3921f9c7..023b8970b3 100644 --- a/src/genn/genn/code_generator/customUpdateGroupMerged.cc +++ b/src/genn/genn/code_generator/customUpdateGroupMerged.cc @@ -252,40 +252,57 @@ CustomUpdateWUGroupMergedBase::CustomUpdateWUGroupMergedBase(size_t index, const const std::vector> &groups) : GroupMerged(index, precision, groups) { - addField("unsigned int", "rowStride", - [&backend](const CustomUpdateWUInternal &cg, size_t) - { - const SynapseGroupInternal *sgInternal = static_cast(cg.getSynapseGroup()); - return std::to_string(backend.getSynapticMatrixRowStride(*sgInternal)); - }); - - addField("unsigned int", "numSrcNeurons", - [](const CustomUpdateWUInternal &cg, size_t) - { - const SynapseGroupInternal *sgInternal = static_cast(cg.getSynapseGroup()); - return std::to_string(sgInternal->getSrcNeuronGroup()->getNumNeurons()); - }); - - addField("unsigned int", "numTrgNeurons", - [](const CustomUpdateWUInternal &cg, size_t) - { - const SynapseGroupInternal *sgInternal = static_cast(cg.getSynapseGroup()); - return std::to_string(sgInternal->getTrgNeuronGroup()->getNumNeurons()); - }); - - // If synapse group has sparse connectivity - if(getArchetype().getSynapseGroup()->getMatrixType() & SynapseMatrixConnectivity::SPARSE) { - addField(getArchetype().getSynapseGroup()->getSparseIndType() + "*", "ind", + // If underlying synapse group has kernel weights + if (getArchetype().getSynapseGroup()->getMatrixType() & SynapseMatrixWeight::KERNEL) { + // Loop through kernel size dimensions + for (size_t d = 0; d < getArchetype().getSynapseGroup()->getKernelSize().size(); d++) { + // If this dimension has a heterogeneous size, add it to struct + if (isKernelSizeHeterogeneous(d)) { + addField("unsigned int", "kernelSize" + std::to_string(d), + [d](const CustomUpdateWUInternal &cu, size_t) + { + return std::to_string(cu.getSynapseGroup()->getKernelSize().at(d)); + }); + } + } + } + // Otherwise + else { + addField("unsigned int", "rowStride", [&backend](const CustomUpdateWUInternal &cg, size_t) { - return backend.getDeviceVarPrefix() + "ind" + cg.getSynapseGroup()->getName(); + const SynapseGroupInternal *sgInternal = static_cast(cg.getSynapseGroup()); + return std::to_string(backend.getSynapticMatrixRowStride(*sgInternal)); + }); + + addField("unsigned int", "numSrcNeurons", + [](const CustomUpdateWUInternal &cg, size_t) + { + const SynapseGroupInternal *sgInternal = static_cast(cg.getSynapseGroup()); + return std::to_string(sgInternal->getSrcNeuronGroup()->getNumNeurons()); + }); + + addField("unsigned int", "numTrgNeurons", + [](const CustomUpdateWUInternal &cg, size_t) + { + const SynapseGroupInternal *sgInternal = static_cast(cg.getSynapseGroup()); + return std::to_string(sgInternal->getTrgNeuronGroup()->getNumNeurons()); }); - addField("unsigned int*", "rowLength", - [&backend](const CustomUpdateWUInternal &cg, size_t) - { - return backend.getDeviceVarPrefix() + "rowLength" + cg.getSynapseGroup()->getName(); - }); + // If synapse group has sparse connectivity + if(getArchetype().getSynapseGroup()->getMatrixType() & SynapseMatrixConnectivity::SPARSE) { + addField(getArchetype().getSynapseGroup()->getSparseIndType() + "*", "ind", + [&backend](const CustomUpdateWUInternal &cg, size_t) + { + return backend.getDeviceVarPrefix() + "ind" + cg.getSynapseGroup()->getName(); + }); + + addField("unsigned int*", "rowLength", + [&backend](const CustomUpdateWUInternal &cg, size_t) + { + return backend.getDeviceVarPrefix() + "rowLength" + cg.getSynapseGroup()->getName(); + }); + } } // Add heterogeneous custom update model parameters @@ -309,7 +326,7 @@ CustomUpdateWUGroupMergedBase::CustomUpdateWUGroupMergedBase(size_t index, const addVarReferences(varRefs, backend.getDeviceVarPrefix(), [](const CustomUpdateWUInternal &cg) { return cg.getVarReferences(); }); - // Loop through variables + // Loop through variables for(size_t v = 0; v < varRefs.size(); v++) { // If variable has a transpose if(getArchetype().getVarReferences().at(v).getTransposeSynapseGroup() != nullptr) { @@ -325,6 +342,7 @@ CustomUpdateWUGroupMergedBase::CustomUpdateWUGroupMergedBase(size_t index, const // Add EGPs to struct this->addEGPs(cm->getExtraGlobalParams(), backend.getDeviceVarPrefix()); } + // ---------------------------------------------------------------------------- // CustomUpdateWUGroupMerged //---------------------------------------------------------------------------- diff --git a/src/genn/genn/code_generator/generateModules.cc b/src/genn/genn/code_generator/generateModules.cc index c9d735b3d9..7d3f791f57 100644 --- a/src/genn/genn/code_generator/generateModules.cc +++ b/src/genn/genn/code_generator/generateModules.cc @@ -160,8 +160,8 @@ std::pair, MemAlloc> CodeGenerator::generateAll(const M LOGI_CODE_GEN << "\t" << modelMerged.getMergedCustomUpdateTransposeWUGroups().size() << " merged custom weight transpose update groups"; LOGI_CODE_GEN << "\t" << modelMerged.getMergedNeuronInitGroups().size() << " merged neuron init groups"; LOGI_CODE_GEN << "\t" << modelMerged.getMergedCustomUpdateInitGroups().size() << " merged custom update init groups"; - LOGI_CODE_GEN << "\t" << modelMerged.getMergedCustomWUUpdateDenseInitGroups().size() << " merged custom WU update dense init groups"; - LOGI_CODE_GEN << "\t" << modelMerged.getMergedSynapseDenseInitGroups().size() << " merged synapse dense init groups"; + LOGI_CODE_GEN << "\t" << modelMerged.getMergedCustomWUUpdateInitGroups().size() << " merged custom WU update init groups"; + LOGI_CODE_GEN << "\t" << modelMerged.getMergedSynapseInitGroups().size() << " merged synapse init groups"; LOGI_CODE_GEN << "\t" << modelMerged.getMergedSynapseConnectivityInitGroups().size() << " merged synapse connectivity init groups"; LOGI_CODE_GEN << "\t" << modelMerged.getMergedSynapseSparseInitGroups().size() << " merged synapse sparse init groups"; LOGI_CODE_GEN << "\t" << modelMerged.getMergedCustomWUUpdateSparseInitGroups().size() << " merged custom WU update sparse init groups"; @@ -280,8 +280,8 @@ void CodeGenerator::generateInit(const filesystem::path &outputPath, const Model { modelMerged.genMergedGroupPush(os, modelMerged.getMergedNeuronInitGroups(), backend); modelMerged.genMergedGroupPush(os, modelMerged.getMergedCustomUpdateInitGroups(), backend); - modelMerged.genMergedGroupPush(os, modelMerged.getMergedCustomWUUpdateDenseInitGroups(), backend); - modelMerged.genMergedGroupPush(os, modelMerged.getMergedSynapseDenseInitGroups(), backend); + modelMerged.genMergedGroupPush(os, modelMerged.getMergedCustomWUUpdateInitGroups(), backend); + modelMerged.genMergedGroupPush(os, modelMerged.getMergedSynapseInitGroups(), backend); modelMerged.genMergedGroupPush(os, modelMerged.getMergedSynapseConnectivityInitGroups(), backend); modelMerged.genMergedGroupPush(os, modelMerged.getMergedSynapseSparseInitGroups(), backend); }, @@ -290,8 +290,8 @@ void CodeGenerator::generateInit(const filesystem::path &outputPath, const Model { modelMerged.genScalarEGPPush(os, backend); modelMerged.genScalarEGPPush(os, backend); - modelMerged.genScalarEGPPush(os, backend); - modelMerged.genScalarEGPPush(os, backend); + modelMerged.genScalarEGPPush(os, backend); + modelMerged.genScalarEGPPush(os, backend); modelMerged.genScalarEGPPush(os, backend); }, // Initialise sparse push EGP handler diff --git a/src/genn/genn/code_generator/generateRunner.cc b/src/genn/genn/code_generator/generateRunner.cc index ec26257150..3f4f1c19c9 100644 --- a/src/genn/genn/code_generator/generateRunner.cc +++ b/src/genn/genn/code_generator/generateRunner.cc @@ -702,26 +702,8 @@ MemAlloc CodeGenerator::generateRunner(const filesystem::path &outputPath, const runnerVarDecl, runnerMergedStructAlloc); } - // Generate merged custom update initialisation groups - for(const auto &m : modelMerged.getMergedCustomUpdateInitGroups()) { - m.generateRunner(backend, definitionsInternal, definitionsInternalFunc, definitionsInternalVar, - runnerVarDecl, runnerMergedStructAlloc); - } - - // Generate merged custom dense WU update initialisation groups - for(const auto &m : modelMerged.getMergedCustomWUUpdateDenseInitGroups()) { - m.generateRunner(backend, definitionsInternal, definitionsInternalFunc, definitionsInternalVar, - runnerVarDecl, runnerMergedStructAlloc); - } - - // Loop through merged dense synapse init groups - for(const auto &m : modelMerged.getMergedSynapseDenseInitGroups()) { - m.generateRunner(backend, definitionsInternal, definitionsInternalFunc, definitionsInternalVar, - runnerVarDecl, runnerMergedStructAlloc); - } - - // Loop through merged kernel synapse init groups - for(const auto &m : modelMerged.getMergedSynapseKernelInitGroups()) { + // Loop through merged synapse init groups + for(const auto &m : modelMerged.getMergedSynapseInitGroups()) { m.generateRunner(backend, definitionsInternal, definitionsInternalFunc, definitionsInternalVar, runnerVarDecl, runnerMergedStructAlloc); } @@ -738,6 +720,18 @@ MemAlloc CodeGenerator::generateRunner(const filesystem::path &outputPath, const runnerVarDecl, runnerMergedStructAlloc); } + // Generate merged custom update initialisation groups + for(const auto &m : modelMerged.getMergedCustomUpdateInitGroups()) { + m.generateRunner(backend, definitionsInternal, definitionsInternalFunc, definitionsInternalVar, + runnerVarDecl, runnerMergedStructAlloc); + } + + // Generate merged custom WU update initialisation groups + for(const auto &m : modelMerged.getMergedCustomWUUpdateInitGroups()) { + m.generateRunner(backend, definitionsInternal, definitionsInternalFunc, definitionsInternalVar, + runnerVarDecl, runnerMergedStructAlloc); + } + // Generate merged custom sparse WU update initialisation groups for(const auto &m : modelMerged.getMergedCustomWUUpdateSparseInitGroups()) { m.generateRunner(backend, definitionsInternal, definitionsInternalFunc, definitionsInternalVar, @@ -1119,7 +1113,13 @@ MemAlloc CodeGenerator::generateRunner(const filesystem::path &outputPath, const mem, statePushPullFunctions, [&backend](const CustomUpdateWUInternal &c) { - return c.getSynapseGroup()->getSrcNeuronGroup()->getNumNeurons() * backend.getSynapticMatrixRowStride(*c.getSynapseGroup()); + const SynapseGroupInternal *sg = c.getSynapseGroup(); + if (sg->getMatrixType() & SynapseMatrixWeight::KERNEL) { + return sg->getKernelSizeFlattened(); + } + else { + return sg->getSrcNeuronGroup()->getNumNeurons() * backend.getSynapticMatrixRowStride(*sg); + } }); allVarStreams << std::endl; diff --git a/src/genn/genn/code_generator/groupMerged.cc b/src/genn/genn/code_generator/groupMerged.cc index 8b191b9f2f..af1a50d88a 100644 --- a/src/genn/genn/code_generator/groupMerged.cc +++ b/src/genn/genn/code_generator/groupMerged.cc @@ -775,50 +775,6 @@ bool SynapseGroupMergedBase::isTrgNeuronDerivedParamHeterogeneous(size_t paramIn isParamValueHeterogeneous(paramIndex, [](const SynapseGroupInternal &sg) { return sg.getTrgNeuronGroup()->getDerivedParams(); })); } //---------------------------------------------------------------------------- -bool SynapseGroupMergedBase::isKernelSizeHeterogeneous(size_t dimensionIndex) const -{ - // Get size of this kernel dimension for archetype - const unsigned archetypeValue = getArchetype().getKernelSize().at(dimensionIndex); - - // Return true if any of the other groups have a different value - return std::any_of(getGroups().cbegin(), getGroups().cend(), - [archetypeValue, dimensionIndex](const GroupInternal &g) - { - return (g.getKernelSize().at(dimensionIndex) != archetypeValue); - }); -} -//---------------------------------------------------------------------------- -std::string SynapseGroupMergedBase::getKernelSize(size_t dimensionIndex) const -{ - // If kernel size if heterogeneous in this dimension, return group structure entry - if(isKernelSizeHeterogeneous(dimensionIndex)) { - return "group->kernelSize" + std::to_string(dimensionIndex); - } - // Otherwise, return literal - else { - return std::to_string(getArchetype().getKernelSize().at(dimensionIndex)); - } -} -//---------------------------------------------------------------------------- -void SynapseGroupMergedBase::genKernelIndex(std::ostream &os, const CodeGenerator::Substitutions &subs) const -{ - // Loop through kernel dimensions to calculate array index - const auto &kernelSize = getArchetype().getKernelSize(); - for(size_t i = 0; i < kernelSize.size(); i++) { - os << "(" << subs["id_kernel_" + std::to_string(i)]; - // Loop through remainining dimensions of kernel and multiply - for(size_t j = i + 1; j < kernelSize.size(); j++) { - os << " * " << getKernelSize(j); - } - os << ")"; - - // If this isn't the last dimension, add + - if(i != (kernelSize.size() - 1)) { - os << " + "; - } - } -} -//---------------------------------------------------------------------------- std::string SynapseGroupMergedBase::getPreSlot(unsigned int batchSize) const { if(getArchetype().getSrcNeuronGroup()->isDelayRequired()) { @@ -920,7 +876,8 @@ SynapseGroupMergedBase::SynapseGroupMergedBase(size_t index, const std::string & || (role == Role::SynapseDynamics)); const WeightUpdateModels::Base *wum = getArchetype().getWUModel(); - if(role != Role::KernelInit) { + // If role isn't an init role or weights aren't kernel + if(role != Role::Init || !(getArchetype().getMatrixType() & SynapseMatrixWeight::KERNEL)) { addField("unsigned int", "rowStride", [&backend](const SynapseGroupInternal &sg, size_t) { return std::to_string(backend.getSynapticMatrixRowStride(sg)); }); addField("unsigned int", "numSrcNeurons", @@ -1171,7 +1128,7 @@ SynapseGroupMergedBase::SynapseGroupMergedBase(size_t index, const std::string & // Otherwise (weights are individual or procedural) else { const bool connectInitRole = (role == Role::ConnectivityInit); - const bool varInitRole = (role == Role::DenseInit || role == Role::SparseInit || role == Role::KernelInit); + const bool varInitRole = (role == Role::Init || role == Role::SparseInit); const bool proceduralWeights = (getArchetype().getMatrixType() & SynapseMatrixWeight::PROCEDURAL); const bool kernelWeights = (getArchetype().getMatrixType() & SynapseMatrixWeight::KERNEL); const bool individualWeights = (getArchetype().getMatrixType() & SynapseMatrixWeight::INDIVIDUAL); @@ -1323,7 +1280,7 @@ boost::uuids::detail::sha1::digest_type SynapseGroupMergedBase::getHashDigest(Ro // Otherwise (weights are individual or procedural) else { const bool connectInitRole = (role == Role::ConnectivityInit); - const bool varInitRole = (role == Role::DenseInit || role == Role::SparseInit); + const bool varInitRole = (role == Role::Init || role == Role::SparseInit); const bool proceduralWeights = (getArchetype().getMatrixType() & SynapseMatrixWeight::PROCEDURAL); const bool individualWeights = (getArchetype().getMatrixType() & SynapseMatrixWeight::INDIVIDUAL); const bool kernelWeights = (getArchetype().getMatrixType() & SynapseMatrixWeight::INDIVIDUAL); diff --git a/src/genn/genn/code_generator/initGroupMerged.cc b/src/genn/genn/code_generator/initGroupMerged.cc index 97e2828b0e..41c9f60c36 100644 --- a/src/genn/genn/code_generator/initGroupMerged.cc +++ b/src/genn/genn/code_generator/initGroupMerged.cc @@ -542,77 +542,74 @@ void NeuronInitGroupMerged::genInitSpikeTime(CodeStream &os, const BackendBase & } //---------------------------------------------------------------------------- -// CodeGenerator::SynapseDenseInitGroupMerged +// CodeGenerator::SynapseInitGroupMerged //---------------------------------------------------------------------------- -const std::string SynapseDenseInitGroupMerged::name = "SynapseDenseInit"; +const std::string SynapseInitGroupMerged::name = "SynapseInit"; //---------------------------------------------------------------------------- -void SynapseDenseInitGroupMerged::generateInit(const BackendBase &backend, CodeStream &os, const ModelSpecMerged &modelMerged, Substitutions &popSubs) const +void SynapseInitGroupMerged::generateInit(const BackendBase &backend, CodeStream &os, const ModelSpecMerged &modelMerged, Substitutions &popSubs) const { - // Loop through rows - os << "for(unsigned int i = 0; i < group->numSrcNeurons; i++)"; - { - CodeStream::Scope b(os); + // If model is batched and has kernel weights + const bool kernel = (getArchetype().getMatrixType() & SynapseMatrixWeight::KERNEL); + if (kernel && modelMerged.getModel().getBatchSize() > 1) { + // Loop through kernel dimensions and multiply together to calculate batch stride + os << "const unsigned int batchStride = "; + const auto &kernelSize = getArchetype().getKernelSize(); + for (size_t i = 0; i < kernelSize.size(); i++) { + os << getKernelSize(i); + + if (i != (kernelSize.size() - 1)) { + os << " * "; + } + } + os << ";" << std::endl;; + } + + + // If we're using non-kernel weights, generate loop over source neurons + if (!kernel) { + os << "for(unsigned int i = 0; i < group->numSrcNeurons; i++)"; + os << CodeStream::OB(1); popSubs.addVarSubstitution("id_pre", "i"); - genInitWUVarCode(os, popSubs, getArchetype().getWUModel()->getVars(), - getArchetype().getWUVarInitialisers(), "group->numSrcNeurons * group->rowStride", getIndex(), - modelMerged.getModel().getPrecision(), modelMerged.getModel().getBatchSize(), - [this](size_t v, size_t p) { return isWUVarInitParamHeterogeneous(v, p); }, - [this](size_t v, size_t p) { return isWUVarInitDerivedParamHeterogeneous(v, p); }, - [&backend](CodeStream &os, const Substitutions &kernelSubs, BackendBase::Handler handler) - { - backend.genDenseSynapseVariableRowInit(os, kernelSubs, handler); - }); } -} -//---------------------------------------------------------------------------- -// CodeGenerator::SynapseSparseInitGroupMerged -//---------------------------------------------------------------------------- -const std::string SynapseSparseInitGroupMerged::name = "SynapseSparseInit"; -//---------------------------------------------------------------------------- -void SynapseSparseInitGroupMerged::generateInit(const BackendBase &backend, CodeStream &os, const ModelSpecMerged &modelMerged, Substitutions &popSubs) const -{ + // Generate initialisation code + const std::string stride = kernel ? "batchStride" : "group->numSrcNeurons * group->rowStride"; genInitWUVarCode(os, popSubs, getArchetype().getWUModel()->getVars(), - getArchetype().getWUVarInitialisers(), "group->numSrcNeurons * group->rowStride", getIndex(), + getArchetype().getWUVarInitialisers(), stride, getIndex(), modelMerged.getModel().getPrecision(), modelMerged.getModel().getBatchSize(), [this](size_t v, size_t p) { return isWUVarInitParamHeterogeneous(v, p); }, [this](size_t v, size_t p) { return isWUVarInitDerivedParamHeterogeneous(v, p); }, - [&backend](CodeStream &os, const Substitutions &kernelSubs, BackendBase::Handler handler) + [&backend, kernel, this](CodeStream &os, const Substitutions &kernelSubs, BackendBase::Handler handler) { - backend.genSparseSynapseVariableRowInit(os, kernelSubs, handler); + if (kernel) { + backend.genKernelSynapseVariableInit(os, *this, kernelSubs, handler); + } + else { + backend.genDenseSynapseVariableRowInit(os, kernelSubs, handler); + } }); + + // If we're using non-kernel weights, close loop + if (!kernel) { + os << CodeStream::CB(1); + } } //---------------------------------------------------------------------------- -// CodeGenerator::SynapseKernelInitGroupMerged +// CodeGenerator::SynapseSparseInitGroupMerged //---------------------------------------------------------------------------- -const std::string SynapseKernelInitGroupMerged::name = "SynapseKernelInit"; +const std::string SynapseSparseInitGroupMerged::name = "SynapseSparseInit"; //---------------------------------------------------------------------------- -void SynapseKernelInitGroupMerged::generateInit(const BackendBase &backend, CodeStream &os, const ModelSpecMerged &modelMerged, Substitutions &popSubs) const +void SynapseSparseInitGroupMerged::generateInit(const BackendBase &backend, CodeStream &os, const ModelSpecMerged &modelMerged, Substitutions &popSubs) const { - // If model is batched - if(modelMerged.getModel().getBatchSize() > 1) { - // Loop through kernel dimensions and multiply together to calculate batch stride - os << "const unsigned int batchStride = "; - const auto &kernelSize = getArchetype().getKernelSize(); - for(size_t i = 0; i < kernelSize.size(); i++) { - os << getKernelSize(i); - - if(i != (kernelSize.size() - 1)) { - os << " * "; - } - } - os << ";" << std::endl;; - } - genInitWUVarCode(os, popSubs, getArchetype().getWUModel()->getVars(), - getArchetype().getWUVarInitialisers(), "batchStride", getIndex(), + getArchetype().getWUVarInitialisers(), "group->numSrcNeurons * group->rowStride", getIndex(), modelMerged.getModel().getPrecision(), modelMerged.getModel().getBatchSize(), [this](size_t v, size_t p) { return isWUVarInitParamHeterogeneous(v, p); }, [this](size_t v, size_t p) { return isWUVarInitDerivedParamHeterogeneous(v, p); }, - [&backend, this](CodeStream &os, const Substitutions &kernelSubs, BackendBase::Handler handler) + [&backend](CodeStream &os, const Substitutions &kernelSubs, BackendBase::Handler handler) { - backend.genKernelSynapseVariableInit(os, *this, kernelSubs, handler); + backend.genSparseSynapseVariableRowInit(os, kernelSubs, handler); }); } @@ -749,63 +746,110 @@ void CustomUpdateInitGroupMerged::generateInit(const BackendBase &backend, CodeS } // ---------------------------------------------------------------------------- -// CustomWUUpdateDenseInitGroupMerged +// CustomWUUpdateInitGroupMerged //---------------------------------------------------------------------------- -const std::string CustomWUUpdateDenseInitGroupMerged::name = "CustomWUUpdateDenseInit"; +const std::string CustomWUUpdateInitGroupMerged::name = "CustomWUUpdateInit"; //---------------------------------------------------------------------------- -CustomWUUpdateDenseInitGroupMerged::CustomWUUpdateDenseInitGroupMerged(size_t index, const std::string &precision, const std::string &, const BackendBase &backend, - const std::vector> &groups) +CustomWUUpdateInitGroupMerged::CustomWUUpdateInitGroupMerged(size_t index, const std::string &precision, const std::string &, const BackendBase &backend, + const std::vector> &groups) : CustomUpdateInitGroupMergedBase(index, precision, backend, groups) { - addField("unsigned int", "rowStride", - [&backend](const CustomUpdateWUInternal &cg, size_t) { return std::to_string(backend.getSynapticMatrixRowStride(*cg.getSynapseGroup())); }); - - addField("unsigned int", "numSrcNeurons", - [](const CustomUpdateWUInternal &cg, size_t) { return std::to_string(cg.getSynapseGroup()->getSrcNeuronGroup()->getNumNeurons()); }); - addField("unsigned int", "numTrgNeurons", - [](const CustomUpdateWUInternal &cg, size_t) { return std::to_string(cg.getSynapseGroup()->getTrgNeuronGroup()->getNumNeurons()); }); + if(getArchetype().getSynapseGroup()->getMatrixType() & SynapseMatrixWeight::KERNEL) { + // Loop through kernel size dimensions + for (size_t d = 0; d < getArchetype().getSynapseGroup()->getKernelSize().size(); d++) { + // If this dimension has a heterogeneous size, add it to struct + if (isKernelSizeHeterogeneous(d)) { + addField("unsigned int", "kernelSize" + std::to_string(d), + [d](const CustomUpdateWUInternal &g, size_t) { return std::to_string(g.getSynapseGroup()->getKernelSize().at(d)); }); + } + } + } + else { + addField("unsigned int", "rowStride", + [&backend](const CustomUpdateWUInternal &cg, size_t) { return std::to_string(backend.getSynapticMatrixRowStride(*cg.getSynapseGroup())); }); + addField("unsigned int", "numSrcNeurons", + [](const CustomUpdateWUInternal &cg, size_t) { return std::to_string(cg.getSynapseGroup()->getSrcNeuronGroup()->getNumNeurons()); }); + addField("unsigned int", "numTrgNeurons", + [](const CustomUpdateWUInternal &cg, size_t) { return std::to_string(cg.getSynapseGroup()->getTrgNeuronGroup()->getNumNeurons()); }); + } } //---------------------------------------------------------------------------- -boost::uuids::detail::sha1::digest_type CustomWUUpdateDenseInitGroupMerged::getHashDigest() const +boost::uuids::detail::sha1::digest_type CustomWUUpdateInitGroupMerged::getHashDigest() const { boost::uuids::detail::sha1 hash; // Update hash with generic custom update init data updateBaseHash(hash); - // Update hash with sizes of pre and postsynaptic neuron groups - updateHash([](const CustomUpdateWUInternal &cg) - { - return static_cast(cg.getSynapseGroup())->getSrcNeuronGroup()->getNumNeurons(); - }, hash); + // If underlying synapse group has kernel weights, update hash with kernel size + if(getArchetype().getSynapseGroup()->getMatrixType() & SynapseMatrixWeight::KERNEL) { + updateHash([](const CustomUpdateWUInternal &g) { return g.getSynapseGroup()->getKernelSize(); }, hash); + } + // Otherwise, update hash with sizes of pre and postsynaptic neuron groups + else { + updateHash([](const CustomUpdateWUInternal &cg) + { + return static_cast(cg.getSynapseGroup())->getSrcNeuronGroup()->getNumNeurons(); + }, hash); - updateHash([](const CustomUpdateWUInternal &cg) - { - return static_cast(cg.getSynapseGroup())->getTrgNeuronGroup()->getNumNeurons(); - }, hash); + updateHash([](const CustomUpdateWUInternal &cg) + { + return static_cast(cg.getSynapseGroup())->getTrgNeuronGroup()->getNumNeurons(); + }, hash); - // **TODO** rowstride + updateHash([](const CustomUpdateWUInternal &cg) + { + return static_cast(cg.getSynapseGroup())->getMaxConnections(); + }, hash); + } return hash.get_digest(); } // ---------------------------------------------------------------------------- -void CustomWUUpdateDenseInitGroupMerged::generateInit(const BackendBase &backend, CodeStream &os, const ModelSpecMerged &modelMerged, Substitutions &popSubs) const +void CustomWUUpdateInitGroupMerged::generateInit(const BackendBase &backend, CodeStream &os, const ModelSpecMerged &modelMerged, Substitutions &popSubs) const { - // Loop through rows - os << "for(unsigned int i = 0; i < group->numSrcNeurons; i++)"; - { - CodeStream::Scope b(os); + const bool kernel = (getArchetype().getSynapseGroup()->getMatrixType() & SynapseMatrixWeight::KERNEL); + if(kernel && modelMerged.getModel().getBatchSize() > 1) { + // Loop through kernel dimensions and multiply together to calculate batch stride + os << "const unsigned int batchStride = "; + const auto &kernelSize = getArchetype().getSynapseGroup()->getKernelSize(); + for (size_t i = 0; i < kernelSize.size(); i++) { + os << getKernelSize(i); + + if (i != (kernelSize.size() - 1)) { + os << " * "; + } + } + os << ";" << std::endl; + } + + if(!kernel) { + os << "for(unsigned int i = 0; i < group->numSrcNeurons; i++)"; + os << CodeStream::OB(3); popSubs.addVarSubstitution("id_pre", "i"); - genInitWUVarCode(os, popSubs, getArchetype().getCustomUpdateModel()->getVars(), - getArchetype().getVarInitialisers(), "group->numSrcNeurons * group->rowStride", getIndex(), - modelMerged.getModel().getPrecision(), getArchetype().isBatched() ? modelMerged.getModel().getBatchSize() : 1, - [this](size_t v, size_t p) { return isVarInitParamHeterogeneous(v, p); }, - [this](size_t v, size_t p) { return isVarInitDerivedParamHeterogeneous(v, p); }, - [&backend](CodeStream &os, const Substitutions &kernelSubs, BackendBase::Handler handler) - { - return backend.genDenseSynapseVariableRowInit(os, kernelSubs, handler); - }); + } + + // Loop through rows + const std::string stride = kernel ? "batchStride" : "group->numSrcNeurons * group->rowStride"; + genInitWUVarCode(os, popSubs, getArchetype().getCustomUpdateModel()->getVars(), + getArchetype().getVarInitialisers(), stride, getIndex(), + modelMerged.getModel().getPrecision(), getArchetype().isBatched() ? modelMerged.getModel().getBatchSize() : 1, + [this](size_t v, size_t p) { return isVarInitParamHeterogeneous(v, p); }, + [this](size_t v, size_t p) { return isVarInitDerivedParamHeterogeneous(v, p); }, + [&backend, kernel, this](CodeStream &os, const Substitutions &kernelSubs, BackendBase::Handler handler) + { + if (kernel) { + backend.genKernelCustomUpdateVariableInit(os, *this, kernelSubs, handler); + } + else { + backend.genDenseSynapseVariableRowInit(os, kernelSubs, handler); + } + + }); + + if(!kernel) { + os << CodeStream::CB(3); } } @@ -847,7 +891,7 @@ boost::uuids::detail::sha1::digest_type CustomWUUpdateSparseInitGroupMerged::get // Update hash with generic custom update init data updateBaseHash(hash); - // Update hash with sizes of pre and postsynaptic neuron groups + // Update hash with sizes of pre and postsynaptic neuron groups; and max row length updateHash([](const CustomUpdateWUInternal &cg) { return static_cast(cg.getSynapseGroup())->getSrcNeuronGroup()->getNumNeurons(); @@ -858,8 +902,10 @@ boost::uuids::detail::sha1::digest_type CustomWUUpdateSparseInitGroupMerged::get return static_cast(cg.getSynapseGroup())->getTrgNeuronGroup()->getNumNeurons(); }, hash); - - // **TODO** rowstride + updateHash([](const CustomUpdateWUInternal& cg) + { + return cg.getSynapseGroup()->getMaxConnections(); + }, hash); return hash.get_digest(); } diff --git a/src/genn/genn/code_generator/modelSpecMerged.cc b/src/genn/genn/code_generator/modelSpecMerged.cc index 4486fffc27..2aa9beb7be 100644 --- a/src/genn/genn/code_generator/modelSpecMerged.cc +++ b/src/genn/genn/code_generator/modelSpecMerged.cc @@ -56,24 +56,31 @@ ModelSpecMerged::ModelSpecMerged(const ModelSpecInternal &model, const BackendBa createMergedGroupsHash(model, backend, model.getNeuronGroups(), m_MergedNeuronInitGroups, [](const NeuronGroupInternal &){ return true; }, &NeuronGroupInternal::getInitHashDigest); + + LOGD_CODE_GEN << "Merging synapse initialization groups:"; + createMergedGroupsHash(model, backend, model.getSynapseGroups(), m_MergedSynapseInitGroups, + [](const SynapseGroupInternal &sg) + { + return (((sg.getMatrixType() & SynapseMatrixConnectivity::DENSE) + || (sg.getMatrixType() & SynapseMatrixWeight::KERNEL)) + && sg.isWUVarInitRequired()); + }, + &SynapseGroupInternal::getWUInitHashDigest); LOGD_CODE_GEN << "Merging custom update initialization groups:"; createMergedGroupsHash(model, backend, model.getCustomUpdates(), m_MergedCustomUpdateInitGroups, [](const CustomUpdateInternal &cg) { return cg.isVarInitRequired(); }, &CustomUpdateInternal::getInitHashDigest); - LOGD_CODE_GEN << "Merging custom dense weight update initialization groups:"; - createMergedGroupsHash(model, backend, model.getCustomWUUpdates(), m_MergedCustomWUUpdateDenseInitGroups, - [](const CustomUpdateWUInternal &cg) { return (cg.getSynapseGroup()->getMatrixType() & SynapseMatrixConnectivity::DENSE) && cg.isVarInitRequired(); }, - &CustomUpdateWUInternal::getInitHashDigest); - - LOGD_CODE_GEN << "Merging synapse dense initialization groups:"; - createMergedGroupsHash(model, backend, model.getSynapseGroups(), m_MergedSynapseDenseInitGroups, - [](const SynapseGroupInternal &sg) + LOGD_CODE_GEN << "Merging custom weight update initialization groups:"; + createMergedGroupsHash(model, backend, model.getCustomWUUpdates(), m_MergedCustomWUUpdateInitGroups, + [](const CustomUpdateWUInternal &cg) { - return ((sg.getMatrixType() & SynapseMatrixConnectivity::DENSE) && sg.isWUVarInitRequired()); + return (((cg.getSynapseGroup()->getMatrixType() & SynapseMatrixConnectivity::DENSE) + || (cg.getSynapseGroup()->getMatrixType() & SynapseMatrixWeight::KERNEL)) + && cg.isVarInitRequired()); }, - &SynapseGroupInternal::getWUInitHashDigest); + &CustomUpdateWUInternal::getInitHashDigest); LOGD_CODE_GEN << "Merging synapse connectivity initialisation groups:"; createMergedGroupsHash(model, backend, model.getSynapseGroups(), m_MergedSynapseConnectivityInitGroups, @@ -92,17 +99,12 @@ ModelSpecMerged::ModelSpecMerged(const ModelSpecInternal &model, const BackendBa LOGD_CODE_GEN << "Merging custom sparse weight update initialization groups:"; createMergedGroupsHash(model, backend, model.getCustomWUUpdates(), m_MergedCustomWUUpdateSparseInitGroups, - [](const CustomUpdateWUInternal &cg) { return (cg.getSynapseGroup()->getMatrixType() & SynapseMatrixConnectivity::SPARSE) && cg.isVarInitRequired(); }, - &CustomUpdateWUInternal::getInitHashDigest); - - LOGD_CODE_GEN << "Merging synapse kernel initialization groups:"; - createMergedGroupsHash(model, backend, model.getSynapseGroups(), m_MergedSynapseKernelInitGroups, - [&backend](const SynapseGroupInternal &sg) + [](const CustomUpdateWUInternal &cg) { - return ((sg.getMatrixType() & SynapseMatrixWeight::KERNEL) && sg.isWUVarInitRequired()); + return (cg.getSynapseGroup()->getMatrixType() & SynapseMatrixConnectivity::SPARSE) && cg.isVarInitRequired(); }, - &SynapseGroupInternal::getWUInitHashDigest); - + &CustomUpdateWUInternal::getInitHashDigest); + LOGD_CODE_GEN << "Merging neuron groups which require their spike queues updating:"; createMergedGroupsHash(model, backend, model.getNeuronGroups(), m_MergedNeuronSpikeQueueUpdateGroups, [](const NeuronGroupInternal &){ return true; }, @@ -211,13 +213,12 @@ ModelSpecMerged::ModelSpecMerged(const ModelSpecInternal &model, const BackendBa // Loop through init groups and assign memory spaces assignGroups(backend, m_MergedNeuronInitGroups, memorySpaces); - assignGroups(backend, m_MergedSynapseDenseInitGroups, memorySpaces); + assignGroups(backend, m_MergedSynapseInitGroups, memorySpaces); assignGroups(backend, m_MergedSynapseSparseInitGroups, memorySpaces); - assignGroups(backend, m_MergedSynapseKernelInitGroups, memorySpaces); assignGroups(backend, m_MergedSynapseConnectivityInitGroups, memorySpaces); assignGroups(backend, m_MergedCustomUpdateInitGroups, memorySpaces); - assignGroups(backend, m_MergedCustomWUUpdateDenseInitGroups, memorySpaces); - assignGroups(backend, m_MergedCustomWUUpdateSparseInitGroups, memorySpaces); + assignGroups(backend, m_MergedCustomWUUpdateInitGroups, memorySpaces); + assignGroups(backend, m_MergedCustomWUUpdateSparseInitGroups, memorySpaces); } //---------------------------------------------------------------------------- boost::uuids::detail::sha1::digest_type ModelSpecMerged::getHashDigest(const BackendBase &backend) const @@ -277,8 +278,8 @@ boost::uuids::detail::sha1::digest_type ModelSpecMerged::getHashDigest(const Bac Utils::updateHash(g.getHashDigest(), hash); } - // Concatenate hash digest of synapse dense init groups - for(const auto &g : m_MergedSynapseDenseInitGroups) { + // Concatenate hash digest of synapse init groups + for(const auto &g : m_MergedSynapseInitGroups) { Utils::updateHash(g.getHashDigest(), hash); } @@ -286,12 +287,6 @@ boost::uuids::detail::sha1::digest_type ModelSpecMerged::getHashDigest(const Bac for(const auto &g : m_MergedSynapseSparseInitGroups) { Utils::updateHash(g.getHashDigest(), hash); } - - // Update hash with hash digest of synapse sparse init groups - for(const auto &g : m_MergedSynapseKernelInitGroups) { - Utils::updateHash(g.getHashDigest(), hash); - } - // Concatenate hash digest of synapse connectivity init groups for(const auto &g : m_MergedSynapseConnectivityInitGroups) { Utils::updateHash(g.getHashDigest(), hash); @@ -302,8 +297,8 @@ boost::uuids::detail::sha1::digest_type ModelSpecMerged::getHashDigest(const Bac Utils::updateHash(g.getHashDigest(), hash); } - // Concatenate hash digest of custom dense WU update init groups - for(const auto &g : m_MergedCustomWUUpdateDenseInitGroups) { + // Concatenate hash digest of custom WU update init groups + for(const auto &g : m_MergedCustomWUUpdateInitGroups) { Utils::updateHash(g.getHashDigest(), hash); } @@ -455,7 +450,7 @@ boost::uuids::detail::sha1::digest_type ModelSpecMerged::getInitArchetypeHashDig } // Concatenate hash digest of archetype synapse dense init group - for(const auto &g : m_MergedSynapseDenseInitGroups) { + for(const auto &g : m_MergedSynapseInitGroups) { Utils::updateHash(g.getArchetype().getWUInitHashDigest(), hash); } @@ -474,8 +469,8 @@ boost::uuids::detail::sha1::digest_type ModelSpecMerged::getInitArchetypeHashDig Utils::updateHash(g.getArchetype().getInitHashDigest(), hash); } - // Concatenate hash digest of archetype custom dense WU update init group - for(const auto &g : m_MergedCustomWUUpdateDenseInitGroups) { + // Concatenate hash digest of archetype custom WU update init group + for(const auto &g : m_MergedCustomWUUpdateInitGroups) { Utils::updateHash(g.getArchetype().getInitHashDigest(), hash); } diff --git a/src/genn/genn/models.cc b/src/genn/genn/models.cc index c6a8ae89cc..663a7889d1 100644 --- a/src/genn/genn/models.cc +++ b/src/genn/genn/models.cc @@ -115,18 +115,19 @@ WUVarReference::WUVarReference(const SynapseGroup *sg, const std::string &varNam m_TransposeVar((transposeSG == nullptr) ? Models::Base::Var() : transposeSG->getWUModel()->getVars().at(m_TransposeVarIndex)), m_GetTransposeTargetName((transposeSG == nullptr) ? GetTargetNameFn() : [transposeSG]() { return transposeSG->getName(); }) { - if(!(sg->getMatrixType() & SynapseMatrixWeight::INDIVIDUAL)) { - throw std::runtime_error("Only INDIVIDUAL weight update models can be referenced."); + if(!(sg->getMatrixType() & SynapseMatrixWeight::INDIVIDUAL) && !(sg->getMatrixType() & SynapseMatrixWeight::KERNEL)) { + throw std::runtime_error("Only INDIVIDUAL or KERNEL weight update variables can be referenced."); } if(sg->isWeightSharingSlave()) { throw std::runtime_error("Only weight update model variables in weight sharing master synapse group can be referenced."); } + // If a transpose synapse group is specified if(m_TransposeSG != nullptr) { - // Check that tranpose group also has individual variables - if(!(m_TransposeSG->getMatrixType() & SynapseMatrixWeight::INDIVIDUAL)) { - throw std::runtime_error("Only INDIVIDUAL weight update models can be referenced."); + // Check that both tranpose and original group has individual variables + if(!(m_TransposeSG->getMatrixType() & SynapseMatrixWeight::INDIVIDUAL) || !(sg->getMatrixType() & SynapseMatrixWeight::INDIVIDUAL)) { + throw std::runtime_error("Transpose updates can only reference INDIVIDUAL weight update variables."); } // Check that both the tranpose and main synapse groups have dense connectivity @@ -146,7 +147,7 @@ WUVarReference::WUVarReference(const SynapseGroup *sg, const std::string &varNam throw std::runtime_error("Transpose updates can only be performed on variables with the same type"); } - // Check duplicatedness of varibles + // Check duplicatedness of variables if((getVar().access & VarAccessDuplication::DUPLICATE) != (getTransposeVar().access & VarAccessDuplication::DUPLICATE)) { throw std::runtime_error("Transpose updates can only be performed on similarly batched variables"); } diff --git a/tests/features/custom_update/model.cc b/tests/features/custom_update/model.cc index 1e7b4d20b5..8d4f12d68d 100644 --- a/tests/features/custom_update/model.cc +++ b/tests/features/custom_update/model.cc @@ -86,6 +86,11 @@ void modelDefinition(ModelSpec &model) model.setDT(1.0); model.setName("custom_update"); + InitToeplitzConnectivitySnippet::Conv2D::ParamValues convParams( + 3, 3, // conv_kh, conv_kw + 10, 10, 1, // conv_ih, conv_iw, conv_ic + 10, 10, 1); // conv_oh, conv_ow, conv_oc + model.addNeuronPopulation("SpikeSource", 100, {}, {}); auto *ng = model.addNeuronPopulation("Neuron", 100, {}, {0.0}); auto *cs = model.addCurrentSource("CurrentSource", "Neuron", {}, {0.0}); @@ -97,9 +102,16 @@ void modelDefinition(ModelSpec &model) auto *sparseSG = model.addSynapsePopulation( "Sparse", SynapseMatrixType::SPARSE_INDIVIDUALG, NO_DELAY, "SpikeSource", "Neuron", - {}, {0}, {0.0}, {0.0}, - {}, {0}, + {}, {0.0}, {0.0}, {0.0}, + {}, {0.0}, initConnectivity({0.1})); + auto *kernelSG = model.addSynapsePopulation( + "Kernel", SynapseMatrixType::TOEPLITZ_KERNELG, NO_DELAY, + "SpikeSource", "Neuron", + {}, {0.0}, {0.0}, {0.0}, + {}, {0.0}, + initToeplitzConnectivity(convParams)); + TestCU::VarReferences cuTestVarReferences(createVarRef(ng, "V")); auto *cu = model.addCustomUpdate("CustomUpdate", "Test2", {}, {0.0}, cuTestVarReferences); @@ -141,6 +153,10 @@ void modelDefinition(ModelSpec &model) model.addCustomUpdate("WUSparseSetTime", "Test", {}, {0.0}, wuSparseVarReferences); + SetTime::WUVarReferences wuKernelVarReferences(createWUVarRef(kernelSG, "g")); // R + model.addCustomUpdate("WUKernelSetTime", "Test", + {}, {0.0}, wuKernelVarReferences); + SetTime::WUVarReferences wuCUVarReferences(createWUVarRef(cuWU, "C")); // R model.addCustomUpdate("CustomWUUpdateSetTime", "Test", {}, {0.0}, wuCUVarReferences); diff --git a/tests/features/custom_update/test.cc b/tests/features/custom_update/test.cc index 5b7397cd5c..741b96f0b2 100644 --- a/tests/features/custom_update/test.cc +++ b/tests/features/custom_update/test.cc @@ -54,6 +54,8 @@ TEST_F(SimTest, CustomUpdate) pullVWUSparseSetTimeFromDevice(); pullgSparseFromDevice(); pullSparseConnectivityFromDevice(); + pullVWUKernelSetTimeFromDevice(); + pullgKernelFromDevice(); pullVCustomWUUpdateSetTimeFromDevice(); pullCCustomWUUpdateFromDevice(); @@ -98,6 +100,11 @@ TEST_F(SimTest, CustomUpdate) EXPECT_TRUE(std::all_of(&gDense[0], &gDense[100 * 100], [](scalar v) { return v == t; })); + EXPECT_TRUE(std::all_of(&VWUKernelSetTime[0], &VWUKernelSetTime[3 * 3], + [](scalar v) { return v == t; })); + EXPECT_TRUE(std::all_of(&gKernel[0], &gKernel[3 * 3], + [](scalar v) { return v == t; })); + EXPECT_TRUE(std::all_of(&VCustomWUUpdateSetTime[0], &VCustomWUUpdateSetTime[100 * 100], [](scalar v) { return v == t; })); EXPECT_TRUE(std::all_of(&CCustomWUUpdate[0], &CCustomWUUpdate[100 * 100], diff --git a/tests/features/custom_update_batch/model.cc b/tests/features/custom_update_batch/model.cc index ecd0dd69c8..911513c1bc 100644 --- a/tests/features/custom_update_batch/model.cc +++ b/tests/features/custom_update_batch/model.cc @@ -31,7 +31,7 @@ class SetTimeBatch : public CustomUpdateModels::Base { public: DECLARE_CUSTOM_UPDATE_MODEL(SetTimeBatch, 0, 1, 1); - + SET_UPDATE_CODE( "$(V) = ($(batch) * 1000.0) + $(t);\n" "$(R) = ($(batch) * 1000.0) + $(t);\n"); @@ -45,7 +45,7 @@ class SetTime : public CustomUpdateModels::Base { public: DECLARE_CUSTOM_UPDATE_MODEL(SetTime, 0, 1, 1); - + SET_UPDATE_CODE( "$(R) = $(V) + ($(batch) * 1000.0) + $(t);\n"); @@ -54,6 +54,35 @@ class SetTime : public CustomUpdateModels::Base }; IMPLEMENT_MODEL(SetTime); +class OneToOneKernel : public InitSparseConnectivitySnippet::Base +{ +public: + DECLARE_SNIPPET(OneToOneKernel, 2); + + SET_PARAM_NAMES({"conv_kh", "conv_kw"}); + + SET_ROW_BUILD_CODE( + "for(int i = 0; i < (int)$(conv_kw); i++) {\n" + " if(($(id_pre) + i) < $(num_post)) {\n" + " $(addSynapse, $(id_pre) + i, $(id_pre) % (int)$(conv_kh), i);\n" + " }\n" + "}\n" + "$(endRow);\n"); + + SET_CALC_MAX_ROW_LENGTH_FUNC( + [](unsigned int, unsigned int, const std::vector &pars) + { + return (unsigned int)pars[1]; + }); + + SET_CALC_KERNEL_SIZE_FUNC( + [](const std::vector &pars)->std::vector + { + return {(unsigned int)pars[0], (unsigned int)pars[1]}; + }); +}; +IMPLEMENT_SNIPPET(OneToOneKernel); + void modelDefinition(ModelSpec &model) { #ifdef CL_HPP_TARGET_OPENCL_VERSION @@ -82,6 +111,12 @@ void modelDefinition(ModelSpec &model) {}, {0.0, 0.0}, {}, {}, initConnectivity({0.1})); + auto *kernelSG = model.addSynapsePopulation( + "Kernel", SynapseMatrixType::PROCEDURAL_KERNELG, NO_DELAY, + "SpikeSource", "Neuron", + {}, {0.0, 0.0}, + {}, {}, + initConnectivity({3, 3})); //--------------------------------------------------------------------------- // Custom updates @@ -109,5 +144,13 @@ void modelDefinition(ModelSpec &model) SetTime::WUVarReferences wumSparseSharedVarReferences(createWUVarRef(sparseSG, "U")); // R model.addCustomUpdate("WUMSparseSharedSetTime", "Test", {}, {0.0}, wumSparseSharedVarReferences); + + SetTimeBatch::WUVarReferences wumKernelDuplicateVarReferences(createWUVarRef(kernelSG, "V")); // R + model.addCustomUpdate("WUMKernelDuplicateSetTime", "Test", + {}, {0.0}, wumKernelDuplicateVarReferences); + + SetTime::WUVarReferences wumKernelSharedVarReferences(createWUVarRef(kernelSG, "U")); // R + model.addCustomUpdate("WUMKernelSharedSetTime", "Test", + {}, {0.0}, wumKernelSharedVarReferences); } \ No newline at end of file diff --git a/tests/features/custom_update_batch/test.cc b/tests/features/custom_update_batch/test.cc index 1caa72d8e4..2286c7acd7 100644 --- a/tests/features/custom_update_batch/test.cc +++ b/tests/features/custom_update_batch/test.cc @@ -52,19 +52,23 @@ TEST_F(SimTest, CustomUpdateBatch) pullVNeuronDuplicateSetTimeFromDevice(); pullVWUMDenseDuplicateSetTimeFromDevice(); pullVWUMSparseDuplicateSetTimeFromDevice(); + pullVWUMKernelDuplicateSetTimeFromDevice(); pullVNeuronFromDevice(); pullUNeuronFromDevice(); pullVDenseFromDevice(); pullUDenseFromDevice(); pullVSparseFromDevice(); pullUSparseFromDevice(); - + pullVKernelFromDevice(); + pullUKernelFromDevice(); + // Check shared neuron and synapse variables match time ASSERT_TRUE(std::all_of(&UNeuron[0], &UNeuron[50], [](scalar v) { return v == t; })); ASSERT_TRUE(std::all_of(&UDense[0], &UDense[50 * 50], [](scalar v) { return v == t; })); - + ASSERT_TRUE(std::all_of(&UKernel[0], &UKernel[3 * 3], + [](scalar v) { return v == t; })); checkSparseVar(USparse, [](scalar v){ return v == t; }); // Loop through batches @@ -73,6 +77,8 @@ TEST_F(SimTest, CustomUpdateBatch) const unsigned int endNeuronIdx = startNeuronIdx + 50; const unsigned int startDenseSynIdx = b * (50 * 50); const unsigned int endDenseSynIdx = startDenseSynIdx + (50 * 50); + const unsigned int startKernelIdx = b * (3 * 3); + const unsigned int endKernelIdx = startKernelIdx + (3 * 3); const unsigned int startSparseSynIdx = b * (50 * maxRowLengthSparse); const float batchOffset = b * 1000.0f; @@ -86,11 +92,16 @@ TEST_F(SimTest, CustomUpdateBatch) [batchOffset](scalar v) { return v == (batchOffset + t); })); ASSERT_TRUE(std::all_of(&VDense[startDenseSynIdx], &VDense[endDenseSynIdx], [batchOffset](scalar v) { return v == (batchOffset + t); })); - + + ASSERT_TRUE(std::all_of(&VWUMKernelDuplicateSetTime[startKernelIdx], &VWUMKernelDuplicateSetTime[endKernelIdx], + [batchOffset](scalar v) { return v == (batchOffset + t); })); + ASSERT_TRUE(std::all_of(&VKernel[startKernelIdx], &VKernel[endKernelIdx], + [batchOffset](scalar v) { return v == (batchOffset + t); })); + checkSparseVar(&VSparse[startSparseSynIdx], [batchOffset](scalar v) { return v == (batchOffset + t); }); - checkSparseVar(&VWUMSparseDuplicateSetTime[startSparseSynIdx], - [batchOffset](scalar v) { return v == (batchOffset + t); }); + checkSparseVar(&VWUMSparseDuplicateSetTime[startSparseSynIdx], + [batchOffset](scalar v) { return v == (batchOffset + t); }); } } diff --git a/tests/features/var_init/model.cc b/tests/features/var_init/model.cc index f0321913bc..305d438e4d 100644 --- a/tests/features/var_init/model.cc +++ b/tests/features/var_init/model.cc @@ -84,35 +84,6 @@ class NopCustomUpdateModel : public CustomUpdateModels::Base }; IMPLEMENT_MODEL(NopCustomUpdateModel); -class OneToOneKernel : public InitSparseConnectivitySnippet::Base -{ -public: - DECLARE_SNIPPET(OneToOneKernel, 2); - - SET_PARAM_NAMES({"conv_kh", "conv_kw"}); - - SET_ROW_BUILD_CODE( - "for(int i = 0; i < (int)$(conv_kw); i++) {\n" - " if(($(id_pre) + i) < $(num_post)) {\n" - " $(addSynapse, $(id_pre) + i, $(id_pre) % (int)$(conv_kh), i);\n" - " }\n" - "}\n" - "$(endRow);\n"); - - SET_CALC_MAX_ROW_LENGTH_FUNC( - [](unsigned int, unsigned int, const std::vector &pars) - { - return (unsigned int)pars[1]; - }); - - SET_CALC_KERNEL_SIZE_FUNC( - [](const std::vector &pars)->std::vector - { - return {(unsigned int)pars[0], (unsigned int)pars[1]}; - }); -}; -IMPLEMENT_SNIPPET(OneToOneKernel); - void modelDefinition(ModelSpec &model) { #ifdef CL_HPP_TARGET_OPENCL_VERSION @@ -207,9 +178,10 @@ void modelDefinition(ModelSpec &model) initVar(gammaParams), initVar(binomialParams)); - OneToOneKernel::ParamValues oneToOneKernelParams( - 3, - 3); + InitToeplitzConnectivitySnippet::Conv2D::ParamValues convParams( + 3, 3, // conv_kh, conv_kw + 100, 100, 5, // conv_ih, conv_iw, conv_ic + 100, 100, 5); // conv_oh, conv_ow, conv_oc // Neuron populations model.addNeuronPopulation("SpikeSource1", 1, {}, {}); @@ -232,12 +204,12 @@ void modelDefinition(ModelSpec &model) {}, postsynapticInit, initConnectivity()); - model.addSynapsePopulation( - "Kernel", SynapseMatrixType::PROCEDURAL_KERNELG, NO_DELAY, - "SpikeSource1", "Pop", + SynapseGroup *sgKernel = model.addSynapsePopulation( + "Kernel", SynapseMatrixType::TOEPLITZ_KERNELG, NO_DELAY, + "SpikeSource2", "Pop", {}, weightUpdateInit, {}, {}, {}, {}, - initConnectivity(oneToOneKernelParams)); + initToeplitzConnectivity(convParams)); // Custom updates NopCustomUpdateModel::VarReferences neuronVarReferences(createVarRef(ng, "constant_val")); // R @@ -267,6 +239,10 @@ void modelDefinition(ModelSpec &model) NopCustomUpdateModel::WUVarReferences wuDenseVarReferences(createWUVarRef(sgDense, "constant_val")); // R model.addCustomUpdate("WUDenseCustomUpdate", "Test", {}, customUpdateInit, wuDenseVarReferences); + + NopCustomUpdateModel::WUVarReferences wuKernelVarReferences(createWUVarRef(sgKernel, "constant_val")); // R + model.addCustomUpdate("WUKernelCustomUpdate", "Test", + {}, customUpdateInit, wuKernelVarReferences); model.setPrecision(GENN_FLOAT); } diff --git a/tests/features/var_init/test.cc b/tests/features/var_init/test.cc index a1e95699a0..c37fb00c3d 100644 --- a/tests/features/var_init/test.cc +++ b/tests/features/var_init/test.cc @@ -81,6 +81,9 @@ TEST_F(SimTest, Vars) pullPSMCustomUpdateStateFromDevice(); pullWUPreCustomUpdateStateFromDevice(); pullWUPostCustomUpdateStateFromDevice(); + pullWUSparseCustomUpdateStateFromDevice(); + pullWUDenseCustomUpdateStateFromDevice(); + pullWUKernelCustomUpdateStateFromDevice(); // Test host-generated vars PROB_TEST(, Pop, 50000); @@ -90,11 +93,14 @@ TEST_F(SimTest, Vars) PROB_TEST(, Sparse, 50000); PROB_TEST(pre_, Sparse, 50000); PROB_TEST(post_, Sparse, 50000); - PROB_TEST(, Kernel, 9); + PROB_TEST(, Kernel, 3 * 3 * 5 * 5); PROB_TEST(, NeuronCustomUpdate, 50000); PROB_TEST(, CurrentSourceCustomUpdate, 50000); PROB_TEST(, PSMCustomUpdate, 50000); PROB_TEST(, WUPreCustomUpdate, 50000); PROB_TEST(, WUPostCustomUpdate, 50000); + PROB_TEST(, WUDenseCustomUpdate, 50000); + PROB_TEST(, WUSparseCustomUpdate, 50000); + PROB_TEST(, WUKernelCustomUpdate, 3 * 3 * 5 * 5); } diff --git a/tests/unit/customUpdate.cc b/tests/unit/customUpdate.cc index 944d1eab2c..538aa7e5ee 100644 --- a/tests/unit/customUpdate.cc +++ b/tests/unit/customUpdate.cc @@ -558,7 +558,7 @@ TEST(CustomUpdates, CompareDifferentWUTranspose) // **NOTE** transpose variables don't matter for initialization ASSERT_TRUE(modelSpecMerged.getMergedCustomUpdateTransposeWUGroups().size() == 2); ASSERT_TRUE(modelSpecMerged.getMergedCustomUpdateWUGroups().empty()); - ASSERT_TRUE(modelSpecMerged.getMergedCustomWUUpdateDenseInitGroups().size() == 1); + ASSERT_TRUE(modelSpecMerged.getMergedCustomWUUpdateInitGroups().size() == 1); ASSERT_TRUE(modelSpecMerged.getMergedCustomWUUpdateSparseInitGroups().empty()); } //-------------------------------------------------------------------------- @@ -609,7 +609,7 @@ TEST(CustomUpdates, CompareDifferentWUConnectivity) // Check correct groups are merged ASSERT_TRUE(modelSpecMerged.getMergedCustomUpdateTransposeWUGroups().empty()); ASSERT_TRUE(modelSpecMerged.getMergedCustomUpdateWUGroups().size() == 2); - ASSERT_TRUE(modelSpecMerged.getMergedCustomWUUpdateDenseInitGroups().size() == 1); + ASSERT_TRUE(modelSpecMerged.getMergedCustomWUUpdateInitGroups().size() == 1); ASSERT_TRUE(modelSpecMerged.getMergedCustomWUUpdateSparseInitGroups().size() == 1); } //-------------------------------------------------------------------------- @@ -653,4 +653,4 @@ TEST(CustomUpdates, InvalidUpdateGroupName) } catch(const std::runtime_error &) { } -} \ No newline at end of file +} diff --git a/tests/unit/synapseGroup.cc b/tests/unit/synapseGroup.cc index b886c4fd68..d8cee2ade5 100644 --- a/tests/unit/synapseGroup.cc +++ b/tests/unit/synapseGroup.cc @@ -430,7 +430,7 @@ TEST(SynapseGroup, CompareWUDifferentGlobalG) ASSERT_TRUE(modelSpecMerged.getMergedNeuronUpdateGroups().size() == 2); ASSERT_TRUE(modelSpecMerged.getMergedPresynapticUpdateGroups().size() == 1); ASSERT_TRUE(modelSpecMerged.getMergedSynapseConnectivityInitGroups().empty()); - ASSERT_TRUE(modelSpecMerged.getMergedSynapseDenseInitGroups().empty()); + ASSERT_TRUE(modelSpecMerged.getMergedSynapseInitGroups().empty()); ASSERT_TRUE(modelSpecMerged.getMergedSynapseSparseInitGroups().empty()); // Check that global g var is heterogeneous @@ -485,7 +485,7 @@ TEST(SynapseGroup, CompareWUDifferentProceduralConnectivity) ASSERT_TRUE(modelSpecMerged.getMergedNeuronUpdateGroups().size() == 2); ASSERT_TRUE(modelSpecMerged.getMergedPresynapticUpdateGroups().size() == 1); ASSERT_TRUE(modelSpecMerged.getMergedSynapseConnectivityInitGroups().empty()); - ASSERT_TRUE(modelSpecMerged.getMergedSynapseDenseInitGroups().empty()); + ASSERT_TRUE(modelSpecMerged.getMergedSynapseInitGroups().empty()); ASSERT_TRUE(modelSpecMerged.getMergedSynapseSparseInitGroups().empty()); // Check that connectivity parameter is heterogeneous @@ -551,7 +551,7 @@ TEST(SynapseGroup, CompareWUDifferentToeplitzConnectivity) ASSERT_EQ(modelSpecMerged.getMergedNeuronUpdateGroups().size(), 3); ASSERT_EQ(modelSpecMerged.getMergedPresynapticUpdateGroups().size(), 1); ASSERT_TRUE(modelSpecMerged.getMergedSynapseConnectivityInitGroups().empty()); - ASSERT_TRUE(modelSpecMerged.getMergedSynapseDenseInitGroups().empty()); + ASSERT_EQ(modelSpecMerged.getMergedSynapseInitGroups().size(), 1); ASSERT_TRUE(modelSpecMerged.getMergedSynapseSparseInitGroups().empty()); // Check that connectivity parameter is heterogeneous @@ -617,7 +617,7 @@ TEST(SynapseGroup, CompareWUDifferentProceduralVars) ASSERT_TRUE(modelSpecMerged.getMergedNeuronUpdateGroups().size() == 2); ASSERT_TRUE(modelSpecMerged.getMergedPresynapticUpdateGroups().size() == 1); ASSERT_TRUE(modelSpecMerged.getMergedSynapseConnectivityInitGroups().empty()); - ASSERT_TRUE(modelSpecMerged.getMergedSynapseDenseInitGroups().empty()); + ASSERT_TRUE(modelSpecMerged.getMergedSynapseInitGroups().empty()); ASSERT_TRUE(modelSpecMerged.getMergedSynapseSparseInitGroups().empty()); // Check that only synaptic weight initialistion parameters are heterogeneous @@ -671,7 +671,7 @@ TEST(SynapseGroup, CompareWUDifferentProceduralSnippet) ASSERT_TRUE(modelSpecMerged.getMergedNeuronUpdateGroups().size() == 2); ASSERT_TRUE(modelSpecMerged.getMergedPresynapticUpdateGroups().size() == 2); ASSERT_TRUE(modelSpecMerged.getMergedSynapseConnectivityInitGroups().empty()); - ASSERT_TRUE(modelSpecMerged.getMergedSynapseDenseInitGroups().empty()); + ASSERT_TRUE(modelSpecMerged.getMergedSynapseInitGroups().empty()); ASSERT_TRUE(modelSpecMerged.getMergedSynapseSparseInitGroups().empty()); } @@ -731,7 +731,7 @@ TEST(SynapseGroup, InitCompareWUDifferentVars) ASSERT_TRUE(modelSpecMerged.getMergedNeuronUpdateGroups().size() == 2); ASSERT_TRUE(modelSpecMerged.getMergedPresynapticUpdateGroups().size() == 1); ASSERT_TRUE(modelSpecMerged.getMergedSynapseConnectivityInitGroups().size() == 1); - ASSERT_TRUE(modelSpecMerged.getMergedSynapseDenseInitGroups().empty()); + ASSERT_TRUE(modelSpecMerged.getMergedSynapseInitGroups().empty()); ASSERT_TRUE(modelSpecMerged.getMergedSynapseSparseInitGroups().size() == 1); // Check that only synaptic weight initialistion parameters are heterogeneous