Skip to content

Commit

Permalink
Merge pull request #78 from PAICookers/frontend
Browse files Browse the repository at this point in the history
Frontend: added new neuron
  • Loading branch information
KafCoppelia authored Mar 19, 2024
2 parents c401877 + 3358b50 commit f4a6721
Show file tree
Hide file tree
Showing 19 changed files with 322 additions and 217 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,8 @@

- 支持无限嵌套深度的网络
- 支持全展开2D卷积算子构建与部署(`padding` 不支持)
- 修复当 `keep_shape=True` 时,,神经元状态变量在运行时尺寸错误
- 修复当 `keep_shape=True` 时,神经元状态变量在运行时尺寸错误

## v1.0.0a6

- 新增 `Always1Neuron` 神经元,该神经元将在工作期间持续输出1,不得单独存在,需存在前向突触与其连接。
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<a href="https://github.com/PAICookers/PAIBox/blob/master/pyproject.toml">
<img src="https://img.shields.io/pypi/pyversions/paibox">
</a>
<a href="https://github.com/PAICookers/PAIBox/releases/tag/v1.0.0a5">
<a href="https://github.com/PAICookers/PAIBox/releases/tag/v1.0.0a6">
<img src="https://img.shields.io/github/v/release/PAICookers/PAIBox?include_prereleases&color=orange">
</a>
<a href="https://www.codefactor.io/repository/github/PAICookers/PAIBox">
Expand Down
2 changes: 0 additions & 2 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,3 @@
- 目前仅支持芯片的SNN模式。要想支持ANN模式,前端上需要支持ANN模式的算理。同时,ANN/SNN模式是物理核的行为,因此当网络拓扑中同时存在两种模式的神经元时,需要以此为约束条件,从而影响分组结果(类似第6点。例如,同为ANN模式下的不同神经元可被分在同一组)
- [ ] 神经元计算模型随机性仿真
- 基于硬件神经元计算机制,实现带有部分随机数的计算机制。并考虑与硬件的实现相接近的程度(能否接近实现与硬件一致的随机数发生行为)
- [ ] 网络节点间自定义延迟( `tick_relative` )尝试ringbuffer结构优化
- 目前使用的delay_register方案,将在每个神经元组后方申请较大内存,用于存放delay至后继节点的数据。可尝试使用ringbuffer的思路对此结构进行优化。
2 changes: 2 additions & 0 deletions paibox/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,10 +233,12 @@ def reset_state(self, *args, **kwargs):

@property
def shape_in(self) -> Tuple[int, ...]:
"""Actual shape of input."""
raise NotImplementedError

@property
def shape_out(self) -> Tuple[int, ...]:
"""Actual shape of output."""
raise NotImplementedError

@property
Expand Down
4 changes: 2 additions & 2 deletions paibox/mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,8 @@ def set_reset_value(self, name: str, init_value: Any) -> None:

def __getattr__(self, name: str) -> Any:
if "_memories" in self.__dict__:
_memories = self.__dict__.get("_memories")
if _memories is not None and name in _memories:
_memories = self.__dict__["_memories"]
if name in _memories:
return _memories[name]

raise AttributeError(f"Attribute '{name}' not found!")
Expand Down
251 changes: 146 additions & 105 deletions paibox/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@
from .collector import Collector
from .exceptions import PAIBoxWarning, RegisterError
from .mixin import Container
from .neuron import Neuron
from .node import NodeDict
from .projection import InputProj, Projection
from .synapses import RIGISTER_MASTER_KEY_FORMAT, SynSys

__all__ = ["DynSysGroup", "Network"]

ComponentsType: TypeAlias = Union[InputProj, NeuDyn, SynSys]
ComponentsType: TypeAlias = Union[InputProj, Neuron, SynSys]


class DynSysGroup(DynamicSys, Container):
Expand All @@ -32,16 +33,7 @@ def __init__(
)

def update(self, **kwargs) -> None:
"""For a network, the operating nodes within it will be distributed according to the network level \
where they are located. For I, S & N, if the network is a two-level nested network, it can be \
divided into Ix, Sx, Nx and Iy, Sy, Ny, where x & y are two parts containing many operations. \
TODO Prove that the operation sequence I->S->N can be divided into Ix->Sx->Nx->Iy->Sy->Ny & it has \
nothing to do with the network topology.
The above expression cannot be completely established, and the condition needs to be met: the \
dependent synapses of the neuron are in the same subgraph.
"""
"""Find nodes of the network recursively."""
nodes = (
self.nodes(include_self=False, find_recursive=True)
.subset(DynamicSys)
Expand Down Expand Up @@ -77,136 +69,177 @@ def __call__(self, **kwargs) -> None:
return self.update(**kwargs)

def add_components(self, *implicit: DynamicSys, **explicit: DynamicSys) -> None:
"""Add new components. When a component is passed in explicitly, its tag name \
can be specified. Otherwise `.name` will be used.
"""Add new components. When a component is passed in explicitly, its tag name can \
be specified. Otherwise `.name` will be used.
NOTE: After instantiated the components outside the `DynSysGroup`, you should \
call `add_components()` to actually add the new components to itself.
NOTE: After instantiated the components outside the `DynSysGroup`, you should call \
`add_components()` to actually add the new components to itself.
"""
for comp in implicit:
setattr(self, comp.name, comp)

for tag, comp in explicit.items():
setattr(self, tag, comp)

def _remove_component(self, remove: DynamicSys) -> None:
"""Remove a component in the network."""
for tag, obj in self.__dict__.items():
if obj is remove:
delattr(self, tag)
break

return None
def disconnect_syn(
self, target_syn: SynSys, exclude_source: bool = False
) -> SynSys:
"""Disconnect a synapse in the nwtwork.
def _disconnect_neudyn(
self,
neudyn_a: NeuDyn,
condition: Callable[[SynSys], bool],
neudyn_b: Optional[NeuDyn] = None,
remove_syn: bool = True,
) -> List[SynSys]:
nodes = self.nodes(level=1, include_self=False).subset(DynamicSys).unique()

if neudyn_b is None:
self._assert_neudyn(nodes, neudyn_a)
else:
self._assert_neudyn(nodes, neudyn_a, neudyn_b)

target_syns = self._find_syn_to_unregi(nodes, condition)
Args:
- target_syn: target synapse.
- exclude_source: whether to disconnect the source. If so, remove the synapse \
from the network
if target_syns:
for syn in target_syns:
self._disconnect_syn(syn)
Returns: the disconnected synapse.
"""
ret = target_syn.dest.unregister_master(
RIGISTER_MASTER_KEY_FORMAT.format(target_syn.name)
)
if ret is not target_syn:
raise RegisterError("unregister failed!")

# FIXME The disconnected synapses will not effect the simulation.
# However, it will effect the placement in the backend.
if remove_syn:
self._remove_component(syn)
if not exclude_source:
self._remove_component(target_syn)

return target_syns
else:
warnings.warn("There is no synapse to unregister.", PAIBoxWarning)
return []
return target_syn

def disconnect_neudyn_from(
self, neudyn_a: NeuDyn, neudyn_b: NeuDyn, remove: bool = True
def disconnect_neuron_from(
self, neuron_a: Neuron, neuron_b: Neuron
) -> List[SynSys]:
"""Disconnect synapses between `NeuDyn` A & B.
"""Disconnect synapses between `Neuron` A & B and remove the synapses from the network.
Args:
- neudyn_a: target `NeuDyn` A.
- neudyn_b: target `NeuDyn` B.
- remove: whether to remove the original synapses from the network.
- neuron_a: target neuron A.
- neuron_b: target neuron B.
Returns: the disconnected synapses.
Returns: the disconnected synapses in list.
"""
return self._disconnect_neudyn(
neudyn_a,
lambda syn: syn.source is neudyn_a and syn.dest is neudyn_b,
neudyn_b,
remove,
return self._disconn_neuron(
neuron_a,
lambda syn: syn.source is neuron_a and syn.dest is neuron_b,
neuron_b,
remove_syn=True,
)

def diconnect_neudyn_succ(
self, neudyn: NeuDyn, remove: bool = True
) -> List[SynSys]:
"""Disconnect successor synapses of `neudyn`.
# Not sure about specific needs
# def diconnect_neuron_succ(self, neuron: Neuron) -> List[SynSys]:
# """Disconnect successor synapses of `neuron`.

Args:
- neudyn: target `NeuDyn`.
- remove: whether to remove the original synapses from the network.
# Args:
# - neuron: target neuron.
# - remove: whether to remove the original synapses from the network.
# - new_source: only valid when `remove` is false.

Returns: the disconnected synapses.
"""
return self._disconnect_neudyn(
neudyn, lambda syn: syn.source is neudyn, remove_syn=remove
)
# Returns: the disconnected synapses.
# """
# return self._disconn_neuron(
# neuron, lambda syn: syn.source is neuron, remove_syn=True
# )

def diconnect_neudyn_pred(
self, neudyn: NeuDyn, remove: bool = True
) -> List[SynSys]:
"""Disconnect predecessor synapses of `neudyn`.
# def replace_neuron_succ(self, neuron: Neuron, new_source: Neuron) -> List[SynSys]:
# """Replace the source of successor synapses of `neuron` with new one."""
# disconn_syns = self._disconn_neuron(
# neuron, lambda syn: syn.source is neuron, remove_syn=False
# )

Args:
- neudyn: target `NeuDyn`.
- remove: whether to remove the original synapses from the network.
# for syn in disconn_syns:
# syn.source = new_source

Returns: the disconnected synapses.
"""
return self._disconnect_neudyn(
neudyn, lambda syn: syn.dest is neudyn, remove_syn=remove
)
# return disconn_syns

# def replace_neuron_pred(self, neuron: Neuron, new_source: Neuron) -> List[SynSys]:
# """Replace the destination of predecessor synapses of `neuron` with new one.

# Args:
# - neuron: target neuron.
# - remove: whether to remove the original synapses from the network.

def insert_neudyn(
# Returns: the disconnected synapses.
# """
# disconn_syns = self._disconn_neuron(
# neuron, lambda syn: syn.dest is neuron, remove_syn=False
# )

# for syn in disconn_syns:
# syn.dest = new_source

# return disconn_syns

def insert_between_neuron(
self,
neudyn_a: NeuDyn,
neudyn_b: NeuDyn,
components_to_insert: Tuple[ComponentsType, ...],
neuron_a: Neuron,
neuron_b: Neuron,
cpn_to_insert: Tuple[ComponentsType, ...],
replace: bool = True,
remove: bool = True,
) -> List[SynSys]:
"""Insert new components between `NeuDyn` A & B.
"""Insert new components between `Neuron` A & B.
Args:
- neudyn_a: target `NeuDyn` A.
- neudyn_b: target `NeuDyn` B.
- components_to_insert: new components to insert between `neudyn_a` & `neudyn_b`.
- neuron_a: target neuron A.
- neuron_b: target neuron B.
- cpn_to_insert: components to insert between `neuron_a` & `neuron_b`.
- replace: whether to disconnect the original synapses. Default is `True`.
- remove: whether to remove the original synapses from the network. Valid only when `replace` is `True`.
Returns: the disconnected synapses.
Returns: the disconnected synapses in list.
"""
if replace:
removed_syn = self.disconnect_neudyn_from(neudyn_a, neudyn_b, remove=remove)
removed_syn = self.disconnect_neuron_from(neuron_a, neuron_b)
else:
removed_syn = []

self.add_components(*components_to_insert)
self.add_components(*cpn_to_insert)

return removed_syn

def _remove_component(self, remove: DynamicSys) -> None:
"""Remove a component in the network."""
for tag, obj in self.__dict__.items():
if obj is remove:
delattr(self, tag)
break

return None

def _disconn_neuron(
self,
neuron_a: Neuron,
condition: Callable[[SynSys], bool],
neuron_b: Optional[Neuron] = None,
remove_syn: bool = True,
) -> List[SynSys]:
nodes = (
self.nodes(include_self=False, find_recursive=True)
.subset(DynamicSys)
.unique()
)

if neuron_b is None:
self._assert_neuron(nodes, neuron_a)
else:
self._assert_neuron(nodes, neuron_a, neuron_b)

target_syns = self._find_syn_to_disconn(nodes, condition)

if target_syns:
for syn in target_syns:
self._disconn_syn(syn)

# The disconnected synapses will not effect the simulation, but will
# effect the placement in the backend.
# If the disconnected synapses aren't removed from the network, do cleaning
# before the compilation in the backend.
# TODO Add a pre-processing step before the compilation.
if remove_syn:
self._remove_component(syn)

return target_syns
else:
warnings.warn("there is no synapses to disconnect.", PAIBoxWarning)
return []

@staticmethod
def _find_syn_to_unregi(
def _find_syn_to_disconn(
nodes: Collector, condition: Callable[[SynSys], bool]
) -> List[SynSys]:
syns = []
Expand All @@ -218,19 +251,27 @@ def _find_syn_to_unregi(
return syns

@staticmethod
def _disconnect_syn(target_syn: SynSys) -> None:
def _disconn_syn(target_syn: SynSys) -> None:
ret = target_syn.dest.unregister_master(
RIGISTER_MASTER_KEY_FORMAT.format(target_syn.name)
)
if ret is not target_syn:
raise RegisterError("unregister failed!")

@staticmethod
def _disconn_succ_syn(target_syn: SynSys) -> None:
ret = target_syn.dest.unregister_master(
RIGISTER_MASTER_KEY_FORMAT.format(target_syn.name)
)
if ret is not target_syn:
raise RegisterError("Unregister failed!")
raise RegisterError("unregister failed!")

@staticmethod
def _assert_neudyn(nodes: Collector, *neudyns: NeuDyn) -> None:
neu_dyns = nodes.subset(NeuDyn)
def _assert_neuron(nodes: Collector, *neurons: Neuron) -> None:
neu_dyns = nodes.subset(Neuron)

if any(neudyn not in neu_dyns.values() for neudyn in neudyns):
raise ValueError("Not all NeuDyn found in the network.")
if any(neuron not in neu_dyns.values() for neuron in neurons):
raise ValueError("not all neurons found in the network.")


Network: TypeAlias = DynSysGroup
Expand Down
8 changes: 2 additions & 6 deletions paibox/neuron/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
from .base import Neuron
from .neurons import IF as IF
from .neurons import LIF as LIF
from .neurons import Always1Neuron as Always1Neuron
from .neurons import PhasicSpiking as PhasicSpiking
from .neurons import TonicSpiking as TonicSpiking

__all__ = [
"IF",
"LIF",
"TonicSpiking",
"PhasicSpiking",
]
__all__ = ["IF", "LIF", "TonicSpiking", "PhasicSpiking", "Always1Neuron"]
Loading

0 comments on commit f4a6721

Please sign in to comment.