NengoDL Simulator¶
This is the class that allows users to access the nengo_dl
backend. This can be used as a drop-in replacement for nengo.Simulator
(i.e., simply replace any instance of nengo.Simulator
with
nengo_dl.Simulator
and everything will continue to function as
normal).
In addition, the Simulator exposes features unique to the
NengoDL backend, such as Simulator.train
.
The full class documentation can be viewed in the API Reference; here we will explain the practical usage of the Simulator in more depth.
Simulator arguments¶
The NengoDL Simulator
has a number of optional arguments, beyond
those in nengo.Simulator
, which control features specific to
the nengo_dl
backend.
device¶
This specifies the computational device on which the simulation will
run. The default is None
, which means that operations will be assigned
according to TensorFlow’s internal logic (generally speaking, this means that
things will be assigned to the GPU if tensorflow-gpu
is installed,
otherwise everything will be assigned to the CPU). The device can be set
manually by passing the TensorFlow device specification to this
parameter. For example, setting device="/cpu:0"
will force everything
to run on the CPU. This may be worthwhile for small models, where the extra
overhead of communicating with the GPU outweighs the actual computations. On
systems with multiple GPUs, device="/gpu:0"
/"/gpu:1"
/etc. will select
which one to use.
unroll_simulation¶
This controls how many simulation iterations are executed each time through the outer simulation loop. That is, we could run 20 timesteps as
for i in range(20):
<run 1 step>
or
for i in range(5):
<run 1 step>
<run 1 step>
<run 1 step>
<run 1 step>
This is an optimization process known as “loop unrolling”, and
unroll_simulation
controls how many simulation steps are unrolled. The
first example above would correspond to unroll_simulation=1
, and the
second would be unroll_simulation=4
.
Unrolling the simulation will result in faster simulation speed, but increased build time and memory usage.
In general, unrolling the simulation will have no impact on the output of a
simulation. The only case in which unrolling may have an impact is if
the number of simulation steps is not evenly divisible by
unroll_simulation
. In that case extra simulation steps will be executed,
and then data will be truncated to the correct number of steps. However, those
extra steps could still change the internal state of the simulation, which
will affect any subsequent calls to sim.run
. So it is recommended that the
number of steps always be evenly divisible by unroll_simulation
.
minibatch_size¶
nengo_dl
allows a model to be simulated with multiple simultaneous inputs,
processing those values in parallel through the network. For example, instead
of executing a model three times with three different inputs, the model can
be executed once with those three inputs in parallel. minibatch_size
specifies how many inputs will be processed at a time. The default is
None
, meaning that this feature is not used and only one input will be
processed at a time (as in standard Nengo simulators).
In order to take advantage of the parallel inputs, multiple inputs need to
be passed to Simulator.run
via the data
argument. This
is discussed in more detail below.
When using Simulator.train
, this parameter controls how many items
from the training data will be used for each optimization iteration.
tensorboard¶
This can be used to specify an output directory if you would like to export data from the simulation in a format that can be visualized in TensorBoard.
To view the collected data, run the command
tensorboard --logdir <tensorboard_dir>
(where tensorboard_dir
is the directory name passed to tensorboard
),
then open a web browser and navigate to http://localhost:6006.
By default the TensorBoard output will only contain a visualization of the TensorFlow graph constructed for this Simulator. However, TensorBoard can also be used to track various aspects of the simulation throughout the training process; see the sim.train documentation for details.
Repeated Simulator calls with the same output directory will be organized into
subfolders according to run number (e.g., <tensorboard_dir>/run_0
).
Simulator.run arguments¶
Simulator.run
(and its variations Simulator.step
/
Simulator.run_steps
) also have some optional parameters beyond those
in the standard Nengo simulator.
data¶
This parameter can be used to override the value of any
input Node
in a model (an input node is defined as
a node with no incoming connections). For example
n_steps = 5
with nengo.Network() as net:
node = nengo.Node([0])
p = nengo.Probe(node)
with nengo_dl.Simulator(net) as sim:
sim.run_steps(n_steps)
will execute the model in the standard way, and if we check the output of
node
print(sim.data[p])
>>> [[ 0.] [ 0.] [ 0.] [ 0.] [ 0.]]
we see that it is all zero, as defined.
data
is specified as a
dictionary of {my_node: override_value}
pairs, where my_node
is the
Node to be overridden and override_value
is a numpy array with shape
(minibatch_size, n_steps, my_node.size_out)
that gives the Node output
value on each simulation step. For example, if we instead run the model via
sim.run_steps(n_steps, data={node: np.ones((1, n_steps, 1))})
print(sim.data[p])
>>> [[ 1.] [ 1.] [ 1.] [ 1.] [ 1.]]
we see that the output of node
is all ones, which is the override
value we specified.
data
is usually used in concert with the minibatching feature of
nengo_dl
(see above). nengo_dl
allows multiple
inputs to be processed simultaneously, but when we construct a
Node
we can only specify one value. For example, if we
use minibatching on the above network
mini = 3
with nengo_dl.Simulator(net, minibatch_size=mini) as sim:
sim.run_steps(n_steps)
print(sim.data[p])
>>> [[[ 0.] [ 0.] [ 0.] [ 0.] [ 0.]]
[[ 0.] [ 0.] [ 0.] [ 0.] [ 0.]]
[[ 0.] [ 0.] [ 0.] [ 0.] [ 0.]]]
we see that the output is an array of zeros with size
(mini, n_steps, 1)
. That is, we simulated 3 inputs
simultaneously, but those inputs all had the same value (the one we defined
when the Node was constructed) so it wasn’t very
useful. To take full advantage of the minibatching we need to override the
node values, so that we can specify a different value for each item in the
minibatch:
with nengo_dl.Simulator(net, minibatch_size=mini) as sim:
sim.run_steps(n_steps, data={
node: np.zeros((mini, n_steps, 1)) + np.arange(mini)[:, None, None]})
print(sim.data[p])
>>> [[[ 0.] [ 0.] [ 0.] [ 0.] [ 0.]]
[[ 1.] [ 1.] [ 1.] [ 1.] [ 1.]]
[[ 2.] [ 2.] [ 2.] [ 2.] [ 2.]]]
Here we can see that 3 independent inputs have been processed during the simulation. In a simple network such as this, minibatching will not make much difference. But for larger models it will be much more efficient to process multiple inputs in parallel rather than one at a time.
profile¶
If set to True
, profiling data will be collected while the simulation
runs. This will significantly slow down the simulation, so it should be left
on False
(the default) in most cases. It is mainly used by developers,
in order to help identify performance bottlenecks.
Profiling data will be saved to a file named nengo_dl_profile.json
. It
can be viewed by opening a Chrome browser, navigating to
chrome://tracing and loading the nengo_dl_profile.json
file. A
filename can be passed instead of True
, to change the output filename.
Note that in order for GPU profiling to work, you need to manually add
<cuda>/extras/CUPTI/libx64
to the LD_LIBRARY_PATH
environment variable
(where <cuda>
is your CUDA installation directory).