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


Remote Memory Access

The goal of this tutorial is to show how to enable remote memory access between processes using Lava RefPorts. In previous tutorials you have been introduced to Processes which define behavior and ProcessModels which implement the behavior for specific compute resources, e.g., CPU or Loihi Neurocores.

In general, processes have only access to its own state and communicate with the enviornment only through messages using ports. Lava also allows certain processes (e.g. those on CPUs) to perform remote memory access of internal states on other processes. Remote memory access between processes is potentially unsafe and should be used with care, but can be very useful in defined cases. One such case would be accessing (read/write) a Var of a Process on a Loihi NeuroCore from another Process on the embedded CPU.

In Lava, even remote memory access between Processes is realized via message-passing to remain true to the overall event-based message passing concept. The read/write is implemented via channels and message passing between processes and the remote process modifies its memory itself based on instructions from another process. However, as a convenience feature, RefPorts and VarPorts syntactically simplify the act of interacting with remote Vars.

Thus, RefPorts allow in Lava one Process to access the internal Vars of another Process. RefPorts give access to other Vars as if it was an internal Var.

edeeac4a14aa4bd7b047237cffd0fe3d

In this tutorial, we will create minimal Processes and ProcessModels to demonstrate reading and writing of Vars using RefPorts and VarPorts. Furthermore, we will explain the possibilities to connect RefPorts with VarPorts and Vars as well as the difference of explicitly and implicitly created VarPorts.

Create a minimal Process and ProcessModel with a RefPort

The ProcessModel Tutorial walks through the creation of Processes and corresponding ProcessModels. In order to demonstrate RefPorts we create a minimal process P1 with a RefPort ref and a minimal process P2 with a Var var.

f897ff8237a84ee7af0c48bf7b98d76d

[1]:
from lava.magma.core.process.process import AbstractProcess
from lava.magma.core.process.variable import Var
from lava.magma.core.process.ports.ports import RefPort


# A minimal process with a Var and a RefPort
class P1(AbstractProcess):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.ref = RefPort(shape=(1,))


# A minimal process with a Var
class P2(AbstractProcess):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.var = Var(shape=(1,), init=5)

Create a Python Process Model implementing the Loihi Sync Protocol and requiring a CPU compute resource

We also create the corresponding ProcessModels PyProcModel1 and PyProcModel2 which implement the process P1 and P2. The value of the Var of P2 var is initialized with the value 5. The behavior we implement prints out the value of the var in P1 every time step, demonstrating the read ability of a RefPort ref. Afterwards we set the value of var by adding the current time step to it and write it with ref, demonstrating the write abiltity of a RefPort.

[2]:
import numpy as np

from lava.magma.core.sync.protocols.loihi_protocol import LoihiProtocol
from lava.magma.core.model.py.ports import PyRefPort
from lava.magma.core.model.py.type import LavaPyType
from lava.magma.core.resources import CPU
from lava.magma.core.decorator import implements, requires
from lava.magma.core.model.py.model import PyLoihiProcessModel


# A minimal PyProcModel implementing P1
@implements(proc=P1, protocol=LoihiProtocol)
@requires(CPU)
class PyProcModel1(PyLoihiProcessModel):
    ref: PyRefPort = LavaPyType(PyRefPort.VEC_DENSE, int)

    def post_guard(self):
        return True

    def run_post_mgmt(self):
        # Retrieve current value of the Var of P2
        cur_val = self.ref.read()
        print("Value of var: {} at time step: {}".format(cur_val, self.time_step))

        # Add the current time step to the current value
        new_data = cur_val + self.time_step
        # Write the new value to the Var of P2
        self.ref.write(new_data)


# A minimal PyProcModel implementing P2
@implements(proc=P2, protocol=LoihiProtocol)
@requires(CPU)
class PyProcModel2(PyLoihiProcessModel):
    var: np.ndarray = LavaPyType(np.ndarray, np.int32)

Run the Processes

The RefPort ref needs to be connected with the Var var, before execution. The expected output will be the initial value 5 of var at the beginning, followed by 6 (5+1), 8 (6+2), 11 (8+3), 15 (11+4).

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

# Create process P1 and P2
proc1 = P1()
proc2 = P2()

# Connect RefPort 'ref' of P1 with Var 'var' of P2 using an implicit VarPort
proc1.ref.connect_var(proc2.var)

# Run the network for 5 time steps
proc1.run(condition=RunSteps(num_steps=5), run_cfg=Loihi1SimCfg())
proc1.stop()
Value of var: [5] at time step: 1
Value of var: [6] at time step: 2
Value of var: [8] at time step: 3
Value of var: [11] at time step: 4
Value of var: [15] at time step: 5

Implicit and explicit VarPorts

In the example above we demonstrated the read and write ability of a RefPort which used an implicit VarPort to connect to the Var. An implicit VarPort is created when connect_var(..) is used to connect a RefPort with a Var. A RefPort can also be connected to a VarPort explicitly defined in a Process using connect(..). In order to demonstrate explicit VarPorts we redefine Process P2 and the corresponding ProcessModel.

[4]:
from lava.magma.core.process.ports.ports import VarPort

# A minimal process with a Var and an explicit VarPort
class P2(AbstractProcess):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.var = Var(shape=(1,), init=5)
        self.var_port = VarPort(self.var)
[5]:
from lava.magma.core.model.py.ports import PyVarPort

# A minimal PyProcModel implementing P2
@implements(proc=P2, protocol=LoihiProtocol)
@requires(CPU)
class PyProcModel2(PyLoihiProcessModel):
    var: np.ndarray = LavaPyType(np.ndarray, np.int32)
    var_port: PyVarPort = LavaPyType(PyVarPort.VEC_DENSE, int)

This time the RefPort ref is connected to the explicitly defined VarPort var_port. The output is the same as before.

[6]:
# Create process P1 and P2
proc1 = P1()
proc2 = P2()

# Connect RefPort 'ref' of P1 with VarPort 'var_port' of P2
proc1.ref.connect(proc2.var_port)

# Run the network for 5 time steps
proc1.run(condition=RunSteps(num_steps=5), run_cfg=Loihi1SimCfg())
proc1.stop()
Value of var: [5] at time step: 1
Value of var: [6] at time step: 2
Value of var: [8] at time step: 3
Value of var: [11] at time step: 4
Value of var: [15] at time step: 5

Options to connect RefPorts and VarPorts

RefPorts can be connected in different ways to Vars and VarPorts. RefPorts and VarPorts can also be connected to themselves in case of hierarchical processes.

8f5a6f981aa84f64bcea53a4003a1769

  • RefPorts can be connected to RefPorts or VarPorts using connect(..)

  • RefPorts can be connected to Vars using connect_var(..)

  • RefPorts can receive connections from RefPorts using connect_from(..)

  • VarPorts can be connected to VarPorts using connect(..)

  • VarPorts can receive connections from VarPorts or RefPorts using connect_from(..)

How to learn more?

If you want to find out more about Remote Memory Access, have a look at the Lava documentation or dive into the source code.

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