From 19bb4b432e14aea5cb3396604c04a2a1a83f3cf4 Mon Sep 17 00:00:00 2001 From: Marcus G K Williams <168222+mgkwill@users.noreply.github.com> Date: Wed, 9 Feb 2022 17:33:51 -0800 Subject: [PATCH 01/18] Address review comments Signed-off-by: Marcus G K Williams <168222+mgkwill@users.noreply.github.com> --- src/lava/magma/compiler/compiler.py | 18 +++++++++++------- src/lava/magma/runtime/runtime.py | 2 +- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/lava/magma/compiler/compiler.py b/src/lava/magma/compiler/compiler.py index 9a1cbbd2f..f985587d1 100644 --- a/src/lava/magma/compiler/compiler.py +++ b/src/lava/magma/compiler/compiler.py @@ -1,6 +1,7 @@ # Copyright (C) 2021 Intel Corporation # SPDX-License-Identifier: BSD-3-Clause # See: https://spdx.org/licenses/ +import logging import importlib import importlib.util as import_utils import inspect @@ -47,7 +48,10 @@ # ToDo: (AW) Document all class methods and class class Compiler: - def __init__(self, compile_cfg: ty.Optional[ty.Dict[str, ty.Any]] = None): + def __init__(self, compile_cfg: ty.Optional[ty.Dict[str, ty.Any]] = None, + loglevel=logging.WARNING): + self.log = logging.getLogger(__name__) + self.log.setLevel(loglevel) self._compile_config = {"pypy_channel_size": 64} if compile_cfg: self._compile_config.update(compile_cfg) @@ -195,12 +199,12 @@ def _select_proc_models( run_cfg: RunConfig) -> ty.Type[AbstractProcessModel]: """Selects a ProcessModel from list of provided models given RunCfg.""" selected_proc_model = run_cfg.select(proc, models) - err_msg = f"RunConfig {run_cfg.__class__.__qualname__}.select() must " \ - f"return a sub-class of AbstractProcessModel. Got" \ - f" {type(selected_proc_model)} instead." - if not isinstance(selected_proc_model, type): - raise AssertionError(err_msg) - if not issubclass(selected_proc_model, AbstractProcessModel): + + if not isinstance(selected_proc_model, type) \ + or not issubclass(selected_proc_model, AbstractProcessModel): + err_msg = f"RunConfig {run_cfg.__class__.__qualname__}.select()" \ + f" must return a sub-class of AbstractProcessModel. Got" \ + f" {type(selected_proc_model)} instead." raise AssertionError(err_msg) return selected_proc_model diff --git a/src/lava/magma/runtime/runtime.py b/src/lava/magma/runtime/runtime.py index 3e06d58ea..ce119eff7 100644 --- a/src/lava/magma/runtime/runtime.py +++ b/src/lava/magma/runtime/runtime.py @@ -220,7 +220,7 @@ def _run(self, run_condition): actors.join() if actors.exception: _, traceback = actors.exception - print(traceback) + self.log.error(traceback) error_cnt += 1 raise RuntimeError( From 1afbf35cb18daf0f721ecaf5975b2b8c167ec009 Mon Sep 17 00:00:00 2001 From: "Risbud, Sumedh" Date: Thu, 10 Feb 2022 11:35:37 -0800 Subject: [PATCH 02/18] Improve mnist tutorial (#147) * Minor: Removed a divide by zero warning from the fixed point LIF ProcessModel Signed-off-by: Risbud, Sumedh * Improved to MNIST end-to-end tutorial - uses fixed point bit-accurate ProcessModels for LIF and Dense - resets internal neural state of all LIF neurons - these changes are needed to make the pre-trained networks parameters work, because the network was trained with these assumptions Signed-off-by: Risbud, Sumedh * Post code review @awintel and @phstratmann Signed-off-by: Risbud, Sumedh * Post code review @awintel and @phstratmann Signed-off-by: Risbud, Sumedh * Post re-review by @phstratmann Signed-off-by: Risbud, Sumedh Co-authored-by: PhilippPlank <32519998+PhilippPlank@users.noreply.github.com> --- ...utorial01_mnist_digit_classification.ipynb | 333 ++++++++++++------ 1 file changed, 217 insertions(+), 116 deletions(-) diff --git a/tutorials/end_to_end/tutorial01_mnist_digit_classification.ipynb b/tutorials/end_to_end/tutorial01_mnist_digit_classification.ipynb index b82d29f12..627903e62 100644 --- a/tutorials/end_to_end/tutorial01_mnist_digit_classification.ipynb +++ b/tutorials/end_to_end/tutorial01_mnist_digit_classification.ipynb @@ -19,55 +19,49 @@ "source": [ "_**Motivation**: In this tutorial, we will build a Lava Process for an MNIST\n", "classifier, using the Lava Processes for LIF neurons and Dense connectivity.\n", - "Between those leaning towards Neuroscience and those partial to Computer\n", - "Science, this tutorial aims to be appealing to the former. It is supposed to\n", - "get one started with Lava in a few minutes._" + "The tutorial is useful to get started with Lava in a few minutes._" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### This tutorial assumes that you:\n", + "#### This tutorial assumes that you:\n", "- have the [Lava framework installed](../in_depth/tutorial01_installing_lava.ipynb \"Tutorial on Installing Lava\")\n", "- are familiar with the [Process concept in Lava](../in_depth/tutorial02_processes.ipynb \"Tutorial on Processes\")\n", "\n", - "### This tutorial gives a bird's-eye-view of\n", + "#### This tutorial gives a bird's-eye view of\n", "- how Lava Process(es) can perform the MNIST digit classification task using\n", "[Leaky Integrate-and-Fire (LIF)](https://github.com/lava-nc/lava/tree/main/src/lava/proc/lif \"Lava's LIF neuron\") neurons and [Dense\n", "(fully connected)](https://github.com/lava-nc/lava/tree/main/src/lava/proc/dense \"Lava's Dense Connectivity\") connectivity.\n", "- how to create a Process \n", "- how to create Python ProcessModels \n", "- how to connect Processes\n", - "- how to execute them\n", - "\n", - "### Follow the links below for deep-dive tutorials on\n", - "- [Processes](../in_depth/tutorial02_processes.ipynb \"Tutorial on Processes\")\n", - "- [ProcessModel](../in_depth/tutorial03_process_models.ipynb \"Tutorial on ProcessModels\")\n", - "- [Execution](../in_depth/tutorial04_execution.ipynb \"Tutorial on Executing Processes\")" + "- how to execute them" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Our MNIST Classifier\n", + "#### Our MNIST Classifier\n", "In this tutorial, we will build a multi-layer feed-forward classifier without\n", " any convolutional layers. The architecture is shown below.\n", "\n", "> **Important Note**:\n", ">\n", - "> Right now, this model uses arbitrary _untrained_ network paramters (weights and biases)! We will update this model and fix this shortcoming in the next few days after release.\n", - "> Thus the MNIST classifier is not expected to produce any meaningful output at this point in time. \n", - "> Nevertheless, this example illustrates how to build, compile and run an otherwise functional model in Lava." + "> The classifier is a simple feed-forward model using pre-trained network \n", + "> parameters (weights and biases). It illustrates how to build, compile and \n", + "> run a functional model in Lava. Please refer to \n", + "> [Lava-DL](https://github.com/lava-nc/lava-dl) to understand how to train \n", + "> deep networks with Lava." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "
\"Training\n",\"MNIST
" ] }, @@ -76,12 +70,12 @@ "metadata": {}, "source": [ "The 3 Processes shown above are:\n", - " 1. Spike Input Process - generates spikes via integrate and fire dynamics,\n", + " - *SpikeInput* Process - generates spikes via integrate and fire dynamics,\n", " using the image input\n", - " 2. MNIST Feed-forward process - encapsulates feed-forward architecture of\n", + " - *ImageClassifier* Process - encapsulates feed-forward architecture of\n", " Dense connectivity and LIF neurons\n", - " 3. Output Process - accumulates output spikes from the feed-forward process\n", - "and infers the class label; compares the predicted class label with the ground truth" + " - *Output* Process - accumulates output spikes from the feed-forward process\n", + "and infers the class label" ] }, { @@ -97,7 +91,6 @@ "metadata": {}, "outputs": [], "source": [ - "# Assumes: $PYTHONPATH contains lava repository root\n", "import os\n", "import numpy as np" ] @@ -106,13 +99,13 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Create the Process class\n", + "#### Lava Processes\n", "\n", "Below we create the Lava Process classes. We need to define only the structure of the process here. The details about how the Process will be executed are specified in the [ProcessModels](../in_depth/tutorial03_process_models.ipynb \"Tutorial on ProcessModels\") below.\n", "\n", "As mentioned above, we define Processes for \n", "- converting input images to binary spikes from those biases (_SpikeInput_),\n", - "- the 4-layer fully connected feed-forward network (_MnistClassifier_)\n", + "- the 3-layer fully connected feed-forward network (_MnistClassifier_)\n", "- accumulating the output spikes and inferring the class for an input image\n", "(_OutputProcess_)" ] @@ -123,7 +116,7 @@ "metadata": {}, "outputs": [], "source": [ - "# Import Process level premitives\n", + "# Import Process level primitives\n", "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 InPort, OutPort" @@ -144,8 +137,8 @@ " n_img = kwargs.pop('num_images', 25)\n", " n_steps_img = kwargs.pop('num_steps_per_image', 128)\n", " shape = (784,)\n", - " self.spikes_out = OutPort(shape=shape)\n", - " self.label_out = OutPort(shape=(1,))\n", + " self.spikes_out = OutPort(shape=shape) # Input spikes to the classifier\n", + " self.label_out = OutPort(shape=(1,)) # Ground truth labels to OutputProc\n", " self.num_images = Var(shape=(1,), init=n_img)\n", " self.num_steps_per_image = Var(shape=(1,), init=n_steps_img)\n", " self.input_img = Var(shape=shape)\n", @@ -154,14 +147,13 @@ " self.vth = Var(shape=(1,), init=kwargs['vth'])\n", " \n", " \n", - "class MnistClassifier(AbstractProcess):\n", - " \"\"\"A 4 layer feed-forward network with LIF and Dense Processes.\"\"\"\n", + "class ImageClassifier(AbstractProcess):\n", + " \"\"\"A 3 layer feed-forward network with LIF and Dense Processes.\"\"\"\n", "\n", " def __init__(self, **kwargs):\n", " super().__init__(**kwargs)\n", - " # As mentioned before, the weights and biases saved on the disk are\n", - " # arbitrary numbers. These will not produce any meaningful output\n", - " # classification.\n", + " \n", + " # Using pre-trained weights and biases\n", " trained_weights_path = kwargs.pop('trained_weights_path', os.path\n", " .join('.','mnist_pretrained.npy'))\n", " real_path_trained_wgts = os.path.realpath(trained_weights_path)\n", @@ -183,6 +175,15 @@ " self.w_dense2 = Var(shape=w2.shape, init=w2)\n", " self.b_output_lif = Var(shape=(w2.shape[0],), init=b3)\n", " \n", + " # Up-level currents and voltages of LIF Processes\n", + " # for resetting (see at the end of the tutorial)\n", + " self.lif1_u = Var(shape=(w0.shape[0],), init=0)\n", + " self.lif1_v = Var(shape=(w0.shape[0],), init=0)\n", + " self.lif2_u = Var(shape=(w1.shape[0],), init=0)\n", + " self.lif2_v = Var(shape=(w1.shape[0],), init=0)\n", + " self.oplif_u = Var(shape=(w2.shape[0],), init=0)\n", + " self.oplif_v = Var(shape=(w2.shape[0],), init=0)\n", + " \n", " \n", "class OutputProcess(AbstractProcess):\n", " \"\"\"Process to gather spikes from 10 output LIF neurons and interpret the\n", @@ -195,7 +196,7 @@ " self.num_images = Var(shape=(1,), init=n_img)\n", " self.spikes_in = InPort(shape=shape)\n", " self.label_in = InPort(shape=(1,))\n", - " self.spikes_accum = Var(shape=shape)\n", + " self.spikes_accum = Var(shape=shape) # Accumulated spikes for classification\n", " self.num_steps_per_image = Var(shape=(1,), init=128)\n", " self.pred_labels = Var(shape=(n_img,))\n", " self.gt_labels = Var(shape=(n_img,))" @@ -207,9 +208,7 @@ "tags": [] }, "source": [ - "### Create ProcessModels for Python execution\n", - "The code in these ProcessModels is what will get executed. Processes above\n", - "were declarations, in a way." + "#### ProcessModels for Python execution\n" ] }, { @@ -246,13 +245,33 @@ } }, "source": [ - "#### ProcessModel for producing spiking input" + "**Decorators for ProcessModels**: \n", + "- `@implements`: associates a ProcessModel with a Process through \n", + "the argument `proc`. Using `protocol` argument, we will specify the \n", + "synchronization protocol used by the ProcessModel. In this tutorial, \n", + "all ProcessModels execute \n", + "according to the `LoihiProtocol`. Which means, similar to the Loihi \n", + "chip, each time-step is divided into _spiking_, _pre-management_, \n", + "_post-management_, and _learning_ phases. It is necessary to specify \n", + "behaviour of a ProcessModel during the spiking phase using `run_spk` \n", + "function. Other phases are optional.\n", + "- `@requires`: specifies the hardware resource on which a ProcessModel \n", + "will be executed. In this tutorial, we will execute all ProcessModels \n", + "on a CPU." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**SpikingInput ProcessModel**" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": { + "collapsed": false, "jupyter": { "outputs_hidden": false }, @@ -274,23 +293,33 @@ " ground_truth_label: int = LavaPyType(int, int, precision=32)\n", " v: np.ndarray = LavaPyType(np.ndarray, int, precision=32)\n", " vth: int = LavaPyType(int, int, precision=32)\n", - " mnist_dataset = MnistDataset()\n", - " curr_img_id = -1\n", + " \n", + " def __init__(self, proc_params):\n", + " super().__init__(proc_params=proc_params)\n", + " self.mnist_dataset = MnistDataset()\n", + " self.curr_img_id = 0\n", "\n", " def post_guard(self):\n", + " \"\"\"Guard function for PostManagement phase.\n", + " \"\"\"\n", " if self.current_ts % self.num_steps_per_image == 1:\n", - " self.curr_img_id += 1\n", " return True\n", " return False\n", "\n", " def run_post_mgmt(self):\n", + " \"\"\"Post-Management phase: executed only when guard function above \n", + " returns True.\n", + " \"\"\"\n", " img = self.mnist_dataset.images[self.curr_img_id]\n", " self.ground_truth_label = self.mnist_dataset.labels[self.curr_img_id]\n", " self.input_img = img.astype(np.int32) - 127\n", " self.v = np.zeros(self.v.shape)\n", " self.label_out.send(np.array([self.ground_truth_label]))\n", + " self.curr_img_id += 1\n", "\n", " def run_spk(self):\n", + " \"\"\"Spiking phase: executed unconditionally at every time-step\n", + " \"\"\"\n", " self.v[:] = self.v + self.input_img\n", " s_out = self.v > self.vth\n", " self.v[s_out] = 0 # reset voltage to 0 after a spike\n", @@ -305,16 +334,21 @@ } }, "source": [ - "#### ProcessModel for the feed-forward network\n", + "**ImageClassifier ProcessModel**\n", + "\n", "Notice that the following process model is further decomposed into\n", "sub-Processes, which implement LIF neural dynamics and Dense connectivity. We\n", - " will not go into the details of how these are implemented in this tutorial." + " will not go into the details of how these are implemented in this tutorial.\n", + " \n", + "Also notice that a *SubProcessModel* does not actually contain any concrete \n", + "execution. This is handled by the ProcessModels of the constituent Processes." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": { + "collapsed": false, "jupyter": { "outputs_hidden": false }, @@ -327,18 +361,9 @@ "from lava.proc.lif.process import LIF\n", "from lava.proc.dense.process import Dense \n", "\n", - "@implements(MnistClassifier)\n", + "@implements(ImageClassifier)\n", "@requires(CPU)\n", - "class PyMnistClassifierModel(AbstractSubProcessModel):\n", - " spikes_in: PyInPort = LavaPyType(PyInPort.VEC_DENSE, bool, precision=1)\n", - " spikes_out: PyOutPort = LavaPyType(PyOutPort.VEC_DENSE, bool, precision=1)\n", - " w_dense0: np.ndarray = LavaPyType(np.ndarray, int, precision=8)\n", - " b_lif1: np.ndarray = LavaPyType(np.ndarray, int, precision=13)\n", - " w_dense1: np.ndarray = LavaPyType(np.ndarray, int, precision=8)\n", - " b_lif2: np.ndarray = LavaPyType(np.ndarray, int, precision=13)\n", - " w_dense2: np.ndarray = LavaPyType(np.ndarray, int, precision=8)\n", - " b_output_lif: np.ndarray = LavaPyType(np.ndarray, int, precision=13)\n", - "\n", + "class PyImageClassifierModel(AbstractSubProcessModel):\n", " def __init__(self, proc):\n", " self.dense0 = Dense(shape=(64, 784), weights=proc.w_dense0.init)\n", " self.lif1 = LIF(shape=(64,), b=proc.b_lif1.init, vth=400,\n", @@ -348,15 +373,23 @@ " dv=0, du=4095)\n", " self.dense2 = Dense(shape=(10, 64), weights=proc.w_dense2.init)\n", " self.output_lif = LIF(shape=(10,), b=proc.b_output_lif.init,\n", - " vth=2**17-1, dv=0, du=4095)\n", - "\n", - " proc.in_ports.spikes_in.connect(self.dense0.in_ports.s_in)\n", - " self.dense0.out_ports.a_out.connect(self.lif1.in_ports.a_in)\n", - " self.lif1.out_ports.s_out.connect(self.dense1.in_ports.s_in)\n", - " self.dense1.out_ports.a_out.connect(self.lif2.in_ports.a_in)\n", - " self.lif2.out_ports.s_out.connect(self.dense2.in_ports.s_in)\n", - " self.dense2.out_ports.a_out.connect(self.output_lif.in_ports.a_in)\n", - " self.output_lif.out_ports.s_out.connect(proc.out_ports.spikes_out)" + " vth=1, dv=0, du=4095)\n", + "\n", + " proc.spikes_in.connect(self.dense0.s_in)\n", + " self.dense0.a_out.connect(self.lif1.a_in)\n", + " self.lif1.s_out.connect(self.dense1.s_in)\n", + " self.dense1.a_out.connect(self.lif2.a_in)\n", + " self.lif2.s_out.connect(self.dense2.s_in)\n", + " self.dense2.a_out.connect(self.output_lif.a_in)\n", + " self.output_lif.s_out.connect(proc.spikes_out)\n", + " \n", + " # Create aliases of SubProcess variables\n", + " proc.lif1_u.alias(self.lif1.u)\n", + " proc.lif1_v.alias(self.lif1.v)\n", + " proc.lif2_u.alias(self.lif2.u)\n", + " proc.lif2_v.alias(self.lif2.v)\n", + " proc.oplif_u.alias(self.output_lif.u)\n", + " proc.oplif_v.alias(self.output_lif.v)" ] }, { @@ -367,13 +400,14 @@ } }, "source": [ - "#### Finally, ProcessModel for inference output" + "**OutputProcess ProcessModel**" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "metadata": { + "collapsed": false, "jupyter": { "outputs_hidden": false }, @@ -386,91 +420,148 @@ "@implements(proc=OutputProcess, protocol=LoihiProtocol)\n", "@requires(CPU)\n", "class PyOutputProcessModel(PyLoihiProcessModel):\n", - " spikes_in: PyInPort = LavaPyType(PyInPort.VEC_DENSE, bool, precision=1)\n", " label_in: PyInPort = LavaPyType(PyInPort.VEC_DENSE, int, precision=32)\n", + " spikes_in: PyInPort = LavaPyType(PyInPort.VEC_DENSE, bool, precision=1)\n", " num_images: int = LavaPyType(int, int, precision=32)\n", " spikes_accum: np.ndarray = LavaPyType(np.ndarray, np.int32, precision=32)\n", " num_steps_per_image: int = LavaPyType(int, int, precision=32)\n", " pred_labels: np.ndarray = LavaPyType(np.ndarray, int, precision=32)\n", " gt_labels: np.ndarray = LavaPyType(np.ndarray, int, precision=32)\n", - " current_img_id = -1\n", + " \n", + " def __init__(self, proc_params):\n", + " super().__init__(proc_params=proc_params)\n", + " self.current_img_id = 0\n", "\n", - " # This is needed for Loihi synchronization protocol\n", " def post_guard(self):\n", - " if self.current_ts % self.num_steps_per_image == 1 and self\\\n", - " .current_ts > 1:\n", - " self.current_img_id += 1\n", + " \"\"\"Guard function for PostManagement phase.\n", + " \"\"\"\n", + " if self.current_ts % self.num_steps_per_image == 0 and \\\n", + " self.current_ts > 1:\n", " return True\n", " return False\n", "\n", " def run_post_mgmt(self):\n", - " print(f'Curr Img: {self.current_img_id}')\n", - " pred_label = np.argmax(self.spikes_accum)\n", - " self.pred_labels[self.current_img_id] = pred_label\n", - " self.spikes_accum = np.zeros(self.spikes_accum.shape)\n", + " \"\"\"Post-Management phase: executed only when guard function above \n", + " returns True.\n", + " \"\"\"\n", " gt_label = self.label_in.recv()\n", + " pred_label = np.argmax(self.spikes_accum)\n", " self.gt_labels[self.current_img_id] = gt_label\n", - " print(f'Pred Label: {pred_label}', end='\\t')\n", - " print(f'Ground Truth: {gt_label}')\n", + " self.pred_labels[self.current_img_id] = pred_label\n", + " self.current_img_id += 1\n", + " self.spikes_accum = np.zeros_like(self.spikes_accum)\n", "\n", " def run_spk(self):\n", - " spikes_buffer = self.spikes_in.recv()\n", - " self.spikes_accum += spikes_buffer" + " \"\"\"Spiking phase: executed unconditionally at every time-step\n", + " \"\"\"\n", + " spk_in = self.spikes_in.recv()\n", + " self.spikes_accum = self.spikes_accum + spk_in" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Run the Process" + "#### Connecting Processes" ] }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 8, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Curr Img: 0\n", - "Pred Label: 0\tGround Truth: [5]\n", - "Curr Img: 1\n", - "Pred Label: 0\tGround Truth: [0]\n", - "Curr Img: 2\n", - "Pred Label: 0\tGround Truth: [4]\n", - "Curr Img: 3\n", - "Pred Label: 0\tGround Truth: [1]\n", - "Curr Img: 4\n", - "Pred Label: 0\tGround Truth: [9]\n" - ] - } - ], + "outputs": [], "source": [ - "num_images = 5\n", + "num_images = 25\n", "num_steps_per_image = 128\n", "\n", - "# Create instances\n", + "# Create Process instances\n", "spike_input = SpikeInput(num_images=num_images,\n", " num_steps_per_image=num_steps_per_image,\n", " vth=1)\n", - "mnist_clf = MnistClassifier(\n", + "mnist_clf = ImageClassifier(\n", " trained_weights_path=os.path.join('.', 'mnist_pretrained.npy'))\n", "output_proc = OutputProcess(num_images=num_images)\n", "\n", - "# Connect instances\n", - "spike_input.out_ports.spikes_out.connect(mnist_clf.in_ports.spikes_in)\n", - "mnist_clf.out_ports.spikes_out.connect(output_proc.in_ports.spikes_in)\n", - "spike_input.out_ports.label_out.connect(output_proc.in_ports.label_in)\n", + "# Connect Processes\n", + "spike_input.spikes_out.connect(mnist_clf.spikes_in)\n", + "mnist_clf.spikes_out.connect(output_proc.spikes_in)\n", + "# Connect Input directly to Output for ground truth labels\n", + "spike_input.label_out.connect(output_proc.label_in)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Execution and results\n", + "Below, we will run the classifier process in a loop of `num_images`\n", + "number of iterations. Each iteration will run the Process for \n", + "`num_steps_per_image` number of time-steps. \n", "\n", + "We take this approach to clear the neural states of all three LIF \n", + "layers inside the classifier after every image. We need to clear \n", + "the neural states, because the network parameters were trained \n", + "assuming clear neural states for each inference.\n", + "\n", + "> Note:\n", + "Below we have used `Var.set()` function to set the values\n", + "of internal state variables. The same behaviour can be \n", + "achieved by using `RefPorts`. See the \n", + "[RefPorts tutorial](../in_depth/tutorial07_remote_memory_access.ipynb)\n", + "to learn more about how to use `RefPorts` to access internal \n", + "state variables of Lava Processes." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Current image: 25\n", + "Ground truth: [5 0 4 1 9 2 1 3 1 4 3 5 3 6 1 7 2 8 6 9 4 0 9 1 1]\n", + "Predictions : [3 0 4 1 4 2 1 3 1 4 3 5 3 6 1 7 2 8 5 9 4 0 9 1 1]\n", + "Accuracy : 88.0\n" + ] + } + ], + "source": [ "from lava.magma.core.run_conditions import RunSteps\n", "from lava.magma.core.run_configs import Loihi1SimCfg\n", "\n", - "mnist_clf.run(\n", - " condition=RunSteps(num_steps=(num_images+1) * num_steps_per_image),\n", - " run_cfg=Loihi1SimCfg(select_sub_proc_model=True))\n", - "mnist_clf.stop()" + "# Loop over all images\n", + "for img_id in range(num_images):\n", + " print(f\"\\rCurrent image: {img_id+1}\", end=\"\")\n", + " \n", + " # Run each image-inference for fixed number of steps\n", + " mnist_clf.run(\n", + " condition=RunSteps(num_steps=num_steps_per_image),\n", + " run_cfg=Loihi1SimCfg(select_sub_proc_model=True,\n", + " select_tag='fixed_pt'))\n", + " \n", + " # Reset internal neural state of LIF neurons\n", + " mnist_clf.lif1_u.set(np.zeros((64,), dtype=np.int32))\n", + " mnist_clf.lif1_v.set(np.zeros((64,), dtype=np.int32))\n", + " mnist_clf.lif2_u.set(np.zeros((64,), dtype=np.int32))\n", + " mnist_clf.lif2_v.set(np.zeros((64,), dtype=np.int32))\n", + " mnist_clf.oplif_u.set(np.zeros((10,), dtype=np.int32))\n", + " mnist_clf.oplif_v.set(np.zeros((10,), dtype=np.int32))\n", + "\n", + "# Gather ground truth and predictions before stopping exec\n", + "ground_truth = output_proc.gt_labels.get().astype(np.int32)\n", + "predictions = output_proc.pred_labels.get().astype(np.int32)\n", + "\n", + "# Stop the execution\n", + "mnist_clf.stop()\n", + "\n", + "accuracy = np.sum(ground_truth==predictions)/ground_truth.size * 100\n", + "\n", + "print(f\"\\nGround truth: {ground_truth}\\n\"\n", + " f\"Predictions : {predictions}\\n\"\n", + " f\"Accuracy : {accuracy}\")" ] }, { @@ -479,8 +570,11 @@ "source": [ "> **Important Note**:\n", ">\n", - "> Right now, this model uses arbitrary _untrained_ network paramters (weights and biases)! We will update this model and fix this shortcoming in the next few days after release.\n", - "> Thus the MNIST classifier is not expected to produce any meaningful output at this point in time. " + "> The classifier is a simple feed-forward model using pre-trained network \n", + "> parameters (weights and biases). It illustrates how to build, compile and \n", + "> run a functional model in Lava. Please refer to \n", + "> [Lava-DL](https://github.com/lava-nc/lava-dl) to understand how to train \n", + "> deep networks with Lava." ] }, { @@ -489,6 +583,13 @@ "source": [ "## How to learn more?\n", "\n", + "#### Follow the links below for deep-dive tutorials on the concepts in this tutorial:\n", + "- [Processes](../in_depth/tutorial02_processes.ipynb \"Tutorial on Processes\")\n", + "- [ProcessModel](../in_depth/tutorial03_process_models.ipynb \"Tutorial on ProcessModels\")\n", + "- [Execution](../in_depth/tutorial04_execution.ipynb \"Tutorial on Executing Processes\")\n", + "- [SubProcessModels](../in_depth/tutorial06_hierarchical_processes.ipynb) or [Hierarchical Processes](../in_depth/tutorial06_hierarchical_processes.ipynb)\n", + "- [RefPorts](../in_depth/tutorial07_remote_memory_access.ipynb)\n", + "\n", "If you want to find out more about Lava, have a look at the [Lava documentation](https://lava-nc.org/ \"Lava Documentation\") or dive into the [source code](https://github.com/lava-nc/lava/ \"Lava Source Code\").\n", "\n", "To receive regular updates on the latest developments and releases of the Lava Software Framework please subscribe to the [INRC newsletter](http://eepurl.com/hJCyhb \"INRC Newsletter\")." @@ -497,7 +598,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -511,9 +612,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.11" + "version": "3.8.10" } }, "nbformat": 4, "nbformat_minor": 4 -} +} \ No newline at end of file From 63cb4ca3bbc0a84e914b886be7670ae7c76c962e Mon Sep 17 00:00:00 2001 From: Marcus G K Williams <168222+mgkwill@users.noreply.github.com> Date: Fri, 11 Feb 2022 08:55:40 -0800 Subject: [PATCH 03/18] Check process lineage before join (#177) Signed-off-by: Marcus G K Williams <168222+mgkwill@users.noreply.github.com> --- .../magma/runtime/message_infrastructure/multiprocessing.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lava/magma/runtime/message_infrastructure/multiprocessing.py b/src/lava/magma/runtime/message_infrastructure/multiprocessing.py index 3b3a1aaf0..2b057447e 100644 --- a/src/lava/magma/runtime/message_infrastructure/multiprocessing.py +++ b/src/lava/magma/runtime/message_infrastructure/multiprocessing.py @@ -8,6 +8,7 @@ AbstractRuntimeServiceBuilder import multiprocessing as mp +import os from multiprocessing.managers import SharedMemoryManager import traceback @@ -76,7 +77,8 @@ def build_actor(self, target_fn: ty.Callable, builder: ty.Union[ def stop(self): """Stops the shared memory manager""" for actor in self._actors: - actor.join() + if actor._parent_pid == os.getpid(): + actor.join() self._smm.shutdown() From 5aa3985ea82d598fd9dc072c92d460dc6fbadcca Mon Sep 17 00:00:00 2001 From: Marcus G K Williams <168222+mgkwill@users.noreply.github.com> Date: Tue, 15 Feb 2022 14:07:31 -0800 Subject: [PATCH 04/18] Add NxSDKRuntimeService Signed-off-by: Marcus G K Williams <168222+mgkwill@users.noreply.github.com> --- src/lava/magma/compiler/builders/builder.py | 24 +- .../magma/compiler/builders/interfaces.py | 5 +- src/lava/magma/core/model/model.py | 6 +- src/lava/magma/core/model/nc/model.py | 209 +++++++++- src/lava/magma/core/model/nc/ports.py | 358 ++++++++++++++++++ src/lava/magma/core/model/nc/type.py | 9 + src/lava/magma/core/model/py/model.py | 57 +-- src/lava/magma/core/process/process.py | 6 +- src/lava/magma/core/run_configs.py | 34 +- .../core/sync/protocols/async_protocol.py | 3 +- .../core/sync/protocols/loihi_protocol.py | 5 +- .../core/sync/protocols/nxsdk_protocol.py | 23 ++ src/lava/magma/runtime/runtime.py | 19 +- .../magma/runtime/runtime_services/enums.py | 43 +++ .../runtime/runtime_services/interfaces.py | 48 +++ .../{ => runtime_services}/runtime_service.py | 202 ++++++---- tests/lava/magma/compiler/test_compiler.py | 4 +- .../magma/core/process/test_lif_dense_lif.py | 4 +- .../magma/runtime/test_exception_handling.py | 28 +- tests/lava/magma/runtime/test_get_set_var.py | 6 +- .../lava/magma/runtime/test_loihi_protocol.py | 6 +- .../lava/magma/runtime/test_ref_var_ports.py | 5 +- .../magma/runtime/test_runtime_service.py | 192 +++++++++- tests/lava/proc/conv/test_models.py | 3 +- tests/lava/proc/dense/test_models.py | 4 +- tests/lava/proc/io/test_dataloader.py | 4 +- tests/lava/proc/lif/test_models.py | 4 +- 27 files changed, 1160 insertions(+), 151 deletions(-) create mode 100644 src/lava/magma/core/model/nc/ports.py create mode 100644 src/lava/magma/core/model/nc/type.py create mode 100644 src/lava/magma/core/sync/protocols/nxsdk_protocol.py create mode 100644 src/lava/magma/runtime/runtime_services/enums.py create mode 100644 src/lava/magma/runtime/runtime_services/interfaces.py rename src/lava/magma/runtime/{ => runtime_services}/runtime_service.py (66%) diff --git a/src/lava/magma/compiler/builders/builder.py b/src/lava/magma/compiler/builders/builder.py index 6cba83b68..01a381e12 100644 --- a/src/lava/magma/compiler/builders/builder.py +++ b/src/lava/magma/compiler/builders/builder.py @@ -1,4 +1,4 @@ -# Copyright (C) 2021 Intel Corporation +# Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: BSD-3-Clause # See: https://spdx.org/licenses/ @@ -10,8 +10,11 @@ from lava.magma.core.sync.protocol import AbstractSyncProtocol from lava.magma.runtime.message_infrastructure.message_infrastructure_interface\ import MessageInfrastructureInterface -from lava.magma.runtime.runtime_service import PyRuntimeService, \ - AbstractRuntimeService +from lava.magma.runtime.runtime_services.enums import LoihiVersion +from lava.magma.runtime.runtime_services.runtime_service import ( + AbstractRuntimeService, + NxSDKRuntimeService +) if ty.TYPE_CHECKING: from lava.magma.core.process.process import AbstractProcess @@ -487,14 +490,21 @@ def set_csp_proc_ports(self, csp_ports: ty.List[AbstractCspPort]): if isinstance(port, CspRecvPort): self.csp_proc_recv_port.update({port.name: port}) - def build(self) -> PyRuntimeService: - """Build Runtime Service + def build(self, + loihi_version: LoihiVersion = LoihiVersion.N3 + ) -> AbstractRuntimeService: + """Build the runtime service Returns ------- - PyRuntimeService + A concreate instance of AbstractRuntimeService + [PyRuntimeService or NxSDKRuntimeService] """ - rs = self.rs_class(protocol=self.sync_protocol) + if isinstance(self.rs_class, NxSDKRuntimeService): + rs = self.rs_class(protocol=self.sync_protocol, + loihi_version=loihi_version) + else: + rs = self.rs_class(protocol=self.sync_protocol) rs.runtime_service_id = self._runtime_service_id rs.model_ids = self._model_ids diff --git a/src/lava/magma/compiler/builders/interfaces.py b/src/lava/magma/compiler/builders/interfaces.py index e367d2747..7151d693c 100644 --- a/src/lava/magma/compiler/builders/interfaces.py +++ b/src/lava/magma/compiler/builders/interfaces.py @@ -1,4 +1,4 @@ -# Copyright (C) 2021 Intel Corporation +# Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: BSD-3-Clause # See: https://spdx.org/licenses/ @@ -9,7 +9,8 @@ from lava.magma.compiler.channels.interfaces import AbstractCspPort from lava.magma.core.model.model import AbstractProcessModel from lava.magma.core.sync.protocol import AbstractSyncProtocol -from lava.magma.runtime.runtime_service import AbstractRuntimeService +from lava.magma.runtime.runtime_services.runtime_service import \ + AbstractRuntimeService class AbstractProcessBuilder(ABC): diff --git a/src/lava/magma/core/model/model.py b/src/lava/magma/core/model/model.py index a0b209ec4..83c6ce07e 100644 --- a/src/lava/magma/core/model/model.py +++ b/src/lava/magma/core/model/model.py @@ -3,6 +3,7 @@ # See: https://spdx.org/licenses/ from __future__ import annotations import typing as ty +import logging from abc import ABC if ty.TYPE_CHECKING: @@ -56,7 +57,10 @@ class level attributes with the same name if they exist. required_resources: ty.List[ty.Type[AbstractResource]] = [] tags: ty.List[str] = [] - def __init__(self, proc_params: ty.Dict[str, ty.Any]) -> None: + def __init__(self, proc_params: ty.Dict[str, ty.Any], + loglevel=logging.WARNING) -> None: + self.log = logging.getLogger(__name__) + self.log.setLevel(loglevel) self.proc_params: ty.Dict[str, ty.Any] = proc_params def __repr__(self): diff --git a/src/lava/magma/core/model/nc/model.py b/src/lava/magma/core/model/nc/model.py index 8253c7cd5..c861ee320 100644 --- a/src/lava/magma/core/model/nc/model.py +++ b/src/lava/magma/core/model/nc/model.py @@ -1,8 +1,25 @@ # Copyright (C) 2021 Intel Corporation # SPDX-License-Identifier: BSD-3-Clause from abc import ABC, abstractmethod +import logging +import numpy as np +import typing as ty from lava.magma.core.model.model import AbstractProcessModel +from lava.magma.core.model.nc.ports import AbstractNcPort, NcVarPort +from lava.magma.compiler.channels.pypychannel import CspSelector, CspSendPort, CspRecvPort +from lava.magma.runtime.mgmt_token_enums import ( + MGMT_COMMAND, + MGMT_RESPONSE, + enum_equal, + enum_to_np +) + +try: + from nxsdk.arch.base.nxboard import NxBoard +except(ImportError): + class NxBoard(): + pass # ToDo: Move somewhere else. Just created for typing @@ -12,6 +29,17 @@ def alloc(self, *args, **kwargs): class Net(ABC): + """Represents a collection of logical entities (Attribute Groups) + that consume resources on a NeuroCore. + + * InputAxons + * Synapses + * DendriticAccumulator + * Compartments + * OutputAxons + * Synaptic pre traces + * Synaptic post traces + """ def __init__(self): self.out_ax = AbstractNodeGroup() self.cx = AbstractNodeGroup() @@ -29,7 +57,43 @@ def connect(self, from_thing, to_thing): class AbstractNcProcessModel(AbstractProcessModel, ABC): - """Abstract interface for a NeuroCore ProcessModels.""" + """Abstract interface for a NeuroCore ProcessModels + + Example for how variables and ports might be initialized: + a_in: NcInPort = LavaNcType(NcInPort.VEC_DENSE, float) + s_out: NcInPort = LavaNcType(NcOutPort.VEC_DENSE, bool, precision=1) + u: np.ndarray = LavaNcType(np.ndarray, np.int32, precision=24) + v: np.ndarray = LavaNcType(np.ndarray, np.int32, precision=24) + bias: np.ndarray = LavaNcType(np.ndarray, np.int16, precision=12) + du: int = LavaNcType(int, np.uint16, precision=12) + """ + def __init__(self, proc_params: ty.Dict[str, ty.Any], + loglevel=logging.WARNING) -> None: + super().__init__(proc_params, loglevel=loglevel) + self.model_id: ty.Optional[int] = None + self.service_to_process: ty.Optional[CspRecvPort] = None + self.process_to_service: ty.Optional[CspSendPort] = None + self.nc_ports: ty.List[AbstractNcPort] = [] + self.var_ports: ty.List[NcVarPort] = [] + self.var_id_to_var_map: ty.Dict[int, ty.Any] = {} + + def __setattr__(self, key: str, value: ty.Any): + self.__dict__[key] = value + if isinstance(value, AbstractNcPort): + self.nc_ports.append(value) + # Store all VarPorts for efficient RefPort -> VarPort handling + if isinstance(value, NcVarPort): + self.var_ports.append(value) + + @abstractmethod + def run(self): + pass + + def join(self): + self.service_to_process.join() + self.process_to_service.join() + for p in self.nc_ports: + p.join() @abstractmethod def allocate(self, net: Net): @@ -38,3 +102,146 @@ def allocate(self, net: Net): Note: This should work as before. """ pass + + +class NcProcessModel(AbstractNcProcessModel): + def __init__(self, proc_params: ty.Dict[str, ty.Any], + board: ty.Type[NxBoard], + loglevel=logging.WARNING): + super(AbstractNcProcessModel, self).__init__(proc_params, + loglevel=loglevel) + self.board = board + + def start(self): + self.service_to_process.start() + self.process_to_service.start() + for p in self.nc_ports: + p.start() + # self.board.start() + self.run() + + def allocate(self): + pass + + def run(self): + """Retrieves commands from the runtime service calls + their corresponding methods of the ProcessModels. The phase + is retrieved from runtime service (service_to_process). After + calling the method of a phase of all ProcessModels the runtime + service is informed about completion. The loop ends when the + STOP command is received.""" + selector = CspSelector() + channel_actions = [(self.service_to_process, lambda: 'cmd')] + action = 'cmd' + while True: + if action == 'cmd': + cmd = self.service_to_process.recv() + if enum_equal(cmd, MGMT_COMMAND.STOP): + self.board.stop() + self.process_to_service.send(MGMT_RESPONSE.TERMINATED) + self.join() + return + if enum_equal(cmd, MGMT_COMMAND.PAUSE): + self.board.pause() + self.process_to_service.send(MGMT_RESPONSE.PAUSED) + self.join() + return + try: + if enum_equal(cmd, MGMT_COMMAND.RUN): + self.process_to_service.send(MGMT_RESPONSE.DONE) + num_steps = self.service_to_process.recv() + if num_steps > 0: + # self.board.run(numSteps=num_steps, aSync=False) + self.process_to_service.send(MGMT_RESPONSE.DONE) + else: + self.log.error(f"Exception: number of time steps" + f"not greater than 0, cannot invoke " + f"run(num_steps) in {self.__class__}") + self.process_to_service.send(MGMT_RESPONSE.ERROR) + elif enum_equal(cmd, MGMT_COMMAND.GET_DATA): + # Handle get/set Var requests from runtime service + self._handle_get_var() + elif enum_equal(cmd, MGMT_COMMAND.SET_DATA): + # Handle get/set Var requests from runtime service + self._handle_set_var() + else: + raise ValueError( + f"Wrong Phase Info Received : {cmd}") + except Exception as inst: + self.log.error(f"Exception {inst} occured while" + f" running command {cmd} in {self.__class__}") + # Inform runtime service about termination + self.process_to_service.send(MGMT_RESPONSE.ERROR) + self.join() + raise inst + else: + # Handle VarPort requests from RefPorts + self._handle_var_port(action) + + for var_port in self.var_ports: + for csp_port in var_port.csp_ports: + if isinstance(csp_port, CspRecvPort): + channel_actions.append( + (csp_port, lambda: var_port)) + action = selector.select(*channel_actions) + + def _handle_get_var(self): + """Handles the get Var command from runtime service.""" + # 1. Receive Var ID and retrieve the Var + var_id = int(self.service_to_process.recv()[0].item()) + var_name = self.var_id_to_var_map[var_id] + var = getattr(self, var_name) + + # Here get the var from Loihi + + # 2. Send Var data + data_port = self.process_to_service + # Header corresponds to number of values + # Data is either send once (for int) or one by one (array) + if isinstance(var, int) or isinstance(var, np.integer): + data_port.send(enum_to_np(1)) + data_port.send(enum_to_np(var)) + elif isinstance(var, np.ndarray): + # FIXME: send a whole vector (also runtime_service.py) + var_iter = np.nditer(var) + num_items: np.integer = np.prod(var.shape) + data_port.send(enum_to_np(num_items)) + for value in var_iter: + data_port.send(enum_to_np(value, np.float64)) + + def _handle_set_var(self): + """Handles the set Var command from runtime service.""" + # 1. Receive Var ID and retrieve the Var + var_id = int(self.service_to_process.recv()[0].item()) + var_name = self.var_id_to_var_map[var_id] + var = getattr(self, var_name) + + # 2. Receive Var data + data_port = self.service_to_process + if isinstance(var, int) or isinstance(var, np.integer): + # First item is number of items (1) - not needed + data_port.recv() + # Data to set + buffer = data_port.recv()[0] + if isinstance(var, int): + setattr(self, var_name, buffer.item()) + else: + setattr(self, var_name, buffer.astype(var.dtype)) + elif isinstance(var, np.ndarray): + # First item is number of items + num_items = data_port.recv()[0] + var_iter = np.nditer(var, op_flags=['readwrite']) + # Set data one by one + for i in var_iter: + if num_items == 0: + break + num_items -= 1 + i[...] = data_port.recv()[0] + else: + raise RuntimeError("Unsupported type") + + # Here set var in Loihi? + + def _handle_var_port(self, var_port): + """Handles read/write requests on the given VarPort.""" + var_port.service() diff --git a/src/lava/magma/core/model/nc/ports.py b/src/lava/magma/core/model/nc/ports.py new file mode 100644 index 000000000..eca2a703b --- /dev/null +++ b/src/lava/magma/core/model/nc/ports.py @@ -0,0 +1,358 @@ +# Copyright (C) 2022 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause +# See: https://spdx.org/licenses/ +import typing as ty +from abc import abstractmethod +import functools as ft +import numpy as np + +from lava.magma.compiler.channels.interfaces import AbstractCspPort +from lava.magma.compiler.channels.pypychannel import CspSendPort, CspRecvPort +from lava.magma.core.model.interfaces import AbstractPortImplementation +from lava.magma.runtime.mgmt_token_enums import enum_to_np, enum_equal + + +class AbstractNcPort(AbstractPortImplementation): + @property + @abstractmethod + def csp_ports(self) -> ty.List[AbstractCspPort]: + """Returns all csp ports of the port.""" + pass + + +class NcInPort(AbstractNcPort): + """NeuroCore implementation of InPort used within AbstractNcProcessModel. + If buffer is empty, recv() will be blocking. + """ + + VEC_DENSE: ty.Type["NcInPortVectorDense"] = None + VEC_SPARSE: ty.Type["NcInPortVectorSparse"] = None + SCALAR_DENSE: ty.Type["NcInPortScalarDense"] = None + SCALAR_SPARSE: ty.Type["NcInPortScalarSparse"] = None + + def __init__(self, csp_recv_ports: ty.List[CspRecvPort], *args): + self._csp_recv_ports = csp_recv_ports + super().__init__(*args) + + @property + def csp_ports(self) -> ty.List[AbstractCspPort]: + """Returns all csp ports of the port.""" + return self._csp_recv_ports + + @abstractmethod + def recv(self): + pass + + @abstractmethod + def peek(self): + pass + + def probe(self) -> bool: + """Executes probe method of all csp ports and accumulates the returned + bool values with AND operation. The accumulator acc is initialized to + True. + + Returns the accumulated bool value. + """ + # Returns True only when probe returns True for all _csp_recv_ports. + return ft.reduce( + lambda acc, csp_port: acc and csp_port.probe(), + self._csp_recv_ports, + True, + ) + + +class NcInPortVectorDense(NcInPort): + def recv(self) -> np.ndarray: + return ft.reduce( + lambda acc, csp_port: acc + csp_port.recv(), + self._csp_recv_ports, + np.zeros(self._shape, self._d_type), + ) + + def peek(self) -> np.ndarray: + return ft.reduce( + lambda acc, csp_port: acc + csp_port.peek(), + self._csp_recv_ports, + np.zeros(self._shape, self._d_type), + ) + + +class NcInPortVectorSparse(NcInPort): + def recv(self) -> ty.Tuple[np.ndarray, np.ndarray]: + pass + + def peek(self) -> ty.Tuple[np.ndarray, np.ndarray]: + pass + + +class NcInPortScalarDense(NcInPort): + def recv(self) -> int: + pass + + def peek(self) -> int: + pass + + +class NcInPortScalarSparse(NcInPort): + def recv(self) -> ty.Tuple[int, int]: + pass + + def peek(self) -> ty.Tuple[int, int]: + pass + + +NcInPort.VEC_DENSE = NcInPortVectorDense +NcInPort.VEC_SPARSE = NcInPortVectorSparse +NcInPort.SCALAR_DENSE = NcInPortScalarDense +NcInPort.SCALAR_SPARSE = NcInPortScalarSparse + + +class NcOutPort(AbstractNcPort): + """Ncthon implementation of OutPort used within AbstractNcProcessModels.""" + + VEC_DENSE: ty.Type["NcOutPortVectorDense"] = None + VEC_SPARSE: ty.Type["NcOutPortVectorSparse"] = None + SCALAR_DENSE: ty.Type["NcOutPortScalarDense"] = None + SCALAR_SPARSE: ty.Type["NcOutPortScalarSparse"] = None + + def __init__(self, csp_send_ports: ty.List[CspSendPort], *args): + self._csp_send_ports = csp_send_ports + super().__init__(*args) + + @property + def csp_ports(self) -> ty.List[AbstractCspPort]: + """Returns all csp ports of the port.""" + return self._csp_send_ports + + @abstractmethod + def send(self, data: ty.Union[np.ndarray, int]): + pass + + def flush(self): + pass + + +class NcOutPortVectorDense(NcOutPort): + def send(self, data: np.ndarray): + """Sends data only if port is not dangling.""" + for csp_port in self._csp_send_ports: + csp_port.send(data) + + +class NcOutPortVectorSparse(NcOutPort): + def send(self, data: np.ndarray, idx: np.ndarray): + pass + + +class NcOutPortScalarDense(NcOutPort): + def send(self, data: int): + pass + + +class NcOutPortScalarSparse(NcOutPort): + def send(self, data: int, idx: int): + pass + + +NcOutPort.VEC_DENSE = NcOutPortVectorDense +NcOutPort.VEC_SPARSE = NcOutPortVectorSparse +NcOutPort.SCALAR_DENSE = NcOutPortScalarDense +NcOutPort.SCALAR_SPARSE = NcOutPortScalarSparse + + +class VarPortCmd: + GET = enum_to_np(0) + SET = enum_to_np(1) + + +class NcRefPort(AbstractNcPort): + """NeuroCore implementation of RefPort used + within AbstractNcProcessModels.""" + + VEC_DENSE: ty.Type["NcRefPortVectorDense"] = None + VEC_SPARSE: ty.Type["NcRefPortVectorSparse"] = None + SCALAR_DENSE: ty.Type["NcRefPortScalarDense"] = None + SCALAR_SPARSE: ty.Type["NcRefPortScalarSparse"] = None + + def __init__(self, + csp_send_port: ty.Optional[CspSendPort], + csp_recv_port: ty.Optional[CspRecvPort], *args): + self._csp_recv_port = csp_recv_port + self._csp_send_port = csp_send_port + super().__init__(*args) + + @property + def csp_ports(self) -> ty.List[AbstractCspPort]: + """Returns all csp ports of the port.""" + if self._csp_send_port is not None and self._csp_recv_port is not None: + return [self._csp_send_port, self._csp_recv_port] + else: + # In this case the port was not connected + return [] + + def read( + self, + ) -> ty.Union[ + np.ndarray, ty.Tuple[np.ndarray, np.ndarray], int, ty.Tuple[int, int] + ]: + pass + + def write( + self, + data: ty.Union[ + np.ndarray, + ty.Tuple[np.ndarray, np.ndarray], + int, + ty.Tuple[int, int], + ], + ): + pass + + +class NcRefPortVectorDense(NcRefPort): + def read(self) -> np.ndarray: + """Requests the data from a VarPort and returns the data.""" + if self._csp_send_port and self._csp_recv_port: + header = np.ones(self._csp_send_port.shape) * VarPortCmd.GET + self._csp_send_port.send(header) + + return self._csp_recv_port.recv() + + return np.zeros(self._shape, self._d_type) + + def write(self, data: np.ndarray): + """Sends the data to a VarPort to set its Var.""" + if self._csp_send_port: + header = np.ones(self._csp_send_port.shape) * VarPortCmd.SET + self._csp_send_port.send(header) + self._csp_send_port.send(data) + + +class NcRefPortVectorSparse(NcRefPort): + def read(self) -> ty.Tuple[np.ndarray, np.ndarray]: + pass + + def write(self, data: np.ndarray, idx: np.ndarray): + pass + + +class NcRefPortScalarDense(NcRefPort): + def read(self) -> int: + pass + + def write(self, data: int): + pass + + +class NcRefPortScalarSparse(NcRefPort): + def read(self) -> ty.Tuple[int, int]: + pass + + def write(self, data: int, idx: int): + pass + + +NcRefPort.VEC_DENSE = NcRefPortVectorDense +NcRefPort.VEC_SPARSE = NcRefPortVectorSparse +NcRefPort.SCALAR_DENSE = NcRefPortScalarDense +NcRefPort.SCALAR_SPARSE = NcRefPortScalarSparse + + +class NcVarPort(AbstractNcPort): + """NeuroCore implementation of VarPort used within AbstractNcProcessModel. + """ + + VEC_DENSE: ty.Type["NcVarPortVectorDense"] = None + VEC_SPARSE: ty.Type["NcVarPortVectorSparse"] = None + SCALAR_DENSE: ty.Type["NcVarPortScalarDense"] = None + SCALAR_SPARSE: ty.Type["NcVarPortScalarSparse"] = None + + def __init__(self, + var_name: str, + csp_send_port: ty.Optional[CspSendPort], + csp_recv_port: ty.Optional[CspRecvPort], *args): + self._csp_recv_port = csp_recv_port + self._csp_send_port = csp_send_port + self.var_name = var_name + super().__init__(*args) + + @property + def csp_ports(self) -> ty.List[AbstractCspPort]: + """Returns all csp ports of the port.""" + if self._csp_send_port is not None and self._csp_recv_port is not None: + return [self._csp_send_port, self._csp_recv_port] + else: + # In this case the port was not connected + return [] + + def service(self): + pass + + +class NcVarPortVectorDense(NcVarPort): + def service(self): + """Sets the received value to the given var or sends the value of the + var to the csp_send_port, depending on the received header information + of the csp_recv_port.""" + + # Inspect incoming data + if self._csp_send_port is not None and self._csp_recv_port is not None: + if self._csp_recv_port.probe(): + # If received data is a matrix, flatten and take the first + # element as cmd + cmd = enum_to_np((self._csp_recv_port.recv()).flatten()[0]) + + # Set the value of the Var with the given data + if enum_equal(cmd, VarPortCmd.SET): + data = self._csp_recv_port.recv() + setattr(self._process_model, self.var_name, data) + elif enum_equal(cmd, VarPortCmd.GET): + data = getattr(self._process_model, self.var_name) + self._csp_send_port.send(data) + else: + raise ValueError(f"Wrong Command Info Received : {cmd}") + + +class NcVarPortVectorSparse(NcVarPort): + def recv(self) -> ty.Tuple[np.ndarray, np.ndarray]: + pass + + def peek(self) -> ty.Tuple[np.ndarray, np.ndarray]: + pass + + +class NcVarPortScalarDense(NcVarPort): + def recv(self) -> int: + pass + + def peek(self) -> int: + pass + + +class NcVarPortScalarSparse(NcVarPort): + def recv(self) -> ty.Tuple[int, int]: + pass + + def peek(self) -> ty.Tuple[int, int]: + pass + + +NcVarPort.VEC_DENSE = NcVarPortVectorDense +NcVarPort.VEC_SPARSE = NcVarPortVectorSparse +NcVarPort.SCALAR_DENSE = NcVarPortScalarDense +NcVarPort.SCALAR_SPARSE = NcVarPortScalarSparse + + +class RefVarTypeMapping: + """Class to get the mapping of NcRefPort types to NcVarPortTypes.""" + + mapping: ty.Dict[NcRefPort, NcVarPort] = { + NcRefPortVectorDense: NcVarPortVectorDense, + NcRefPortVectorSparse: NcVarPortVectorSparse, + NcRefPortScalarDense: NcVarPortScalarDense, + NcRefPortScalarSparse: NcVarPortScalarSparse} + + @classmethod + def get(cls, ref_port: NcRefPort): + return cls.mapping[ref_port] diff --git a/src/lava/magma/core/model/nc/type.py b/src/lava/magma/core/model/nc/type.py new file mode 100644 index 000000000..68b5ff581 --- /dev/null +++ b/src/lava/magma/core/model/nc/type.py @@ -0,0 +1,9 @@ +# Copyright (C) 2022 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause +from dataclasses import dataclass + + +@dataclass +class LavaNcType: + d_type: str + precision: int = None # If None, infinite precision is assumed diff --git a/src/lava/magma/core/model/py/model.py b/src/lava/magma/core/model/py/model.py index 7989b5b92..7bea30dc1 100644 --- a/src/lava/magma/core/model/py/model.py +++ b/src/lava/magma/core/model/py/model.py @@ -4,6 +4,8 @@ import typing as ty from abc import ABC, abstractmethod +import logging + import numpy as np from lava.magma.compiler.channels.pypychannel import CspSendPort, CspRecvPort, \ @@ -15,6 +17,7 @@ enum_equal, MGMT_COMMAND, MGMT_RESPONSE, ) +from lava.magma.runtime.runtime_services.enums import LoihiPhase class AbstractPyProcessModel(AbstractProcessModel, ABC): @@ -29,8 +32,9 @@ class AbstractPyProcessModel(AbstractProcessModel, ABC): du: int = LavaPyType(int, np.uint16, precision=12) """ - def __init__(self, proc_params: ty.Dict[str, ty.Any]) -> None: - super().__init__(proc_params) + def __init__(self, proc_params: ty.Dict[str, ty.Any], + loglevel=logging.WARNING) -> None: + super().__init__(proc_params, loglevel=loglevel) self.model_id: ty.Optional[int] = None self.service_to_process: ty.Optional[CspRecvPort] = None self.process_to_service: ty.Optional[CspSendPort] = None @@ -65,17 +69,12 @@ def join(self): class PyLoihiProcessModel(AbstractPyProcessModel): - def __init__(self, proc_params: ty.Dict[str, ty.Any]): - super(PyLoihiProcessModel, self).__init__(proc_params) + def __init__(self, proc_params: ty.Dict[str, ty.Any], + loglevel=logging.WARNING): + super(PyLoihiProcessModel, self).__init__(proc_params, + loglevel=loglevel) self.current_ts = 0 - class Phase: - SPK = enum_to_np(1) - PRE_MGMT = enum_to_np(2) - LRN = enum_to_np(3) - POST_MGMT = enum_to_np(4) - HOST = enum_to_np(5) - def run_spk(self): pass @@ -107,7 +106,7 @@ def run(self): loop ends when the STOP command is received.""" selector = CspSelector() action = 'cmd' - phase = PyLoihiProcessModel.Phase.SPK + phase = LoihiPhase.SPK while True: if action == 'cmd': cmd = self.service_to_process.recv() @@ -117,52 +116,53 @@ def run(self): return try: # Spiking phase - increase time step - if enum_equal(cmd, PyLoihiProcessModel.Phase.SPK): + if enum_equal(cmd, LoihiPhase.SPK): self.current_ts += 1 - phase = PyLoihiProcessModel.Phase.SPK + phase = LoihiPhase.SPK self.run_spk() self.process_to_service.send(MGMT_RESPONSE.DONE) # Pre-management phase elif enum_equal(cmd, - PyLoihiProcessModel.Phase.PRE_MGMT): + LoihiPhase.PRE_MGMT): # Enable via guard method - phase = PyLoihiProcessModel.Phase.PRE_MGMT + phase = LoihiPhase.PRE_MGMT if self.pre_guard(): self.run_pre_mgmt() self.process_to_service.send(MGMT_RESPONSE.DONE) # Learning phase - elif enum_equal(cmd, PyLoihiProcessModel.Phase.LRN): + elif enum_equal(cmd, LoihiPhase.LRN): # Enable via guard method - phase = PyLoihiProcessModel.Phase.LRN + phase = LoihiPhase.LRN if self.lrn_guard(): self.run_lrn() self.process_to_service.send(MGMT_RESPONSE.DONE) # Post-management phase elif enum_equal(cmd, - PyLoihiProcessModel.Phase.POST_MGMT): + LoihiPhase.POST_MGMT): # Enable via guard method - phase = PyLoihiProcessModel.Phase.POST_MGMT + phase = LoihiPhase.POST_MGMT if self.post_guard(): self.run_post_mgmt() self.process_to_service.send(MGMT_RESPONSE.DONE) # Host phase - called at the last time step before STOP - elif enum_equal(cmd, PyLoihiProcessModel.Phase.HOST): - phase = PyLoihiProcessModel.Phase.HOST + elif enum_equal(cmd, LoihiPhase.HOST): + phase = LoihiPhase.HOST pass elif enum_equal(cmd, MGMT_COMMAND.GET_DATA) and \ - enum_equal(phase, PyLoihiProcessModel.Phase.HOST): + enum_equal(phase, LoihiPhase.HOST): # Handle get/set Var requests from runtime service self._handle_get_var() elif enum_equal(cmd, MGMT_COMMAND.SET_DATA) and \ - enum_equal(phase, PyLoihiProcessModel.Phase.HOST): + enum_equal(phase, LoihiPhase.HOST): # Handle get/set Var requests from runtime service self._handle_set_var() else: raise ValueError( f"Wrong Phase Info Received : {cmd}") except Exception as inst: - print("Exception happened") + self.log.info(f"Exception {inst} occured while" + f" running command {cmd} in {self.__class__}") # Inform runtime service about termination self.process_to_service.send(MGMT_RESPONSE.ERROR) self.join() @@ -172,12 +172,13 @@ def run(self): self._handle_var_port(action) channel_actions = [(self.service_to_process, lambda: 'cmd')] - if enum_equal(phase, PyLoihiProcessModel.Phase.PRE_MGMT) or \ - enum_equal(phase, PyLoihiProcessModel.Phase.POST_MGMT): + if enum_equal(phase, LoihiPhase.PRE_MGMT) or \ + enum_equal(phase, LoihiPhase.POST_MGMT): for var_port in self.var_ports: for csp_port in var_port.csp_ports: if isinstance(csp_port, CspRecvPort): - channel_actions.append((csp_port, lambda: var_port)) + channel_actions.append( + (csp_port, lambda: var_port)) action = selector.select(*channel_actions) def _handle_get_var(self): diff --git a/src/lava/magma/core/process/process.py b/src/lava/magma/core/process/process.py index fe60638bf..8c08eede2 100644 --- a/src/lava/magma/core/process/process.py +++ b/src/lava/magma/core/process/process.py @@ -1,6 +1,7 @@ # Copyright (C) 2021 Intel Corporation # SPDX-License-Identifier: BSD-3-Clause # See: https://spdx.org/licenses/ +import logging import typing as ty from _collections import OrderedDict @@ -228,6 +229,8 @@ def __init__(self, **kwargs): self.name: str = kwargs.pop("name", f"Process_{self.id}") + self.loglevel: int = kwargs.pop("loglevel", logging.WARNING) + # kwargs will be used for ProcessModel initialization later self.init_args: dict = kwargs @@ -398,7 +401,8 @@ def run(self, if not self._runtime: executable = self.compile(run_cfg) self._runtime = Runtime(executable, - ActorType.MultiProcessing) + ActorType.MultiProcessing, + loglevel=self.loglevel) self._runtime.initialize() self._runtime.start(condition) diff --git a/src/lava/magma/core/run_configs.py b/src/lava/magma/core/run_configs.py index 2e4d10fa6..395eab98d 100644 --- a/src/lava/magma/core/run_configs.py +++ b/src/lava/magma/core/run_configs.py @@ -2,6 +2,9 @@ # SPDX-License-Identifier: BSD-3-Clause # See: https://spdx.org/licenses/ from __future__ import annotations + +import logging + import typing as ty from abc import ABC @@ -42,7 +45,10 @@ class RunConfig(ABC): """ def __init__(self, - custom_sync_domains: ty.Optional[ty.List[SyncDomain]] = None): + custom_sync_domains: ty.Optional[ty.List[SyncDomain]] = None, + loglevel=logging.WARNING): + self.log = logging.getLogger(__name__) + self.log.setLevel(loglevel) self.custom_sync_domains = [] if custom_sync_domains: if not isinstance(custom_sync_domains, list): @@ -122,8 +128,10 @@ def __init__(self, select_sub_proc_model: ty.Optional[bool] = False, exception_proc_model_map: ty.Optional[ty.Dict[ ty.Type[AbstractProcess], ty.Type[ - AbstractProcessModel]]] = None): - super().__init__(custom_sync_domains=custom_sync_domains) + AbstractProcessModel]]] = None, + loglevel=logging.WARNING): + super().__init__(custom_sync_domains=custom_sync_domains, + loglevel=loglevel) self.select_tag = select_tag self.select_sub_proc_model = select_sub_proc_model self.exception_proc_model_map = exception_proc_model_map @@ -240,11 +248,11 @@ def _ispypm(pm: ty.Type[AbstractProcessModel]) -> bool: # Assumption: User doesn't care about tags. We return the first # SubProcessModel found if self.select_tag is None: - print(f"[{self.__class__.__qualname__}]: Using the first " - f"SubProcessModel " - f"{proc_models[sub_pm_idxs[0]].__qualname__} " - f"available for Process " - f"{proc.name}::{proc.__class__.__qualname__}.") + self.log.info(f"[{self.__class__.__qualname__}]: Using the" + f" first SubProcessModel " + f"{proc_models[sub_pm_idxs[0]].__qualname__} " + f"available for Process " + f"{proc.name}::{proc.__class__.__qualname__}.") return proc_models[sub_pm_idxs[0]] # Case 3a(iii): User asked for a specific tag: # ------------------------------------------- @@ -276,11 +284,11 @@ def _ispypm(pm: ty.Type[AbstractProcessModel]) -> bool: # Assumption: User doesn't care about tags. We return the first # PyProcessModel found if self.select_tag is None: - print(f"[{self.__class__.__qualname__}]: Using the first " - f"PyProcessModel " - f"{proc_models[py_pm_idxs[0]].__qualname__} " - f"available for Process " - f"{proc.name}::{proc.__class__.__qualname__}.") + self.log.info(f"[{self.__class__.__qualname__}]: Using the first " + f"PyProcessModel " + f"{proc_models[py_pm_idxs[0]].__qualname__} " + f"available for Process " + f"{proc.name}::{proc.__class__.__qualname__}.") return proc_models[py_pm_idxs[0]] # Case 3b(ii): User asked for a specific tag: # ------------------------------------------ diff --git a/src/lava/magma/core/sync/protocols/async_protocol.py b/src/lava/magma/core/sync/protocols/async_protocol.py index e504cedc1..54ddeda8b 100644 --- a/src/lava/magma/core/sync/protocols/async_protocol.py +++ b/src/lava/magma/core/sync/protocols/async_protocol.py @@ -5,7 +5,8 @@ from lava.magma.core.resources import CPU from lava.magma.core.sync.protocol import AbstractSyncProtocol -from lava.magma.runtime.runtime_service import AsyncPyRuntimeService +from lava.magma.runtime.runtime_services.runtime_service import \ + AsyncPyRuntimeService @dataclass diff --git a/src/lava/magma/core/sync/protocols/loihi_protocol.py b/src/lava/magma/core/sync/protocols/loihi_protocol.py index 3a7cf147b..00469c724 100644 --- a/src/lava/magma/core/sync/protocols/loihi_protocol.py +++ b/src/lava/magma/core/sync/protocols/loihi_protocol.py @@ -1,10 +1,13 @@ +# Copyright (C) 2022 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause +# See: https://spdx.org/licenses/ from collections import namedtuple from dataclasses import dataclass from lava.magma.core.resources import CPU, NeuroCore from lava.magma.core.sync.protocol import AbstractSyncProtocol from lava.magma.runtime.mgmt_token_enums import enum_to_np -from lava.magma.runtime.runtime_service import ( +from lava.magma.runtime.runtime_services.runtime_service import ( LoihiPyRuntimeService, LoihiCRuntimeService, ) diff --git a/src/lava/magma/core/sync/protocols/nxsdk_protocol.py b/src/lava/magma/core/sync/protocols/nxsdk_protocol.py new file mode 100644 index 000000000..61c883b43 --- /dev/null +++ b/src/lava/magma/core/sync/protocols/nxsdk_protocol.py @@ -0,0 +1,23 @@ +# Copyright (C) 2022 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause +# See: https://spdx.org/licenses/ +from dataclasses import dataclass + +from lava.magma.core.resources import CPU, NeuroCore +from lava.magma.core.sync.protocol import AbstractSyncProtocol +from lava.magma.runtime.runtime_services.runtime_service import ( + PyRuntimeService, + NxSDKRuntimeService, +) + + +@dataclass +class NxsdkProtocol(AbstractSyncProtocol): + """Synchronizer class that implement NxsdkProtocol + protocol using NxCore for its domain. + """ + runtime_service = {CPU: PyRuntimeService, NeuroCore: NxSDKRuntimeService} + + @property + def runtime_service(self): + return self.runtime_service diff --git a/src/lava/magma/runtime/runtime.py b/src/lava/magma/runtime/runtime.py index ce119eff7..23f6bbc19 100644 --- a/src/lava/magma/runtime/runtime.py +++ b/src/lava/magma/runtime/runtime.py @@ -3,10 +3,13 @@ # See: https://spdx.org/licenses/ from __future__ import annotations +import logging + +import numpy as np + import typing import typing as ty -import numpy as np from lava.magma.compiler.channels.pypychannel import CspSendPort, CspRecvPort from lava.magma.compiler.exec_var import AbstractExecVar @@ -17,7 +20,8 @@ MessageInfrastructureFactory from lava.magma.runtime.mgmt_token_enums import enum_to_np, enum_equal, \ MGMT_COMMAND, MGMT_RESPONSE -from lava.magma.runtime.runtime_service import AsyncPyRuntimeService +from lava.magma.runtime.runtime_services.runtime_service \ + import AsyncPyRuntimeService if ty.TYPE_CHECKING: from lava.magma.core.process.process import AbstractProcess @@ -46,7 +50,10 @@ class Runtime: def __init__(self, exe: Executable, - message_infrastructure_type: ActorType): + message_infrastructure_type: ActorType, + loglevel=logging.WARNING): + self.log = logging.getLogger(__name__) + self.log.setLevel(loglevel) self._run_cond: typing.Optional[AbstractRunCondition] = None self._executable: Executable = exe @@ -199,7 +206,7 @@ def start(self, run_condition: AbstractRunCondition): self._is_started = True self._run(run_condition) else: - print("Runtime not initialized yet.") + self.log.info("Runtime not initialized yet.") def _run(self, run_condition): if self._is_started: @@ -236,7 +243,7 @@ def _run(self, run_condition): raise ValueError(f"Wrong type of run_condition : " f"{run_condition.__class__}") else: - print("Runtime not started yet.") + self.log.info("Runtime not started yet.") def wait(self): if self._is_running: @@ -264,7 +271,7 @@ def stop(self): self._is_started = False # Send messages to RuntimeServices to stop as soon as possible. else: - print("Runtime not started yet.") + self.log.info("Runtime not started yet.") finally: self._messaging_infrastructure.stop() diff --git a/src/lava/magma/runtime/runtime_services/enums.py b/src/lava/magma/runtime/runtime_services/enums.py new file mode 100644 index 000000000..30c51935b --- /dev/null +++ b/src/lava/magma/runtime/runtime_services/enums.py @@ -0,0 +1,43 @@ +# Copyright (C) 2021 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause +# See: https://spdx.org/licenses/ +from enum import IntEnum +from lava.magma.runtime.mgmt_token_enums import enum_to_np + + +class LoihiVersion(IntEnum): + """Enumerator of different Loihi Versions.""" + N2 = 2 + N3 = 3 + + +class LoihiPhase: + """Enumerator of Lava Loihi phases""" + SPK = enum_to_np(1) + PRE_MGMT = enum_to_np(2) + LRN = enum_to_np(3) + POST_MGMT = enum_to_np(4) + HOST = enum_to_np(5) + + +class NxSdkPhase: + """Enumerator phases in which snip can run in NxSDK.""" + + EMBEDDED_INIT = 1 + """INIT Phase of Embedded Snip. This executes only once.""" + EMBEDDED_SPIKING = 2 + """SPIKING Phase of Embedded Snip.""" + EMBEDDED_PRELEARN_MGMT = 3 + """Pre-Learn Management Phase of Embedded Snip.""" + EMBEDDED_MGMT = 4 + """Management Phase of Embedded Snip.""" + HOST_PRE_EXECUTION = 5 + """Host Pre Execution Phase for Host Snip.""" + HOST_POST_EXECUTION = 6 + """Host Post Execution Phase for Host Snip.""" + HOST_CONCURRENT_EXECUTION = 7 + """Concurrent Execution for Host Snip.""" + EMBEDDED_USER_CMD = 8 + """Any User Command to execute during embedded execution. (Internal Use Only)""" + EMBEDDED_REMOTE_MGMT = 9 + """A management phase snip triggered remotely""" \ No newline at end of file diff --git a/src/lava/magma/runtime/runtime_services/interfaces.py b/src/lava/magma/runtime/runtime_services/interfaces.py new file mode 100644 index 000000000..6042ed8d0 --- /dev/null +++ b/src/lava/magma/runtime/runtime_services/interfaces.py @@ -0,0 +1,48 @@ +# Copyright (C) 2022 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause +# See: https://spdx.org/licenses/ +import typing as ty +from abc import ABC, abstractmethod + +from lava.magma.compiler.channels.pypychannel import CspRecvPort, CspSendPort +from lava.magma.core.sync.protocol import AbstractSyncProtocol + + +class AbstractRuntimeService(ABC): + def __init__(self, protocol): + self.protocol: ty.Optional[AbstractSyncProtocol] = protocol + + self.runtime_service_id: ty.Optional[int] = None + + self.runtime_to_service: ty.Optional[CspRecvPort] = None + self.service_to_runtime: ty.Optional[CspSendPort] = None + + self.model_ids: ty.List[int] = [] + + self.service_to_process: ty.Iterable[CspSendPort] = [] + self.process_to_service: ty.Iterable[CspRecvPort] = [] + + def __repr__(self): + return f"Synchronizer : {self.__class__}, \ + RuntimeServiceId : {self.runtime_service_id}, \ + Protocol: {self.protocol}" + + def start(self): + self.runtime_to_service.start() + self.service_to_runtime.start() + for i in range(len(self.service_to_process)): + self.service_to_process[i].start() + self.process_to_service[i].start() + self.run() + + @abstractmethod + def run(self): + pass + + def join(self): + self.runtime_to_service.join() + self.service_to_runtime.join() + + for i in range(len(self.service_to_process)): + self.service_to_process[i].join() + self.process_to_service[i].join() diff --git a/src/lava/magma/runtime/runtime_service.py b/src/lava/magma/runtime/runtime_services/runtime_service.py similarity index 66% rename from src/lava/magma/runtime/runtime_service.py rename to src/lava/magma/runtime/runtime_services/runtime_service.py index 0912ee4a6..3f8e9e9c1 100644 --- a/src/lava/magma/runtime/runtime_service.py +++ b/src/lava/magma/runtime/runtime_services/runtime_service.py @@ -1,13 +1,11 @@ -# Copyright (C) 2021 Intel Corporation +# Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: BSD-3-Clause # See: https://spdx.org/licenses/ import typing as ty -from abc import ABC, abstractmethod import numpy as np -from lava.magma.compiler.channels.pypychannel import CspRecvPort, CspSendPort, \ - CspSelector +from lava.magma.compiler.channels.pypychannel import CspSelector from lava.magma.core.sync.protocol import AbstractSyncProtocol from lava.magma.runtime.mgmt_token_enums import ( enum_to_np, @@ -15,48 +13,19 @@ MGMT_RESPONSE, MGMT_COMMAND, ) +from lava.magma.runtime.runtime_services.enums import ( + LoihiPhase, + LoihiVersion +) +from lava.magma.runtime.runtime_services.interfaces import \ + AbstractRuntimeService - -class AbstractRuntimeService(ABC): - def __init__(self, protocol): - self.protocol: ty.Optional[AbstractSyncProtocol] = protocol - - self.runtime_service_id: ty.Optional[int] = None - - self.runtime_to_service: ty.Optional[CspRecvPort] = None - self.service_to_runtime: ty.Optional[CspSendPort] = None - - self.model_ids: ty.List[int] = [] - - self.service_to_process: ty.Iterable[CspSendPort] = [] - self.process_to_service: ty.Iterable[CspRecvPort] = [] - - def __repr__(self): - return f"Synchronizer : {self.__class__}, \ - RuntimeServiceId : {self.runtime_service_id}, \ - Protocol: {self.protocol}" - - def start(self): - self.runtime_to_service.start() - self.service_to_runtime.start() - for i in range(len(self.service_to_process)): - self.service_to_process[i].start() - self.process_to_service[i].start() - self.run() - - @abstractmethod - def run(self): +try: + from nxsdk.arch.base.nxboard import NxBoard +except(ImportError): + class NxBoard(): pass - def join(self): - self.runtime_to_service.join() - self.service_to_runtime.join() - - for i in range(len(self.service_to_process)): - self.service_to_process[i].join() - self.process_to_service[i].join() - - class PyRuntimeService(AbstractRuntimeService): pass @@ -65,35 +34,32 @@ class CRuntimeService(AbstractRuntimeService): pass +class NcRuntimeService(AbstractRuntimeService): + pass + + class LoihiPyRuntimeService(PyRuntimeService): """RuntimeService that implements Loihi SyncProtocol in Python.""" - class Phase: - SPK = enum_to_np(1) - PRE_MGMT = enum_to_np(2) - LRN = enum_to_np(3) - POST_MGMT = enum_to_np(4) - HOST = enum_to_np(5) - def _next_phase(self, curr_phase, is_last_time_step: bool): """Advances the current phase to the next phase. On the first time step it starts with HOST phase and advances to SPK. Afterwards it loops: SPK -> PRE_MGMT -> LRN -> POST_MGMT -> SPK On the last time step POST_MGMT advances to HOST phase.""" - if curr_phase == LoihiPyRuntimeService.Phase.SPK: - return LoihiPyRuntimeService.Phase.PRE_MGMT - elif curr_phase == LoihiPyRuntimeService.Phase.PRE_MGMT: - return LoihiPyRuntimeService.Phase.LRN - elif curr_phase == LoihiPyRuntimeService.Phase.LRN: - return LoihiPyRuntimeService.Phase.POST_MGMT - elif curr_phase == LoihiPyRuntimeService.Phase.POST_MGMT and \ + if curr_phase == LoihiPhase.SPK: + return LoihiPhase.PRE_MGMT + elif curr_phase == LoihiPhase.PRE_MGMT: + return LoihiPhase.LRN + elif curr_phase == LoihiPhase.LRN: + return LoihiPhase.POST_MGMT + elif curr_phase == LoihiPhase.POST_MGMT and \ is_last_time_step: - return LoihiPyRuntimeService.Phase.HOST - elif curr_phase == LoihiPyRuntimeService.Phase.POST_MGMT and not \ + return LoihiPhase.HOST + elif curr_phase == LoihiPhase.POST_MGMT and not \ is_last_time_step: - return LoihiPyRuntimeService.Phase.SPK - elif curr_phase == LoihiPyRuntimeService.Phase.HOST: - return LoihiPyRuntimeService.Phase.SPK + return LoihiPhase.SPK + elif curr_phase == LoihiPhase.HOST: + return LoihiPhase.SPK def _send_pm_cmd(self, phase: MGMT_COMMAND): """Sends a command (phase information) to all ProcessModels.""" @@ -161,7 +127,7 @@ def run(self): last time step is reached. The runtime is informed after the last time step. The loop ends when receiving the STOP command from the runtime.""" selector = CspSelector() - phase = LoihiPyRuntimeService.Phase.HOST + phase = LoihiPhase.HOST channel_actions = [(self.runtime_to_service, lambda: 'cmd')] @@ -199,7 +165,7 @@ def run(self): # The number of time steps was received ("command") # Start iterating through Loihi phases curr_time_step = 0 - phase = LoihiPyRuntimeService.Phase.HOST + phase = LoihiPhase.HOST while True: # Check if it is the last time step is_last_ts = enum_equal(enum_to_np(curr_time_step), @@ -207,13 +173,13 @@ def run(self): # Advance to the next phase phase = self._next_phase(phase, is_last_ts) # Increase time step if spiking phase - if enum_equal(phase, LoihiPyRuntimeService.Phase.SPK): + if enum_equal(phase, LoihiPhase.SPK): curr_time_step += 1 # Inform ProcessModels about current phase self._send_pm_cmd(phase) # ProcessModels respond with DONE if not HOST phase if not enum_equal( - phase, LoihiPyRuntimeService.Phase.HOST): + phase, LoihiPhase.HOST): for rsp in self._get_pm_resp(): if not enum_equal(rsp, MGMT_RESPONSE.DONE): @@ -230,14 +196,14 @@ def run(self): # If HOST phase (last time step ended) break the loop if enum_equal( - phase, LoihiPyRuntimeService.Phase.HOST): + phase, LoihiPhase.HOST): break # Inform the runtime that last time step was reached self.service_to_runtime.send(MGMT_RESPONSE.DONE) def _handle_get_set(self, phase, command): - if enum_equal(phase, LoihiPyRuntimeService.Phase.HOST): + if enum_equal(phase, LoihiPhase.HOST): if enum_equal(command, MGMT_COMMAND.GET_DATA): requests: ty.List[np.ndarray] = [command] # recv model_id @@ -295,3 +261,103 @@ def run(self): if not enum_equal(rsp, MGMT_RESPONSE.DONE): raise ValueError(f"Wrong Response Received : {rsp}") self.service_to_runtime.send(MGMT_RESPONSE.DONE) + + +class NxSDKRuntimeService(NcRuntimeService): + """NxSDK RuntimeService that implements NxCore SyncProtocol. + + The NxSDKRuntimeService is a wrapper around NxCore that allows + interaction with Loihi through NxCore API and GRPC communication + channels to Loihi. + + Parameters + ---------- + protocol: ty.Type[LoihiProtocol] + Communication protocol used by NxSDKRuntimeService + loihi_version: LoihiVersion + Version of Loihi Chip to use, N2 or N3 + """ + + def __init__(self, + protocol: ty.Type[AbstractSyncProtocol], + loihi_version: LoihiVersion = LoihiVersion.N3,): + super(NxSDKRuntimeService, self).__init__( + protocol=protocol + ) + self.board: NxBoard = None + self.num_steps = 0 + + if loihi_version == LoihiVersion.N3: + from nxsdk.arch.n3b.n3board import N3Board + # # TODO: Need to find good way to set Board Init + self.board = N3Board(1, 1, [2], [[5, 5]]) + elif loihi_version == LoihiVersion.N2: + from nxsdk.arch.n2a.n2board import N2Board # noqa F401 + self.board = N2Board(1, 1, [2], [[5, 5]]) + else: + raise ValueError('Unsupported Loihi version ' + + 'used in board selection') + + def _send_pm_cmd(self, cmd: MGMT_COMMAND): + for stop_send_port in self.service_to_process: + stop_send_port.send(cmd) + + def _send_pm_rn(self, run_number: int): + for stop_send_port in self.service_to_process: + stop_send_port.send(run_number) + + def _get_pm_resp(self) -> ty.Iterable[MGMT_RESPONSE]: + rcv_msgs = [] + for ptos_recv_port in self.process_to_service: + rcv_msgs.append(ptos_recv_port.recv()) + return rcv_msgs + + def run(self): + self.num_steps = self.runtime_to_service.recv() + self.service_to_runtime.send(MGMT_RESPONSE.DONE) + + selector = CspSelector() + channel_actions = [(self.runtime_to_service, lambda: 'cmd')] + + while True: + action = selector.select(*channel_actions) + if action == 'cmd': + command = self.runtime_to_service.recv() + if enum_equal(command, MGMT_COMMAND.STOP): + self._send_pm_cmd(command) + rsps = self._get_pm_resp() + for rsp in rsps: + if not enum_equal(rsp, MGMT_RESPONSE.TERMINATED): + raise ValueError(f"Wrong Response Received : {rsp}") + self.service_to_runtime.send(MGMT_RESPONSE.TERMINATED) + self.join() + return + elif enum_equal(command, MGMT_COMMAND.PAUSE): + self._send_pm_cmd(command) + rsps = self._get_pm_resp() + for rsp in rsps: + if not enum_equal(rsp, MGMT_RESPONSE.PAUSED): + raise ValueError(f"Wrong Response Received : {rsp}") + + self.service_to_runtime.send(MGMT_RESPONSE.PAUSED) + break + elif enum_equal(command, MGMT_COMMAND.RUN): + self._send_pm_cmd(MGMT_COMMAND.RUN) + rsps = self._get_pm_resp() + self._send_pm_cmd(self.num_steps) + rsps = rsps + self._get_pm_resp() + for rsp in rsps: + if not enum_equal(rsp, MGMT_RESPONSE.DONE): + raise ValueError(f"Wrong Response Received : {rsp}") + self.service_to_runtime.send(MGMT_RESPONSE.DONE) + else: + self.service_to_runtime.send(MGMT_RESPONSE.ERROR) + + self._send_pm_cmd(MGMT_COMMAND.STOP) + return + + def get_board(self) -> NxBoard: + if self.board is not None: + return self.board + else: + AssertionError("Cannot return board, self.board is None") diff --git a/tests/lava/magma/compiler/test_compiler.py b/tests/lava/magma/compiler/test_compiler.py index 46a0e4cf7..9df84bb21 100644 --- a/tests/lava/magma/compiler/test_compiler.py +++ b/tests/lava/magma/compiler/test_compiler.py @@ -1,6 +1,7 @@ # Copyright (C) 2021 Intel Corporation # SPDX-License-Identifier: BSD-3-Clause # See: https://spdx.org/licenses/ +import logging import unittest from lava.magma.compiler.channels.interfaces import ChannelType @@ -126,7 +127,8 @@ def run(self): # A minimal RunConfig that will select SubProcModel if there is one class RunCfg(RunConfig): def __init__(self, custom_sync_domains=None, select_sub_proc_model=False): - super().__init__(custom_sync_domains=custom_sync_domains) + super().__init__(custom_sync_domains=custom_sync_domains, + loglevel=logging.WARNING) self.select_sub_proc_model = select_sub_proc_model def select(self, proc, proc_models): diff --git a/tests/lava/magma/core/process/test_lif_dense_lif.py b/tests/lava/magma/core/process/test_lif_dense_lif.py index 879918023..f6a1ddaec 100644 --- a/tests/lava/magma/core/process/test_lif_dense_lif.py +++ b/tests/lava/magma/core/process/test_lif_dense_lif.py @@ -1,6 +1,7 @@ # Copyright (C) 2021 Intel Corporation # SPDX-License-Identifier: BSD-3-Clause # See: https://spdx.org/licenses/ +import logging import unittest from lava.magma.core.process.process import AbstractProcess @@ -13,7 +14,8 @@ class SimpleRunConfig(RunConfig): def __init__(self, **kwargs): sync_domains = kwargs.pop("sync_domains") - super().__init__(custom_sync_domains=sync_domains) + super().__init__(custom_sync_domains=sync_domains, + loglevel=logging.WARNING) self.model = None if "model" in kwargs: self.model = kwargs.pop("model") diff --git a/tests/lava/magma/runtime/test_exception_handling.py b/tests/lava/magma/runtime/test_exception_handling.py index 1c5ea0bbe..b0e2b81c9 100644 --- a/tests/lava/magma/runtime/test_exception_handling.py +++ b/tests/lava/magma/runtime/test_exception_handling.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: BSD-3-Clause # See: https://spdx.org/licenses/ +import logging import unittest from lava.magma.core.decorator import implements, requires, tag @@ -19,21 +20,21 @@ # A minimal process with an OutPort class P1(AbstractProcess): def __init__(self, **kwargs): - super().__init__(**kwargs) + super().__init__(loglevel=logging.CRITICAL, **kwargs) self.out = OutPort(shape=(2,)) # A minimal process with an InPort class P2(AbstractProcess): def __init__(self, **kwargs): - super().__init__(**kwargs) + super().__init__(loglevel=logging.CRITICAL, **kwargs) self.inp = InPort(shape=(2,)) # A minimal process with an InPort class P3(AbstractProcess): def __init__(self, **kwargs): - super().__init__(**kwargs) + super().__init__(loglevel=logging.CRITICAL, **kwargs) self.inp = InPort(shape=(2,)) @@ -82,12 +83,15 @@ def test_one_pm(self): # Create an instance of P1 proc = P1() + run_steps = RunSteps(num_steps=1) + run_cfg = Loihi1SimCfg(loglevel=logging.CRITICAL) + # Run the network for 1 time step -> no exception - proc.run(condition=RunSteps(num_steps=1), run_cfg=Loihi1SimCfg()) + proc.run(condition=run_steps, run_cfg=run_cfg) # Run the network for another time step -> expect exception with self.assertRaises(RuntimeError) as context: - proc.run(condition=RunSteps(num_steps=1), run_cfg=Loihi1SimCfg()) + proc.run(condition=run_steps, run_cfg=run_cfg) exception = context.exception self.assertEqual(RuntimeError, type(exception)) @@ -102,15 +106,18 @@ def test_two_pm(self): sender = P1() recv = P2() + run_steps = RunSteps(num_steps=1) + run_cfg = Loihi1SimCfg(loglevel=logging.CRITICAL) + # Connect sender with receiver sender.out.connect(recv.inp) # Run the network for 1 time step -> no exception - sender.run(condition=RunSteps(num_steps=1), run_cfg=Loihi1SimCfg()) + sender.run(condition=run_steps, run_cfg=run_cfg) # Run the network for another time step -> expect exception with self.assertRaises(RuntimeError) as context: - sender.run(condition=RunSteps(num_steps=1), run_cfg=Loihi1SimCfg()) + sender.run(condition=run_steps, run_cfg=run_cfg) exception = context.exception self.assertEqual(RuntimeError, type(exception)) @@ -126,15 +133,18 @@ def test_three_pm(self): recv1 = P2() recv2 = P3() + run_steps = RunSteps(num_steps=1) + run_cfg = Loihi1SimCfg(loglevel=logging.CRITICAL) + # Connect sender with receiver sender.out.connect([recv1.inp, recv2.inp]) # Run the network for 1 time step -> no exception - sender.run(condition=RunSteps(num_steps=1), run_cfg=Loihi1SimCfg()) + sender.run(condition=run_steps, run_cfg=run_cfg) # Run the network for another time step -> expect exception with self.assertRaises(RuntimeError) as context: - sender.run(condition=RunSteps(num_steps=1), run_cfg=Loihi1SimCfg()) + sender.run(condition=run_steps, run_cfg=run_cfg) exception = context.exception self.assertEqual(RuntimeError, type(exception)) diff --git a/tests/lava/magma/runtime/test_get_set_var.py b/tests/lava/magma/runtime/test_get_set_var.py index edc3deb33..33fe79999 100644 --- a/tests/lava/magma/runtime/test_get_set_var.py +++ b/tests/lava/magma/runtime/test_get_set_var.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: BSD-3-Clause # See: https://spdx.org/licenses/ +import logging import numpy as np import unittest @@ -19,7 +20,7 @@ class SimpleProcess(AbstractProcess): def __init__(self, **kwargs): - super().__init__(**kwargs) + super().__init__(loglevel=logging.WARNING, **kwargs) shape = kwargs["shape"] self.u = Var(shape=shape, init=np.array([[7, 8], [9, 10]], dtype=np.int32)) @@ -30,7 +31,8 @@ def __init__(self, **kwargs): class SimpleRunConfig(RunConfig): def __init__(self, **kwargs): sync_domains = kwargs.pop("sync_domains") - super().__init__(custom_sync_domains=sync_domains) + super().__init__(custom_sync_domains=sync_domains, + loglevel=logging.WARNING) self.model = None if "model" in kwargs: self.model = kwargs.pop("model") diff --git a/tests/lava/magma/runtime/test_loihi_protocol.py b/tests/lava/magma/runtime/test_loihi_protocol.py index 817ab4ccd..5791a15cd 100644 --- a/tests/lava/magma/runtime/test_loihi_protocol.py +++ b/tests/lava/magma/runtime/test_loihi_protocol.py @@ -1,3 +1,4 @@ +import logging import unittest from lava.magma.core.decorator import implements, requires @@ -14,7 +15,7 @@ class SimpleProcess(AbstractProcess): def __init__(self, **kwargs): - super().__init__(**kwargs) + super().__init__(loglevel=logging.WARNING, **kwargs) shape = kwargs["shape"] self.u = Var(shape=shape, init=0) self.v = Var(shape=shape, init=0) @@ -23,7 +24,8 @@ def __init__(self, **kwargs): class SimpleRunConfig(RunConfig): def __init__(self, **kwargs): sync_domains = kwargs.pop("sync_domains") - super().__init__(custom_sync_domains=sync_domains) + super().__init__(custom_sync_domains=sync_domains, + loglevel=logging.WARNING) self.model = None if "model" in kwargs: self.model = kwargs.pop("model") diff --git a/tests/lava/magma/runtime/test_ref_var_ports.py b/tests/lava/magma/runtime/test_ref_var_ports.py index fbd62c576..aac3fcf4a 100644 --- a/tests/lava/magma/runtime/test_ref_var_ports.py +++ b/tests/lava/magma/runtime/test_ref_var_ports.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: BSD-3-Clause # See: https://spdx.org/licenses/ +import logging import numpy as np import unittest @@ -22,7 +23,7 @@ # A minimal process with a Var and a RefPort, VarPort class P1(AbstractProcess): def __init__(self, **kwargs): - super().__init__(**kwargs) + super().__init__(loglevel=logging.WARNING, **kwargs) self.ref1 = RefPort(shape=(3,)) self.var1 = Var(shape=(2,), init=17) self.var_port_var1 = VarPort(self.var1) @@ -31,7 +32,7 @@ def __init__(self, **kwargs): # A minimal process with 2 Vars and a RefPort, VarPort class P2(AbstractProcess): def __init__(self, **kwargs): - super().__init__(**kwargs) + super().__init__(loglevel=logging.WARNING, **kwargs) self.var2 = Var(shape=(3,), init=4) self.var_port_var2 = VarPort(self.var2) self.ref2 = RefPort(shape=(2,)) diff --git a/tests/lava/magma/runtime/test_runtime_service.py b/tests/lava/magma/runtime/test_runtime_service.py index 4a846a93f..c8a6cf96a 100644 --- a/tests/lava/magma/runtime/test_runtime_service.py +++ b/tests/lava/magma/runtime/test_runtime_service.py @@ -1,14 +1,27 @@ +import logging +import os +import random import unittest from multiprocessing.managers import SharedMemoryManager import numpy as np from lava.magma.compiler.channels.pypychannel import PyPyChannel +from lava.magma.compiler.executable import Executable +from lava.magma.core.resources import HeadNode +from lava.magma.compiler.node import Node, NodeConfig from lava.magma.core.decorator import implements +from lava.magma.core.model.nc.model import NcProcessModel from lava.magma.core.model.py.model import AbstractPyProcessModel +from lava.magma.core.process.message_interface_enum import ActorType from lava.magma.core.process.process import AbstractProcess from lava.magma.core.sync.protocol import AbstractSyncProtocol -from lava.magma.runtime.runtime_service import PyRuntimeService +from lava.magma.core.sync.protocols.nxsdk_protocol import NxsdkProtocol +from lava.magma.runtime.runtime import Runtime +from lava.magma.runtime.runtime_services.runtime_service import ( + PyRuntimeService, + NxSDKRuntimeService +) class MockInterface: @@ -83,5 +96,182 @@ def test_runtime_service_start_run(self): smm.shutdown() +class NxSDKTestProcess(AbstractProcess): + def __init__(self, **kwargs): + super().__init__(loglevel=logging.DEBUG, **kwargs) + + +@implements(proc=NxSDKTestProcess, protocol=NxsdkProtocol) +class NxSDKTestProcessModel(NcProcessModel): + def start(self, num_steps): + self.service_to_process.start() + self.process_to_service.start() + for p in self.nc_ports: + p.start() + self.board.run(num_steps) + + def stop(self): + self.board.stop() + + def test_setup(self): + self.nxCore = self.board.nxChips[0].nxCores[0] + self.axon_map = self.nxCore.axonMap + + def test_idx(self, test_case: unittest.TestCase): + value = random.getrandbits(15) + # Setting the value of idx as value + self.axon_map[0].idx = value + self.axon_map.push(0) + # Checking the value of idx + self.axon_map.fetch(0) + test_case.assertEqual(self.axon_map[0].idx, value) + + value = random.getrandbits(15) + # Setting the value of idx as value + self.axon_map[1].idx = value + self.axon_map.push(1) + # Checking the value of idx + self.axon_map.fetch(1) + test_case.assertEqual(self.axon_map[1].idx, value) + + def test_len(self, test_case: unittest.TestCase): + value = random.getrandbits(13) + # Setting the value of len as value + self.axon_map[0].len = value + self.axon_map.push(0) + # Checking the value of len + self.axon_map.fetch(0) + test_case.assertEqual(self.axon_map[0].len, value) + + value = random.getrandbits(13) + # Setting the value of len as value + self.axon_map[1].len = value + self.axon_map.push(1) + # Checking the value of len + self.axon_map.fetch(1) + test_case.assertEqual(self.axon_map[1].len, value) + + def test_data(self, test_case: unittest.TestCase): + value = random.getrandbits(36) + # Setting the value of data as value + self.axon_map[0].data = value + self.axon_map.push(0) + # Checking the value of data + self.axon_map.fetch(0) + test_case.assertEqual(self.axon_map[0].data, value) + + value = random.getrandbits(36) + # Setting the value of data as value + self.axon_map[1].data = value + self.axon_map.push(1) + # Checking the value of data + self.axon_map.fetch(1) + test_case.assertEqual(self.axon_map[1].data, value) + + +class TestNxSDKRuntimeService(unittest.TestCase): + # Run Loihi Tests using example command below: + # + # SLURM=1 LOIHI_GEN=N3B3 BOARD=ncl-og-05 PARTITION=oheogulch + # RUN_LOIHI_UNIT_TESTS=1 python -m unittest + # tests/lava/magma/runtime/test_runtime_service.py + + env_loihi_tests = int(os.environ.get("RUN_LOIHI_UNIT_TESTS")) + run_loihi_tests = False + if env_loihi_tests == 1: + run_loihi_tests = True + + def test_runtime_service_construction(self): + p = NxsdkProtocol() + rs = NxSDKRuntimeService(protocol=p) + self.assertEqual(rs.protocol, p) + self.assertEqual(rs.service_to_runtime, None) + self.assertEqual(rs.service_to_process, []) + self.assertEqual(rs.runtime_to_service, None) + self.assertEqual(rs.process_to_service, []) + + @unittest.skipUnless(run_loihi_tests, "runtime_service_to_process_to_loihi") + def test_runtime_service_loihi_start_run(self): + p = NxsdkProtocol() + rs = NxSDKRuntimeService(protocol=p) + pm = NxSDKTestProcessModel(proc_params={}, + board=rs.get_board()) + + smm = SharedMemoryManager() + smm.start() + runtime_to_service = create_channel(smm, name="runtime_to_service") + service_to_runtime = create_channel(smm, name="service_to_runtime") + service_to_process = [create_channel(smm, name="service_to_process")] + process_to_service = [create_channel(smm, name="process_to_service")] + runtime_to_service.dst_port.start() + service_to_runtime.src_port.start() + + pm.service_to_process = service_to_process[0].dst_port + pm.process_to_service = process_to_service[0].src_port + pm.nc_ports = [] + + rs.num_steps = 1 + pm.start(rs.num_steps) + + rs.runtime_to_service = runtime_to_service.src_port + rs.service_to_runtime = service_to_runtime.dst_port + rs.service_to_process = [service_to_process[0].src_port] + rs.process_to_service = [process_to_service[0].dst_port] + + rs.join() + + pm.test_setup() + pm.test_idx(self) + pm.test_len(self) + pm.test_data(self) + + pm.join() + pm.stop() + smm.shutdown() + + # @unittest.skipUnless(run_loihi_tests, \ + # "runtime_to_runtime_service_to_process \ + # communication") + @unittest.skip("runtime_to_runtime_service_to_process_to_loihi \ + communication") + def test_runtime_service_loihi_channels_start_run(self): + node: Node = Node(HeadNode, []) + exec: Executable = Executable() + exec.node_configs.append(NodeConfig([node])) + runtime: Runtime = Runtime(exec, ActorType.MultiProcessing) + runtime.initialize() + p = NxsdkProtocol() + rs = NxSDKRuntimeService(protocol=p) + pm = NcProcessModel(proc_params={}, + board=rs.get_board()) + + smm = SharedMemoryManager() + smm.start() + runtime_to_service = create_channel(smm, name="runtime_to_service") + service_to_runtime = create_channel(smm, name="service_to_runtime") + service_to_process = [create_channel(smm, name="service_to_process")] + process_to_service = [create_channel(smm, name="process_to_service")] + runtime_to_service.dst_port.start() + service_to_runtime.src_port.start() + + rs.num_steps = 1 + + rs.runtime_to_service = runtime_to_service.src_port + rs.service_to_runtime = service_to_runtime.dst_port + rs.service_to_process = [service_to_process[0].src_port] + rs.process_to_service = [process_to_service[0].dst_port] + + pm.service_to_process = service_to_process[0].dst_port + pm.process_to_service = process_to_service[0].src_port + pm.nc_ports = [] + + pm.start() + + rs.join() + + pm.stop() + smm.shutdown() + + if __name__ == '__main__': unittest.main() diff --git a/tests/lava/proc/conv/test_models.py b/tests/lava/proc/conv/test_models.py index dad135adb..bf5d24fa5 100644 --- a/tests/lava/proc/conv/test_models.py +++ b/tests/lava/proc/conv/test_models.py @@ -1,6 +1,7 @@ # Copyright (C) 2021 Intel Corporation # SPDX-License-Identifier: BSD-3-Clause # See: https://spdx.org/licenses/ +import logging from typing import Dict, List, Tuple, Type, Union import unittest import numpy as np @@ -24,7 +25,7 @@ class ConvRunConfig(RunConfig): """Run configuration selects appropriate Conv ProcessModel based on tag: floating point precision or Loihi bit-accurate fixed point precision""" def __init__(self, select_tag: str = 'fixed_pt'): - super().__init__(custom_sync_domains=None) + super().__init__(custom_sync_domains=None, loglevel=logging.WARNING) self.select_tag = select_tag def select( diff --git a/tests/lava/proc/dense/test_models.py b/tests/lava/proc/dense/test_models.py index d828f18b2..73161f0d2 100644 --- a/tests/lava/proc/dense/test_models.py +++ b/tests/lava/proc/dense/test_models.py @@ -1,6 +1,7 @@ # Copyright (C) 2021 Intel Corporation # SPDX-License-Identifier: BSD-3-Clause # See: https://spdx.org/licenses/ +import logging import unittest import numpy as np @@ -23,7 +24,8 @@ class DenseRunConfig(RunConfig): floating point precision or Loihi bit-accurate fixed-point precision""" def __init__(self, custom_sync_domains=None, select_tag='fixed_pt'): - super().__init__(custom_sync_domains=custom_sync_domains) + super().__init__(custom_sync_domains=custom_sync_domains, + loglevel=logging.WARNING) self.select_tag = select_tag def select(self, proc, proc_models): diff --git a/tests/lava/proc/io/test_dataloader.py b/tests/lava/proc/io/test_dataloader.py index c0c8b6742..5ee42663c 100644 --- a/tests/lava/proc/io/test_dataloader.py +++ b/tests/lava/proc/io/test_dataloader.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: BSD-3-Clause # See: https://spdx.org/licenses/ +import logging from typing import List, Tuple import unittest import numpy as np @@ -27,7 +28,8 @@ class TestRunConfig(RunConfig): """Run configuration selects appropriate ProcessModel based on tag: floating point precision or Loihi bit-accurate fixed point precision""" def __init__(self, select_tag: str = 'fixed_pt') -> None: - super().__init__(custom_sync_domains=None) + super().__init__(custom_sync_domains=None, + loglevel=logging.WARNING) self.select_tag = select_tag def select( diff --git a/tests/lava/proc/lif/test_models.py b/tests/lava/proc/lif/test_models.py index ad64de5d3..060d4694b 100644 --- a/tests/lava/proc/lif/test_models.py +++ b/tests/lava/proc/lif/test_models.py @@ -1,6 +1,7 @@ # Copyright (C) 2021 Intel Corporation # SPDX-License-Identifier: BSD-3-Clause # See: https://spdx.org/licenses/ +import logging import unittest import numpy as np @@ -22,7 +23,8 @@ class LifRunConfig(RunConfig): """Run configuration selects appropriate LIF ProcessModel based on tag: floating point precision or Loihi bit-accurate fixed point precision""" def __init__(self, custom_sync_domains=None, select_tag='fixed_pt'): - super().__init__(custom_sync_domains=custom_sync_domains) + super().__init__(custom_sync_domains=custom_sync_domains, + loglevel=logging.WARNING) self.select_tag = select_tag def select(self, proc, proc_models): From e6f3785ee90e1fa28185de04d2036c469f23e434 Mon Sep 17 00:00:00 2001 From: Marcus G K Williams <168222+mgkwill@users.noreply.github.com> Date: Tue, 15 Feb 2022 14:27:45 -0800 Subject: [PATCH 05/18] Fix unit test, linting Signed-off-by: Marcus G K Williams <168222+mgkwill@users.noreply.github.com> --- src/lava/magma/core/model/nc/model.py | 16 +++++++++++----- src/lava/magma/runtime/runtime_services/enums.py | 5 +++-- .../runtime/runtime_services/runtime_service.py | 1 + tests/lava/magma/runtime/test_runtime_service.py | 4 ++-- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/lava/magma/core/model/nc/model.py b/src/lava/magma/core/model/nc/model.py index c861ee320..9942b9038 100644 --- a/src/lava/magma/core/model/nc/model.py +++ b/src/lava/magma/core/model/nc/model.py @@ -7,7 +7,11 @@ from lava.magma.core.model.model import AbstractProcessModel from lava.magma.core.model.nc.ports import AbstractNcPort, NcVarPort -from lava.magma.compiler.channels.pypychannel import CspSelector, CspSendPort, CspRecvPort +from lava.magma.compiler.channels.pypychannel import ( + CspSelector, + CspSendPort, + CspRecvPort +) from lava.magma.runtime.mgmt_token_enums import ( MGMT_COMMAND, MGMT_RESPONSE, @@ -154,9 +158,10 @@ def run(self): # self.board.run(numSteps=num_steps, aSync=False) self.process_to_service.send(MGMT_RESPONSE.DONE) else: - self.log.error(f"Exception: number of time steps" + self.log.error(f"Exception: number of time steps " f"not greater than 0, cannot invoke " - f"run(num_steps) in {self.__class__}") + f"run(num_steps) in " + f"{self.__class__}") self.process_to_service.send(MGMT_RESPONSE.ERROR) elif enum_equal(cmd, MGMT_COMMAND.GET_DATA): # Handle get/set Var requests from runtime service @@ -168,8 +173,9 @@ def run(self): raise ValueError( f"Wrong Phase Info Received : {cmd}") except Exception as inst: - self.log.error(f"Exception {inst} occured while" - f" running command {cmd} in {self.__class__}") + self.log.error(f"Exception {inst} occured while " + f"running command {cmd} in " + f"{self.__class__}") # Inform runtime service about termination self.process_to_service.send(MGMT_RESPONSE.ERROR) self.join() diff --git a/src/lava/magma/runtime/runtime_services/enums.py b/src/lava/magma/runtime/runtime_services/enums.py index 30c51935b..9c3c5dda5 100644 --- a/src/lava/magma/runtime/runtime_services/enums.py +++ b/src/lava/magma/runtime/runtime_services/enums.py @@ -38,6 +38,7 @@ class NxSdkPhase: HOST_CONCURRENT_EXECUTION = 7 """Concurrent Execution for Host Snip.""" EMBEDDED_USER_CMD = 8 - """Any User Command to execute during embedded execution. (Internal Use Only)""" + """Any User Command to execute during embedded execution. + (Internal Use Only)""" EMBEDDED_REMOTE_MGMT = 9 - """A management phase snip triggered remotely""" \ No newline at end of file + """A management phase snip triggered remotely""" diff --git a/src/lava/magma/runtime/runtime_services/runtime_service.py b/src/lava/magma/runtime/runtime_services/runtime_service.py index 3f8e9e9c1..312db5cc7 100644 --- a/src/lava/magma/runtime/runtime_services/runtime_service.py +++ b/src/lava/magma/runtime/runtime_services/runtime_service.py @@ -26,6 +26,7 @@ class NxBoard(): pass + class PyRuntimeService(AbstractRuntimeService): pass diff --git a/tests/lava/magma/runtime/test_runtime_service.py b/tests/lava/magma/runtime/test_runtime_service.py index c8a6cf96a..fab13238a 100644 --- a/tests/lava/magma/runtime/test_runtime_service.py +++ b/tests/lava/magma/runtime/test_runtime_service.py @@ -176,9 +176,9 @@ class TestNxSDKRuntimeService(unittest.TestCase): # RUN_LOIHI_UNIT_TESTS=1 python -m unittest # tests/lava/magma/runtime/test_runtime_service.py - env_loihi_tests = int(os.environ.get("RUN_LOIHI_UNIT_TESTS")) + env_loihi_tests = os.environ.get("RUN_LOIHI_UNIT_TESTS") run_loihi_tests = False - if env_loihi_tests == 1: + if env_loihi_tests == "1": run_loihi_tests = True def test_runtime_service_construction(self): From 4ea87a819ec5b92628bc3c7e3ef966d52d685c96 Mon Sep 17 00:00:00 2001 From: Marcus G K Williams <168222+mgkwill@users.noreply.github.com> Date: Tue, 15 Feb 2022 14:32:00 -0800 Subject: [PATCH 06/18] Remove comments Signed-off-by: Marcus G K Williams <168222+mgkwill@users.noreply.github.com> --- src/lava/magma/core/model/nc/model.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lava/magma/core/model/nc/model.py b/src/lava/magma/core/model/nc/model.py index 9942b9038..b4f8169e6 100644 --- a/src/lava/magma/core/model/nc/model.py +++ b/src/lava/magma/core/model/nc/model.py @@ -85,7 +85,7 @@ def __setattr__(self, key: str, value: ty.Any): self.__dict__[key] = value if isinstance(value, AbstractNcPort): self.nc_ports.append(value) - # Store all VarPorts for efficient RefPort -> VarPort handling + if isinstance(value, NcVarPort): self.var_ports.append(value) @@ -121,7 +121,7 @@ def start(self): self.process_to_service.start() for p in self.nc_ports: p.start() - # self.board.start() + self.run() def allocate(self): @@ -176,7 +176,7 @@ def run(self): self.log.error(f"Exception {inst} occured while " f"running command {cmd} in " f"{self.__class__}") - # Inform runtime service about termination + self.process_to_service.send(MGMT_RESPONSE.ERROR) self.join() raise inst From b7c4271b91888dc8bbdb89f0273bac7346cdf306 Mon Sep 17 00:00:00 2001 From: Marcus G K Williams <168222+mgkwill@users.noreply.github.com> Date: Tue, 15 Feb 2022 14:37:50 -0800 Subject: [PATCH 07/18] Handle nxsdk import exception Signed-off-by: Marcus G K Williams <168222+mgkwill@users.noreply.github.com> --- .../runtime_services/runtime_service.py | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/lava/magma/runtime/runtime_services/runtime_service.py b/src/lava/magma/runtime/runtime_services/runtime_service.py index 312db5cc7..c4b67befd 100644 --- a/src/lava/magma/runtime/runtime_services/runtime_service.py +++ b/src/lava/magma/runtime/runtime_services/runtime_service.py @@ -288,16 +288,21 @@ def __init__(self, self.board: NxBoard = None self.num_steps = 0 - if loihi_version == LoihiVersion.N3: - from nxsdk.arch.n3b.n3board import N3Board - # # TODO: Need to find good way to set Board Init - self.board = N3Board(1, 1, [2], [[5, 5]]) - elif loihi_version == LoihiVersion.N2: - from nxsdk.arch.n2a.n2board import N2Board # noqa F401 - self.board = N2Board(1, 1, [2], [[5, 5]]) - else: - raise ValueError('Unsupported Loihi version ' - + 'used in board selection') + try: + if loihi_version == LoihiVersion.N3: + from nxsdk.arch.n3b.n3board import N3Board + # # TODO: Need to find good way to set Board Init + self.board = N3Board(1, 1, [2], [[5, 5]]) + elif loihi_version == LoihiVersion.N2: + from nxsdk.arch.n2a.n2board import N2Board # noqa F401 + self.board = N2Board(1, 1, [2], [[5, 5]]) + else: + raise ValueError('Unsupported Loihi version ' + + 'used in board selection') + except(ImportError): + class NxBoard(): + pass + self.board = NxBoard() def _send_pm_cmd(self, cmd: MGMT_COMMAND): for stop_send_port in self.service_to_process: From b29e8eb1cb4d49ff348cbfbd10d636cb8b263266 Mon Sep 17 00:00:00 2001 From: Marcus G K Williams <168222+mgkwill@users.noreply.github.com> Date: Tue, 15 Feb 2022 14:40:36 -0800 Subject: [PATCH 08/18] Fix indentation issue Signed-off-by: Marcus G K Williams <168222+mgkwill@users.noreply.github.com> --- src/lava/magma/runtime/runtime_services/runtime_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lava/magma/runtime/runtime_services/runtime_service.py b/src/lava/magma/runtime/runtime_services/runtime_service.py index c4b67befd..44d408db7 100644 --- a/src/lava/magma/runtime/runtime_services/runtime_service.py +++ b/src/lava/magma/runtime/runtime_services/runtime_service.py @@ -298,7 +298,7 @@ def __init__(self, self.board = N2Board(1, 1, [2], [[5, 5]]) else: raise ValueError('Unsupported Loihi version ' - + 'used in board selection') + + 'used in board selection') except(ImportError): class NxBoard(): pass From 0c793ef4bb086a442cdb19902ae401d4ce89b39b Mon Sep 17 00:00:00 2001 From: Marcus G K Williams <168222+mgkwill@users.noreply.github.com> Date: Tue, 15 Feb 2022 15:03:19 -0800 Subject: [PATCH 09/18] Uncomment board.run in nc proc model Signed-off-by: Marcus G K Williams <168222+mgkwill@users.noreply.github.com> --- src/lava/magma/core/model/nc/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lava/magma/core/model/nc/model.py b/src/lava/magma/core/model/nc/model.py index b4f8169e6..e054d3788 100644 --- a/src/lava/magma/core/model/nc/model.py +++ b/src/lava/magma/core/model/nc/model.py @@ -155,7 +155,7 @@ def run(self): self.process_to_service.send(MGMT_RESPONSE.DONE) num_steps = self.service_to_process.recv() if num_steps > 0: - # self.board.run(numSteps=num_steps, aSync=False) + self.board.run(numSteps=num_steps, aSync=False) self.process_to_service.send(MGMT_RESPONSE.DONE) else: self.log.error(f"Exception: number of time steps " From 5fb1b449127232c6ebf2b6c6a595ccdf6d6a77d9 Mon Sep 17 00:00:00 2001 From: Marcus G K Williams <168222+mgkwill@users.noreply.github.com> Date: Wed, 16 Feb 2022 16:25:52 -0800 Subject: [PATCH 10/18] Address review, rework NxSdkRuntime Service Signed-off-by: Marcus G K Williams <168222+mgkwill@users.noreply.github.com> --- src/lava/magma/compiler/builders/builder.py | 228 +++++++++++++----- .../magma/compiler/builders/interfaces.py | 5 - src/lava/magma/compiler/compiler.py | 23 +- src/lava/magma/core/model/interfaces.py | 5 + src/lava/magma/core/model/model.py | 20 -- src/lava/magma/core/model/nc/model.py | 188 +-------------- .../core/sync/protocols/loihi_protocol.py | 16 +- .../core/sync/protocols/nxsdk_protocol.py | 23 -- src/lava/magma/runtime/runtime.py | 36 ++- .../runtime/runtime_services/interfaces.py | 15 +- .../runtime_services/runtime_service.py | 106 ++++---- .../test_nxsdkruntimeservice_loihi_.py | 111 +++++++++ .../magma/runtime/test_runtime_service.py | 121 ++-------- tests/lava/test_utils/__init__.py | 0 tests/lava/test_utils/utils.py | 15 ++ 15 files changed, 431 insertions(+), 481 deletions(-) delete mode 100644 src/lava/magma/core/sync/protocols/nxsdk_protocol.py create mode 100644 tests/lava/magma/runtime/test_nxsdkruntimeservice_loihi_.py create mode 100644 tests/lava/test_utils/__init__.py create mode 100644 tests/lava/test_utils/utils.py diff --git a/src/lava/magma/compiler/builders/builder.py b/src/lava/magma/compiler/builders/builder.py index 1cdb00e2c..fe5e8633c 100644 --- a/src/lava/magma/compiler/builders/builder.py +++ b/src/lava/magma/compiler/builders/builder.py @@ -13,12 +13,11 @@ from lava.magma.runtime.runtime_services.enums import LoihiVersion from lava.magma.runtime.runtime_services.runtime_service import ( AbstractRuntimeService, - NxSDKRuntimeService + NxSdkRuntimeService ) if ty.TYPE_CHECKING: from lava.magma.core.process.process import AbstractProcess - from lava.magma.core.model.model import AbstractProcessModel from lava.magma.runtime.runtime import Runtime from lava.magma.compiler.channels.pypychannel import CspSendPort, CspRecvPort @@ -27,6 +26,9 @@ AbstractRuntimeServiceBuilder, AbstractChannelBuilder ) +from lava.magma.core.model.model import AbstractProcessModel +from lava.magma.core.model.nc.model import AbstractNcProcessModel +from lava.magma.core.model.nc.type import LavaNcType from lava.magma.core.model.py.model import AbstractPyProcessModel from lava.magma.core.model.py.type import LavaPyType from lava.magma.compiler.utils import VarInitializer, PortInitializer, \ @@ -42,49 +44,21 @@ ChannelType -class PyProcessBuilder(AbstractProcessBuilder): - """A PyProcessBuilder instantiates and initializes a PyProcessModel. - - The compiler creates a PyProcessBuilder for each PyProcessModel. In turn, - the runtime, loads a PyProcessBuilder onto a compute node where it builds - the PyProcessModel and its associated ports. - - In order to build the PyProcessModel, the builder inspects all LavaType - class variables of a PyProcessModel, creates the corresponding data type - with the specified properties, the shape and the initial value provided by - the Lava Var. In addition, the builder creates the required PyPort - instances. Finally, the builder assigns both port and variable - implementations to the PyProcModel. - - Once the PyProcessModel is built, it is the RuntimeService's job to - connect channels to ports and start the process. - - Note: For unit testing it should be possible to build processes locally - instead of on a remote node. For pure atomic unit testing a ProcessModel - locally, PyInPorts and PyOutPorts must be fed manually with data. - """ +class _AbstractProcessBuilder(AbstractProcessBuilder): + """A _AbstractProcessBuilder instantiates and initializes + an AbstractProcessModel but is not meant to be used + directly but inherited from""" def __init__( - self, proc_model: ty.Type[AbstractPyProcessModel], - model_id: int, - proc_params: ty.Dict[str, ty.Any] = None): - super(PyProcessBuilder, self).__init__( + self, proc_model: ty.Type[AbstractProcessModel], + model_id: int): + super(_AbstractProcessBuilder, self).__init__( proc_model=proc_model, model_id=model_id ) - if not issubclass(proc_model, AbstractPyProcessModel): - raise AssertionError("Is not a subclass of AbstractPyProcessModel") - self.vars: ty.Dict[str, VarInitializer] = {} - self.py_ports: ty.Dict[str, PortInitializer] = {} - self.ref_ports: ty.Dict[str, PortInitializer] = {} - self.var_ports: ty.Dict[str, VarPortInitializer] = {} - self.csp_ports: ty.Dict[str, ty.List[AbstractCspPort]] = {} - self.csp_rs_send_port: ty.Dict[str, CspSendPort] = {} - self.csp_rs_recv_port: ty.Dict[str, CspRecvPort] = {} - self.proc_params = proc_params @property - def proc_model(self) -> ty.Type[AbstractPyProcessModel]: + def proc_model(self) -> ty.Type[AbstractProcessModel]: return self._proc_model # ToDo: Perhaps this should even be done in Compiler? @@ -140,6 +114,68 @@ def _check_not_assigned_yet( f"Member '{key}' already found in {m_type}." ) + # ToDo: Also check that Vars are initializable with var.value provided + def set_variables(self, variables: ty.List[VarInitializer]): + """Appends the given list of variables to the ProcessModel. Used by the + compiler to create a ProcessBuilder during the compilation of + ProcessModels. + + Parameters + ---------- + variables : ty.List[VarInitializer] + + """ + self._check_members_exist(variables, "Var") + new_vars = {v.name: v for v in variables} + self._check_not_assigned_yet(self.vars, new_vars.keys(), "vars") + self.vars.update(new_vars) + + +class PyProcessBuilder(_AbstractProcessBuilder): + """A PyProcessBuilder instantiates and initializes a PyProcessModel. + + The compiler creates a PyProcessBuilder for each PyProcessModel. In turn, + the runtime, loads a PyProcessBuilder onto a compute node where it builds + the PyProcessModel and its associated ports. + + In order to build the PyProcessModel, the builder inspects all LavaType + class variables of a PyProcessModel, creates the corresponding data type + with the specified properties, the shape and the initial value provided by + the Lava Var. In addition, the builder creates the required PyPort + instances. Finally, the builder assigns both port and variable + implementations to the PyProcModel. + + Once the PyProcessModel is built, it is the RuntimeService's job to + connect channels to ports and start the process. + + Note: For unit testing it should be possible to build processes locally + instead of on a remote node. For pure atomic unit testing a ProcessModel + locally, PyInPorts and PyOutPorts must be fed manually with data. + """ + + def __init__( + self, proc_model: ty.Type[AbstractPyProcessModel], + model_id: int, + proc_params: ty.Dict[str, ty.Any] = None): + super(PyProcessBuilder, self).__init__( + proc_model=proc_model, + model_id=model_id + ) + if not issubclass(proc_model, AbstractPyProcessModel): + raise AssertionError("Is not a subclass of AbstractPyProcessModel") + self.vars: ty.Dict[str, VarInitializer] = {} + self.py_ports: ty.Dict[str, PortInitializer] = {} + self.ref_ports: ty.Dict[str, PortInitializer] = {} + self.var_ports: ty.Dict[str, VarPortInitializer] = {} + self.csp_ports: ty.Dict[str, ty.List[AbstractCspPort]] = {} + self.csp_rs_send_port: ty.Dict[str, CspSendPort] = {} + self.csp_rs_recv_port: ty.Dict[str, CspRecvPort] = {} + self.proc_params = proc_params + + @property + def proc_model(self) -> ty.Type[AbstractPyProcessModel]: + return self._proc_model + def check_all_vars_and_ports_set(self): """Checks that Vars and PyPorts assigned from Process have a corresponding LavaPyType. @@ -192,23 +228,7 @@ def check_lava_py_types(self): raise AssertionError( f"LavaPyType for '{name}' must be a strict sub-type of " f"PyRefPort in '{self.proc_model.__name__}'." - ) - - # ToDo: Also check that Vars are initializable with var.value provided - def set_variables(self, variables: ty.List[VarInitializer]): - """Appends the given list of variables to the ProcessModel. Used by the - compiler to create a ProcessBuilder during the compilation of - ProcessModels. - - Parameters - ---------- - variables : ty.List[VarInitializer] - - """ - self._check_members_exist(variables, "Var") - new_vars = {v.name: v for v in variables} - self._check_not_assigned_yet(self.vars, new_vars.keys(), "vars") - self.vars.update(new_vars) + ) def set_py_ports(self, py_ports: ty.List[PortInitializer], check=True): """Appends the given list of PyPorts to the ProcessModel. Used by the @@ -434,10 +454,98 @@ class CProcessBuilder(AbstractProcessBuilder): pass -class NcProcessBuilder(AbstractProcessBuilder): - """Neuromorphic Core Process Builder""" +class NcProcessBuilder(_AbstractProcessBuilder): + """NcProcessBuilder instantiates and initializes an NcProcessModel. - pass + The compiler creates a NcProcessBuilder for each NcProcessModel. In turn, + the runtime, loads a NcProcessBuilder onto a compute node where it builds + the NcProcessModel and its associated vars. + + In order to build the NcProcessModel, the builder inspects all LavaType + class variables of a NcProcessModel, creates the corresponding data type + with the specified properties, the shape and the initial value provided by + the Lava Var. Finally, the builder assigns variable + implementations to the NcProcModel.""" + def __init__( + self, proc_model: ty.Type[AbstractNcProcessModel], + model_id: int, + proc_params: ty.Dict[str, ty.Any] = None): + super(NcProcessBuilder, self).__init__( + proc_model=proc_model, + model_id=model_id + ) + if not issubclass(proc_model, AbstractNcProcessModel): + raise AssertionError("Is not a subclass of AbstractNcProcessModel") + self.vars: ty.Dict[str, VarInitializer] = {} + self.proc_params = proc_params + + def _get_lava_type(self, name: str) -> LavaNcType: + return getattr(self.proc_model, name) + + def check_all_vars_set(self): + """Checks that Vars assigned from Process have a + corresponding LavaNcType. + + Raises + ------ + AssertionError + No LavaNcType found in ProcModel + """ + for attr_name in dir(self.proc_model): + attr = getattr(self.proc_model, attr_name) + if isinstance(attr, LavaNcType): + if ( + attr_name not in self.vars + ): + raise AssertionError( + f"No LavaNcType '{attr_name}' found in ProcModel " + f"'{self.proc_model.__name__}'." + ) + + def build(self): + """Builds a NcProcessModel at runtime within Runtime. + + The Compiler initializes the NcProcBuilder with the ProcModel, + VarInitializers and PortInitializers. + + At deployment to a node, the Builder.build(..) gets executed + resulting in the following: + 1. ProcModel gets instantiated + 2. Vars are initialized and assigned to ProcModel + + Returns + ------- + AbstractNcProcessModel + + + Raises + ------ + NotImplementedError + """ + + pm = self.proc_model(self.proc_params) + pm.model_id = self._model_id + + # Initialize Vars + for name, v in self.vars.items(): + # Build variable + lt = self._get_lava_type(name) + if issubclass(lt.cls, np.ndarray): + var = lt.cls(v.shape, lt.d_type) + var[:] = v.value + elif issubclass(lt.cls, (int, float)): + var = v.value + else: + raise NotImplementedError + + # Create dynamic variable attribute on ProcModel + setattr(pm, name, var) + # Create private attribute for variable precision + setattr(pm, "_" + name + "_p", lt.precision) + + pm.var_id_to_var_map[v.var_id] = name + + return pm class RuntimeServiceBuilder(AbstractRuntimeServiceBuilder): @@ -498,9 +606,9 @@ def build(self, Returns ------- A concreate instance of AbstractRuntimeService - [PyRuntimeService or NxSDKRuntimeService] + [PyRuntimeService or NxSdkRuntimeService] """ - if isinstance(self.rs_class, NxSDKRuntimeService): + if isinstance(self.rs_class, NxSdkRuntimeService): rs = self.rs_class(protocol=self.sync_protocol, loihi_version=loihi_version) else: diff --git a/src/lava/magma/compiler/builders/interfaces.py b/src/lava/magma/compiler/builders/interfaces.py index 7151d693c..10b377f54 100644 --- a/src/lava/magma/compiler/builders/interfaces.py +++ b/src/lava/magma/compiler/builders/interfaces.py @@ -6,7 +6,6 @@ import typing as ty -from lava.magma.compiler.channels.interfaces import AbstractCspPort from lava.magma.core.model.model import AbstractProcessModel from lava.magma.core.sync.protocol import AbstractSyncProtocol from lava.magma.runtime.runtime_services.runtime_service import \ @@ -32,10 +31,6 @@ def __init__( self._proc_model = proc_model self._model_id = model_id - @abstractmethod - def set_csp_ports(self, csp_ports: ty.List[AbstractCspPort]): - pass - @property @abstractmethod def proc_model(self) -> "AbstractProcessModel": diff --git a/src/lava/magma/compiler/compiler.py b/src/lava/magma/compiler/compiler.py index 45c867474..7460e7371 100644 --- a/src/lava/magma/compiler/compiler.py +++ b/src/lava/magma/compiler/compiler.py @@ -19,7 +19,7 @@ import lava.magma.compiler.exec_var as exec_var from lava.magma.compiler.builders.builder import ChannelBuilderMp from lava.magma.compiler.builders.builder import PyProcessBuilder, \ - AbstractRuntimeServiceBuilder, RuntimeServiceBuilder, \ + NcProcessBuilder, AbstractRuntimeServiceBuilder, RuntimeServiceBuilder, \ AbstractChannelBuilder, ServiceChannelBuilderMp from lava.magma.compiler.builders.builder import RuntimeChannelBuilderMp from lava.magma.compiler.channels.interfaces import ChannelType @@ -379,8 +379,17 @@ def _compile_proc_models( elif issubclass(pm, AbstractCProcessModel): raise NotImplementedError elif issubclass(pm, AbstractNcProcessModel): - # ToDo: This needs to call NeuroCoreCompiler - raise NotImplementedError + for p in procs: + b = NcProcessBuilder(pm, p.id, p.proc_params) + # Create Var- and PortInitializers from lava.process Vars + # and Ports + v = [VarInitializer(v.name, v.shape, v.init, v.id) + for v in p.vars] + + # Assigns initializers to builder + b.set_variables(v) + b.check_all_vars_set() + nc_builders[p] = b else: raise TypeError("Non-supported ProcessModel type {}" .format(pm)) @@ -771,8 +780,12 @@ def _create_exec_vars(self, elif issubclass(pm, AbstractCProcessModel): ev = exec_var.CExecVar(v, node_id, run_srv_id) elif issubclass(pm, AbstractNcProcessModel): - raise NotImplementedError( - "NcProcessModel not yet supported.") + # ev = exec_var.NcExecVar(chip_id: int + # core_id: int + # register_base_addr: int + # entry_id: int + # field: str) + ev = exec_var.PyExecVar(v, node_id, run_srv_id) else: raise NotImplementedError("Illegal ProcessModel type.") exec_vars[v.id] = ev diff --git a/src/lava/magma/core/model/interfaces.py b/src/lava/magma/core/model/interfaces.py index 4170f89a6..65200b949 100644 --- a/src/lava/magma/core/model/interfaces.py +++ b/src/lava/magma/core/model/interfaces.py @@ -32,3 +32,8 @@ def join(self): """Join all csp ports""" for csp_port in self.csp_ports: csp_port.join() + + +class AbstractNodeGroup: + def alloc(self, *args, **kwargs): + pass diff --git a/src/lava/magma/core/model/model.py b/src/lava/magma/core/model/model.py index 83c6ce07e..56a017f5e 100644 --- a/src/lava/magma/core/model/model.py +++ b/src/lava/magma/core/model/model.py @@ -81,23 +81,3 @@ def __repr__(self): + " has tags " + tags ) - - # ToDo: (AW) Should AbstractProcessModel even have a run() method? What - # if a sub class like AbstractCProcessModel for a LMT does not even need - # a 'run'? - def run(self): - raise NotImplementedError("'run' method is not implemented.") - - # ToDo: What does this function do here? The AbstractProcModel can't - # depend on one specific Python implementation of ports/channels. It can - # probably not even have a start function. Because for a CProcModel - # running on LMT there might not even be Python start function to call. - # Starting the ports is likely the RuntimeService's or Builder's job - # which is what makes a process run on a certain compute resource. - def start(self): - # Store the list of csp_ports. Start them here. - raise NotImplementedError - # TODO: Iterate over all inports and outports of the process - # and start them - - self.run() diff --git a/src/lava/magma/core/model/nc/model.py b/src/lava/magma/core/model/nc/model.py index e054d3788..babd23ce7 100644 --- a/src/lava/magma/core/model/nc/model.py +++ b/src/lava/magma/core/model/nc/model.py @@ -2,34 +2,10 @@ # SPDX-License-Identifier: BSD-3-Clause from abc import ABC, abstractmethod import logging -import numpy as np import typing as ty +from lava.magma.core.model.interfaces import AbstractNodeGroup from lava.magma.core.model.model import AbstractProcessModel -from lava.magma.core.model.nc.ports import AbstractNcPort, NcVarPort -from lava.magma.compiler.channels.pypychannel import ( - CspSelector, - CspSendPort, - CspRecvPort -) -from lava.magma.runtime.mgmt_token_enums import ( - MGMT_COMMAND, - MGMT_RESPONSE, - enum_equal, - enum_to_np -) - -try: - from nxsdk.arch.base.nxboard import NxBoard -except(ImportError): - class NxBoard(): - pass - - -# ToDo: Move somewhere else. Just created for typing -class AbstractNodeGroup: - def alloc(self, *args, **kwargs): - pass class Net(ABC): @@ -61,7 +37,7 @@ def connect(self, from_thing, to_thing): class AbstractNcProcessModel(AbstractProcessModel, ABC): - """Abstract interface for a NeuroCore ProcessModels + """Abstract interface for NeuroCore ProcessModels Example for how variables and ports might be initialized: a_in: NcInPort = LavaNcType(NcInPort.VEC_DENSE, float) @@ -75,179 +51,23 @@ def __init__(self, proc_params: ty.Dict[str, ty.Any], loglevel=logging.WARNING) -> None: super().__init__(proc_params, loglevel=loglevel) self.model_id: ty.Optional[int] = None - self.service_to_process: ty.Optional[CspRecvPort] = None - self.process_to_service: ty.Optional[CspSendPort] = None - self.nc_ports: ty.List[AbstractNcPort] = [] - self.var_ports: ty.List[NcVarPort] = [] - self.var_id_to_var_map: ty.Dict[int, ty.Any] = {} - - def __setattr__(self, key: str, value: ty.Any): - self.__dict__[key] = value - if isinstance(value, AbstractNcPort): - self.nc_ports.append(value) - - if isinstance(value, NcVarPort): - self.var_ports.append(value) - - @abstractmethod - def run(self): - pass - - def join(self): - self.service_to_process.join() - self.process_to_service.join() - for p in self.nc_ports: - p.join() @abstractmethod def allocate(self, net: Net): """Allocates resources required by Process via Net provided by compiler. - Note: This should work as before. """ pass class NcProcessModel(AbstractNcProcessModel): def __init__(self, proc_params: ty.Dict[str, ty.Any], - board: ty.Type[NxBoard], loglevel=logging.WARNING): super(AbstractNcProcessModel, self).__init__(proc_params, loglevel=loglevel) - self.board = board - - def start(self): - self.service_to_process.start() - self.process_to_service.start() - for p in self.nc_ports: - p.start() - self.run() - - def allocate(self): + def allocate(self, net: Net): pass def run(self): - """Retrieves commands from the runtime service calls - their corresponding methods of the ProcessModels. The phase - is retrieved from runtime service (service_to_process). After - calling the method of a phase of all ProcessModels the runtime - service is informed about completion. The loop ends when the - STOP command is received.""" - selector = CspSelector() - channel_actions = [(self.service_to_process, lambda: 'cmd')] - action = 'cmd' - while True: - if action == 'cmd': - cmd = self.service_to_process.recv() - if enum_equal(cmd, MGMT_COMMAND.STOP): - self.board.stop() - self.process_to_service.send(MGMT_RESPONSE.TERMINATED) - self.join() - return - if enum_equal(cmd, MGMT_COMMAND.PAUSE): - self.board.pause() - self.process_to_service.send(MGMT_RESPONSE.PAUSED) - self.join() - return - try: - if enum_equal(cmd, MGMT_COMMAND.RUN): - self.process_to_service.send(MGMT_RESPONSE.DONE) - num_steps = self.service_to_process.recv() - if num_steps > 0: - self.board.run(numSteps=num_steps, aSync=False) - self.process_to_service.send(MGMT_RESPONSE.DONE) - else: - self.log.error(f"Exception: number of time steps " - f"not greater than 0, cannot invoke " - f"run(num_steps) in " - f"{self.__class__}") - self.process_to_service.send(MGMT_RESPONSE.ERROR) - elif enum_equal(cmd, MGMT_COMMAND.GET_DATA): - # Handle get/set Var requests from runtime service - self._handle_get_var() - elif enum_equal(cmd, MGMT_COMMAND.SET_DATA): - # Handle get/set Var requests from runtime service - self._handle_set_var() - else: - raise ValueError( - f"Wrong Phase Info Received : {cmd}") - except Exception as inst: - self.log.error(f"Exception {inst} occured while " - f"running command {cmd} in " - f"{self.__class__}") - - self.process_to_service.send(MGMT_RESPONSE.ERROR) - self.join() - raise inst - else: - # Handle VarPort requests from RefPorts - self._handle_var_port(action) - - for var_port in self.var_ports: - for csp_port in var_port.csp_ports: - if isinstance(csp_port, CspRecvPort): - channel_actions.append( - (csp_port, lambda: var_port)) - action = selector.select(*channel_actions) - - def _handle_get_var(self): - """Handles the get Var command from runtime service.""" - # 1. Receive Var ID and retrieve the Var - var_id = int(self.service_to_process.recv()[0].item()) - var_name = self.var_id_to_var_map[var_id] - var = getattr(self, var_name) - - # Here get the var from Loihi - - # 2. Send Var data - data_port = self.process_to_service - # Header corresponds to number of values - # Data is either send once (for int) or one by one (array) - if isinstance(var, int) or isinstance(var, np.integer): - data_port.send(enum_to_np(1)) - data_port.send(enum_to_np(var)) - elif isinstance(var, np.ndarray): - # FIXME: send a whole vector (also runtime_service.py) - var_iter = np.nditer(var) - num_items: np.integer = np.prod(var.shape) - data_port.send(enum_to_np(num_items)) - for value in var_iter: - data_port.send(enum_to_np(value, np.float64)) - - def _handle_set_var(self): - """Handles the set Var command from runtime service.""" - # 1. Receive Var ID and retrieve the Var - var_id = int(self.service_to_process.recv()[0].item()) - var_name = self.var_id_to_var_map[var_id] - var = getattr(self, var_name) - - # 2. Receive Var data - data_port = self.service_to_process - if isinstance(var, int) or isinstance(var, np.integer): - # First item is number of items (1) - not needed - data_port.recv() - # Data to set - buffer = data_port.recv()[0] - if isinstance(var, int): - setattr(self, var_name, buffer.item()) - else: - setattr(self, var_name, buffer.astype(var.dtype)) - elif isinstance(var, np.ndarray): - # First item is number of items - num_items = data_port.recv()[0] - var_iter = np.nditer(var, op_flags=['readwrite']) - # Set data one by one - for i in var_iter: - if num_items == 0: - break - num_items -= 1 - i[...] = data_port.recv()[0] - else: - raise RuntimeError("Unsupported type") - - # Here set var in Loihi? - - def _handle_var_port(self, var_port): - """Handles read/write requests on the given VarPort.""" - var_port.service() + pass diff --git a/src/lava/magma/core/sync/protocols/loihi_protocol.py b/src/lava/magma/core/sync/protocols/loihi_protocol.py index 00469c724..cf3017a10 100644 --- a/src/lava/magma/core/sync/protocols/loihi_protocol.py +++ b/src/lava/magma/core/sync/protocols/loihi_protocol.py @@ -4,12 +4,18 @@ from collections import namedtuple from dataclasses import dataclass -from lava.magma.core.resources import CPU, NeuroCore +from lava.magma.core.resources import ( + CPU, + NeuroCore, + Loihi1NeuroCore, + Loihi2NeuroCore +) from lava.magma.core.sync.protocol import AbstractSyncProtocol from lava.magma.runtime.mgmt_token_enums import enum_to_np from lava.magma.runtime.runtime_services.runtime_service import ( LoihiPyRuntimeService, LoihiCRuntimeService, + NxSdkRuntimeService ) Proc_Function_With_Guard = namedtuple("Proc_Function_With_Guard", "guard func") @@ -26,7 +32,8 @@ class Phase: @dataclass class LoihiProtocol(AbstractSyncProtocol): # The phases of Loihi protocol - phases = [Phase.SPK, Phase.PRE_MGMT, Phase.LRN, Phase.POST_MGMT, Phase.HOST] + phases = [Phase.SPK, Phase.PRE_MGMT, + Phase.LRN, Phase.POST_MGMT, Phase.HOST] # Methods that processes implementing protocol may provide proc_functions = [ Proc_Function_With_Guard("pre_guard", "run_pre_mgmt"), @@ -42,4 +49,7 @@ class LoihiProtocol(AbstractSyncProtocol): @property def runtime_service(self): - return {CPU: LoihiPyRuntimeService, NeuroCore: LoihiCRuntimeService} + return {CPU: LoihiPyRuntimeService, + NeuroCore: LoihiCRuntimeService, + Loihi1NeuroCore: NxSdkRuntimeService, + Loihi2NeuroCore: NxSdkRuntimeService} diff --git a/src/lava/magma/core/sync/protocols/nxsdk_protocol.py b/src/lava/magma/core/sync/protocols/nxsdk_protocol.py deleted file mode 100644 index 61c883b43..000000000 --- a/src/lava/magma/core/sync/protocols/nxsdk_protocol.py +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright (C) 2022 Intel Corporation -# SPDX-License-Identifier: BSD-3-Clause -# See: https://spdx.org/licenses/ -from dataclasses import dataclass - -from lava.magma.core.resources import CPU, NeuroCore -from lava.magma.core.sync.protocol import AbstractSyncProtocol -from lava.magma.runtime.runtime_services.runtime_service import ( - PyRuntimeService, - NxSDKRuntimeService, -) - - -@dataclass -class NxsdkProtocol(AbstractSyncProtocol): - """Synchronizer class that implement NxsdkProtocol - protocol using NxCore for its domain. - """ - runtime_service = {CPU: PyRuntimeService, NeuroCore: NxSDKRuntimeService} - - @property - def runtime_service(self): - return self.runtime_service diff --git a/src/lava/magma/runtime/runtime.py b/src/lava/magma/runtime/runtime.py index 23f6bbc19..4c5649098 100644 --- a/src/lava/magma/runtime/runtime.py +++ b/src/lava/magma/runtime/runtime.py @@ -27,7 +27,7 @@ from lava.magma.core.process.process import AbstractProcess from lava.magma.compiler.builders.builder import AbstractProcessBuilder, \ RuntimeChannelBuilderMp, ServiceChannelBuilderMp, \ - RuntimeServiceBuilder + RuntimeServiceBuilder, PyProcessBuilder from lava.magma.compiler.channels.interfaces import Channel from lava.magma.core.resources import HeadNode from lava.magma.core.run_conditions import RunSteps, RunContinuous @@ -76,17 +76,8 @@ def __del__(self): def initialize(self): """Initializes the runtime""" - # Right now assume there is only 1 node config - node_configs: ty.List[NodeConfig] = self._executable.node_configs - if len(node_configs) != 1: - raise AssertionError - - node_config: NodeConfig = node_configs[0] + node_config: NodeConfig = self.node_cfg[0] - # Right now assume there is only 1 node in node_config with resource - # type CPU - if len(node_config) != 1: - raise AssertionError if node_config[0].node_type != HeadNode: raise AssertionError @@ -104,11 +95,10 @@ def _start_ports(self): for port in self.service_to_runtime: port.start() - # ToDo: (AW) Hack: This currently just returns the one and only NodeCfg @property - def node_cfg(self) -> NodeConfig: + def node_cfg(self) -> ty.List[NodeConfig]: """Returns the selected NodeCfg.""" - return self._executable.node_configs[0] + return self._executable.node_configs def _build_message_infrastructure(self): self._messaging_infrastructure = MessageInfrastructureFactory.create( @@ -158,19 +148,25 @@ def _build_sync_channels(self): self.service_to_runtime.append(channel.dst_port) elif isinstance(sync_channel_builder, ServiceChannelBuilderMp): if isinstance(sync_channel_builder.src_process, - RuntimeServiceBuilder): + RuntimeServiceBuilder) \ + and isinstance(sync_channel_builder. + dst_process, + PyProcessBuilder): sync_channel_builder.src_process.set_csp_proc_ports( [channel.src_port]) self._get_process_builder_for_process( - sync_channel_builder.dst_process).set_rs_csp_ports( - [channel.dst_port]) - else: + sync_channel_builder.dst_process) \ + .set_rs_csp_ports([channel.dst_port]) + elif isinstance(sync_channel_builder. + dst_process, + PyProcessBuilder): sync_channel_builder.dst_process.set_csp_proc_ports( [channel.dst_port]) self._get_process_builder_for_process( - sync_channel_builder.src_process).set_rs_csp_ports( - [channel.src_port]) + sync_channel_builder.src_process) \ + .set_rs_csp_ports([channel.src_port]) else: + print(sync_channel_builder.dst_process.__class__.__name__) raise ValueError("Unexpected type of Sync Channel Builder") # ToDo: (AW) Why not pass the builder as an argument to the mp.Process diff --git a/src/lava/magma/runtime/runtime_services/interfaces.py b/src/lava/magma/runtime/runtime_services/interfaces.py index 6042ed8d0..cc86542a0 100644 --- a/src/lava/magma/runtime/runtime_services/interfaces.py +++ b/src/lava/magma/runtime/runtime_services/interfaces.py @@ -4,7 +4,10 @@ import typing as ty from abc import ABC, abstractmethod -from lava.magma.compiler.channels.pypychannel import CspRecvPort, CspSendPort +from lava.magma.compiler.channels.pypychannel import ( + CspRecvPort, + CspSendPort +) from lava.magma.core.sync.protocol import AbstractSyncProtocol @@ -19,9 +22,6 @@ def __init__(self, protocol): self.model_ids: ty.List[int] = [] - self.service_to_process: ty.Iterable[CspSendPort] = [] - self.process_to_service: ty.Iterable[CspRecvPort] = [] - def __repr__(self): return f"Synchronizer : {self.__class__}, \ RuntimeServiceId : {self.runtime_service_id}, \ @@ -30,9 +30,6 @@ def __repr__(self): def start(self): self.runtime_to_service.start() self.service_to_runtime.start() - for i in range(len(self.service_to_process)): - self.service_to_process[i].start() - self.process_to_service[i].start() self.run() @abstractmethod @@ -42,7 +39,3 @@ def run(self): def join(self): self.runtime_to_service.join() self.service_to_runtime.join() - - for i in range(len(self.service_to_process)): - self.service_to_process[i].join() - self.process_to_service[i].join() diff --git a/src/lava/magma/runtime/runtime_services/runtime_service.py b/src/lava/magma/runtime/runtime_services/runtime_service.py index 44d408db7..bd31c32f1 100644 --- a/src/lava/magma/runtime/runtime_services/runtime_service.py +++ b/src/lava/magma/runtime/runtime_services/runtime_service.py @@ -1,11 +1,16 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: BSD-3-Clause # See: https://spdx.org/licenses/ +import logging import typing as ty import numpy as np -from lava.magma.compiler.channels.pypychannel import CspSelector +from lava.magma.compiler.channels.pypychannel import ( + CspSelector, + CspRecvPort, + CspSendPort +) from lava.magma.core.sync.protocol import AbstractSyncProtocol from lava.magma.runtime.mgmt_token_enums import ( enum_to_np, @@ -28,7 +33,32 @@ class NxBoard(): class PyRuntimeService(AbstractRuntimeService): - pass + def __init__(self, + protocol: ty.Type[AbstractSyncProtocol], + loglevel=logging.WARNING): + self.log = logging.getLogger(__name__) + self.log.setLevel(loglevel) + super(PyRuntimeService, self).__init__( + protocol=protocol + ) + self.service_to_process: ty.Iterable[CspSendPort] = [] + self.process_to_service: ty.Iterable[CspRecvPort] = [] + + def start(self): + self.runtime_to_service.start() + self.service_to_runtime.start() + for i in range(len(self.service_to_process)): + self.service_to_process[i].start() + self.process_to_service[i].start() + self.run() + + def join(self): + self.runtime_to_service.join() + self.service_to_runtime.join() + + for i in range(len(self.service_to_process)): + self.service_to_process[i].join() + self.process_to_service[i].join() class CRuntimeService(AbstractRuntimeService): @@ -144,7 +174,8 @@ def run(self): rsps = self._get_pm_resp() for rsp in rsps: if not enum_equal(rsp, MGMT_RESPONSE.TERMINATED): - raise ValueError(f"Wrong Response Received : {rsp}") + raise ValueError( + f"Wrong Response Received : {rsp}") # Inform the runtime about successful termination self.service_to_runtime.send(MGMT_RESPONSE.TERMINATED) self.join() @@ -155,7 +186,8 @@ def run(self): rsps = self._get_pm_resp() for rsp in rsps: if not enum_equal(rsp, MGMT_RESPONSE.PAUSED): - raise ValueError(f"Wrong Response Received : {rsp}") + raise ValueError( + f"Wrong Response Received : {rsp}") # Inform the runtime about successful pausing self.service_to_runtime.send(MGMT_RESPONSE.PAUSED) break @@ -264,25 +296,25 @@ def run(self): self.service_to_runtime.send(MGMT_RESPONSE.DONE) -class NxSDKRuntimeService(NcRuntimeService): - """NxSDK RuntimeService that implements NxCore SyncProtocol. - - The NxSDKRuntimeService is a wrapper around NxCore that allows - interaction with Loihi through NxCore API and GRPC communication - channels to Loihi. +class NxSdkRuntimeService(NcRuntimeService): + """The NxSdkRuntimeService uses NxCore to coordinate + communication and executinon on Loihi within a SyncDomain. Parameters ---------- - protocol: ty.Type[LoihiProtocol] - Communication protocol used by NxSDKRuntimeService + protocol: ty.Type[AbstractSyncProtocol] + Communication protocol used by NxSdkRuntimeService loihi_version: LoihiVersion Version of Loihi Chip to use, N2 or N3 """ def __init__(self, protocol: ty.Type[AbstractSyncProtocol], - loihi_version: LoihiVersion = LoihiVersion.N3,): - super(NxSDKRuntimeService, self).__init__( + loihi_version: LoihiVersion = LoihiVersion.N3, + loglevel=logging.WARNING): + self.log = logging.getLogger(__name__) + self.log.setLevel(loglevel) + super(NxSdkRuntimeService, self).__init__( protocol=protocol ) self.board: NxBoard = None @@ -294,7 +326,7 @@ def __init__(self, # # TODO: Need to find good way to set Board Init self.board = N3Board(1, 1, [2], [[5, 5]]) elif loihi_version == LoihiVersion.N2: - from nxsdk.arch.n2a.n2board import N2Board # noqa F401 + from nxsdk.arch.n2a.n2board import N2Board # noqa F401 self.board = N2Board(1, 1, [2], [[5, 5]]) else: raise ValueError('Unsupported Loihi version ' @@ -304,20 +336,6 @@ class NxBoard(): pass self.board = NxBoard() - def _send_pm_cmd(self, cmd: MGMT_COMMAND): - for stop_send_port in self.service_to_process: - stop_send_port.send(cmd) - - def _send_pm_rn(self, run_number: int): - for stop_send_port in self.service_to_process: - stop_send_port.send(run_number) - - def _get_pm_resp(self) -> ty.Iterable[MGMT_RESPONSE]: - rcv_msgs = [] - for ptos_recv_port in self.process_to_service: - rcv_msgs.append(ptos_recv_port.recv()) - return rcv_msgs - def run(self): self.num_steps = self.runtime_to_service.recv() self.service_to_runtime.send(MGMT_RESPONSE.DONE) @@ -330,40 +348,20 @@ def run(self): if action == 'cmd': command = self.runtime_to_service.recv() if enum_equal(command, MGMT_COMMAND.STOP): - self._send_pm_cmd(command) - rsps = self._get_pm_resp() - for rsp in rsps: - if not enum_equal(rsp, MGMT_RESPONSE.TERMINATED): - raise ValueError(f"Wrong Response Received : {rsp}") + self.board.stop() + self.service_to_runtime.send(MGMT_RESPONSE.TERMINATED) self.join() return elif enum_equal(command, MGMT_COMMAND.PAUSE): - self._send_pm_cmd(command) - rsps = self._get_pm_resp() - for rsp in rsps: - if not enum_equal(rsp, MGMT_RESPONSE.PAUSED): - raise ValueError(f"Wrong Response Received : {rsp}") + self.board.pause() self.service_to_runtime.send(MGMT_RESPONSE.PAUSED) break elif enum_equal(command, MGMT_COMMAND.RUN): - self._send_pm_cmd(MGMT_COMMAND.RUN) - rsps = self._get_pm_resp() - self._send_pm_cmd(self.num_steps) - rsps = rsps + self._get_pm_resp() - for rsp in rsps: - if not enum_equal(rsp, MGMT_RESPONSE.DONE): - raise ValueError(f"Wrong Response Received : {rsp}") + self.board.run(numSteps=self.num_steps, aSync=False) + self.service_to_runtime.send(MGMT_RESPONSE.DONE) else: self.service_to_runtime.send(MGMT_RESPONSE.ERROR) - - self._send_pm_cmd(MGMT_COMMAND.STOP) return - - def get_board(self) -> NxBoard: - if self.board is not None: - return self.board - else: - AssertionError("Cannot return board, self.board is None") diff --git a/tests/lava/magma/runtime/test_nxsdkruntimeservice_loihi_.py b/tests/lava/magma/runtime/test_nxsdkruntimeservice_loihi_.py new file mode 100644 index 000000000..8a966f699 --- /dev/null +++ b/tests/lava/magma/runtime/test_nxsdkruntimeservice_loihi_.py @@ -0,0 +1,111 @@ +import logging +import unittest + +from tests.lava.test_utils.utils import Utils + +from lava.magma.core.decorator import implements, requires +from lava.magma.core.model.nc.type import LavaNcType +from lava.magma.core.process.process import AbstractProcess +from lava.magma.core.process.variable import Var +from lava.magma.core.resources import Loihi2NeuroCore, Loihi1NeuroCore +from lava.magma.core.run_conditions import RunSteps +from lava.magma.core.run_configs import RunConfig +from lava.magma.core.sync.domain import SyncDomain +from lava.magma.core.sync.protocols.loihi_protocol import LoihiProtocol +from lava.magma.core.model.nc.model import NcProcessModel + + +class SimpleProcess(AbstractProcess): + def __init__(self, **kwargs): + super().__init__(loglevel=logging.WARNING, **kwargs) + shape = kwargs["shape"] + self.u = Var(shape=shape, init=0) + self.v = Var(shape=shape, init=0) + + +class SimpleRunConfig(RunConfig): + def __init__(self, **kwargs): + sync_domains = kwargs.pop("sync_domains") + super().__init__(custom_sync_domains=sync_domains, + loglevel=logging.WARNING) + self.model = None + if "model" in kwargs: + self.model = kwargs.pop("model") + + def select(self, process, proc_models): + if self.model is not None: + if self.model == "sub" and isinstance(process, SimpleProcess): + return proc_models[1] + + return proc_models[0] + + +@implements(proc=SimpleProcess, protocol=LoihiProtocol) +@requires(Loihi1NeuroCore) +class SimpleProcessModel1(NcProcessModel): + u = LavaNcType(int, int) + v = LavaNcType(int, int) + + def post_guard(self): + return False + + def pre_guard(self): + return False + + def lrn_guard(self): + return False + + +@implements(proc=SimpleProcess, protocol=LoihiProtocol) +@requires(Loihi2NeuroCore) +class SimpleProcessModel2(NcProcessModel): + u = LavaNcType(int, int) + v = LavaNcType(int, int) + + def post_guard(self): + return False + + def pre_guard(self): + return False + + def lrn_guard(self): + return False + + +@unittest.skip +class TestProcessLoihi1(unittest.TestCase): + # Run Loihi Tests using example command below: + # + # SLURM=1 LOIHI_GEN=N3B3 BOARD=ncl-og-05 PARTITION=oheogulch + # RUN_LOIHI_TESTS=1 python -m unittest + # tests/lava/magma/runtime/test_nxsdkruntimeservice_loihi_.py + + run_loihi_tests: bool = Utils.get_env_test_setting("RUN_LOIHI_TESTS") + + @unittest.skipUnless(run_loihi_tests, + "runtime_to_runtimeservice_to_nxcore_to_loihi") + def test_synchronization_single_process_model(self): + process = SimpleProcess(shape=(2, 2)) + simple_sync_domain = SyncDomain("simple", LoihiProtocol(), [process]) + run_config = SimpleRunConfig(sync_domains=[simple_sync_domain]) + process.run(condition=RunSteps(num_steps=10), run_cfg=run_config) + process.run(condition=RunSteps(num_steps=5), run_cfg=run_config) + process.stop() + + +class TestProcessLoihi2(unittest.TestCase): + run_loihi_tests: bool = Utils.get_env_test_setting("RUN_LOIHI_TESTS") + + @unittest.skipUnless(run_loihi_tests, + "runtime_to_runtimeservice_to_nxcore_to_loihi") + def test_synchronization_single_process_model(self): + process = SimpleProcess(shape=(2, 2)) + simple_sync_domain = SyncDomain("simple", LoihiProtocol(), [process]) + run_config = SimpleRunConfig(sync_domains=[simple_sync_domain]) + process.run(condition=RunSteps(num_steps=10), run_cfg=run_config) + process.run(condition=RunSteps(num_steps=5), run_cfg=run_config) + process.stop() + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/lava/magma/runtime/test_runtime_service.py b/tests/lava/magma/runtime/test_runtime_service.py index fab13238a..1a6e4c55e 100644 --- a/tests/lava/magma/runtime/test_runtime_service.py +++ b/tests/lava/magma/runtime/test_runtime_service.py @@ -1,26 +1,20 @@ -import logging -import os import random import unittest from multiprocessing.managers import SharedMemoryManager import numpy as np +from tests.lava.test_utils.utils import Utils + from lava.magma.compiler.channels.pypychannel import PyPyChannel -from lava.magma.compiler.executable import Executable -from lava.magma.core.resources import HeadNode -from lava.magma.compiler.node import Node, NodeConfig from lava.magma.core.decorator import implements -from lava.magma.core.model.nc.model import NcProcessModel from lava.magma.core.model.py.model import AbstractPyProcessModel -from lava.magma.core.process.message_interface_enum import ActorType from lava.magma.core.process.process import AbstractProcess from lava.magma.core.sync.protocol import AbstractSyncProtocol -from lava.magma.core.sync.protocols.nxsdk_protocol import NxsdkProtocol -from lava.magma.runtime.runtime import Runtime +from lava.magma.core.sync.protocols.loihi_protocol import LoihiProtocol from lava.magma.runtime.runtime_services.runtime_service import ( PyRuntimeService, - NxSDKRuntimeService + NxSdkRuntimeService ) @@ -96,23 +90,16 @@ def test_runtime_service_start_run(self): smm.shutdown() -class NxSDKTestProcess(AbstractProcess): - def __init__(self, **kwargs): - super().__init__(loglevel=logging.DEBUG, **kwargs) - - -@implements(proc=NxSDKTestProcess, protocol=NxsdkProtocol) -class NxSDKTestProcessModel(NcProcessModel): - def start(self, num_steps): - self.service_to_process.start() - self.process_to_service.start() - for p in self.nc_ports: - p.start() - self.board.run(num_steps) +class NxSdkTestRuntimeService(NxSdkRuntimeService): + def run(self): + self.board.run(numSteps=self.num_steps, aSync=False) def stop(self): self.board.stop() + def pause(self): + self.board.pause() + def test_setup(self): self.nxCore = self.board.nxChips[0].nxCores[0] self.axon_map = self.nxCore.axonMap @@ -169,107 +156,49 @@ def test_data(self, test_case: unittest.TestCase): test_case.assertEqual(self.axon_map[1].data, value) -class TestNxSDKRuntimeService(unittest.TestCase): +class TestNxSdkRuntimeService(unittest.TestCase): # Run Loihi Tests using example command below: # # SLURM=1 LOIHI_GEN=N3B3 BOARD=ncl-og-05 PARTITION=oheogulch - # RUN_LOIHI_UNIT_TESTS=1 python -m unittest + # RUN_LOIHI_TESTS=1 python -m unittest # tests/lava/magma/runtime/test_runtime_service.py - env_loihi_tests = os.environ.get("RUN_LOIHI_UNIT_TESTS") - run_loihi_tests = False - if env_loihi_tests == "1": - run_loihi_tests = True + run_loihi_tests: bool = Utils.get_env_test_setting("RUN_LOIHI_TESTS") def test_runtime_service_construction(self): - p = NxsdkProtocol() - rs = NxSDKRuntimeService(protocol=p) + p = LoihiProtocol() + rs = NxSdkTestRuntimeService(protocol=p) self.assertEqual(rs.protocol, p) self.assertEqual(rs.service_to_runtime, None) - self.assertEqual(rs.service_to_process, []) self.assertEqual(rs.runtime_to_service, None) - self.assertEqual(rs.process_to_service, []) - @unittest.skipUnless(run_loihi_tests, "runtime_service_to_process_to_loihi") + @unittest.skipUnless(run_loihi_tests, "runtimeservice_to_nxcore_to_loihi") def test_runtime_service_loihi_start_run(self): - p = NxsdkProtocol() - rs = NxSDKRuntimeService(protocol=p) - pm = NxSDKTestProcessModel(proc_params={}, - board=rs.get_board()) + p = LoihiProtocol() + rs = NxSdkTestRuntimeService(protocol=p) smm = SharedMemoryManager() smm.start() runtime_to_service = create_channel(smm, name="runtime_to_service") service_to_runtime = create_channel(smm, name="service_to_runtime") - service_to_process = [create_channel(smm, name="service_to_process")] - process_to_service = [create_channel(smm, name="process_to_service")] runtime_to_service.dst_port.start() service_to_runtime.src_port.start() - pm.service_to_process = service_to_process[0].dst_port - pm.process_to_service = process_to_service[0].src_port - pm.nc_ports = [] - - rs.num_steps = 1 - pm.start(rs.num_steps) + rs.num_steps = 10 rs.runtime_to_service = runtime_to_service.src_port rs.service_to_runtime = service_to_runtime.dst_port - rs.service_to_process = [service_to_process[0].src_port] - rs.process_to_service = [process_to_service[0].dst_port] rs.join() - pm.test_setup() - pm.test_idx(self) - pm.test_len(self) - pm.test_data(self) - - pm.join() - pm.stop() - smm.shutdown() - - # @unittest.skipUnless(run_loihi_tests, \ - # "runtime_to_runtime_service_to_process \ - # communication") - @unittest.skip("runtime_to_runtime_service_to_process_to_loihi \ - communication") - def test_runtime_service_loihi_channels_start_run(self): - node: Node = Node(HeadNode, []) - exec: Executable = Executable() - exec.node_configs.append(NodeConfig([node])) - runtime: Runtime = Runtime(exec, ActorType.MultiProcessing) - runtime.initialize() - p = NxsdkProtocol() - rs = NxSDKRuntimeService(protocol=p) - pm = NcProcessModel(proc_params={}, - board=rs.get_board()) + rs.test_setup() + rs.test_idx(self) + rs.test_len(self) + rs.test_data(self) - smm = SharedMemoryManager() - smm.start() - runtime_to_service = create_channel(smm, name="runtime_to_service") - service_to_runtime = create_channel(smm, name="service_to_runtime") - service_to_process = [create_channel(smm, name="service_to_process")] - process_to_service = [create_channel(smm, name="process_to_service")] - runtime_to_service.dst_port.start() - service_to_runtime.src_port.start() - - rs.num_steps = 1 - - rs.runtime_to_service = runtime_to_service.src_port - rs.service_to_runtime = service_to_runtime.dst_port - rs.service_to_process = [service_to_process[0].src_port] - rs.process_to_service = [process_to_service[0].dst_port] - - pm.service_to_process = service_to_process[0].dst_port - pm.process_to_service = process_to_service[0].src_port - pm.nc_ports = [] - - pm.start() - - rs.join() + rs.run() + rs.stop() - pm.stop() smm.shutdown() diff --git a/tests/lava/test_utils/__init__.py b/tests/lava/test_utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/lava/test_utils/utils.py b/tests/lava/test_utils/utils.py new file mode 100644 index 000000000..14bfdb629 --- /dev/null +++ b/tests/lava/test_utils/utils.py @@ -0,0 +1,15 @@ +# Copyright (C) 2021 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause +# See: https://spdx.org/licenses/ +import os + + +class Utils(): + + @staticmethod + def get_env_test_setting(env: str): + env_test_setting = os.environ.get(env) + test_setting = False + if env_test_setting == "1": + test_setting = True + return test_setting From 52384667d5367ccb0e720828dca7560562aad150 Mon Sep 17 00:00:00 2001 From: Marcus G K Williams Date: Wed, 23 Feb 2022 16:13:10 -0800 Subject: [PATCH 11/18] Fix unit tests, merge with main Signed-off-by: Marcus G K Williams --- src/lava/magma/compiler/builders/builder.py | 38 +- src/lava/magma/compiler/compiler.py | 76 ++- src/lava/magma/core/model/model.py | 6 +- src/lava/magma/core/model/nc/model.py | 11 +- src/lava/magma/core/model/py/model.py | 517 +++++++++++++----- src/lava/magma/core/process/process.py | 25 +- src/lava/magma/core/process/variable.py | 4 +- src/lava/magma/core/resources.py | 4 + src/lava/magma/core/run_conditions.py | 4 +- src/lava/magma/core/run_configs.py | 6 +- .../core/sync/protocols/async_protocol.py | 1 - .../runtime/message_infrastructure/factory.py | 2 + .../message_infrastructure_interface.py | 7 + .../message_infrastructure/multiprocessing.py | 10 + src/lava/magma/runtime/mgmt_token_enums.py | 10 +- src/lava/magma/runtime/node/node.py | 160 ------ src/lava/magma/runtime/runtime.py | 221 ++++++-- .../runtime_services/runtime_service.py | 360 +++++++++--- src/lava/proc/io/dataloader.py | 4 +- src/lava/proc/io/reset.py | 2 +- src/lava/proc/io/sink.py | 4 +- src/lava/proc/io/source.py | 2 +- src/lava/proc/monitor/models.py | 8 +- tests/lava/magma/compiler/test_compiler.py | 37 +- tests/lava/magma/core/model/test_py_model.py | 45 +- .../magma/core/process/test_lif_dense_lif.py | 4 +- tests/lava/magma/core/process/test_process.py | 26 +- .../lava/magma/runtime/test_async_protocol.py | 78 +++ .../magma/runtime/test_exception_handling.py | 22 +- .../runtime/test_get_set_non_determinism.py | 62 +++ tests/lava/magma/runtime/test_get_set_var.py | 21 +- .../lava/magma/runtime/test_loihi_protocol.py | 6 +- ..._.py => test_nxsdkruntimeservice_loihi.py} | 60 +- .../test_pause_requested_from_model.py | 221 ++++++++ .../lava/magma/runtime/test_ref_var_ports.py | 122 ++++- .../test_run_continuously_and_pause.py | 106 ++++ tests/lava/magma/runtime/test_runtime.py | 34 +- .../magma/runtime/test_runtime_service.py | 3 + tests/lava/proc/conv/test_models.py | 9 +- tests/lava/proc/dense/test_models.py | 12 +- tests/lava/proc/io/test_dataloader.py | 5 +- tests/lava/proc/io/test_source_sink.py | 113 +--- tests/lava/proc/lif/test_models.py | 14 +- tests/lava/proc/monitor/test_monitors.py | 10 +- ...utorial01_mnist_digit_classification.ipynb | 6 +- .../tutorial07_remote_memory_access.ipynb | 4 +- 46 files changed, 1760 insertions(+), 742 deletions(-) delete mode 100644 src/lava/magma/runtime/node/node.py create mode 100644 tests/lava/magma/runtime/test_async_protocol.py create mode 100644 tests/lava/magma/runtime/test_get_set_non_determinism.py rename tests/lava/magma/runtime/{test_nxsdkruntimeservice_loihi_.py => test_nxsdkruntimeservice_loihi.py} (50%) create mode 100644 tests/lava/magma/runtime/test_pause_requested_from_model.py create mode 100644 tests/lava/magma/runtime/test_run_continuously_and_pause.py diff --git a/src/lava/magma/compiler/builders/builder.py b/src/lava/magma/compiler/builders/builder.py index fe5e8633c..22d02bf6b 100644 --- a/src/lava/magma/compiler/builders/builder.py +++ b/src/lava/magma/compiler/builders/builder.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: BSD-3-Clause # See: https://spdx.org/licenses/ +import logging import typing as ty import numpy as np @@ -228,7 +229,7 @@ def check_lava_py_types(self): raise AssertionError( f"LavaPyType for '{name}' must be a strict sub-type of " f"PyRefPort in '{self.proc_model.__name__}'." - ) + ) def set_py_ports(self, py_ports: ty.List[PortInitializer], check=True): """Appends the given list of PyPorts to the ProcessModel. Used by the @@ -448,7 +449,7 @@ def build(self): return pm -class CProcessBuilder(AbstractProcessBuilder): +class CProcessBuilder(_AbstractProcessBuilder): """C Process Builder""" pass @@ -502,6 +503,9 @@ def check_all_vars_set(self): f"'{self.proc_model.__name__}'." ) + def set_rs_csp_ports(self, csp_ports: ty.List[AbstractCspPort]): + pass + def build(self): """Builds a NcProcessModel at runtime within Runtime. @@ -557,14 +561,19 @@ def __init__( protocol: ty.Type[AbstractSyncProtocol], runtime_service_id: int, model_ids: ty.List[int], + loihi_version: ty.Type[LoihiVersion], + loglevel: int = logging.WARNING ): super(RuntimeServiceBuilder, self).__init__(rs_class, protocol) + self.log = logging.getLogger(__name__) + self.log.setLevel(loglevel) self._runtime_service_id = runtime_service_id self._model_ids: ty.List[int] = model_ids self.csp_send_port: ty.Dict[str, CspSendPort] = {} self.csp_recv_port: ty.Dict[str, CspRecvPort] = {} self.csp_proc_send_port: ty.Dict[str, CspSendPort] = {} self.csp_proc_recv_port: ty.Dict[str, CspRecvPort] = {} + self.loihi_version: ty.Type[LoihiVersion] = loihi_version @property def runtime_service_id(self): @@ -608,21 +617,30 @@ def build(self, A concreate instance of AbstractRuntimeService [PyRuntimeService or NxSdkRuntimeService] """ - if isinstance(self.rs_class, NxSdkRuntimeService): + + self.log.debug("RuntimeService Class: " + str(self.rs_class)) + nxsdk_rts = False + if self.rs_class == NxSdkRuntimeService: rs = self.rs_class(protocol=self.sync_protocol, loihi_version=loihi_version) + nxsdk_rts = True + self.log.debug("Initilized NxSdkRuntimeService") else: rs = self.rs_class(protocol=self.sync_protocol) + self.log.debug("Initilized PyRuntimeService") rs.runtime_service_id = self._runtime_service_id rs.model_ids = self._model_ids - for port in self.csp_proc_send_port.values(): - if "service_to_process" in port.name: - rs.service_to_process.append(port) + if not nxsdk_rts: + for port in self.csp_proc_send_port.values(): + if "service_to_process" in port.name: + rs.service_to_process.append(port) - for port in self.csp_proc_recv_port.values(): - if "process_to_service" in port.name: - rs.process_to_service.append(port) + for port in self.csp_proc_recv_port.values(): + if "process_to_service" in port.name: + rs.process_to_service.append(port) + + self.log.debug("Setup 'RuntimeService <--> Rrocess; ports") for port in self.csp_send_port.values(): if "service_to_runtime" in port.name: @@ -632,6 +650,8 @@ def build(self, if "runtime_to_service" in port.name: rs.runtime_to_service = port + self.log.debug("Setup 'Runtime <--> RuntimeService' ports") + return rs diff --git a/src/lava/magma/compiler/compiler.py b/src/lava/magma/compiler/compiler.py index 7460e7371..2a02c3148 100644 --- a/src/lava/magma/compiler/compiler.py +++ b/src/lava/magma/compiler/compiler.py @@ -30,26 +30,35 @@ from lava.magma.core import resources from lava.magma.core.model.c.model import AbstractCProcessModel from lava.magma.core.model.model import AbstractProcessModel -from lava.magma.core.model.nc.model import AbstractNcProcessModel +from lava.magma.core.model.nc.model import ( + AbstractNcProcessModel, + NcProcessModel +) from lava.magma.core.model.py.model import AbstractPyProcessModel from lava.magma.core.model.py.ports import RefVarTypeMapping from lava.magma.core.model.sub.model import AbstractSubProcessModel from lava.magma.core.process.ports.ports import AbstractPort, VarPort, \ ImplicitVarPort, RefPort from lava.magma.core.process.process import AbstractProcess -from lava.magma.core.resources import CPU, NeuroCore +from lava.magma.core.resources import ( + CPU, + Loihi1NeuroCore, + Loihi2NeuroCore, + NeuroCore +) from lava.magma.core.run_configs import RunConfig from lava.magma.core.sync.domain import SyncDomain from lava.magma.core.sync.protocols.async_protocol import AsyncProtocol from lava.magma.runtime.runtime import Runtime +from lava.magma.runtime.runtime_services.enums import LoihiVersion PROC_MAP = ty.Dict[AbstractProcess, ty.Type[AbstractProcessModel]] # ToDo: (AW) Document all class methods and class class Compiler: - def __init__(self, compile_cfg: ty.Optional[ty.Dict[str, ty.Any]] = None, - loglevel=logging.WARNING): + def __init__(self, loglevel: int = logging.WARNING, + compile_cfg: ty.Optional[ty.Dict[str, ty.Any]] = None): self.log = logging.getLogger(__name__) self.log.setLevel(loglevel) self._compile_config = {"pypy_channel_size": 64} @@ -404,7 +413,8 @@ def _compile_proc_models( @staticmethod def _create_sync_domains( - proc_map: PROC_MAP, run_cfg: RunConfig, node_cfgs + proc_map: PROC_MAP, run_cfg: RunConfig, node_cfgs, + log: logging.getLoggerClass() ) -> ty.Tuple[ty.List[SyncDomain], ty.Dict[Node, ty.List[SyncDomain]]]: """Validates custom sync domains provided by run_cfg and otherwise creates default sync domains. @@ -419,7 +429,6 @@ def _create_sync_domains( unassigned processes to those default sync domains based on the sync protocol that the chosen process model implements. """ - proc_to_domain_map = OrderedDict() sync_domains = OrderedDict() @@ -435,13 +444,16 @@ def _create_sync_domains( # Validate and map all processes in sync domain for p in sd.processes: + log.debug("Process: " + str(p)) pm = proc_map[p] # Auto-assign AsyncProtocol if none was assigned if not pm.implements_protocol: proto = AsyncProtocol + log.debug("Protocol: AsyncProtocol") else: proto = pm.implements_protocol + log.debug("Protocol: " + proto.__name__) # Check that SyncProtocols of process model and sync domain # are compatible @@ -475,6 +487,7 @@ def _create_sync_domains( proto = AsyncProtocol else: proto = pm.implements_protocol + log.debug("Protocol: " + proto.__name__) # Add process to existing or new default sync domain if not part # of custom sync domain @@ -494,13 +507,16 @@ def _create_sync_domains( defaultdict(list) for node_cfg in node_cfgs: for node in node_cfg: + log.debug("Node: " + str(node.node_type.__name__)) node_to_sync_domain_dict[node].extend( [proc_to_domain_map[proc] for proc in node.processes]) return list(sync_domains.values()), node_to_sync_domain_dict # ToDo: (AW) Implement the general NodeConfig generation algorithm @staticmethod - def _create_node_cfgs(proc_map: PROC_MAP) -> ty.List[NodeConfig]: + def _create_node_cfgs(proc_map: PROC_MAP, + log: logging.getLoggerClass() + ) -> ty.List[NodeConfig]: """Creates and returns a list of NodeConfigs from the AbstractResource requirements of all process's ProcessModels where each NodeConfig is a set of Nodes that satisfies the resource @@ -575,10 +591,27 @@ def _create_node_cfgs(proc_map: PROC_MAP) -> ty.List[NodeConfig]: Finally, we are left with a list of (the best) legal NodeCfgs. """ procs = list(proc_map.keys()) + if log.level == logging.DEBUG: + for proc in procs: + log.debug("Proc Name: " + proc.name + " Proc: " + str(proc)) + proc_models = list(proc_map.items()) + for procm in proc_models: + log.debug("ProcModels: " + str(procm[1])) + n = Node(node_type=resources.HeadNode, processes=procs) ncfg = NodeConfig() ncfg.append(n) + # Until NodeConfig generation algorithm present + # check if NcProcessModel is present in proc_map + # if so add hardcoded Node for OheoGulch + for proc_model in proc_map.items(): + if issubclass(proc_model[1], NcProcessModel): + n1 = Node(node_type=resources.OheoGulch, processes=procs) + ncfg.append(n1) + log.debug("OheoGulch Node Added to NodeConfig: " + + str(n1.node_type)) + return [ncfg] @staticmethod @@ -684,26 +717,45 @@ def _create_channel_builders(self, proc_map: PROC_MAP) \ # ToDo: (AW) Fix type resolution issues @staticmethod def _create_runtime_service_as_py_process_model( - node_to_sync_domain_dict: ty.Dict[Node, ty.List[SyncDomain]]) \ + node_to_sync_domain_dict: ty.Dict[Node, ty.List[SyncDomain]], + log: logging.getLoggerClass() = logging.getLogger()) \ -> ty.Tuple[ ty.Dict[SyncDomain, AbstractRuntimeServiceBuilder], ty.Dict[int, int]]: rs_builders: ty.Dict[SyncDomain, AbstractRuntimeServiceBuilder] = {} proc_id_to_runtime_service_id_map: ty.Dict[int, int] = {} rs_id: int = 0 + loihi_version: LoihiVersion = LoihiVersion.N3 for node, sync_domains in node_to_sync_domain_dict.items(): sync_domain_set = set(sync_domains) for sync_domain in sync_domain_set: + if log.level == logging.DEBUG: + for resource in node.node_type.resources: + log.debug("node.node_type.resources: " + + resource.__name__) if NeuroCore in node.node_type.resources: rs_class = sync_domain.protocol.runtime_service[NeuroCore] + elif Loihi1NeuroCore in node.node_type.resources: + log.debug("sync_domain.protocol. " + + "runtime_service[Loihi1NeuroCore]") + rs_class = sync_domain.protocol. \ + runtime_service[Loihi1NeuroCore] + loihi_version: LoihiVersion = LoihiVersion.N2 + elif Loihi2NeuroCore in node.node_type.resources: + log.debug("sync_domain.protocol. " + + "runtime_service[Loihi2NeuroCore]") + rs_class = sync_domain.protocol. \ + runtime_service[Loihi2NeuroCore] else: rs_class = sync_domain.protocol.runtime_service[CPU] + log.debug("RuntimeService Class: " + str(rs_class.__name__)) model_ids: ty.List[int] = [p.id for p in sync_domain.processes] rs_builder = \ RuntimeServiceBuilder(rs_class=rs_class, protocol=sync_domain.protocol, runtime_service_id=rs_id, - model_ids=model_ids) + model_ids=model_ids, + loihi_version=loihi_version) rs_builders[sync_domain] = rs_builder for p in sync_domain.processes: proc_id_to_runtime_service_id_map[p.id] = rs_id @@ -1009,11 +1061,11 @@ def compile(self, proc: AbstractProcess, run_cfg: RunConfig) -> Executable: exe = self._compile_proc_models(proc_groups) # 4. Create NodeConfigs (just pick one manually for now): - node_cfgs = self._create_node_cfgs(proc_map) + node_cfgs = self._create_node_cfgs(proc_map, self.log) # 5. Create SyncDomains sync_domains, node_to_sync_domain_dict = self._create_sync_domains( - proc_map, run_cfg, node_cfgs) + proc_map, run_cfg, node_cfgs, self.log) # 6. Create Channel builders channel_builders = self._create_channel_builders(proc_map) @@ -1021,7 +1073,7 @@ def compile(self, proc: AbstractProcess, run_cfg: RunConfig) -> Executable: # 7. Create Runtime Service builders runtime_service_builders, proc_id_to_runtime_service_id_map = \ self._create_runtime_service_as_py_process_model( - node_to_sync_domain_dict) + node_to_sync_domain_dict, self.log) # 8. Create ExecVars self._create_exec_vars(node_cfgs, diff --git a/src/lava/magma/core/model/model.py b/src/lava/magma/core/model/model.py index 56a017f5e..713d5d8dc 100644 --- a/src/lava/magma/core/model/model.py +++ b/src/lava/magma/core/model/model.py @@ -2,6 +2,7 @@ # SPDX-License-Identifier: BSD-3-Clause # See: https://spdx.org/licenses/ from __future__ import annotations + import typing as ty import logging from abc import ABC @@ -57,8 +58,9 @@ class level attributes with the same name if they exist. required_resources: ty.List[ty.Type[AbstractResource]] = [] tags: ty.List[str] = [] - def __init__(self, proc_params: ty.Dict[str, ty.Any], - loglevel=logging.WARNING) -> None: + def __init__(self, + proc_params: ty.Dict[str, ty.Any], + loglevel: int = logging.WARNING) -> None: self.log = logging.getLogger(__name__) self.log.setLevel(loglevel) self.proc_params: ty.Dict[str, ty.Any] = proc_params diff --git a/src/lava/magma/core/model/nc/model.py b/src/lava/magma/core/model/nc/model.py index babd23ce7..e2bb15978 100644 --- a/src/lava/magma/core/model/nc/model.py +++ b/src/lava/magma/core/model/nc/model.py @@ -47,8 +47,10 @@ class AbstractNcProcessModel(AbstractProcessModel, ABC): bias: np.ndarray = LavaNcType(np.ndarray, np.int16, precision=12) du: int = LavaNcType(int, np.uint16, precision=12) """ - def __init__(self, proc_params: ty.Dict[str, ty.Any], - loglevel=logging.WARNING) -> None: + def __init__(self, + log: logging.getLoggerClass, + proc_params: ty.Dict[str, ty.Any], + loglevel: int = logging.WARNING) -> None: super().__init__(proc_params, loglevel=loglevel) self.model_id: ty.Optional[int] = None @@ -61,8 +63,9 @@ def allocate(self, net: Net): class NcProcessModel(AbstractNcProcessModel): - def __init__(self, proc_params: ty.Dict[str, ty.Any], - loglevel=logging.WARNING): + def __init__(self, + proc_params: ty.Dict[str, ty.Any], + loglevel: int = logging.WARNING): super(AbstractNcProcessModel, self).__init__(proc_params, loglevel=loglevel) diff --git a/src/lava/magma/core/model/py/model.py b/src/lava/magma/core/model/py/model.py index 7bea30dc1..6381ac6b3 100644 --- a/src/lava/magma/core/model/py/model.py +++ b/src/lava/magma/core/model/py/model.py @@ -17,7 +17,6 @@ enum_equal, MGMT_COMMAND, MGMT_RESPONSE, ) -from lava.magma.runtime.runtime_services.enums import LoihiPhase class AbstractPyProcessModel(AbstractProcessModel, ABC): @@ -32,17 +31,41 @@ class AbstractPyProcessModel(AbstractProcessModel, ABC): du: int = LavaPyType(int, np.uint16, precision=12) """ - def __init__(self, proc_params: ty.Dict[str, ty.Any], - loglevel=logging.WARNING) -> None: - super().__init__(proc_params, loglevel=loglevel) + def __init__(self, + proc_params: ty.Dict[str, ty.Any], + loglevel: int = logging.WARNING) -> None: + super().__init__(proc_params=proc_params, loglevel=loglevel) self.model_id: ty.Optional[int] = None self.service_to_process: ty.Optional[CspRecvPort] = None self.process_to_service: ty.Optional[CspSendPort] = None self.py_ports: ty.List[AbstractPyPort] = [] self.var_ports: ty.List[PyVarPort] = [] self.var_id_to_var_map: ty.Dict[int, ty.Any] = {} + self._selector: CspSelector = CspSelector() + self._action: str = 'cmd' + self._stopped: bool = False + self._channel_actions: ty.List[ty.Tuple[ty.Union[CspSendPort, + CspRecvPort], + ty.Callable]] = [] + self._cmd_handlers: ty.Dict[MGMT_COMMAND, ty.Callable] = { + MGMT_COMMAND.STOP[0]: self._stop, + MGMT_COMMAND.PAUSE[0]: self._pause, + MGMT_COMMAND.GET_DATA[0]: self._get_var, + MGMT_COMMAND.SET_DATA[0]: self._set_var + } def __setattr__(self, key: str, value: ty.Any): + """ + Sets attribute in the object. This function is used by the builder + to add ports to py_ports and var_ports list. + + Parameters + ---------- + key: Attribute being set + value: Value of the attribute + ------- + + """ self.__dict__[key] = value if isinstance(value, AbstractPyPort): self.py_ports.append(value) @@ -51,137 +74,31 @@ def __setattr__(self, key: str, value: ty.Any): self.var_ports.append(value) def start(self): + """ + Starts the process model, by spinning up all the ports (mgmt and + py_ports) and calls the run function. + """ self.service_to_process.start() self.process_to_service.start() for p in self.py_ports: p.start() self.run() - @abstractmethod - def run(self): - pass - - def join(self): - self.service_to_process.join() - self.process_to_service.join() - for p in self.py_ports: - p.join() - - -class PyLoihiProcessModel(AbstractPyProcessModel): - def __init__(self, proc_params: ty.Dict[str, ty.Any], - loglevel=logging.WARNING): - super(PyLoihiProcessModel, self).__init__(proc_params, - loglevel=loglevel) - self.current_ts = 0 - - def run_spk(self): - pass - - def run_pre_mgmt(self): - pass - - def run_lrn(self): - pass - - def run_post_mgmt(self): - pass - - def pre_guard(self): - pass - - def lrn_guard(self): - pass + def _stop(self): + """ + Command handler for Stop command. + """ + self.process_to_service.send(MGMT_RESPONSE.TERMINATED) + self._stopped = True + self.join() - def post_guard(self): - pass + def _pause(self): + """ + Command handler for Pause command. + """ + self.process_to_service.send(MGMT_RESPONSE.PAUSED) - # TODO: (PP) need to handle PAUSE command - def run(self): - """Retrieves commands from the runtime service to iterate through the - phases of Loihi and calls their corresponding methods of the - ProcessModels. The phase is retrieved from runtime service - (service_to_process). After calling the method of a phase of all - ProcessModels the runtime service is informed about completion. The - loop ends when the STOP command is received.""" - selector = CspSelector() - action = 'cmd' - phase = LoihiPhase.SPK - while True: - if action == 'cmd': - cmd = self.service_to_process.recv() - if enum_equal(cmd, MGMT_COMMAND.STOP): - self.process_to_service.send(MGMT_RESPONSE.TERMINATED) - self.join() - return - try: - # Spiking phase - increase time step - if enum_equal(cmd, LoihiPhase.SPK): - self.current_ts += 1 - phase = LoihiPhase.SPK - self.run_spk() - self.process_to_service.send(MGMT_RESPONSE.DONE) - # Pre-management phase - elif enum_equal(cmd, - LoihiPhase.PRE_MGMT): - # Enable via guard method - phase = LoihiPhase.PRE_MGMT - if self.pre_guard(): - self.run_pre_mgmt() - self.process_to_service.send(MGMT_RESPONSE.DONE) - # Learning phase - elif enum_equal(cmd, LoihiPhase.LRN): - # Enable via guard method - phase = LoihiPhase.LRN - if self.lrn_guard(): - self.run_lrn() - self.process_to_service.send(MGMT_RESPONSE.DONE) - # Post-management phase - elif enum_equal(cmd, - LoihiPhase.POST_MGMT): - # Enable via guard method - phase = LoihiPhase.POST_MGMT - if self.post_guard(): - self.run_post_mgmt() - self.process_to_service.send(MGMT_RESPONSE.DONE) - # Host phase - called at the last time step before STOP - elif enum_equal(cmd, LoihiPhase.HOST): - phase = LoihiPhase.HOST - pass - elif enum_equal(cmd, MGMT_COMMAND.GET_DATA) and \ - enum_equal(phase, LoihiPhase.HOST): - # Handle get/set Var requests from runtime service - self._handle_get_var() - elif enum_equal(cmd, - MGMT_COMMAND.SET_DATA) and \ - enum_equal(phase, LoihiPhase.HOST): - # Handle get/set Var requests from runtime service - self._handle_set_var() - else: - raise ValueError( - f"Wrong Phase Info Received : {cmd}") - except Exception as inst: - self.log.info(f"Exception {inst} occured while" - f" running command {cmd} in {self.__class__}") - # Inform runtime service about termination - self.process_to_service.send(MGMT_RESPONSE.ERROR) - self.join() - raise inst - else: - # Handle VarPort requests from RefPorts - self._handle_var_port(action) - - channel_actions = [(self.service_to_process, lambda: 'cmd')] - if enum_equal(phase, LoihiPhase.PRE_MGMT) or \ - enum_equal(phase, LoihiPhase.POST_MGMT): - for var_port in self.var_ports: - for csp_port in var_port.csp_ports: - if isinstance(csp_port, CspRecvPort): - channel_actions.append( - (csp_port, lambda: var_port)) - action = selector.select(*channel_actions) - - def _handle_get_var(self): + def _get_var(self): """Handles the get Var command from runtime service.""" # 1. Receive Var ID and retrieve the Var var_id = int(self.service_to_process.recv()[0].item()) @@ -203,7 +120,7 @@ def _handle_get_var(self): for value in var_iter: data_port.send(enum_to_np(value, np.float64)) - def _handle_set_var(self): + def _set_var(self): """Handles the set Var command from runtime service.""" # 1. Receive Var ID and retrieve the Var var_id = int(self.service_to_process.recv()[0].item()) @@ -221,6 +138,7 @@ def _handle_set_var(self): setattr(self, var_name, buffer.item()) else: setattr(self, var_name, buffer.astype(var.dtype)) + self.process_to_service.send(MGMT_RESPONSE.SET_COMPLETE) elif isinstance(var, np.ndarray): # First item is number of items num_items = data_port.recv()[0] @@ -231,9 +149,352 @@ def _handle_set_var(self): break num_items -= 1 i[...] = data_port.recv()[0] + self.process_to_service.send(MGMT_RESPONSE.SET_COMPLETE) else: + self.process_to_service.send(MGMT_RESPONSE.ERROR) raise RuntimeError("Unsupported type") def _handle_var_port(self, var_port): """Handles read/write requests on the given VarPort.""" var_port.service() + + def run(self): + """Retrieves commands from the runtime service and calls their + corresponding methods of the ProcessModels. + After calling the method of the ProcessModels, the runtime service + is informed about completion. The loop ends when the STOP command is + received.""" + while True: + if self._action == 'cmd': + cmd = self.service_to_process.recv()[0] + try: + if cmd in self._cmd_handlers: + self._cmd_handlers[cmd]() + if cmd == MGMT_COMMAND.STOP[0] or self._stopped: + return + else: + raise ValueError( + f"Illegal RuntimeService command! ProcessModels of " + f"type {self.__class__.__qualname__} " + f"{self.model_id} cannot handle " + f"command: {cmd} ") + except Exception as inst: + # Inform runtime service about termination + self.process_to_service.send(MGMT_RESPONSE.ERROR) + self.join() + raise inst + else: + # Handle VarPort requests from RefPorts + self._handle_var_port(self._action) + self._channel_actions = [(self.service_to_process, lambda: 'cmd')] + self.add_ports_for_polling() + self._action = self._selector.select(*self._channel_actions) + + @abstractmethod + def add_ports_for_polling(self): + """ + Add various ports to poll for communication on ports + """ + pass + + def join(self): + """ + Wait for all the ports to shutdown. + """ + self.service_to_process.join() + self.process_to_service.join() + for p in self.py_ports: + p.join() + + +class PyLoihiProcessModel(AbstractPyProcessModel): + """ + ProcessModel for processes that resembles process on Loihi. + """ + + def __init__(self, proc_params: ty.Dict[str, ty.Any] = None): + super(PyLoihiProcessModel, self).__init__(proc_params) + self.time_step = 0 + self.phase = PyLoihiProcessModel.Phase.SPK + self._cmd_handlers.update({ + PyLoihiProcessModel.Phase.SPK[0]: self._spike, + PyLoihiProcessModel.Phase.PRE_MGMT[0]: self._pre_mgmt, + PyLoihiProcessModel.Phase.LRN[0]: self._lrn, + PyLoihiProcessModel.Phase.POST_MGMT[0]: self._post_mgmt, + PyLoihiProcessModel.Phase.HOST[0]: self._host + }) + self._req_pause: bool = False + self._req_stop: bool = False + + class Phase: + """ + Different States of the State Machine of a Loihi Process + """ + SPK = enum_to_np(1) + PRE_MGMT = enum_to_np(2) + LRN = enum_to_np(3) + POST_MGMT = enum_to_np(4) + HOST = enum_to_np(5) + + class Response: + """ + Different types of response for a RuntimeService Request + """ + STATUS_DONE = enum_to_np(0) + """Signfies Ack or Finished with the Command""" + STATUS_TERMINATED = enum_to_np(-1) + """Signifies Termination""" + STATUS_ERROR = enum_to_np(-2) + """Signifies Error raised""" + STATUS_PAUSED = enum_to_np(-3) + """Signifies Execution State to be Paused""" + REQ_PRE_LRN_MGMT = enum_to_np(-4) + """Signifies Request of PREMPTION before Learning""" + REQ_LEARNING = enum_to_np(-5) + """Signifies Request of LEARNING""" + REQ_POST_LRN_MGMT = enum_to_np(-6) + """Signifies Request of PREMPTION after Learning""" + REQ_PAUSE = enum_to_np(-7) + """Signifies Request of PAUSE""" + REQ_STOP = enum_to_np(-8) + """Signifies Request of STOP""" + + def run_spk(self): + """ + Function that runs in Spiking Phase + """ + pass + + def run_pre_mgmt(self): + """ + Function that runs in Pre Lrn Mgmt Phase + """ + pass + + def run_lrn(self): + """ + Function that runs in Learning Phase + """ + pass + + def run_post_mgmt(self): + """ + Function that runs in Post Lrn Mgmt Phase + """ + pass + + def pre_guard(self): + """ + Guard function that determines if pre lrn mgmt phase will get + executed or not for the current timestep. + """ + pass + + def lrn_guard(self): + """ + Guard function that determines if lrn phase will get + executed or not for the current timestep. + """ + pass + + def post_guard(self): + """ + Guard function that determines if post lrn mgmt phase will get + executed or not for the current timestep. + """ + pass + + def _spike(self): + """ + Command handler for Spiking Phase + """ + self.time_step += 1 + self.phase = PyLoihiProcessModel.Phase.SPK + self.run_spk() + if self._req_pause or self._req_stop: + self._handle_pause_or_stop_req() + return + if self.lrn_guard() and self.pre_guard(): + self.process_to_service.send( + PyLoihiProcessModel.Response.REQ_PRE_LRN_MGMT) + elif self.lrn_guard(): + self.process_to_service.send( + PyLoihiProcessModel.Response.REQ_LEARNING) + elif self.post_guard(): + self.process_to_service.send( + PyLoihiProcessModel.Response.REQ_POST_LRN_MGMT) + else: + self.process_to_service.send( + PyLoihiProcessModel.Response.STATUS_DONE) + + def _pre_mgmt(self): + """ + Command handler for Pre Lrn Mgmt Phase + """ + self.phase = PyLoihiProcessModel.Phase.PRE_MGMT + if self.pre_guard(): + self.run_pre_mgmt() + if self._req_pause or self._req_stop: + self._handle_pause_or_stop_req() + return + self.process_to_service.send( + PyLoihiProcessModel.Response.REQ_LEARNING) + + def _post_mgmt(self): + """ + Command handler for Post Lrn Mgmt Phase + """ + self.phase = PyLoihiProcessModel.Phase.POST_MGMT + if self.post_guard(): + self.run_post_mgmt() + if self._req_pause or self._req_stop: + self._handle_pause_or_stop_req() + return + self.process_to_service.send(PyLoihiProcessModel.Response.STATUS_DONE) + + def _lrn(self): + """ + Command handler for Lrn Phase + """ + self.phase = PyLoihiProcessModel.Phase.LRN + if self.lrn_guard(): + self.run_lrn() + if self._req_pause or self._req_stop: + self._handle_pause_or_stop_req() + return + if self.post_guard(): + self.process_to_service.send( + PyLoihiProcessModel.Response.REQ_POST_LRN_MGMT) + return + self.process_to_service.send(PyLoihiProcessModel.Response.STATUS_DONE) + + def _host(self): + """ + Command handler for Host Phase + """ + self.phase = PyLoihiProcessModel.Phase.HOST + + def _stop(self): + """ + Command handler for Stop Command. + """ + self.process_to_service.send( + PyLoihiProcessModel.Response.STATUS_TERMINATED) + self.join() + + def _pause(self): + """ + Command handler for Pause Command. + """ + self.process_to_service.send(PyLoihiProcessModel.Response.STATUS_PAUSED) + + def _handle_pause_or_stop_req(self): + """ + Helper function that checks if stop or pause is being requested by the + user and handles it. + """ + if self._req_pause: + self._req_rs_pause() + elif self._req_stop: + self._req_rs_stop() + + def _req_rs_pause(self): + """ + Helper function that handles pause requested by the user. + """ + self._req_pause = False + self.process_to_service.send(PyLoihiProcessModel.Response.REQ_PAUSE) + + def _req_rs_stop(self): + """ + Helper function that handles stop requested by the user. + """ + self._req_stop = False + self.process_to_service.send(PyLoihiProcessModel.Response.REQ_STOP) + + def add_ports_for_polling(self): + """ + Add various ports to poll for communication on ports + """ + if enum_equal(self.phase, PyLoihiProcessModel.Phase.PRE_MGMT) or \ + enum_equal(self.phase, PyLoihiProcessModel.Phase.POST_MGMT): + for var_port in self.var_ports: + for csp_port in var_port.csp_ports: + if isinstance(csp_port, CspRecvPort): + def func(fvar_port=var_port): + return lambda: fvar_port + self._channel_actions.append((csp_port, func(var_port))) + + +class PyAsyncProcessModel(AbstractPyProcessModel): + """ + Process Model for Asynchronous Processes. + """ + + def __init__(self, proc_params: ty.Dict[str, ty.Any] = None): + super(PyAsyncProcessModel, self).__init__(proc_params) + self._cmd_handlers.update({ + MGMT_COMMAND.RUN[0]: self._run_async + }) + + class Response: + """ + Different types of response for a RuntimeService Request + """ + STATUS_DONE = enum_to_np(0) + """Signfies Ack or Finished with the Command""" + STATUS_TERMINATED = enum_to_np(-1) + """Signifies Termination""" + STATUS_ERROR = enum_to_np(-2) + """Signifies Error raised""" + STATUS_PAUSED = enum_to_np(-3) + """Signifies Execution State to be Paused""" + REQ_PAUSE = enum_to_np(-4) + """Signifies Request of PAUSE""" + REQ_STOP = enum_to_np(-5) + """Signifies Request of STOP""" + + def _pause(self): + """ + Command handler for Pause Command. + """ + pass + + def check_for_stop_cmd(self) -> bool: + """ + Checks if the RS has sent a STOP command. + """ + if self.service_to_process.probe(): + cmd = self.service_to_process.peek() + if enum_equal(cmd, MGMT_COMMAND.STOP): + self.service_to_process.recv() + self._stop() + return True + return False + + def run_async(self): + """ + User needs to define this function which will run asynchronously when + RUN command is received. + """ + raise NotImplementedError("run_async has not been defined") + + def _run_async(self): + """ + Helper function to wrap run_async function + """ + self.run_async() + + def _get_var(self): + """Handles the get Var command from runtime service.""" + raise NotImplementedError + + def _set_var(self): + """Handles the set Var command from runtime service.""" + raise NotImplementedError + + def add_ports_for_polling(self): + """ + Add various ports to poll for communication on ports + """ + pass diff --git a/src/lava/magma/core/process/process.py b/src/lava/magma/core/process/process.py index 8c08eede2..329139c3d 100644 --- a/src/lava/magma/core/process/process.py +++ b/src/lava/magma/core/process/process.py @@ -4,7 +4,6 @@ import logging import typing as ty from _collections import OrderedDict - from lava.magma.compiler.executable import Executable from lava.magma.core.process.interfaces import \ AbstractProcessMember, IdGeneratorSingleton @@ -145,7 +144,7 @@ class AbstractProcess(metaclass=ProcessPostInitCaller): ProcessModels enable seamless cross-platform execution of processes. In particular they allow to build applications or algorithms using processes - agnostic of the ProcessModel chosen at compile current_ts. There are two + agnostic of the ProcessModel chosen at compile time. There are two broad categories of ProcessModels: 1. LeafProcessModels allow to implement the behavior of a process directly in different languages for a particular compute resource. @@ -201,7 +200,7 @@ def __init__(self, **kwargs): 'compile(..)' or 'run(..)' on any of them compiles and runs all of them automatically. - At compile current_ts, the user must provide the Lava compiler with a + At compile time, the user must provide the Lava compiler with a specific instance of a RunConfig class. A RunConfig class represents a set of rules that allows the compiler to select one and only one ProcessModel of a specific Process to be compiled for execution with specific compute @@ -216,7 +215,7 @@ def __init__(self, **kwargs): the execution of a set of processes can either be paused or stopped by calling the corresponding 'pause()' or 'stop()' methods. - In order to save current_ts setting up processes for future use, processes + In order to save time setting up processes for future use, processes can also be saved and reloaded from disk. """ @@ -230,6 +229,12 @@ def __init__(self, **kwargs): self.name: str = kwargs.pop("name", f"Process_{self.id}") self.loglevel: int = kwargs.pop("loglevel", logging.WARNING) + self.logfile: str = kwargs.pop("logfile", "lava.log") + self.logenable: bool = bool(kwargs.pop("logenable", False)) + if self.logenable: + logging.basicConfig(filename=self.logfile) + self.log = logging.getLogger(__name__) + self.log.setLevel(self.loglevel) # kwargs will be used for ProcessModel initialization later self.init_args: dict = kwargs @@ -347,7 +352,7 @@ def compile(self, run_cfg: RunConfig) -> Executable: ProcessModel for each compiled process. """ from lava.magma.compiler.compiler import Compiler - compiler = Compiler() + compiler = Compiler(loglevel=self.loglevel) return compiler.compile(self, run_cfg) def save(self, path: str): @@ -392,12 +397,6 @@ def run(self, :param run_cfg: RunConfig is used by compiler to select a ProcessModel for each compiled process. """ - - if not condition.blocking: - # Currently non-blocking execution is not implemented - raise NotImplementedError("Non-blocking Execution is currently not" - " supported. Please use blocking=True.") - if not self._runtime: executable = self.compile(run_cfg) self._runtime = Runtime(executable, @@ -410,12 +409,12 @@ def run(self, def wait(self): """Waits until end of process execution or for as long as RunCondition is met by blocking execution at command line level.""" - if not self.runtime: + if self.runtime: self.runtime.wait() def pause(self): """Pauses process execution while running in non-blocking mode.""" - if not self.runtime: + if self.runtime: self.runtime.pause() def stop(self): diff --git a/src/lava/magma/core/process/variable.py b/src/lava/magma/core/process/variable.py index 6885189b6..734dbac72 100644 --- a/src/lava/magma/core/process/variable.py +++ b/src/lava/magma/core/process/variable.py @@ -18,7 +18,7 @@ class Var(AbstractProcessMember): values not just scalar objects with a shape. - Vars can be initialized with numeric objects with a dimensionality equal or less than specified by its shape. The initial value will be - broadcast to the shape of the Var at compile current_ts. + broadcast to the shape of the Var at compile time. - Vars have a name: The Variable name will be assigned by the parent process of a Var. - Vars are mutable at runtime. @@ -49,7 +49,7 @@ def __init__( Parameters: :param shape: Tuple specifying the shape of variable tensor. :param init: Initial value assigned to Var. Compiler will broadcast - 'init' to 'shape' of Var at compile current_ts. + 'init' to 'shape' of Var at compile time. :param shareable: Specifies whether Variable allows shared memory access by processes other than the Var's parent process. """ diff --git a/src/lava/magma/core/resources.py b/src/lava/magma/core/resources.py index 84bc82854..c2e0cbb5e 100644 --- a/src/lava/magma/core/resources.py +++ b/src/lava/magma/core/resources.py @@ -114,3 +114,7 @@ class KapohoPoint(Loihi2System): class Unalaska(Loihi2System): resources = [CPU, Loihi2NeuroCore, LMT, PB] + + +class OheoGulch(Loihi2System): + resources = [Loihi2NeuroCore, LMT, PB] diff --git a/src/lava/magma/core/run_conditions.py b/src/lava/magma/core/run_conditions.py index 1ef0508b8..57d5345f4 100644 --- a/src/lava/magma/core/run_conditions.py +++ b/src/lava/magma/core/run_conditions.py @@ -12,7 +12,7 @@ def __init__(self, blocking): class RunSteps(AbstractRunCondition): - """Runs a process for a specified number of current_ts steps with respect to a + """Runs a process for a specified number of time steps with respect to a SyncDomain assigned to any sub processes.""" def __init__(self, num_steps: int, blocking: bool = True): @@ -21,7 +21,7 @@ def __init__(self, num_steps: int, blocking: bool = True): class RunContinuous(AbstractRunCondition): - """Runs a Process continuously without a current_ts step limit.""" + """Runs a Process continuously without a time step limit.""" def __init__(self): super().__init__(blocking=False) diff --git a/src/lava/magma/core/run_configs.py b/src/lava/magma/core/run_configs.py index 395eab98d..9be4d35fa 100644 --- a/src/lava/magma/core/run_configs.py +++ b/src/lava/magma/core/run_configs.py @@ -2,9 +2,7 @@ # SPDX-License-Identifier: BSD-3-Clause # See: https://spdx.org/licenses/ from __future__ import annotations - import logging - import typing as ty from abc import ABC @@ -46,7 +44,7 @@ class RunConfig(ABC): def __init__(self, custom_sync_domains: ty.Optional[ty.List[SyncDomain]] = None, - loglevel=logging.WARNING): + loglevel: int = logging.WARNING): self.log = logging.getLogger(__name__) self.log.setLevel(loglevel) self.custom_sync_domains = [] @@ -129,7 +127,7 @@ def __init__(self, exception_proc_model_map: ty.Optional[ty.Dict[ ty.Type[AbstractProcess], ty.Type[ AbstractProcessModel]]] = None, - loglevel=logging.WARNING): + loglevel: int = logging.WARNING): super().__init__(custom_sync_domains=custom_sync_domains, loglevel=loglevel) self.select_tag = select_tag diff --git a/src/lava/magma/core/sync/protocols/async_protocol.py b/src/lava/magma/core/sync/protocols/async_protocol.py index 54ddeda8b..b7fe85170 100644 --- a/src/lava/magma/core/sync/protocols/async_protocol.py +++ b/src/lava/magma/core/sync/protocols/async_protocol.py @@ -14,7 +14,6 @@ class AsyncProtocol(AbstractSyncProtocol): phases = [] proc_funcions = [] - # TODO: AsyncProtocol needs to implement AsyncRuntimeService @property def runtime_service(self): return {CPU: AsyncPyRuntimeService} diff --git a/src/lava/magma/runtime/message_infrastructure/factory.py b/src/lava/magma/runtime/message_infrastructure/factory.py index 7db90bd91..f740e8006 100644 --- a/src/lava/magma/runtime/message_infrastructure/factory.py +++ b/src/lava/magma/runtime/message_infrastructure/factory.py @@ -5,6 +5,8 @@ from lava.magma.runtime.message_infrastructure.multiprocessing import \ MultiProcessing +"""Factory class to create the messaging infrastructure""" + class MessageInfrastructureFactory: """Creates the message infrastructure instance based on type""" diff --git a/src/lava/magma/runtime/message_infrastructure/message_infrastructure_interface.py b/src/lava/magma/runtime/message_infrastructure/message_infrastructure_interface.py index 1d9d30bd1..cbcfd482c 100644 --- a/src/lava/magma/runtime/message_infrastructure/message_infrastructure_interface.py +++ b/src/lava/magma/runtime/message_infrastructure/message_infrastructure_interface.py @@ -13,6 +13,11 @@ from lava.magma.compiler.channels.interfaces import ChannelType, Channel from lava.magma.core.sync.domain import SyncDomain +"""A Message Infrastructure Interface which can create actors which would +participate in message passing/exchange, start and stop them as well as +declare the underlying Channel Infrastructure Class to be used for message +passing implementation.""" + class MessageInfrastructureInterface(ABC): """Interface to provide the ability to create actors which can @@ -42,4 +47,6 @@ def actors(self) -> ty.List[ty.Any]: @abstractmethod def channel_class(self, channel_type: ChannelType) -> ty.Type[Channel]: + """Given the Channel Type, Return the Channel Implementation to + be used during execution""" pass diff --git a/src/lava/magma/runtime/message_infrastructure/multiprocessing.py b/src/lava/magma/runtime/message_infrastructure/multiprocessing.py index 2b057447e..4d47e7180 100644 --- a/src/lava/magma/runtime/message_infrastructure/multiprocessing.py +++ b/src/lava/magma/runtime/message_infrastructure/multiprocessing.py @@ -20,7 +20,15 @@ import MessageInfrastructureInterface +"""Implements the Message Infrastructure Interface using Python +MultiProcessing Library. The MultiProcessing API is used to create actors +which will participate in exchanging messages. The Channel Infrastructure +further uses the SharedMemoryManager from MultiProcessing Library to +implement the communication backend in this implementation.""" + + class SystemProcess(mp.Process): + """Wraps a process so that the exceptions can be collected if present""" def __init__(self, *args, **kwargs): mp.Process.__init__(self, *args, **kwargs) self._pconn, self._cconn = mp.Pipe() @@ -83,6 +91,8 @@ def stop(self): self._smm.shutdown() def channel_class(self, channel_type: ChannelType) -> ty.Type[Channel]: + """Given a channel type, returns the shared memory based class + implementation for the same""" if channel_type == ChannelType.PyPy: return PyPyChannel else: diff --git a/src/lava/magma/runtime/mgmt_token_enums.py b/src/lava/magma/runtime/mgmt_token_enums.py index f0417dc24..91f2d70ce 100644 --- a/src/lava/magma/runtime/mgmt_token_enums.py +++ b/src/lava/magma/runtime/mgmt_token_enums.py @@ -2,9 +2,11 @@ # SPDX-License-Identifier: BSD-3-Clause # See: https://spdx.org/licenses/ import typing as ty - import numpy as np +"""Defines message tokens for Actions (Commands) and Responses. Also defines +helper functions to convert scalar values to these message tokens""" + def enum_to_np(value: ty.Union[int, float], d_type: type = np.float64) -> np.array: @@ -64,3 +66,9 @@ class MGMT_RESPONSE: """Signifies Error raised""" PAUSED = enum_to_np(-3) """Signifies Execution State to be Paused""" + REQ_PAUSE = enum_to_np(-4) + """Signifies Request of PAUSE""" + REQ_STOP = enum_to_np(-5) + """Signifies Request of STOP""" + SET_COMPLETE = enum_to_np(-6) + """Signifies Completion of Set Var""" diff --git a/src/lava/magma/runtime/node/node.py b/src/lava/magma/runtime/node/node.py deleted file mode 100644 index 8bdc8ad90..000000000 --- a/src/lava/magma/runtime/node/node.py +++ /dev/null @@ -1,160 +0,0 @@ -# Copyright (C) 2021 Intel Corporation -# SPDX-License-Identifier: BSD-3-Clause -# See: https://spdx.org/licenses/ - - -# # ToDo: This needs to be re-architected. We cannot spin up processes just -# # locally within a single Node. Processes on one node might connect to -# # processes on another node. The entire system setup must happen inside the -# # Runtime. The Runtime needs to allocate all nodes (right now there just -# # happens to be one), then it creates the channels connecting processes on -# # all nodes, then it deploys all ProcBuilders to all these nodes. -# class Node: -# """Manages a node. It is responsible for spinning up the synchronizers ( -# one for each sync domain) and the associated process models. It also -# creates the Mgmt channels associated with all of them so as to be able to -# communicate the Mgmt commands""" -# def __init__(self, actor_id: int): -# self.id = actor_id -# self.rton_recv_port: ty.Optional[CspRecvPort] = None -# self.ntor_send_port: ty.Optional[CspSendPort] = None -# self.synchronizer_processes = [] -# self.process_model_processes = [] -# -# # TODO: Abstract this out. See similar comment in runtime -# self._smm: ty.Optional[SharedMemoryManager] = None -# -# def start(self, -# compiled_processes_on_node: ty.Iterable[ -# 'CompiledProcessMetadata'], -# rton_recv_port: CspRecvPort, -# ntor_send_port: CspSendPort): -# """Starts the Node""" -# -# self._smm = SharedMemoryManager() -# self._smm.start() -# -# self.rton_recv_port, self.ntor_send_port = rton_recv_port, \ -# ntor_send_port -# self.rton_recv_port.start() -# self.ntor_send_port.start() -# self._spin_up_synchronizer_and_process_models(compiled_processes_on_node) -# self._command_loop() -# -# def _send_sync_cmd_and_get_rsp(self, cmd: MGMT_COMMAND)->ty.Iterable[ -# MGMT_RESPONSE]: -# rsps = [] -# for idx, send_port in enumerate(self.send_ports): -# send_port.send(cmd) -# for idx, recv_port in enumerate(self.recv_ports): -# rsps.append(recv_port.recv()) -# return rsps -# -# def _command_loop(self): -# while True: -# command = self.rton_recv_port.recv() -# rsps = self._send_sync_cmd_and_get_rsp(command) -# if np.array_equal(command, MGMT_COMMAND.STOP): -# for rsp in rsps: -# if not np.array_equal(rsp, MGMT_RESPONSE.TERMINATED): -# raise ValueError(f"Wrong Response Received : {rsp}") -# self.ntor_send_port.send(MGMT_RESPONSE.TERMINATED) -# self._join() -# return -# elif np.array_equal(command, MGMT_COMMAND.PAUSE): -# for rsp in rsps: -# if not np.array_equal(rsp, MGMT_RESPONSE.PAUSED): -# raise ValueError(f"Wrong Response Received : {rsp}") -# self.ntor_send_port.send(MGMT_RESPONSE.PAUSED) -# else: -# for rsp in rsps: -# if not np.array_equal(rsp, MGMT_RESPONSE.DONE): -# raise ValueError(f"Wrong Response Received : {rsp}") -# self.ntor_send_port.send(MGMT_RESPONSE.DONE) -# -# # ToDo: (AW) Functions like these need better inline documentation to -# # explain what's going on. Alternatively, break it up into sub functions -# # with descriptive names. Otherwise one has to carefully read line by -# # line to understand how it works. -# # ToDo: (AW) The assignment of processes to SyncDomains should have -# # already been figured out in the compiler. The Runtime or NodeManager -# # should only read the resulting NodeConfiguration and execute it. -# # ToDo: General rule: The more global a method or attribute, the longer -# # the name may by. The more local the shorter its name should be. This -# # way you void having to create massive local variable names that cause -# # very long code lines. -# def _spin_up_synchronizer_and_process_models(self, -# compiled_processes_on_node): -# # Create map from runtime_service to processes serviced by -# # runtime_service -# self.send_ports = [] -# self.recv_ports = [] -# synchronizer_cls_to_compiled_process_dict = defaultdict(list) -# for compiled_process in compiled_processes_on_node: -# sync_domain = compiled_process.sync_domain -# # TODO: Taking the first available runtime_service -# synchronizer_cls = sync_domain.protocol.runtime_service[0] -# synchronizer_cls_to_compiled_process_dict[synchronizer_cls].append(compiled_process) -# -# # Create and connect runtime_service and processes per SyncDomain -# for actor_id_counter, (s, p) in enumerate( -# synchronizer_cls_to_compiled_process_dict.items()): -# # Create system process for each Lava process and to/from sync -# # channels -# synchronizer_to_pm_send_ports: ty.Iterable[CspSendPort] = [] -# pm_to_synchronizer_recv_ports: ty.Iterable[CspRecvPort] = [] -# for pm_actor_id_counter, compiled_process_metadata in -# enumerate(p): -# pm_object = compiled_process_metadata.process_model() -# synchronizer_to_pm_sync_channel = -# create_pypy_mgmt_channel(smm=self._smm, -# name=f"stop_{pm_object.__class__.__name__}_{pm_actor_id_counter}") -# pm_to_synchronizer_sync_channel = create_pypy_mgmt_channel( -# smm=self._smm, -# name=f"ptos_{pm_object.__class__.__name__}_{pm_actor_id_counter}") -# -# synchronizer_to_pm_send_ports.append(synchronizer_to_pm_sync_channel.send_port) -# pm_to_synchronizer_recv_ports.append(pm_to_synchronizer_sync_channel.recv_port) -# -# pm = Process(target=pm_object.start, -# args=(synchronizer_to_pm_sync_channel.recv_port, -# pm_to_synchronizer_sync_channel.send_port)) -# -# pm.start() -# self.process_model_processes.append(pm) -# synchronizer_object = s(actor_id=actor_id_counter) -# -# # Create runtime_service and channels to connect to Node -# node_to_synchronizer_mgmt_channel = create_pypy_mgmt_channel( -# smm=self._smm, -# name=f"ntos_{synchronizer_object.__class__.__name__}" -# f"_{synchronizer_object.id}") -# synchronizer_to_node_mgmt_channel = create_pypy_mgmt_channel( -# smm=self._smm, -# name=f"ston_{synchronizer_object.__class__.__name__}" -# f"_{synchronizer_object.id}") -# -# sp = Process(target=synchronizer_object.start, -# args=(p[0].process_model.implements_protocol, -# node_to_synchronizer_mgmt_channel.recv_port, -# synchronizer_to_node_mgmt_channel.send_port, -# synchronizer_to_pm_send_ports, -# pm_to_synchronizer_recv_ports)) -# self.synchronizer_processes.append(sp) -# node_to_synchronizer_mgmt_channel.send_port.start() -# synchronizer_to_node_mgmt_channel.recv_port.start() -# self.send_ports.append(node_to_synchronizer_mgmt_channel.send_port) -# self.recv_ports.append(synchronizer_to_node_mgmt_channel.recv_port) -# sp.start() -# actor_id_counter += 1 -# -# def _join(self): -# """Join all spun up ports and sub processes""" -# for port in self.send_ports: -# port.join() -# for port in self.recv_ports: -# port.join() -# for p in self.synchronizer_processes: -# p.join() -# for p in self.process_model_processes: -# p.join() diff --git a/src/lava/magma/runtime/runtime.py b/src/lava/magma/runtime/runtime.py index 4c5649098..7804fd195 100644 --- a/src/lava/magma/runtime/runtime.py +++ b/src/lava/magma/runtime/runtime.py @@ -7,6 +7,7 @@ import numpy as np +import sys import typing import typing as ty @@ -14,10 +15,11 @@ from lava.magma.compiler.channels.pypychannel import CspSendPort, CspRecvPort from lava.magma.compiler.exec_var import AbstractExecVar from lava.magma.core.process.message_interface_enum import ActorType -from lava.magma.runtime.message_infrastructure.message_infrastructure_interface\ - import MessageInfrastructureInterface from lava.magma.runtime.message_infrastructure.factory import \ MessageInfrastructureFactory +from lava.magma.runtime.message_infrastructure \ + .message_infrastructure_interface \ + import MessageInfrastructureInterface from lava.magma.runtime.mgmt_token_enums import enum_to_np, enum_equal, \ MGMT_COMMAND, MGMT_RESPONSE from lava.magma.runtime.runtime_services.runtime_service \ @@ -27,7 +29,7 @@ from lava.magma.core.process.process import AbstractProcess from lava.magma.compiler.builders.builder import AbstractProcessBuilder, \ RuntimeChannelBuilderMp, ServiceChannelBuilderMp, \ - RuntimeServiceBuilder, PyProcessBuilder + RuntimeServiceBuilder from lava.magma.compiler.channels.interfaces import Channel from lava.magma.core.resources import HeadNode from lava.magma.core.run_conditions import RunSteps, RunContinuous @@ -35,23 +37,71 @@ from lava.magma.compiler.node import NodeConfig from lava.magma.core.run_conditions import AbstractRunCondition +"""Defines a Runtime which takes a lava executable and a pluggable message +passing infrastructure (for instance multiprocessing+shared memory or ray in +future), builds the components of the executable populated by the compiler +and starts the execution. Runtime is also responsible for auxiliary actions +such as pause, stop, wait (non-blocking run) etc. + +Overall Runtime Architecture: + (c) InVar/ + OutVar/ + RefVar + _____ + (c) runtime_to_service (c) service_to_process | | + ---------------------> ---------------------> | V +(s) Runtime (*s) RuntimeService (*s) Process Models + <--------------------- <--------------------- + (c) service_to_runtime (c) process_to_service + +(s) - Service +(c) - Channel +(*) - Multiple + +Runtime coordinates with multiple RuntimeServices depending on how many got +created. The number of RuntimeServices is determined at compile time based +on the RunConfiguration supplied to the compiler. + +Each RuntimeService is assigned a group of process models it is supposed to +manage. Actions/Commands issued by the Runtime are relayed to the +RuntimeService using the runtime_to_service channel and the response are +returned back using the service_to_runtime channel. + +The RuntimeService further takes this forward for each process model in +similar fashion. A RuntimeService is connected to the process model it is +coordinating by two channels - service_to_process for sending +actions/commands to process model and process_to_service to get response back +from process model. + +Process Models communicate with each other via channels defined by +InVar/OutVar/RefVar ports. +""" + -# Function to build and attach a system process to def target_fn(*args, **kwargs): + """ + Function to build and attach a system process to + + :param args: List Parameters to be passed onto the process + :param kwargs: Dict Parameters to be passed onto the process + :return: None + """ builder = kwargs.pop("builder") actor = builder.build() actor.start(*args, **kwargs) class Runtime: - """Lava runtime which consumes an executable and run run_condition. Exposes + """Lava runtime which consumes an executable and run + run_condition. Exposes the APIs to start, pause, stop and wait on an execution. Execution could - be blocking and non-blocking as specified by the run run_condition.""" + be blocking and non-blocking as specified by the run + run_condition.""" def __init__(self, exe: Executable, message_infrastructure_type: ActorType, - loglevel=logging.WARNING): + loglevel: int = logging.WARNING): self.log = logging.getLogger(__name__) self.log.setLevel(loglevel) self._run_cond: typing.Optional[AbstractRunCondition] = None @@ -61,9 +111,11 @@ def __init__(self, message_infrastructure_type self._messaging_infrastructure: \ ty.Optional[MessageInfrastructureInterface] = None - self._is_initialized = False - self._is_running = False - self._is_started = False + self._is_initialized: bool = False + self._is_running: bool = False + self._is_started: bool = False + self._req_paused: bool = False + self._req_stop: bool = False self.runtime_to_service: ty.Iterable[CspSendPort] = [] self.service_to_runtime: ty.Iterable[CspRecvPort] = [] @@ -74,9 +126,9 @@ def __del__(self): if self._is_started: self.stop() - def initialize(self): + def initialize(self, node_cfg_idx: int = 0): """Initializes the runtime""" - node_config: NodeConfig = self.node_cfg[0] + node_config: NodeConfig = self.node_cfg[node_cfg_idx] if node_config[0].node_type != HeadNode: raise AssertionError @@ -90,6 +142,8 @@ def initialize(self): self._is_initialized = True def _start_ports(self): + """Start the ports of the runtime to communicate with runtime + services""" for port in self.runtime_to_service: port.start() for port in self.service_to_runtime: @@ -101,11 +155,19 @@ def node_cfg(self) -> ty.List[NodeConfig]: return self._executable.node_configs def _build_message_infrastructure(self): + """Create the Messaging Infrastructure Backend given the + _messaging_infrastructure_type and Start it""" self._messaging_infrastructure = MessageInfrastructureFactory.create( self._messaging_infrastructure_type) self._messaging_infrastructure.start() def _get_process_builder_for_process(self, process): + """ + Given a process return its process builder + + :param process: AbstractProcess + :return: AbstractProcessBuilder + """ process_builders: ty.Dict[ "AbstractProcess", "AbstractProcessBuilder" ] = {} @@ -115,6 +177,8 @@ def _get_process_builder_for_process(self, process): return process_builders[process] def _build_channels(self): + """Given the channel builders for an executable, + build these channels""" if self._executable.channel_builders: for channel_builder in self._executable.channel_builders: channel = channel_builder.build( @@ -128,6 +192,8 @@ def _build_channels(self): [channel.dst_port]) def _build_sync_channels(self): + """Builds the channels needed for synchronization between runtime + components""" if self._executable.sync_channel_builders: for sync_channel_builder in self._executable.sync_channel_builders: channel: Channel = sync_channel_builder.build( @@ -148,30 +214,27 @@ def _build_sync_channels(self): self.service_to_runtime.append(channel.dst_port) elif isinstance(sync_channel_builder, ServiceChannelBuilderMp): if isinstance(sync_channel_builder.src_process, - RuntimeServiceBuilder) \ - and isinstance(sync_channel_builder. - dst_process, - PyProcessBuilder): + RuntimeServiceBuilder): sync_channel_builder.src_process.set_csp_proc_ports( [channel.src_port]) self._get_process_builder_for_process( sync_channel_builder.dst_process) \ .set_rs_csp_ports([channel.dst_port]) - elif isinstance(sync_channel_builder. - dst_process, - PyProcessBuilder): + else: sync_channel_builder.dst_process.set_csp_proc_ports( [channel.dst_port]) self._get_process_builder_for_process( sync_channel_builder.src_process) \ .set_rs_csp_ports([channel.src_port]) else: - print(sync_channel_builder.dst_process.__class__.__name__) + self.log.info( + sync_channel_builder.dst_process.__class__.__name__) raise ValueError("Unexpected type of Sync Channel Builder") # ToDo: (AW) Why not pass the builder as an argument to the mp.Process # constructor which will then be passed to the target function? def _build_processes(self): + """Builds the process for all process builders within an executable""" process_builders_collection: ty.List[ ty.Dict[AbstractProcess, AbstractProcessBuilder]] = [ self._executable.py_builders, @@ -189,6 +252,7 @@ def _build_processes(self): builder=proc_builder) def _build_runtime_services(self): + """Builds the runtime services""" runtime_service_builders = self._executable.rs_builders if self._executable.rs_builders: for sd, rs_builder in runtime_service_builders.items(): @@ -196,7 +260,48 @@ def _build_runtime_services(self): target_fn=target_fn, builder=rs_builder) + def _get_resp_for_run(self): + """ + Gets response from RuntimeServices + """ + if self._is_running: + for recv_port in self.service_to_runtime: + data = recv_port.recv() + if enum_equal(data, MGMT_RESPONSE.REQ_PAUSE): + self._req_paused = True + elif enum_equal(data, MGMT_RESPONSE.REQ_STOP): + self._req_stop = True + elif not enum_equal(data, MGMT_RESPONSE.DONE): + if enum_equal(data, MGMT_RESPONSE.ERROR): + # Receive all errors from the ProcessModels + error_cnt = 0 + for actors in \ + self._messaging_infrastructure.actors: + actors.join() + if actors.exception: + _, traceback = actors.exception + self.log.info(traceback) + error_cnt += 1 + raise RuntimeError( + f"{error_cnt} Exception(s) occurred. See " + f"output above for details.") + else: + raise RuntimeError(f"Runtime Received {data}") + if self._req_paused: + self._req_paused = False + self.pause() + if self._req_stop: + self._req_stop = False + self.stop() + self._is_running = False + def start(self, run_condition: AbstractRunCondition): + """ + Given a run condition, starts the runtime + + :param run_condition: AbstractRunCondition + :return: None + """ if self._is_initialized: # Start running self._is_started = True @@ -204,7 +309,13 @@ def start(self, run_condition: AbstractRunCondition): else: self.log.info("Runtime not initialized yet.") - def _run(self, run_condition): + def _run(self, run_condition: AbstractRunCondition): + """ + Helper method for starting the runtime + + :param run_condition: AbstractRunCondition + :return: None + """ if self._is_started: self._is_running = True if isinstance(run_condition, RunSteps): @@ -212,29 +323,11 @@ def _run(self, run_condition): for send_port in self.runtime_to_service: send_port.send(enum_to_np(self.num_steps)) if run_condition.blocking: - for recv_port in self.service_to_runtime: - data = recv_port.recv() - if not enum_equal(data, MGMT_RESPONSE.DONE): - if enum_equal(data, MGMT_RESPONSE.ERROR): - # Receive all errors from the ProcessModels - error_cnt = 0 - for actors in \ - self._messaging_infrastructure.actors: - actors.join() - if actors.exception: - _, traceback = actors.exception - self.log.error(traceback) - error_cnt += 1 - - raise RuntimeError( - f"{error_cnt} Exception(s) occurred. See " - f"output above for details.") - else: - raise RuntimeError(f"Runtime Received {data}") - if run_condition.blocking: - self._is_running = False + self._get_resp_for_run() elif isinstance(run_condition, RunContinuous): - pass + self.num_steps = sys.maxsize + for send_port in self.runtime_to_service: + send_port.send(enum_to_np(self.num_steps)) else: raise ValueError(f"Wrong type of run_condition : " f"{run_condition.__class__}") @@ -242,16 +335,34 @@ def _run(self, run_condition): self.log.info("Runtime not started yet.") def wait(self): + """Waits for existing run to end. This is helpful if the execution + was started in non-blocking mode earlier.""" + self._get_resp_for_run() + + def pause(self): + """Pauses the execution""" if self._is_running: + for send_port in self.runtime_to_service: + send_port.send(MGMT_COMMAND.PAUSE) for recv_port in self.service_to_runtime: data = recv_port.recv() - if not enum_equal(data, MGMT_RESPONSE.DONE): - raise RuntimeError(f"Runtime Received {data}") + if not enum_equal(data, MGMT_RESPONSE.PAUSED): + if enum_equal(data, MGMT_RESPONSE.ERROR): + # Receive all errors from the ProcessModels + error_cnt = 0 + for actors in \ + self._messaging_infrastructure.actors: + actors.join() + if actors.exception: + _, traceback = actors.exception + self.log.info(traceback) + error_cnt += 1 + self.stop() + raise RuntimeError( + f"{error_cnt} Exception(s) occurred. See " + f"output above for details.") self._is_running = False - def pause(self): - raise NotImplementedError - def stop(self): """Stops an ongoing or paused run.""" try: @@ -280,6 +391,10 @@ def join(self): def set_var(self, var_id: int, value: np.ndarray, idx: np.ndarray = None): """Sets value of a variable with id 'var_id'.""" + if self._is_running: + self.log.info( + "WARNING: Cannot Set a Var when the execution is going on") + return node_config: NodeConfig = self._executable.node_configs[0] ev: AbstractExecVar = node_config.exec_vars[var_id] runtime_srv_id: int = ev.runtime_srv_id @@ -299,6 +414,8 @@ def set_var(self, var_id: int, value: np.ndarray, idx: np.ndarray = None): req_port.send(enum_to_np(model_id)) req_port.send(enum_to_np(var_id)) + rsp_port: CspRecvPort = self.service_to_runtime[runtime_srv_id] + # 2. Reshape the data buffer: np.ndarray = value if idx: @@ -312,11 +429,19 @@ def set_var(self, var_id: int, value: np.ndarray, idx: np.ndarray = None): data_port.send(enum_to_np(num_items)) for i in range(num_items): data_port.send(enum_to_np(buffer[0, i], np.float64)) + rsp = rsp_port.recv() + if not enum_equal(rsp, MGMT_RESPONSE.SET_COMPLETE): + raise RuntimeError("Var Set couldn't get successfully " + "completed") else: raise RuntimeError("Runtime has not started") def get_var(self, var_id: int, idx: np.ndarray = None) -> np.ndarray: """Gets value of a variable with id 'var_id'.""" + if self._is_running: + self.log.info( + "WARNING: Cannot Get a Var when the execution is going on") + return node_config: NodeConfig = self._executable.node_configs[0] ev: AbstractExecVar = node_config.exec_vars[var_id] runtime_srv_id: int = ev.runtime_srv_id diff --git a/src/lava/magma/runtime/runtime_services/runtime_service.py b/src/lava/magma/runtime/runtime_services/runtime_service.py index bd31c32f1..aae47d7d5 100644 --- a/src/lava/magma/runtime/runtime_services/runtime_service.py +++ b/src/lava/magma/runtime/runtime_services/runtime_service.py @@ -1,6 +1,7 @@ # Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: BSD-3-Clause # See: https://spdx.org/licenses/ +from abc import abstractmethod import logging import typing as ty @@ -31,11 +32,38 @@ class NxBoard(): pass +"""This file defines the interface for RuntimeService which is responsible for +coordinating the execution of a group of process models belonging to a common +synchronization domain. The domain might follow a SyncProtocol or could be +asynchronous too. The processes and their corresponding process models are +selected by the Runtime depending on the RunConfiguration assigned at the +start of execution. For each group of processes which follow the same +protocol and would execute on the same node, Runtime creates a RuntimeService +which will coordinate all actions/commands from Runtime onto the processes as +well as return any acknowledgement back to Runtime. + +Currently we envision few different kinds of RuntimeService: + +1. PyRuntimeService: (Abstract Class) Coordinates process models executing on + the CPU and written in Python. + Following are the Concrete Implementations: + a. LoihiPyRuntimeService: Coordinates process models executing on + the CPU and written in Python and following the LoihiProtocol. + b. AsyncPyRuntimeService: Coordinates process models executing on + the CPU and written in Python and following the AsyncProtocol. + +2. CRuntimeService: (Abstract Class) Coordinates/Manages process models + executing on the CPU/Embedded CPU and written in C + Following are the Concrete Implementations: + a. LoihiCRuntimeService: Coordinates process models executing on + the CPU/Embedded CPU and written in C and following the LoihiProtocol. +""" + class PyRuntimeService(AbstractRuntimeService): def __init__(self, protocol: ty.Type[AbstractSyncProtocol], - loglevel=logging.WARNING): + loglevel: int = logging.WARNING,): self.log = logging.getLogger(__name__) self.log.setLevel(loglevel) super(PyRuntimeService, self).__init__( @@ -45,6 +73,8 @@ def __init__(self, self.process_to_service: ty.Iterable[CspRecvPort] = [] def start(self): + """Start the necessary channels to coordinate with runtime and group + of processes this RuntimeService is managing""" self.runtime_to_service.start() self.service_to_runtime.start() for i in range(len(self.service_to_process)): @@ -52,7 +82,16 @@ def start(self): self.process_to_service[i].start() self.run() + @abstractmethod + def run(self): + """Override this method to implement the runtime service. The run + method is invoked upon start which called when the execution is + started by the runtime.""" + pass + def join(self): + """Stop the necessary channels to coordinate with runtime and group + of processes this RuntimeService is managing""" self.runtime_to_service.join() self.service_to_runtime.join() @@ -72,25 +111,67 @@ class NcRuntimeService(AbstractRuntimeService): class LoihiPyRuntimeService(PyRuntimeService): """RuntimeService that implements Loihi SyncProtocol in Python.""" - def _next_phase(self, curr_phase, is_last_time_step: bool): + def __init__(self, protocol): + super().__init__(protocol) + self.req_pre_lrn_mgmt = False + self.req_post_lrn_mgmt = False + self.req_lrn = False + self.req_stop = False + self.req_pause = False + self.paused = False + self._error = False + + class Phase: + SPK = enum_to_np(1) + PRE_MGMT = enum_to_np(2) + LRN = enum_to_np(3) + POST_MGMT = enum_to_np(4) + HOST = enum_to_np(5) + + class PMResponse: + STATUS_DONE = enum_to_np(0) + """Signfies Ack or Finished with the Command""" + STATUS_TERMINATED = enum_to_np(-1) + """Signifies Termination""" + STATUS_ERROR = enum_to_np(-2) + """Signifies Error raised""" + STATUS_PAUSED = enum_to_np(-3) + """Signifies Execution State to be Paused""" + REQ_PRE_LRN_MGMT = enum_to_np(-4) + """Signifies Request of PREMPTION""" + REQ_LEARNING = enum_to_np(-5) + """Signifies Request of LEARNING""" + REQ_POST_LRN_MGMT = enum_to_np(-6) + """Signifies Request of PREMPTION""" + REQ_PAUSE = enum_to_np(-7) + """Signifies Request of PAUSE""" + REQ_STOP = enum_to_np(-8) + """Signifies Request of STOP""" + + def _next_phase(self, is_last_time_step: bool): """Advances the current phase to the next phase. On the first time step it starts with HOST phase and advances to SPK. Afterwards it loops: SPK -> PRE_MGMT -> LRN -> POST_MGMT -> SPK On the last time step POST_MGMT advances to HOST phase.""" - if curr_phase == LoihiPhase.SPK: - return LoihiPhase.PRE_MGMT - elif curr_phase == LoihiPhase.PRE_MGMT: - return LoihiPhase.LRN - elif curr_phase == LoihiPhase.LRN: - return LoihiPhase.POST_MGMT - elif curr_phase == LoihiPhase.POST_MGMT and \ - is_last_time_step: - return LoihiPhase.HOST - elif curr_phase == LoihiPhase.POST_MGMT and not \ - is_last_time_step: - return LoihiPhase.SPK - elif curr_phase == LoihiPhase.HOST: - return LoihiPhase.SPK + if self.req_pre_lrn_mgmt: + self.req_pre_lrn_mgmt = False + return LoihiPyRuntimeService.Phase.PRE_MGMT + if self.req_post_lrn_mgmt: + self.req_post_lrn_mgmt = False + return LoihiPyRuntimeService.Phase.POST_MGMT + if self.req_lrn: + self.req_lrn = False + return LoihiPyRuntimeService.Phase.LRN + if self.req_pause: + self.req_pause = False + return MGMT_COMMAND.PAUSE + if self.req_stop: + self.req_stop = False + return MGMT_COMMAND.STOP + + if is_last_time_step: + return LoihiPyRuntimeService.Phase.HOST + return LoihiPyRuntimeService.Phase.SPK def _send_pm_cmd(self, phase: MGMT_COMMAND): """Sends a command (phase information) to all ProcessModels.""" @@ -113,6 +194,29 @@ def _get_pm_resp(self) -> ty.Iterable[MGMT_RESPONSE]: ptos_recv_port = self.process_to_service[counter] rcv_msgs.append(ptos_recv_port.recv()) counter += 1 + for idx, recv_msg in enumerate(rcv_msgs): + if enum_equal(recv_msg, + LoihiPyRuntimeService.PMResponse.STATUS_ERROR): + self._error = True + if enum_equal(recv_msg, + LoihiPyRuntimeService.PMResponse.REQ_PRE_LRN_MGMT): + self.req_pre_lrn_mgmt = True + if enum_equal(recv_msg, + LoihiPyRuntimeService.PMResponse.REQ_POST_LRN_MGMT): + self.req_post_lrn_mgmt = True + if enum_equal(recv_msg, + LoihiPyRuntimeService.PMResponse.REQ_LEARNING): + self.req_lrn = True + if enum_equal(recv_msg, + LoihiPyRuntimeService.PMResponse.REQ_PAUSE): + # ToDo: Add some mechanism to get the exact process id + self.log.info(f"Process : {idx} has requested Pause") + self.req_pause = True + if enum_equal(recv_msg, + LoihiPyRuntimeService.PMResponse.REQ_STOP): + # ToDo: Add some mechanism to get the exact process id + self.log.info(f"Process : {idx} has requested Stop") + self.req_stop = True return rcv_msgs def _relay_to_runtime_data_given_model_id(self, model_id: int): @@ -127,19 +231,21 @@ def _relay_to_runtime_data_given_model_id(self, model_id: int): value = data_recv_port.recv() data_relay_port.send(value) - def _relay_to_pm_data_given_model_id(self, model_id: int): + def _relay_to_pm_data_given_model_id(self, model_id: int) -> MGMT_RESPONSE: """Relays data received from the runtime to the ProcessModel given by the model id.""" process_idx = self.model_ids.index(model_id) - data_recv_port = self.runtime_to_service data_relay_port = self.service_to_process[process_idx] + resp_port = self.process_to_service[process_idx] # Receive and relay number of items num_items = data_recv_port.recv() data_relay_port.send(num_items) # Receive and relay data1, data2, ... for i in range(int(num_items[0].item())): data_relay_port.send(data_recv_port.recv()) + rsp = resp_port.recv() + return rsp def _relay_pm_ack_given_model_id(self, model_id: int): """Relays ack received from ProcessModel given by model id to the @@ -150,6 +256,30 @@ def _relay_pm_ack_given_model_id(self, model_id: int): ack_relay_port = self.service_to_runtime ack_relay_port.send(ack_recv_port.recv()) + def _handle_pause(self): + # Inform all ProcessModels about the PAUSE command + self._send_pm_cmd(MGMT_COMMAND.PAUSE) + rsps = self._get_pm_resp() + for rsp in rsps: + if not enum_equal(rsp, + LoihiPyRuntimeService.PMResponse.STATUS_PAUSED): + raise ValueError(f"Wrong Response Received : {rsp}") + # Inform the runtime about successful pausing + self.service_to_runtime.send(MGMT_RESPONSE.PAUSED) + + def _handle_stop(self): + # Inform all ProcessModels about the STOP command + self._send_pm_cmd(MGMT_COMMAND.STOP) + rsps = self._get_pm_resp() + for rsp in rsps: + if not enum_equal(rsp, + LoihiPyRuntimeService.PMResponse.STATUS_TERMINATED + ): + raise ValueError(f"Wrong Response Received : {rsp}") + # Inform the runtime about successful termination + self.service_to_runtime.send(MGMT_RESPONSE.TERMINATED) + self.join() + def run(self): """Retrieves commands from the runtime. On STOP or PAUSE commands all ProcessModels are notified and expected to TERMINATE or PAUSE, @@ -165,36 +295,19 @@ def run(self): while True: # Probe if there is a new command from the runtime action = selector.select(*channel_actions) - if action == 'cmd': command = self.runtime_to_service.recv() if enum_equal(command, MGMT_COMMAND.STOP): - # Inform all ProcessModels about the STOP command - self._send_pm_cmd(command) - rsps = self._get_pm_resp() - for rsp in rsps: - if not enum_equal(rsp, MGMT_RESPONSE.TERMINATED): - raise ValueError( - f"Wrong Response Received : {rsp}") - # Inform the runtime about successful termination - self.service_to_runtime.send(MGMT_RESPONSE.TERMINATED) - self.join() + self._handle_stop() return elif enum_equal(command, MGMT_COMMAND.PAUSE): - # Inform all ProcessModels about the PAUSE command - self._send_pm_cmd(command) - rsps = self._get_pm_resp() - for rsp in rsps: - if not enum_equal(rsp, MGMT_RESPONSE.PAUSED): - raise ValueError( - f"Wrong Response Received : {rsp}") - # Inform the runtime about successful pausing - self.service_to_runtime.send(MGMT_RESPONSE.PAUSED) - break + self._handle_pause() + self.paused = True elif enum_equal(command, MGMT_COMMAND.GET_DATA) or \ enum_equal(command, MGMT_COMMAND.SET_DATA): self._handle_get_set(phase, command) else: + self.paused = False # The number of time steps was received ("command") # Start iterating through Loihi phases curr_time_step = 0 @@ -204,7 +317,15 @@ def run(self): is_last_ts = enum_equal(enum_to_np(curr_time_step), command) # Advance to the next phase - phase = self._next_phase(phase, is_last_ts) + phase = self._next_phase(is_last_ts) + if enum_equal(phase, MGMT_COMMAND.STOP): + self.service_to_runtime.send( + MGMT_RESPONSE.REQ_STOP) + break + if enum_equal(phase, MGMT_COMMAND.PAUSE): + self.service_to_runtime.send( + MGMT_RESPONSE.REQ_PAUSE) + break # Increase time step if spiking phase if enum_equal(phase, LoihiPhase.SPK): curr_time_step += 1 @@ -212,28 +333,41 @@ def run(self): self._send_pm_cmd(phase) # ProcessModels respond with DONE if not HOST phase if not enum_equal( - phase, LoihiPhase.HOST): - - for rsp in self._get_pm_resp(): - if not enum_equal(rsp, MGMT_RESPONSE.DONE): - if enum_equal(rsp, MGMT_RESPONSE.ERROR): - # Forward error to runtime - self.service_to_runtime.send( - MGMT_RESPONSE.ERROR) - # stop all other pm - self._send_pm_cmd(MGMT_COMMAND.STOP) - return - else: - raise ValueError( - f"Wrong Response Received : {rsp}") + phase, LoihiPyRuntimeService.Phase.HOST): + self._get_pm_resp() + if self._error: + # Forward error to runtime + self.service_to_runtime.send( + MGMT_RESPONSE.ERROR) + # stop all other pm + self._send_pm_cmd(MGMT_COMMAND.STOP) + return + # Check if pause or stop received from Runtime + # TODO: Do we actualy need to wait for PMs to be in + # HOST or MGMT phase to stop or pause them? + if self.runtime_to_service.probe(): + cmd = self.runtime_to_service.peek() + if enum_equal(cmd, MGMT_COMMAND.STOP): + self.runtime_to_service.recv() + self._handle_stop() + return + if enum_equal(cmd, MGMT_COMMAND.PAUSE): + self.runtime_to_service.recv() + self._handle_pause() + self.paused = True + break # If HOST phase (last time step ended) break the loop if enum_equal( phase, LoihiPhase.HOST): break - + if self.paused or enum_equal(phase, MGMT_COMMAND.STOP) or \ + enum_equal(phase, MGMT_COMMAND.PAUSE): + continue # Inform the runtime that last time step was reached self.service_to_runtime.send(MGMT_RESPONSE.DONE) + else: + self.service_to_runtime.send(MGMT_RESPONSE.ERROR) def _handle_get_set(self, phase, command): if enum_equal(phase, LoihiPhase.HOST): @@ -252,7 +386,8 @@ def _handle_get_set(self, phase, command): # recv var_id requests.append(self.runtime_to_service.recv()) self._send_pm_req_given_model_id(model_id, *requests) - self._relay_to_pm_data_given_model_id(model_id) + rsp = self._relay_to_pm_data_given_model_id(model_id) + self.service_to_runtime.send(rsp) else: raise RuntimeError(f"Unknown request {command}") @@ -265,6 +400,26 @@ class LoihiCRuntimeService(AbstractRuntimeService): class AsyncPyRuntimeService(PyRuntimeService): """RuntimeService that implements Async SyncProtocol in Py.""" + def __init__(self, protocol): + super().__init__(protocol) + self.req_stop = False + self.req_pause = False + self._error = False + + class PMResponse: + STATUS_DONE = enum_to_np(0) + """Signfies Ack or Finished with the Command""" + STATUS_TERMINATED = enum_to_np(-1) + """Signifies Termination""" + STATUS_ERROR = enum_to_np(-2) + """Signifies Error raised""" + STATUS_PAUSED = enum_to_np(-3) + """Signifies Execution State to be Paused""" + REQ_PAUSE = enum_to_np(-4) + """Signifies Request of PAUSE""" + REQ_STOP = enum_to_np(-5) + """Signifies Request of STOP""" + def _send_pm_cmd(self, cmd: MGMT_COMMAND): for stop_send_port in self.service_to_process: stop_send_port.send(cmd) @@ -275,25 +430,67 @@ def _get_pm_resp(self) -> ty.Iterable[MGMT_RESPONSE]: rcv_msgs.append(ptos_recv_port.recv()) return rcv_msgs + def _handle_pause(self): + # Inform the runtime about successful pausing + self.service_to_runtime.send(MGMT_RESPONSE.PAUSED) + + def _handle_stop(self): + self._send_pm_cmd(MGMT_COMMAND.STOP) + rsps = self._get_pm_resp() + for rsp in rsps: + if not enum_equal(rsp, + LoihiPyRuntimeService.PMResponse.STATUS_TERMINATED + ): + self.service_to_runtime.send(MGMT_RESPONSE.ERROR) + raise ValueError(f"Wrong Response Received : {rsp}") + # Inform the runtime about successful termination + self.service_to_runtime.send(MGMT_RESPONSE.TERMINATED) + self.join() + def run(self): + """Retrieves commands from the runtime and relays them to the process + models. Also send the acknowledgement back to runtime.""" + selector = CspSelector() + channel_actions = [(self.runtime_to_service, lambda: 'cmd')] while True: - command = self.runtime_to_service.recv() - if enum_equal(command, MGMT_COMMAND.STOP): - self._send_pm_cmd(command) - rsps = self._get_pm_resp() - for rsp in rsps: - if not enum_equal(rsp, MGMT_RESPONSE.TERMINATED): - raise ValueError(f"Wrong Response Received : {rsp}") - self.service_to_runtime.send(MGMT_RESPONSE.TERMINATED) - self.join() - return + # Probe if there is a new command from the runtime + action = selector.select(*channel_actions) + channel_actions = [] + if action == 'cmd': + command = self.runtime_to_service.recv() + if enum_equal(command, MGMT_COMMAND.STOP): + self._handle_stop() + return + elif enum_equal(command, MGMT_COMMAND.PAUSE): + self._handle_pause() + else: + self._send_pm_cmd(MGMT_COMMAND.RUN) + for ptos_recv_port in self.process_to_service: + channel_actions.append((ptos_recv_port, + lambda: 'resp')) + elif action == 'resp': + resps = self._get_pm_resp() + for resp in resps: + if enum_equal(resp, + AsyncPyRuntimeService.PMResponse.REQ_PAUSE): + self.req_pause = True + if enum_equal(resp, + AsyncPyRuntimeService.PMResponse.REQ_STOP): + self.req_stop = True + if enum_equal(resp, + AsyncPyRuntimeService.PMResponse.STATUS_ERROR + ): + self._error = True + if self.req_stop: + self.service_to_runtime.send(MGMT_RESPONSE.REQ_STOP) + if self.req_pause: + self.service_to_runtime.send(MGMT_RESPONSE.REQ_PAUSE) + if self._error: + self.service_to_runtime.send(MGMT_RESPONSE.ERROR) else: - self._send_pm_cmd(MGMT_COMMAND.RUN) - rsps = self._get_pm_resp() - for rsp in rsps: - if not enum_equal(rsp, MGMT_RESPONSE.DONE): - raise ValueError(f"Wrong Response Received : {rsp}") - self.service_to_runtime.send(MGMT_RESPONSE.DONE) + self.service_to_runtime.send(MGMT_RESPONSE.ERROR) + raise ValueError(f"Wrong type of channel action : {action}") + channel_actions.append((self.runtime_to_service, lambda: 'cmd')) class NxSdkRuntimeService(NcRuntimeService): @@ -311,7 +508,7 @@ class NxSdkRuntimeService(NcRuntimeService): def __init__(self, protocol: ty.Type[AbstractSyncProtocol], loihi_version: LoihiVersion = LoihiVersion.N3, - loglevel=logging.WARNING): + loglevel: int = logging.WARNING): self.log = logging.getLogger(__name__) self.log.setLevel(loglevel) super(NxSdkRuntimeService, self).__init__( @@ -323,7 +520,7 @@ def __init__(self, try: if loihi_version == LoihiVersion.N3: from nxsdk.arch.n3b.n3board import N3Board - # # TODO: Need to find good way to set Board Init + # # TODO: Use dynamic set Board Init self.board = N3Board(1, 1, [2], [[5, 5]]) elif loihi_version == LoihiVersion.N2: from nxsdk.arch.n2a.n2board import N2Board # noqa F401 @@ -336,10 +533,10 @@ class NxBoard(): pass self.board = NxBoard() - def run(self): - self.num_steps = self.runtime_to_service.recv() - self.service_to_runtime.send(MGMT_RESPONSE.DONE) + self.log.debug("NxSdkRuntimeService is initialized") + def run(self): + self.log.debug("NxSdkRuntime is running") selector = CspSelector() channel_actions = [(self.runtime_to_service, lambda: 'cmd')] @@ -347,6 +544,7 @@ def run(self): action = selector.select(*channel_actions) if action == 'cmd': command = self.runtime_to_service.recv() + self.log.debug("Recieved command: " + str(command[0])) if enum_equal(command, MGMT_COMMAND.STOP): self.board.stop() @@ -358,7 +556,13 @@ def run(self): self.service_to_runtime.send(MGMT_RESPONSE.PAUSED) break - elif enum_equal(command, MGMT_COMMAND.RUN): + # If message recieved from Runtime is greater than zero + # it is the num_steps for a run, use num_steps to start + # a run + elif command[0] > 0: + self.log.debug("Command: " + str(command[0]) + + " > 0, setting num_steps and running") + self.num_steps = command self.board.run(numSteps=self.num_steps, aSync=False) self.service_to_runtime.send(MGMT_RESPONSE.DONE) diff --git a/src/lava/proc/io/dataloader.py b/src/lava/proc/io/dataloader.py index a478d916e..f1554ddce 100644 --- a/src/lava/proc/io/dataloader.py +++ b/src/lava/proc/io/dataloader.py @@ -113,7 +113,7 @@ def run_spk(self) -> None: self.ground_truth.send(self.ground_truth_array()) def post_guard(self) -> None: - return (self.current_ts - 1) % self.interval == self.offset + return (self.time_step - 1) % self.interval == self.offset def run_post_mgmt(self) -> None: data, self.ground_truth_state = self.dataset[self.sample_id] @@ -181,7 +181,7 @@ def run_spk(self) -> None: self.sample_time += 1 def post_guard(self) -> None: - return (self.current_ts - 1) % self.interval == self.offset + return (self.time_step - 1) % self.interval == self.offset def run_post_mgmt(self) -> None: data, self.ground_truth_state = self.dataset[self.sample_id] diff --git a/src/lava/proc/io/reset.py b/src/lava/proc/io/reset.py index 207c4e2d3..a8f6c8b69 100644 --- a/src/lava/proc/io/reset.py +++ b/src/lava/proc/io/reset.py @@ -55,7 +55,7 @@ class AbstractPyReset(PyLoihiProcessModel): offset: np.ndarray = LavaPyType(np.ndarray, int) def post_guard(self) -> None: - return self.current_ts % self.interval == self.offset + return self.time_step % self.interval == self.offset def run_post_mgmt(self) -> None: self.state.write(0 * self.state.read() + self.reset_value) diff --git a/src/lava/proc/io/sink.py b/src/lava/proc/io/sink.py index 5861ad8c0..ae74c8e79 100644 --- a/src/lava/proc/io/sink.py +++ b/src/lava/proc/io/sink.py @@ -48,7 +48,7 @@ def run_spk(self) -> None: """Receive spikes and store in an internal variable""" data = self.a_in.recv() buffer = self.data.shape[-1] - self.data[..., (self.current_ts - 1) % buffer] = data + self.data[..., (self.time_step - 1) % buffer] = data @implements(proc=RingBuffer, protocol=LoihiProtocol) @@ -117,7 +117,7 @@ def __init__(self, proc_params: Dict[str, Any]) -> None: self.counter = 0 def post_guard(self) -> None: - return (self.current_ts - 1) % self.interval == self.offset + return (self.time_step - 1) % self.interval == self.offset def run_post_mgmt(self) -> None: data = self.state.read() diff --git a/src/lava/proc/io/source.py b/src/lava/proc/io/source.py index 236c71b46..94feeed5f 100644 --- a/src/lava/proc/io/source.py +++ b/src/lava/proc/io/source.py @@ -38,7 +38,7 @@ class AbstractPyRingBuffer(PyLoihiProcessModel): def run_spk(self) -> None: buffer = self.data.shape[-1] - self.s_out.send(self.data[..., (self.current_ts - 1) % buffer]) + self.s_out.send(self.data[..., (self.time_step - 1) % buffer]) @implements(proc=RingBuffer, protocol=LoihiProtocol) diff --git a/src/lava/proc/monitor/models.py b/src/lava/proc/monitor/models.py index 4c3bc6724..3a13d8c8d 100644 --- a/src/lava/proc/monitor/models.py +++ b/src/lava/proc/monitor/models.py @@ -28,10 +28,10 @@ class PyMonitorModel(PyLoihiProcessModel): ref_port_0: PyRefPort = LavaPyType(PyRefPort.VEC_DENSE, int) in_port_0: PyInPort = LavaPyType(PyInPort.VEC_DENSE, int) - def pre_guard(self): + def post_guard(self): return True - def run_pre_mgmt(self): + def run_post_mgmt(self): """ During this phase, RefPorts of Monitor process collects data from monitored Vars @@ -43,7 +43,7 @@ def run_pre_mgmt(self): for i in range(self.proc_params["n_ref_ports"]): ref_port_name = self.proc_params["RefPorts"][i] var_read_name = self.proc_params["VarsData1"][i] - getattr(self, var_read_name)[self.current_ts - 1, ...] = \ + getattr(self, var_read_name)[self.time_step - 1, ...] = \ np.squeeze(np.array(getattr(self, ref_port_name).read())) def run_spk(self): @@ -57,5 +57,5 @@ def run_spk(self): for i in range(self.proc_params["n_in_ports"]): in_port_name = self.proc_params["InPorts"][i] out_read_name = self.proc_params["VarsData2"][i] - getattr(self, out_read_name)[self.current_ts - 1, ...] = \ + getattr(self, out_read_name)[self.time_step - 1, ...] = \ np.squeeze(np.array(getattr(self, in_port_name).recv())) diff --git a/tests/lava/magma/compiler/test_compiler.py b/tests/lava/magma/compiler/test_compiler.py index 9df84bb21..78496b3b0 100644 --- a/tests/lava/magma/compiler/test_compiler.py +++ b/tests/lava/magma/compiler/test_compiler.py @@ -56,6 +56,7 @@ def __init__(self, **kwargs): class MockRuntimeService: + __name__ = "MockRuntimeService" pass @@ -126,9 +127,12 @@ def run(self): # A minimal RunConfig that will select SubProcModel if there is one class RunCfg(RunConfig): - def __init__(self, custom_sync_domains=None, select_sub_proc_model=False): + def __init__(self, + loglevel: int = logging.WARNING, + custom_sync_domains=None, + select_sub_proc_model=False): super().__init__(custom_sync_domains=custom_sync_domains, - loglevel=logging.WARNING) + loglevel=loglevel) self.select_sub_proc_model = select_sub_proc_model def select(self, proc, proc_models): @@ -491,7 +495,10 @@ def test_create_sync_domain(self): # Processes not assigned to a custom sync domain will get assigned # automatically to a default sync domain created for each unique sync # protocol - sd = Compiler._create_sync_domains(proc_map, run_cfg, []) + c = Compiler() + sd = c._create_sync_domains(proc_map, run_cfg, + [], + log=c.log) # We expect 5 sync domains: The 2 custom ones and 2 default ones # created for p3 and p5 and 1 AsyncDomain for processes not @@ -524,7 +531,10 @@ def test_create_sync_domains_run_config_without_sync_domain(self): # In this case, only default SyncDomains will be chosen based on the # implemented SyncProtocols of each ProcessModel c = Compiler() - sd = c._create_sync_domains(proc_map, run_cfg, []) + sd = c._create_sync_domains(proc_map, + run_cfg, + [], + log=c.log) self.assertEqual(len(sd[0]), 2) self.assertEqual(sd[0][0].protocol.__class__, ProtocolA) @@ -544,7 +554,10 @@ def test_create_sync_domains_proc_assigned_to_multiple_domains(self): c = Compiler() with self.assertRaises(AssertionError): - c._create_sync_domains(proc_map, run_cfg, []) + c._create_sync_domains(proc_map, + run_cfg, + [], + log=c.log) def test_create_sync_domains_proc_assigned_to_incompatible_domain(self): """Checks that a process can only be assigned to a sync domain with a @@ -563,7 +576,10 @@ def test_create_sync_domains_proc_assigned_to_incompatible_domain(self): # In this case, sync domain creation will fail c = Compiler() with self.assertRaises(AssertionError): - c._create_sync_domains(proc_map, run_cfg, []) + c._create_sync_domains(proc_map, + run_cfg, + [], + log=c.log) def test_create_sync_domain_non_unique_domain_names(self): """Checks that sync domain names must be unique.""" @@ -581,7 +597,10 @@ def test_create_sync_domain_non_unique_domain_names(self): # This does not compile because domain names must be unique c = Compiler() with self.assertRaises(AssertionError): - c._create_sync_domains(proc_map, run_cfg, []) + c._create_sync_domains(proc_map, + run_cfg, + [], + log=c.log) def test_create_node_cfgs(self): """Checks creation of NodeConfigs. @@ -597,7 +616,7 @@ def test_create_node_cfgs(self): # This creates the naive NodeConfig for now: c = Compiler() - ncfgs = c._create_node_cfgs(proc_map) + ncfgs = c._create_node_cfgs(proc_map, log=c.log) # It will be a single NodeCfg of type HeadNode containing all processes from lava.magma.core.resources import HeadNode @@ -771,7 +790,7 @@ def test_create_py_exec_vars(self): # First we need to compile a NodeConfig c = Compiler() - node_cfgs = c._create_node_cfgs(proc_map) + node_cfgs = c._create_node_cfgs(proc_map, log=c.log) # Creating exec_vars adds any ExecVars to each NodeConfig c._create_exec_vars(node_cfgs, proc_map, { diff --git a/tests/lava/magma/core/model/test_py_model.py b/tests/lava/magma/core/model/test_py_model.py index 107c8f93f..57ce76805 100644 --- a/tests/lava/magma/core/model/test_py_model.py +++ b/tests/lava/magma/core/model/test_py_model.py @@ -1,26 +1,25 @@ # Copyright (C) 2021 Intel Corporation # SPDX-License-Identifier: BSD-3-Clause # See: https://spdx.org/licenses/ -import unittest import typing as ty +import unittest import numpy as np -from lava.magma.core.process.process import AbstractProcess -from lava.magma.core.process.variable import Var -from lava.magma.core.process.ports.ports import InPort, OutPort, RefPort, \ - VarPort +from lava.magma.compiler.builders.builder import PyProcessBuilder +from lava.magma.compiler.channels.interfaces import AbstractCspPort +from lava.magma.compiler.utils import VarInitializer, PortInitializer, \ + VarPortInitializer from lava.magma.core.decorator import implements, requires -from lava.magma.core.resources import CPU from lava.magma.core.model.py.model import AbstractPyProcessModel -from lava.magma.core.model.py.type import LavaPyType from lava.magma.core.model.py.ports import PyInPort, PyOutPort, PyRefPort, \ PyVarPort - -from lava.magma.compiler.utils import VarInitializer, PortInitializer, \ - VarPortInitializer -from lava.magma.compiler.builders.builder import PyProcessBuilder -from lava.magma.compiler.channels.interfaces import AbstractCspPort +from lava.magma.core.model.py.type import LavaPyType +from lava.magma.core.process.ports.ports import InPort, OutPort, RefPort, \ + VarPort +from lava.magma.core.process.process import AbstractProcess +from lava.magma.core.process.variable import Var +from lava.magma.core.resources import CPU # A test Process with a variety of Ports and Vars of different shapes, @@ -51,6 +50,9 @@ class ProcModel(AbstractPyProcessModel): v4_tensor = LavaPyType(np.ndarray, np.int32, 6) out_port: PyOutPort = LavaPyType(PyOutPort.VEC_DENSE, int, 8) + def add_ports_for_polling(self): + pass + def run(self): """Every PyProcModel must implement a run(..) method. Here we perform just some fake computation to demonstrate initialized Vars can be used. @@ -90,7 +92,7 @@ def join(self): class ProcForLavaPyType(AbstractProcess): def __init__(self, **kwargs): super().__init__(**kwargs) - self.port = InPort((1, )) + self.port = InPort((1,)) # A correct ProcessModel @@ -99,6 +101,9 @@ def __init__(self, **kwargs): class ProcModelForLavaPyType0(AbstractPyProcessModel): port: PyInPort = LavaPyType(PyInPort.VEC_DENSE, int) + def add_ports_for_polling(self): + pass + # A wrong ProcessModel with completely wrong type @implements(proc=ProcForLavaPyType) @@ -106,6 +111,9 @@ class ProcModelForLavaPyType0(AbstractPyProcessModel): class ProcModelForLavaPyType1(AbstractPyProcessModel): port: PyInPort = LavaPyType(123, int) # type: ignore + def add_ports_for_polling(self): + pass + # A wrong ProcessModel with wrong sub type @implements(proc=ProcForLavaPyType) @@ -113,6 +121,9 @@ class ProcModelForLavaPyType1(AbstractPyProcessModel): class ProcModelForLavaPyType2(AbstractPyProcessModel): port: PyInPort = LavaPyType(PyInPort, int) + def add_ports_for_polling(self): + pass + # A wrong ProcessModel with wrong port type @implements(proc=ProcForLavaPyType) @@ -120,6 +131,9 @@ class ProcModelForLavaPyType2(AbstractPyProcessModel): class ProcModelForLavaPyType3(AbstractPyProcessModel): port: PyInPort = LavaPyType(PyOutPort, int) + def add_ports_for_polling(self): + pass + # A minimal process to test RefPorts and VarPorts class ProcRefVar(AbstractProcess): @@ -138,6 +152,9 @@ class PyProcModelRefVar(AbstractPyProcessModel): var: np.ndarray = LavaPyType(np.ndarray, np.int32) var_port: PyVarPort = LavaPyType(PyVarPort.VEC_DENSE, int) + def add_ports_for_polling(self): + pass + class TestPyProcessBuilder(unittest.TestCase): """ProcessModels are not not created directly but through a corresponding @@ -191,7 +208,7 @@ def test_set_variables_and_ports(self): self.assertEqual(list(b.vars.values()), v) self.assertEqual(list(b.py_ports.values()), py_ports) self.assertEqual(list(v for vv in b.csp_ports.values() - for v in vv), csp_ports) + for v in vv), csp_ports) self.assertEqual(b.vars["v1_scalar"], v[0]) self.assertEqual(b.py_ports["in_port"], py_ports[0]) self.assertEqual(b.csp_ports["out_port"], [csp_ports[1]]) diff --git a/tests/lava/magma/core/process/test_lif_dense_lif.py b/tests/lava/magma/core/process/test_lif_dense_lif.py index f6a1ddaec..b13a791a2 100644 --- a/tests/lava/magma/core/process/test_lif_dense_lif.py +++ b/tests/lava/magma/core/process/test_lif_dense_lif.py @@ -1,7 +1,6 @@ # Copyright (C) 2021 Intel Corporation # SPDX-License-Identifier: BSD-3-Clause # See: https://spdx.org/licenses/ -import logging import unittest from lava.magma.core.process.process import AbstractProcess @@ -14,9 +13,8 @@ class SimpleRunConfig(RunConfig): def __init__(self, **kwargs): sync_domains = kwargs.pop("sync_domains") - super().__init__(custom_sync_domains=sync_domains, - loglevel=logging.WARNING) self.model = None + super().__init__(custom_sync_domains=sync_domains) if "model" in kwargs: self.model = kwargs.pop("model") diff --git a/tests/lava/magma/core/process/test_process.py b/tests/lava/magma/core/process/test_process.py index 3a25f9a9a..14cfb4c1e 100644 --- a/tests/lava/magma/core/process/test_process.py +++ b/tests/lava/magma/core/process/test_process.py @@ -3,19 +3,18 @@ # See: https://spdx.org/licenses/ import unittest -from lava.magma.core.process.process import ( - AbstractProcess, - Collection, - ProcessServer, -) -from lava.magma.core.process.variable import Var from lava.magma.core.process.ports.ports import ( InPort, OutPort, RefPort, VarPort, ) -from lava.magma.core.run_conditions import RunSteps, RunContinuous +from lava.magma.core.process.process import ( + AbstractProcess, + Collection, + ProcessServer, +) +from lava.magma.core.process.variable import Var class MinimalProcess(AbstractProcess): @@ -261,19 +260,6 @@ class Proc(AbstractProcess): yet_another_proc = Proc() self.assertFalse(yet_another_proc.is_sub_proc_of(proc1)) - # TODO: (PP) Modify unit test when non-blocking execution is implemented - def test_non_blocking_execution_fail(self): - """Checks that non-blocking execution raises an NotImplementedError, as - non-blocking execution is currently not available.""" - - proc = MinimalProcess() - - with self.assertRaises(NotImplementedError): - proc.run(RunSteps(num_steps=0, blocking=False), ...) - - with self.assertRaises(NotImplementedError): - proc.run(RunContinuous(), ...) - if __name__ == "__main__": unittest.main() diff --git a/tests/lava/magma/runtime/test_async_protocol.py b/tests/lava/magma/runtime/test_async_protocol.py new file mode 100644 index 000000000..32d58a079 --- /dev/null +++ b/tests/lava/magma/runtime/test_async_protocol.py @@ -0,0 +1,78 @@ +import unittest + +from lava.magma.core.decorator import implements, requires +from lava.magma.core.model.py.model import PyAsyncProcessModel +from lava.magma.core.model.py.type import LavaPyType +from lava.magma.core.process.process import AbstractProcess +from lava.magma.core.process.variable import Var +from lava.magma.core.resources import CPU +from lava.magma.core.run_conditions import RunContinuous +from lava.magma.core.run_configs import RunConfig +from lava.magma.core.sync.domain import SyncDomain +from lava.magma.core.sync.protocols.async_protocol import AsyncProtocol + + +class SimpleProcess(AbstractProcess): + def __init__(self, **kwargs): + super().__init__(**kwargs) + shape = kwargs["shape"] + self.u = Var(shape=shape, init=0) + self.v = Var(shape=shape, init=0) + + +class SimpleRunConfig(RunConfig): + def __init__(self, **kwargs): + sync_domains = kwargs.pop("sync_domains") + super().__init__(custom_sync_domains=sync_domains) + self.model = None + if "model" in kwargs: + self.model = kwargs.pop("model") + + def select(self, process, proc_models): + if self.model is not None: + if self.model == "sub" and isinstance(process, SimpleProcess): + return proc_models[1] + + return proc_models[0] + + +@implements(proc=SimpleProcess, protocol=AsyncProtocol) +@requires(CPU) +class SimpleProcessModel(PyAsyncProcessModel): + u = LavaPyType(int, int) + v = LavaPyType(int, int) + + def run_async(self): + while True: + self.u = self.u + 10 + self.v = self.v + 1000 + if self.check_for_stop_cmd(): + return + + +class TestProcess(unittest.TestCase): + def test_async_process_model(self): + """ + Verifies the working of Asynchronous Process + """ + process = SimpleProcess(shape=(2, 2)) + simple_sync_domain = SyncDomain("simple", AsyncProtocol(), [process]) + run_config = SimpleRunConfig(sync_domains=[simple_sync_domain]) + process.run(condition=RunContinuous(), run_cfg=run_config) + process.stop() + + def test_async_process_model_pause(self): + """ + Verifies the working of Asynchronous Process, pause should have no + effect + """ + process = SimpleProcess(shape=(2, 2)) + simple_sync_domain = SyncDomain("simple", AsyncProtocol(), [process]) + run_config = SimpleRunConfig(sync_domains=[simple_sync_domain]) + process.run(condition=RunContinuous(), run_cfg=run_config) + process.pause() + process.stop() + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/lava/magma/runtime/test_exception_handling.py b/tests/lava/magma/runtime/test_exception_handling.py index b0e2b81c9..495645b9a 100644 --- a/tests/lava/magma/runtime/test_exception_handling.py +++ b/tests/lava/magma/runtime/test_exception_handling.py @@ -20,21 +20,24 @@ # A minimal process with an OutPort class P1(AbstractProcess): def __init__(self, **kwargs): - super().__init__(loglevel=logging.CRITICAL, **kwargs) + super().__init__( + loglevel=logging.CRITICAL, **kwargs) self.out = OutPort(shape=(2,)) # A minimal process with an InPort class P2(AbstractProcess): def __init__(self, **kwargs): - super().__init__(loglevel=logging.CRITICAL, **kwargs) + super().__init__( + loglevel=logging.CRITICAL, **kwargs) self.inp = InPort(shape=(2,)) # A minimal process with an InPort class P3(AbstractProcess): def __init__(self, **kwargs): - super().__init__(loglevel=logging.CRITICAL, **kwargs) + super().__init__( + loglevel=logging.CRITICAL, **kwargs) self.inp = InPort(shape=(2,)) @@ -46,7 +49,7 @@ class PyProcModel1(PyLoihiProcessModel): out: PyOutPort = LavaPyType(PyOutPort.VEC_DENSE, int) def run_spk(self): - if self.current_ts > 1: + if self.time_step > 1: # Raise exception raise AssertionError("All the error info") @@ -59,7 +62,7 @@ class PyProcModel2(PyLoihiProcessModel): inp: PyInPort = LavaPyType(PyInPort.VEC_DENSE, int) def run_spk(self): - if self.current_ts > 1: + if self.time_step > 1: # Raise exception raise TypeError("All the error info") @@ -84,7 +87,8 @@ def test_one_pm(self): proc = P1() run_steps = RunSteps(num_steps=1) - run_cfg = Loihi1SimCfg(loglevel=logging.CRITICAL) + run_cfg = Loihi1SimCfg( + loglevel=logging.CRITICAL) # Run the network for 1 time step -> no exception proc.run(condition=run_steps, run_cfg=run_cfg) @@ -107,7 +111,8 @@ def test_two_pm(self): recv = P2() run_steps = RunSteps(num_steps=1) - run_cfg = Loihi1SimCfg(loglevel=logging.CRITICAL) + run_cfg = Loihi1SimCfg( + loglevel=logging.CRITICAL) # Connect sender with receiver sender.out.connect(recv.inp) @@ -134,7 +139,8 @@ def test_three_pm(self): recv2 = P3() run_steps = RunSteps(num_steps=1) - run_cfg = Loihi1SimCfg(loglevel=logging.CRITICAL) + run_cfg = Loihi1SimCfg( + loglevel=logging.CRITICAL) # Connect sender with receiver sender.out.connect([recv1.inp, recv2.inp]) diff --git a/tests/lava/magma/runtime/test_get_set_non_determinism.py b/tests/lava/magma/runtime/test_get_set_non_determinism.py new file mode 100644 index 000000000..7f354bbd3 --- /dev/null +++ b/tests/lava/magma/runtime/test_get_set_non_determinism.py @@ -0,0 +1,62 @@ +# Copyright (C) 2021 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause +# See: https://spdx.org/licenses/ + +import unittest + +import numpy as np + +from lava.magma.core.decorator import implements, requires, tag +from lava.magma.core.model.py.model import PyLoihiProcessModel +from lava.magma.core.model.py.type import LavaPyType +from lava.magma.core.process.process import AbstractProcess +from lava.magma.core.process.variable import Var +from lava.magma.core.resources import CPU +from lava.magma.core.run_conditions import RunSteps +from lava.magma.core.run_configs import Loihi1SimCfg +from lava.magma.core.sync.protocols.loihi_protocol import LoihiProtocol + + +class DemoProcess(AbstractProcess): + def __init__(self, **kwargs): + super().__init__(**kwargs) + nb_runs = kwargs.pop("nb_runs") + self.changed = Var(shape=(1,), init=np.array([True])) + self.changed_history = Var(shape=(nb_runs,), init=np.nan) + + +@implements(proc=DemoProcess, protocol=LoihiProtocol) +@requires(CPU) +@tag('floating_pt') +class DemoProcessModel(PyLoihiProcessModel): + changed: np.ndarray = LavaPyType(np.ndarray, bool) + changed_history: np.ndarray = LavaPyType(np.ndarray, bool) + + def run_spk(self): + self.changed_history[self.time_step - 1] = self.changed[0] + self.changed[0] = False + + +class TestNonDeterminismUpdate(unittest.TestCase): + def test_non_determinism_update(self): + nb_runs = 10000 + demo_process = DemoProcess(nb_runs=nb_runs) + for i in range(nb_runs): + demo_process.run(condition=RunSteps(num_steps=1), + run_cfg=Loihi1SimCfg()) + + demo_process.changed.set(np.array([True])) + + # Uncomment this, test will pass + # Comment this, test will probably fail + # demo_process.changed.get() + + changed_history = demo_process.changed_history.get() + + demo_process.stop() + + np.testing.assert_array_equal(np.ones((nb_runs,)), changed_history) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/lava/magma/runtime/test_get_set_var.py b/tests/lava/magma/runtime/test_get_set_var.py index 33fe79999..f1fe92b02 100644 --- a/tests/lava/magma/runtime/test_get_set_var.py +++ b/tests/lava/magma/runtime/test_get_set_var.py @@ -1,8 +1,6 @@ # Copyright (C) 2021 Intel Corporation # SPDX-License-Identifier: BSD-3-Clause # See: https://spdx.org/licenses/ - -import logging import numpy as np import unittest @@ -20,7 +18,7 @@ class SimpleProcess(AbstractProcess): def __init__(self, **kwargs): - super().__init__(loglevel=logging.WARNING, **kwargs) + super().__init__(**kwargs) shape = kwargs["shape"] self.u = Var(shape=shape, init=np.array([[7, 8], [9, 10]], dtype=np.int32)) @@ -31,8 +29,7 @@ def __init__(self, **kwargs): class SimpleRunConfig(RunConfig): def __init__(self, **kwargs): sync_domains = kwargs.pop("sync_domains") - super().__init__(custom_sync_domains=sync_domains, - loglevel=logging.WARNING) + super().__init__(custom_sync_domains=sync_domains) self.model = None if "model" in kwargs: self.model = kwargs.pop("model") @@ -132,6 +129,20 @@ def test_get_set_var_using_var_api(self): assert np.array_equal(process.v.get(), expected_result_v) process.stop() + def test_get_set_variable_set_before_next_run(self): + process = SimpleProcess(shape=(2, 2)) + simple_sync_domain = SyncDomain("simple", LoihiProtocol(), [process]) + run_config = SimpleRunConfig(sync_domains=[simple_sync_domain]) + process.run(condition=RunSteps(num_steps=10), run_cfg=run_config) + + # Retrieve value of u + expected_result_u = np.array([[7, 8], [9, 10]], dtype=np.int32) + process.u.set(expected_result_u) + # Check if values stay modified after another execution + process.run(condition=RunSteps(num_steps=1), run_cfg=run_config) + assert np.array_equal(process.u.get(), expected_result_u) + process.stop() + if __name__ == '__main__': unittest.main() diff --git a/tests/lava/magma/runtime/test_loihi_protocol.py b/tests/lava/magma/runtime/test_loihi_protocol.py index 5791a15cd..817ab4ccd 100644 --- a/tests/lava/magma/runtime/test_loihi_protocol.py +++ b/tests/lava/magma/runtime/test_loihi_protocol.py @@ -1,4 +1,3 @@ -import logging import unittest from lava.magma.core.decorator import implements, requires @@ -15,7 +14,7 @@ class SimpleProcess(AbstractProcess): def __init__(self, **kwargs): - super().__init__(loglevel=logging.WARNING, **kwargs) + super().__init__(**kwargs) shape = kwargs["shape"] self.u = Var(shape=shape, init=0) self.v = Var(shape=shape, init=0) @@ -24,8 +23,7 @@ def __init__(self, **kwargs): class SimpleRunConfig(RunConfig): def __init__(self, **kwargs): sync_domains = kwargs.pop("sync_domains") - super().__init__(custom_sync_domains=sync_domains, - loglevel=logging.WARNING) + super().__init__(custom_sync_domains=sync_domains) self.model = None if "model" in kwargs: self.model = kwargs.pop("model") diff --git a/tests/lava/magma/runtime/test_nxsdkruntimeservice_loihi_.py b/tests/lava/magma/runtime/test_nxsdkruntimeservice_loihi.py similarity index 50% rename from tests/lava/magma/runtime/test_nxsdkruntimeservice_loihi_.py rename to tests/lava/magma/runtime/test_nxsdkruntimeservice_loihi.py index 8a966f699..e7271b305 100644 --- a/tests/lava/magma/runtime/test_nxsdkruntimeservice_loihi_.py +++ b/tests/lava/magma/runtime/test_nxsdkruntimeservice_loihi.py @@ -7,17 +7,16 @@ from lava.magma.core.model.nc.type import LavaNcType from lava.magma.core.process.process import AbstractProcess from lava.magma.core.process.variable import Var -from lava.magma.core.resources import Loihi2NeuroCore, Loihi1NeuroCore +from lava.magma.core.resources import Loihi2NeuroCore from lava.magma.core.run_conditions import RunSteps from lava.magma.core.run_configs import RunConfig -from lava.magma.core.sync.domain import SyncDomain from lava.magma.core.sync.protocols.loihi_protocol import LoihiProtocol from lava.magma.core.model.nc.model import NcProcessModel class SimpleProcess(AbstractProcess): def __init__(self, **kwargs): - super().__init__(loglevel=logging.WARNING, **kwargs) + super().__init__(**kwargs) shape = kwargs["shape"] self.u = Var(shape=shape, init=0) self.v = Var(shape=shape, init=0) @@ -25,40 +24,18 @@ def __init__(self, **kwargs): class SimpleRunConfig(RunConfig): def __init__(self, **kwargs): - sync_domains = kwargs.pop("sync_domains") - super().__init__(custom_sync_domains=sync_domains, - loglevel=logging.WARNING) + super().__init__() self.model = None if "model" in kwargs: self.model = kwargs.pop("model") def select(self, process, proc_models): - if self.model is not None: - if self.model == "sub" and isinstance(process, SimpleProcess): - return proc_models[1] - return proc_models[0] -@implements(proc=SimpleProcess, protocol=LoihiProtocol) -@requires(Loihi1NeuroCore) -class SimpleProcessModel1(NcProcessModel): - u = LavaNcType(int, int) - v = LavaNcType(int, int) - - def post_guard(self): - return False - - def pre_guard(self): - return False - - def lrn_guard(self): - return False - - @implements(proc=SimpleProcess, protocol=LoihiProtocol) @requires(Loihi2NeuroCore) -class SimpleProcessModel2(NcProcessModel): +class SimpleProcessModel(NcProcessModel): u = LavaNcType(int, int) v = LavaNcType(int, int) @@ -72,36 +49,23 @@ def lrn_guard(self): return False -@unittest.skip -class TestProcessLoihi1(unittest.TestCase): - # Run Loihi Tests using example command below: +class TestProcessLoihi2(unittest.TestCase): + # Run Loihi Tests using command below: # # SLURM=1 LOIHI_GEN=N3B3 BOARD=ncl-og-05 PARTITION=oheogulch # RUN_LOIHI_TESTS=1 python -m unittest # tests/lava/magma/runtime/test_nxsdkruntimeservice_loihi_.py + # TestProcessLoihi2.test_nxsdkruntimeservice_run_loihi run_loihi_tests: bool = Utils.get_env_test_setting("RUN_LOIHI_TESTS") @unittest.skipUnless(run_loihi_tests, "runtime_to_runtimeservice_to_nxcore_to_loihi") - def test_synchronization_single_process_model(self): - process = SimpleProcess(shape=(2, 2)) - simple_sync_domain = SyncDomain("simple", LoihiProtocol(), [process]) - run_config = SimpleRunConfig(sync_domains=[simple_sync_domain]) - process.run(condition=RunSteps(num_steps=10), run_cfg=run_config) - process.run(condition=RunSteps(num_steps=5), run_cfg=run_config) - process.stop() - - -class TestProcessLoihi2(unittest.TestCase): - run_loihi_tests: bool = Utils.get_env_test_setting("RUN_LOIHI_TESTS") - - @unittest.skipUnless(run_loihi_tests, - "runtime_to_runtimeservice_to_nxcore_to_loihi") - def test_synchronization_single_process_model(self): - process = SimpleProcess(shape=(2, 2)) - simple_sync_domain = SyncDomain("simple", LoihiProtocol(), [process]) - run_config = SimpleRunConfig(sync_domains=[simple_sync_domain]) + def test_nxsdkruntimeservice_run_loihi(self): + process = SimpleProcess(shape=(2, 2), + loglevel=logging.DEBUG, + logenable=True) + run_config = SimpleRunConfig(sync_domains=[]) process.run(condition=RunSteps(num_steps=10), run_cfg=run_config) process.run(condition=RunSteps(num_steps=5), run_cfg=run_config) process.stop() diff --git a/tests/lava/magma/runtime/test_pause_requested_from_model.py b/tests/lava/magma/runtime/test_pause_requested_from_model.py new file mode 100644 index 000000000..597693f82 --- /dev/null +++ b/tests/lava/magma/runtime/test_pause_requested_from_model.py @@ -0,0 +1,221 @@ +# Copyright (C) 2021 Intel Corporation +# SPDX-License-Identifier: BSD-3-Clause +# See: https://spdx.org/licenses/ + +import unittest +from time import time, sleep + +from lava.magma.core.decorator import implements, requires +from lava.magma.core.model.py.model import PyLoihiProcessModel, \ + PyAsyncProcessModel +from lava.magma.core.process.process import AbstractProcess +from lava.magma.core.resources import CPU +from lava.magma.core.run_conditions import RunSteps +from lava.magma.core.run_configs import RunConfig +from lava.magma.core.sync.domain import SyncDomain +from lava.magma.core.sync.protocols.async_protocol import AsyncProtocol +from lava.magma.core.sync.protocols.loihi_protocol import LoihiProtocol + + +class P1(AbstractProcess): + pass + + +class P2(AbstractProcess): + pass + + +class P3(AbstractProcess): + pass + + +class P4(AbstractProcess): + pass + + +class P5(AbstractProcess): + pass + + +class P6(AbstractProcess): + pass + + +class SimpleRunConfig(RunConfig): + def __init__(self, **kwargs): + sync_domains = kwargs.pop("sync_domains") + super().__init__(custom_sync_domains=sync_domains) + self.model = None + if "model" in kwargs: + self.model = kwargs.pop("model") + + def select(self, process, proc_models): + return proc_models[0] + + +@implements(proc=P1, protocol=LoihiProtocol) +@requires(CPU) +class P1Model(PyLoihiProcessModel): + def run_spk(self): + if self.time_step == 3: + self._req_pause = True + else: + sleep(1) + + +@implements(proc=P2, protocol=LoihiProtocol) +@requires(CPU) +class P2Model(PyLoihiProcessModel): + def pre_guard(self): + return True + + def run_pre_mgmt(self): + if self.time_step == 3: + self._req_pause = True + else: + sleep(1) + + +@implements(proc=P3, protocol=LoihiProtocol) +@requires(CPU) +class P3Model(PyLoihiProcessModel): + def lrn_guard(self): + return True + + def run_lrn(self): + if self.time_step == 3: + self._req_pause = True + else: + sleep(1) + + +@implements(proc=P4, protocol=LoihiProtocol) +@requires(CPU) +class P4Model(PyLoihiProcessModel): + def post_guard(self): + return True + + def run_post_mgmt(self): + if self.time_step == 3: + self._req_pause = True + else: + sleep(1) + + +@implements(proc=P5, protocol=LoihiProtocol) +@requires(CPU) +class P5Model(PyLoihiProcessModel): + def post_guard(self): + return True + + def run_post_mgmt(self): + if self.time_step == 3: + self._req_stop = True + else: + sleep(1) + + +@implements(proc=P6, protocol=AsyncProtocol) +@requires(CPU) +class P6Model(PyAsyncProcessModel): + def run(self): + pass + + def async_fun(self): + while True: + if self.check_for_stop_cmd(): + return + + +class TestPauseRequestedFromModel(unittest.TestCase): + def test_pause_request_from_model_in_spk_phase(self): + """Ensure pause is serviced correctly when requested from the run + function of a model""" + s = time() + process = P1() + simple_sync_domain = SyncDomain("simple", LoihiProtocol(), [process]) + run_config = SimpleRunConfig(sync_domains=[simple_sync_domain]) + process.run(condition=RunSteps(num_steps=100), run_cfg=run_config) + e = time() + self.assertTrue(e - s < 100, "") + self.assertFalse(process.runtime._is_running) + self.assertTrue(process.runtime._is_started) + process.stop() + + def test_pause_request_from_model_in_pre_mgmt_phase(self): + """Ensure pause is serviced correctly when requested from the run + function of a model""" + s = time() + process = P2() + simple_sync_domain = SyncDomain("simple", LoihiProtocol(), [process]) + run_config = SimpleRunConfig(sync_domains=[simple_sync_domain]) + process.run(condition=RunSteps(num_steps=100), run_cfg=run_config) + e = time() + self.assertTrue(e - s < 100, "") + self.assertFalse(process.runtime._is_running) + self.assertTrue(process.runtime._is_started) + process.stop() + + def test_pause_request_from_model_in_lrn_phase(self): + """Ensure pause is serviced correctly when requested from the run + function of a model""" + s = time() + process = P3() + simple_sync_domain = SyncDomain("simple", LoihiProtocol(), [process]) + run_config = SimpleRunConfig(sync_domains=[simple_sync_domain]) + process.run(condition=RunSteps(num_steps=100), run_cfg=run_config) + e = time() + self.assertTrue(e - s < 100, "") + self.assertFalse(process.runtime._is_running) + self.assertTrue(process.runtime._is_started) + process.stop() + + def test_pause_request_from_model_in_post_mgmt_phase(self): + """Ensure pause is serviced correctly when requested from the run + function of a model""" + s = time() + process = P4() + simple_sync_domain = SyncDomain("simple", LoihiProtocol(), [process]) + run_config = SimpleRunConfig(sync_domains=[simple_sync_domain]) + process.run(condition=RunSteps(num_steps=100), run_cfg=run_config) + e = time() + self.assertTrue(e - s < 100, "") + self.assertFalse(process.runtime._is_running) + self.assertTrue(process.runtime._is_started) + process.stop() + + def test_stop_request_from_model_in_post_mgmt_phase(self): + """Ensure pause is serviced correctly when requested from the run + function of a model""" + s = time() + process = P5() + simple_sync_domain = SyncDomain("simple", LoihiProtocol(), [process]) + run_config = SimpleRunConfig(sync_domains=[simple_sync_domain]) + process.run(condition=RunSteps(num_steps=100), run_cfg=run_config) + e = time() + self.assertTrue(e - s < 100, "") + self.assertFalse(process.runtime._is_running) + + @unittest.skip + # TODO: Right now we only discover connected processes from a process + # We need to discuss if it makes sense to also discover other unconnected + # processes. + def test_pause_request_from_hierarchical_model(self): + """Test 2 models - one sync and another aynsc - able to pause and + stop""" + s = time() + p4 = P4() + p6 = P6() + sync_domain = SyncDomain("simple", LoihiProtocol(), [p4]) + async_domain = SyncDomain("asimple", AsyncProtocol(), [p6]) + run_config = SimpleRunConfig(sync_domains=[sync_domain, async_domain]) + p4.run(condition=RunSteps(num_steps=100), run_cfg=run_config) + e = time() + self.assertTrue(e - s < 100, "") + self.assertFalse(p4.runtime._is_running) + self.assertTrue(p4.runtime._is_started) + p4.stop() + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/lava/magma/runtime/test_ref_var_ports.py b/tests/lava/magma/runtime/test_ref_var_ports.py index aac3fcf4a..68bb8fce5 100644 --- a/tests/lava/magma/runtime/test_ref_var_ports.py +++ b/tests/lava/magma/runtime/test_ref_var_ports.py @@ -1,8 +1,6 @@ # Copyright (C) 2021 Intel Corporation # SPDX-License-Identifier: BSD-3-Clause # See: https://spdx.org/licenses/ - -import logging import numpy as np import unittest @@ -10,33 +8,75 @@ from lava.magma.core.model.py.model import PyLoihiProcessModel from lava.magma.core.model.py.ports import PyRefPort, PyVarPort from lava.magma.core.model.py.type import LavaPyType +from lava.magma.core.model.sub.model import AbstractSubProcessModel from lava.magma.core.process.ports.ports import RefPort, VarPort from lava.magma.core.process.process import AbstractProcess from lava.magma.core.process.variable import Var from lava.magma.core.resources import CPU from lava.magma.core.sync.domain import SyncDomain from lava.magma.core.sync.protocols.loihi_protocol import LoihiProtocol -from lava.magma.core.run_configs import RunConfig +from lava.magma.core.run_configs import RunConfig, Loihi1SimCfg from lava.magma.core.run_conditions import RunSteps -# A minimal process with a Var and a RefPort, VarPort +# A minimal hierarchical process with a RefPort +class HP1(AbstractProcess): + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.h_ref = RefPort(shape=(3,)) + + +# A minimal hierarchical process with a Var +class HP2(AbstractProcess): + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.h_var = Var(shape=(3, )) + + +# A minimal process with a Var, 2 RefPorts and a VarPort class P1(AbstractProcess): def __init__(self, **kwargs): - super().__init__(loglevel=logging.WARNING, **kwargs) + super().__init__(**kwargs) self.ref1 = RefPort(shape=(3,)) + self.ref3 = RefPort(shape=(2,)) self.var1 = Var(shape=(2,), init=17) self.var_port_var1 = VarPort(self.var1) -# A minimal process with 2 Vars and a RefPort, VarPort +# A minimal process with 3 Vars and a RefPort, VarPort class P2(AbstractProcess): def __init__(self, **kwargs): - super().__init__(loglevel=logging.WARNING, **kwargs) + super().__init__(**kwargs) self.var2 = Var(shape=(3,), init=4) self.var_port_var2 = VarPort(self.var2) self.ref2 = RefPort(shape=(2,)) self.var3 = Var(shape=(2,), init=1) + self.var4 = Var(shape=(2,), init=1) + + +# A minimal hierarchical PyProcModel implementing HP2 +@implements(proc=HP1) +class PyProcModelHP1(AbstractSubProcessModel): + + def __init__(self, proc): + """Builds sub Process structure of the Process.""" + + # Connect the RefPort of the hierarchical process with the RefPort of + # the nested process + self.p1 = P1() + self.p1.ref1.connect(proc.ref_ports.h_ref) + + +# A minimal hierarchical PyProcModel implementing HP2 +@implements(proc=HP2) +class PyProcModelHP2(AbstractSubProcessModel): + + def __init__(self, proc): + """Builds sub Process structure of the Process.""" + + self.p2 = P2() + # Reference h_var with var of the nested process + proc.vars.h_var.alias(self.p2.var2) # A minimal PyProcModel implementing P1 @@ -44,16 +84,18 @@ def __init__(self, **kwargs): @requires(CPU) class PyProcModel1(PyLoihiProcessModel): ref1: PyRefPort = LavaPyType(PyRefPort.VEC_DENSE, int) + ref3: PyRefPort = LavaPyType(PyRefPort.VEC_DENSE, int) var1: np.ndarray = LavaPyType(np.ndarray, np.int32) var_port_var1: PyVarPort = LavaPyType(PyVarPort.VEC_DENSE, int) - def pre_guard(self): + def post_guard(self): return True - def run_pre_mgmt(self): - if self.current_ts > 1: - ref_data = np.array([5, 5, 5]) + self.current_ts + def run_post_mgmt(self): + if self.time_step > 1: + ref_data = np.array([5, 5, 5]) + self.time_step self.ref1.write(ref_data) + self.ref3.write(ref_data[:2]) # A minimal PyProcModel implementing P2 @@ -64,12 +106,13 @@ class PyProcModel2(PyLoihiProcessModel): var2: np.ndarray = LavaPyType(np.ndarray, np.int32) var_port_var2: PyVarPort = LavaPyType(PyVarPort.VEC_DENSE, int) var3: np.ndarray = LavaPyType(np.ndarray, np.int32) + var4: np.ndarray = LavaPyType(np.ndarray, np.int32) - def pre_guard(self): + def post_guard(self): return True - def run_pre_mgmt(self): - if self.current_ts > 1: + def run_post_mgmt(self): + if self.time_step > 1: self.var3 = self.ref2.read() @@ -221,6 +264,57 @@ def test_implicit_Ref_Var_port_read(self): np.all(recv.var3.get() == np.array([17., 17.]))) recv.stop() + def test_multiple_var_ports(self): + """Tests connecting multiple RefPorts to different Vars of a target + Process. The RefPort sends data after the first time step to the + VarPort, starting with (5 + current time step) = 7). After 2 time steps + the value for var2 and var4 is expected to be 7.""" + + sender1 = P1() + sender2 = P1() + + recv = P2() + + sender1.ref1.connect_var(recv.var2) + sender2.ref3.connect_var(recv.var4) + + simple_sync_domain = SyncDomain("simple", LoihiProtocol(), + [sender1, sender2, recv]) + + # Run for two time steps + recv.run(RunSteps(num_steps=2, blocking=True), + MyRunCfg(custom_sync_domains=[simple_sync_domain])) + + self.assertTrue( + np.all(recv.var2.get() == np.array([7., 7., 7.]))) + self.assertTrue( + np.all(recv.var4.get() == np.array([7., 7.]))) + recv.stop() + + def test_hierarchical_ref_ports(self): + """Tests if sending data via a RefPort of an instance of the + hierarchical process HP1 to a Var of an instance of the hierarchical + process HP2 works. The RefPort of HP1 connects to the RefPort ref1 + of its nested process P1. HP2 has a Var h_var which aliases the Var var2 + of its nested process P2. The RefPort ref1 sends data to h_ref which + sends the data further to h_var. The RefPort sends data after the + first time step to the Var, starting with (5 + current time step) = 7). + After 2 time steps the value for h_var is expected to be 7.""" + + sender = HP1() + recv = HP2() + + sender.h_ref.connect_var(recv.h_var) + + # Run for two time steps + recv.run(RunSteps(num_steps=2, blocking=True), + run_cfg=Loihi1SimCfg(select_sub_proc_model=True)) + + self.assertTrue( + np.all(recv.h_var.get() == np.array([7., 7., 7.]))) + + recv.stop() + if __name__ == '__main__': unittest.main() diff --git a/tests/lava/magma/runtime/test_run_continuously_and_pause.py b/tests/lava/magma/runtime/test_run_continuously_and_pause.py new file mode 100644 index 000000000..2aa881d85 --- /dev/null +++ b/tests/lava/magma/runtime/test_run_continuously_and_pause.py @@ -0,0 +1,106 @@ +import unittest +from time import sleep + +from lava.magma.core.decorator import implements, requires +from lava.magma.core.model.py.model import PyLoihiProcessModel +from lava.magma.core.model.py.type import LavaPyType +from lava.magma.core.process.process import AbstractProcess +from lava.magma.core.process.variable import Var +from lava.magma.core.resources import CPU +from lava.magma.core.run_conditions import RunContinuous, RunSteps +from lava.magma.core.run_configs import RunConfig +from lava.magma.core.sync.domain import SyncDomain +from lava.magma.core.sync.protocols.loihi_protocol import LoihiProtocol + + +class SimpleProcess(AbstractProcess): + def __init__(self, **kwargs): + super().__init__(**kwargs) + shape = kwargs["shape"] + self.u = Var(shape=shape, init=0) + self.v = Var(shape=shape, init=0) + + +@implements(proc=SimpleProcess, protocol=LoihiProtocol) +@requires(CPU) +class SimpleProcessModel(PyLoihiProcessModel): + """ + Defines a SimpleProcessModel + """ + u = LavaPyType(int, int) + v = LavaPyType(int, int) + + +class SimpleRunConfig(RunConfig): + """ + Defines a simple run config + """ + + def __init__(self, **kwargs): + sync_domains = kwargs.pop("sync_domains") + super().__init__(custom_sync_domains=sync_domains) + self.model = None + if "model" in kwargs: + self.model = kwargs.pop("model") + + def select(self, process, proc_models): + if self.model is not None: + if self.model == "sub" and isinstance(process, SimpleProcess): + return proc_models[1] + + return proc_models[0] + + +class TestRunContinuous(unittest.TestCase): + def test_run_continuous_sync(self): + """ + Verifies working of a Synchronous Process in Run Continuous Mode. + """ + process = SimpleProcess(shape=(2, 2)) + simple_sync_domain = SyncDomain("simple", LoihiProtocol(), [process]) + run_config = SimpleRunConfig(sync_domains=[simple_sync_domain]) + process.run(condition=RunContinuous(), run_cfg=run_config) + sleep(2) + process.stop() + + def test_run_continuous_sync_pause(self): + """Verifies working of pause with a Synchronous Process in a + run continuous mode.""" + process = SimpleProcess(shape=(2, 2)) + simple_sync_domain = SyncDomain("simple", LoihiProtocol(), [process]) + run_config = SimpleRunConfig(sync_domains=[simple_sync_domain]) + process.run(condition=RunContinuous(), run_cfg=run_config) + process.pause() + process.run(condition=RunContinuous()) + process.stop() + + def test_run_sync_pause(self): + """ + Verifies working of pause with a Synchronous Process. + """ + process = SimpleProcess(shape=(2, 2)) + simple_sync_domain = SyncDomain("simple", LoihiProtocol(), [process]) + run_config = SimpleRunConfig(sync_domains=[simple_sync_domain]) + process.run(condition=RunSteps(200), run_cfg=run_config) + process.pause() + process.run(condition=RunSteps(200)) + process.stop() + + def test_wait_from_runtime(self): + """Checks non blocking mode run of a function""" + + process = SimpleProcess(shape=(2, 2)) + simple_sync_domain = SyncDomain("simple", LoihiProtocol(), [process]) + run_config = SimpleRunConfig(sync_domains=[simple_sync_domain]) + process.run(condition=RunSteps(num_steps=10000, blocking=False), + run_cfg=run_config) + + process.wait() + process.run(condition=RunSteps(num_steps=10, blocking=False), + run_cfg=run_config) + process.wait() + process.stop() + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/lava/magma/runtime/test_runtime.py b/tests/lava/magma/runtime/test_runtime.py index 63d5aad85..5f5fa16a6 100644 --- a/tests/lava/magma/runtime/test_runtime.py +++ b/tests/lava/magma/runtime/test_runtime.py @@ -3,7 +3,7 @@ from lava.magma.compiler.executable import Executable from lava.magma.core.process.message_interface_enum import ActorType -from lava.magma.core.resources import HeadNode +from lava.magma.core.resources import HeadNode, Loihi2System from lava.magma.compiler.node import Node, NodeConfig from lava.magma.runtime.runtime import Runtime @@ -16,16 +16,16 @@ def test_runtime_creation(self): runtime: Runtime = Runtime(exe=exe, message_infrastructure_type=mp) expected_type: ty.Type = Runtime - assert isinstance( - runtime, expected_type - ), f"Expected type {expected_type} doesn't match {(type(runtime))}" + self.assertIsInstance( + runtime, expected_type, + f"Expected type {expected_type} doesn't match {(type(runtime))}") def test_executable_node_config_assertion(self): """Tests runtime constructions with expected constraints""" exec: Executable = Executable() runtime1: Runtime = Runtime(exec, ActorType.MultiProcessing) - with self.assertRaises(AssertionError): + with self.assertRaises(IndexError): runtime1.initialize() node: Node = Node(HeadNode, []) @@ -33,20 +33,28 @@ def test_executable_node_config_assertion(self): runtime2: Runtime = Runtime(exec, ActorType.MultiProcessing) runtime2.initialize() expected_type: ty.Type = Runtime - assert isinstance( - runtime2, expected_type - ), f"Expected type {expected_type} doesn't match {(type(runtime2))}" + self.assertIsInstance( + runtime2, expected_type, + f"Expected type {expected_type} doesn't match {(type(runtime2))}") runtime2.stop() - exec.node_configs[0].append(node) - runtime3: Runtime = Runtime(exec, ActorType.MultiProcessing) + exec1: Executable = Executable() + node1: Node = Node(Loihi2System, []) + exec1.node_configs.append(NodeConfig([node1])) + runtime3: Runtime = Runtime(exec1, ActorType.MultiProcessing) with self.assertRaises(AssertionError): - runtime3.initialize() + runtime3.initialize(0) exec.node_configs.append(NodeConfig([node])) runtime4: Runtime = Runtime(exec, ActorType.MultiProcessing) - with self.assertRaises(AssertionError): - runtime4.initialize() + runtime4.initialize(0) + self.assertEqual(len(runtime4._executable.node_configs), 2, + "Expected node_configs length to be 2") + node2: Node = Node(Loihi2System, []) + exec.node_configs[0].append(node2) + self.assertEqual(len(runtime4._executable.node_configs[0]), 2, + "Expected node_configs[0] node_config length to be 2") + runtime4.stop() if __name__ == "__main__": diff --git a/tests/lava/magma/runtime/test_runtime_service.py b/tests/lava/magma/runtime/test_runtime_service.py index 1a6e4c55e..66508d175 100644 --- a/tests/lava/magma/runtime/test_runtime_service.py +++ b/tests/lava/magma/runtime/test_runtime_service.py @@ -48,6 +48,9 @@ class SimpleProcessModel(AbstractPyProcessModel): def run(self): pass + def add_ports_for_polling(self): + pass + class SimplePyRuntimeService(PyRuntimeService): def run(self): diff --git a/tests/lava/proc/conv/test_models.py b/tests/lava/proc/conv/test_models.py index bf5d24fa5..0d6fe7291 100644 --- a/tests/lava/proc/conv/test_models.py +++ b/tests/lava/proc/conv/test_models.py @@ -1,7 +1,6 @@ # Copyright (C) 2021 Intel Corporation # SPDX-License-Identifier: BSD-3-Clause # See: https://spdx.org/licenses/ -import logging from typing import Dict, List, Tuple, Type, Union import unittest import numpy as np @@ -25,7 +24,7 @@ class ConvRunConfig(RunConfig): """Run configuration selects appropriate Conv ProcessModel based on tag: floating point precision or Loihi bit-accurate fixed point precision""" def __init__(self, select_tag: str = 'fixed_pt'): - super().__init__(custom_sync_domains=None, loglevel=logging.WARNING) + super().__init__(custom_sync_domains=None) self.select_tag = select_tag def select( @@ -93,6 +92,7 @@ def setup_conv() -> Tuple[ class TestConvProcessModels(unittest.TestCase): """Tests for all ProcessModels of Conv""" + @unittest.skip def test_conv_float(self) -> None: """Test for float conv process.""" num_steps = 10 @@ -120,7 +120,7 @@ def test_conv_float(self) -> None: for t in range(output.shape[-1]): output_gt[..., t] = utils.conv(input[..., t], **params) - error = np.abs(output[..., 1:] - output_gt[..., :-1]).mean() + error = np.abs(output - output_gt).mean() if error >= 1e-6: print(f'{input.shape=}') @@ -139,6 +139,7 @@ def test_conv_float(self) -> None: f'{output_gt[output!=output_gt]=}\n' ) + @unittest.skip def test_conv_fixed(self) -> None: """Test for fixed point conv process.""" num_steps = 10 @@ -167,7 +168,7 @@ def test_conv_fixed(self) -> None: output_gt[..., t] = utils.conv(input[..., t], **params) output_gt = utils.signed_clamp(output_gt, bits=24) - error = np.abs(output[..., 1:] - output_gt[..., :-1]).mean() + error = np.abs(output - output_gt).mean() if error >= 1e-6: print(f'{input.shape=}') diff --git a/tests/lava/proc/dense/test_models.py b/tests/lava/proc/dense/test_models.py index 73161f0d2..aab7ad06e 100644 --- a/tests/lava/proc/dense/test_models.py +++ b/tests/lava/proc/dense/test_models.py @@ -1,7 +1,6 @@ # Copyright (C) 2021 Intel Corporation # SPDX-License-Identifier: BSD-3-Clause # See: https://spdx.org/licenses/ -import logging import unittest import numpy as np @@ -24,8 +23,7 @@ class DenseRunConfig(RunConfig): floating point precision or Loihi bit-accurate fixed-point precision""" def __init__(self, custom_sync_domains=None, select_tag='fixed_pt'): - super().__init__(custom_sync_domains=custom_sync_domains, - loglevel=logging.WARNING) + super().__init__(custom_sync_domains=custom_sync_domains) self.select_tag = select_tag def select(self, proc, proc_models): @@ -98,7 +96,7 @@ def run_spk(self): """ self.a_in.recv() - if self.send_at_times[self.current_ts - 1]: + if self.send_at_times[self.time_step - 1]: self.s_out.send(self.vec_to_send) else: self.s_out.send(np.zeros_like(self.vec_to_send)) @@ -120,7 +118,7 @@ def run_spk(self): """ self.a_in.recv() - if self.send_at_times[self.current_ts - 1]: + if self.send_at_times[self.time_step - 1]: self.s_out.send(self.vec_to_send) else: self.s_out.send(np.zeros_like(self.vec_to_send)) @@ -137,7 +135,7 @@ class PySpkRecvModelFloat(PyLoihiProcessModel): def run_spk(self): """Receive spikes and store in an internal variable""" spk_in = self.s_in.recv() - self.spk_data[self.current_ts - 1, :] = spk_in + self.spk_data[self.time_step - 1, :] = spk_in @implements(proc=VecRecvProcess, protocol=LoihiProtocol) @@ -151,7 +149,7 @@ class PySpkRecvModelFixed(PyLoihiProcessModel): def run_spk(self): """Receive spikes and store in an internal variable""" spk_in = self.s_in.recv() - self.spk_data[self.current_ts - 1, :] = spk_in + self.spk_data[self.time_step - 1, :] = spk_in class TestDenseProcessModelFloat(unittest.TestCase): diff --git a/tests/lava/proc/io/test_dataloader.py b/tests/lava/proc/io/test_dataloader.py index 5ee42663c..f339c8c72 100644 --- a/tests/lava/proc/io/test_dataloader.py +++ b/tests/lava/proc/io/test_dataloader.py @@ -1,8 +1,6 @@ # Copyright (C) 2021 Intel Corporation # SPDX-License-Identifier: BSD-3-Clause # See: https://spdx.org/licenses/ - -import logging from typing import List, Tuple import unittest import numpy as np @@ -28,8 +26,7 @@ class TestRunConfig(RunConfig): """Run configuration selects appropriate ProcessModel based on tag: floating point precision or Loihi bit-accurate fixed point precision""" def __init__(self, select_tag: str = 'fixed_pt') -> None: - super().__init__(custom_sync_domains=None, - loglevel=logging.WARNING) + super().__init__(custom_sync_domains=None) self.select_tag = select_tag def select( diff --git a/tests/lava/proc/io/test_source_sink.py b/tests/lava/proc/io/test_source_sink.py index f4ca41edb..7062e8893 100644 --- a/tests/lava/proc/io/test_source_sink.py +++ b/tests/lava/proc/io/test_source_sink.py @@ -1,51 +1,33 @@ # Copyright (C) 2021 Intel Corporation # SPDX-License-Identifier: BSD-3-Clause # See: https://spdx.org/licenses/ +from typing import List import unittest import numpy as np -from lava.magma.core.model.py.ports import PyOutPort -from lava.magma.core.run_configs import Loihi1SimCfg +from lava.magma.core.run_configs import RunConfig from lava.magma.core.run_conditions import RunSteps from lava.proc.io.source import RingBuffer as SendProcess from lava.proc.io.sink import RingBuffer as ReceiveProcess -from lava.proc.io.sink import Read -from lava.proc.io.reset import Reset - -from lava.magma.core.process.variable import Var -from lava.magma.core.process.process import AbstractProcess -from lava.magma.core.process.ports.ports import OutPort - -from lava.magma.core.resources import CPU -from lava.magma.core.decorator import implements, requires, tag from lava.magma.core.model.py.model import PyLoihiProcessModel -from lava.magma.core.sync.protocols.loihi_protocol import LoihiProtocol -from lava.magma.core.model.py.type import LavaPyType np.random.seed(7739) -# simple integrator class to interact with read and reset processes -class Integrator(AbstractProcess): - def __init__(self, delta: int) -> None: - super().__init__() - self.shape = (1,) - self.state = Var(self.shape, 0) - self.delta = Var(self.shape, delta) - self.out = OutPort(self.shape) - +class TestRunConfig(RunConfig): + """Run configuration selects appropriate ProcessModel based on tag + """ + def __init__(self, select_tag: str = 'fixed_pt'): + super(TestRunConfig, self).__init__(custom_sync_domains=None) + self.select_tag = select_tag -@implements(proc=Integrator, protocol=LoihiProtocol) -@requires(CPU) -@tag('fixed_pt') -class PyIntegrator(PyLoihiProcessModel): - out: PyOutPort = LavaPyType(PyOutPort.VEC_DENSE, int) - state: np.ndarray = LavaPyType(np.ndarray, int) - delta: np.ndarray = LavaPyType(np.ndarray, int) - - def run_spk(self) -> None: - self.state += self.delta - self.out.send(self.state) + def select( + self, _, proc_models: List[PyLoihiProcessModel] + ) -> PyLoihiProcessModel: + for pm in proc_models: + if self.select_tag in pm.tags: + return pm + raise AssertionError('No legal ProcessModel found.') class TestSendReceive(unittest.TestCase): @@ -64,7 +46,7 @@ def test_source_sink(self) -> None: source.out_ports.s_out.connect(sink.in_ports.a_in) run_condition = RunSteps(num_steps=num_steps) - run_config = Loihi1SimCfg(select_tag='floating_pt') + run_config = TestRunConfig(select_tag='floating_pt') sink.run(condition=run_condition, run_cfg=run_config) output = sink.data.get() sink.stop() @@ -75,66 +57,3 @@ def test_source_sink(self) -> None: f'{output[output!=input]=}\n' f'{input[output!=input] =}\n' ) - - def test_read(self) -> None: - num_steps = 15 - delta = 5 - interval = 4 - offset = 2 - integrator = Integrator(delta) - logger = Read(num_steps // interval + 1, interval, offset) - logger.connect_var(integrator.state) - - run_condition = RunSteps(num_steps=num_steps) - run_config = Loihi1SimCfg(select_tag='fixed_pt') - integrator.run(condition=run_condition, run_cfg=run_config) - output = logger.data.get() - integrator.stop() - - # + delta because we are reading data after state is changed - ground_truth = np.arange(num_steps) * delta + delta - ground_truth = ground_truth[offset::interval] - ground_truth.reshape(1, -1) - - error = np.abs(output[..., :len(ground_truth)] - ground_truth).sum() - - self.assertTrue( - error == 0, - f'Read Var has errors. Expected {ground_truth=}, found {output=}.' - ) - - def test_reset(self) -> None: - num_steps = 15 - delta = 5 - interval = 4 - offset = 2 - integrator = Integrator(delta) - # TODO: DISCUSS - # It is not possible to attach two RefPort to same var - # so Read and Reset cannot be used on same process - # logger = Read( - # integrator.state, - # num_steps // interval + 1, interval, offset + 1 - # ) - resetter = Reset(reset_value=-delta, interval=interval, offset=offset) - sink = ReceiveProcess(shape=integrator.shape, buffer=num_steps) - resetter.connect_var(integrator.state) - integrator.out.connect(sink.a_in) - - run_condition = RunSteps(num_steps=num_steps) - run_config = Loihi1SimCfg(select_tag='fixed_pt') - integrator.run(condition=run_condition, run_cfg=run_config) - # output = logger.data.get() - output = sink.data.get() - integrator.stop() - - # + delta because we are reading data after state is changed - ground_truth = np.arange(num_steps) + 1 - ground_truth[offset:] -= ground_truth[offset] - ground_truth = (ground_truth % interval) * delta - error = np.abs(output - ground_truth).sum() - - self.assertTrue( - error == 0, - f'Read Var has errors. Expected {ground_truth=}, found {output=}.' - ) diff --git a/tests/lava/proc/lif/test_models.py b/tests/lava/proc/lif/test_models.py index 060d4694b..e671634ef 100644 --- a/tests/lava/proc/lif/test_models.py +++ b/tests/lava/proc/lif/test_models.py @@ -1,7 +1,6 @@ # Copyright (C) 2021 Intel Corporation # SPDX-License-Identifier: BSD-3-Clause # See: https://spdx.org/licenses/ -import logging import unittest import numpy as np @@ -23,8 +22,7 @@ class LifRunConfig(RunConfig): """Run configuration selects appropriate LIF ProcessModel based on tag: floating point precision or Loihi bit-accurate fixed point precision""" def __init__(self, custom_sync_domains=None, select_tag='fixed_pt'): - super().__init__(custom_sync_domains=custom_sync_domains, - loglevel=logging.WARNING) + super().__init__(custom_sync_domains=custom_sync_domains) self.select_tag = select_tag def select(self, proc, proc_models): @@ -87,7 +85,7 @@ def run_spk(self): """ Send `spikes_to_send` if current time-step requires it """ - if self.send_at_times[self.current_ts - 1]: + if self.send_at_times[self.time_step - 1]: self.s_out.send(self.vec_to_send) else: self.s_out.send(np.zeros_like(self.vec_to_send)) @@ -106,7 +104,7 @@ def run_spk(self): """ Send `spikes_to_send` if current time-step requires it """ - if self.send_at_times[self.current_ts - 1]: + if self.send_at_times[self.time_step - 1]: self.s_out.send(self.vec_to_send) else: self.s_out.send(np.zeros_like(self.vec_to_send)) @@ -123,7 +121,7 @@ class PySpkRecvModelFloat(PyLoihiProcessModel): def run_spk(self): """Receive spikes and store in an internal variable""" spk_in = self.s_in.recv() - self.spk_data[self.current_ts - 1, :] = spk_in + self.spk_data[self.time_step - 1, :] = spk_in @implements(proc=VecRecvProcess, protocol=LoihiProtocol) @@ -137,7 +135,7 @@ class PySpkRecvModelFixed(PyLoihiProcessModel): def run_spk(self): """Receive spikes and store in an internal variable""" spk_in = self.s_in.recv() - self.spk_data[self.current_ts - 1, :] = spk_in + self.spk_data[self.time_step - 1, :] = spk_in class TestLIFProcessModelsFloat(unittest.TestCase): @@ -269,7 +267,7 @@ def test_bitacc_pm_no_decay(self): vec_to_send=np.zeros(shape, dtype=np.int16), send_at_times=np.ones((num_steps,), dtype=bool)) # Set up bias = 2 * 2**6 = 128 and threshold = 8<<6 - # du and dv = 0 => bias driven neurons spike at every 5th time-step. + # du and dv = 0 => bias driven neurons spike at every 4th time-step. lif = LIF(shape=shape, du=0, dv=0, bias=2 * np.ones(shape, dtype=np.int32), diff --git a/tests/lava/proc/monitor/test_monitors.py b/tests/lava/proc/monitor/test_monitors.py index f276368e2..95677d5aa 100644 --- a/tests/lava/proc/monitor/test_monitors.py +++ b/tests/lava/proc/monitor/test_monitors.py @@ -39,13 +39,13 @@ class PyProcModel1(PyLoihiProcessModel): u: np.ndarray = LavaPyType(np.ndarray, np.int32) v: np.ndarray = LavaPyType(np.ndarray, np.int32) - def pre_guard(self): + def post_guard(self): return True - def run_pre_mgmt(self): - if self.current_ts > 1: - self.s = np.array([self.current_ts]) - self.u = 2 * np.array([self.current_ts]) + def run_post_mgmt(self): + if self.time_step > 1: + self.s = np.array([self.time_step]) + self.u = 2 * np.array([self.time_step]) self.v = np.array([[1, 2], [3, 4]]) diff --git a/tutorials/end_to_end/tutorial01_mnist_digit_classification.ipynb b/tutorials/end_to_end/tutorial01_mnist_digit_classification.ipynb index 627903e62..c6c4f5527 100644 --- a/tutorials/end_to_end/tutorial01_mnist_digit_classification.ipynb +++ b/tutorials/end_to_end/tutorial01_mnist_digit_classification.ipynb @@ -302,7 +302,7 @@ " def post_guard(self):\n", " \"\"\"Guard function for PostManagement phase.\n", " \"\"\"\n", - " if self.current_ts % self.num_steps_per_image == 1:\n", + " if self.time_step % self.num_steps_per_image == 1:\n", " return True\n", " return False\n", "\n", @@ -435,8 +435,8 @@ " def post_guard(self):\n", " \"\"\"Guard function for PostManagement phase.\n", " \"\"\"\n", - " if self.current_ts % self.num_steps_per_image == 0 and \\\n", - " self.current_ts > 1:\n", + " if self.time_step % self.num_steps_per_image == 0 and \\\n", + " self.time_step > 1:\n", " return True\n", " return False\n", "\n", diff --git a/tutorials/in_depth/tutorial07_remote_memory_access.ipynb b/tutorials/in_depth/tutorial07_remote_memory_access.ipynb index 8e3b1aa29..05f9db31b 100644 --- a/tutorials/in_depth/tutorial07_remote_memory_access.ipynb +++ b/tutorials/in_depth/tutorial07_remote_memory_access.ipynb @@ -117,10 +117,10 @@ " def run_pre_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.current_ts))\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.current_ts\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", From ef3bfe9656955ca744f77d01f7685b904f03fd3b Mon Sep 17 00:00:00 2001 From: Marcus G K Williams Date: Wed, 23 Feb 2022 18:55:36 -0800 Subject: [PATCH 12/18] Remove nc/ports.py again Remove commented code in compiler.py Signed-off-by: Marcus G K Williams --- src/lava/magma/compiler/compiler.py | 5 - src/lava/magma/core/model/nc/ports.py | 358 -------------------------- 2 files changed, 363 deletions(-) delete mode 100644 src/lava/magma/core/model/nc/ports.py diff --git a/src/lava/magma/compiler/compiler.py b/src/lava/magma/compiler/compiler.py index 2a02c3148..73aa76c26 100644 --- a/src/lava/magma/compiler/compiler.py +++ b/src/lava/magma/compiler/compiler.py @@ -832,11 +832,6 @@ def _create_exec_vars(self, elif issubclass(pm, AbstractCProcessModel): ev = exec_var.CExecVar(v, node_id, run_srv_id) elif issubclass(pm, AbstractNcProcessModel): - # ev = exec_var.NcExecVar(chip_id: int - # core_id: int - # register_base_addr: int - # entry_id: int - # field: str) ev = exec_var.PyExecVar(v, node_id, run_srv_id) else: raise NotImplementedError("Illegal ProcessModel type.") diff --git a/src/lava/magma/core/model/nc/ports.py b/src/lava/magma/core/model/nc/ports.py deleted file mode 100644 index eca2a703b..000000000 --- a/src/lava/magma/core/model/nc/ports.py +++ /dev/null @@ -1,358 +0,0 @@ -# Copyright (C) 2022 Intel Corporation -# SPDX-License-Identifier: BSD-3-Clause -# See: https://spdx.org/licenses/ -import typing as ty -from abc import abstractmethod -import functools as ft -import numpy as np - -from lava.magma.compiler.channels.interfaces import AbstractCspPort -from lava.magma.compiler.channels.pypychannel import CspSendPort, CspRecvPort -from lava.magma.core.model.interfaces import AbstractPortImplementation -from lava.magma.runtime.mgmt_token_enums import enum_to_np, enum_equal - - -class AbstractNcPort(AbstractPortImplementation): - @property - @abstractmethod - def csp_ports(self) -> ty.List[AbstractCspPort]: - """Returns all csp ports of the port.""" - pass - - -class NcInPort(AbstractNcPort): - """NeuroCore implementation of InPort used within AbstractNcProcessModel. - If buffer is empty, recv() will be blocking. - """ - - VEC_DENSE: ty.Type["NcInPortVectorDense"] = None - VEC_SPARSE: ty.Type["NcInPortVectorSparse"] = None - SCALAR_DENSE: ty.Type["NcInPortScalarDense"] = None - SCALAR_SPARSE: ty.Type["NcInPortScalarSparse"] = None - - def __init__(self, csp_recv_ports: ty.List[CspRecvPort], *args): - self._csp_recv_ports = csp_recv_ports - super().__init__(*args) - - @property - def csp_ports(self) -> ty.List[AbstractCspPort]: - """Returns all csp ports of the port.""" - return self._csp_recv_ports - - @abstractmethod - def recv(self): - pass - - @abstractmethod - def peek(self): - pass - - def probe(self) -> bool: - """Executes probe method of all csp ports and accumulates the returned - bool values with AND operation. The accumulator acc is initialized to - True. - - Returns the accumulated bool value. - """ - # Returns True only when probe returns True for all _csp_recv_ports. - return ft.reduce( - lambda acc, csp_port: acc and csp_port.probe(), - self._csp_recv_ports, - True, - ) - - -class NcInPortVectorDense(NcInPort): - def recv(self) -> np.ndarray: - return ft.reduce( - lambda acc, csp_port: acc + csp_port.recv(), - self._csp_recv_ports, - np.zeros(self._shape, self._d_type), - ) - - def peek(self) -> np.ndarray: - return ft.reduce( - lambda acc, csp_port: acc + csp_port.peek(), - self._csp_recv_ports, - np.zeros(self._shape, self._d_type), - ) - - -class NcInPortVectorSparse(NcInPort): - def recv(self) -> ty.Tuple[np.ndarray, np.ndarray]: - pass - - def peek(self) -> ty.Tuple[np.ndarray, np.ndarray]: - pass - - -class NcInPortScalarDense(NcInPort): - def recv(self) -> int: - pass - - def peek(self) -> int: - pass - - -class NcInPortScalarSparse(NcInPort): - def recv(self) -> ty.Tuple[int, int]: - pass - - def peek(self) -> ty.Tuple[int, int]: - pass - - -NcInPort.VEC_DENSE = NcInPortVectorDense -NcInPort.VEC_SPARSE = NcInPortVectorSparse -NcInPort.SCALAR_DENSE = NcInPortScalarDense -NcInPort.SCALAR_SPARSE = NcInPortScalarSparse - - -class NcOutPort(AbstractNcPort): - """Ncthon implementation of OutPort used within AbstractNcProcessModels.""" - - VEC_DENSE: ty.Type["NcOutPortVectorDense"] = None - VEC_SPARSE: ty.Type["NcOutPortVectorSparse"] = None - SCALAR_DENSE: ty.Type["NcOutPortScalarDense"] = None - SCALAR_SPARSE: ty.Type["NcOutPortScalarSparse"] = None - - def __init__(self, csp_send_ports: ty.List[CspSendPort], *args): - self._csp_send_ports = csp_send_ports - super().__init__(*args) - - @property - def csp_ports(self) -> ty.List[AbstractCspPort]: - """Returns all csp ports of the port.""" - return self._csp_send_ports - - @abstractmethod - def send(self, data: ty.Union[np.ndarray, int]): - pass - - def flush(self): - pass - - -class NcOutPortVectorDense(NcOutPort): - def send(self, data: np.ndarray): - """Sends data only if port is not dangling.""" - for csp_port in self._csp_send_ports: - csp_port.send(data) - - -class NcOutPortVectorSparse(NcOutPort): - def send(self, data: np.ndarray, idx: np.ndarray): - pass - - -class NcOutPortScalarDense(NcOutPort): - def send(self, data: int): - pass - - -class NcOutPortScalarSparse(NcOutPort): - def send(self, data: int, idx: int): - pass - - -NcOutPort.VEC_DENSE = NcOutPortVectorDense -NcOutPort.VEC_SPARSE = NcOutPortVectorSparse -NcOutPort.SCALAR_DENSE = NcOutPortScalarDense -NcOutPort.SCALAR_SPARSE = NcOutPortScalarSparse - - -class VarPortCmd: - GET = enum_to_np(0) - SET = enum_to_np(1) - - -class NcRefPort(AbstractNcPort): - """NeuroCore implementation of RefPort used - within AbstractNcProcessModels.""" - - VEC_DENSE: ty.Type["NcRefPortVectorDense"] = None - VEC_SPARSE: ty.Type["NcRefPortVectorSparse"] = None - SCALAR_DENSE: ty.Type["NcRefPortScalarDense"] = None - SCALAR_SPARSE: ty.Type["NcRefPortScalarSparse"] = None - - def __init__(self, - csp_send_port: ty.Optional[CspSendPort], - csp_recv_port: ty.Optional[CspRecvPort], *args): - self._csp_recv_port = csp_recv_port - self._csp_send_port = csp_send_port - super().__init__(*args) - - @property - def csp_ports(self) -> ty.List[AbstractCspPort]: - """Returns all csp ports of the port.""" - if self._csp_send_port is not None and self._csp_recv_port is not None: - return [self._csp_send_port, self._csp_recv_port] - else: - # In this case the port was not connected - return [] - - def read( - self, - ) -> ty.Union[ - np.ndarray, ty.Tuple[np.ndarray, np.ndarray], int, ty.Tuple[int, int] - ]: - pass - - def write( - self, - data: ty.Union[ - np.ndarray, - ty.Tuple[np.ndarray, np.ndarray], - int, - ty.Tuple[int, int], - ], - ): - pass - - -class NcRefPortVectorDense(NcRefPort): - def read(self) -> np.ndarray: - """Requests the data from a VarPort and returns the data.""" - if self._csp_send_port and self._csp_recv_port: - header = np.ones(self._csp_send_port.shape) * VarPortCmd.GET - self._csp_send_port.send(header) - - return self._csp_recv_port.recv() - - return np.zeros(self._shape, self._d_type) - - def write(self, data: np.ndarray): - """Sends the data to a VarPort to set its Var.""" - if self._csp_send_port: - header = np.ones(self._csp_send_port.shape) * VarPortCmd.SET - self._csp_send_port.send(header) - self._csp_send_port.send(data) - - -class NcRefPortVectorSparse(NcRefPort): - def read(self) -> ty.Tuple[np.ndarray, np.ndarray]: - pass - - def write(self, data: np.ndarray, idx: np.ndarray): - pass - - -class NcRefPortScalarDense(NcRefPort): - def read(self) -> int: - pass - - def write(self, data: int): - pass - - -class NcRefPortScalarSparse(NcRefPort): - def read(self) -> ty.Tuple[int, int]: - pass - - def write(self, data: int, idx: int): - pass - - -NcRefPort.VEC_DENSE = NcRefPortVectorDense -NcRefPort.VEC_SPARSE = NcRefPortVectorSparse -NcRefPort.SCALAR_DENSE = NcRefPortScalarDense -NcRefPort.SCALAR_SPARSE = NcRefPortScalarSparse - - -class NcVarPort(AbstractNcPort): - """NeuroCore implementation of VarPort used within AbstractNcProcessModel. - """ - - VEC_DENSE: ty.Type["NcVarPortVectorDense"] = None - VEC_SPARSE: ty.Type["NcVarPortVectorSparse"] = None - SCALAR_DENSE: ty.Type["NcVarPortScalarDense"] = None - SCALAR_SPARSE: ty.Type["NcVarPortScalarSparse"] = None - - def __init__(self, - var_name: str, - csp_send_port: ty.Optional[CspSendPort], - csp_recv_port: ty.Optional[CspRecvPort], *args): - self._csp_recv_port = csp_recv_port - self._csp_send_port = csp_send_port - self.var_name = var_name - super().__init__(*args) - - @property - def csp_ports(self) -> ty.List[AbstractCspPort]: - """Returns all csp ports of the port.""" - if self._csp_send_port is not None and self._csp_recv_port is not None: - return [self._csp_send_port, self._csp_recv_port] - else: - # In this case the port was not connected - return [] - - def service(self): - pass - - -class NcVarPortVectorDense(NcVarPort): - def service(self): - """Sets the received value to the given var or sends the value of the - var to the csp_send_port, depending on the received header information - of the csp_recv_port.""" - - # Inspect incoming data - if self._csp_send_port is not None and self._csp_recv_port is not None: - if self._csp_recv_port.probe(): - # If received data is a matrix, flatten and take the first - # element as cmd - cmd = enum_to_np((self._csp_recv_port.recv()).flatten()[0]) - - # Set the value of the Var with the given data - if enum_equal(cmd, VarPortCmd.SET): - data = self._csp_recv_port.recv() - setattr(self._process_model, self.var_name, data) - elif enum_equal(cmd, VarPortCmd.GET): - data = getattr(self._process_model, self.var_name) - self._csp_send_port.send(data) - else: - raise ValueError(f"Wrong Command Info Received : {cmd}") - - -class NcVarPortVectorSparse(NcVarPort): - def recv(self) -> ty.Tuple[np.ndarray, np.ndarray]: - pass - - def peek(self) -> ty.Tuple[np.ndarray, np.ndarray]: - pass - - -class NcVarPortScalarDense(NcVarPort): - def recv(self) -> int: - pass - - def peek(self) -> int: - pass - - -class NcVarPortScalarSparse(NcVarPort): - def recv(self) -> ty.Tuple[int, int]: - pass - - def peek(self) -> ty.Tuple[int, int]: - pass - - -NcVarPort.VEC_DENSE = NcVarPortVectorDense -NcVarPort.VEC_SPARSE = NcVarPortVectorSparse -NcVarPort.SCALAR_DENSE = NcVarPortScalarDense -NcVarPort.SCALAR_SPARSE = NcVarPortScalarSparse - - -class RefVarTypeMapping: - """Class to get the mapping of NcRefPort types to NcVarPortTypes.""" - - mapping: ty.Dict[NcRefPort, NcVarPort] = { - NcRefPortVectorDense: NcVarPortVectorDense, - NcRefPortVectorSparse: NcVarPortVectorSparse, - NcRefPortScalarDense: NcVarPortScalarDense, - NcRefPortScalarSparse: NcVarPortScalarSparse} - - @classmethod - def get(cls, ref_port: NcRefPort): - return cls.mapping[ref_port] From 1274d3ee39ba8200b1d2dd5516f3df16c1617a7f Mon Sep 17 00:00:00 2001 From: Marcus G K Williams Date: Thu, 24 Feb 2022 12:10:34 -0800 Subject: [PATCH 13/18] Update comments logging Signed-off-by: Marcus G K Williams --- src/lava/magma/compiler/compiler.py | 8 ++++---- src/lava/magma/core/model/nc/model.py | 4 +--- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/lava/magma/compiler/compiler.py b/src/lava/magma/compiler/compiler.py index 73aa76c26..da1eb22d4 100644 --- a/src/lava/magma/compiler/compiler.py +++ b/src/lava/magma/compiler/compiler.py @@ -390,8 +390,7 @@ def _compile_proc_models( elif issubclass(pm, AbstractNcProcessModel): for p in procs: b = NcProcessBuilder(pm, p.id, p.proc_params) - # Create Var- and PortInitializers from lava.process Vars - # and Ports + # Create VarInitializers from lava.process Vars v = [VarInitializer(v.name, v.shape, v.init, v.id) for v in p.vars] @@ -485,6 +484,7 @@ def _create_sync_domains( # Auto-assign AsyncProtocol if none was assigned if not pm.implements_protocol: proto = AsyncProtocol + log.debug("Protocol: AsyncProtocol") else: proto = pm.implements_protocol log.debug("Protocol: " + proto.__name__) @@ -602,8 +602,8 @@ def _create_node_cfgs(proc_map: PROC_MAP, ncfg = NodeConfig() ncfg.append(n) - # Until NodeConfig generation algorithm present - # check if NcProcessModel is present in proc_map + # Until NodeConfig generation algorithm is present + # check if NcProcessModel is present in proc_map, # if so add hardcoded Node for OheoGulch for proc_model in proc_map.items(): if issubclass(proc_model[1], NcProcessModel): diff --git a/src/lava/magma/core/model/nc/model.py b/src/lava/magma/core/model/nc/model.py index e2bb15978..f8d246790 100644 --- a/src/lava/magma/core/model/nc/model.py +++ b/src/lava/magma/core/model/nc/model.py @@ -39,9 +39,7 @@ def connect(self, from_thing, to_thing): class AbstractNcProcessModel(AbstractProcessModel, ABC): """Abstract interface for NeuroCore ProcessModels - Example for how variables and ports might be initialized: - a_in: NcInPort = LavaNcType(NcInPort.VEC_DENSE, float) - s_out: NcInPort = LavaNcType(NcOutPort.VEC_DENSE, bool, precision=1) + Example for how variables might be initialized: u: np.ndarray = LavaNcType(np.ndarray, np.int32, precision=24) v: np.ndarray = LavaNcType(np.ndarray, np.int32, precision=24) bias: np.ndarray = LavaNcType(np.ndarray, np.int16, precision=12) From 73e8367dd7fb8b41e3928b9f5dd87c122f49f3cb Mon Sep 17 00:00:00 2001 From: Marcus G K Williams Date: Thu, 24 Feb 2022 12:18:52 -0800 Subject: [PATCH 14/18] Update test Utils method name and document Signed-off-by: Marcus G K Williams --- .../magma/runtime/test_nxsdkruntimeservice_loihi.py | 2 +- tests/lava/magma/runtime/test_runtime_service.py | 2 +- tests/lava/test_utils/utils.py | 13 ++++++++++--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/tests/lava/magma/runtime/test_nxsdkruntimeservice_loihi.py b/tests/lava/magma/runtime/test_nxsdkruntimeservice_loihi.py index e7271b305..fbaa76ea4 100644 --- a/tests/lava/magma/runtime/test_nxsdkruntimeservice_loihi.py +++ b/tests/lava/magma/runtime/test_nxsdkruntimeservice_loihi.py @@ -57,7 +57,7 @@ class TestProcessLoihi2(unittest.TestCase): # tests/lava/magma/runtime/test_nxsdkruntimeservice_loihi_.py # TestProcessLoihi2.test_nxsdkruntimeservice_run_loihi - run_loihi_tests: bool = Utils.get_env_test_setting("RUN_LOIHI_TESTS") + run_loihi_tests: bool = Utils.get_bool_env_setting("RUN_LOIHI_TESTS") @unittest.skipUnless(run_loihi_tests, "runtime_to_runtimeservice_to_nxcore_to_loihi") diff --git a/tests/lava/magma/runtime/test_runtime_service.py b/tests/lava/magma/runtime/test_runtime_service.py index 66508d175..ff835d7f0 100644 --- a/tests/lava/magma/runtime/test_runtime_service.py +++ b/tests/lava/magma/runtime/test_runtime_service.py @@ -166,7 +166,7 @@ class TestNxSdkRuntimeService(unittest.TestCase): # RUN_LOIHI_TESTS=1 python -m unittest # tests/lava/magma/runtime/test_runtime_service.py - run_loihi_tests: bool = Utils.get_env_test_setting("RUN_LOIHI_TESTS") + run_loihi_tests: bool = Utils.get_bool_env_setting("RUN_LOIHI_TESTS") def test_runtime_service_construction(self): p = LoihiProtocol() diff --git a/tests/lava/test_utils/utils.py b/tests/lava/test_utils/utils.py index 14bfdb629..c268a38fb 100644 --- a/tests/lava/test_utils/utils.py +++ b/tests/lava/test_utils/utils.py @@ -1,14 +1,21 @@ -# Copyright (C) 2021 Intel Corporation +# Copyright (C) 2022 Intel Corporation # SPDX-License-Identifier: BSD-3-Clause # See: https://spdx.org/licenses/ import os class Utils(): + """Utility Class containing testing helper + code that can be reused between tests + """ @staticmethod - def get_env_test_setting(env: str): - env_test_setting = os.environ.get(env) + def get_bool_env_setting(env_var: str): + """Get an environment varible and return + True if the variable is set to 1 else return + false + """ + env_test_setting = os.environ.get(env_var) test_setting = False if env_test_setting == "1": test_setting = True From 382a82769b8d887ccefba303400807d6c0b8a981 Mon Sep 17 00:00:00 2001 From: Marcus G K Williams Date: Thu, 24 Feb 2022 12:23:56 -0800 Subject: [PATCH 15/18] Update test name and docs for nxsdkruntimeservice Signed-off-by: Marcus G K Williams --- tests/lava/magma/runtime/test_nxsdkruntimeservice_loihi.py | 7 +++---- tests/lava/magma/runtime/test_runtime_service.py | 6 +++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/lava/magma/runtime/test_nxsdkruntimeservice_loihi.py b/tests/lava/magma/runtime/test_nxsdkruntimeservice_loihi.py index fbaa76ea4..bdad98072 100644 --- a/tests/lava/magma/runtime/test_nxsdkruntimeservice_loihi.py +++ b/tests/lava/magma/runtime/test_nxsdkruntimeservice_loihi.py @@ -52,16 +52,15 @@ def lrn_guard(self): class TestProcessLoihi2(unittest.TestCase): # Run Loihi Tests using command below: # - # SLURM=1 LOIHI_GEN=N3B3 BOARD=ncl-og-05 PARTITION=oheogulch + # "SLURM=1 LOIHI_GEN=N3B3 BOARD=ncl-og-05 PARTITION=oheogulch # RUN_LOIHI_TESTS=1 python -m unittest - # tests/lava/magma/runtime/test_nxsdkruntimeservice_loihi_.py - # TestProcessLoihi2.test_nxsdkruntimeservice_run_loihi + # tests/lava/magma/runtime/test_nxsdkruntimeservice_loihi.py" run_loihi_tests: bool = Utils.get_bool_env_setting("RUN_LOIHI_TESTS") @unittest.skipUnless(run_loihi_tests, "runtime_to_runtimeservice_to_nxcore_to_loihi") - def test_nxsdkruntimeservice_run_loihi(self): + def test_nxsdkruntimeservice_loihi(self): process = SimpleProcess(shape=(2, 2), loglevel=logging.DEBUG, logenable=True) diff --git a/tests/lava/magma/runtime/test_runtime_service.py b/tests/lava/magma/runtime/test_runtime_service.py index ff835d7f0..b049aa6b7 100644 --- a/tests/lava/magma/runtime/test_runtime_service.py +++ b/tests/lava/magma/runtime/test_runtime_service.py @@ -160,11 +160,11 @@ def test_data(self, test_case: unittest.TestCase): class TestNxSdkRuntimeService(unittest.TestCase): - # Run Loihi Tests using example command below: + # Run Loihi Tests using example below: # - # SLURM=1 LOIHI_GEN=N3B3 BOARD=ncl-og-05 PARTITION=oheogulch + # "SLURM=1 LOIHI_GEN=N3B3 BOARD=ncl-og-05 PARTITION=oheogulch # RUN_LOIHI_TESTS=1 python -m unittest - # tests/lava/magma/runtime/test_runtime_service.py + # tests/lava/magma/runtime/test_runtime_service.py" run_loihi_tests: bool = Utils.get_bool_env_setting("RUN_LOIHI_TESTS") From 1991f384913236fb5cf88443d308c2946e323ef2 Mon Sep 17 00:00:00 2001 From: Marcus G K Williams Date: Thu, 24 Feb 2022 14:11:16 -0800 Subject: [PATCH 16/18] Update docstrings for RuntimeService Signed-off-by: Marcus G K Williams --- .../runtime_services/runtime_service.py | 39 ++++++++++++++----- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/src/lava/magma/runtime/runtime_services/runtime_service.py b/src/lava/magma/runtime/runtime_services/runtime_service.py index aae47d7d5..00e6065dd 100644 --- a/src/lava/magma/runtime/runtime_services/runtime_service.py +++ b/src/lava/magma/runtime/runtime_services/runtime_service.py @@ -32,21 +32,22 @@ class NxBoard(): pass -"""This file defines the interface for RuntimeService which is responsible for +"""The RuntimeService interface is responsible for coordinating the execution of a group of process models belonging to a common -synchronization domain. The domain might follow a SyncProtocol or could be -asynchronous too. The processes and their corresponding process models are -selected by the Runtime depending on the RunConfiguration assigned at the +synchronization domain. The domain will follow a SyncProtocol or will be +asynchronous. The processes and their corresponding process models are +selected by the Runtime dependent on the RunConfiguration assigned at the start of execution. For each group of processes which follow the same -protocol and would execute on the same node, Runtime creates a RuntimeService -which will coordinate all actions/commands from Runtime onto the processes as -well as return any acknowledgement back to Runtime. +protocol and execute on the same node, the Runtime creates a RuntimeService. +Each RuntimeService coordinates all actions and commands from the Runtime, + transmitting them to the the processes under it's managment and +returning action and command responses back to Runtime. -Currently we envision few different kinds of RuntimeService: +RuntimeService Types: 1. PyRuntimeService: (Abstract Class) Coordinates process models executing on the CPU and written in Python. - Following are the Concrete Implementations: + Concrete Implementations: a. LoihiPyRuntimeService: Coordinates process models executing on the CPU and written in Python and following the LoihiProtocol. b. AsyncPyRuntimeService: Coordinates process models executing on @@ -54,13 +55,23 @@ class NxBoard(): 2. CRuntimeService: (Abstract Class) Coordinates/Manages process models executing on the CPU/Embedded CPU and written in C - Following are the Concrete Implementations: + Concrete Implementations: a. LoihiCRuntimeService: Coordinates process models executing on the CPU/Embedded CPU and written in C and following the LoihiProtocol. +3. NcRuntimeService: (Abstract Class) Coordinates/Manages process models + executing on a Loihi NeuroCore. + Concrete Implementations: + a. NxSdkRuntimeService: Coordinates process models executing on a Loihi + NeuroCore and written in Python following the LoihiProtocol. """ class PyRuntimeService(AbstractRuntimeService): + """Abstract RuntimeService for Python, it provides base methods + for start and run. It is not meant to instantiated directly + but used by inheritance + """ + def __init__(self, protocol: ty.Type[AbstractSyncProtocol], loglevel: int = logging.WARNING,): @@ -503,6 +514,8 @@ class NxSdkRuntimeService(NcRuntimeService): Communication protocol used by NxSdkRuntimeService loihi_version: LoihiVersion Version of Loihi Chip to use, N2 or N3 + loglevel: int + Log level to use for logging """ def __init__(self, @@ -536,6 +549,12 @@ class NxBoard(): self.log.debug("NxSdkRuntimeService is initialized") def run(self): + """Retrieves commands from the runtime. STOP and PAUSE commands are + relayed to NxCore. Otherwise the number of time steps is received as + a RUN command. In this case RUN is relayed to NxCore with number of time + steps. The loop ends when receiving the STOP command from the runtime. + """ + self.log.debug("NxSdkRuntime is running") selector = CspSelector() channel_actions = [(self.runtime_to_service, lambda: 'cmd')] From 027910d97efae67c2e898497f2f45d53ea2a6a48 Mon Sep 17 00:00:00 2001 From: Marcus G K Williams Date: Thu, 24 Feb 2022 15:11:50 -0800 Subject: [PATCH 17/18] Update logging Signed-off-by: Marcus G K Williams --- src/lava/magma/core/process/process.py | 27 +++++++++++++++---- .../runtime/test_nxsdkruntimeservice_loihi.py | 4 +-- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/lava/magma/core/process/process.py b/src/lava/magma/core/process/process.py index 329139c3d..5a330ad40 100644 --- a/src/lava/magma/core/process/process.py +++ b/src/lava/magma/core/process/process.py @@ -228,13 +228,30 @@ def __init__(self, **kwargs): self.name: str = kwargs.pop("name", f"Process_{self.id}") + # Setup Logging self.loglevel: int = kwargs.pop("loglevel", logging.WARNING) + self.loglevelconsole: int = kwargs.pop("loglevelconsole", logging.ERROR) self.logfile: str = kwargs.pop("logfile", "lava.log") - self.logenable: bool = bool(kwargs.pop("logenable", False)) - if self.logenable: - logging.basicConfig(filename=self.logfile) - self.log = logging.getLogger(__name__) - self.log.setLevel(self.loglevel) + self.logfileenable: bool = bool(kwargs.pop("logfileenable", False)) + self.log = logging.getLogger() + + formatter = logging.Formatter( + '%(asctime)s:%(levelname)s: %(name)s - %(message)s', + datefmt='%m/%d/%Y %I:%M:%S%p') + + console_handler = logging.StreamHandler() + console_handler.setLevel(self.loglevelconsole) + console_handler.setFormatter(formatter) + + if self.logfileenable: + logging.basicConfig( + filename=self.logfile, + level=self.loglevel, + format='%(asctime)s:%(levelname)s: %(name)s - %(message)s', + datefmt='%m/%d/%Y %I:%M:%S%p' + ) + + self.log.addHandler(console_handler) # kwargs will be used for ProcessModel initialization later self.init_args: dict = kwargs diff --git a/tests/lava/magma/runtime/test_nxsdkruntimeservice_loihi.py b/tests/lava/magma/runtime/test_nxsdkruntimeservice_loihi.py index bdad98072..5a459803b 100644 --- a/tests/lava/magma/runtime/test_nxsdkruntimeservice_loihi.py +++ b/tests/lava/magma/runtime/test_nxsdkruntimeservice_loihi.py @@ -61,9 +61,7 @@ class TestProcessLoihi2(unittest.TestCase): @unittest.skipUnless(run_loihi_tests, "runtime_to_runtimeservice_to_nxcore_to_loihi") def test_nxsdkruntimeservice_loihi(self): - process = SimpleProcess(shape=(2, 2), - loglevel=logging.DEBUG, - logenable=True) + process = SimpleProcess(shape=(2, 2)) run_config = SimpleRunConfig(sync_domains=[]) process.run(condition=RunSteps(num_steps=10), run_cfg=run_config) process.run(condition=RunSteps(num_steps=5), run_cfg=run_config) From 3b232d879c5bdb2c119a6ad63a396e68681e17fa Mon Sep 17 00:00:00 2001 From: Marcus G K Williams Date: Thu, 24 Feb 2022 15:18:34 -0800 Subject: [PATCH 18/18] Remove unneeded logging import Signed-off-by: Marcus G K Williams --- tests/lava/magma/runtime/test_nxsdkruntimeservice_loihi.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/lava/magma/runtime/test_nxsdkruntimeservice_loihi.py b/tests/lava/magma/runtime/test_nxsdkruntimeservice_loihi.py index 5a459803b..106125d6f 100644 --- a/tests/lava/magma/runtime/test_nxsdkruntimeservice_loihi.py +++ b/tests/lava/magma/runtime/test_nxsdkruntimeservice_loihi.py @@ -1,4 +1,3 @@ -import logging import unittest from tests.lava.test_utils.utils import Utils