sinabs 3.0.4.dev25__py3-none-any.whl → 3.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sinabs/activation/reset_mechanism.py +3 -3
- sinabs/activation/surrogate_gradient_fn.py +4 -4
- sinabs/backend/dynapcnn/__init__.py +5 -4
- sinabs/backend/dynapcnn/chip_factory.py +33 -61
- sinabs/backend/dynapcnn/chips/dynapcnn.py +182 -86
- sinabs/backend/dynapcnn/chips/speck2e.py +6 -5
- sinabs/backend/dynapcnn/chips/speck2f.py +6 -5
- sinabs/backend/dynapcnn/config_builder.py +39 -59
- sinabs/backend/dynapcnn/connectivity_specs.py +48 -0
- sinabs/backend/dynapcnn/discretize.py +91 -155
- sinabs/backend/dynapcnn/dvs_layer.py +59 -101
- sinabs/backend/dynapcnn/dynapcnn_layer.py +185 -119
- sinabs/backend/dynapcnn/dynapcnn_layer_utils.py +335 -0
- sinabs/backend/dynapcnn/dynapcnn_network.py +602 -325
- sinabs/backend/dynapcnn/dynapcnnnetwork_module.py +370 -0
- sinabs/backend/dynapcnn/exceptions.py +122 -3
- sinabs/backend/dynapcnn/io.py +55 -92
- sinabs/backend/dynapcnn/mapping.py +111 -75
- sinabs/backend/dynapcnn/nir_graph_extractor.py +877 -0
- sinabs/backend/dynapcnn/sinabs_edges_handler.py +1024 -0
- sinabs/backend/dynapcnn/utils.py +214 -459
- sinabs/backend/dynapcnn/weight_rescaling_methods.py +53 -0
- sinabs/conversion.py +2 -2
- sinabs/from_torch.py +23 -1
- sinabs/hooks.py +38 -41
- sinabs/layers/alif.py +16 -16
- sinabs/layers/crop2d.py +2 -2
- sinabs/layers/exp_leak.py +1 -1
- sinabs/layers/iaf.py +11 -11
- sinabs/layers/lif.py +9 -9
- sinabs/layers/neuromorphic_relu.py +9 -8
- sinabs/layers/pool2d.py +5 -5
- sinabs/layers/quantize.py +1 -1
- sinabs/layers/stateful_layer.py +10 -7
- sinabs/layers/to_spike.py +9 -9
- sinabs/network.py +14 -12
- sinabs/nir.py +4 -3
- sinabs/synopcounter.py +10 -7
- sinabs/utils.py +155 -7
- sinabs/validate_memory_speck.py +0 -5
- {sinabs-3.0.4.dev25.dist-info → sinabs-3.1.1.dist-info}/METADATA +3 -2
- sinabs-3.1.1.dist-info/RECORD +65 -0
- {sinabs-3.0.4.dev25.dist-info → sinabs-3.1.1.dist-info}/licenses/AUTHORS +1 -0
- sinabs-3.1.1.dist-info/pbr.json +1 -0
- sinabs-3.0.4.dev25.dist-info/RECORD +0 -59
- sinabs-3.0.4.dev25.dist-info/pbr.json +0 -1
- {sinabs-3.0.4.dev25.dist-info → sinabs-3.1.1.dist-info}/WHEEL +0 -0
- {sinabs-3.0.4.dev25.dist-info → sinabs-3.1.1.dist-info}/licenses/LICENSE +0 -0
- {sinabs-3.0.4.dev25.dist-info → sinabs-3.1.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
from typing import Callable, Dict, Iterable, List, Optional, Set, Tuple, Union
|
|
2
|
+
|
|
3
|
+
from torch import nn
|
|
4
|
+
|
|
5
|
+
from sinabs import layers as sl
|
|
6
|
+
from sinabs.utils import expand_to_pair
|
|
7
|
+
|
|
8
|
+
from .dynapcnn_layer import DynapcnnLayer
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def construct_dynapcnnlayers_from_mapper(
|
|
12
|
+
dcnnl_map: Dict,
|
|
13
|
+
dvs_layer_info: Union[None, Dict],
|
|
14
|
+
discretize: bool,
|
|
15
|
+
rescale_fn: Optional[Callable] = None,
|
|
16
|
+
) -> Tuple[Dict[int, DynapcnnLayer], Dict[int, Set[int]], List[int]]:
|
|
17
|
+
"""Construct DynapcnnLayer instances from `dcnnl_map`
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
dcnnl_map: dict holding info needed to instantiate `DynapcnnLayer` instances.
|
|
21
|
+
dvs_layer_info: dict holding info about `DVSLayer` instance and its destinations.
|
|
22
|
+
discretize: bool indicating whether layer parameters should be
|
|
23
|
+
discretized (weights, biases, thresholds).
|
|
24
|
+
rescale_fn: Optional callable that is used to determine layer rescaling
|
|
25
|
+
in case of conflicting preceeding average pooling.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
A tuple containing a dict of new `DynapcnnLayer` instances, with keys
|
|
29
|
+
corresponding to `dcnnl_map`, a dict mapping each layer index to a set
|
|
30
|
+
of destination indices and a list of layer indices that act as entry
|
|
31
|
+
points to the network.
|
|
32
|
+
"""
|
|
33
|
+
finalize_dcnnl_map(dcnnl_map, dvs_layer_info, rescale_fn)
|
|
34
|
+
|
|
35
|
+
dynapcnn_layers = {
|
|
36
|
+
layer_idx: construct_single_dynapcnn_layer(layer_info, discretize)
|
|
37
|
+
for layer_idx, layer_info in dcnnl_map.items()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
destination_map = construct_destination_map(dcnnl_map, dvs_layer_info)
|
|
41
|
+
|
|
42
|
+
entry_points = collect_entry_points(dcnnl_map, dvs_layer_info)
|
|
43
|
+
|
|
44
|
+
return dynapcnn_layers, destination_map, entry_points
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def finalize_dcnnl_map(
|
|
48
|
+
dcnnl_map: Dict, dvs_info: Union[Dict, None], rescale_fn: Optional[Callable] = None
|
|
49
|
+
) -> None:
|
|
50
|
+
"""Finalize DynapCNNLayer map by consolidating information
|
|
51
|
+
|
|
52
|
+
Update `dcnnl_map` in-place
|
|
53
|
+
- Consolidate chained pooling layers
|
|
54
|
+
- Determine rescaling of layer weights
|
|
55
|
+
- Fix input shapes
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
dcnnl_map: Dict holding info needed to instantiate `DynapcnnLayer` instances.
|
|
59
|
+
rescale_fn: Optional callable that is used to determine layer rescaling
|
|
60
|
+
in case of conflicting preceeding average pooling.
|
|
61
|
+
"""
|
|
62
|
+
# Consolidate pooling information for DVS layer
|
|
63
|
+
consolidate_dvs_pooling(dvs_info, dcnnl_map)
|
|
64
|
+
|
|
65
|
+
# Consolidate pooling information for each destination
|
|
66
|
+
for layer_info in dcnnl_map.values():
|
|
67
|
+
consolidate_layer_pooling(layer_info, dcnnl_map)
|
|
68
|
+
|
|
69
|
+
for layer_info in dcnnl_map.values():
|
|
70
|
+
# Consolidate scale factors
|
|
71
|
+
consolidate_layer_scaling(layer_info, rescale_fn)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def consolidate_dvs_pooling(dvs_info: Union[Dict, None], dcnnl_map: Dict):
|
|
75
|
+
"""Consolidate pooling information for DVS layer
|
|
76
|
+
|
|
77
|
+
Update `dvs_info` and `dcnnl_map` in place.
|
|
78
|
+
- Extract pooling and scale factor of consecutive pooling operations
|
|
79
|
+
- Add entries "cumulative_pooling" and "cumulative_scaling"
|
|
80
|
+
- Update DVSLayer pooling if applicable
|
|
81
|
+
- For each destination, add cumulative rescale factor to "rescale_factors"
|
|
82
|
+
entry in corresponding entry of `dcnnl_map`.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
dvs_info: Dict holding info of DVS layer.
|
|
86
|
+
dcnnl_map: Dict holding info needed to instantiate `DynapcnnLayer` instances.
|
|
87
|
+
"""
|
|
88
|
+
if dvs_info is None or dvs_info["pooling"] is None:
|
|
89
|
+
# Nothing to do
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
# Check whether pooling can be incorporated into the DVSLayer.
|
|
93
|
+
dvs_layer = dvs_info["module"]
|
|
94
|
+
crop_layer = dvs_layer.crop_layer
|
|
95
|
+
if (
|
|
96
|
+
crop_layer.top_crop != 0
|
|
97
|
+
or crop_layer.left_crop != 0
|
|
98
|
+
or crop_layer.bottom_crop != dvs_layer.input_shape[1]
|
|
99
|
+
or crop_layer.right_crop != dvs_layer.input_shape[2]
|
|
100
|
+
):
|
|
101
|
+
raise ValueError(
|
|
102
|
+
"DVSLayer with cropping is followed by a pooling layer. "
|
|
103
|
+
"This is currently not supported. Please define pooling "
|
|
104
|
+
"directly within the DVSLayer (with the `pool` argument) "
|
|
105
|
+
"and remove the pooling layer that follows the DVSLayer"
|
|
106
|
+
)
|
|
107
|
+
flip_layer = dvs_layer.flip_layer
|
|
108
|
+
if flip_layer.flip_x or flip_layer.flip_y or flip_layer.swap_xy:
|
|
109
|
+
raise ValueError(
|
|
110
|
+
"DVSLayer with flipping or dimension swapping is followed "
|
|
111
|
+
"by a pooling layer. This is currently not supported. "
|
|
112
|
+
"Please define pooling directly within the DVSLayer "
|
|
113
|
+
"(with the `pool` argument) and remove the pooling "
|
|
114
|
+
"layer that follows the DVSLayer"
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# Incorporate pooling into DVSLayer
|
|
118
|
+
pool_layer = dvs_info["pooling"]["module"]
|
|
119
|
+
added_pooling, scale = extract_pooling_from_module(pool_layer)
|
|
120
|
+
dvs_pooling = expand_to_pair(dvs_layer.pool_layer.kernel_size)
|
|
121
|
+
cumulative_pooling = (
|
|
122
|
+
dvs_pooling[0] * added_pooling[0],
|
|
123
|
+
dvs_pooling[1] * added_pooling[1],
|
|
124
|
+
)
|
|
125
|
+
dvs_layer.pool_layer.kernel_size = cumulative_pooling
|
|
126
|
+
dvs_layer.pool_layer.stride = None
|
|
127
|
+
|
|
128
|
+
# Update cropping layer to account for reduced size after pooling
|
|
129
|
+
dvs_layer.crop_layer.bottom_crop //= added_pooling[0]
|
|
130
|
+
dvs_layer.crop_layer.right_crop //= added_pooling[1]
|
|
131
|
+
|
|
132
|
+
# Set rescale_factor for targeted dynapcnn layers
|
|
133
|
+
if dvs_info["destinations"] is not None:
|
|
134
|
+
for dest_lyr_idx in dvs_info["destinations"]:
|
|
135
|
+
dcnnl_map[dest_lyr_idx]["rescale_factors"].add(scale)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def consolidate_layer_pooling(layer_info: Dict, dcnnl_map: Dict):
|
|
139
|
+
"""Consolidate pooling information for individual layer
|
|
140
|
+
|
|
141
|
+
Update `layer_info` and `dcnnl_map` in place.
|
|
142
|
+
- Extract pooling and scale factor of consecutive pooling operations
|
|
143
|
+
- To each "destination" add entries "cumulative_pooling" and
|
|
144
|
+
"cumulative_scaling"
|
|
145
|
+
- Add "pooling_list" to `layer_info` with all poolings of a layer
|
|
146
|
+
in order of its "destination"s.
|
|
147
|
+
- For each destination, add cumulative rescale factor to "rescale_factors"
|
|
148
|
+
entry in corresponding entry of `dcnnl_map`.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
layer_info: Dict holding info of single layer. Corresponds to single
|
|
152
|
+
entry in `dcnnl_map`.
|
|
153
|
+
dcnnl_map: Dict holding info needed to instantiate `DynapcnnLayer` instances.
|
|
154
|
+
"""
|
|
155
|
+
layer_info["pooling_list"] = []
|
|
156
|
+
for destination in layer_info["destinations"]:
|
|
157
|
+
pool, scale = consolidate_dest_pooling(destination["pooling_modules"])
|
|
158
|
+
destination["cumulative_pooling"] = pool
|
|
159
|
+
layer_info["pooling_list"].append(pool)
|
|
160
|
+
destination["cumulative_scaling"] = scale
|
|
161
|
+
if (dest_lyr_idx := destination["destination_layer"]) is not None:
|
|
162
|
+
dcnnl_map[dest_lyr_idx]["rescale_factors"].add(scale)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def consolidate_dest_pooling(
|
|
166
|
+
modules: Iterable[nn.Module],
|
|
167
|
+
) -> Tuple[Tuple[int, int], float]:
|
|
168
|
+
"""Consolidate pooling information for consecutive pooling modules
|
|
169
|
+
for single destination.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
modules: Iterable of pooling modules.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
A tuple containing the cumulative_pooling information, a tuple of two ints,
|
|
176
|
+
indicating pooling along vertical and horizontal dimensions for all modules
|
|
177
|
+
together and the cumulative_scaling, a float, indicating by how much subsequent
|
|
178
|
+
weights need to be rescaled to account for average pooling being converted
|
|
179
|
+
to sum pooling, considering all provided modules.
|
|
180
|
+
"""
|
|
181
|
+
cumulative_pooling = [1, 1]
|
|
182
|
+
cumulative_scaling = 1.0
|
|
183
|
+
|
|
184
|
+
for pooling_layer in modules:
|
|
185
|
+
pooling, rescale_factor = extract_pooling_from_module(pooling_layer)
|
|
186
|
+
cumulative_pooling[0] *= pooling[0]
|
|
187
|
+
cumulative_pooling[1] *= pooling[1]
|
|
188
|
+
cumulative_scaling *= rescale_factor
|
|
189
|
+
|
|
190
|
+
return cumulative_pooling, cumulative_scaling
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def extract_pooling_from_module(
|
|
194
|
+
pooling_layer: Union[nn.AvgPool2d, sl.SumPool2d],
|
|
195
|
+
) -> Tuple[Tuple[int, int], float]:
|
|
196
|
+
"""Extract pooling size and required rescaling factor from pooling module
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
pooling_layer: pooling module.
|
|
200
|
+
|
|
201
|
+
Returns:
|
|
202
|
+
A tuple containing pooling, a tuple of two ints, indicating pooling along
|
|
203
|
+
vertical and horizontal dimensions and the scale_factor, a float, indicating
|
|
204
|
+
by how much subsequent weights need to be rescaled to account for average
|
|
205
|
+
pooling being converted to sum pooling.
|
|
206
|
+
"""
|
|
207
|
+
pooling = expand_to_pair(pooling_layer.kernel_size)
|
|
208
|
+
|
|
209
|
+
if pooling_layer.stride is not None:
|
|
210
|
+
stride = expand_to_pair(pooling_layer.stride)
|
|
211
|
+
if pooling != stride:
|
|
212
|
+
raise ValueError(
|
|
213
|
+
f"Stride length {pooling_layer.stride} should be the same as pooling kernel size {pooling_layer.kernel_size}"
|
|
214
|
+
)
|
|
215
|
+
if isinstance(pooling_layer, nn.AvgPool2d):
|
|
216
|
+
scale_factor = 1.0 / (pooling[0] * pooling[1])
|
|
217
|
+
elif isinstance(pooling_layer, sl.SumPool2d):
|
|
218
|
+
scale_factor = 1.0
|
|
219
|
+
else:
|
|
220
|
+
raise ValueError(f"Unsupported type {type(pooling_layer)} for pooling layer")
|
|
221
|
+
|
|
222
|
+
return pooling, scale_factor
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def consolidate_layer_scaling(layer_info: Dict, rescale_fn: Optional[Callable] = None):
|
|
226
|
+
"""Determine scale factor of single layer
|
|
227
|
+
|
|
228
|
+
Add "rescale_factor" entry to `layer_info`. If more than one
|
|
229
|
+
different rescale factors have been determined due to conflicting
|
|
230
|
+
average pooling in preceding layers, requires `rescale_fn` to
|
|
231
|
+
resolve.
|
|
232
|
+
|
|
233
|
+
Args:
|
|
234
|
+
layer_info: Dict holding info of single layer.
|
|
235
|
+
rescale_fn: Optional callable that is used to determine layer rescaling
|
|
236
|
+
in case of conflicting preceeding average pooling.
|
|
237
|
+
"""
|
|
238
|
+
if len(layer_info["rescale_factors"]) == 0:
|
|
239
|
+
rescale_factor = 1
|
|
240
|
+
elif len(layer_info["rescale_factors"]) == 1:
|
|
241
|
+
rescale_factor = layer_info["rescale_factors"].pop()
|
|
242
|
+
else:
|
|
243
|
+
if rescale_fn is None:
|
|
244
|
+
raise ValueError(
|
|
245
|
+
"Average pooling layers of conflicting sizes pointing to "
|
|
246
|
+
"same destination. Either replace them by SumPool2d layers "
|
|
247
|
+
"or provide a `rescale_fn` to resolve this"
|
|
248
|
+
)
|
|
249
|
+
else:
|
|
250
|
+
rescale_factor = rescale_fn(layer_info["rescale_factors"])
|
|
251
|
+
layer_info["rescale_factor"] = rescale_factor
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def construct_single_dynapcnn_layer(
|
|
255
|
+
layer_info: Dict, discretize: bool
|
|
256
|
+
) -> DynapcnnLayer:
|
|
257
|
+
"""Instantiate a DynapcnnLayer instance from the information
|
|
258
|
+
in `layer_info'
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
layer_info: Dict holding info of single layer.
|
|
262
|
+
discretize: bool indicating whether layer parameters should be
|
|
263
|
+
discretized (weights, biases, thresholds).
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
"""
|
|
267
|
+
return DynapcnnLayer(
|
|
268
|
+
conv=layer_info["conv"]["module"],
|
|
269
|
+
spk=layer_info["neuron"]["module"],
|
|
270
|
+
in_shape=layer_info["input_shape"],
|
|
271
|
+
pool=layer_info["pooling_list"],
|
|
272
|
+
discretize=discretize,
|
|
273
|
+
rescale_weights=layer_info["rescale_factor"],
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def construct_destination_map(
|
|
278
|
+
dcnnl_map: Dict[int, Dict], dvs_layer_info: Union[None, Dict]
|
|
279
|
+
) -> Dict[int, List[int]]:
|
|
280
|
+
"""Create a dict that holds destinations for each layer
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
dcnnl_map: Dict holding info needed to instantiate `DynapcnnLayer` instances.
|
|
284
|
+
dvs_layer_info: Dict holding info about `DVSLayer` instance and its destinations.
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
Dict with layer indices (int) as keys and list of destination indices (int) as values.
|
|
288
|
+
Layer outputs that are not sent to other dynapcnn layers are considered
|
|
289
|
+
exit points of the network and represented by negative indices.
|
|
290
|
+
"""
|
|
291
|
+
destination_map = dict()
|
|
292
|
+
for layer_index, layer_info in dcnnl_map.items():
|
|
293
|
+
destination_indices = []
|
|
294
|
+
none_counter = 0
|
|
295
|
+
for dest in layer_info["destinations"]:
|
|
296
|
+
if (dest_idx := dest["destination_layer"]) is None:
|
|
297
|
+
# For `None` destinations use unique negative index
|
|
298
|
+
none_counter += 1
|
|
299
|
+
destination_indices.append(-none_counter)
|
|
300
|
+
else:
|
|
301
|
+
destination_indices.append(dest_idx)
|
|
302
|
+
destination_map[layer_index] = destination_indices
|
|
303
|
+
if dvs_layer_info is not None:
|
|
304
|
+
if (dest_info := dvs_layer_info["destinations"]) is None:
|
|
305
|
+
destination_map["dvs"] = [-1]
|
|
306
|
+
else:
|
|
307
|
+
# Copy destination list from dvs layer info
|
|
308
|
+
destination_map["dvs"] = [d for d in dest_info]
|
|
309
|
+
|
|
310
|
+
return destination_map
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def collect_entry_points(
|
|
314
|
+
dcnnl_map: Dict[int, Dict], dvs_layer_info: Union[None, Dict]
|
|
315
|
+
) -> Set[int]:
|
|
316
|
+
"""Return set of layer indices that are entry points
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
dcnnl_map: dict holding info needed to instantiate `DynapcnnLayer` instances.
|
|
320
|
+
dvs_layer_info: dict holding info about `DVSLayer` instance and its destinations.
|
|
321
|
+
If it is not None, it will be the only entry point returned.
|
|
322
|
+
|
|
323
|
+
Returns:
|
|
324
|
+
Set of all layer indices which act as entry points to the network. When `dvs_layer_info`
|
|
325
|
+
is provided, the DVS layer is the exclusive entry point into the network and a dictionary
|
|
326
|
+
`{"dvs"}` is returned instead.
|
|
327
|
+
"""
|
|
328
|
+
if dvs_layer_info is None:
|
|
329
|
+
return {
|
|
330
|
+
layer_index
|
|
331
|
+
for layer_index, layer_info in dcnnl_map.items()
|
|
332
|
+
if layer_info["is_entry_node"]
|
|
333
|
+
}
|
|
334
|
+
else:
|
|
335
|
+
return {"dvs"}
|