Skip to content

Commit

Permalink
Add functions for energy calculation
Browse files Browse the repository at this point in the history
  • Loading branch information
rowleya committed Oct 25, 2024
1 parent 829787f commit 62c4564
Show file tree
Hide file tree
Showing 6 changed files with 216 additions and 5 deletions.
9 changes: 9 additions & 0 deletions spinn_machine/machine.py
Original file line number Diff line number Diff line change
Expand Up @@ -833,6 +833,15 @@ def get_fpga_link_with_id(
f" {board_address}")
return self._fpga_links[b_key]

@property
def n_fpga_links(self) -> int:
"""
The number of FPGA links in the machine.
:rtype: int
"""
return len(self._fpga_links)

def add_spinnaker_links(self) -> None:
"""
Add SpiNNaker links that are on a given machine depending on the
Expand Down
50 changes: 48 additions & 2 deletions spinn_machine/version/abstract_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
from __future__ import annotations
import logging
import re
from typing import (Dict, Iterable, List, Optional, Sequence, Tuple,
TYPE_CHECKING)
from typing import (
Dict, Iterable, List, Optional, Sequence, Tuple, TypeAlias, TYPE_CHECKING)

from spinn_utilities.abstract_base import AbstractBase, abstractmethod
from spinn_utilities.log import FormatAdapter
Expand All @@ -28,6 +28,10 @@

logger = FormatAdapter(logging.getLogger(__name__))

ChipXY: TypeAlias = Tuple[int, int]
RouterPackets: TypeAlias = Dict[ChipXY, Dict[str, int]]
ChipActiveTime: TypeAlias = Dict[ChipXY, float]

CORE_RANGE = re.compile(r"(\d+)-(\d+)")
CORE_SINGLE = re.compile(r"(-*)(\d+)")

Expand Down Expand Up @@ -528,5 +532,47 @@ def version_parse_cores_string(self, core_string: str) -> Iterable[int]:
"""
raise NotImplementedError

@abstractmethod
def get_idle_energy(
self, time_s: float, n_frames: int, n_boards: int,
n_chips: int) -> float:
"""
Returns the idle energy consumption of the system in joules
:param float time_s: The time to calculate the energy for in seconds
:param int n_frames: The number of frames
:param int n_boards: The number of boards
:param int n_chips: The number of chips
:rtype: float
"""
raise NotImplementedError

@abstractmethod
def get_active_energy(
self, time_s: float, n_frames: int, n_boards: int, n_chips: int,
chip_active_time: ChipActiveTime,
router_packets: RouterPackets) -> float:
"""
Returns the active energy consumption of the system in joules
:param float time_s: The time to calculate the energy for in seconds
:param int n_frames: The number of frames
:param int n_boards: The number of boards
:param int n_chips: The number of chips
:param dict chip_active_time: The time the cores were active in seconds
:param dict router_packets: The number of packets sent by each router
:rtype: float
"""
raise NotImplementedError

@abstractmethod
def get_router_report_packet_types(self) -> List[str]:
"""
Returns the list of packet types that the router can send
:rtype: list(str)
"""
raise NotImplementedError

def __hash__(self):
return self.number
24 changes: 24 additions & 0 deletions spinn_machine/version/version_3.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from spinn_machine.full_wrap_machine import FullWrapMachine
from spinn_machine.machine import Machine
from .version_spin1 import VersionSpin1
from .abstract_version import ChipActiveTime, RouterPackets

CHIPS_PER_BOARD: Final = {(0, 0): 18, (0, 1): 18, (1, 0): 18, (1, 1): 18}

Expand Down Expand Up @@ -86,5 +87,28 @@ def spinnaker_links(self) -> List[Tuple[int, int, int]]:
def fpga_links(self) -> List[Tuple[int, int, int, int, int]]:
return []

@overrides(VersionSpin1.get_idle_energy)
def get_idle_energy(
self, time_s: float, n_frames: int, n_boards: int,
n_chips: int) -> float:
if n_frames != 0:
raise SpinnMachineException(
"A version 3 SpiNNaker 1 board has no frames!")
if n_boards != 1:
raise SpinnMachineException(
"A version 3 SpiNNaker 1 board has exactly one board!")

return n_chips * self.WATTS_PER_IDLE_CHIP * time_s

@overrides(VersionSpin1.get_active_energy)
def get_active_energy(
self, time_s: float, n_frames: int, n_boards: int, n_chips: int,
chip_active_time: ChipActiveTime,
router_packets: RouterPackets) -> float:
return (
self.get_idle_energy(time_s, n_frames, n_boards, n_chips) +
self._get_router_active_energy(router_packets) +
self._get_core_active_energy(chip_active_time))

def __eq__(self, other):
return isinstance(other, Version3)
62 changes: 62 additions & 0 deletions spinn_machine/version/version_5.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

from .version_48_chips import Version48Chips
from .version_spin1 import VersionSpin1
from .abstract_version import ChipActiveTime, RouterPackets

CHIPS_PER_BOARD: Final = {
(0, 0): 18, (0, 1): 18, (0, 2): 18, (0, 3): 18, (1, 0): 18, (1, 1): 17,
Expand All @@ -39,6 +40,30 @@ class Version5(VersionSpin1, Version48Chips):
"""
__slots__ = ()

#: given from Indar's measurements
WATTS_PER_BOARD_FPGAS: Final = 0.584635

#: measured from the real power meter and timing between
#: the photos for a days powered off
#: this is the cost of just a frame itself, including the switch and
#: cooling, while the system is idle.
WATTS_FOR_FRAME_IDLE_COST: Final = 117

#: measured from the loading of the column and extrapolated
#: this is the cost of just a frame itself, including the switch and
#: cooling, while the system is active, over the idle cost for simplicity
WATTS_PER_FRAME_ACTIVE_COST_OVERHEAD: Final = 154.163558 - 117

# pylint: disable=invalid-name
#: measured from the real power meter and timing between the photos
#: for a day powered off
WATTS_FOR_BOXED_48_CHIP_FRAME_IDLE_COST: Final = 4.5833333

# pylint: disable=invalid-name
#: measured from the real power meter and timing between the photos
#: for a day powered off
WATTS_FOR_UNBOXED_48_CHIP_FRAME_IDLE_COST: Final = 4.5833333

@property
@overrides(VersionSpin1.name)
def name(self) -> str:
Expand Down Expand Up @@ -83,5 +108,42 @@ def fpga_links(self) -> List[Tuple[int, int, int, int, int]]:
(7, 6, 0, 2, 10), (7, 6, 1, 2, 9),
(7, 7, 0, 2, 8), (7, 7, 1, 2, 7), (7, 7, 2, 2, 6)]

@overrides(VersionSpin1.get_idle_energy)
def get_idle_energy(
self, time_s: float, n_frames: int, n_boards: int,
n_chips: int) -> float:
# Chips idle energy
energy = n_chips * self.WATTS_PER_IDLE_CHIP * time_s

# The container of the boards idle energy
if n_frames != 0:
energy += n_frames * self.WATTS_FOR_FRAME_IDLE_COST * time_s
elif n_boards == 1:
energy += (
self.WATTS_FOR_BOXED_48_CHIP_FRAME_IDLE_COST * time_s)
else:
energy += (
n_boards * self.WATTS_FOR_UNBOXED_48_CHIP_FRAME_IDLE_COST *
time_s)

# The FPGA idle energy
energy += n_boards * self.WATTS_PER_BOARD_FPGAS * time_s
return energy

@overrides(VersionSpin1.get_active_energy)
def get_active_energy(
self, time_s: float, n_frames: int, n_boards: int, n_chips: int,
chip_active_time: ChipActiveTime,
router_packets: RouterPackets) -> float:
container_energy = 0.0
if n_frames != 0:
container_energy = (
n_frames * self.WATTS_FOR_FRAME_ACTIVE_COST_OVERHEAD * time_s)
return (
container_energy +
self.get_idle_energy(time_s, n_frames, n_boards, n_chips) +
self._get_router_active_energy(router_packets) +
self._get_core_active_energy(chip_active_time))

def __eq__(self, other):
return isinstance(other, Version5)
53 changes: 51 additions & 2 deletions spinn_machine/version/version_spin1.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import List, Iterable, Tuple
from typing import List, Iterable, Tuple, Final
from spinn_utilities.abstract_base import AbstractBase
from spinn_utilities.exceptions import ConfigException
from spinn_utilities.overrides import overrides

from spinn_machine.exceptions import SpinnMachineException
from .abstract_version import AbstractVersion
from .abstract_version import (
AbstractVersion, RouterPackets, ChipActiveTime)


class VersionSpin1(AbstractVersion, metaclass=AbstractBase):
Expand All @@ -27,6 +28,37 @@ class VersionSpin1(AbstractVersion, metaclass=AbstractBase):
Shared code for all Spin1 board versions
"""

#: stated in papers (SpiNNaker: A 1-W 18 core system-on-Chip for
#: Massively-Parallel Neural Network Simulation)
WATTS_PER_IDLE_CHIP: Final = 0.360

#: stated in papers (SpiNNaker: A 1-W 18 core system-on-Chip for
#: Massively-Parallel Neural Network Simulation)
WATTS_PER_CORE_ACTIVE_OVERHEAD: Final = (1.0 - WATTS_PER_IDLE_CHIP) / 18

JOULES_PER_ROUTER_BIT = 0.000000000025

#: stated in papers (SpiNNaker: A 1-W 18 core system-on-Chip for
#: Massively-Parallel Neural Network Simulation)
# - 25pJ per bit - spike packets are 40 bits so 1nJ per spike
JOULES_PER_PACKET: Final = JOULES_PER_ROUTER_BIT * 40

#: As above, but with extra 32-bits
JOULES_PER_PACKET_WITH_PAYLOAD: Final = JOULES_PER_ROUTER_BIT * 72

#: Cost of each packet type
COST_PER_PACKET_TYPE = {
"Local_Multicast_Packets": JOULES_PER_PACKET,
"External_Multicast_Packets": JOULES_PER_PACKET,
"Reinjected": JOULES_PER_PACKET,
"Local_P2P_Packets": JOULES_PER_PACKET_WITH_PAYLOAD,
"External_P2P_Packets": JOULES_PER_PACKET_WITH_PAYLOAD,
"Local_NN_Packets": JOULES_PER_PACKET,
"External_NN_Packets": JOULES_PER_PACKET,
"Local_FR_Packets": JOULES_PER_PACKET_WITH_PAYLOAD,
"External_FR_Packets": JOULES_PER_PACKET_WITH_PAYLOAD
}

__slots__ = ()

def __init__(self) -> None:
Expand Down Expand Up @@ -73,3 +105,20 @@ def id_to_qx_qy_qp(self, core_id: int) -> Tuple[int, int, int]:
def version_parse_cores_string(self, core_string: str) -> Iterable[int]:
raise ConfigException(
f"{core_string} does not represent cores for Version 1 boards")

@overrides(AbstractVersion.get_router_report_packet_types)
def get_router_report_packet_types(self) -> List[str]:
return list(self.COST_PER_PACKET_TYPE.keys())

def _get_router_active_energy(
self, router_packets: RouterPackets) -> float:
return sum(
value * self.COST_PER_PACKET_TYPE[name]
for packets in router_packets.values()
for name, value in packets.items())

def _get_core_active_energy(
self, core_active_times: ChipActiveTime) -> float:
return sum(
time * self.WATTS_PER_CHIP_ACTIVE_OVERHEAD
for time in core_active_times.values())
23 changes: 22 additions & 1 deletion spinn_machine/version/version_spin2.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
from spinn_utilities.exceptions import ConfigException
from spinn_utilities.overrides import overrides

from .abstract_version import AbstractVersion
from .abstract_version import (
AbstractVersion, ChipActiveTime, RouterPackets)

CHIPS_PER_BOARD: Final = {(0, 0): 152}
CORE_QX_QY_QP = re.compile(r"(\d)\.(\d)\.(\d)")
Expand Down Expand Up @@ -126,3 +127,23 @@ def version_parse_cores_string(self, core_string: str) -> Iterable[int]:

raise ConfigException(
f"{core_string} does not represent cores for Version 2 boards")

@overrides(AbstractVersion.get_idle_energy)
def get_idle_energy(
self, time_s: float, n_frames: int, n_boards: int,
n_chips: int) -> float:
# TODO: Work this out for SpiNNaker 2
raise NotImplementedError

@overrides(AbstractVersion.get_active_energy)
def get_active_energy(
self, time_s: float, n_frames: int, n_boards: int, n_chips: int,
chip_active_time: ChipActiveTime,
router_packets: RouterPackets) -> float:
# TODO: Work this out for SpiNNaker 2
raise NotImplementedError

@overrides(AbstractVersion.get_router_report_packet_types)
def get_router_report_packet_types(self) -> List[str]:
# TODO: Work this out for SpiNNaker 2
raise NotImplementedError

0 comments on commit 62c4564

Please sign in to comment.