Skip to content

Commit d84078e

Browse files
authored
Merge pull request #329 from synsense/328-upgrade-tests-for-new-types-of-network-architecture
328 upgrade tests and documentation for new types of network architecture
2 parents 70cee7a + f454dfe commit d84078e

27 files changed

Lines changed: 381 additions & 136 deletions

docs/about/release_notes.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Release notes
22

3+
## v3.1.3 (04/02/2026)
4+
5+
* Add tests for mapping non-sequential networks
6+
* Fix networks used for tests, they didnt meet the hardware constraints
7+
* Update documentation with the non-sequential networks types that have a test
8+
39
## v3.1.2 (12/12/2025)
410

511
* Update use of visualizer in Sinabs tutorials, previous tutorials were using a deprecated visualizer version.

docs/getting_started/iaf_neuron_model.ipynb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,6 @@
163163
"import torch\n",
164164
"import sinabs.layers as sl\n",
165165
"\n",
166-
"\n",
167166
"# Define a neuron in 'SINABS'\n",
168167
"neuron = sl.IAF()\n",
169168
"\n",

docs/speck/faqs/available_network_arch.md

Lines changed: 80 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -39,93 +39,102 @@ dynapcnn.to(devcie="your device", chip_layers_ordering=[2, 5, 7, 1])
3939

4040
## What network structure can I define?
4141

42-
`Sinabs` can parse a `torch.nn.Sequential` like architecture, so it is recommended to
43-
use a `Sequential` like network.
42+
`Sinabs` can parse a `torch.nn.Sequential` like architecture, so it is recommended to use a `Sequential` like network.
4443

4544
As of `v3.1.0`, we released a network graph extraction feature that helps users deploy their networks with more complex architectures into the devkit.
4645
Our `Speck` chip, in fact, supports branched architectures. With the graph extraction feature, we support a range of network structures, as shown below:
4746

47+
A network with a merge and a split:
4848

49-
Two independent networks:
50-
51-
![Two independent networks](imgs/two-independent-networks.png)
49+
![A network with a merge and a split](imgs/network-with-merge-and-split.png)
5250

5351
Two networks with merging outputs:
5452

5553
![Two networks with merging outputs](imgs/two-networks-merging-output.png)
5654

57-
A network with a merge and a split:
55+
A network with residual connections:
5856

59-
![A network with a merge and a split](imgs/network-with-merge-and-split.png)
57+
![A network with residual connections](imgs/network-with-residual-connection.png)
58+
59+
A more complex network:
6060

61+
![A more complex network](imgs/complex-network.png)
6162

6263
Note: with the graph extracture feature it is possible to implement recurrent neural networks. However, this is not recommended or supported as it can result in deadlock on the chip.
6364

65+
Note2: the use of two parallel network although supported by our chip was not fully considered in our sinabs implementation.
66+
6467
## How to make use of the graph extraction feature?
6568

6669
For general architectures, users need to define their classes, by defining at least the `__init__` method with all the layers, as well as an appropriate `forward` method.
6770

6871
Here is an example to define a network with a merge and a split:
6972

7073
```python
74+
import torch
7175
import torch.nn as nn
7276

7377
from sinabs.activation.surrogate_gradient_fn import PeriodicExponential
7478
from sinabs.layers import IAFSqueeze, Merge, SumPool2d
7579

80+
7681
class SNN(nn.Module):
7782
def __init__(self, batch_size) -> None:
7883
super().__init__()
79-
80-
self.conv1 = nn.Conv2d(2, 1, 2, 1, bias=False)
81-
self.iaf1 = IAFSqueeze(
84+
# -- graph node A --
85+
self.conv_A = nn.Conv2d(2, 4, 2, 1, bias=False)
86+
self.iaf_A = IAFSqueeze(
8287
batch_size=batch_size,
8388
min_v_mem=-1.0,
8489
spike_threshold=1.0,
8590
surrogate_grad_fn=PeriodicExponential(),
8691
)
87-
88-
self.conv2 = nn.Conv2d(1, 1, 2, 1, bias=False)
89-
self.iaf2 = IAFSqueeze(
92+
# -- graph node B --
93+
self.conv_B = nn.Conv2d(4, 4, 2, 1, bias=False)
94+
self.iaf2_B = IAFSqueeze(
9095
batch_size=batch_size,
9196
min_v_mem=-1.0,
9297
spike_threshold=1.0,
9398
surrogate_grad_fn=PeriodicExponential(),
9499
)
95-
self.pool2 = SumPool2d(2, 2)
96-
97-
self.conv3 = nn.Conv2d(1, 1, 2, 1, bias=False)
98-
self.iaf3 = IAFSqueeze(
100+
self.pool_B = SumPool2d(2, 2)
101+
# -- graph node C --
102+
self.conv_C = nn.Conv2d(4, 4, 2, 1, bias=False)
103+
self.iaf_C = IAFSqueeze(
99104
batch_size=batch_size,
100105
min_v_mem=-1.0,
101106
spike_threshold=1.0,
102107
surrogate_grad_fn=PeriodicExponential(),
103108
)
104-
self.pool3 = SumPool2d(2, 2)
105-
self.pool3a = SumPool2d(5, 5)
106-
107-
self.conv4 = nn.Conv2d(1, 1, 2, 1, bias=False)
108-
self.iaf4 = IAFSqueeze(
109+
self.pool_C = SumPool2d(2, 2)
110+
# -- graph node D --
111+
self.conv_D = nn.Conv2d(4, 4, 2, 1, bias=False)
112+
self.iaf_D = IAFSqueeze(
109113
batch_size=batch_size,
110114
min_v_mem=-1.0,
111115
spike_threshold=1.0,
112116
surrogate_grad_fn=PeriodicExponential(),
113117
)
114-
self.pool4 = SumPool2d(3, 3)
115-
116-
self.flat1 = nn.Flatten()
117-
self.flat2 = nn.Flatten()
118-
119-
self.conv5 = nn.Conv2d(1, 1, 2, 1, bias=False)
120-
self.iaf5 = IAFSqueeze(
118+
# -- graph node E --
119+
self.conv_E = nn.Conv2d(4, 4, 2, 1, bias=False)
120+
self.iaf3_E = IAFSqueeze(
121121
batch_size=batch_size,
122122
min_v_mem=-1.0,
123123
spike_threshold=1.0,
124124
surrogate_grad_fn=PeriodicExponential(),
125125
)
126-
127-
self.fc2 = nn.Linear(25, 10, bias=False)
128-
self.iaf2_fc = IAFSqueeze(
126+
self.pool_E = SumPool2d(2, 2)
127+
# -- graph node F --
128+
self.conv_F = nn.Conv2d(4, 4, 2, 1, bias=False)
129+
self.iaf_F = IAFSqueeze(
130+
batch_size=batch_size,
131+
min_v_mem=-1.0,
132+
spike_threshold=1.0,
133+
surrogate_grad_fn=PeriodicExponential(),
134+
)
135+
# -- graph node G --
136+
self.fc3 = nn.Linear(144, 10, bias=False)
137+
self.iaf3_fc = IAFSqueeze(
129138
batch_size=batch_size,
130139
min_v_mem=-1.0,
131140
spike_threshold=1.0,
@@ -134,43 +143,47 @@ class SNN(nn.Module):
134143

135144
# -- merges --
136145
self.merge1 = Merge()
137-
self.merge2 = Merge()
146+
147+
# -- falts --
148+
self.flat_D = nn.Flatten()
149+
self.flat_F = nn.Flatten()
138150

139151
def forward(self, x):
140152
# conv 1 - A/0
141-
con1_out = self.conv1(x)
142-
iaf1_out = self.iaf1(con1_out)
153+
convA_out = self.conv_A(x)
154+
iaf_A_out = self.iaf_A(convA_out)
143155

144156
# conv 2 - B/1
145-
conv2_out = self.conv2(iaf1_out)
146-
iaf2_out = self.iaf2(conv2_out)
147-
pool2_out = self.pool2(iaf2_out)
157+
conv_B_out = self.conv_B(iaf_A_out)
158+
iaf_B_out = self.iaf2_B(conv_B_out)
159+
pool_B_out = self.pool_B(iaf_B_out)
148160

149161
# conv 3 - C/2
150-
conv3_out = self.conv3(iaf1_out)
151-
iaf3_out = self.iaf3(conv3_out)
152-
pool3_out = self.pool3(iaf3_out)
153-
pool3a_out = self.pool3a(iaf3_out)
154-
155-
# conv 4 - D/3
156-
merge1_out = self.merge1(pool2_out, pool3_out)
157-
conv4_out = self.conv4(merge1_out)
158-
iaf4_out = self.iaf4(conv4_out)
159-
pool4_out = self.pool4(iaf4_out)
160-
flat1_out = self.flat1(pool4_out)
161-
162-
# conv 5 - E/4
163-
conv5_out = self.conv5(pool3a_out)
164-
iaf5_out = self.iaf5(conv5_out)
165-
flat2_out = self.flat2(iaf5_out)
166-
167-
# fc 2 - F/5
168-
merge2_out = self.merge2(flat2_out, flat1_out)
169-
170-
fc2_out = self.fc2(merge2_out)
171-
iaf2_fc_out = self.iaf2_fc(fc2_out)
172-
173-
return iaf2_fc_out
162+
conv_C_out = self.conv_C(pool_B_out)
163+
iaf_C_out = self.iaf_C(conv_C_out)
164+
pool_C_out = self.pool_C(iaf_C_out)
165+
166+
# conv 4 - D/4
167+
conv_D_out = self.conv_D(pool_C_out)
168+
iaf_D_out = self.iaf_D(conv_D_out)
169+
# fc 1 - E/3
170+
conv_E_out = self.conv_E(pool_B_out)
171+
iaf3_E_out = self.iaf3_E(conv_E_out)
172+
pool_E_out = self.pool_E(iaf3_E_out)
173+
174+
# fc 2 - F/6
175+
conv_F_out = self.conv_F(pool_E_out)
176+
iaf_F_out = self.iaf_F(conv_F_out)
177+
178+
# fc 2 - G/5
179+
flat_D_out = self.flat_D(iaf_D_out)
180+
flat_F_out = self.flat_F(iaf_F_out)
181+
182+
merge1_out = self.merge1(flat_D_out, flat_F_out)
183+
fc3_out = self.fc3(merge1_out)
184+
iaf3_fc_out = self.iaf3_fc(fc3_out)
185+
186+
return iaf3_fc_out
174187
```
175188

176189
## Can I achieve a "Residual Connection" like ResNet does?
@@ -180,9 +193,9 @@ change the `samna.speck2f.configuration.CNNLayerDestination.layer` to achieve th
180193
familiar with the `samna-configuration`.
181194
You can also make use of our network graph extraction feature, to implement residual networks.
182195

183-
## How to use "Residual Connection" manually?
196+
## How can I define "Residual Connection" manually?
184197

185-
Alright! Here I will give an example of achieving the "Residual Connection" by manually modify the `samna-configuration`.
198+
You can also achieve "Residual Connection" by manually modify the `samna-configuration`.
186199

187200
Let's say you want an architecture like below:
188201

@@ -221,7 +234,7 @@ class ResidualBlock(nn.Module):
221234

222235
```
223236

224-
Since currently Sinabs can only parse Sequential like network, we need to do some tedious work like below:
237+
You can write it like:
225238

226239
```python
227240
# define a Sequential first
@@ -264,8 +277,8 @@ devkit.get_model().apply_configuration(samna_cfg)
264277

265278
```
266279

267-
I have to say it is not an elegant solution though, it should help you to achieve an initial Residual Block. We will
268-
improve this part after Sinabs has the ability for extracting model's graph.
280+
It is a lot of manual work but it will let you have your Residual Block.
281+
We recommend to use our network graph extraction feature for residual connections.
269282

270283
## What execution order should I be aware of when I am implementing a sequential structure?
271284
You should be aware with the internal layer order.
10.7 KB
Loading
8.44 KB
Loading
-6.92 KB
Binary file not shown.

docs/speck/notebooks/nmnist_quick_start.ipynb

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,6 @@
9797
"source": [
9898
"from torch import nn\n",
9999
"\n",
100-
"\n",
101100
"# define a CNN model\n",
102101
"cnn = nn.Sequential(\n",
103102
" # [2, 34, 34] -> [8, 17, 17]\n",
@@ -1181,7 +1180,6 @@
11811180
"source": [
11821181
"from sinabs.backend.dynapcnn.dynapcnn_visualizer import DynapcnnVisualizer\n",
11831182
"\n",
1184-
"\n",
11851183
"visualizer = DynapcnnVisualizer(\n",
11861184
" window_scale=(4, 8),\n",
11871185
" dvs_shape=(34, 34),\n",

docs/speck/notebooks/play_with_speck_dvs.ipynb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,6 @@
103103
"from sinabs.backend.dynapcnn.dynapcnn_visualizer import DynapcnnVisualizer\n",
104104
"from sinabs.backend.dynapcnn import DynapcnnNetwork\n",
105105
"\n",
106-
"\n",
107106
"# create a dummy snn for DynapcnnNetwork initialization\n",
108107
"snn = nn.Sequential(\n",
109108
" nn.Conv2d(1, 1, kernel_size=(3, 3), stride=(2, 2), padding=(1, 1), bias=False),\n",

docs/tutorials/nmnist.ipynb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,6 @@
292292
"source": [
293293
"from tqdm.notebook import tqdm\n",
294294
"\n",
295-
"\n",
296295
"n_epochs = 1\n",
297296
"optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)\n",
298297
"crit = nn.functional.cross_entropy\n",

sinabs/backend/dynapcnn/chips/dynapcnn.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def get_dynapcnn_layer_config_dict(
101101
dimensions["input_shape"]["feature_count"] = channel_count
102102

103103
# dimensions["output_feature_count"] already done in conv2d_to_dict
104-
(f, h, w) = layer.get_neuron_shape()
104+
f, h, w = layer.get_neuron_shape()
105105
dimensions["output_shape"]["size"] = {}
106106
dimensions["output_shape"]["feature_count"] = f
107107
dimensions["output_shape"]["size"]["x"] = w
@@ -121,7 +121,7 @@ def get_dynapcnn_layer_config_dict(
121121
config_dict["dimensions"] = dimensions
122122
# Update parameters from convolution
123123
if layer.conv_layer.bias is not None:
124-
(weights, biases) = layer.conv_layer.parameters()
124+
weights, biases = layer.conv_layer.parameters()
125125
else:
126126
(weights,) = layer.conv_layer.parameters()
127127
biases = torch.zeros(layer.conv_layer.out_channels)

0 commit comments

Comments
 (0)