Skip to content

Commit 097ec37

Browse files
authored
Merge pull request #260 from NeuroBench/dev
Dev
2 parents bb23c6b + b1f429a commit 097ec37

File tree

16 files changed

+2154
-2698
lines changed

16 files changed

+2154
-2698
lines changed

.bumpversion.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
44
serialize = ["{major}.{minor}.{patch}"]
55
regex = false
6-
current_version = "2.0.0"
6+
current_version = "2.1.0"
77
ignore_missing_version = false
88
search = "{current_version}"
99
replace = "{new_version}"

.github/workflows/python-tests.yaml

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,30 +44,38 @@ jobs:
4444
4545
test-code:
4646
runs-on: ubuntu-latest
47+
strategy:
48+
matrix:
49+
python-version: ['3.10', '3.11', '3.12']
4750

4851
steps:
4952
- uses: actions/checkout@v4
5053

5154
- name: Set up Python
5255
uses: actions/setup-python@v5
5356
with:
54-
python-version: '3.11'
57+
python-version: ${{ matrix.python-version }}
5558

5659
- name: Install Poetry
5760
run: |
5861
python -m pip install --upgrade pip
62+
pip install --upgrade setuptools
5963
pip install poetry
6064
6165
- name: Install dependencies
6266
run: |
63-
python -m pip install --upgrade pip
6467
poetry install --with dev
6568
6669
- name: Run tests
6770
run: |
68-
poetry run pytest --cov --cov-report=xml
71+
if [ "${{ matrix.python-version }}" = "3.11" ]; then
72+
poetry run pytest --cov --cov-report=xml
73+
else
74+
poetry run pytest
75+
fi
6976
7077
- name: Upload coverage to Codecov
78+
if: matrix.python-version == '3.11'
7179
uses: codecov/codecov-action@v4
7280
with:
7381
directory: ./coverage/reports/

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
project = "NeuroBench"
1515
copyright = "2024, Jason Yik, Noah Pacik-Nelson, Korneel Van Den Berghe, Benedetto Leto"
1616
author = "Jason Yik, Noah Pacik-Nelson, Korneel Van Den Berghe"
17-
release = "2.0.0"
17+
release = "2.1.0"
1818

1919
# -- General configuration ---------------------------------------------------
2020
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration

examples/nehar/benchmark.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
ActivationSparsity,
1010
MembraneUpdates,
1111
SynapticOperations,
12-
ClassificationAccuracy
12+
ClassificationAccuracy,
13+
ActivationSparsityByLayer,
1314
)
1415
from neurobench.metrics.static import (
1516
ParameterCount,
@@ -47,7 +48,7 @@
4748

4849
# #
4950
static_metrics = [ParameterCount, Footprint, ConnectionSparsity]
50-
workload_metrics = [ActivationSparsity, MembraneUpdates, SynapticOperations, ClassificationAccuracy]
51+
workload_metrics = [ActivationSparsity, ActivationSparsityByLayer,MembraneUpdates, SynapticOperations, ClassificationAccuracy]
5152
# #
5253
benchmark = Benchmark(
5354
model, test_set_loader, [], postprocessors, [static_metrics, workload_metrics]

neurobench/benchmarks/benchmark.py

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,11 @@
1616
import json
1717
import csv
1818
import os
19-
from typing import Literal, List, Type, Optional, Dict, Any
19+
from typing import Literal, List, Type, Optional, Dict, Any, Callable, Tuple
2020
import pathlib
2121
import snntorch
2222
from torch import Tensor
23-
24-
if snntorch.__version__ >= "0.9.0":
25-
from snntorch import export_to_nir
26-
2723
import torch
28-
import nir
2924

3025

3126
class Benchmark:
@@ -39,18 +34,25 @@ def __init__(
3934
self,
4035
model: NeuroBenchModel,
4136
dataloader: Optional[DataLoader],
42-
preprocessors: Optional[List[NeuroBenchPreProcessor]],
43-
postprocessors: Optional[List[NeuroBenchPostProcessor]],
37+
preprocessors: Optional[
38+
List[
39+
NeuroBenchPreProcessor
40+
| Callable[[Tuple[Tensor, Tensor]], Tuple[Tensor, Tensor]]
41+
]
42+
],
43+
postprocessors: Optional[
44+
List[NeuroBenchPostProcessor | Callable[[Tensor], Tensor]]
45+
],
4446
metric_list: List[List[Type[StaticMetric | WorkloadMetric]]],
4547
):
4648
"""
4749
Args:
4850
model: A NeuroBenchModel.
4951
dataloader: A PyTorch DataLoader.
50-
preprocessors: A list of NeuroBenchPreProcessors.
51-
postprocessors: A list of NeuroBenchPostProcessors.
52-
metric_list: A list of lists of strings of metrics to run.
53-
First item is static metrics, second item is data metrics.
52+
preprocessors: A list of NeuroBenchPreProcessors or callable functions (e.g. lambda) with matching interfaces.
53+
postprocessors: A list of NeuroBenchPostProcessors or callable functions (e.g. lambda) with matching interfaces.
54+
metric_list: A list of lists of StaticMetric and WorkloadMetric classes of metrics to run.
55+
First item is StaticMetrics, second item is WorkloadMetrics.
5456
"""
5557

5658
self.model = model
@@ -66,8 +68,13 @@ def run(
6668
quiet: bool = False,
6769
verbose: bool = False,
6870
dataloader: Optional[DataLoader] = None,
69-
preprocessors: Optional[NeuroBenchPreProcessor] = None,
70-
postprocessors: Optional[NeuroBenchPostProcessor] = None,
71+
preprocessors: Optional[
72+
NeuroBenchPreProcessor
73+
| Callable[[Tuple[Tensor, Tensor]], Tuple[Tensor, Tensor]]
74+
] = None,
75+
postprocessors: Optional[
76+
NeuroBenchPostProcessor | Callable[[Tensor], Tensor]
77+
] = None,
7178
device: Optional[str] = None,
7279
) -> Dict[str, Any]:
7380
"""
@@ -117,10 +124,10 @@ def run(
117124
batch_size = data[0].size(0)
118125

119126
# Preprocessing data
120-
data = self.processor_manager.preprocess(data)
127+
input, target = self.processor_manager.preprocess(data)
121128

122129
# Run model on test data
123-
preds = self.model(data[0])
130+
preds = self.model(input)
124131

125132
# Postprocessing data
126133
preds = self.processor_manager.postprocess(preds)
@@ -220,8 +227,18 @@ def to_nir(self, dummy_input: Tensor, filename: str, **kwargs) -> None:
220227
If the installed version of `snntorch` is less than `0.9.0`.
221228
222229
"""
230+
try:
231+
import nir
232+
except ImportError:
233+
raise ImportError(
234+
"Exporting to NIR requires the `nir` package. Install it using `pip install nir`."
235+
)
223236
if snntorch.__version__ < "0.9.0":
224237
raise ValueError("Exporting to NIR requires snntorch version >= 0.9.0")
238+
239+
if snntorch.__version__ >= "0.9.0":
240+
from snntorch.export_nir import export_to_nir
241+
225242
nir_graph = export_to_nir(self.model.__net__(), dummy_input, **kwargs)
226243
nir.write(filename, nir_graph)
227244
print(f"Model exported to {filename}")

neurobench/hooks/neuron.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class NeuronHook(ABC):
1010
1111
"""
1212

13-
def __init__(self, layer):
13+
def __init__(self, layer, name=None):
1414
"""
1515
Initializes the class.
1616
@@ -24,6 +24,7 @@ def __init__(self, layer):
2424
self.activation_inputs = []
2525
self.pre_fire_mem_potential = []
2626
self.post_fire_mem_potential = []
27+
self.name = name
2728
if layer is not None:
2829
self.hook = layer.register_forward_hook(self.hook_fn)
2930
self.hook_pre = layer.register_forward_pre_hook(self.pre_hook_fn)
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
from .static_metric import StaticMetric
2-
from .workload_metric import WorkloadMetric
2+
from .workload_metric import WorkloadMetric, AccumulatedMetric
33

4-
__all__ = ["StaticMetric", "WorkloadMetric"]
4+
__all__ = ["StaticMetric", "WorkloadMetric", "AccumulatedMetric"]

neurobench/metrics/workload/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,19 @@
22
from .activation_sparsity import ActivationSparsity
33
from .synaptic_operations import SynapticOperations
44
from .classification_accuracy import ClassificationAccuracy
5+
from .activation_sparsity_by_layer import ActivationSparsityByLayer
56
from .mse import MSE
67
from .smape import SMAPE
78
from .r2 import R2
89
from .coco_map import CocoMap
910

10-
__stateless__ = ["ClassificationAccuracy", "ActivationSparsity", "MSE", "SMAPE"]
11+
__stateless__ = [
12+
"ClassificationAccuracy",
13+
"ActivationSparsity",
14+
"MSE",
15+
"SMAPE",
16+
"ActivationSparsityByLayer",
17+
]
1118

1219
__stateful__ = ["MembraneUpdates", "SynapticOperations", "R2", "CocoMap"]
1320

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
from neurobench.metrics.abstract.workload_metric import AccumulatedMetric
2+
from collections import defaultdict
3+
import torch
4+
5+
6+
class ActivationSparsityByLayer(AccumulatedMetric):
7+
"""
8+
Sparsity layer-wise of model activations.
9+
10+
Calculated as the number of zero activations over the number of activations layer by
11+
layer, over all timesteps, samples in data.
12+
13+
"""
14+
15+
def __init__(self):
16+
"""Initialize the ActivationSparsityByLayer metric."""
17+
self.layer_sparsity = defaultdict(float)
18+
self.layer_neuro_num = defaultdict(int)
19+
self.layer_spike_num = defaultdict(int)
20+
21+
def reset(self):
22+
"""Reset the metric."""
23+
self.layer_sparsity = defaultdict(float)
24+
self.layer_neuro_num = defaultdict(int)
25+
self.layer_spike_num = defaultdict(int)
26+
27+
def __call__(self, model, preds, data):
28+
"""
29+
Compute activation sparsity layer by layer.
30+
31+
Args:
32+
model: A NeuroBenchModel.
33+
preds: A tensor of model predictions.
34+
data: A tuple of data and labels.
35+
Returns:
36+
float: Activation sparsity
37+
38+
"""
39+
40+
for hook in model.activation_hooks:
41+
name = hook.name
42+
if name is None:
43+
continue
44+
for output in hook.activation_outputs:
45+
spike_num, neuro_num = torch.count_nonzero(
46+
output.dequantize() if output.is_quantized else output
47+
).item(), torch.numel(output)
48+
49+
self.layer_spike_num[name] += spike_num
50+
self.layer_neuro_num[name] += neuro_num
51+
52+
return self.compute()
53+
54+
def compute(self):
55+
"""Compute the activation sparsity layer by layer."""
56+
for key in self.layer_neuro_num:
57+
sparsity = (
58+
(self.layer_neuro_num[key] - self.layer_spike_num[key])
59+
/ self.layer_neuro_num[key]
60+
if self.layer_neuro_num[key] != 0
61+
else 0.0
62+
)
63+
self.layer_sparsity[key] = sparsity
64+
return dict(self.layer_sparsity)

neurobench/models/neurobench_model.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,9 @@ def is_activation_layer(module):
7979
def find_activation_layers(module):
8080
"""Recursively find activation layers in a module."""
8181
layers = []
82-
for child in module.children():
82+
for child_name, child in module.named_children():
8383
if is_activation_layer(child):
84-
layers.append(child)
84+
layers.append({"layer_name": child_name, "layer": child})
8585
elif list(child.children()): # Check for nested submodules
8686
layers.extend(find_activation_layers(child))
8787
return layers
@@ -135,7 +135,9 @@ def register_hooks(self):
135135

136136
# Registered activation hooks
137137
for layer in self.activation_layers():
138-
self.activation_hooks.append(NeuronHook(layer))
138+
layer_name = layer["layer_name"]
139+
layer = layer["layer"]
140+
self.activation_hooks.append(NeuronHook(layer, layer_name))
139141

140142
for layer in self.connection_layers():
141143
self.connection_hooks.append(LayerHook(layer))

0 commit comments

Comments
 (0)