Copyright (C) 2021 Intel Corporation SPDX-License-Identifier: BSD-3-Clause See: https://spdx.org/licenses/


Execution

This tutorial covers how to execute single Processes and networks of Processes, how to configure execution, how to pause, resume, and stop execution, and how to manually set up a Compiler and RunTime for more fine-grained control.

Configuring and starting execution

To start executing a Process call its method run(condition=..., run_cfg=...). The execution must be configured by passing in both a RunCondition and a RunConfiguration.

Run conditions

A RunCondition specifies how long a Process is executed.

The run condition RunSteps executes a Process for a specified number time steps, here 42 in the example below. The execution will automatically pause after the specified number of time steps. You can also specify whether or not the call to run() will block the program flow.

[1]:
from lava.magma.core.run_conditions import RunSteps

run_condition = RunSteps(num_steps=42, blocking=False)

The run condition RunContinuous enables you to run a Process continuously. In this case, the Process will run indefinitely until you explicitly call pause() or stop() (see below). This call never blocks the program flow (blocking=False).

[2]:
from lava.magma.core.run_conditions import RunContinuous

run_condition = RunContinuous()

Run configurations

A RunConfig specifies on what devices the Processes should be executed. Based on the RunConfig, a Process selects and initializes exactly one of its associated ProcessModels, which implement the behavior of the Process in a particular programming language and for a particular computing resource. If the Process has a SubProcessModel composed of other Processes, the RunConfig chooses the appropriate ProcessModel implementation of the child Process.

Lava currently supports execution on Loihi 2 using the predefined RunConfig Loihi2HwCfg. Simulation of Loihi executed on a single CPU is possible using the RunConfig Loihi1SimCfg. We will make more predefined run configurations available with the upcoming support for Loihi 1 and other devices such as GPUs.

The example below specifies that the Process (and all its connected Processes and SubProcesses) are executed in Python on a CPU.

[3]:
from lava.magma.core.run_configs import Loihi1SimCfg

run_cfg = Loihi1SimCfg()

We can now use both a RunCondition and a RunConfig to execute a simple leaky integrate-and-fire (LIF) neuron.

[4]:
from lava.proc.lif.process import LIF
from lava.magma.core.run_conditions import RunSteps
from lava.magma.core.run_configs import Loihi1SimCfg

# create a Process for a LIF neuron
lif = LIF(shape=(1,))

# execute that Process for 42 time steps in simulation
lif.run(condition=RunSteps(num_steps=42), run_cfg=Loihi1SimCfg())

Running multiple Processes

Calling run() on a Process will also execute all Processes that are connected to it. In the example below, three Processes lif1, dense, and lif2 are connected in a sequence. We call run() on Process lif2. Since lif2 is connected to dense and dense is connected to lif1, all three Processes will be executed. As demonstrated here, the execution will cover the entire connected network of Processes, irrespective of the direction in which the Processes are connected.

[5]:
import numpy as np
from lava.proc.lif.process import LIF
from lava.proc.dense.process import Dense
from lava.magma.core.run_conditions import RunSteps
from lava.magma.core.run_configs import Loihi1SimCfg

# create processes
lif1 = LIF(shape=(1,))
dense = Dense(weights=np.eye(1))
lif2 = LIF(shape=(1,))

# connect the OutPort of lif1 to the InPort of dense
lif1.s_out.connect(dense.s_in)
# connect the OutPort of dense to the InPort of lif2
dense.a_out.connect(lif2.a_in)

# execute Process lif2 and all Processes connected to it (dense, lif1)
lif2.run(condition=RunSteps(num_steps=42), run_cfg=Loihi1SimCfg())

Pausing, resuming, and stopping execution

Another way to execute Processes is to use the RunCondition RunContinuous which runs the network non-blocking until the the execution is paused or stopped by the user.

Calling the pause() method of a Process pauses execution but preserves its state. The Process can then be inspected and manipulated by the user, as shown in the example below.

Afterward, execution can be resumed by calling run() again.

Calling the stop() method of a Process completely terminates its execution. Contrary to pausing execution, stop() does not preserve the state of the Process. If a Process executed on a hardware device, the connection between the Process and the device is terminated as well.

[6]:
import numpy as np
from lava.proc.lif.process import LIF
from lava.magma.core.run_conditions import RunContinuous
from lava.magma.core.run_configs import Loihi1SimCfg

lif3 = LIF(shape=(1, ))

# start continuous execution
lif3.run(condition=RunContinuous(), run_cfg=Loihi1SimCfg())

# pause execution
lif3.pause()

# inspect the state of the Process, here, the voltage variable 'v'
print(lif3.v.get())

# manipulate the state of the Process, here, resetting the voltage to zero
lif3.v.set(np.array([0]))

# resume continuous execution
lif3.run(condition=RunContinuous(), run_cfg=Loihi1SimCfg())

# terminate execution;
# after this, you no longer have access to the state of lif
lif3.stop()

[0.]

Manual compilation and execution

In many cases, creating an instance of a Process and calling its run() method is all you need to do. Calling run() internally first compiles the Process and then starts execution. These steps can also be manually invoked in sequence, for instance to inspect or manipulate the Process before starting execution.

  1. Instantiation stage: This is the call to the init-method of a Process, which instantiates an object of the Process.

[7]:
from lava.proc.lif.process import LIF
from lava.proc.dense.process import Dense

lif1 = LIF(shape=(1,))
dense = Dense(weights=np.eye(1))
lif2 = LIF(shape=(1,))
  1. Configuration stage: After a Process has been instantiated, it can be configured further through its public API and connected to other Processes via its Ports. In addition, probes can be defined for Lava Vars in order to record a time series of its evolution during execution.

[8]:
# connect the processes
lif1.s_out.connect(dense.s_in)
dense.a_out.connect(lif2.a_in)
  1. Compile stage: After a Process has been configured, it needs to be compiled to become executable. After the compilation stage, the state of the Process can still be manipulated and inspected.

[9]:
from lava.magma.compiler.compiler import Compiler
from lava.magma.core.run_configs import Loihi1SimCfg

# create a compiler
compiler = Compiler()

# compile the Process (and all connected Processes) into an executable
executable = compiler.compile(lif2, run_cfg=Loihi1SimCfg())
  1. Execution stage: When compilation is complete, Processes can be executed. The execution stage ensures that the (prior) compilation stage has been completed and otherwise invokes it.

[10]:
from lava.magma.runtime.runtime import Runtime
from lava.magma.core.run_conditions import RunSteps
from lava.magma.core.process.message_interface_enum import ActorType

# create and initialize a runtime
mp = ActorType.MultiProcessing
runtime = Runtime(exe=executable,
                  message_infrastructure_type=mp)
runtime.initialize()

# start execution
runtime.start(run_condition=RunSteps(num_steps=42))

# stop execution
runtime.stop()

The following does all of the above automatically:

[11]:
from lava.proc.lif.process import LIF
from lava.proc.dense.process import Dense
from lava.magma.core.run_conditions import RunSteps
from lava.magma.core.run_configs import Loihi1SimCfg

# create Processes
lif = LIF(shape=(1,))
dense = Dense(weights=np.eye(1))

# connect Processes
lif.s_out.connect(dense.s_in)

# execute Processes
lif.run(condition=RunSteps(num_steps=42), run_cfg=Loihi1SimCfg())

# stop Processes
lif.stop()

How to learn more?

Learn more about Lava in the next tutorial about how to transfer data between Processes.

If you want to find out more about how to compile and execute Processes, have a look at the Lava documentation or dive into the Compiler and RunTime source code.

To receive regular updates on the latest developments and releases of the Lava Software Framework please subscribe to the INRC newsletter.