Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.

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_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
LOCAL_ONLY_IMPL = $(NEURON_DIR)/neuron/local_only/local_only_conv_impl.c

include ../local_only.mk
3 changes: 2 additions & 1 deletion neural_modelling/makefiles/local_only_combined/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

MODELS = IF_curr_exp_conv\
MODELS = IF_curr_delta_conv\
IF_curr_exp_conv\
IF_curr_exp_pool_dense

all:
Expand Down
70 changes: 47 additions & 23 deletions neural_modelling/src/neuron/local_only/local_only_conv_impl.c
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,12 @@ 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;
uint32_t delay;
uint32_t strides_delay_step;
lc_weight_t weights[]; // n_weights = next_even(kernel.width * kernel.height)
} connector;

Expand Down Expand Up @@ -138,27 +141,33 @@ 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;
int32_t r = 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;
}

Expand All @@ -169,21 +178,34 @@ 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;

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);
if ((tmp_col < config.post_start.col) || (tmp_col > config.post_end.col)) {
log_debug("tmp_col outside");
continue;
}

Expand All @@ -192,18 +214,20 @@ 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;
if (weight > 0) {
rb_index = synapse_row_get_ring_buffer_index(time + 1,
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 + 1,
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ typedef struct {
uint32_t n_weights;
uint16_t positive_synapse_type;
uint16_t negative_synapse_type;
uint32_t delay;
dimension dimensions[];
// Also follows:
// lc_weight_t weights[];
Expand Down Expand Up @@ -213,12 +214,12 @@ void local_only_impl_process_spike(
}
uint32_t rb_index = 0;
if (weight > 0) {
rb_index = synapse_row_get_ring_buffer_index(time + 1,
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 + 1,
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,14 @@
from spynnaker.pyNN.utilities.utility_calls import get_n_bits
from spynnaker.pyNN.models.abstract_models import HasShapeKeyFields
from spynnaker.pyNN.utilities.constants import SPIKE_PARTITION_ID
from spynnaker.pyNN.data.spynnaker_data_view import SpynnakerDataView

#: The number of 32-bit words in the source_key_info struct
SOURCE_KEY_INFO_WORDS = 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 = 18


class ConvolutionConnector(AbstractConnector):
Expand All @@ -50,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:
Expand Down Expand Up @@ -134,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
Expand Down Expand Up @@ -215,11 +221,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):
Expand All @@ -230,7 +235,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}, "
Expand Down Expand Up @@ -281,10 +288,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(
Expand All @@ -293,18 +310,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)]
Expand All @@ -315,17 +332,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):
Expand All @@ -343,9 +361,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)
Expand Down Expand Up @@ -376,13 +394,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)
Expand All @@ -395,6 +417,12 @@ def write_local_only_data(
spec.write_value(pos_synapse_type, data_type=DataType.UINT16)
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())

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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@
from collections.abc import Iterable
from spinn_front_end_common.utilities.exceptions import ConfigurationException
from spynnaker.pyNN.models.abstract_models import HasShapeKeyFields
from spynnaker.pyNN.data.spynnaker_data_view import SpynnakerDataView


_DIMENSION_SIZE = (2 * BYTES_PER_WORD) + (6 * BYTES_PER_SHORT)
_KEY_INFO_SIZE = 3 * BYTES_PER_WORD
_CONN_SIZE = _KEY_INFO_SIZE + (2 * BYTES_PER_WORD) + (2 * BYTES_PER_SHORT)
_CONN_SIZE = _KEY_INFO_SIZE + (3 * BYTES_PER_WORD) + (2 * BYTES_PER_SHORT)
_DIM_DTYPE = [("mask", "uint32"), ("shift", "uint32"), ("pre_start", "uint16"),
("pre_in_post_start", "uint16"), ("pre_in_post_end", "uint16"),
("pre_in_post_shape", "uint16"), ("recip_pool_stride", "uint16"),
Expand Down Expand Up @@ -283,6 +284,10 @@ def write_local_only_data(
spec.write_value(pos_synapse_type, data_type=DataType.UINT16)
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())

# Generate the dimension information
dim_info = numpy.zeros(n_dims, dtype=_DIM_DTYPE)
if self.__pool_stride is not None:
Expand Down
Loading