From 8ce3372f74d4a734dfbb5a5e0b08280de02fd5ea Mon Sep 17 00:00:00 2001 From: Emil Jansson Date: Thu, 12 Jan 2023 14:02:53 +0100 Subject: [PATCH 01/15] Added makefile for IF_curr_delta neurons to be used in convolutions --- .../IF_curr_delta_conv/Makefile | 26 +++++++++++++++++++ .../makefiles/local_only_combined/Makefile | 3 ++- 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 neural_modelling/makefiles/local_only_combined/IF_curr_delta_conv/Makefile diff --git a/neural_modelling/makefiles/local_only_combined/IF_curr_delta_conv/Makefile b/neural_modelling/makefiles/local_only_combined/IF_curr_delta_conv/Makefile new file mode 100644 index 0000000000..8fdc82724e --- /dev/null +++ b/neural_modelling/makefiles/local_only_combined/IF_curr_delta_conv/Makefile @@ -0,0 +1,26 @@ +# Copyright (c) 2021-2022 The University of Manchester +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +APP = $(notdir $(CURDIR)) + +NEURON_MODEL = $(NEURON_DIR)/neuron/models/neuron_model_lif_impl.c +NEURON_MODEL_H = $(NEURON_DIR)/neuron/models/neuron_model_lif_impl.h +INPUT_TYPE_H = $(NEURON_DIR)/neuron/input_types/input_type_current.h +NEURON_IMPL_H = $(NEURON_DIR)/neuron/implementations/neuron_impl_standard.h +THRESHOLD_TYPE_H = $(NEURON_DIR)/neuron/threshold_types/threshold_type_static.h +SYNAPSE_TYPE_H = $(NEURON_DIR)/neuron/synapse_types/synapse_types_delta_impl.h +LOCAL_ONLY_IMPL = $(NEURON_DIR)/neuron/local_only/local_only_conv_impl.c + +include ../local_only.mk diff --git a/neural_modelling/makefiles/local_only_combined/Makefile b/neural_modelling/makefiles/local_only_combined/Makefile index 4b67130b99..b3b9193129 100644 --- a/neural_modelling/makefiles/local_only_combined/Makefile +++ b/neural_modelling/makefiles/local_only_combined/Makefile @@ -13,7 +13,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -MODELS = IF_curr_exp_conv\ +MODELS = IF_curr_delta_conv\ + IF_curr_exp_conv\ IF_curr_exp_pool_dense all: From 7accc3f51f16d1c79cfa47bb6b08f9067446fbe6 Mon Sep 17 00:00:00 2001 From: Emil Jansson Date: Thu, 12 Jan 2023 14:02:53 +0100 Subject: [PATCH 02/15] Added makefile for IF_curr_delta neurons to be used in convolutions --- .../IF_curr_delta_conv/Makefile | 26 +++++++++++++++++++ .../makefiles/local_only_combined/Makefile | 3 ++- 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 neural_modelling/makefiles/local_only_combined/IF_curr_delta_conv/Makefile diff --git a/neural_modelling/makefiles/local_only_combined/IF_curr_delta_conv/Makefile b/neural_modelling/makefiles/local_only_combined/IF_curr_delta_conv/Makefile new file mode 100644 index 0000000000..8fdc82724e --- /dev/null +++ b/neural_modelling/makefiles/local_only_combined/IF_curr_delta_conv/Makefile @@ -0,0 +1,26 @@ +# Copyright (c) 2021-2022 The University of Manchester +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +APP = $(notdir $(CURDIR)) + +NEURON_MODEL = $(NEURON_DIR)/neuron/models/neuron_model_lif_impl.c +NEURON_MODEL_H = $(NEURON_DIR)/neuron/models/neuron_model_lif_impl.h +INPUT_TYPE_H = $(NEURON_DIR)/neuron/input_types/input_type_current.h +NEURON_IMPL_H = $(NEURON_DIR)/neuron/implementations/neuron_impl_standard.h +THRESHOLD_TYPE_H = $(NEURON_DIR)/neuron/threshold_types/threshold_type_static.h +SYNAPSE_TYPE_H = $(NEURON_DIR)/neuron/synapse_types/synapse_types_delta_impl.h +LOCAL_ONLY_IMPL = $(NEURON_DIR)/neuron/local_only/local_only_conv_impl.c + +include ../local_only.mk diff --git a/neural_modelling/makefiles/local_only_combined/Makefile b/neural_modelling/makefiles/local_only_combined/Makefile index 4b67130b99..b3b9193129 100644 --- a/neural_modelling/makefiles/local_only_combined/Makefile +++ b/neural_modelling/makefiles/local_only_combined/Makefile @@ -13,7 +13,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -MODELS = IF_curr_exp_conv\ +MODELS = IF_curr_delta_conv\ + IF_curr_exp_conv\ IF_curr_exp_pool_dense all: From 6f70b3012a280c359f060f36fdd4edf950a7c5a5 Mon Sep 17 00:00:00 2001 From: emijan-kth <56410542+emijan-kth@users.noreply.github.com> Date: Thu, 2 Feb 2023 16:17:32 +0100 Subject: [PATCH 03/15] Multiple fixes to convolutions. (#2) Arbitrary size of convolution kernels (including non-square, non-odd sized) Strides, in particular non-square Fixed [row, column] order on convolution parameters (eg. padding and strides), in line with documentation comments --- .../IF_curr_delta_conv/Makefile | 2 +- .../neuron/local_only/local_only_conv_impl.c | 56 ++++++++++------ .../connectors/convolution_connector.py | 66 ++++++++++++------- 3 files changed, 77 insertions(+), 47 deletions(-) diff --git a/neural_modelling/makefiles/local_only_combined/IF_curr_delta_conv/Makefile b/neural_modelling/makefiles/local_only_combined/IF_curr_delta_conv/Makefile index 8fdc82724e..5026a0cca8 100644 --- a/neural_modelling/makefiles/local_only_combined/IF_curr_delta_conv/Makefile +++ b/neural_modelling/makefiles/local_only_combined/IF_curr_delta_conv/Makefile @@ -17,7 +17,7 @@ APP = $(notdir $(CURDIR)) NEURON_MODEL = $(NEURON_DIR)/neuron/models/neuron_model_lif_impl.c NEURON_MODEL_H = $(NEURON_DIR)/neuron/models/neuron_model_lif_impl.h -INPUT_TYPE_H = $(NEURON_DIR)/neuron/input_types/input_type_current.h +INPUT_TYPE_H = $(NEURON_DIR)/neuron/input_types/input_type_delta.h NEURON_IMPL_H = $(NEURON_DIR)/neuron/implementations/neuron_impl_standard.h THRESHOLD_TYPE_H = $(NEURON_DIR)/neuron/threshold_types/threshold_type_static.h SYNAPSE_TYPE_H = $(NEURON_DIR)/neuron/synapse_types/synapse_types_delta_impl.h diff --git a/neural_modelling/src/neuron/local_only/local_only_conv_impl.c b/neural_modelling/src/neuron/local_only/local_only_conv_impl.c index 5b82bdbd95..709c7213e6 100644 --- a/neural_modelling/src/neuron/local_only/local_only_conv_impl.c +++ b/neural_modelling/src/neuron/local_only/local_only_conv_impl.c @@ -60,6 +60,7 @@ typedef struct { lc_shape_t kernel; lc_shape_t padding; lc_coord_t recip_strides; + lc_coord_t strides; lc_coord_t recip_pool_strides; uint16_t positive_synapse_type; uint16_t negative_synapse_type; @@ -138,7 +139,15 @@ bool local_only_impl_initialise(void *address){ return true; } -//! \brief Multiply an integer by a 16-bit reciprocal and return the floored +//! \brief Calculate the remainder from a division +static inline int16_t calc_remainder(int16_t dividend, int16_t divisor, int16_t quotient) { + int16_t remainder = dividend - quotient * divisor; + log_debug("remainder: %d = %d * %d + %d", + dividend, quotient, divisor, remainder); + return remainder; +} + +//! \brief Calculate remainder Multiply an integer by a 16-bit reciprocal and return the floored //! integer result static inline int16_t recip_multiply(int16_t integer, int16_t recip) { int32_t i = integer; @@ -146,19 +155,17 @@ static inline int16_t recip_multiply(int16_t integer, int16_t recip) { return (int16_t) ((i * r) >> RECIP_FRACT_BITS); } -//! \brief Do a mapping from pre to post 2D spaces, we use the standard -//! padding, kernel, strides from Convolutional Neural Networks -//! because of the way we're looping through the kernel, we divide the kernel -//! shape by 2. -static inline lc_coord_t map_pre_to_post(connector *connector, lc_coord_t pre, - int16_t half_kh, int16_t half_kw) { - lc_coord_t post = pre; - post.col = recip_multiply(post.col, connector->recip_pool_strides.col); - post.row = recip_multiply(post.row, connector->recip_pool_strides.row); - post.col = post.col - half_kw + connector->padding.width; - post.row = post.row - half_kh + connector->padding.height; - post.col = recip_multiply(post.col, connector->recip_strides.col); - post.row = recip_multiply(post.row, connector->recip_strides.row); +//! \brief Do a mapping from pre to post 2D spaces +static inline lc_coord_t map_pre_to_post(connector *connector, lc_coord_t pre, lc_coord_t *start_i) { + pre.col = recip_multiply(pre.col, connector->recip_pool_strides.col); + pre.row = recip_multiply(pre.row, connector->recip_pool_strides.row); + pre.col += connector->padding.width; + pre.row += connector->padding.height; + lc_coord_t post; + post.col = recip_multiply(pre.col, connector->recip_strides.col); + post.row = recip_multiply(pre.row, connector->recip_strides.row); + start_i->col = calc_remainder(pre.col, connector->strides.col, post.col); + start_i->row = calc_remainder(pre.row, connector->strides.row, post.row); return post; } @@ -169,21 +176,26 @@ static inline lc_coord_t map_pre_to_post(connector *connector, lc_coord_t pre, static inline void do_convolution_operation( uint32_t time, lc_coord_t pre_coord, connector *connector, uint16_t *ring_buffers) { - int32_t half_kh = connector->kernel.height / 2; - int32_t half_kw = connector->kernel.width / 2; - lc_coord_t post_coord = map_pre_to_post(connector, pre_coord, half_kh, half_kw); + lc_coord_t start_i; + log_debug("kernel height: %d, kernel width: %d, padding height: %d, padding width: %d, strides row: %d, strides col: %d", connector->kernel.height, connector->kernel.width, connector->padding.height, connector->padding.width, connector->strides.row, connector->strides.col); + lc_coord_t post_coord = map_pre_to_post(connector, pre_coord, &start_i); log_debug("pre row %d, col %d AS post row %d, col %d", pre_coord.row, pre_coord.col, post_coord.row, post_coord.col); int32_t kw = connector->kernel.width; - for (int32_t r = -half_kh, kr = 0; r <= half_kh; r++, kr++) { - int32_t tmp_row = post_coord.row + r; + for (int32_t i_row = start_i.row, tmp_row = post_coord.row; i_row < connector->kernel.height; i_row += connector->strides.row, --tmp_row) { + int32_t kr = connector->kernel.height - 1 - i_row; + log_debug("i_row = %u, kr = %u, tmp_row = %u", i_row, kr, tmp_row); + if ((tmp_row < config.post_start.row) || (tmp_row > config.post_end.row)) { + log_debug("tmp_row outside"); continue; } - for (int32_t c = -half_kw, kc = 0; c <= half_kw; c++, kc++) { - int32_t tmp_col = post_coord.col + c; + for (int32_t i_col = start_i.col, tmp_col = post_coord.col; i_col < connector->kernel.width; i_col += connector->strides.col, --tmp_col) { + int32_t kc = connector->kernel.width - 1 - i_col; + log_debug("i_col = %u, kc = %u, tmp_col = %u", i_col, kc, tmp_col); if ((tmp_col < config.post_start.col) || (tmp_col > config.post_end.col)) { + log_debug("tmp_col outside"); continue; } @@ -192,8 +204,10 @@ static inline void do_convolution_operation( ((tmp_row - config.post_start.row) * config.post_shape.width) + (tmp_col - config.post_start.col); uint32_t k = (kr * kw) + kc; + log_debug("weight index = %u", k); lc_weight_t weight = connector->weights[k]; if (weight == 0) { + log_debug("zero weight"); continue; } uint32_t rb_index = 0; diff --git a/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py b/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py index 55d699f554..9bda1e36c8 100644 --- a/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py +++ b/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py @@ -33,7 +33,7 @@ #: The number of 16-bit shorts in the connector struct, #: ignoring the source_key_info struct and the weights (which are dynamic) -CONNECTOR_CONFIG_SHORTS = 12 +CONNECTOR_CONFIG_SHORTS = 14 class ConvolutionConnector(AbstractConnector): @@ -215,11 +215,10 @@ def get_post_shape(self, shape): shape = (post_pool_shape // self.__pool_stride) + 1 kernel_shape = numpy.array(self.__kernel_weights.shape) - post_shape = (shape - (kernel_shape - 1) + - (2 * self.__padding_shape)) + post_shape = shape - kernel_shape + (2 * self.__padding_shape) return numpy.clip( - post_shape // self.__strides, 1, numpy.inf).astype('int') + post_shape // self.__strides + 1, 1, numpy.inf).astype('int') @overrides(AbstractConnector.validate_connection) def validate_connection(self, application_edge, synapse_info): @@ -230,7 +229,9 @@ def validate_connection(self, application_edge, synapse_info): "The ConvolutionConnector only works where the Populations" " of a Projection are both 2D. Please ensure that both the" " Populations use a Grid2D structure.") - expected_post_shape = tuple(self.get_post_shape(pre.atoms_shape)) + pre_shape = pre.atoms_shape + expected_post_shape = tuple(self.get_post_shape((pre_shape[1], pre_shape[0]))) + expected_post_shape = expected_post_shape[1], expected_post_shape[0] if expected_post_shape != post.atoms_shape: raise ConfigurationException( f"With a source population with shape {pre.atoms_shape}, " @@ -281,10 +282,20 @@ def get_connected_vertices(self, s_info, source_vertex, target_vertex): pre_slices = [m_vertex.vertex_slice for m_vertex in pre_vertices] pre_slices_x = [vtx_slice.get_slice(0) for vtx_slice in pre_slices] pre_slices_y = [vtx_slice.get_slice(1) for vtx_slice in pre_slices] - pre_ranges = [[[px.start, py.start], [px.stop - 1, py.stop - 1]] + pre_ranges = [[[py.start, px.start], [py.stop - 1, px.stop - 1]] for px, py in zip(pre_slices_x, pre_slices_y)] - pres_as_posts = self.__pre_as_post(pre_ranges) - hlf_k_w, hlf_k_h = numpy.array(self.__kernel_weights.shape) // 2 + pre_vertex_in_post_layer, start_i = self.__pre_as_post(pre_ranges) + + pre_vertex_in_post_layer_upper_left = pre_vertex_in_post_layer[:,0] + pre_vertex_in_post_layer_lower_right = pre_vertex_in_post_layer[:,1] + + kernel_shape = numpy.array(self.__kernel_weights.shape) + + j = (kernel_shape - 1 - start_i) // self.__strides + j_upper_left = j[:,0] + + pre_vertex_max_reach_in_post_layer_upper_left = pre_vertex_in_post_layer_upper_left - j_upper_left + pre_vertex_max_reach_in_post_layer_lower_right = pre_vertex_in_post_layer_lower_right connected = list() for post in target_vertex.splitter.get_in_coming_vertices( @@ -293,18 +304,18 @@ def get_connected_vertices(self, s_info, source_vertex, target_vertex): post_slice_x = post_slice.get_slice(0) post_slice_y = post_slice.get_slice(1) - # Get ranges allowed in post - min_x = post_slice_x.start - hlf_k_w - max_x = (post_slice_x.stop + hlf_k_w) - 1 - min_y = post_slice_y.start - hlf_k_h - max_y = (post_slice_y.stop + hlf_k_h) - 1 + # Get ranges allowed in post vertex + min_x = post_slice_x.start + max_x = post_slice_x.stop - 1 + min_y = post_slice_y.start + max_y = post_slice_y.stop - 1 # Test that the start coords are in range i.e. less than max start_in_range = numpy.logical_not( - numpy.any(pres_as_posts[:, 0] > [max_x, max_y], axis=1)) + numpy.any(pre_vertex_max_reach_in_post_layer_upper_left > [max_y, max_x], axis=1)) # Test that the end coords are in range i.e. more than min end_in_range = numpy.logical_not( - numpy.any(pres_as_posts[:, 1] < [min_x, min_y], axis=1)) + numpy.any(pre_vertex_max_reach_in_post_layer_lower_right < [min_y, min_x], axis=1)) # When both things are true, we have a vertex in range pre_in_range = pre_vertices[ numpy.logical_and(start_in_range, end_in_range)] @@ -315,17 +326,18 @@ def get_connected_vertices(self, s_info, source_vertex, target_vertex): def __pre_as_post(self, pre_coords): """ Write pre coords as post coords. - :param Iterable pre_coords: An iterable of (x, y) coordinates + :param Iterable pre_coords: An iterable of (y, x) coordinates :rtype: numpy.ndarray """ coords = numpy.array(pre_coords) if self.__pool_stride is not None: coords //= self.__pool_stride - kernel_shape = numpy.array(self.__kernel_weights.shape) - coords = coords - kernel_shape // 2 + self.__padding_shape - coords //= self.__strides - return coords + coords += self.__padding_shape + coord_by_strides = coords // self.__strides + start_i = coords % self.__strides + + return coord_by_strides, start_i @property def local_only_n_bytes(self): @@ -343,9 +355,9 @@ def write_local_only_data( weight_scales): # Get info about things kernel_shape = self.__kernel_weights.shape - ps_x, ps_y = 1, 1 + ps_y, ps_x = 1, 1 if self.__pool_stride is not None: - ps_x, ps_y = self.__pool_stride + ps_y, ps_x = self.__pool_stride # Write source key info spec.write_value(key, data_type=DataType.UINT32) @@ -376,13 +388,17 @@ def write_local_only_data( # Write remaining connector details spec.write_value(start[1], data_type=DataType.INT16) spec.write_value(start[0], data_type=DataType.INT16) - spec.write_value(kernel_shape[1], data_type=DataType.INT16) spec.write_value(kernel_shape[0], data_type=DataType.INT16) - spec.write_value(self.__padding_shape[1], data_type=DataType.INT16) + spec.write_value(kernel_shape[1], data_type=DataType.INT16) spec.write_value(self.__padding_shape[0], data_type=DataType.INT16) + spec.write_value(self.__padding_shape[1], data_type=DataType.INT16) + spec.write_value(self.__recip(self.__strides[0]), + data_type=DataType.INT16) spec.write_value(self.__recip(self.__strides[1]), data_type=DataType.INT16) - spec.write_value(self.__recip(self.__strides[0]), + spec.write_value(self.__strides[0], + data_type=DataType.INT16) + spec.write_value(self.__strides[1], data_type=DataType.INT16) spec.write_value(self.__recip(ps_y), data_type=DataType.INT16) spec.write_value(self.__recip(ps_x), data_type=DataType.INT16) From bb90642846ec993975146c9eeee6e9880609c0e5 Mon Sep 17 00:00:00 2001 From: Emil Jansson Date: Fri, 10 Feb 2023 20:30:20 +0100 Subject: [PATCH 04/15] Fixed merge error: local_only_delays should have increased size of connector struct. --- .../neural_projections/connectors/convolution_connector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py b/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py index 2a6ac1ffc0..eb0b09a85f 100644 --- a/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py +++ b/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py @@ -34,7 +34,7 @@ #: The number of 16-bit shorts in the connector struct, #: ignoring the source_key_info struct and the weights (which are dynamic) -CONNECTOR_CONFIG_SHORTS = 14 +CONNECTOR_CONFIG_SHORTS = 16 class ConvolutionConnector(AbstractConnector): From 91eeed643a03be914b79467fadc0e941a1e3d818 Mon Sep 17 00:00:00 2001 From: Emil Jansson Date: Fri, 10 Feb 2023 20:39:36 +0100 Subject: [PATCH 05/15] Added option to ConvolutionConnector for delays varying horizontally over kernel. --- .../src/neuron/local_only/local_only_conv_impl.c | 13 +++++++++++-- .../connectors/convolution_connector.py | 13 ++++++++++--- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/neural_modelling/src/neuron/local_only/local_only_conv_impl.c b/neural_modelling/src/neuron/local_only/local_only_conv_impl.c index 58d2c98c6d..0d50f3e684 100644 --- a/neural_modelling/src/neuron/local_only/local_only_conv_impl.c +++ b/neural_modelling/src/neuron/local_only/local_only_conv_impl.c @@ -65,6 +65,7 @@ typedef struct { uint16_t positive_synapse_type; uint16_t negative_synapse_type; uint32_t delay; + uint32_t strides_delay_step; lc_weight_t weights[]; // n_weights = next_even(kernel.width * kernel.height) } connector; @@ -192,6 +193,14 @@ static inline void do_convolution_operation( log_debug("tmp_row outside"); continue; } + + uint32_t delay = connector->delay; + if (connector->strides_delay_step != 0) + { + delay -= start_i.col * connector->strides_delay_step; + log_debug("start_i.col = %u, delay = %u", start_i.col, delay); + } + for (int32_t i_col = start_i.col, tmp_col = post_coord.col; i_col < connector->kernel.width; i_col += connector->strides.col, --tmp_col) { int32_t kc = connector->kernel.width - 1 - i_col; log_debug("i_col = %u, kc = %u, tmp_col = %u", i_col, kc, tmp_col); @@ -213,12 +222,12 @@ static inline void do_convolution_operation( } uint32_t rb_index = 0; if (weight > 0) { - rb_index = synapse_row_get_ring_buffer_index(time + connector->delay, + rb_index = synapse_row_get_ring_buffer_index(time + delay, connector->positive_synapse_type, post_index, synapse_type_index_bits, synapse_index_bits, synapse_delay_mask); } else { - rb_index = synapse_row_get_ring_buffer_index(time + connector->delay, + rb_index = synapse_row_get_ring_buffer_index(time + delay, connector->negative_synapse_type, post_index, synapse_type_index_bits, synapse_index_bits, synapse_delay_mask); diff --git a/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py b/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py index eb0b09a85f..5dcf453bb9 100644 --- a/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py +++ b/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py @@ -34,7 +34,7 @@ #: The number of 16-bit shorts in the connector struct, #: ignoring the source_key_info struct and the weights (which are dynamic) -CONNECTOR_CONFIG_SHORTS = 16 +CONNECTOR_CONFIG_SHORTS = 18 class ConvolutionConnector(AbstractConnector): @@ -51,13 +51,16 @@ class ConvolutionConnector(AbstractConnector): "__pool_shape", "__pool_stride", "__positive_receptor_type", - "__negative_receptor_type" + "__negative_receptor_type", + "__horizontal_delay_step" ] def __init__(self, kernel_weights, kernel_shape=None, strides=None, padding=None, pool_shape=None, pool_stride=None, positive_receptor_type="excitatory", - negative_receptor_type="inhibitory", safe=True, + negative_receptor_type="inhibitory", + horizontal_delay_step=0, + safe=True, verbose=False, callback=None): """ :param kernel_weights: @@ -135,6 +138,8 @@ def __init__(self, kernel_weights, kernel_shape=None, strides=None, self.__positive_receptor_type = positive_receptor_type self.__negative_receptor_type = negative_receptor_type + self.__horizontal_delay_step = horizontal_delay_step + @property def positive_receptor_type(self): return self.__positive_receptor_type @@ -416,6 +421,8 @@ def write_local_only_data( spec.write_value(app_edge.post_vertex.synapse_dynamics.delay * SpynnakerDataView.get_simulation_time_step_per_ms()) + spec.write_value(self.__horizontal_delay_step, data_type=DataType.UINT32) + # Encode weights with weight scaling encoded_kernel_weights = self.__kernel_weights.flatten() if len(encoded_kernel_weights) % 2 != 0: From 4003c952a555714f1426274eabd1ab255c45c11d Mon Sep 17 00:00:00 2001 From: Emil Jansson Date: Wed, 15 Feb 2023 10:07:23 +0100 Subject: [PATCH 06/15] shapes and delays --- .../neural_projections/connectors/convolution_connector.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py b/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py index 5dcf453bb9..88d78fc1ba 100644 --- a/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py +++ b/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py @@ -138,6 +138,9 @@ def __init__(self, kernel_weights, kernel_shape=None, strides=None, self.__positive_receptor_type = positive_receptor_type self.__negative_receptor_type = negative_receptor_type + if horizontal_delay_step: + self.__kernel_shape = self.__get_kernel_shape(kernel_shape) + self.__horizontal_delay_step = horizontal_delay_step @property @@ -187,6 +190,8 @@ def __decode_kernel(self, w, shape): raise SynapticConfigurationException( f"Unknown combination of kernel_weights ({w}) and" f" kernel_shape ({shape})") + + self.__kernel_shape = self.__kernel_weights.shape @staticmethod def __to_2d_shape(shape, param_name): From 584ed954881ec3430fe428d1162be0cd105f74b2 Mon Sep 17 00:00:00 2001 From: Emil Jansson Date: Thu, 16 Feb 2023 11:25:25 +0100 Subject: [PATCH 07/15] Set kernel_shape overrides shape of kernel_weights --- .../connectors/convolution_connector.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py b/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py index 88d78fc1ba..f396edee3f 100644 --- a/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py +++ b/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py @@ -46,6 +46,7 @@ class ConvolutionConnector(AbstractConnector): __slots__ = [ "__kernel_weights", + "__kernel_shape", "__strides", "__padding_shape", "__pool_shape", @@ -225,7 +226,7 @@ def get_post_shape(self, shape): post_pool_shape = shape - (self.__pool_shape - 1) shape = (post_pool_shape // self.__pool_stride) + 1 - kernel_shape = numpy.array(self.__kernel_weights.shape) + kernel_shape = numpy.array(self.__kernel_shape) post_shape = shape - kernel_shape + (2 * self.__padding_shape) return numpy.clip( @@ -274,12 +275,12 @@ def get_delay_maximum(self, synapse_info): def get_n_connections_from_pre_vertex_maximum( self, n_post_atoms, synapse_info, min_delay=None, max_delay=None): - w, h = self.__kernel_weights.shape + w, h = self.__kernel_shape return numpy.clip(w * h, 0, n_post_atoms) @overrides(AbstractConnector.get_n_connections_to_post_vertex_maximum) def get_n_connections_to_post_vertex_maximum(self, synapse_info): - w, h = self.__kernel_weights.shape + w, h = self.__kernel_shape return numpy.clip(w * h, 0, synapse_info.n_pre_neurons) @overrides(AbstractConnector.get_weight_maximum) @@ -300,7 +301,7 @@ def get_connected_vertices(self, s_info, source_vertex, target_vertex): pre_vertex_in_post_layer_upper_left = pre_vertex_in_post_layer[:,0] pre_vertex_in_post_layer_lower_right = pre_vertex_in_post_layer[:,1] - kernel_shape = numpy.array(self.__kernel_weights.shape) + kernel_shape = numpy.array(self.__kernel_shape) j = (kernel_shape - 1 - start_i) // self.__strides j_upper_left = j[:,0] From 13cf51e3c053fd1e86ba2a0b0baf98c76a69c8bd Mon Sep 17 00:00:00 2001 From: Emil Jansson Date: Thu, 16 Feb 2023 21:43:21 +0100 Subject: [PATCH 08/15] Multisynaptic connections with varying delays. --- .../neuron/local_only/local_only_conv_impl.c | 109 +++++++++++------- .../connectors/convolution_connector.py | 25 ++-- 2 files changed, 85 insertions(+), 49 deletions(-) diff --git a/neural_modelling/src/neuron/local_only/local_only_conv_impl.c b/neural_modelling/src/neuron/local_only/local_only_conv_impl.c index 0d50f3e684..b133b969ff 100644 --- a/neural_modelling/src/neuron/local_only/local_only_conv_impl.c +++ b/neural_modelling/src/neuron/local_only/local_only_conv_impl.c @@ -64,8 +64,14 @@ typedef struct { lc_coord_t recip_pool_strides; uint16_t positive_synapse_type; uint16_t negative_synapse_type; - uint32_t delay; - uint32_t strides_delay_step; + union { + uint32_t delay; + struct { + uint16_t multisynaptic_delay_max; + uint16_t multisynaptic_delay_step; + }; + }; + uint32_t num_multisynaptic_connections; lc_weight_t weights[]; // n_weights = next_even(kernel.width * kernel.height) } connector; @@ -194,55 +200,74 @@ static inline void do_convolution_operation( continue; } - uint32_t delay = connector->delay; - if (connector->strides_delay_step != 0) - { - delay -= start_i.col * connector->strides_delay_step; - log_debug("start_i.col = %u, delay = %u", start_i.col, delay); - } - for (int32_t i_col = start_i.col, tmp_col = post_coord.col; i_col < connector->kernel.width; i_col += connector->strides.col, --tmp_col) { int32_t kc = connector->kernel.width - 1 - i_col; + + uint32_t delay; + if (connector->num_multisynaptic_connections == 1) + { + delay = connector->delay; + } + else + { + kc = connector->kernel.width - 1 - connector->num_multisynaptic_connections * i_col; + if (kc < 0) + { + log_debug("Multisynaptic connection: i_col = %u, kc = %u, tmp_col = %u", i_col, kc, tmp_col); + break; + } + delay = connector->multisynaptic_delay_max; + log_debug("Multisynaptic connection: start_i.col = %u, delay = %u", start_i.col, delay); + } + log_debug("i_col = %u, kc = %u, tmp_col = %u", i_col, kc, tmp_col); + if ((tmp_col < config.post_start.col) || (tmp_col > config.post_end.col)) { log_debug("tmp_col outside"); continue; } - // This the neuron id relative to the neurons on this core - uint32_t post_index = - ((tmp_row - config.post_start.row) * config.post_shape.width) - + (tmp_col - config.post_start.col); - uint32_t k = (kr * kw) + kc; - log_debug("weight index = %u", k); - lc_weight_t weight = connector->weights[k]; - if (weight == 0) { - log_debug("zero weight"); - continue; - } - uint32_t rb_index = 0; - if (weight > 0) { - rb_index = synapse_row_get_ring_buffer_index(time + delay, - connector->positive_synapse_type, post_index, - synapse_type_index_bits, synapse_index_bits, - synapse_delay_mask); - } else { - rb_index = synapse_row_get_ring_buffer_index(time + delay, - connector->negative_synapse_type, post_index, - synapse_type_index_bits, synapse_index_bits, - synapse_delay_mask); - weight = -weight; - } - log_debug("Updating ring_buffers[%u] for post neuron %u = %u, %u, with weight %u", - rb_index, post_index, tmp_col, tmp_row, weight); - - // Add weight to current ring buffer value, avoiding saturation - uint32_t accumulation = ring_buffers[rb_index] + weight; - uint32_t sat_test = accumulation & 0x10000; - if (sat_test) { - accumulation = sat_test - 1; + for (int32_t multisynapse_index = 0; + multisynapse_index < connector->num_multisynaptic_connections; + ++multisynapse_index, delay -= connector->multisynaptic_delay_step, --kc) + { + log_debug("kc = %u, delay = %u", kc, delay); + + // This the neuron id relative to the neurons on this core + uint32_t post_index = + ((tmp_row - config.post_start.row) * config.post_shape.width) + + (tmp_col - config.post_start.col); + uint32_t k = (kr * kw) + kc; + log_debug("weight index = %u", k); + lc_weight_t weight = connector->weights[k]; + if (weight == 0) { + log_debug("zero weight"); + continue; + } + uint32_t rb_index = 0; + if (weight > 0) { + rb_index = synapse_row_get_ring_buffer_index(time + delay, + connector->positive_synapse_type, post_index, + synapse_type_index_bits, synapse_index_bits, + synapse_delay_mask); + } else { + rb_index = synapse_row_get_ring_buffer_index(time + delay, + connector->negative_synapse_type, post_index, + synapse_type_index_bits, synapse_index_bits, + synapse_delay_mask); + weight = -weight; + } + log_debug("Updating ring_buffers[%u] for post neuron %u = %u, %u, with weight %u", + rb_index, post_index, tmp_col, tmp_row, weight); + + // Add weight to current ring buffer value, avoiding saturation + uint32_t accumulation = ring_buffers[rb_index] + weight; + uint32_t sat_test = accumulation & 0x10000; + if (sat_test) { + accumulation = sat_test - 1; + } + ring_buffers[rb_index] = accumulation; } - ring_buffers[rb_index] = accumulation; } } } diff --git a/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py b/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py index f396edee3f..a1c1b5c653 100644 --- a/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py +++ b/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py @@ -53,14 +53,16 @@ class ConvolutionConnector(AbstractConnector): "__pool_stride", "__positive_receptor_type", "__negative_receptor_type", - "__horizontal_delay_step" + "__num_multisynaptic_connections", + "__multisynaptic_delay_min" ] def __init__(self, kernel_weights, kernel_shape=None, strides=None, padding=None, pool_shape=None, pool_stride=None, positive_receptor_type="excitatory", negative_receptor_type="inhibitory", - horizontal_delay_step=0, + num_multisynaptic_connections=1, + multisynaptic_delay_min=0, safe=True, verbose=False, callback=None): """ @@ -139,10 +141,11 @@ def __init__(self, kernel_weights, kernel_shape=None, strides=None, self.__positive_receptor_type = positive_receptor_type self.__negative_receptor_type = negative_receptor_type - if horizontal_delay_step: + self.__num_multisynaptic_connections = num_multisynaptic_connections + if num_multisynaptic_connections != 1: self.__kernel_shape = self.__get_kernel_shape(kernel_shape) + self.__multisynaptic_delay_min = multisynaptic_delay_min - self.__horizontal_delay_step = horizontal_delay_step @property def positive_receptor_type(self): @@ -424,10 +427,18 @@ def write_local_only_data( spec.write_value(neg_synapse_type, data_type=DataType.UINT16) # Write delay - spec.write_value(app_edge.post_vertex.synapse_dynamics.delay * - SpynnakerDataView.get_simulation_time_step_per_ms()) + max_delay = app_edge.post_vertex.synapse_dynamics.delay * \ + SpynnakerDataView.get_simulation_time_step_per_ms() - spec.write_value(self.__horizontal_delay_step, data_type=DataType.UINT32) + if self.__num_multisynaptic_connections == 1: + spec.write_value(max_delay) + else: + spec.write_value(max_delay, data_type=DataType.UINT16) + delay_range = max_delay - self.__multisynaptic_delay_min + delay_step = delay_range / (self.__num_multisynaptic_connections - 1) + spec.write_value(delay_step, data_type=DataType.UINT16) + + spec.write_value(self.__num_multisynaptic_connections, data_type=DataType.UINT32) # Encode weights with weight scaling encoded_kernel_weights = self.__kernel_weights.flatten() From 986aa80d5174953f17c6d2eddd3236fa2ba6bd6d Mon Sep 17 00:00:00 2001 From: Emil Jansson Date: Mon, 27 Feb 2023 11:02:43 +0100 Subject: [PATCH 09/15] Revert "Added option to ConvolutionConnector for delays varying horizontally over kernel." This reverts commit 91eeed643a03be914b79467fadc0e941a1e3d818. --- .../src/neuron/local_only/local_only_conv_impl.c | 13 ++----------- .../connectors/convolution_connector.py | 13 +++---------- 2 files changed, 5 insertions(+), 21 deletions(-) diff --git a/neural_modelling/src/neuron/local_only/local_only_conv_impl.c b/neural_modelling/src/neuron/local_only/local_only_conv_impl.c index 53dd5bc891..2f2e2616f7 100644 --- a/neural_modelling/src/neuron/local_only/local_only_conv_impl.c +++ b/neural_modelling/src/neuron/local_only/local_only_conv_impl.c @@ -65,7 +65,6 @@ typedef struct { uint16_t positive_synapse_type; uint16_t negative_synapse_type; uint32_t delay; - uint32_t strides_delay_step; lc_weight_t weights[]; // n_weights = next_even(kernel.width * kernel.height) } connector; @@ -193,14 +192,6 @@ static inline void do_convolution_operation( log_debug("tmp_row outside"); continue; } - - uint32_t delay = connector->delay; - if (connector->strides_delay_step != 0) - { - delay -= start_i.col * connector->strides_delay_step; - log_debug("start_i.col = %u, delay = %u", start_i.col, delay); - } - for (int32_t i_col = start_i.col, tmp_col = post_coord.col; i_col < connector->kernel.width; i_col += connector->strides.col, --tmp_col) { int32_t kc = connector->kernel.width - 1 - i_col; log_debug("i_col = %u, kc = %u, tmp_col = %u", i_col, kc, tmp_col); @@ -222,12 +213,12 @@ static inline void do_convolution_operation( } uint32_t rb_index = 0; if (weight > 0) { - rb_index = synapse_row_get_ring_buffer_index(time + delay, + rb_index = synapse_row_get_ring_buffer_index(time + connector->delay, connector->positive_synapse_type, post_index, synapse_type_index_bits, synapse_index_bits, synapse_delay_mask); } else { - rb_index = synapse_row_get_ring_buffer_index(time + delay, + rb_index = synapse_row_get_ring_buffer_index(time + connector->delay, connector->negative_synapse_type, post_index, synapse_type_index_bits, synapse_index_bits, synapse_delay_mask); diff --git a/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py b/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py index 2cdb4fe286..74b7224b3c 100644 --- a/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py +++ b/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py @@ -34,7 +34,7 @@ #: The number of 16-bit shorts in the connector struct, #: ignoring the source_key_info struct and the weights (which are dynamic) -CONNECTOR_CONFIG_SHORTS = 18 +CONNECTOR_CONFIG_SHORTS = 16 class ConvolutionConnector(AbstractConnector): @@ -51,16 +51,13 @@ class ConvolutionConnector(AbstractConnector): "__pool_shape", "__pool_stride", "__positive_receptor_type", - "__negative_receptor_type", - "__horizontal_delay_step" + "__negative_receptor_type" ] def __init__(self, kernel_weights, kernel_shape=None, strides=None, padding=None, pool_shape=None, pool_stride=None, positive_receptor_type="excitatory", - negative_receptor_type="inhibitory", - horizontal_delay_step=0, - safe=True, + negative_receptor_type="inhibitory", safe=True, verbose=False, callback=None): """ :param kernel_weights: @@ -138,8 +135,6 @@ def __init__(self, kernel_weights, kernel_shape=None, strides=None, self.__positive_receptor_type = positive_receptor_type self.__negative_receptor_type = negative_receptor_type - self.__horizontal_delay_step = horizontal_delay_step - @property def positive_receptor_type(self): return self.__positive_receptor_type @@ -426,8 +421,6 @@ def write_local_only_data( app_edge.post_vertex.splitter.max_support_delay()) spec.write_value(local_delay) - spec.write_value(self.__horizontal_delay_step, data_type=DataType.UINT32) - # Encode weights with weight scaling encoded_kernel_weights = self.__kernel_weights.flatten() if len(encoded_kernel_weights) % 2 != 0: From 7d2a5bfbd800942a186b6b9a16bc9c9a5ff2902c Mon Sep 17 00:00:00 2001 From: Emil Jansson Date: Mon, 27 Feb 2023 12:04:54 +0100 Subject: [PATCH 10/15] Fixed merge error: should have increased size of connector struct. --- .../neural_projections/connectors/convolution_connector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py b/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py index 64d8b258f3..8d876b1d15 100644 --- a/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py +++ b/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py @@ -34,7 +34,7 @@ #: The number of 16-bit shorts in the connector struct, #: ignoring the source_key_info struct and the weights (which are dynamic) -CONNECTOR_CONFIG_SHORTS = 16 +CONNECTOR_CONFIG_SHORTS = 18 class ConvolutionConnector(AbstractConnector): From af90e25d483744ce942231cb10a654374733caaa Mon Sep 17 00:00:00 2001 From: Emil Jansson Date: Sat, 4 Mar 2023 10:21:23 +0100 Subject: [PATCH 11/15] Completed merge. --- .../neuron/local_only/local_only_conv_impl.c | 64 +++++++-------- .../connectors/convolution_connector.py | 77 +++++++------------ 2 files changed, 56 insertions(+), 85 deletions(-) diff --git a/neural_modelling/src/neuron/local_only/local_only_conv_impl.c b/neural_modelling/src/neuron/local_only/local_only_conv_impl.c index b05244b9e5..87fa7f517b 100644 --- a/neural_modelling/src/neuron/local_only/local_only_conv_impl.c +++ b/neural_modelling/src/neuron/local_only/local_only_conv_impl.c @@ -89,7 +89,15 @@ typedef struct { // The main configuration data static conv_config *config; -static lc_weight_t *weights;t_simulation_time_st +static lc_weight_t *weights; + +//! \brief Load the required data into DTCM. +bool local_only_impl_initialise(void *address){ + log_info("+++++++++++++++++ CONV init ++++++++++++++++++++"); + conv_config* sdram_config = address; + uint32_t n_bytes = sizeof(conv_config) + + (sizeof(connector) * sdram_config->n_connectors); + config = spin1_malloc(n_bytes); if (config == NULL) { log_error("Can't allocate memory for config!"); return false; @@ -187,7 +195,7 @@ static inline void do_convolution_operation( int32_t kr = connector->kernel.height - 1 - i_row; log_debug("i_row = %u, kr = %u, tmp_row = %u", i_row, kr, tmp_row); - if ((tmp_row < config.post_start.row) || (tmp_row > config.post_end.row)) { + if ((tmp_row < config->post_start.row) || (tmp_row > config->post_end.row)) { log_debug("tmp_row outside"); continue; } @@ -214,7 +222,7 @@ static inline void do_convolution_operation( log_debug("i_col = %u, kc = %u, tmp_col = %u", i_col, kc, tmp_col); - if ((tmp_col < config.post_start.col) || (tmp_col > config.post_end.col)) { + if ((tmp_col < config->post_start.col) || (tmp_col > config->post_end.col)) { log_debug("tmp_col outside"); continue; } @@ -227,8 +235,8 @@ static inline void do_convolution_operation( // This the neuron id relative to the neurons on this core uint32_t post_index = - ((tmp_row - config.post_start.row) * config.post_shape.width) - + (tmp_col - config.post_start.col); + ((tmp_row - config->post_start.row) * config->post_shape.width) + + (tmp_col - config->post_start.col); uint32_t k = (kr * kw) + kc; log_debug("weight index = %u", k); lc_weight_t weight = connector_weights[k]; @@ -266,8 +274,8 @@ static inline void do_convolution_operation( static inline bool key_to_index_lookup(uint32_t spike, connector **conn, uint32_t *core_local_col, uint32_t *core_local_row) { - for (uint32_t i = 0; i < config.n_connectors; i++) { - connector *c = connectors[i]; + for (uint32_t i = 0; i < config->n_connectors; i++) { + connector *c = &(config->connectors[i]); if ((spike & c->key_info.mask) == c->key_info.key) { uint32_t local_spike = (spike & ~c->key_info.mask) >> c->key_info.n_colour_bits; *conn = c; @@ -279,14 +287,6 @@ static inline bool key_to_index_lookup(uint32_t spike, connector **conn, return false; } -static inline void get_row_col(uint32_t spike, uint32_t index, - uint32_t *core_local_col, uint32_t *core_local_row) { - connector *c = connectors[index]; - uint32_t local_spike = (spike & ~c->key_info.mask) >> c->key_info.n_colour_bits; - *core_local_col = (local_spike & c->key_info.col_mask) >> c->key_info.col_shift; - *core_local_row = (local_spike & c->key_info.row_mask) >> c->key_info.row_shift; -} - //! \brief Process incoming spikes. In this implementation we need to: //! 1. Check if it's in the population table //! 2. Convert the relative (per core) Id to a global (per population) one @@ -295,30 +295,24 @@ static inline void get_row_col(uint32_t spike, uint32_t index, //! 4. Add the weights to the appropriate current buffers void local_only_impl_process_spike( uint32_t time, uint32_t spike, uint16_t* ring_buffers) { + connector *connector; + uint32_t core_local_col; + uint32_t core_local_row; // Lookup the spike, and if found, get the appropriate parts - uint32_t start; - uint32_t end; - if (!key_to_index_lookup(spike, &start, &end)) { - log_warning("Spike %u didn't match any connectors!", spike); + if (!key_to_index_lookup( + spike, &connector, &core_local_col, &core_local_row)) { return; } - log_debug("Received spike %u, using connectors between %u and %u", spike, start, end); // compute the population-based coordinates - for (uint32_t i = start; i < end; i++) { - uint32_t core_local_col; - uint32_t core_local_row; - connector *connector = connectors[i]; - get_row_col(spike, i, &core_local_col, &core_local_row); - lc_coord_t pre_coord = { - core_local_row + connector->pre_start.row, - core_local_col + connector->pre_start.col - }; - log_debug("Spike %u = %u, %u (Global: %u, %u)", spike, core_local_col, core_local_row, - pre_coord.col, pre_coord.row); - - // Compute the convolution - do_convolution_operation(time, pre_coord, connector, ring_buffers); - } + lc_coord_t pre_coord = { + core_local_row + connector->pre_start.row, + core_local_col + connector->pre_start.col + }; + log_debug("Received spike %u = %u, %u (Global: %u, %u)", spike, core_local_col, core_local_row, + pre_coord.col, pre_coord.row); + + // Compute the convolution + do_convolution_operation(time, pre_coord, connector, ring_buffers); } diff --git a/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py b/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py index e0d3b57725..2c540d6009 100644 --- a/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py +++ b/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py @@ -33,7 +33,7 @@ #: The number of 16-bit shorts in the connector struct, #: ignoring the source_key_info struct and the weights (which are dynamic) -CONNECTOR_CONFIG_SHORTS = 18 +CONNECTOR_CONFIG_SHORTS = 20 class ConvolutionConnector(AbstractConnector): @@ -340,13 +340,25 @@ def get_connected_vertices(self, s_info, source_vertex, target_vertex): return connected def get_max_n_incoming_slices(self, source_vertex, target_vertex): - pre_slices = list(source_vertex.splitter.get_out_going_slices()) + pre_vertices = numpy.array( + source_vertex.splitter.get_out_going_vertices(SPIKE_PARTITION_ID)) + pre_slices = [m_vertex.vertex_slice for m_vertex in pre_vertices] pre_slices_x = [vtx_slice.get_slice(0) for vtx_slice in pre_slices] pre_slices_y = [vtx_slice.get_slice(1) for vtx_slice in pre_slices] - pre_ranges = [[[px.start, py.start], [px.stop - 1, py.stop - 1]] + pre_ranges = [[[py.start, px.start], [py.stop - 1, px.stop - 1]] for px, py in zip(pre_slices_x, pre_slices_y)] - pres_as_posts = self.__pre_as_post(pre_ranges) - hlf_k_w, hlf_k_h = numpy.array(self.__kernel_weights.shape) // 2 + pre_vertex_in_post_layer, start_i = self.__pre_as_post(pre_ranges) + + pre_vertex_in_post_layer_upper_left = pre_vertex_in_post_layer[:,0] + pre_vertex_in_post_layer_lower_right = pre_vertex_in_post_layer[:,1] + + kernel_shape = numpy.array(self.__kernel_shape) + + j = (kernel_shape - 1 - start_i) // self.__strides + j_upper_left = j[:,0] + + pre_vertex_max_reach_in_post_layer_upper_left = pre_vertex_in_post_layer_upper_left - j_upper_left + pre_vertex_max_reach_in_post_layer_lower_right = pre_vertex_in_post_layer_lower_right max_connected = 0 for post_slice in target_vertex.splitter.get_in_coming_slices(): @@ -372,39 +384,6 @@ def get_max_n_incoming_slices(self, source_vertex, target_vertex): return max_connected - def get_max_n_incoming_slices(self, source_vertex, target_vertex): - pre_slices = list(source_vertex.splitter.get_out_going_slices()) - pre_slices_x = [vtx_slice.get_slice(0) for vtx_slice in pre_slices] - pre_slices_y = [vtx_slice.get_slice(1) for vtx_slice in pre_slices] - pre_ranges = [[[px.start, py.start], [px.stop - 1, py.stop - 1]] - for px, py in zip(pre_slices_x, pre_slices_y)] - pres_as_posts = self.__pre_as_post(pre_ranges) - hlf_k_w, hlf_k_h = numpy.array(self.__kernel_weights.shape) // 2 - - max_connected = 0 - for post_slice in target_vertex.splitter.get_in_coming_slices(): - post_slice_x = post_slice.get_slice(0) - post_slice_y = post_slice.get_slice(1) - - # Get ranges allowed in post - min_x = post_slice_x.start - hlf_k_w - max_x = (post_slice_x.stop + hlf_k_w) - 1 - min_y = post_slice_y.start - hlf_k_h - max_y = (post_slice_y.stop + hlf_k_h) - 1 - - # Test that the start coords are in range i.e. less than max - start_in_range = numpy.logical_not( - numpy.any(pres_as_posts[:, 0] > [max_x, max_y], axis=1)) - # Test that the end coords are in range i.e. more than min - end_in_range = numpy.logical_not( - numpy.any(pres_as_posts[:, 1] < [min_x, min_y], axis=1)) - # When both things are true, we have a vertex in range - pre_in_range = numpy.logical_and(start_in_range, end_in_range) - n_connected = pre_in_range.sum() - max_connected = max(max_connected, n_connected) - - return max_connected - def __pre_as_post(self, pre_coords): """ Write pre coords as post coords. @@ -465,8 +444,6 @@ def get_local_only_data( values.extend([col_mask, 0, row_mask, n_bits_col]) # Do a new list for remaining connector details as uint16s - - # Write synapse information pos_synapse_type = app_edge.post_vertex.get_synapse_id_by_target( self.__positive_receptor_type) neg_synapse_type = app_edge.post_vertex.get_synapse_id_by_target( @@ -476,15 +453,10 @@ def get_local_only_data( kernel_shape[0], kernel_shape[1], self.__padding_shape[0], self.__padding_shape[1], self.__recip(self.__strides[0]), self.__recip(self.__strides[1]), - self.__strides[0], self.__strides[1] + self.__strides[0], self.__strides[1], self.__recip(ps_y), self.__recip(ps_x), pos_synapse_type, neg_synapse_type], dtype="uint16") - data = [numpy.array(values, dtype="uint32"), - short_values.view("uint32"), - numpy.array([weight_index], dtype="uint32")] - return data - # Write delay max_delay = app_edge.post_vertex.synapse_dynamics.delay * \ SpynnakerDataView.get_simulation_time_step_per_ms() @@ -493,14 +465,19 @@ def get_local_only_data( app_edge.post_vertex.splitter.max_support_delay()) if self.__num_multisynaptic_connections == 1: - spec.write_value(max_delay) + delay_view = numpy.array([max_delay], dtype="uint32") else: - spec.write_value(max_delay, data_type=DataType.UINT16) delay_range = max_delay - self.__multisynaptic_delay_min delay_step = delay_range / (self.__num_multisynaptic_connections - 1) - spec.write_value(delay_step, data_type=DataType.UINT16) + delay_view = numpy.array([max_delay, delay_step], dtype="uint16").view("uint32") - spec.write_value(self.__num_multisynaptic_connections, data_type=DataType.UINT32) + # Compile all values + data = [numpy.array(values, dtype="uint32"), + short_values.view("uint32"), + delay_view, + numpy.array([self.__num_multisynaptic_connections], dtype="uint32"), + numpy.array([weight_index], dtype="uint32")] + return data def get_encoded_kernel_weights(self, app_edge, weight_scales): # Encode weights with weight scaling From b6a447e13a8507105edb9fef0989c0e008b5f9ac Mon Sep 17 00:00:00 2001 From: Andrew Rowley Date: Tue, 14 Mar 2023 10:20:47 +0000 Subject: [PATCH 12/15] Fix counting --- .../neural_projections/connectors/convolution_connector.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py b/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py index 6e48cffc5f..ed5cf3f0f2 100644 --- a/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py +++ b/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py @@ -360,9 +360,7 @@ def get_max_n_incoming_slices(self, source_vertex, target_vertex): pre_vertex_in_post_layer_lower_right) max_connected = 0 - for post in target_vertex.splitter.get_in_coming_vertices( - SPIKE_PARTITION_ID): - post_slice = post.vertex_slice + for post_slice in target_vertex.splitter.get_in_coming_slices(): post_slice_x = post_slice.get_slice(0) post_slice_y = post_slice.get_slice(1) @@ -383,7 +381,7 @@ def get_max_n_incoming_slices(self, source_vertex, target_vertex): # When both things are true, we have a vertex in range pre_in_range = pre_vertices[ numpy.logical_and(start_in_range, end_in_range)] - n_connected = pre_in_range.sum() + n_connected = len(pre_in_range) max_connected = max(max_connected, n_connected) return max_connected From 8022750dd68b152dfdeaebbf18a9741ed2e409e8 Mon Sep 17 00:00:00 2001 From: Andrew Rowley Date: Tue, 14 Mar 2023 11:11:12 +0000 Subject: [PATCH 13/15] Line too long --- neural_modelling/src/neuron/local_only/local_only_conv_impl.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/neural_modelling/src/neuron/local_only/local_only_conv_impl.c b/neural_modelling/src/neuron/local_only/local_only_conv_impl.c index 184c597771..5488b10dd8 100644 --- a/neural_modelling/src/neuron/local_only/local_only_conv_impl.c +++ b/neural_modelling/src/neuron/local_only/local_only_conv_impl.c @@ -163,7 +163,8 @@ static inline int16_t recip_multiply(int16_t integer, int16_t recip) { } //! \brief Do a mapping from pre to post 2D spaces -static inline lc_coord_t map_pre_to_post(connector *connector, lc_coord_t pre, lc_coord_t *start_i) { +static inline lc_coord_t map_pre_to_post(connector *connector, lc_coord_t pre, + lc_coord_t *start_i) { pre.col = recip_multiply(pre.col, connector->recip_pool_strides.col); pre.row = recip_multiply(pre.row, connector->recip_pool_strides.row); pre.col += connector->padding.width; From 6ff18c829382421a665a5ab0bce61421e4950bbd Mon Sep 17 00:00:00 2001 From: Andrew Rowley Date: Tue, 14 Mar 2023 11:11:55 +0000 Subject: [PATCH 14/15] Flake8 --- .../neural_projections/connectors/convolution_connector.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py b/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py index c29251d33b..f1aff1bfa4 100644 --- a/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py +++ b/spynnaker/pyNN/models/neural_projections/connectors/convolution_connector.py @@ -486,7 +486,8 @@ def get_local_only_data( data = [numpy.array(values, dtype="uint32"), short_values.view("uint32"), delay_view, - numpy.array([self.__num_multisynaptic_connections], dtype="uint32"), + numpy.array([self.__num_multisynaptic_connections], + dtype="uint32"), numpy.array([weight_index], dtype="uint32")] return data From 0b216a2d0019cfe7a4b83ad22a90d742018ee770 Mon Sep 17 00:00:00 2001 From: Andrew Rowley Date: Tue, 14 Mar 2023 11:19:13 +0000 Subject: [PATCH 15/15] Rats --- .../IF_curr_delta_conv/Makefile | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/neural_modelling/makefiles/local_only_combined/IF_curr_delta_conv/Makefile b/neural_modelling/makefiles/local_only_combined/IF_curr_delta_conv/Makefile index 5026a0cca8..c87b8ef7e8 100644 --- a/neural_modelling/makefiles/local_only_combined/IF_curr_delta_conv/Makefile +++ b/neural_modelling/makefiles/local_only_combined/IF_curr_delta_conv/Makefile @@ -1,17 +1,16 @@ -# Copyright (c) 2021-2022 The University of Manchester +# Copyright (c) 2023 The University of Manchester # -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at # -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. +# https://www.apache.org/licenses/LICENSE-2.0 # -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. APP = $(notdir $(CURDIR))