{ "cells": [ { "cell_type": "markdown", "id": "8ff39dda", "metadata": {}, "source": [ "*Copyright (C) 2021 Intel Corporation*
\n", "*SPDX-License-Identifier: BSD-3-Clause*
\n", "*See: https://spdx.org/licenses/*\n", "\n", "---\n", "\n", "# Remote Memory Access\n", "\n", "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.\n", "\n", "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. \n", "\n", "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. \n", "\n", "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_. \n", "\n", "\n", "\n", "\n", "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. " ] }, { "cell_type": "markdown", "id": "d3568645", "metadata": {}, "source": [ "## Recommended tutorials before starting: \n", "\n", "- [Installing Lava](./tutorial01_installing_lava.ipynb \"Tutorial on Installing Lava\")\n", "- [Processes](./tutorial02_processes.ipynb \"Tutorial on Processes\")\n", "- [ProcessModel](./tutorial03_process_models.ipynb \"Tutorial on ProcessModels\")\n", "- [Execution](./tutorial04_execution.ipynb \"Tutorial on Executing Processes\")\n", "- [Connecting Processes](./tutorial05_connect_processes.ipynb \"Tutorial on Connecting Processes\")\n", "- [Hierarchical Processes](./tutorial06_hierarchical_processes.ipynb \"Tutorial on Hierarchical Processes\")" ] }, { "cell_type": "markdown", "id": "b2e5ed15", "metadata": {}, "source": [ "## Create a minimal _Process_ and _ProcessModel_ with a _RefPort_" ] }, { "cell_type": "markdown", "id": "9407a4f8", "metadata": {}, "source": [ "The [ProcessModel Tutorial](./tutorial03_process_models.ipynb) 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`. \n", "\n", "" ] }, { "cell_type": "code", "execution_count": 1, "id": "b72d269a", "metadata": {}, "outputs": [], "source": [ "from lava.magma.core.process.process import AbstractProcess\n", "from lava.magma.core.process.variable import Var\n", "from lava.magma.core.process.ports.ports import RefPort\n", "\n", "\n", "# A minimal process with a Var and a RefPort\n", "class P1(AbstractProcess):\n", " def __init__(self, **kwargs):\n", " super().__init__(**kwargs)\n", " self.ref = RefPort(shape=(1,))\n", "\n", " \n", "# A minimal process with a Var\n", "class P2(AbstractProcess):\n", " def __init__(self, **kwargs):\n", " super().__init__(**kwargs)\n", " self.var = Var(shape=(1,), init=5)" ] }, { "cell_type": "markdown", "id": "d3beee93", "metadata": {}, "source": [ "#### Create a Python Process Model implementing the Loihi Sync Protocol and requiring a CPU compute resource\n", "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_." ] }, { "cell_type": "code", "execution_count": 2, "id": "b9b8bad0", "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "\n", "from lava.magma.core.sync.protocols.loihi_protocol import LoihiProtocol\n", "from lava.magma.core.model.py.ports import PyRefPort\n", "from lava.magma.core.model.py.type import LavaPyType\n", "from lava.magma.core.resources import CPU\n", "from lava.magma.core.decorator import implements, requires\n", "from lava.magma.core.model.py.model import PyLoihiProcessModel\n", "\n", "\n", "# A minimal PyProcModel implementing P1\n", "@implements(proc=P1, protocol=LoihiProtocol)\n", "@requires(CPU)\n", "class PyProcModel1(PyLoihiProcessModel):\n", " ref: PyRefPort = LavaPyType(PyRefPort.VEC_DENSE, int)\n", "\n", " def post_guard(self):\n", " return True\n", "\n", " def run_post_mgmt(self):\n", " # Retrieve current value of the Var of P2\n", " cur_val = self.ref.read()\n", " print(\"Value of var: {} at time step: {}\".format(cur_val, self.time_step))\n", " \n", " # Add the current time step to the current value\n", " new_data = cur_val + self.time_step\n", " # Write the new value to the Var of P2\n", " self.ref.write(new_data)\n", "\n", "\n", "# A minimal PyProcModel implementing P2\n", "@implements(proc=P2, protocol=LoihiProtocol)\n", "@requires(CPU)\n", "class PyProcModel2(PyLoihiProcessModel):\n", " var: np.ndarray = LavaPyType(np.ndarray, np.int32)" ] }, { "cell_type": "markdown", "id": "614aa4be", "metadata": {}, "source": [ "## Run the _Processes_\n", "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)." ] }, { "cell_type": "code", "execution_count": 3, "id": "47698fc0", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Value of var: [5] at time step: 1\n", "Value of var: [6] at time step: 2\n", "Value of var: [8] at time step: 3\n", "Value of var: [11] at time step: 4\n", "Value of var: [15] at time step: 5\n" ] } ], "source": [ "from lava.magma.core.run_configs import Loihi1SimCfg\n", "from lava.magma.core.run_conditions import RunSteps\n", "\n", "# Create process P1 and P2\n", "proc1 = P1()\n", "proc2 = P2()\n", "\n", "# Connect RefPort 'ref' of P1 with Var 'var' of P2 using an implicit VarPort\n", "proc1.ref.connect_var(proc2.var)\n", "\n", "# Run the network for 5 time steps\n", "proc1.run(condition=RunSteps(num_steps=5), run_cfg=Loihi1SimCfg())\n", "proc1.stop()" ] }, { "cell_type": "markdown", "id": "b9bf440f", "metadata": {}, "source": [ "## Implicit and explicit VarPorts\n", "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_." ] }, { "cell_type": "code", "execution_count": 4, "id": "d9dd7405", "metadata": {}, "outputs": [], "source": [ "from lava.magma.core.process.ports.ports import VarPort\n", "\n", "# A minimal process with a Var and an explicit VarPort\n", "class P2(AbstractProcess):\n", " def __init__(self, **kwargs):\n", " super().__init__(**kwargs)\n", " self.var = Var(shape=(1,), init=5)\n", " self.var_port = VarPort(self.var)" ] }, { "cell_type": "code", "execution_count": 5, "id": "e2215c6d", "metadata": {}, "outputs": [], "source": [ "from lava.magma.core.model.py.ports import PyVarPort\n", "\n", "# A minimal PyProcModel implementing P2\n", "@implements(proc=P2, protocol=LoihiProtocol)\n", "@requires(CPU)\n", "class PyProcModel2(PyLoihiProcessModel):\n", " var: np.ndarray = LavaPyType(np.ndarray, np.int32)\n", " var_port: PyVarPort = LavaPyType(PyVarPort.VEC_DENSE, int)" ] }, { "cell_type": "markdown", "id": "683df3ff", "metadata": {}, "source": [ "This time the _RefPort_ `ref` is connected to the explicitly defined _VarPort_ `var_port`. The output is the same as before." ] }, { "cell_type": "code", "execution_count": 6, "id": "bfc0ec3b", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Value of var: [5] at time step: 1\n", "Value of var: [6] at time step: 2\n", "Value of var: [8] at time step: 3\n", "Value of var: [11] at time step: 4\n", "Value of var: [15] at time step: 5\n" ] } ], "source": [ "# Create process P1 and P2\n", "proc1 = P1()\n", "proc2 = P2()\n", "\n", "# Connect RefPort 'ref' of P1 with VarPort 'var_port' of P2\n", "proc1.ref.connect(proc2.var_port)\n", "\n", "# Run the network for 5 time steps\n", "proc1.run(condition=RunSteps(num_steps=5), run_cfg=Loihi1SimCfg())\n", "proc1.stop()" ] }, { "cell_type": "markdown", "id": "ff8db42e", "metadata": {}, "source": [ "## Options to connect RefPorts and VarPorts\n", "_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. \n", "\n", "\n", "\n", "* _RefPorts_ can be connected to _RefPorts_ or _VarPorts_ using `connect(..)`\n", "* _RefPorts_ can be connected to _Vars_ using `connect_var(..)`\n", "* _RefPorts_ can receive connections from _RefPorts_ using `connect_from(..)`\n", "\n", "* _VarPorts_ can be connected to _VarPorts_ using `connect(..)`\n", "* _VarPorts_ can receive connections from _VarPorts_ or _RefPorts_ using `connect_from(..)`" ] }, { "cell_type": "markdown", "id": "1eb2d987", "metadata": {}, "source": [ "## How to learn more?\n", "\n", "If you want to find out more about _Remote Memory Access_, have a look at the [Lava documentation](https://lava-nc.org/) or dive into the [source code](https://github.com/lava-nc/lava/tree/main/src/lava/magma/core/process/ports/ports.py).\n", "\n", "To receive regular updates on the latest developments and releases of the Lava Software Framework please subscribe to [our newsletter](http://eepurl.com/hJCyhb)." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.4" } }, "nbformat": 4, "nbformat_minor": 5 }