snnTorch
snnTorch is a PyTorch library for building and training spiking neural networks with surrogate gradients. Learn neurons, encoding, loss and tabular uses.
Overview
snnTorch is an open-source Python library that adds spiking neural network layers to PyTorch. It gives you neuron models that fire discrete spikes, tools to turn data into spike trains, and loss functions that work on spike counts. Because it follows normal PyTorch conventions, you train a spiking network with the same optimizers, data loaders and GPU workflow that you already use for any other model.
The key trick is the surrogate gradient. A spike is a hard threshold, and a hard threshold has no useful derivative. snnTorch swaps in a smooth stand-in during the backward pass so that gradient descent works. This lets you train a spiking model end to end and unroll it in time, much like a recurrent network. The result is a practical bridge between standard deep learning and the spiking neural networks used in neuromorphic computing.
What it is
snnTorch is a library, not a standalone framework. It does not replace PyTorch. It extends it.
The main parts
- Neuron layers. Modules such as
Leaky,Synaptic,AlphaandLapicquethat behave like layers but keep a membrane state over time. - Surrogate gradient functions. A set of smooth approximations to the spike function, used only in the backward pass.
- Spike generation tools. Functions that convert numbers or images into spike trains using rate, latency or delta coding.
- Loss functions. Losses designed for spike outputs, such as cross entropy on spike counts or on membrane potential.
- Training helpers. Utilities for backpropagation through time and for plotting spikes.
The design philosophy
The library treats a spiking neuron as an activation function with memory.
A normal ReLU forgets its input the moment it is done.
A spiking neuron keeps a running voltage.
That voltage leaks over time, grows with new input and resets when the neuron fires.
If you can write a recurrent network in PyTorch, you can write a spiking network in snnTorch.
Who uses it
- Students learning how spiking networks work.
- Researchers prototyping new neuron models and learning rules.
- Engineers who want to train a model before exporting it to low-power hardware.
- Teams exploring sparse, temporal signals such as event camera data.
Where it came from
Spiking neural networks have a long history in neuroscience. The leaky integrate-and-fire model dates back more than a century in its basic form, and it remains the workhorse of the field because it is simple and cheap.
For many years, training spiking networks was a problem. Hard thresholds made backpropagation impossible in the usual sense. Researchers used other routes: converting a trained conventional network into a spiking one, using local learning rules such as spike-timing-dependent plasticity, or using evolutionary search.
In the mid 2010s, work on surrogate gradients showed that you could keep the hard spike in the forward pass and use a smooth derivative in the backward pass. This made direct training competitive with conversion methods. Around the same time, deep learning libraries made automatic differentiation easy.
snnTorch appeared around 2020 as a way to bring these ideas into the PyTorch ecosystem. It came with a set of interactive tutorials that walk from a single neuron to a full classifier. A related tutorial paper, "Training Spiking Neural Networks Using Lessons From Deep Learning," explains how choices such as loss, optimizer and regularization carry over from standard practice.
Related libraries followed similar paths. Norse focuses on bio-inspired components, SpikingJelly bundles datasets and a full training stack, and Lava targets deployment on event-driven hardware. For the research context on this site, see /model/research.
How it works
This section follows data through a typical snnTorch classifier.
Step 1: Turn data into spikes
Real data is made of numbers. A spiking network only understands events in time. So the first step is encoding.
Three common encodings are:
- Rate coding. A larger value produces more spikes over the time window. A pixel with value 0.8 might spike on 80 percent of steps.
- Latency coding. A larger value produces an earlier spike. Strong signals arrive first.
- Delta coding. A spike is emitted only when the value changes by more than a margin. Static input produces no spikes.
Analogy: rate coding is like a Geiger counter that clicks faster near stronger radiation. Latency coding is like a race where the strongest runner crosses the line first. Delta coding is like a motion sensor that is silent until something moves.
Step 2: Pass spikes through a weighted layer
A normal nn.Linear layer multiplies the spike vector by a weight matrix. The output is a current. No special spiking layer is needed here.
Step 3: Update the membrane in a neuron layer
The Leaky neuron takes the current and updates its membrane potential.
The update has three parts:
- The old potential decays by a factor
beta, which is between 0 and 1. - The new input current is added.
- If the potential is above the threshold, the neuron emits a spike and its potential is reduced.
Analogy: the neuron is a bucket with a small hole. Water (input) pours in. Water leaks out through the hole (decay). When the bucket is full the bucket tips and sends one pulse (spike), then it is partly emptied.
Step 4: Repeat over time steps
The same layers run again and again for each time step.
The membrane state carries over from one step to the next. This is why the model has memory.
For T time steps, you collect the output spikes at each step.
Step 5: Read the output
For classification, the output layer has one neuron per class.
Two common read-outs are:
- Spike count. The class whose neuron fired most often wins.
- Membrane potential. The class with the highest final or maximum potential wins.
Step 6: Compute loss and backpropagate
The loss compares the output to the label. Examples include cross entropy on summed spikes and mean squared error on target firing counts.
During the backward pass, the hard spike function is replaced by a surrogate derivative. A popular choice is a fast sigmoid or an arctangent shape. Gradients flow back through time and through layers, then the optimizer updates the weights.
This is called backpropagation through time (BPTT).
Why surrogate gradients work
The true derivative of a step function is zero almost everywhere and infinite at the step. Neither helps learning.
The surrogate is a gentle hill centered on the threshold. Neurons whose potential is near the threshold get a strong learning signal. Neurons far from the threshold get a weak one.
It is like adjusting a dimmer switch near the point where the light flips on. You care most about the region near the flip.
Neuron model variants
snnTorch offers several neuron types. They differ in how much state they keep.
| Neuron | State kept | When to use |
|---|---|---|
| Lapicque | Membrane, with physical parameters (resistance, capacitance) | Learning the link to biology |
| Leaky | Membrane with a simple decay | Default choice for most tasks |
| Synaptic | Membrane plus synaptic current | When input smoothing helps |
| Alpha | Two coupled traces | Smooth, biologically shaped responses |
| Recurrent variants | Membrane plus feedback of own spikes | Temporal tasks with memory |
More state means more memory and more tuning.
Start with Leaky, and move up only if you can show a gain.
Reset mechanisms
After a spike the potential must come down. There are three common ways.
- Subtract. Remove the threshold value from the potential. Extra charge is kept.
- Zero. Set the potential to zero. All extra charge is lost.
- None. Do not reset. Used for special read-out layers.
Reset by subtraction tends to preserve more information, since a large input can cause several spikes in a row.
Loss functions for spikes
| Loss idea | What it compares | Effect |
|---|---|---|
| Cross entropy on spike counts | Total spikes per class neuron | Encourages the right class to fire most |
| Cross entropy on membrane | Final or peak potential | Smooth signal, easier training |
| Mean squared error on counts | Counts against target counts | Controls firing rates directly |
| Rate target loss | Counts against a correct and incorrect rate | Pushes right class to fire often, others rarely |
| Latency loss | Time of first spike | Rewards fast decisions |
A useful rule: if you need very few spikes, use a loss that also rewards early or sparse firing.
Key concepts and terms
| Term | Meaning | Everyday picture |
|---|---|---|
| Spike | Binary event emitted when the threshold is crossed | A doorbell ring |
| Membrane potential | Internal voltage of a neuron | Water level in a bucket |
| Beta | Decay factor of the membrane per step | Size of the leak |
| Threshold | Potential at which a neuron fires | Height of the bucket rim |
| Reset | What happens to potential after a spike | Emptying the bucket |
| Surrogate gradient | Smooth backward-pass stand-in for the spike | A dimmer switch near the flip point |
| BPTT | Backpropagation through time | Replaying a film in reverse to find mistakes |
| Rate coding | Value maps to spike frequency | Geiger counter |
| Latency coding | Value maps to spike time | Race finish order |
| Delta coding | Spikes only on change | Motion sensor |
| Time step | One tick of simulated time | A frame of a film |
| Hidden state | Memory kept between steps | A notepad |
| Sparsity | Fraction of units that are silent | A quiet room |
| Event data | Asynchronous stream of changes | A camera that reports only motion |
A worked example
A small classifier
The sketch below builds a two-layer spiking classifier for flat input vectors. It follows the usual API pattern. Check the current documentation for exact argument names before running it.
import torch
import torch.nn as nn
import snntorch as snn
from snntorch import surrogate, functional as SF
from snntorch import spikegen
beta = 0.9
spike_grad = surrogate.fast_sigmoid(slope=25)
num_steps = 25
class Net(nn.Module):
def __init__(self, n_in, n_hidden, n_out):
super().__init__()
self.fc1 = nn.Linear(n_in, n_hidden)
self.lif1 = snn.Leaky(beta=beta, spike_grad=spike_grad)
self.fc2 = nn.Linear(n_hidden, n_out)
self.lif2 = snn.Leaky(beta=beta, spike_grad=spike_grad)
def forward(self, x):
mem1 = self.lif1.init_leaky()
mem2 = self.lif2.init_leaky()
spk_rec = []
for _ in range(num_steps):
cur1 = self.fc1(x)
spk1, mem1 = self.lif1(cur1, mem1)
cur2 = self.fc2(spk1)
spk2, mem2 = self.lif2(cur2, mem2)
spk_rec.append(spk2)
return torch.stack(spk_rec)
net = Net(20, 64, 3)
loss_fn = SF.ce_rate_loss()
opt = torch.optim.Adam(net.parameters(), lr=2e-3)
x = torch.rand(32, 20) # batch of 32 rows, 20 features in [0, 1]
y = torch.randint(0, 3, (32,)) # fake labels
for epoch in range(5):
spk = net(x) # shape: time x batch x classes
loss = loss_fn(spk, y)
opt.zero_grad()
loss.backward()
opt.step()
print(epoch, float(loss))
Reading the code
- The input
xis fed as a constant current at every step. This is the simplest option and acts like a direct analog encoding. - Each
Leakylayer returns spikes and the updated membrane. - The loop collects output spikes at each step.
ce_rate_lossapplies cross entropy to the spike counts of each output neuron.- The backward pass uses the fast sigmoid surrogate automatically.
A numeric walk-through of the Leaky update
Take beta = 0.9, threshold 1.0, and reset by subtraction. The input current is 0.6 at every step.
| Step | Decayed potential | After adding input | Spike? | After reset |
|---|---|---|---|---|
| 1 | 0.000 | 0.600 | no | 0.600 |
| 2 | 0.540 | 1.140 | yes | 0.140 |
| 3 | 0.126 | 0.726 | no | 0.726 |
| 4 | 0.653 | 1.253 | yes | 0.253 |
| 5 | 0.228 | 0.828 | no | 0.828 |
The neuron fires on steps 2 and 4. A stronger current would fire more often. This is how a constant input turns into a spike rate.
Encoding with spikegen
To use rate coding instead of constant current, convert each row into a spike train before the loop.
from snntorch import spikegen
data = torch.rand(32, 20) # values in [0, 1]
spike_data = spikegen.rate(data, num_steps=25) # shape: 25 x 32 x 20
You then feed spike_data[t] at each step. Rate coding adds noise, because each spike is random, so you often need more steps for stable results.
Working with event datasets
Event cameras and similar sensors output a list of tuples: x, y, time and polarity.
You cannot feed this list to a network directly.
The usual route is to bin events into frames over short time windows.
Each frame then becomes one time step of input.
Typical steps:
- Load the event stream with a dataset library.
- Choose a time window, for example a few milliseconds.
- Accumulate events into a two-channel grid, one channel per polarity.
- Stack grids into a tensor of time x channel x height x width.
- Feed one slice per step.
Choosing the window is a trade-off. A short window keeps timing detail but gives sparse frames. A long window gives richer frames but blurs time.
Training loop pattern
def train_epoch(net, loader, loss_fn, opt, device):
net.train()
total = 0.0
for data, target in loader:
data, target = data.to(device), target.to(device)
spk = net(data) # time x batch x classes
loss = loss_fn(spk, target)
opt.zero_grad()
loss.backward()
opt.step()
total += float(loss)
return total / len(loader)
def accuracy(net, loader, device):
net.eval()
correct = n = 0
with torch.no_grad():
for data, target in loader:
data, target = data.to(device), target.to(device)
spk = net(data)
pred = spk.sum(dim=0).argmax(dim=1)
correct += int((pred == target).sum())
n += target.numel()
return correct / n
Notice that the prediction is the class with the most spikes summed over time.
Measuring sparsity
To estimate how efficient a model could be on suitable hardware, count spikes.
def spike_rate(spk):
# spk: time x batch x units, values 0 or 1
return float(spk.mean())
A rate of 0.05 means that on average only 5 percent of neurons fire per step.
Lower is better for energy, as long as accuracy holds.
Strengths and limitations
Strengths
- Familiar workflow. If you know PyTorch, you can start in minutes.
- GPU training. Models train on the same hardware as other deep networks.
- Good teaching material. Tutorials build from one neuron to a full network.
- Flexible neuron models. You can compare first order, synaptic and alpha style neurons.
- Works with event data. Event camera datasets can be loaded with companion tools and fed directly.
- Sparse activity. Output is mostly zeros, which can reduce operations on suitable hardware.
Limitations
- Slower than a dense network on a GPU. Looping over time steps multiplies compute and memory. On a GPU, spiking models are often slower than an equivalent conventional model.
- Memory growth. BPTT stores states for every step, so long sequences use a lot of memory.
- Energy gains need hardware. Sparsity only turns into saved energy on hardware that exploits it.
- Accuracy gap on some tasks. On many static benchmarks, a standard network still wins by a margin.
- Hyperparameter sensitivity. Beta, threshold, surrogate slope and number of steps all matter.
- Not a deployment framework. Exporting to a chip needs additional tools such as Lava.
How it compares with alternatives
| Library | Base | Strength | Typical use |
|---|---|---|---|
| snnTorch | PyTorch | Simple API, strong tutorials | Learning and prototyping |
| Norse | PyTorch | Bio-inspired components and functional style | Research on learning rules |
| SpikingJelly | PyTorch | Datasets, many neuron types, fast kernels | Benchmarks on event data |
| Lava | Own runtime | Process model and hardware backends | Deployment on event-driven systems |
| Loihi | Hardware | Low-power spiking execution | Edge neuromorphic inference |
| PyTorch | Native | General deep learning | Everything else |
| JAX | Native | Compilation and functional transforms | Custom research code |
When to choose snnTorch: you want a quick, readable way to train a spiking model on a GPU and you value good documentation.
When to choose something else: you need hardware deployment first, or you need very fast kernels for large event datasets.
Role in tabular foundation models
Tabular foundation models learn from raw tables. They deal with rows and columns, mixed types such as numbers and categories, missing values and often unhelpful or absent column names. Many are pre-trained on large numbers of synthetic or real tables so that they can predict on a new table through in-context learning, without gradient updates at prediction time. See TabPFN and FT-Transformer, and the structure notes at /model/architecture.
snnTorch is not a tabular model. It is a library for a different model family. Still, there are concrete points of contact, and some limits worth stating plainly.
Points of contact
- Encoding rows as spikes. A tabular row is a vector of numbers. You can feed it as constant current, or convert each feature with rate or latency coding. This is the same kind of step that tabular models perform when they turn each cell into an embedding. The choice matters: it decides what information the network can see.
- Missing values. A missing cell can be represented as a lack of spikes on that input line. In a spiking model this is natural, because silence is a valid state. Still, silence is also what a zero value looks like under rate coding, so you would add a mask feature to keep the two cases apart. For model-based fill-in see imputation networks.
- Anomaly detection. Spiking autoencoders and prediction-error models can flag unusual rows or windows. Because activity is sparse and temporal, they suit streams of sensor tables. See Anomaly Detection.
- Continual learning. Local learning rules and small recurrent state fit the idea of updating a model as new rows arrive, without retraining on all history. This echoes the continual learning goals of tabular foundation models, though the methods differ.
- Time-series tables. Tables with a timestamp column, such as machine logs, have natural temporal structure that spiking models can exploit.
Where it does not fit
- Static mixed-type tables. For ordinary tables with categories, skewed numbers and few rows, gradient boosting such as XGBoost or a pre-trained model such as TabPFN is usually far stronger and simpler.
- In-context learning. A spiking network has no built-in way to read a few labelled rows in its input and adapt. That behavior comes from attention in transformers.
- Column semantics. Understanding that a column named "revenue" or a column of postcodes means something specific needs text or learned embeddings. snnTorch offers none.
- Sector or semantic classification. Classifying a table by industry from its headers and values is a language and context task, not a spiking one.
A realistic role
Think of snnTorch as a research tool for the low-power edge of a tabular pipeline. It can encode streaming sensor rows, produce an anomaly flag or compact score, and hand results to a larger tabular model upstream. It is a complement, not a competitor. For how tabular models are judged, see /model/benchmarks.
Practical guidance and best practices
Start with the simplest working model
- Use a single hidden layer and a small number of steps, such as 10 to 25.
- Use
Leakyneurons with beta around 0.9. - Use a fast sigmoid surrogate with a moderate slope.
- Check that the loss falls on a small batch before scaling up.
Pick the encoding on purpose
- For static vectors, try constant current first. It is deterministic and fast.
- Use rate coding when you want a faithful spiking input and can afford more steps.
- Use latency coding for very low spike counts.
- Use delta coding for streams where change is what matters.
Watch the firing rate
Log the mean firing rate per layer.
If it is near zero, the layer is dead and gradients will be tiny. Raise the initial weights or lower the threshold.
If it is near one, the layer is saturated. Lower weights or raise the threshold.
A healthy hidden layer fires on a modest fraction of neurons at each step.
Control memory
- Use truncated BPTT for long sequences.
- Use mixed precision where supported.
- Reduce batch size or the number of steps before reducing model width.
Regularize
Spiking networks overfit like any other. Add dropout, weight decay and data augmentation. Some practitioners add a firing rate penalty to encourage sparsity.
Keep a reference model
Train a plain network of the same shape with ReLU. Compare accuracy and cost. If the spiking model offers no advantage for your goal, you should know early.
Make runs repeatable
import torch, random, numpy as np
seed = 7
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
Record beta, threshold, slope, steps and encoding in a config file next to each run.
run: snn-baseline-01
neuron: leaky
beta: 0.9
threshold: 1.0
surrogate: fast_sigmoid
slope: 25
num_steps: 25
encoding: constant_current
optimizer: adam
lr: 0.002
Plan the path to hardware
If the end goal is a chip, restrict yourself early to operations that map well: simple neurons, bounded weights and limited precision. Consider quantization-aware training. Then use an export route such as Lava for Loihi.
Design checklist before you start
- State the goal: accuracy, latency, energy, or learning about spiking models.
- Pick the data type: static vectors, frames, event streams, or time series.
- Choose the encoding and justify it.
- Fix a plain baseline and its score.
- Decide the metric, including spikes per sample.
- Set a time budget for tuning.
Writing these down stops the project from drifting into tuning for its own sake.
Debugging tips
- Print the shape of every tensor once. Time x batch x features is the usual order.
- Run one sample for one step and compare to a hand calculation.
- Plot membrane traces for three neurons. They should rise, leak and drop after spikes.
- Freeze the first layer and see if later layers learn. This isolates faults.
- Lower the learning rate by ten if the loss jumps around.
Common mistakes
- Forgetting to reset state between batches. Membrane potential from the last sample leaks into the next one. Initialize hidden states at the start of each forward pass.
- Too few time steps. The network cannot integrate enough input to fire. Increase steps or input scale.
- Wrong loss for the read-out. Using a spike-count loss with membrane read-out, or the reverse, hides learning signals.
- Surrogate slope too steep or too flat. Too steep makes gradients vanish away from the threshold. Too flat blurs the signal.
- Ignoring input scale. Features that are not normalized produce either silence or constant firing.
- Comparing against a weak baseline. Always compare to a tuned conventional model on the same data.
- Assuming energy savings. A spiking model on a GPU does not save energy. Savings depend on hardware.
- Using it for plain tabular prediction. For rows and columns with mixed types, start with scikit-learn style baselines and gradient boosting.
- Mixing encodings and masks. Treating a missing value as a zero changes the meaning of the data.
- Skipping visualization. A spike raster plot reveals dead or saturated layers in seconds.
Frequently asked questions
What is snnTorch used for?
snnTorch is used to build and train spiking neural networks inside PyTorch. People use it to learn how spiking models work, to prototype new neuron models and to train networks on temporal or event-based data before moving them to low-power hardware.
Do I need to know neuroscience to use it?
No. A working picture of a leaky integrate-and-fire neuron is enough. The library hides most of the biology behind familiar PyTorch modules, and the tutorials explain each idea with small code examples before moving to larger models.
What is a surrogate gradient?
A surrogate gradient is a smooth replacement for the derivative of the spike function. The forward pass still uses a hard threshold, so outputs are real spikes. The backward pass uses a gentle curve, such as a fast sigmoid, so that gradient descent receives a useful signal near the threshold.
Is a spiking network more accurate than a regular network?
Usually not on static data. Standard networks often reach higher accuracy on image or table benchmarks. Spiking models are attractive for their sparse, event-driven computation and their fit to neuromorphic hardware, not for raw accuracy on dense inputs.
Will snnTorch make my model use less energy?
Not by itself. On a GPU, looping over time steps can cost more energy than a conventional model. Savings appear when the network runs on hardware that skips work for silent neurons, and when activity is genuinely sparse.
Can I use snnTorch on tabular data?
You can, by encoding each row as current or as spike trains. It is a research option rather than a recommended default. For most tables, gradient boosting or a tabular foundation model such as TabPFN is simpler and stronger.
How does it handle missing values?
The library has no special missing value handling. Silence on an input line can represent a missing value, but under rate coding a zero value looks the same. A common fix is to add a separate mask input that tells the network which cells were observed.
How is it different from Norse and SpikingJelly?
All three are PyTorch based. snnTorch emphasizes a simple API and teaching material. Norse emphasizes bio-inspired components and a functional style. SpikingJelly bundles many datasets, neuron types and optimized kernels. The best choice depends on your task and taste.
Can I deploy an snnTorch model on neuromorphic hardware?
Possibly, but it needs extra steps. You would export weights and neuron parameters, quantize them, and map them into a deployment framework such as Lava or a vendor toolchain. Expect some accuracy change after quantization.
How many time steps should I use?
Start with 10 to 25 and tune. More steps give the network more time to integrate and improve stability under rate coding, but they raise compute and memory. Fewer steps are faster but may leave neurons silent.
Glossary
- Alpha neuron: a neuron model whose synaptic current rises and falls smoothly after a spike.
- Backpropagation through time (BPTT): gradient training that unrolls a network across time steps.
- Beta: the per-step decay factor of the membrane potential.
- Delta coding: an encoding that emits spikes when the signal changes.
- Event data: data that records changes as they happen rather than fixed frames.
- Firing rate: the number of spikes per neuron per unit time.
- Latency coding: an encoding where larger values produce earlier spikes.
- Leaky integrate-and-fire (LIF): a neuron that sums input, leaks, and fires at a threshold.
- Membrane potential: the internal voltage state of a neuron.
- Neuromorphic hardware: chips designed to run spiking networks with low power.
- Rate coding: an encoding where larger values produce more spikes.
- Raster plot: a chart of spike times for many neurons.
- Reset mechanism: the rule that lowers potential after a spike, by subtraction or to zero.
- Surrogate gradient: a smooth stand-in for the spike derivative used in training.
- Threshold: the potential level that triggers a spike.
Summary
snnTorch is a PyTorch library that makes spiking neural networks easy to build and train. It provides leaky and synaptic neuron layers, spike encoders, spike-based losses and surrogate gradients, so that backpropagation through time works with hard spikes.
For tabular foundation models it plays a supporting, research-oriented role. It can encode rows as spikes, represent missing cells through silence plus a mask, and support anomaly detection and continual updates on sparse streams. It does not provide in-context learning, column understanding or mixed-type embeddings. For plain tables choose XGBoost or TabPFN. To go deeper into the theory read Spiking Neural Networks, compare with Norse and SpikingJelly, and see /model/research for the broader context.
Related
It belongs to Neuromorphic in my stack, under AI and machine learning. Others in the same group: