Source code for spynnaker.pyNN.extra_algorithms.splitter_components.splitter_abstract_pop_vertex_fixed

# Copyright (c) 2020 The University of Manchester
#
# 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
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# 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.
from collections import defaultdict
from spinn_utilities.overrides import overrides
from spinn_utilities.ordered_set import OrderedSet
from pacman.exceptions import PacmanConfigurationException
from pacman.model.resources import MultiRegionSDRAM
from pacman.model.partitioner_splitters import AbstractSplitterCommon
from pacman.utilities.algorithm_utilities\
    .partition_algorithm_utilities import get_multidimensional_slices
from spynnaker.pyNN.models.neuron import (
    AbstractPopulationVertex, PopulationMachineVertex,
    PopulationMachineLocalOnlyCombinedVertex, LocalOnlyProvenance)
from spynnaker.pyNN.models.neuron.population_machine_vertex import (
    NeuronProvenance, SynapseProvenance, MainProvenance,
    SpikeProcessingProvenance)
from spynnaker.pyNN.models.neuron.master_pop_table import (
    MasterPopTableAsBinarySearch)
from spynnaker.pyNN.utilities.bit_field_utilities import (
    get_sdram_for_bit_field_region)
from spynnaker.pyNN.models.neuron.synapse_dynamics import (
    AbstractSynapseDynamicsStructural)
from spynnaker.pyNN.models.neuron.local_only import AbstractLocalOnly
from spynnaker.pyNN.models.utility_models.delays import DelayExtensionVertex
from spynnaker.pyNN.models.neuron.synaptic_matrices import SynapticMatrices
from spynnaker.pyNN.models.neuron.neuron_data import NeuronData
from .abstract_spynnaker_splitter_delay import AbstractSpynnakerSplitterDelay

# The maximum number of bits for the ring buffer index that are likely to
# fit in DTCM (14-bits = 16,384 16-bit ring buffer entries = 32Kb DTCM
MAX_RING_BUFFER_BITS = 14


class SplitterAbstractPopulationVertexFixed(
        AbstractSplitterCommon, AbstractSpynnakerSplitterDelay):
    """
    Handles the splitting of the :py:class:`AbstractPopulationVertex`
    using fixed slices.
    """

    __slots__ = [
        # The pre-calculated slices of the vertex
        "__slices",
        "__max_delay",
        "__expect_delay_extension"
    ]

    def __init__(self):
        super().__init__()
        self.__slices = None
        self.__max_delay = None
        self.__expect_delay_extension = None

[docs] @overrides(AbstractSplitterCommon.set_governed_app_vertex) def set_governed_app_vertex(self, app_vertex): super().set_governed_app_vertex(app_vertex) if not isinstance(app_vertex, AbstractPopulationVertex): raise PacmanConfigurationException( f"The vertex {app_vertex} cannot be supported by the " "SplitterAbstractPopulationVertexFixed as the only vertex " "supported by this splitter is a AbstractPopulationVertex. " "Please use the correct splitter for your vertex and try " "again.")
[docs] @overrides(AbstractSplitterCommon.create_machine_vertices) def create_machine_vertices(self, chip_counter): app_vertex = self.governed_app_vertex app_vertex.synapse_recorder.add_region_offset( len(app_vertex.neuron_recorder.get_recordable_variables())) max_atoms_per_core = min( app_vertex.get_max_atoms_per_core(), app_vertex.n_atoms) ring_buffer_shifts = app_vertex.get_ring_buffer_shifts() weight_scales = app_vertex.get_weight_scales(ring_buffer_shifts) all_syn_block_sz = app_vertex.get_synapses_size( max_atoms_per_core) structural_sz = app_vertex.get_structural_dynamics_size( max_atoms_per_core) sdram = self.get_sdram_used_by_atoms( max_atoms_per_core, all_syn_block_sz, structural_sz) synapse_regions = PopulationMachineVertex.SYNAPSE_REGIONS synaptic_matrices = SynapticMatrices( app_vertex, synapse_regions, max_atoms_per_core, weight_scales, all_syn_block_sz) neuron_data = NeuronData(app_vertex) self.__create_slices() for index, vertex_slice in enumerate(self.__slices): chip_counter.add_core(sdram) label = f"{app_vertex.label}{vertex_slice}" machine_vertex = self.create_machine_vertex( vertex_slice, sdram, label, structural_sz, ring_buffer_shifts, weight_scales, index, max_atoms_per_core, synaptic_matrices, neuron_data) self.governed_app_vertex.remember_machine_vertex(machine_vertex)
[docs] @overrides(AbstractSplitterCommon.get_in_coming_slices) def get_in_coming_slices(self): self.__create_slices() return self.__slices
[docs] @overrides(AbstractSplitterCommon.get_out_going_slices) def get_out_going_slices(self): self.__create_slices() return self.__slices
[docs] @overrides(AbstractSplitterCommon.get_out_going_vertices) def get_out_going_vertices(self, partition_id): return list(self.governed_app_vertex.machine_vertices)
[docs] @overrides(AbstractSplitterCommon.get_in_coming_vertices) def get_in_coming_vertices(self, partition_id): return list(self.governed_app_vertex.machine_vertices)
[docs] @overrides(AbstractSplitterCommon.get_source_specific_in_coming_vertices) def get_source_specific_in_coming_vertices( self, source_vertex, partition_id): # Determine the real pre-vertex pre_vertex = source_vertex if isinstance(source_vertex, DelayExtensionVertex): pre_vertex = source_vertex.source_vertex # Use the real pre-vertex to get the projections targets = defaultdict(OrderedSet) for proj in self.governed_app_vertex.get_incoming_projections_from( pre_vertex): # pylint: disable=protected-access s_info = proj._synapse_information # Use the original source vertex to get the connected vertices, # as the real source machine vertices must make it in to this array for (tgt, srcs) in s_info.synapse_dynamics.get_connected_vertices( s_info, source_vertex, self.governed_app_vertex): targets[tgt].update(srcs) return [(m_vertex, tgts) for m_vertex, tgts in targets.items()]
[docs] @overrides(AbstractSplitterCommon.machine_vertices_for_recording) def machine_vertices_for_recording(self, variable_to_record): return self.governed_app_vertex.machine_vertices
[docs] def create_machine_vertex( self, vertex_slice, sdram, label, structural_sz, ring_buffer_shifts, weight_scales, index, max_atoms_per_core, synaptic_matrices, neuron_data): # If using local-only create a local-only vertex s_dynamics = self.governed_app_vertex.synapse_dynamics if isinstance(s_dynamics, AbstractLocalOnly): return PopulationMachineLocalOnlyCombinedVertex( sdram, label, self.governed_app_vertex, vertex_slice, index, ring_buffer_shifts, weight_scales, neuron_data, max_atoms_per_core) # Otherwise create a normal vertex return PopulationMachineVertex( sdram, label, self.governed_app_vertex, vertex_slice, index, ring_buffer_shifts, weight_scales, structural_sz, max_atoms_per_core, synaptic_matrices, neuron_data)
[docs] def get_sdram_used_by_atoms( self, n_atoms, all_syn_block_sz, structural_sz): """ Gets the resources of a slice of atoms. :param int n_atoms: :rtype: ~pacman.model.resources.MultiRegionSDRAM """ # pylint: disable=arguments-differ variable_sdram = self.__get_variable_sdram(n_atoms) constant_sdram = self.__get_constant_sdram( n_atoms, all_syn_block_sz, structural_sz) sdram = MultiRegionSDRAM() sdram.nest(len(PopulationMachineVertex.REGIONS) + 1, variable_sdram) sdram.merge(constant_sdram) # return the total resources. return sdram
def __get_variable_sdram(self, n_atoms): """ Returns the variable SDRAM from the recorders. :param int n_atoms: The number of atoms to account for :return: the variable SDRAM used by the neuron recorder :rtype: VariableSDRAM """ s_dynamics = self.governed_app_vertex.synapse_dynamics if isinstance(s_dynamics, AbstractSynapseDynamicsStructural): max_rewires_per_ts = s_dynamics.get_max_rewires_per_ts() self.governed_app_vertex.synapse_recorder.set_max_rewires_per_ts( max_rewires_per_ts) return ( self.governed_app_vertex.get_max_neuron_variable_sdram(n_atoms) + self.governed_app_vertex.get_max_synapse_variable_sdram(n_atoms)) def __get_constant_sdram(self, n_atoms, all_syn_block_sz, structural_sz): """ Returns the constant SDRAM used by the atoms. :param int n_atoms: The number of atoms to account for :rtype: ~pacman.model.resources.MultiRegionSDRAM """ s_dynamics = self.governed_app_vertex.synapse_dynamics n_record = ( len(self.governed_app_vertex.neuron_recordables) + len(self.governed_app_vertex.synapse_recordables)) n_provenance = NeuronProvenance.N_ITEMS + MainProvenance.N_ITEMS if isinstance(s_dynamics, AbstractLocalOnly): n_provenance += LocalOnlyProvenance.N_ITEMS else: n_provenance += ( SynapseProvenance.N_ITEMS + SpikeProcessingProvenance.N_ITEMS) sdram = MultiRegionSDRAM() if isinstance(s_dynamics, AbstractLocalOnly): sdram.merge(self.governed_app_vertex.get_common_constant_sdram( n_record, n_provenance, PopulationMachineLocalOnlyCombinedVertex.COMMON_REGIONS)) sdram.merge(self.governed_app_vertex.get_neuron_constant_sdram( n_atoms, PopulationMachineLocalOnlyCombinedVertex.NEURON_REGIONS)) sdram.merge(self.__get_local_only_constant_sdram(n_atoms)) else: sdram.merge(self.governed_app_vertex.get_common_constant_sdram( n_record, n_provenance, PopulationMachineVertex.COMMON_REGIONS)) sdram.merge(self.governed_app_vertex.get_neuron_constant_sdram( n_atoms, PopulationMachineVertex.NEURON_REGIONS)) sdram.merge(self.__get_synapse_constant_sdram( n_atoms, all_syn_block_sz, structural_sz)) return sdram def __get_local_only_constant_sdram(self, n_atoms): app_vertex = self.governed_app_vertex s_dynamics = app_vertex.synapse_dynamics sdram = MultiRegionSDRAM() sdram.add_cost( PopulationMachineLocalOnlyCombinedVertex.REGIONS.LOCAL_ONLY.value, PopulationMachineLocalOnlyCombinedVertex.LOCAL_ONLY_SIZE) sdram.add_cost( (PopulationMachineLocalOnlyCombinedVertex. REGIONS.LOCAL_ONLY_PARAMS.value), s_dynamics.get_parameters_usage_in_bytes( n_atoms, app_vertex.incoming_projections)) return sdram def __get_synapse_constant_sdram( self, n_atoms, all_syn_block_sz, structural_sz): """ Get the amount of fixed SDRAM used by synapse parts. :param int n_atoms: The number of atoms to account for :rtype: ~pacman.model.resources.MultiRegionSDRAM """ app_vertex = self.governed_app_vertex regions = PopulationMachineVertex.SYNAPSE_REGIONS sdram = MultiRegionSDRAM() sdram.add_cost(regions.synapse_params, app_vertex.get_synapse_params_size()) sdram.add_cost(regions.synapse_dynamics, app_vertex.get_synapse_dynamics_size(n_atoms)) sdram.add_cost(regions.structural_dynamics, structural_sz) sdram.add_cost(regions.synaptic_matrix, all_syn_block_sz) sdram.add_cost( regions.pop_table, MasterPopTableAsBinarySearch.get_master_population_table_size( app_vertex.incoming_projections)) sdram.add_cost(regions.connection_builder, app_vertex.get_synapse_expander_size()) sdram.add_cost(regions.bitfield_filter, get_sdram_for_bit_field_region( app_vertex.incoming_projections)) return sdram
[docs] @overrides(AbstractSplitterCommon.reset_called) def reset_called(self): self.__slices = None self.__max_delay = None self.__expect_delay_extension = None
def __create_slices(self): """ Create slices if not already done. """ if self.__slices is not None: return self.__slices = get_multidimensional_slices(self.governed_app_vertex) def __update_max_delay(self): # Find the maximum delay from incoming synapses self.__max_delay, self.__expect_delay_extension = \ self.governed_app_vertex.get_max_delay(MAX_RING_BUFFER_BITS)
[docs] @overrides(AbstractSpynnakerSplitterDelay.max_support_delay) def max_support_delay(self): if self.__max_delay is None: self.__update_max_delay() return self.__max_delay
[docs] @overrides(AbstractSpynnakerSplitterDelay.accepts_edges_from_delay_vertex) def accepts_edges_from_delay_vertex(self): if self.__expect_delay_extension is None: self.__update_max_delay() if self.__expect_delay_extension: return True raise NotImplementedError( "This call was unexpected as it was calculated that " "the max needed delay was less that the max possible")