Skip to content

Commit

Permalink
Multidimensional slices and ask device for slices
Browse files Browse the repository at this point in the history
  • Loading branch information
rowleya committed Jul 5, 2021
1 parent 44581b2 commit 1e724cf
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 37 deletions.
20 changes: 20 additions & 0 deletions pacman/model/graphs/application/application_fpga_vertex.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from .application_vertex import ApplicationVertex
from pacman.exceptions import PacmanInvalidParameterException
from spinn_utilities.overrides import overrides
from spinn_utilities.abstract_base import abstractmethod


class ApplicationFPGAVertex(ApplicationVertex):
Expand Down Expand Up @@ -78,6 +79,25 @@ def n_machine_vertices_per_link(self):
"""
return self._n_machine_vertices_per_link

@abstractmethod
def get_incoming_slice_for_link(self, link, index):
""" Get the slice to be given to the connection from the given link
:param FPGAConnection link: The FPGA connection to get the slice for
:param int index:
The index of the connection on the FGPA link, for when
n_machine_vertices_per_link > 1
:rtype: ~pacman.model.graphs.common.Slice
"""

@abstractmethod
def get_outgoing_slice(self):
""" Get the slice to be given to the outgoing connection
:rtype: ~pacman.model.graphs.common.Slice
"""

@property
def incoming_fpga_connections(self):
""" The connections from one or more FPGAs that packets are expected
Expand Down
10 changes: 10 additions & 0 deletions pacman/model/graphs/application/application_vertex.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,16 @@ def remember_machine_vertex(self, machine_vertex):
str(machine_vertex), machine_vertex)
self._machine_vertices.add(machine_vertex)

def atoms_shape(self):
""" The "shape" of the atoms in the vertex i.e. how the atoms are split
between the dimensions of the vertex. By default everything is
1-dimensional, so the return will be a 1-tuple but can be
overridden by a vertex that supports multiple dimensions.
:rtype: tuple(int, ...)
"""
return (self.n_atoms,)

@abstractproperty
def n_atoms(self):
""" The number of atoms in the vertex
Expand Down
34 changes: 31 additions & 3 deletions pacman/model/graphs/common/slice.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,21 @@
from pacman.exceptions import PacmanValueError


class Slice(collections.namedtuple('Slice', 'lo_atom hi_atom n_atoms')):
class Slice(collections.namedtuple('Slice',
'lo_atom hi_atom n_atoms shape start')):
""" Represents a slice of a vertex.
:attr int lo_atom: The lowest atom represented in the slice.
:attr int hi_atom: The highest atom represented in the slice.
:attr int n_atoms: The number of atoms represented by the slice.
:attr slice as_slice: This slice represented as a `slice` object (for
use in indexing lists, arrays, etc.)
:attr tuple(int,...) shape: The shape of the atoms over multiple
dimensions. By default the shape will be 1-dimensional.
:attr tuple(int,...) start: The start coordinates of the slice. By default
this will be lo_atom in 1 dimension.
"""
def __new__(cls, lo_atom, hi_atom):
def __new__(cls, lo_atom, hi_atom, shape=None, start=None):
""" Create a new Slice object.
:param int lo_atom: Index of the lowest atom to represent.
Expand All @@ -44,14 +49,37 @@ def __new__(cls, lo_atom, hi_atom):
raise PacmanValueError(
'hi_atom {:d} < lo_atom {:d}'.format(hi_atom, lo_atom))

if (start is None) != (shape is None):
raise PacmanValueError(
"Both shape and start must be specified together")
if shape is not None and len(shape) != len(start):
raise PacmanValueError(
"Both shape and start must have the same length")

# Number of atoms represented by this slice
n_atoms = hi_atom - lo_atom + 1

# The shape of the atoms in the slice is all the atoms in a line by
# default
if shape is None:
shape = (n_atoms,)
start = (lo_atom,)

# Create the Slice object as a `namedtuple` with these pre-computed
# values filled in.
return super().__new__(cls, lo_atom, hi_atom, n_atoms)
return super().__new__(cls, lo_atom, hi_atom, n_atoms, shape, start)

@property
def as_slice(self):
# Slice for accessing arrays of values
return slice(self.lo_atom, self.hi_atom + 1)

def get_slice(self, n):
""" Get a slice in the n-th dimension
:param int n: The 0-indexed dimension to get the shape of
"""
if n < 0 or n > len(self.shape):
raise IndexError(f"{n} is invalid for slice with {len(self.shape)}"
" dimensions")
return slice(self.start[n], self.shape[n])
65 changes: 31 additions & 34 deletions pacman/model/partitioner_splitters/splitter_external_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@
MachineFPGAVertex, MachineSpiNNakerLinkVertex, MachineEdge)
from pacman.exceptions import PacmanConfigurationException,\
PacmanNotExistException
from pacman.model.graphs.common.slice import Slice
import math


class SplitterExternalDevice(AbstractSplitterCommon):
Expand All @@ -36,19 +34,21 @@ class SplitterExternalDevice(AbstractSplitterCommon):
# Slices of incoming vertices (not exactly but hopefully close enough)
"__incoming_slices",
# Slice of outgoing vertex (which really doesn't matter here)
"__outgoing_slice"
"__outgoing_slice",
# If the outgoing vertex is one of the incoming ones
"__outgoing_is_incoming"
]

def __init__(self, splitter_name=None):
super(SplitterExternalDevice, self).__init__(splitter_name)
self.__incoming_slices = None
self.__outgoing_slice = None
@overrides(AbstractSplitterCommon.set_governed_app_vertex)
def set_governed_app_vertex(self, app_vertex):
super(SplitterExternalDevice, self).set_governed_app_vertex(app_vertex)

@overrides(AbstractSplitterCommon.create_machine_vertices)
def create_machine_vertices(self, resource_tracker, machine_graph):
self.__incoming_vertices = list()
self.__incoming_slices = list()
self.__outgoing_vertex = None
app_vertex = self._governed_app_vertex
self.__outgoing_slice = None
# Easier to set this True first to avoid a None check later
self.__outgoing_is_incoming = True
if isinstance(app_vertex, ApplicationFPGAVertex):
# This can have multiple FPGA connections per board
seen_incoming = dict()
Expand All @@ -57,60 +57,57 @@ def create_machine_vertices(self, resource_tracker, machine_graph):
label = (f"Machine vertex for {app_vertex.label}"
f":{fpga.fpga_id}:{fpga.fpga_link_id}"
f":{fpga.board_address}")
for _ in range(app_vertex.n_machine_vertices_per_link):
for i in range(app_vertex.n_machine_vertices_per_link):
vertex_slice = app_vertex.get_incoming_slice_for_link(
fpga, i)
vertex = MachineFPGAVertex(
fpga.fpga_id, fpga.fpga_link_id,
fpga.board_address, label, app_vertex=app_vertex)
seen_incoming[fpga] = vertex
machine_graph.add_vertex(vertex)
fpga.board_address, label, app_vertex=app_vertex,
vertex_slice=vertex_slice)
seen_incoming[fpga] = (vertex, vertex_slice)
self.__incoming_vertices.append(vertex)
self.__incoming_slices.append(vertex_slice)
fpga = app_vertex.outgoing_fpga_connection
if fpga is not None:
if fpga in seen_incoming:
self.__outgoing_vertex = seen_incoming[fpga]
self.__outgoing_vertex, self.__outgoing_slice =\
seen_incoming[fpga]
else:
vertex_slice = app_vertex.get_outgoing_slice()
vertex = MachineFPGAVertex(
fpga.fpga_id, fpga.fpga_link_id, fpga.board_address)
machine_graph.add_vertex(vertex)
fpga.fpga_id, fpga.fpga_link_id, fpga.board_address,
app_vertex=app_vertex, vertex_slice=vertex_slice)
self.__outgoing_vertex = vertex
self.__outgoing_slice = vertex_slice
self.__outgoing_is_incoming = False

elif isinstance(app_vertex, ApplicationSpiNNakerLinkVertex):
# So far this only handles one connection in total
label = f"Machine vertex for {app_vertex.label}"
vertex = MachineSpiNNakerLinkVertex(
app_vertex.spinnaker_link_id, app_vertex.board_address, label,
app_vertex=app_vertex)
machine_graph.add_vertex(vertex)
self.__incoming_vertices = [vertex]
self.__outgoing_vertex = vertex
else:
raise PacmanConfigurationException(
f"Unknown vertex type to splitter: {app_vertex}")

@overrides(AbstractSplitterCommon.create_machine_vertices)
def create_machine_vertices(self, resource_tracker, machine_graph):
for vertex in self.__incoming_vertices:
machine_graph.add_vertex(vertex)
if not self.__outgoing_is_incoming:
machine_graph.add_vertex(self.__outgoing_vertex)

@overrides(AbstractSplitterCommon.get_in_coming_slices)
def get_in_coming_slices(self):
if self.__outgoing_vertex is None:
return []
if self.__outgoing_slice is None:
# We actually don't care but hopefully this is OK...
self.__outgoing_slice = Slice(0, self._governed_app_vertex.n_atoms)
return [self.__outgoing_slice], True

@overrides(AbstractSplitterCommon.get_out_going_slices)
def get_out_going_slices(self):
if self.__incoming_slices is not None:
return self.__incoming_slices, True

# This is a bit convoluted, since the slices are ill-defined here;
# The number of slices will at least be correct though.
app_vertex = self._governed_app_vertex
fpga_conns = list(app_vertex.incoming_fpga_connections)
v_per_link = app_vertex.n_machine_vertices_per_link
atoms_per_slice = int(math.ceil(
app_vertex.n_atoms / (len(fpga_conns) * v_per_link)))
self.__incoming_slices = [Slice(0, atoms_per_slice)
for _ in fpga_conns
for _ in range(v_per_link)]
return self.__incoming_slices, True

@overrides(AbstractSplitterCommon.get_in_coming_vertices)
Expand Down

0 comments on commit 1e724cf

Please sign in to comment.