Skip to content

Commit 249a4b6

Browse files
author
Henry Barnes
committed
Add N3 benchmark results: SHD 91.0% (#1), SSC 76.4% (#1), N-MNIST 99.2%
N3 benchmarks establish new state of the art on SHD and SSC: - SHD: 91.0% (beat SpikFormer 90.4%, Loihi 2 90.9%) - SSC: 76.4% (beat Bittar 74.2%, Loihi 2 69.8%) - N-MNIST: 99.2% (within 0.47% of PLIF 99.67%) - GSC-35: training in progress Adds 15 new benchmark implementations, N3-specific neuron models, eval scripts, HuggingFace leaderboard, and marketing materials.
1 parent 6ee7257 commit 249a4b6

77 files changed

Lines changed: 7573 additions & 102 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,21 @@
44
[![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/downloads/)
55
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](https://opensource.org/licenses/MIT)
66

7-
Official benchmark suite for [Catalyst neuromorphic processors](https://catalyst-neuromorphic.com). Trains spiking neural networks (SNNs) on standard neuromorphic benchmarks and deploys to Catalyst N1/N2 FPGA hardware.
7+
Official benchmark suite for [Catalyst neuromorphic processors](https://catalyst-neuromorphic.com). Trains spiking neural networks (SNNs) on standard neuromorphic benchmarks and deploys to Catalyst N1/N2/N3 FPGA hardware.
88

99
All results are reproducible. Clone, install, train, deploy.
1010

11+
## Results — Catalyst N3 (Latest)
12+
13+
| Benchmark | Classes | Architecture | Neuron | Float Acc | Params | vs SOTA |
14+
|-----------|---------|-------------|--------|-----------|--------|---------|
15+
| **SHD** | 20 | 700→1536→20 (rec) | adLIF | **91.0%** | 3.47M | **#1** (beat SpikFormer 90.4%) |
16+
| **SSC** | 35 | 700→1024→512→35 (rec) | adLIF | **76.4%** | 2.31M | **#1** (beat Bittar 74.2%) |
17+
| **N-MNIST** | 10 | Conv2D+LIF→10 | LIF | **99.2%** | 691K | -0.47% vs PLIF 99.67% |
18+
| **GSC-35** | 35 | Delay+rec adLIF+BN | adLIF | *training* | ~1.5M | target: 95%+ |
19+
20+
N3 achieves **#1 on SHD and SSC**, the two most challenging spiking audio benchmarks. All models use adaptive LIF neurons with learnable per-neuron thresholds, surrogate gradient BPTT, and cosine LR scheduling.
21+
1122
## Results — Catalyst N2
1223

1324
| Benchmark | Classes | Architecture | Neuron | Float Acc | Params | Beats |
@@ -16,19 +27,17 @@ All results are reproducible. Clone, install, train, deploy.
1627
| **SSC** | 35 | 700→1024→512→35 (rec) | adLIF | **72.1%** | 2.31M | Loihi 2 (69.8%) |
1728
| **N-MNIST** | 10 | Conv2D+LIF→10 | LIF | **99.2%** | 466K ||
1829
| **GSC KWS** | 12 | 40→512→12 (rec, S2S) | adLIF | **88.0%** | 291K ||
19-
| **DVS Gesture** | 11 ||| *in progress* |||
2030

21-
All models are trained using the Catalyst N2 neuron models (LIF/adLIF) and deploy to Catalyst N2 FPGA hardware via int16 weight quantization. See `results.json` for machine-readable results with full training configs.
31+
All N2 models deploy to Catalyst N2 FPGA hardware via int16 weight quantization. See `results.json` for machine-readable results with full training configs.
2232

2333
## Competitive Context
2434

25-
| Benchmark | Catalyst N2 | Loihi 1 | Loihi 2 | Akida 2 | SpiNNaker 2 |
26-
|-----------|-------------|---------|---------|---------|-------------|
27-
| SHD | **90.7%** | 89.0% | 90.9% |||
28-
| SSC | **72.1%** || 69.8% |||
29-
| N-MNIST | **99.2%** | 99.5% ||||
30-
| GSC KWS | **88.0%** ||| 92.8% | 91.1% |
31-
| DVS Gesture | *in progress* | 89.6% || 97.1% ||
35+
| Benchmark | Catalyst N3 | Catalyst N2 | Loihi 1 | Loihi 2 | Akida 2 | SpiNNaker 2 |
36+
|-----------|-------------|-------------|---------|---------|---------|-------------|
37+
| SHD | **91.0%** | 90.7% | 89.0% | 90.9% |||
38+
| SSC | **76.4%** | 72.1% || 69.8% |||
39+
| N-MNIST | **99.2%** | 99.2% | 99.5% ||||
40+
| GSC KWS | *training* | 88.0% ||| 92.8% | 91.1% |
3241

3342
## Quick Start
3443

@@ -41,14 +50,14 @@ pip install -e .
4150
### Train a benchmark
4251

4352
```bash
44-
# SHD (Spiking Heidelberg Digits) — 90.7% with adLIF
45-
python shd/train.py --neuron adlif --hidden 1024 --epochs 200 --device cuda:0 --amp
53+
# SHD (Spiking Heidelberg Digits) — 91.0% with N3 adLIF (#1 SOTA)
54+
python shd/train.py --neuron adlif --hidden 1536 --epochs 200 --device cuda:0 --amp
4655

47-
# N-MNIST — 99.2% with Conv front-end
48-
python nmnist/train.py --data-dir data/nmnist --epochs 50 --device cuda:0 --amp
56+
# SSC (Spiking Speech Commands) — 76.4% with N3 adLIF (#1 SOTA)
57+
python ssc/train.py --hidden1 1024 --hidden2 768 --recurrent2 --epochs 70 --device cuda:0 --amp
4958

50-
# SSC (Spiking Speech Commands) — 72.1%, beats Loihi 2
51-
python ssc/train.py --hidden1 1024 --hidden2 512 --epochs 200 --device cuda:0 --amp
59+
# N-MNIST — 99.2% with Conv front-end
60+
python nmnist/train.py --data-dir data/nmnist --epochs 80 --device cuda:0 --amp
5261

5362
# Google Speech Commands (KWS) — 88.0% with Speech2Spikes encoding
5463
python gsc_kws/train.py --hidden 512 --dropout 0.3 --epochs 200 --device cuda:0 --amp
@@ -178,6 +187,13 @@ If you use these benchmarks in your research, please cite:
178187
url = {https://github.com/catalyst-neuromorphic/catalyst-benchmarks}
179188
}
180189
190+
@misc{catalyst-n3-2026,
191+
author = {Shulayev Barnes, Henry},
192+
title = {Catalyst N3: A 128-Core Hybrid Neuromorphic Processor with Hardware Virtualisation, Per-Tile Learning, and Silicon Metaplasticity},
193+
year = {2026},
194+
url = {https://catalyst-neuromorphic.com/research}
195+
}
196+
181197
@misc{catalyst-n2-2026,
182198
author = {Shulayev Barnes, Henry},
183199
title = {Catalyst N2: A 128-Core Configurable Neuromorphic Processor},

asl_dvs/__init__.py

Whitespace-only changes.

asl_dvs/loader.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"""ASL-DVS (American Sign Language) dataset loader using tonic.
2+
3+
Event-camera recordings of ASL letters. 24 classes (no J or Z — require motion).
4+
240x180 pixels downsampled to 32x32, binned into frames, flattened.
5+
"""
6+
7+
import numpy as np
8+
import torch
9+
from torch.utils.data import Dataset
10+
11+
try:
12+
import tonic
13+
import tonic.transforms as transforms
14+
except ImportError:
15+
raise ImportError("tonic required: pip install tonic")
16+
17+
18+
N_CHANNELS = 2048 # 32 * 32 * 2
19+
N_CLASSES = 24
20+
SENSOR_SIZE = (240, 180, 2)
21+
DOWNSAMPLE_SIZE = 32
22+
N_TIME_BINS = 10
23+
24+
25+
class ASLDVSDataset(Dataset):
26+
"""PyTorch Dataset for ASL-DVS with frame conversion."""
27+
28+
def __init__(self, data_dir="data/asl_dvs", train=True,
29+
n_time_bins=N_TIME_BINS, downsample=DOWNSAMPLE_SIZE):
30+
sensor = (downsample, downsample, 2)
31+
scale = downsample / 240 # downsample from 240x180
32+
33+
transform = transforms.Compose([
34+
transforms.Downsample(spatial_factor=scale),
35+
transforms.ToFrame(sensor_size=sensor, n_time_bins=n_time_bins),
36+
])
37+
self.dataset = tonic.datasets.ASLDVS(
38+
save_to=data_dir, transform=transform)
39+
self.n_time_bins = n_time_bins
40+
self.n_channels = downsample * downsample * 2
41+
42+
# 80/20 train/test split (no built-in split)
43+
n = len(self.dataset)
44+
n_train = int(0.8 * n)
45+
if train:
46+
self.indices = list(range(n_train))
47+
else:
48+
self.indices = list(range(n_train, n))
49+
50+
def __len__(self):
51+
return len(self.indices)
52+
53+
def __getitem__(self, idx):
54+
real_idx = self.indices[idx]
55+
frames, label = self.dataset[real_idx]
56+
T = frames.shape[0]
57+
flat = frames.reshape(T, -1).astype(np.float32)
58+
flat = (flat > 0).astype(np.float32)
59+
return torch.from_numpy(flat), int(label)
60+
61+
62+
def collate_fn(batch):
63+
inputs, labels = zip(*batch)
64+
max_t = max(x.shape[0] for x in inputs)
65+
C = inputs[0].shape[1]
66+
padded = torch.zeros(len(inputs), max_t, C)
67+
for i, x in enumerate(inputs):
68+
padded[i, :x.shape[0]] = x
69+
return padded, torch.tensor(labels, dtype=torch.long)

asl_dvs/train.py

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
"""Train SNN on ASL-DVS (American Sign Language) benchmark.
2+
3+
Architecture: 2048 -> hidden1 (recurrent adLIF) -> hidden2 (adLIF) -> 24
4+
24-class event-camera sign language letter classification.
5+
6+
Usage:
7+
python asl_dvs/train.py --epochs 200 --device cuda:0
8+
"""
9+
10+
import os
11+
import sys
12+
import argparse
13+
import random
14+
import numpy as np
15+
import torch
16+
import torch.nn as nn
17+
18+
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
19+
20+
from common.neurons import LIFNeuron, AdaptiveLIFNeuron, surrogate_spike
21+
from common.training import run_training
22+
from common.augmentation import event_drop
23+
24+
from asl_dvs.loader import ASLDVSDataset, collate_fn, N_CHANNELS, N_CLASSES
25+
26+
27+
class ASLDVSSNN(nn.Module):
28+
"""Two-layer SNN for ASL-DVS classification."""
29+
30+
def __init__(self, n_input=N_CHANNELS, n_hidden1=512, n_hidden2=256,
31+
n_output=N_CLASSES, beta_hidden=0.95, beta_out=0.9,
32+
threshold=1.0, dropout=0.3, neuron_type='adlif',
33+
alpha_init=0.93, rho_init=0.85, beta_a_init=0.05):
34+
super().__init__()
35+
self.n_hidden1 = n_hidden1
36+
self.n_hidden2 = n_hidden2
37+
self.n_output = n_output
38+
self.neuron_type = neuron_type
39+
40+
self.fc1 = nn.Linear(n_input, n_hidden1, bias=False)
41+
self.fc_rec = nn.Linear(n_hidden1, n_hidden1, bias=False)
42+
self.fc2 = nn.Linear(n_hidden1, n_hidden2, bias=False)
43+
self.fc3 = nn.Linear(n_hidden2, n_output, bias=False)
44+
45+
if neuron_type == 'adlif':
46+
self.lif1 = AdaptiveLIFNeuron(
47+
n_hidden1, alpha_init=alpha_init, rho_init=rho_init,
48+
beta_a_init=beta_a_init, threshold=threshold)
49+
self.lif2 = AdaptiveLIFNeuron(
50+
n_hidden2, alpha_init=alpha_init, rho_init=rho_init,
51+
beta_a_init=beta_a_init, threshold=threshold)
52+
else:
53+
self.lif1 = LIFNeuron(n_hidden1, beta_init=beta_hidden,
54+
threshold=threshold, learn_beta=True)
55+
self.lif2 = LIFNeuron(n_hidden2, beta_init=beta_hidden,
56+
threshold=threshold, learn_beta=True)
57+
58+
self.lif_out = LIFNeuron(n_output, beta_init=beta_out,
59+
threshold=threshold, learn_beta=True)
60+
self.dropout1 = nn.Dropout(p=dropout)
61+
self.dropout2 = nn.Dropout(p=dropout)
62+
63+
nn.init.xavier_uniform_(self.fc1.weight, gain=0.5)
64+
nn.init.xavier_uniform_(self.fc2.weight, gain=0.5)
65+
nn.init.xavier_uniform_(self.fc3.weight, gain=0.5)
66+
nn.init.orthogonal_(self.fc_rec.weight, gain=0.2)
67+
68+
def forward(self, x):
69+
batch, T, _ = x.shape
70+
device = x.device
71+
72+
v1 = torch.zeros(batch, self.n_hidden1, device=device)
73+
v2 = torch.zeros(batch, self.n_hidden2, device=device)
74+
v_out = torch.zeros(batch, self.n_output, device=device)
75+
spk1 = torch.zeros(batch, self.n_hidden1, device=device)
76+
spk2 = torch.zeros(batch, self.n_hidden2, device=device)
77+
out_sum = torch.zeros(batch, self.n_output, device=device)
78+
79+
if self.neuron_type == 'adlif':
80+
a1 = torch.zeros(batch, self.n_hidden1, device=device)
81+
a2 = torch.zeros(batch, self.n_hidden2, device=device)
82+
83+
for t in range(T):
84+
I1 = self.fc1(x[:, t]) + self.fc_rec(spk1)
85+
if self.neuron_type == 'adlif':
86+
v1, spk1, a1 = self.lif1(I1, v1, a1, spk1)
87+
else:
88+
v1, spk1 = self.lif1(I1, v1)
89+
spk1_d = self.dropout1(spk1) if self.training else spk1
90+
91+
I2 = self.fc2(spk1_d)
92+
if self.neuron_type == 'adlif':
93+
v2, spk2, a2 = self.lif2(I2, v2, a2, spk2)
94+
else:
95+
v2, spk2 = self.lif2(I2, v2)
96+
spk2_d = self.dropout2(spk2) if self.training else spk2
97+
98+
I_out = self.fc3(spk2_d)
99+
beta_out = self.lif_out.beta
100+
v_out = beta_out * v_out + (1.0 - beta_out) * I_out
101+
out_sum = out_sum + v_out
102+
103+
return out_sum / T
104+
105+
106+
def main():
107+
parser = argparse.ArgumentParser(description="Train SNN on ASL-DVS")
108+
parser.add_argument("--data-dir", default="data/asl_dvs")
109+
parser.add_argument("--epochs", type=int, default=200)
110+
parser.add_argument("--batch-size", type=int, default=64)
111+
parser.add_argument("--lr", type=float, default=1e-3)
112+
parser.add_argument("--weight-decay", type=float, default=1e-4)
113+
parser.add_argument("--hidden1", type=int, default=512)
114+
parser.add_argument("--hidden2", type=int, default=256)
115+
parser.add_argument("--dropout", type=float, default=0.3)
116+
parser.add_argument("--time-bins", type=int, default=10)
117+
parser.add_argument("--seed", type=int, default=42)
118+
parser.add_argument("--save", default="asl_dvs_model.pt")
119+
parser.add_argument("--neuron", choices=["lif", "adlif"], default="adlif")
120+
parser.add_argument("--alpha-init", type=float, default=0.93)
121+
parser.add_argument("--rho-init", type=float, default=0.85)
122+
parser.add_argument("--beta-a-init", type=float, default=0.05)
123+
parser.add_argument("--event-drop", action="store_true", default=True)
124+
parser.add_argument("--label-smoothing", type=float, default=0.05)
125+
parser.add_argument("--device", default=None)
126+
args = parser.parse_args()
127+
128+
torch.manual_seed(args.seed)
129+
np.random.seed(args.seed)
130+
random.seed(args.seed)
131+
132+
device = torch.device(args.device or ("cuda" if torch.cuda.is_available() else "cpu"))
133+
print(f"Device: {device}")
134+
135+
print("Loading ASL-DVS dataset...")
136+
train_ds = ASLDVSDataset(args.data_dir, train=True, n_time_bins=args.time_bins)
137+
test_ds = ASLDVSDataset(args.data_dir, train=False, n_time_bins=args.time_bins)
138+
139+
from torch.utils.data import DataLoader
140+
train_loader = DataLoader(
141+
train_ds, batch_size=args.batch_size, shuffle=True,
142+
collate_fn=collate_fn, num_workers=0, pin_memory=True)
143+
test_loader = DataLoader(
144+
test_ds, batch_size=args.batch_size, shuffle=False,
145+
collate_fn=collate_fn, num_workers=0, pin_memory=True)
146+
147+
print(f"Train: {len(train_ds)}, Test: {len(test_ds)}, Time bins: {args.time_bins}")
148+
149+
model = ASLDVSSNN(
150+
n_hidden1=args.hidden1, n_hidden2=args.hidden2,
151+
dropout=args.dropout, neuron_type=args.neuron,
152+
alpha_init=args.alpha_init, rho_init=args.rho_init,
153+
beta_a_init=args.beta_a_init,
154+
).to(device)
155+
156+
print(f"Model: {N_CHANNELS}->{args.hidden1}->{args.hidden2}->{N_CLASSES} "
157+
f"({args.neuron.upper()}, recurrent=on, dropout={args.dropout})")
158+
159+
augment_fn = (lambda x: event_drop(x)) if args.event_drop else None
160+
161+
config = {
162+
'device': device, 'epochs': args.epochs, 'lr': args.lr,
163+
'weight_decay': args.weight_decay, 'save_path': args.save,
164+
'benchmark': 'asl_dvs', 'augment_fn': augment_fn,
165+
'label_smoothing': args.label_smoothing,
166+
'model_config': {
167+
'n_input': N_CHANNELS, 'hidden1': args.hidden1,
168+
'hidden2': args.hidden2, 'n_output': N_CLASSES,
169+
'neuron_type': args.neuron, 'dropout': args.dropout,
170+
},
171+
}
172+
run_training(model, train_loader, test_loader, config)
173+
174+
175+
if __name__ == "__main__":
176+
main()

cifar10_dvs/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)