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.
Files changed (49) hide show
  1. sinabs/activation/reset_mechanism.py +3 -3
  2. sinabs/activation/surrogate_gradient_fn.py +4 -4
  3. sinabs/backend/dynapcnn/__init__.py +5 -4
  4. sinabs/backend/dynapcnn/chip_factory.py +33 -61
  5. sinabs/backend/dynapcnn/chips/dynapcnn.py +182 -86
  6. sinabs/backend/dynapcnn/chips/speck2e.py +6 -5
  7. sinabs/backend/dynapcnn/chips/speck2f.py +6 -5
  8. sinabs/backend/dynapcnn/config_builder.py +39 -59
  9. sinabs/backend/dynapcnn/connectivity_specs.py +48 -0
  10. sinabs/backend/dynapcnn/discretize.py +91 -155
  11. sinabs/backend/dynapcnn/dvs_layer.py +59 -101
  12. sinabs/backend/dynapcnn/dynapcnn_layer.py +185 -119
  13. sinabs/backend/dynapcnn/dynapcnn_layer_utils.py +335 -0
  14. sinabs/backend/dynapcnn/dynapcnn_network.py +602 -325
  15. sinabs/backend/dynapcnn/dynapcnnnetwork_module.py +370 -0
  16. sinabs/backend/dynapcnn/exceptions.py +122 -3
  17. sinabs/backend/dynapcnn/io.py +55 -92
  18. sinabs/backend/dynapcnn/mapping.py +111 -75
  19. sinabs/backend/dynapcnn/nir_graph_extractor.py +877 -0
  20. sinabs/backend/dynapcnn/sinabs_edges_handler.py +1024 -0
  21. sinabs/backend/dynapcnn/utils.py +214 -459
  22. sinabs/backend/dynapcnn/weight_rescaling_methods.py +53 -0
  23. sinabs/conversion.py +2 -2
  24. sinabs/from_torch.py +23 -1
  25. sinabs/hooks.py +38 -41
  26. sinabs/layers/alif.py +16 -16
  27. sinabs/layers/crop2d.py +2 -2
  28. sinabs/layers/exp_leak.py +1 -1
  29. sinabs/layers/iaf.py +11 -11
  30. sinabs/layers/lif.py +9 -9
  31. sinabs/layers/neuromorphic_relu.py +9 -8
  32. sinabs/layers/pool2d.py +5 -5
  33. sinabs/layers/quantize.py +1 -1
  34. sinabs/layers/stateful_layer.py +10 -7
  35. sinabs/layers/to_spike.py +9 -9
  36. sinabs/network.py +14 -12
  37. sinabs/nir.py +4 -3
  38. sinabs/synopcounter.py +10 -7
  39. sinabs/utils.py +155 -7
  40. sinabs/validate_memory_speck.py +0 -5
  41. {sinabs-3.0.4.dev25.dist-info → sinabs-3.1.1.dist-info}/METADATA +3 -2
  42. sinabs-3.1.1.dist-info/RECORD +65 -0
  43. {sinabs-3.0.4.dev25.dist-info → sinabs-3.1.1.dist-info}/licenses/AUTHORS +1 -0
  44. sinabs-3.1.1.dist-info/pbr.json +1 -0
  45. sinabs-3.0.4.dev25.dist-info/RECORD +0 -59
  46. sinabs-3.0.4.dev25.dist-info/pbr.json +0 -1
  47. {sinabs-3.0.4.dev25.dist-info → sinabs-3.1.1.dist-info}/WHEEL +0 -0
  48. {sinabs-3.0.4.dev25.dist-info → sinabs-3.1.1.dist-info}/licenses/LICENSE +0 -0
  49. {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"}