神经形态计算软件框架——Lava入门

文档教程:Lava Software Framework — Lava documentation (lava-nc.org)

Github:https://github.com/lava-nc参考Lava DL了解如何使用Lava训练深层网络

Gitee的examples文件夹中添加的是笔者对tutorials文件夹中每个教程的代码实现与优化,以及一些可执行的Demo

A software framework for neuromorphic computing

Getting Started

Installing Lava from Binaries

如果您只需要在python环境中使用lava包,我们将通过GitHub下载并安装lava-nclava-dl,最后通过conda下载并安装lava。

打开python终端并运行:

[Windows/MacOS/Linux]

pip install lava-nc-0.3.0.tar.gz
pip install lava-dl-0.2.0.tar.gz

# 建议尝试conda安装,'python_requires': '>=3.8,<3.11' conda
install lava -c conda-forge conda install lava-dl -c conda-forge

 

Running Lava on Intel Loihi

英特尔的神经形态Loihi 1或2研究系统目前尚未上市。有兴趣将Lava与Loihi系统结合使用的开发人员需要加入Intel神经形态研究社区(INRC)。一旦成为INRC的成员,开发人员将获得云托管Loihi系统的访问权,或能够以贷款方式获得物理Loihi系统。此外,Intel还将提供magma库的其他专有组件,用于为需要安装到同一Lava命名空间中的Loihi系统编译Process。

登录英特尔外部vLab计算机。在您的INRC帐户设置期间向您发送了说明。如果您不知道/不记得登录说明,请发送电子邮件至:nrc_support@intel-research.net

# Login to INRC VM with your credentials
# Follow the instructions to Install or Clone Lava
cd /nfs/ncl/releases/lava/0.3.0
pip install lava-nc-0.3.0.tar.gz

请发送电子邮件至inrc_interest@intel.com申请研究提案模板以申请INRC成员资格。

 

Coding example (Lava-0.3.0)

End-to-end:

1、tutorial01_mnist_digit_classification.ipynb [To Fixed]

在本教程中,我们将构建一个没有任何卷积层的多层前馈分类器。架构如下所示:

上述三个Process是:

  • SpikeInput Process——使用图像输入,通过整合和发放动态生成脉冲
  • ImageClassifier Process——封装Dense连接和LIF神经元的前馈架构
  • Output Process——累积前馈进程的输出脉冲并推断类标签

Lava Process

下面我们创建Lava Process类。我们只需要在这里定义Process的结构。下面的ProcessModels中指定了有关如何执行Process的详细信息。

如上所述,我们定义Process为:

  • 将输入图像从这些偏差转换为二值脉冲(SpikeInput)
  • 三层全连接前馈网络(MnistClassifier)
  • 累积输出脉冲并推断输入图像的类别(OutputProcess)

ProcessModels的修饰符

  • @implements: 通过参数proc将ProcessModel与Process关联。使用参数protocol,我们将指定ProcessModel使用的同步协议。在本教程中,所有ProcessModels按照LoihiProtocol执行。这意味着,与Loihi芯片类似,每个时间步骤都分为spikingpre-learning-managementlearning和post-learning-management四个阶段。有必要使用run_spk函数在spiking阶段指定ProcessModel的行为。其他阶段是可选的,其对应函数分别为run_pre_mgmt、run_lrn以及run_post_mgmt。这三个函数都有对应的守卫函数(Guard function)来决定是否执行该阶段。
  • @requires: 指定将在其上执行ProcessModel的硬件资源。在本教程中,我们将在一张CPU上执行所有ProcessModels。

ImageClassifier ProcessModel

请注意,以下ProcessModel进一步分解为子Process,这些子Process实现了LIF神经动态和Dense连接。

还要注意,SubProcessModel模型实际上并不包含任何具体的执行。这由组成Process的ProcessModel来处理。

Execution and results

下面,我们将在num_images迭代次数的循环中运行分类器进程。每次迭代都将Process运行num_steps_per_image的时间步数。我们采用这种方法在每张图像之后清除分类器内所有三个LIF层的神经状态。我们需要清除神经状态,因为网络参数是在假设每个推理的神经状态重置的情况下训练的。

注意:下面我们使用Var.set()函数来设置内部状态变量的值。使用RefPorts可以实现相同的行为。

### MNIST Digit Classification with Lava (tutorial01_mnist_digit_classification)

# General Imports
import os
import numpy as np

# Import Process level primitives
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

class SpikeInput(AbstractProcess):
    """Reads image data from the MNIST dataset and converts it to spikes.
    The resulting spike rate is proportional to the pixel value"""

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        n_img = kwargs.pop('num_images', 25)
        n_steps_img = kwargs.pop('num_steps_per_image', 128)
        shape = (784,)
        self.spikes_out = OutPort(shape=shape)  # Input spikes to the classifier
        self.label_out = OutPort(shape=(1,))  # Ground truth labels to OutputProc
        self.num_images = Var(shape=(1,), init=n_img)
        self.num_steps_per_image = Var(shape=(1,), init=n_steps_img)
        self.input_img = Var(shape=shape)
        self.ground_truth_label = Var(shape=(1,))
        self.v = Var(shape=shape, init=0)
        self.vth = Var(shape=(1,), init=kwargs['vth'])
        
        
class ImageClassifier(AbstractProcess):
    """A 3 layer feed-forward network with LIF and Dense Processes."""

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        
        # Using pre-trained weights and biases
        trained_weights_path = kwargs.pop('trained_weights_path', os.path
                                          .join('.','mnist_pretrained.npy'))
        real_path_trained_wgts = os.path.realpath(trained_weights_path)

        wb_list = np.load(real_path_trained_wgts, allow_pickle=True)
        w0 = wb_list[0].transpose().astype(np.int32)
        w1 = wb_list[2].transpose().astype(np.int32)
        w2 = wb_list[4].transpose().astype(np.int32)
        b1 = wb_list[1].astype(np.int32)
        b2 = wb_list[3].astype(np.int32)
        b3 = wb_list[5].astype(np.int32)

        self.spikes_in = InPort(shape=(w0.shape[1],))
        self.spikes_out = OutPort(shape=(w2.shape[0],))
        self.w_dense0 = Var(shape=w0.shape, init=w0)
        self.b_lif1 = Var(shape=(w0.shape[0],), init=b1)
        self.w_dense1 = Var(shape=w1.shape, init=w1)
        self.b_lif2 = Var(shape=(w1.shape[0],), init=b2)
        self.w_dense2 = Var(shape=w2.shape, init=w2)
        self.b_output_lif = Var(shape=(w2.shape[0],), init=b3)
        
        # Up-level currents and voltages of LIF Processes
        # for resetting (see at the end of the tutorial)
        self.lif1_u = Var(shape=(w0.shape[0],), init=0)
        self.lif1_v = Var(shape=(w0.shape[0],), init=0)
        self.lif2_u = Var(shape=(w1.shape[0],), init=0)
        self.lif2_v = Var(shape=(w1.shape[0],), init=0)
        self.oplif_u = Var(shape=(w2.shape[0],), init=0)
        self.oplif_v = Var(shape=(w2.shape[0],), init=0)
        
        
class OutputProcess(AbstractProcess):
    """Process to gather spikes from 10 output LIF neurons and interpret the
    highest spiking rate as the classifier output"""

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        shape = (10,)
        n_img = kwargs.pop('num_images', 25)
        self.num_images = Var(shape=(1,), init=n_img)
        self.spikes_in = InPort(shape=shape)
        self.label_in = InPort(shape=(1,))
        self.spikes_accum = Var(shape=shape)  # Accumulated spikes for classification
        self.num_steps_per_image = Var(shape=(1,), init=128)
        self.pred_labels = Var(shape=(n_img,))
        self.gt_labels = Var(shape=(n_img,))

# ProcessModels for Python execution
# Import parent classes for ProcessModels
from lava.magma.core.model.sub.model import AbstractSubProcessModel
from lava.magma.core.model.py.model import PyLoihiProcessModel

# Import ProcessModel ports, data-types
from lava.magma.core.model.py.ports import PyInPort, PyOutPort
from lava.magma.core.model.py.type import LavaPyType

# Import execution protocol and hardware resources
from lava.magma.core.sync.protocols.loihi_protocol import LoihiProtocol
from lava.magma.core.resources import CPU

# Import decorators
from lava.magma.core.decorator import implements, requires

# Import MNIST dataset
from lava.utils.dataloader.mnist import MnistDataset
np.set_printoptions(linewidth=np.inf)

# SpikingInput ProcessModel
@implements(proc=SpikeInput, protocol=LoihiProtocol)
@requires(CPU)
class PySpikeInputModel(PyLoihiProcessModel):
    num_images: int = LavaPyType(int, int, precision=32)
    spikes_out: PyOutPort = LavaPyType(PyOutPort.VEC_DENSE, bool, precision=1)
    label_out: PyOutPort = LavaPyType(PyOutPort.VEC_DENSE, np.int32,
                                      precision=32)
    num_steps_per_image: int = LavaPyType(int, int, precision=32)
    input_img: np.ndarray = LavaPyType(np.ndarray, int, precision=32)
    ground_truth_label: int = LavaPyType(int, int, precision=32)
    v: np.ndarray = LavaPyType(np.ndarray, int, precision=32)
    vth: int = LavaPyType(int, int, precision=32)
    
    def __init__(self, proc_params):
        super().__init__(proc_params=proc_params)
        self.mnist_dataset = MnistDataset()
        self.curr_img_id = 0

    def post_guard(self):
        """Guard function for PostManagement phase.
        """
        if self.time_step % self.num_steps_per_image == 1:
            return True
        return False

    def run_post_mgmt(self):
        """Post-Management phase: executed only when guard function above 
        returns True.
        """
        img = self.mnist_dataset.images[self.curr_img_id]
        self.ground_truth_label = self.mnist_dataset.labels[self.curr_img_id]
        self.input_img = img.astype(np.int32) - 127
        self.v = np.zeros(self.v.shape)
        self.label_out.send(np.array([self.ground_truth_label]))
        self.curr_img_id += 1

    def run_spk(self):
        """Spiking phase: executed unconditionally at every time-step
        """
        self.v[:] = self.v + self.input_img
        s_out = self.v > self.vth
        self.v[s_out] = 0  # reset voltage to 0 after a spike
        self.spikes_out.send(s_out)

# ImageClassifier ProcessModel
from lava.proc.lif.process import LIF
from lava.proc.dense.process import Dense            

@implements(ImageClassifier)
@requires(CPU)
class PyImageClassifierModel(AbstractSubProcessModel):
    def __init__(self, proc):
        self.dense0 = Dense(shape=(64, 784), weights=proc.w_dense0.init)
        self.lif1 = LIF(shape=(64,), b=proc.b_lif1.init, vth=400,
                        dv=0, du=4095)
        self.dense1 = Dense(shape=(64, 64), weights=proc.w_dense1.init)
        self.lif2 = LIF(shape=(64,), b=proc.b_lif2.init, vth=350,
                        dv=0, du=4095)
        self.dense2 = Dense(shape=(10, 64), weights=proc.w_dense2.init)
        self.output_lif = LIF(shape=(10,), b=proc.b_output_lif.init,
                              vth=1, dv=0, du=4095)

        proc.spikes_in.connect(self.dense0.s_in)
        self.dense0.a_out.connect(self.lif1.a_in)
        self.lif1.s_out.connect(self.dense1.s_in)
        self.dense1.a_out.connect(self.lif2.a_in)
        self.lif2.s_out.connect(self.dense2.s_in)
        self.dense2.a_out.connect(self.output_lif.a_in)
        self.output_lif.s_out.connect(proc.spikes_out)
        
        # Create aliases of SubProcess variables
        proc.lif1_u.alias(self.lif1.u)
        proc.lif1_v.alias(self.lif1.v)
        proc.lif2_u.alias(self.lif2.u)
        proc.lif2_v.alias(self.lif2.v)
        proc.oplif_u.alias(self.output_lif.u)
        proc.oplif_v.alias(self.output_lif.v)

# OutputProcess ProcessModel
@implements(proc=OutputProcess, protocol=LoihiProtocol)
@requires(CPU)
class PyOutputProcessModel(PyLoihiProcessModel):
    label_in: PyInPort = LavaPyType(PyInPort.VEC_DENSE, int, precision=32)
    spikes_in: PyInPort = LavaPyType(PyInPort.VEC_DENSE, bool, precision=1)
    num_images: int = LavaPyType(int, int, precision=32)
    spikes_accum: np.ndarray = LavaPyType(np.ndarray, np.int32, precision=32)
    num_steps_per_image: int = LavaPyType(int, int, precision=32)
    pred_labels: np.ndarray = LavaPyType(np.ndarray, int, precision=32)
    gt_labels: np.ndarray = LavaPyType(np.ndarray, int, precision=32)
        
    def __init__(self, proc_params):
        super().__init__(proc_params=proc_params)
        self.current_img_id = 0

    def post_guard(self):
        """Guard function for PostManagement phase.
        """
        if self.time_step % self.num_steps_per_image == 0 and \
                self.time_step > 1:
            return True
        return False

    def run_post_mgmt(self):
        """Post-Management phase: executed only when guard function above 
        returns True.
        """
        gt_label = self.label_in.recv()
        pred_label = np.argmax(self.spikes_accum)
        self.gt_labels[self.current_img_id] = gt_label
        self.pred_labels[self.current_img_id] = pred_label
        self.current_img_id += 1
        self.spikes_accum = np.zeros_like(self.spikes_accum)

    def run_spk(self):
        """Spiking phase: executed unconditionally at every time-step
        """
        spk_in = self.spikes_in.recv()
        self.spikes_accum = self.spikes_accum + spk_in

# Connecting Processes
num_images = 25
num_steps_per_image = 128

# Create Process instances
spike_input = SpikeInput(num_images=num_images,
                         num_steps_per_image=num_steps_per_image,
                         vth=1)
mnist_clf = ImageClassifier(
    trained_weights_path=os.path.join('.', 'mnist_pretrained.npy'))
output_proc = OutputProcess(num_images=num_images)

# Connect Processes
spike_input.spikes_out.connect(mnist_clf.spikes_in)
mnist_clf.spikes_out.connect(output_proc.spikes_in)
# Connect Input directly to Output for ground truth labels
spike_input.label_out.connect(output_proc.label_in)

# Execution and results
from lava.magma.core.run_conditions import RunSteps
from lava.magma.core.run_configs import Loihi1SimCfg

# Loop over all images
for img_id in range(num_images):
    print(f"\rCurrent image: {img_id+1}", end="")
    
    # Run each image-inference for fixed number of steps
    mnist_clf.run(
        condition=RunSteps(num_steps=num_steps_per_image),
        run_cfg=Loihi1SimCfg(select_sub_proc_model=True,
                             select_tag='fixed_pt'))
    
    # Reset internal neural state of LIF neurons
    mnist_clf.lif1_u.set(np.zeros((64,), dtype=np.int32))
    mnist_clf.lif1_v.set(np.zeros((64,), dtype=np.int32))
    mnist_clf.lif2_u.set(np.zeros((64,), dtype=np.int32))
    mnist_clf.lif2_v.set(np.zeros((64,), dtype=np.int32))
    mnist_clf.oplif_u.set(np.zeros((10,), dtype=np.int32))
    mnist_clf.oplif_v.set(np.zeros((10,), dtype=np.int32))

# Gather ground truth and predictions before stopping exec
ground_truth = output_proc.gt_labels.get().astype(np.int32)
predictions = output_proc.pred_labels.get().astype(np.int32)

# Stop the execution
mnist_clf.stop()

accuracy = np.sum(ground_truth==predictions)/ground_truth.size * 100

print(f"\nGround truth: {ground_truth}\n"
      f"Predictions : {predictions}\n"
      f"Accuracy    : {accuracy}")
View Code

重要提示:分类器是一个简单的前馈模型,使用预先训练的网络参数(权重和偏差)。它说明了如何在Lava中构建、编译和运行功能模型。请参考Lava DL了解如何使用Lava训练深层网络。

 

In-depth:

1、 tutorial01_installing_lava.ipynb

Running Lava in a Container (Docker Support):Docker image for Lava is currently Work In Progress.

PS:然而,我并没有在Docker Hub找到对应的Lava镜像

 

2、tutorial02_processes.ipynb

What is a Process?

本教程将演示如何创建一个Process来模拟一组LIF神经元。但在Lava中,Process的概念广泛应用于这个例子之外。通常,Process描述一个单独的程序单元,该程序单元封装

  1. 存储其状态的数据,
  2. 描述如何操作数据的算法,
  3. 与其他进程共享数据的端口,以及
  4. 一种便于用户交互的API。

因此,一个Process可以像单个神经元或突触一样简单,像完整的神经网络一样复杂,也可以像外围设备的流接口或规则程序代码的执行实例一样非神经形态。

Process彼此独立,因为它们主要在自己的本地内存上运行,同时通过通道在彼此之间传递消息。因此,不同的Process同时进行异步计算,反映了神经形态硬件固有的高度并行性。此外,并行Process还可以安全地避免共享内存交互的副作用。

一旦用Python编写了一个Process,Lava就允许它在不同的后端(如CPU、GPU或神经形态核)无缝运行。因此,开发人员可以轻松地在经典计算硬件上测试和基准测试他们的应用程序,然后将其部署到神经形态硬件上。此外,Lava利用了分布式异构硬件(如Loihi),因为它可以在神经形态核上运行某些Process,而在嵌入式常规CPU和GPU上并行运行其他Process。

虽然Lava提供了一个不断增长的Process库,但您可以轻松编写自己的Process,以满足您的需要。

How to build a Process?

Overall architecture

Lava中的所有Process都共享一个通用架构,因为它们继承自同一个AbstractProcess类。每个Process由以下四个关键组件组成。

AbstractProcess: Defining VarsPorts, and the API

创建自己的新Process时,需要从AbstractProcess类继承。作为一个例子,我们将实现LIF类,这是一组LIF神经元。

下面的代码实现了您也可以在Lava的Process库中找到的LIF类,但通过一个额外的API方法对其进行了扩展,该方法打印LIF神经元的状态。

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


class LIF(AbstractProcess):
    """Leaky-Integrate-and-Fire neural process with activation input and spike
    output ports a_in and s_out.
    """
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        shape = kwargs.get("shape", (1,))
        self.a_in = InPort(shape=shape)
        self.s_out = OutPort(shape=shape)
        self.u = Var(shape=shape, init=0)
        self.v = Var(shape=shape, init=0)
        self.du = Var(shape=(1,), init=kwargs.pop("du", 0))
        self.dv = Var(shape=(1,), init=kwargs.pop("dv", 0))
        self.bias = Var(shape=shape, init=kwargs.pop("bias", 0))
        self.vth = Var(shape=(1,), init=kwargs.pop("vth", 10))

    def print_vars(self):
        """Prints all variables of a LIF process and their values."""

        sp = 3 * "  "
        print("Variables of the LIF:")
        print(sp + "u:    {}".format(str(self.u.get())))
        print(sp + "v:    {}".format(str(self.v.get())))
        print(sp + "du:   {}".format(str(self.du.get())))
        print(sp + "dv:   {}".format(str(self.dv.get())))
        print(sp + "bias: {}".format(str(self.bias.get())))
        print(sp + "vth:  {}".format(str(self.vth.get())))
View Code

您可能已经注意到,大多数变量都是由标量整数初始化的。但是突触电流u_说明,通常可以用维数等于或小于其形状参数指定的数值对象初始化_Vars。初始值将按比例放大,以匹配运行时的Var维度。

关于Process类,还有两件更重要的事情需要注意:

  • 它只定义LIF神经元的界面,而不定义其时间行为。
  • 它对计算后端完全不可知,因此,如果您想在CPU和Loihi上运行代码,它将保持不变。

ProcessModel: Defining the behavior of a Process

Process的行为由其ProcessModel定义。对于LIF神经元的具体例子,ProcessModel描述了它们的电流和电压如何对突触输入作出反应,这些状态如何随时间演变,以及神经元何时应该发出脉冲。

一个Process可以有多个ProcessModels,分别用于要在其上运行它的每个后端。

下面的代码实现了一个ProcessModel,它定义了CPU应该如何运行LIF Process。请不要担心这里的精确实现。下一篇关于ProcessModels的教程将详细解释代码。

import numpy as np
from lava.magma.core.sync.protocols.loihi_protocol import LoihiProtocol
from lava.magma.core.model.py.ports import PyInPort, PyOutPort
from lava.magma.core.model.py.type import LavaPyType
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

@implements(proc=LIF, protocol=LoihiProtocol)
@requires(CPU)
@tag('floating_pt')
class PyLifModel(PyLoihiProcessModel):
    a_in: PyInPort = LavaPyType(PyInPort.VEC_DENSE, np.float)
    s_out: PyOutPort = LavaPyType(PyOutPort.VEC_DENSE, bool, precision=1)
    u: np.ndarray = LavaPyType(np.ndarray, np.float)
    v: np.ndarray = LavaPyType(np.ndarray, np.float)
    bias: np.ndarray = LavaPyType(np.ndarray, np.float)
    du: float = LavaPyType(float, np.float)
    dv: float = LavaPyType(float, np.float)
    vth: float = LavaPyType(float, np.float)

    def run_spk(self):
        a_in_data = self.a_in.recv()
        self.u[:] = self.u * (1 - self.du)
        self.u[:] += a_in_data
        bias = self.bias
        self.v[:] = self.v * (1 - self.dv) + self.u + bias
        s_out = self.v >= self.vth
        self.v[s_out] = 0  # Reset voltage to 0
        self.s_out.send(s_out)
View Code

Instantiating the Process

现在我们可以创建一个Process的实例,在本例中是一组3个LIF神经元。

n_neurons = 3

lif = LIF(shape=(3,), du=0, dv=0, bias=3, vth=10)

Interacting with Processes

一旦你实例化了一组LIF神经元,你就可以很容易地与它们进行交互。

Accessing Vars

您始终可以读取进程Vars的当前值来确定进程状态。例如,所有三个神经元都应该用零膜电压初始化。

print(lif.v.get())

如上所述,在此示例中,Var v已初始化为标量值,该标量值同时描述所有三个神经元的膜电压。

Using custom APIs

为了方便用户与Process交互,他们可以使用为他们提供的自定义API。对于LIF神经元,定义了一个自定义函数,允许用户检查LIF Process的内部变量。查看是否所有VAR都已正确设置。

lif.print_vars()

Executing a Process

一旦Process被实例化并且您对其状态感到满意,就可以运行该Process。只要为所需的后端定义了ProcessModel,Process就可以在计算硬件之间无缝运行。不要担心这里的细节,您将在单独的教程中了解Lava如何构建、编译和运行Process。

要运行Process,请指定要运行的步骤数并选择所需的后端。

from lava.magma.core.run_configs import Loihi1SimCfg
from lava.magma.core.run_conditions import RunSteps

lif.run(condition=RunSteps(num_steps=1), run_cfg=Loihi1SimCfg())

每个LIF神经元的电压现在应该从其初始值0增加了偏差值。

Update Vars

您还可以手动更新内部变量。例如,您可以在两次运行之间将膜电压设置为新值。

lif.v.set(np.array([1, 2, 3]) )
print(lif.v.get())

请注意,一旦Process运行,set()方法就可用了。在第一次运行之前,请使用Process的__init__函数来设置变量。

后面的教程将演示在运行时使用Process代码访问、存储和更改变量的更复杂方法。

最后,停止Process以终止其执行。

lif.stop()

 

3、tutorial03_process_models.ipynb

本教程解释了Lava ProcessModels如何实现Lava Process的行为。每个Lava Process必须有一个或多个ProcessModels,这些ProcessModels提供了如何执行Lava Process的说明。Lava ProcessModels允许用户使用一种或多种语言(如Python、C或Loihi neurocore接口)和各种计算资源(如CPU、GPU或Loihi芯片)指定Process的行为。通过这种方式,ProcessModels支持跨平台无缝执行Process,并允许用户构建与平台特定实现无关的应用程序和算法。

ProcessModels有两大类:LeafProcessModel和SubProcessModel。LeafProcessModels将是本教程的重点,它直接实现Process的行为。SubProcessModel允许用户使用其他Process实现和组合Process的行为,从而支持创建分层Process。

在本教程中,我们将介绍如何创建多个LeafProcessModels,这些模型可用于实现LIF神经元Process的行为。

Create a LIF Process

首先,我们将严格按照Lava的Magma core库中的定义来定义LIF Process。在此,LIF神经Process通过InPort a_in接受来自突触输入的活动,并通过OutPort s_out输出脉冲活动。 

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

class LIF(AbstractProcess):
    """Leaky-Integrate-and-Fire (LIF) neural Process.

    LIF dynamics abstracts to:
    u[t] = u[t-1] * (1-du) + a_in         # neuron current
    v[t] = v[t-1] * (1-dv) + u[t] + bias  # neuron voltage
    s_out = v[t] > vth                    # spike if threshold is exceeded
    v[t] = 0                              # reset at spike

    Parameters
    ----------
    du: Inverse of decay time-constant for current decay.
    dv: Inverse of decay time-constant for voltage decay.
    bias: Mantissa part of neuron bias.
    bias_exp: Exponent part of neuron bias, if needed. Mostly for fixed point
              implementations. Unnecessary for floating point
              implementations. If specified, bias = bias * 2**bias_exp.
    vth: Neuron threshold voltage, exceeding which, the neuron will spike.
    """
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        shape = kwargs.get("shape", (1,))
        du = kwargs.pop("du", 0)
        dv = kwargs.pop("dv", 0)
        bias = kwargs.pop("bias", 0)
        bias_exp = kwargs.pop("bias_exp", 0)
        vth = kwargs.pop("vth", 10)

        self.shape = shape
        self.a_in = InPort(shape=shape)
        self.s_out = OutPort(shape=shape)
        self.u = Var(shape=shape, init=0)
        self.v = Var(shape=shape, init=0)
        self.du = Var(shape=(1,), init=du)
        self.dv = Var(shape=(1,), init=dv)
        self.bias = Var(shape=shape, init=bias)
        self.bias_exp = Var(shape=shape, init=bias_exp)
        self.vth = Var(shape=(1,), init=vth)
View Code

Create a Python LeafProcessModel that implements the LIF Process

现在,我们将创建一个Python ProcessModel或PyProcessModel,它在CPU计算资源上运行并实现LIF Process行为。

Setup

我们首先导入所需的Lava类。首先,我们设置计算资源(CPU)和SyncProtocol。SyncProtocol定义了并行Process同步的方式和时间。这里我们使用LoihiProtocol,它定义了在Loihi芯片上执行所需的同步阶段,但用户也可以指定一个完全异步的协议或一个自定义的SyncProtocol。导入的修饰符对于指定我们的ProcessModel的资源需求和SyncProtocol是必要的。

import numpy as np
from lava.magma.core.decorator import implements, requires
from lava.magma.core.resources import CPU
from lava.magma.core.sync.protocols.loihi_protocol import LoihiProtocol

现在,我们导入ProcessModel继承的父类,以及所需的Port和Variable类型。PyLoihiProcessModel是实现LoihiProtocol的Python ProcessModel的抽象类。我们的ProcessModel需要镜像LIF Process的Ports和Variables。Python ProcessModel的in-Ports和out-Ports类型分别为PyInPort和PyOutPort,而变量类型为LavaPyType。

from lava.magma.core.model.py.model import PyLoihiProcessModel
from lava.magma.core.model.py.ports import PyInPort, PyOutPort
from lava.magma.core.model.py.type import LavaPyType
from lava.proc.lif.process import LIF

Defining a PyLifModel for LIF

现在,我们定义一个LeafProcessModel PyLifModel,它实现LIF Process的行为。

@implements修饰符指定与ProcessModel相对应的SyncProtocol (protocol=LoihiProtocol)和Process类(proc=LIF)。@requires修饰符指定ProcessModel所需的CPU计算资源。@tag修饰符指定ProcessModel的精度。这里我们演示了一个具有标准浮点精度的ProcessModel。

接下来,我们定义ProcessModel变量和端口。ProcessModel中定义的变量和端口必须(按名称和编号)与用于编译的相应Process中定义的变量和端口完全匹配。我们的LIF示例Process和PyLifModel都有1个输入端口、1个输出端口,以及u、v、du、dv、bias、bias_exp和vth的变量。ProcessModel中的变量和端口必须使用特定于LeafProcessModel实现语言的LavaType对象进行初始化。这里,使用LavaPyType初始化变量,以匹配我们的Python LeafProcessModel实现。通常,LavaTypes指定变量和端口的类型,包括它们的数字d_type、精度和动态范围。Lava编译器读取这些LavaTypes,以根据Process中提供的初始值初始化具体的类对象。

然后填写run_spk()方法来执行LIF神经动态。run_spk()是一种特定于PyLoihiProcessModel类型的LeafProcessModels的方法,该方法通过正确处理LoihiProtocol SyncProtocol的所有阶段来执行用户定义的神经元动态。在本例中,run_spike将通过PyInPort a_in接受来自突触输入的活动,并根据基于电流(CUBA)的动力学积分电流和电压后,通过PyInPort s_out输出脉冲活动。recv()和send()是支持基于通道的ProcessModel输入和输出通信的方法。

import numpy as np
from lava.magma.core.sync.protocols.loihi_protocol import LoihiProtocol
from lava.magma.core.model.py.ports import PyInPort, PyOutPort
from lava.magma.core.model.py.type import LavaPyType
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.proc.lif.process import LIF


@implements(proc=LIF, protocol=LoihiProtocol)
@requires(CPU)
@tag('floating_pt')
class PyLifModel1(PyLoihiProcessModel):
    a_in: PyInPort = LavaPyType(PyInPort.VEC_DENSE, np.float)
    s_out: PyOutPort = LavaPyType(PyOutPort.VEC_DENSE, bool, precision=1)
    u: np.ndarray = LavaPyType(np.ndarray, np.float)
    v: np.ndarray = LavaPyType(np.ndarray, np.float)
    bias: np.ndarray = LavaPyType(np.ndarray, np.float)
    bias_exp: np.ndarray = LavaPyType(np.ndarray, np.float)
    du: float = LavaPyType(float, np.float)
    dv: float = LavaPyType(float, np.float)
    vth: float = LavaPyType(float, np.float)

    def run_spk(self):
        a_in_data = self.a_in.recv()
        self.u[:] = self.u * (1 - self.du)
        self.u[:] += a_in_data
        bias = self.bias * (2**self.bias_exp)
        self.v[:] = self.v * (1 - self.dv) + self.u + bias
        s_out = self.v >= self.vth
        self.v[s_out] = 0  # Reset voltage to 0
        self.s_out.send(s_out)
View Code

Compile and run PyLifModel

from lava.magma.core.run_configs import Loihi1SimCfg
from lava.magma.core.run_conditions import RunSteps

lif = LIF(shape=(3,), du=0, dv=0, bias=3, vth=10)

run_cfg = Loihi1SimCfg()
lif.run(condition=RunSteps(num_steps=10), run_cfg=run_cfg)
print(lif.v.get())
View Code

Create an NcProcessModel that implements the LIF Process

Process可以有多个ProcessModel,不同的ProcessModel可以在不同的计算资源上执行。Lava编译器很快将支持使用AbstractNcProcessModel类在Loihi Neurocores上执行Process (即该ProcessModel目前未实现)。下面是实现相同LIF Process的NcLifModel示例。

# ProcessModels (tutorial03_process_models)

from lava.proc.lif.process import LIF
from lava.magma.core.decorator import implements, requires
from lava.magma.core.resources import Loihi1NeuroCore
from lava.magma.core.model.nc.model import NcLoihiProcessModel
from lava.magma.core.model.nc.ports import NcInPort, NcOutPort
from lava.magma.core.model.nc.type import LavaNcType, NcVar

@implements(proc=LIF) #Note that the NcLoihiProcessModel class implies the useage of the Loihi SyncProtcol
@requires(Loihi1NeuroCore)
class NcLifModel(NcLoihiProcessModel):
    # Declare port implementation
    a_in: InPort =   LavaNcType(NcInPort, precision=16)
    s_out: OutPort = LavaNcType(NcOutPort, precision=1)
    # Declare variable implementation
    u: NcVar =         LavaNcType(NcVar, precision=24)
    v: NcVar =         LavaNcType(NcVar, precision=24)
    b: NcVar =         LavaNcType(NcVar, precision=12)
    du: NcVar =        LavaNcType(NcVar, precision=12)
    dv: NcVar =        LavaNcType(NcVar, precision=12)
    vth: NcVar =       LavaNcType(NcVar, precision=8)

    def allocate(self, net: mg.Net):
        """Allocates neural resources in 'virtual' neuro core."""
        num_neurons = self.in_args['shape'][0]
        # Allocate output axons
        out_ax = net.out_ax.alloc(size=num_neurons)
        net.connect(self.s_out, out_ax)
        # Allocate compartments
        cx_cfg = net.cx_cfg.alloc(size=1,
                                  du=self.du,
                                  dv=self.dv,
                                  vth=self.vth)
        cx = net.cx.alloc(size=num_neurons,
                          u=self.u,
                          v=self.v,
                          b_mant=self.b,
                          cfg=cx_cfg)
        cx.connect(out_ax)
        # Allocate dendritic accumulators
        da = net.da.alloc(size=num_neurons)
        da.connect(cx)
        net.connect(self.a_in, da)
View Code

Selecting 1 ProcessModel: More on LeafProcessModel attributes and relations

我们已经演示了单个LIF Process的多个ProcessModel实现。那么,如何在运行时选择几个ProcessModels中的一个作为Process的实现?为了回答这个问题,我们深入研究了LeafProcessModel的属性以及LeafProcessModel、Process和SyncProtocol之间的关系。

如下所示,LeafProcessModel实现了一个Process(在我们的示例中为LIF)和一个SyncProtocol(在我们的示例中为LoihiProtocol)。LeafProcessModel只有一种类型。在本教程中,PyLifModel的类型为PyloiHipProcessModel,而NcLifModel的类型为nCloiHipProcessModel。LeafProcessModel还具有一个或多个资源需求,用于指定执行所需的计算资源(例如,CPU、GPU或Loihi Neurocores)或外围资源(例如访问摄像头)。最后,LeafProcessModel可以有一个或多个用户定义的标记。除了其他可定制的原因外,标记还可以用于为多Process应用程序分组多个ProcessModel,或者用于区分具有相同类型和SyncProtocol的多个LeafProcessModel实现。作为一个示例,我们在上面演示了一个用于LIF的PyloiHipProcessModel,它使用浮点精度,并具有标记@tag('floating_pt')。还存在一个PyloiHipProcessModel,它使用定点精度,并且在Loihi芯片上执行LIF时具有位精度的行为;此ProcessModel通过标记@tag('fixed')。同时,LeafProcessModel的类型、标记和需求属性允许用户定义一个RunConfig,该RunConfig选择几个LeafProcessModels中的哪一个用于在运行时实现Process。Core Lava库还将提供几个预配置的RunConfig。

 

4、tutorial04_execution.ipynb

本教程介绍如何执行单个Process和Process网络,如何配置执行,如何暂停、恢复和停止执行,以及如何手动设置编译器和运行时以进行更细粒度的控制。

Configuring and starting execution

要开始执行进程,请调用其方法run(condition=..., run_cfg=...)。必须通过传入RunCondition和RunConfiguration来配置执行。

Run conditions

RunCondition指定进程的执行时间。

运行条件RunSteps以指定的时间步数执行Process,下面的示例中为42。执行将在指定的时间步数后自动暂停。您还可以指定对run()的调用是否会阻止程序流。

from lava.magma.core.run_conditions import RunSteps

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

运行条件RunContinuous允许您连续运行Process。在这种情况下,Process将无限期运行,直到显式调用pause()或stop()(请参见下文)。此调用从不阻塞程序流(blocking=False)。

from lava.magma.core.run_conditions import RunContinuous

run_condition = RunContinuous()

Run configurations

RunConfig指定应该在哪些设备上执行Process。基于RunConfig,Process选择并初始化其关联的ProcessModel中的一个,该模型在特定编程语言和特定计算资源中实现Process的行为。如果Process具有由其他Process组成的SubProcessModel,则RunConfig会选择子Process的适当ProcessModel实现。

由于Lava目前只支持在单个CPU上模拟执行,因此唯一预定义的RunConfig是Loihi1SimCfg,它模拟在Loihi上执行的进程。我们将为Loihi 1和2以及其他设备(如GPU)提供更多预定义的运行配置。

下面的示例指定Process(及其所有连接的Process和SubProcess)在CPU上以Python执行。

from lava.magma.core.run_configs import Loihi1SimCfg

run_cfg = Loihi1SimCfg()

我们现在可以使用RunCondition和RunConfig来执行一个简单的LIF神经元。

from lava.proc.lif.process import LIF
from lava.magma.core.run_conditions import RunSteps
from lava.magma.core.run_configs import Loihi1SimCfg

# create a Process for a LIF neuron
lif = LIF()

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

Running multiple Processes

对Process调用run()还将执行与其连接的所有Process。在下面的示例中,三个Process lif1、dense和lif2按顺序连接。我们在Process lif2上调用run()。由于lif2连接到dense,dense连接到lif1,因此将执行所有三个Process。如这里所示,无论Process连接的方向如何,执行都将覆盖整个连接的Process网络。

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

# create processes
lif1 = LIF()
dense = Dense()
lif2 = LIF()

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

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

我们将在将来增加更多关于运行多个Process的内容,包括同步和在不同设备上运行Process网络。

Pausing, resuming, and stopping execution

Important Note:

目前,Lava不支持pause()和RunContinuous。这些功能不久将在功能版本中启用。然而,下面的示例说明了如何暂停、恢复和停止Lava中的Process。

调用Process的pause()方法会暂停执行,但保留其状态。然后,用户可以检查和操作该Process,如下例所示。

之后,可以通过再次调用run()来恢复执行。

调用Process的stop()方法完全终止其执行。与暂停执行相反,stop()不保留Process的状态。如果在硬件设备上执行Process,则Process与设备之间的连接也会终止。

"""
from lava.proc.lif.process import LIF
from lava.magma.core.run_conditions import RunContinuous
from lava.magma.core.run_configs import Loihi1SimCfg

lif3 = LIF()

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

# pause execution
lif3.pause()

# inspect the state of the Process, here, the voltage variable 'v'
print(lif.v.get())
# manipulate the state of the Process, here, resetting the voltage to zero
lif3.v.set(0)

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

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

Manual compilation and execution

在许多情况下,只需创建Process实例并调用其run()方法即可。在内部调用run()首先编译Process,然后开始执行。还可以按顺序手动调用这些步骤,例如在开始执行之前检查或操纵Process。

  1. 实例化阶段:这是对Process的init方法的调用,该方法实例化Process的对象。

from lava.proc.lif.process import LIF
from lava.proc.dense.process import Dense

lif1 = LIF()
dense = Dense()
lif2 = LIF()
View Code

  2. 配置阶段:Process实例化后,可以通过其公共API对其进行进一步配置,并通过其Ports连接到其他Process。此外,可以为Lava Vars定义探测,以便在执行Process中记录其演变的时间序列(即将发布的Lava版本将支持探测)。

# connect the processes
lif1.s_out.connect(dense.s_in)
dense.a_out.connect(lif2.a_in)
View Code

  3. 编译阶段:配置Process后,需要将其编译为可执行的。在编译阶段之后,仍然可以操作和检查Process的状态。

from lava.magma.compiler.compiler import Compiler
from lava.magma.core.run_configs import Loihi1SimCfg

# create a compiler
compiler = Compiler()

# compile the Process (and all connected Processes) into an executable
executable = compiler.compile(lif2, run_cfg=Loihi1SimCfg())
View Code

  4. 执行阶段:编译完成后,可以执行Process。执行阶段确保(之前的)编译阶段已经完成,并以其他方式调用它。

from lava.magma.runtime.runtime import Runtime
from lava.magma.core.run_conditions import RunSteps

# create and initialize a runtime
runtime = Runtime(run_cond=run_condition, exe=executable)
runtime.initialize()

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

# stop execution
runtime.stop()
View Code

以下操作会自动执行以上所有操作:

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

# create Processes
lif = LIF()
dense = Dense()

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

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

# stop Processes
lif.stop()
View Code

 

5、tutorial05_connect_processes.ipynb

Connect processes

本教程介绍如何连接Process以构建异步操作和交互Process网络。

Building a network of Processes

Process是Lava的主要组成部分。每个Process可以执行不同的计算,通常取决于一些输入数据和/或创建输出数据。在Process之间传输I/O数据是Lava的一个关键要素。一个Process可以有各种输入和输出Ports,然后通过通道连接到另一个Process的相应Ports。这允许构建异步操作和交互Process的网络。

Create a connection

目标是将Process P1与Process P2连接起来。P1有一个被称为out的输出端口OutPort,P2有一个被称为inp的输入端口InPort。从P1提供给端口out的数据应传输到P2,并从端口inp接收。

from lava.magma.core.process.process import AbstractProcess
from lava.magma.core.process.ports.ports import InPort, OutPort

作为第一步,我们定义进程P1和P2及其各自的端口out和inp。

# Minimal process with an OutPort
class P1(AbstractProcess):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        shape = kwargs.get('shape', (2,))
        self.out = OutPort(shape=shape)


# Minimal process with an InPort
class P2(AbstractProcess):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        shape = kwargs.get('shape', (2,))
        self.inp = InPort(shape=shape)
View Code

Process P1和P2需要一个相应的ProcessModel来实现它们的Ports和一个用于发送和接收数据的简单RunConfig。

在ProcessModels中,Python代码应该在CPU上执行。输入和输出Port应能够接收/发送整数向量,并打印传输的数据。

因此,ProcessModel继承了AbstractPyProcessModel的形式,以便执行Python代码,而配置的ComputerSource是一个CPU。Port使用LavaPyType。LavaPyType指定端口的预期数据格式。使用参数PyOutPort.VECDENSE_和int选择整数类型的稠密向量。这些Ports可以通过调用send或recv来发送和接收数据。发送和接收的数据随后打印出来。

import numpy as np
from lava.magma.core.model.py.model import PyLoihiProcessModel
from lava.magma.core.decorator import implements, requires, tag
from lava.magma.core.resources import CPU
from lava.magma.core.model.py.type import LavaPyType
from lava.magma.core.model.py.ports import PyInPort, PyOutPort
from lava.magma.core.sync.protocols.loihi_protocol import LoihiProtocol
View Code
# A minimal PyProcModel implementing P1
@implements(proc=P1, protocol=LoihiProtocol)
@requires(CPU)
@tag('floating_pt')
class PyProcModelA(PyLoihiProcessModel):
    out: PyOutPort = LavaPyType(PyOutPort.VEC_DENSE, int)

    def run_spk(self):
        data = np.array([1, 2])
        print("Sent output data of P1: {}".format(data))
        self.out.send(data)


# A minimal PyProcModel implementing P2
@implements(proc=P2, protocol=LoihiProtocol)
@requires(CPU)
@tag('floating_pt')
class PyProcModelB(PyLoihiProcessModel):
    inp: PyInPort = LavaPyType(PyInPort.VEC_DENSE, int)

    def run_spk(self):
        in_data = self.inp.recv()
        print("Received input data for P2: {}".format(in_data))
View Code

接下来,实例化Process P1和P2,并且来自Process P1的输出端口out与Process P2的输入端口inp连接。

sender = P1()
recv = P2()

# Connecting output port to an input port
sender.out.connect(recv.inp)

sender = P1()
recv = P2()

# ... or connecting an input port from an output port
recv.inp.connect_from(sender.out)
View Code

在这些Process上调用run()将首先调用编译器。在编译期间,通过在P1和P2之间创建通道来设置指定的连接。现在,可以在执行期间传输数据,如输出打印语句所示。

from lava.magma.core.run_configs import Loihi1SimCfg
from lava.magma.core.run_conditions import RunSteps
sender.run(RunSteps(num_steps=1), Loihi1SimCfg())
sender.stop()

P1的实例sender通过其输出端口将数据[1 2]发送到P2的实例recv的输入端口,在那里接收数据。

Possible connections

第一个例子非常简单。原则上,Process可以有多个输入和输出端口,这些端口可以彼此自由连接。此外,可以以相同的方式连接在不同计算资源上执行的Process。

不过,有一些事情需要考虑:

  • 输入端口无法连接到输出端口
  • 连接端口的形状和数据类型必须匹配
  • 一个输入端口可能从多个输出端口获取数据——默认行为是传入数据的总和
  • 一个输出端口可以向多个输入端口发送数据——所有输入端口接收相同的数据

Connect multiple InPorts from a single OutPort

sender = P1()
recv1 = P2()
recv2 = P2()
recv3 = P2()

# An OutPort can connect to multiple InPorts
# Either at once...
sender.out.connect([recv1.inp, recv2.inp, recv3.inp])

sender = P1()
recv1 = P2()
recv2 = P2()
recv3 = P2()

# ... or consecutively
sender.out.connect(recv1.inp)
sender.out.connect(recv2.inp)
sender.out.connect(recv3.inp)
View Code
sender.run(RunSteps(num_steps=1), Loihi1SimCfg())
sender.stop()
View Code

P1的实例sender将数据[1 2]发送到P2的3个实例recv1、recv2、recv3。

Connecting multiple InPorts to a single OutPort

如果多个输入端口连接到同一个输出端口,则默认行为是将每个输入端口的数据添加到输出端口。

sender1 = P1()
sender2 = P1()
sender3 = P1()
recv = P2()

# An InPort can connect to multiple OutPorts
# Either at once...
recv.inp.connect_from([sender1.out, sender2.out, sender3.out])

sender1 = P1()
sender2 = P1()
sender3 = P1()
recv = P2()

# ... or consecutively
sender1.out.connect(recv.inp)
sender2.out.connect(recv.inp)
sender3.out.connect(recv.inp)
View Code
sender1.run(RunSteps(num_steps=1), Loihi1SimCfg())
sender1.stop()
View Code

P1的3个实例sender1、sender2、sender3将数据[1 2]发送到P2的实例recv,其中数据总计为[3 6]。

完整代码:

### Connect processes (tutorial05_connect_processes.ipynb)

import numpy as np
from lava.magma.core.process.process import AbstractProcess
from lava.magma.core.process.ports.ports import InPort, OutPort
from lava.magma.core.model.py.model import PyLoihiProcessModel
from lava.magma.core.decorator import implements, requires, tag
from lava.magma.core.resources import CPU
from lava.magma.core.model.py.type import LavaPyType
from lava.magma.core.model.py.ports import PyInPort, PyOutPort
from lava.magma.core.sync.protocols.loihi_protocol import LoihiProtocol
from lava.magma.core.run_configs import Loihi1SimCfg
from lava.magma.core.run_conditions import RunSteps


# Minimal process with an OutPort
class P1(AbstractProcess):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        shape = kwargs.get('shape', (2,))
        self.out = OutPort(shape=shape)


# Minimal process with an InPort
class P2(AbstractProcess):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        shape = kwargs.get('shape', (2,))
        self.inp = InPort(shape=shape)


# A minimal PyProcModel implementing P1
@implements(proc=P1, protocol=LoihiProtocol)
@requires(CPU)
@tag('floating_pt')
class PyProcModelA(PyLoihiProcessModel):
    out: PyOutPort = LavaPyType(PyOutPort.VEC_DENSE, int)

    def run_spk(self):
        data = np.array([1, 2])
        print("Sent output data of P1: {}".format(data))
        self.out.send(data)


# A minimal PyProcModel implementing P2
@implements(proc=P2, protocol=LoihiProtocol)
@requires(CPU)
@tag('floating_pt')
class PyProcModelB(PyLoihiProcessModel):
    inp: PyInPort = LavaPyType(PyInPort.VEC_DENSE, int)

    def run_spk(self):
        in_data = self.inp.recv()
        print("Received input data for P2: {}".format(in_data))


### Example 1 ###
sender = P1()
recv = P2()

# Connecting output port to an input port or connecting an input port from an output port
sender.out.connect(recv.inp)
# recv.inp.connect_from(sender.out)

sender.run(condition=RunSteps(num_steps=1), run_cfg=Loihi1SimCfg())
sender.stop()

print('-------------------------')


### Example 2: Connect multiple InPorts from a single OutPort ###
sender = P1()
recv1 = P2()
recv2 = P2()
recv3 = P2()

# An OutPort can connect to multiple InPorts, either at once or consecutively
sender.out.connect([recv1.inp, recv2.inp, recv3.inp])
# sender.out.connect(recv1.inp)
# sender.out.connect(recv2.inp)
# sender.out.connect(recv3.inp)

sender.run(condition=RunSteps(num_steps=1), run_cfg=Loihi1SimCfg())
sender.stop()

print('-------------------------')


### Example 3: Connecting multiple InPorts to a single OutPort ###
sender1 = P1()
sender2 = P1()
sender3 = P1()
recv = P2()

# An InPort can connect to multiple OutPorts, either at once or consecutively
recv.inp.connect_from([sender1.out, sender2.out, sender3.out])
# sender1.out.connect(recv.inp)
# sender2.out.connect(recv.inp)
# sender3.out.connect(recv.inp)

sender1.run(RunSteps(num_steps=1), Loihi1SimCfg())
sender1.stop()
View Code

 

6、tutorial06_hierarchical_processes.ipynb

Hierarchical Processes and SubProcessModels

之前的教程简要介绍了两类ProcessModels:LeafProcessModels和SubProcessModels。ProcessModel教程详细解释了LeafProcessModels。这些直接以特定计算资源(例如CPU或Loihi Neurocores)所需的语言(例如Python或Loihi Neurocore API)实现Process的行为。相反,SubProcessModels允许用户使用其他Process实现和组合Process的行为。这允许创建分层Process和重用基本ProcessModels,以实现更复杂的ProcessModels。SubProcessModels从其实例化的子Process继承所有计算资源需求。

在本教程中,我们将创建一个具有LIF神经元行为的DenseLayer分层Process。DenseLayer ProcessModel通过基本的LIF和Dense连接Process及其各自的PyLoihiProcessModels实现此行为。

Create LIF and Dense Processes and ProcessModels

ProcessModel教程将介绍如何创建LIF Process和实现PyLoihiProcessModel。我们的DenseLayer Process还需要一个Dense Lava Process和ProcessModel,该模型具有一组稠密的突触连接和权重。Dense连接Process可以用来连接神经Process。为了完整起见,我们将首先简要展示一个示例LIF和Dense Process以及PyLoihiProcessModel。

Create a Dense connection Process

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


class Dense(AbstractProcess):
    """Dense connections between neurons.
    Realizes the following abstract behavior:
    a_out = W * s_in
    """

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        shape = kwargs.get("shape", (1, 1))
        self.s_in = InPort(shape=(shape[1],))
        self.a_out = OutPort(shape=(shape[0],))
        self.weights = Var(shape=shape, init=kwargs.pop("weights", 0))
View Code

Create a Python Dense connection ProcessModel implementing the Loihi Sync Protocol and requiring a CPU compute resource

import numpy as np

from lava.magma.core.sync.protocols.loihi_protocol import LoihiProtocol
from lava.magma.core.model.py.ports import PyInPort, PyOutPort
from lava.magma.core.model.py.type import LavaPyType
from lava.magma.core.resources import CPU
from lava.magma.core.decorator import implements, requires
from lava.magma.core.model.py.model import PyLoihiProcessModel
#from lava.proc.dense.process import Dense


@implements(proc=Dense, protocol=LoihiProtocol)
@requires(CPU)
class PyDenseModel(PyLoihiProcessModel):
    s_in: PyInPort = LavaPyType(PyInPort.VEC_DENSE, bool)
    a_out: PyOutPort = LavaPyType(PyOutPort.VEC_DENSE, np.float)
    weights: np.ndarray = LavaPyType(np.ndarray, np.float)

    def run_spk(self):
        s_in = self.s_in.recv()
        a_out = self.weights[:, s_in].sum(axis=1)
        self.a_out.send(a_out)
View Code

Create a LIF neuron Process

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


class LIF(AbstractProcess):
    """Leaky-Integrate-and-Fire (LIF) neural Process.
    LIF dynamics abstracts to:
    u[t] = u[t-1] * (1-du) + a_in         # neuron current
    v[t] = v[t-1] * (1-dv) + u[t] + bias  # neuron voltage
    s_out = v[t] > vth                    # spike if threshold is exceeded
    v[t] = 0                              # reset at spike
    Parameters
    ----------
    du: Inverse of decay time-constant for current decay.
    dv: Inverse of decay time-constant for voltage decay.
    bias: Neuron bias.
    vth: Neuron threshold voltage, exceeding which, the neuron will spike.
    """
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        shape = kwargs.get("shape", (1,))
        du = kwargs.pop("du", 0)
        dv = kwargs.pop("dv", 0)
        bias = kwargs.pop("bias", 0)
        vth = kwargs.pop("vth", 10)

        self.shape = shape
        self.a_in = InPort(shape=shape)
        self.s_out = OutPort(shape=shape)
        self.u = Var(shape=shape, init=0)
        self.v = Var(shape=shape, init=0)
        self.du = Var(shape=(1,), init=du)
        self.dv = Var(shape=(1,), init=dv)
        self.bias = Var(shape=shape, init=bias)
        self.vth = Var(shape=(1,), init=vth)
        #self.spikes = Var(shape=shape, init=0)
View Code

Create a Python LIF neuron ProcessModel implementing the Loihi Sync Protocol and requiring a CPU compute resource

import numpy as np
from lava.magma.core.sync.protocols.loihi_protocol import LoihiProtocol
from lava.magma.core.model.py.ports import PyInPort, PyOutPort
from lava.magma.core.model.py.type import LavaPyType
from lava.magma.core.resources import CPU
from lava.magma.core.decorator import implements, requires
from lava.magma.core.model.py.model import PyLoihiProcessModel
#from lava.proc.lif.process import LIF


@implements(proc=LIF, protocol=LoihiProtocol)
@requires(CPU)
class PyLifModel(PyLoihiProcessModel):
    a_in: PyInPort = LavaPyType(PyInPort.VEC_DENSE, np.float)
    s_out: PyOutPort = LavaPyType(PyOutPort.VEC_DENSE, bool, precision=1)
    u: np.ndarray = LavaPyType(np.ndarray, np.float)
    v: np.ndarray = LavaPyType(np.ndarray, np.float)
    bias: np.ndarray = LavaPyType(np.ndarray, np.float)
    du: float = LavaPyType(float, np.float)
    dv: float = LavaPyType(float, np.float)
    vth: float = LavaPyType(float, np.float)
    spikes: np.ndarray = LavaPyType(np.ndarray, bool)

    def run_spk(self):
        a_in_data = self.a_in.recv()
        self.u[:] = self.u * (1 - self.du)
        self.u[:] += a_in_data
        self.v[:] = self.v * (1 - self.dv) + self.u + self.bias
        s_out = self.v >= self.vth
        self.v[s_out] = 0  # Reset voltage to 0
        #self.spikes = s_out
        self.s_out.send(s_out)
View Code

Create a DenseLayer Hierarchical Process that encompasses Dense and LIF Process behavior

现在,我们创建了一个结合LIF神经Process和Dense连接Process的DenseLayer分层Process。我们的分层Process包含LIF Process固有的所有变量(u、v、bias、du、dv、vth和s_out)加上Dense Process固有的weights变量。对我们的分层Process的输入端口是s_in,它代表了对我们Dense突触连接的脉冲输入。这些Dense连接与一群LIF神经元形成突触。我们的分层Process的输出端口是s_out,它表示LIF神经元层输出的脉冲。

class DenseLayer(AbstractProcess):
    """Combines Dense and LIF Processes.
    """
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        shape = kwargs.get("shape", (1, 1))
        du = kwargs.pop("du", 0)
        dv = kwargs.pop("dv", 0)
        bias = kwargs.pop("bias", 0)
        bias_exp = kwargs.pop("bias_exp", 0)
        vth = kwargs.pop("vth", 10)
        weights = kwargs.pop("weights", 0)

        self.s_in = InPort(shape=(shape[1],))
        #output of Dense synaptic connections is only used internally
        #self.a_out = OutPort(shape=(shape[0],))
        self.weights = Var(shape=shape, init=weights)
        #input to LIF population from Dense synaptic connections is only used internally
        #self.a_in = InPort(shape=(shape[0],))
        self.s_out = OutPort(shape=(shape[0],))
        self.u = Var(shape=(shape[0],), init=0)
        self.v = Var(shape=(shape[0],), init=0)
        self.bias = Var(shape=(shape[0],), init=bias)
        self.du = Var(shape=(1,), init=du)
        self.dv = Var(shape=(1,), init=dv)
        self.vth = Var(shape=(1,), init=vth)
        #self.spikes = Var(shape=(shape[0],), init=0)
View Code

Create a SubProcessModel that implements the DenseLayer Process using Dense and LIF child Processes

现在,我们将创建实现DenseLayer Process的SubProcessModel。这继承自AbstractSubProcessModel类。回想一下,SubProcessModel还从其子Process的ProcessModel继承计算资源需求。在本例中,我们将使用LIF和Dense ProcessModels,它们需要本教程前面定义的CPU计算资源,因此SubDenseLayerModel将隐含地需要CPU计算资源。

SubDenseLayerModel的__init__()构造函数构建DenseLayer Process的子Process结构。DenseLayer Process通过proc属性传递给__init__()方法。__init__()构造函数首先实例化子LIF和Dense Process。DenseLayer Process的初始条件(实例化子LIF和Dense Process所需的条件)通过proc.init_args访问。

然后,我们将Dense子Process的输入端口connect()到DenseLayer父进程的输入端口,将LIF子Process的输出端口连接到DenseLayer父Process的输出端口。请注意,DenseLayer父Process的端口是使用proc.in_ports和proc.out_ports访问的,而子Process(如LIF)的端口使用self.lif.in_ports和self.lif.out_ports访问。我们的ProcessModel还将Dense连接子Process的输出端口在内部connect()到LIF神经子Process的输入端口。

alias()方法将LIF和Dense子Process的变量公开给DenseLayer父Process。请注意,DenseLayer父Process的变量是使用proc.vars访问的,而像LIF这样的子Process的变量是使用self.lif.vars访问的。请注意,与LeafProcessModel不同,SubProcessModel不需要使用指定的数据类型或精度初始化变量。这是因为所有DenseLayer Process变量(proc.vars)的数据类型和精度都由运行配置选择的特定ProcessModels决定,以实现LIF和Dense子Process。当子Process有多个ProcessModel实现时,这允许跨多种语言和计算资源灵活使用相同的SubProcessModel。因此,SubProcessModel可以使复杂应用程序的组合与特定平台的实现无关。在本例中,我们将使用本教程前面定义的PyLoihiProcessModels实现LIF和Dense Process,因此从LIF和Dense隐式中别名的DenseLayer变量具有PyLifModel和PyDenseModel中指定的LavaPyType类型和精度。

import numpy as np

from lava.proc.lif.process import LIF
from lava.proc.dense.process import Dense
from lava.magma.core.model.sub.model import AbstractSubProcessModel

from lava.magma.core.sync.protocols.loihi_protocol import LoihiProtocol
from lava.magma.core.decorator import implements

@implements(proc=DenseLayer, protocol=LoihiProtocol)
class SubDenseLayerModel(AbstractSubProcessModel):

    def __init__(self, proc):
        """Builds sub Process structure of the Process."""
        # Instantiate child processes
        #input shape is a 2D vec (shape of weight mat)
        shape = proc.init_args.get("shape",(1,1))
        weights = proc.init_args.get("weights",(1,1))
        bias = proc.init_args.get("bias",(1,1))
        vth = proc.init_args.get("vth",(1,1))
        #shape is a 2D vec (shape of weight mat)
        self.dense = Dense(shape=shape, weights=weights)
        #shape is a 1D vec
        self.lif = LIF(shape=(shape[0],),bias=bias,vth=vth)
        # connect Parent in port to child Dense in port
        proc.in_ports.s_in.connect(self.dense.in_ports.s_in)
        # connect Dense Proc out port to LIF Proc in port
        self.dense.out_ports.a_out.connect(self.lif.in_ports.a_in)
        # connect child LIF out port to parent out port
        self.lif.out_ports.s_out.connect(proc.out_ports.s_out)

        proc.vars.u.alias(self.lif.vars.u)
        proc.vars.v.alias(self.lif.vars.v)
        proc.vars.bias.alias(self.lif.vars.bias)
        proc.vars.du.alias(self.lif.vars.du)
        proc.vars.dv.alias(self.lif.vars.dv)
        proc.vars.vth.alias(self.lif.vars.vth)
        proc.vars.weights.alias(self.dense.vars.weights)
        #proc.vars.spikes.alias(self.lif.vars.spikes)
View Code

Run the DenseLayer Process

Run Connected DenseLayer Processes

from lava.magma.core.run_configs import RunConfig, Loihi1SimCfg
from lava.magma.core.run_conditions import RunSteps

dim=(3,3)
#shape=dim
#set targeted weight mat
weights0 = np.zeros(shape=dim)
weights0[1,1]=1
weights1 = weights0
#instantiate 2 DenseLayers
layer0 = DenseLayer(shape=dim,weights=weights0, bias=4, vth=10)
layer1 = DenseLayer(shape=dim,weights=weights1, bias=4, vth=10)
#connect layer 0 to layer 1
layer0.s_out.connect(layer1.s_in)

print('Layer 1 weights: \n', layer1.weights.get(),'\n')
print('\n ----- \n')

rcfg = Loihi1SimCfg(select_tag='floating_pt', select_sub_proc_model=True)

for t in range(9):
    #running layer 1 runs all connected layers (layer 0)
    layer1.run(condition=RunSteps(num_steps=1),run_cfg=rcfg)
    print('t: ',t)
    print('Layer 0 v: ', layer0.v.get())
    print('Layer 1 u: ', layer1.u.get())
    print('Layer 1 v: ', layer1.v.get())
    #print('Layer 1 spikes: ', layer1.spikes.get())
    print('\n ----- \n')
View Code

 

7、tutorial07_remote_memory_access.ipynb

本教程的目标是演示如何使用Lava RefPorts在Process之间启用远程内存访问。在之前的教程中,您已经了解了定义行为的Process和实现特定计算资源(例如CPU或Loihi Neurocores)行为的ProcessModel。

通常,Process只能访问自己的状态,并且只能通过使用端口的消息与环境通信。Lava还允许某些Process(例如CPU上的Process)对其他Process的内部状态执行远程内存访问。Process之间的远程内存访问可能不安全,应谨慎使用,但在定义的情况下可能非常有用。其中一种情况是从嵌入式CPU上的另一个Process访问(读/写)Loihi NeuroCore中一个Process的Var。

在Lava中,即使Process之间的远程内存访问也是通过消息传递来实现的,以保持基于事件的消息传递概念的真实性。读/写是通过Process之间的通道和消息传递实现的,远程Process根据另一个Process的指令修改其内存本身。然而,作为一种方便的功能,RefPorts和VarPorts在语法上简化了与远程Vars交互的行为。

因此,RefPorts允许一个Process在Lava中访问另一个Process的内部Vars。RefPorts允许访问其他Vars,就像它是内部Var一样。

在本教程中,我们将创建最小的Process和ProcessModel,以演示如何使用RefPorts和VarPorts读取和写入Vars。此外,我们将解释将RefPorts与VarPorts和Vars连接的可能性,以及显式和隐式创建的VarPorts的区别。

Create a minimal Process and ProcessModel with a RefPort

ProcessModel教程将介绍如何创建Process和相应的ProcessModel。为了演示RefPorts,我们创建了一个带有RefPort ref的最小Process P1和一个带有Var var的最小Process P2。

from lava.magma.core.process.process import AbstractProcess
from lava.magma.core.process.variable import Var
from lava.magma.core.process.ports.ports import RefPort


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

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

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

我们还创建了相应的ProcessModels PyProcModel1和PyProcModel2,它们实现了Process P1和P2。P2 var的Var值用值5初始化。我们实现的行为会在每个时间步骤打印出P1中的var值,演示RefPort ref的读取能力。之后,我们通过将当前时间步骤添加到var中并使用ref写入来设置var值,演示RefPort的写入能力。

import numpy as np

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


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

    def pre_guard(self):
        return True

    def run_pre_mgmt(self):
        # Retrieve current value of the Var of P2
        cur_val = self.ref.read()
        print("Value of var: {} at time step: {}".format(cur_val, self.time_step))
        
        # Add the current time step to the current value
        new_data = cur_val + self.time_step
        # Write the new value to the Var of P2
        self.ref.write(new_data)


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

Run the Processes

在执行之前,RefPort ref需要与Var var连接。预期输出将是开始时var的初始值5,然后是6 (5+1)、8 (6+2)、11 (8+3)、15(11+4)。 

from lava.magma.core.run_configs import Loihi1SimCfg
from lava.magma.core.run_conditions import RunSteps

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

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

# Run the network for 5 time steps
proc1.run(condition=RunSteps(num_steps=5), run_cfg=Loihi1SimCfg())
proc1.stop()
View Code

Implicit and explicit VarPorts

在上面的示例中,我们演示了RefPort的读写能力,它使用隐式VarPort连接到Var。当connect_Var(..)时,会创建隐式VarPort用于将RefPort与Var连接。RefPort也可以使用connect(..)连接到Process中显式定义的VarPort。为了演示显式VarPort,我们重新定义了Process P2和相应的ProcessModel。

from lava.magma.core.process.ports.ports import VarPort

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

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

这一次,RefPort ref连接到显式定义的VarPort var_port。输出与之前相同。

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

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

# Run the network for 5 time steps
proc1.run(condition=RunSteps(num_steps=5), run_cfg=Loihi1SimCfg())
proc1.stop()
View Code

Options to connect RefPorts and VarPorts

Refports能够以不同的方式连接到Vars和VarPorts。对于分层Process,Refports和VarPorts也可以连接到它们自己。

  • 可以使用connect(..)将RefPorts连接到RefPorts或VarPorts
  • RefPorts可以使用connect_var(..)连接到Vars
  • RefPorts可以使用connect_from(..)从RefPorts接收连接
  • VarPorts可以使用connect(..)连接到VarPorts
  • VarPorts可以使用connect_from(..)从VarPorts或RefPorts接收连接
### Remote Memory Access (tutorial07_remote_memory_access.ipynb)

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 VarPort, RefPort
from lava.magma.core.sync.protocols.loihi_protocol import LoihiProtocol
from lava.magma.core.model.py.ports import PyRefPort, PyVarPort
from lava.magma.core.model.py.type import LavaPyType
from lava.magma.core.resources import CPU
from lava.magma.core.decorator import implements, requires
from lava.magma.core.model.py.model import PyLoihiProcessModel
from lava.magma.core.run_configs import Loihi1SimCfg
from lava.magma.core.run_conditions import RunSteps


class P1(AbstractProcess):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.ref = RefPort(shape=(1,))


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

    # pre_mgmt function can not run normally, use post_mgmt function instead
    def post_guard(self):
        return True

    def run_post_mgmt(self):
        cur_val = self.ref.read()
        print("Value of var: {} at time step: {}".format(cur_val, self.time_step))
        
        new_data = cur_val + self.time_step
        
        self.ref.write(new_data)


class P2(AbstractProcess):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.var = Var(shape=(1,), init=5)


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

proc1 = P1()
proc2 = P2()

proc1.ref.connect_var(proc2.var)

proc1.run(condition=RunSteps(num_steps=5), run_cfg=Loihi1SimCfg())
proc1.stop()

'''
# Implicit and explicit VarPorts
class P2(AbstractProcess):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.var = Var(shape=(1,), init=5)
        self.var_port = VarPort(self.var)


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

proc1 = P1()
proc2 = P2()

proc1.ref.connect(proc2.var_port)

proc1.run(condition=RunSteps(num_steps=5), run_cfg=Loihi1SimCfg())
proc1.stop()
'''
View Code

 

Stay in touch

要定期收到Lava软件框架最新开发和发布的最新信息,请订阅我们的新闻稿

 

Documentation Overview

 

Indices and tables

posted on 2022-05-25 16:03  穷酸秀才大草包  阅读(1153)  评论(0编辑  收藏  举报

导航