Skip to content

Commit

Permalink
squash! Add wall-time step timers to Simulator
Browse files Browse the repository at this point in the history
Having separate build and connect methods didn't seem to make
sense, especially because connect depended on build and other
methods depended on either connect or build and sometimes it
raised an error if one wasn't called, and other times it would
just call it. With the changes in this commit, we just have a
`connect` method which does the build and connect (it could
be called `build`, but the `connected` property makes more sense,
and it would be weird to set `connected` with `build`).
If we expect to be connected and aren't, we raise an error.
  • Loading branch information
tbekolay committed Dec 20, 2019
1 parent 6b6a060 commit 1aeca78
Show file tree
Hide file tree
Showing 3 changed files with 35 additions and 53 deletions.
63 changes: 27 additions & 36 deletions nengo_loihi/hardware/interface.py
Expand Up @@ -53,6 +53,7 @@ class HardwareInterface:
Defaults to one block and one input per core on a single chip.
"""

connection_retries = 10
min_nxsdk_version = LooseVersion("0.8.7")
max_nxsdk_version = LooseVersion("0.9.0")
channel_packet_elements = 64 # size of channel packets in int32s
Expand Down Expand Up @@ -105,8 +106,9 @@ def __enter__(self):
raise SimulationError(
"Loihi interface has been closed and cannot be reopened."
)
if not self.is_built:
self.build()

if not self.connected:
self.connect()
return self

def __exit__(self, exc_type, exc_value, traceback):
Expand Down Expand Up @@ -137,10 +139,14 @@ def _iter_probes(self):
yield probe

@property
def is_built(self):
return self.nxsdk_board is not None
def connected(self):
return self.nxsdk_board is not None and d_func(
self.nxsdk_board, b"ZXhlY3V0b3I=", b"aGFzU3RhcnRlZA=="
)

def connect(self):
"""Builds internal data structures and connects to the board."""

def build(self):
assert self.nxsdk_board is None, "Cannot rebuild model"

self.pes_error_scale = getattr(self.model, "pes_error_scale", 1.0)
Expand All @@ -167,11 +173,22 @@ def build(self):
if self.use_snips:
self.create_io_snip()

def run_steps(self, steps, blocking=True):
if not self.is_built:
self.build()
# --- connect to the board
logger.info("Connecting to Loihi, max attempts: %d", self.connection_retries)
for i in range(self.connection_retries):
try:
d_func(self.nxsdk_board, b"c3RhcnQ=")
if self.connected:
break
except Exception as e:
logger.warning("Connection error: %s", e)
time.sleep(1)
logger.info("Retrying, attempt %d", i + 1)
else:
raise SimulationError("Could not connect to the board")

self.connect() # returns immediately if already connected
def run_steps(self, steps, blocking=True):
assert self.connected, "Interface is not built"

# start the board running the desired number of steps
d_get(self.nxsdk_board, b"cnVu")(steps, **{d(b"YVN5bmM="): not blocking})
Expand Down Expand Up @@ -395,31 +412,6 @@ def host2chip(self, spikes, errors):
def wait_for_completion(self):
d_func(self.nxsdk_board, b"ZmluaXNoUnVu")

def is_connected(self):
return self.nxsdk_board is not None and d_func(
self.nxsdk_board, b"ZXhlY3V0b3I=", b"aGFzU3RhcnRlZA=="
)

def connect(self, attempts=10):
if not self.is_built:
raise SimulationError("Must build model before running")

if self.is_connected():
return

logger.info("Connecting to Loihi, max attempts: %d", attempts)
for i in range(attempts):
try:
d_func(self.nxsdk_board, b"c3RhcnQ=")
if self.is_connected():
break
except Exception as e:
logger.warning("Connection error: %s", e)
time.sleep(1)
logger.info("Retrying, attempt %d", i + 1)
else:
raise SimulationError("Could not connect to the board")

def close(self):
if self.host_socket is not None and self.host_socket_connected:
# send -1 to signal host/chip that we're done
Expand Down Expand Up @@ -477,8 +469,7 @@ def get_probe_output(self, probe):
return self._filter_probe(probe, data)

def create_io_snip(self):
# snips must be created before connecting
assert not self.is_connected(), "still connected"
assert not self.connected, "snips must be created before connecting"

snips_dir = os.path.join(os.path.dirname(__file__), "snips")
env = jinja2.Environment(
Expand Down
20 changes: 8 additions & 12 deletions nengo_loihi/hardware/tests/test_interface.py
Expand Up @@ -8,7 +8,8 @@
from nengo_loihi.hardware import interface as hardware_interface
from nengo_loihi.hardware.allocators import OneToOne
from nengo_loihi.hardware.builder import build_board
from nengo_loihi.nxsdk_obfuscation import d_set
from nengo_loihi.hardware.nxsdk_shim import NxsdkBoard
from nengo_loihi.nxsdk_obfuscation import d


class MockNxsdk:
Expand Down Expand Up @@ -95,17 +96,13 @@ def test_builder_poptype_errors():


@pytest.mark.target_loihi
def test_interface_connection_errors(Simulator):
def test_interface_connection_errors(Simulator, monkeypatch):
with nengo.Network() as net:
nengo.Ensemble(2, 1)

# test unbuilt model error
# test opening closed interface error
sim = Simulator(net)
interface = sim.sims["loihi"]
with pytest.raises(SimulationError, match="build.*before running"):
interface.connect()

# test opening closed interface error
interface.close()
with pytest.raises(SimulationError, match="cannot be reopened"):
with interface:
Expand All @@ -116,12 +113,11 @@ def test_interface_connection_errors(Simulator):
def start(*args, **kwargs):
raise Exception("Mock failure to connect")

with Simulator(net) as sim:
interface = sim.sims["loihi"]
d_set(interface.nxsdk_board, b"c3RhcnQ=", val=start)
monkeypatch.setattr(NxsdkBoard, d(b"c3RhcnQ="), start)

with pytest.raises(SimulationError, match="[Cc]ould not connect"):
interface.connect(attempts=1)
with pytest.raises(SimulationError, match="[Cc]ould not connect"):
with Simulator(net):
pass


@pytest.mark.target_loihi
Expand Down
5 changes: 0 additions & 5 deletions nengo_loihi/simulator.py
Expand Up @@ -408,11 +408,6 @@ def __init__(self, model, sims, precompute, timers):
self.loihi = sims.get("loihi", None)

if self.loihi is not None:
# build and connect outside run_steps
if not self.loihi.is_built:
self.loihi.build()
self.loihi.connect()

run_steps = {
(
True,
Expand Down

4 comments on commit 1aeca78

@hunse
Copy link
Collaborator

@hunse hunse commented on 1aeca78 Dec 20, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One place where it was nice to have this separate was for power measurements. We need to do a baseline power measurement, and it's good to have it as close to the actual run as possible (since chip power draw changes with temperature, etc.). The build can take a long time (minutes). Having them separate, I could make the Simulator for the actual network and have it all built, then make and run another Simulator for the baseline power, before running the actual network. I'm worried if we connect right away, it would stop the baseline power simulator from connecting and running in this kind of setup. It's possible it would still work (if multiple simulators can be connected at once), but definitely something we should check.

@tbekolay
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before this PR, the build was done at the end of __init__, which would make that separation pretty straightforward (build in __init__, then connect when you enter the context manager). Is there a reason that was moved from the __init__?

@hunse
Copy link
Collaborator

@hunse hunse commented on 1aeca78 Dec 20, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We were doing HardwareInterface.create_io_snip at the start of connect, and I moved it to build so that it wouldn't be included in timing. I think I removed it from __init__ when doing that, but looking at it now, I don't see a clear reason that build can't still happen in __init__. So I'd be fine moving it back there.

@tbekolay
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I'll do that.

Please sign in to comment.