AI Computer Institute
Expert-curated CS & AI curriculum aligned to CBSE standards. A bharath.ai initiative. About Us

Neuromorphic Computing and Spiking Neural Networks

📚 Frontier AI⏱️ 27 min read🎓 Grade 12
✍️ AI Computer Institute Editorial Team Updated: August 2026 CBSE-aligned · Peer-reviewed · 27 min read
Content curated by subject matter experts with IIT/NIT backgrounds. All chapters are fact-checked against official CBSE/NCERT syllabi.

A wearable ECG patch has to watch a heart for weeks on a coin-cell battery and catch the rare beat that signals atrial fibrillation. An event-driven spiking chip is the obvious hardware target: it only spends energy when the membrane potential of a neuron actually crosses threshold, so a quiet heart rhythm costs almost nothing to monitor. But before any of that energy budget matters, the network has to be trained to recognise arrhythmia in the first place, from thousands of labelled heartbeats, the same way any classifier is trained. And that is where spiking networks run into a wall that a conventional convolutional network never sees: the neuron's output is a hard 0-or-1 spike, produced by a step function, and the derivative of a step function is zero almost everywhere. Gradient descent needs a derivative to know which way to nudge every weight. If that derivative is zero, standard backpropagation has nothing to work with — every gradient it computes is exactly zero, and the network never learns anything at all.

This chapter is about how that problem is actually solved in practice — surrogate gradient learning, the technique that lets you train a spiking neural network (SNN) with ordinary gradient descent despite the non-differentiable spike — worked through by hand, timestep by timestep, on numbers you can check yourself. It assumes you already know what a leaky integrate-and-fire (LIF) neuron is and what neuromorphic chips like Loihi and TrueNorth look like in silicon; the focus here is narrower and goes deeper: exactly how you get gradients to flow backward through a chain of spikes at all, what that costs in memory compared to on-chip learning rules like STDP, and why the network you train is not the network that runs at deployment time in the way most students initially assume.

Notation refresher: the leaky integrate-and-fire neuron

A single LIF neuron tracks a membrane potential u[t] that leaks toward zero each timestep, gets pushed up by weighted input spikes, and fires when it crosses a threshold θ. Using the soft-reset convention that most surrogate-gradient training code uses (it subtracts the threshold rather than hard-resetting to zero, which keeps the recurrence linear and the bookkeeping clean):

u[t] = β·u[t-1] + w·x[t] - θ·s[t-1]
s[t] = Θ(u[t] - θ)

Here β is the leak (decay) factor, w is the synaptic weight, x[t] is the binary input spike at time t, and Θ is the Heaviside step function: Θ(z) = 1 if z ≥ 0, else 0. The -θ·s[t-1] term is the reset: if the neuron fired last step, subtract the threshold back out of the membrane potential before integrating the next input. This is exactly the same recurrence shape as a vanilla RNN cell — a leaky state, an input projection, a nonlinearity — which is precisely why SNN training reuses so much RNN machinery, and precisely why it inherits RNN training's classic failure modes, as you'll see near the end of this chapter.

Why plain backpropagation cannot see through a spike

To train this neuron with gradient descent, you need ∂s[t]/∂u[t] — how much the spike output changes when the membrane potential changes by a tiny amount. But s[t] = Θ(u[t] - θ) is a step function. Its derivative is 0 everywhere except exactly at u[t] = θ, where it is a Dirac delta — formally infinite, but over a region of zero width. A computer implementing this exactly will report ∂s/∂u = 0 for every floating-point value of u it ever actually encounters, because landing on the exact real number θ has probability zero. Multiply that zero through a chain rule with anything else, and every gradient in the network is zero. Autodiff frameworks like PyTorch will run without crashing — they will just silently teach you nothing, because the loss never moves no matter which direction you nudge any weight.

The surrogate gradient trick

The fix, formalised and popularised by Zenke & Ganguli's SuperSpike (2018) and surveyed comprehensively by Neftci, Mostafa & Zenke (2019), is deceptively simple: keep the exact, discontinuous Heaviside function in the forward pass — the network genuinely spikes 0 or 1, nothing about the forward computation changes — but during the backward pass, when the chain rule asks for ∂s[t]/∂u[t], substitute a smooth, hand-chosen function that looks like a relaxed version of the step, is nonzero in a neighbourhood around the threshold, and points in a locally sensible direction. A common choice is the derivative of a scaled logistic sigmoid centred on the threshold:

σ(u) = 1 / (1 + exp(-k·(u - θ)))
∂s/∂u  ≈  k·σ(u)·(1 - σ(u))     ← surrogate, used only going backward

where k controls how sharply the surrogate is peaked around θ. This function is never evaluated during the forward pass — the forward pass never even computes σ(u). It exists purely as a substitute derivative, injected at exactly the one point in the computational graph where the true derivative is useless.

The misconception to correct directly

The single most common thing students (and more than a few early SNN papers) get wrong here: assuming that "using a surrogate gradient" means the network is secretly soft during training — that the hard spike gets relaxed into some continuous approximation while learning, and only snaps to a true 0/1 spike once deployed. That is backwards, and it matters, because it is exactly what makes surrogate-gradient training compatible with neuromorphic hardware at all. The forward pass is untouched. At every single training step, s[t] is genuinely, exactly, a binary 0 or 1, computed by the real Heaviside function on the real membrane potential — the identical sparse, event-driven computation the chip will run at deployment. The surrogate function is invoked only inside the backward pass, purely as a bookkeeping device for computing a useful weight update; it never touches a forward activation, is never stored as a "soft spike," and has no existence at inference time. Two consequences follow directly from getting this right: first, everything you can say about spike sparsity and event-driven energy savings on real silicon (the actual currency of neuromorphic computing) applies unchanged to a surrogate-gradient-trained network, because the forward computation is identical to a hand-designed or STDP-trained one. Second, the surrogate is a modelling choice with no single "correct" form — different papers use different smooth functions (fast sigmoid, arctangent, piecewise-linear) and get comparably good results, because the surrogate only needs to point gradient descent in a broadly useful direction, not compute anything exact.

Fully worked example: backpropagation through time, three timesteps by hand

Take one LIF neuron with β = 0.9, θ = 1.0, weight w = 0.7, surrogate steepness k = 4, and three input spikes x = [1, 1, 1] at t = 1, 2, 3. Start at u[0] = 0, s[0] = 0.

Forward pass (exact Heaviside — this is the real, chip-executable computation):

t=1: u[1] = 0.9·0 + 0.7·1 - 1.0·0        = 0.700   → s[1] = Θ(0.700-1.0) = 0
t=2: u[2] = 0.9·0.700 + 0.7·1 - 1.0·0    = 1.330   → s[2] = Θ(1.330-1.0) = 1  (spike)
t=3: u[3] = 0.9·1.330 + 0.7·1 - 1.0·1    = 0.897   → s[3] = Θ(0.897-1.0) = 0

The neuron fires once, at t=2. Suppose the target spike train is y = [0, 1, 1] — we wanted a second spike at t=3 that didn't happen. Using the standard SNN loss L = ½·Σ(s[t]-y[t])², only the t=3 term is nonzero: L = ½·(0-1)² = 0.5.

Surrogate values at each membrane potential (using k=4, θ=1.0):

g[1] = 4·σ(4·(0.700-1.0))·(1-σ(...)) = 0.7116
g[2] = 4·σ(4·(1.330-1.0))·(1-σ(...)) = 0.6655
g[3] = 4·σ(4·(0.897-1.0))·(1-σ(...)) = 0.9587

Notice g[3] is the largest — the surrogate peaks exactly at the threshold, and u[3]=0.897 is the closest of the three membrane potentials to θ=1.0. That's the surrogate doing its job: it hands the largest gradient to the timestep where a tiny nudge to w was closest to actually flipping the spike.

Backward pass. Because u[t] depends on u[t-1] both directly (the leak term) and through the reset (-θ·s[t-1], which itself depends on u[t-1] via the surrogate), the local Jacobian is ∂u[t+1]/∂u[t] = β - θ·g[t]. Define δ[t] = ∂L/∂u[t] and unroll backward from t=3:

δ[3] = (s[3]-y[3])·g[3]              = (0-1)·0.9587       = -0.9587
δ[2] = (s[2]-y[2])·g[2] + δ[3]·(β-θ·g[2])
     = (1-1)·0.6655 + (-0.9587)·(0.9-1.0·0.6655)
     = 0 + (-0.9587)·0.2345                                = -0.2248
δ[1] = (s[1]-y[1])·g[1] + δ[2]·(β-θ·g[1])
     = (0-0)·0.7116 + (-0.2248)·(0.9-1.0·0.7116)
     = 0 + (-0.2248)·0.1884                                = -0.0424

Since ∂u[t]/∂w = x[t] at every timestep, the total gradient is a sum over time:

∂L/∂w = δ[1]·x[1] + δ[2]·x[2] + δ[3]·x[3]
       = -0.0424 + (-0.2248) + (-0.9587) = -1.226

Sanity check the sign: the gradient is negative, so gradient descent (w ← w - η·∂L/∂w) increases w. That is exactly what should happen — the network was supposed to fire at t=3 and u[3]=0.897 fell just short of θ=1.0; a larger weight pushes future membrane potentials higher and closer to firing when it should. The gradient found the right direction using only local, smooth surrogate information, without ever touching the real (useless) derivative of the step function.

Here is that entire computation as runnable, self-contained Python — every value traced above appears in the printed output:

import math

beta, theta, w, k = 0.9, 1.0, 0.7, 4.0
x = [1, 1, 1]
y = [0, 1, 1]

def sigmoid(z):
    return 1 / (1 + math.exp(-z))

u, s = [0.0], [0.0]
for t in range(1, 4):
    ut = beta * u[t-1] + w * x[t-1] - theta * s[t-1]
    st = 1.0 if (ut - theta) >= 0 else 0.0
    u.append(ut)
    s.append(st)

g = [None]
for t in range(1, 4):
    z = k * (u[t] - theta)
    sg = sigmoid(z)
    g.append(k * sg * (1 - sg))

delta = [None] * 4
delta[3] = (s[3] - y[2]) * g[3]
delta[2] = (s[2] - y[1]) * g[2] + delta[3] * (beta - theta * g[2])
delta[1] = (s[1] - y[0]) * g[1] + delta[2] * (beta - theta * g[1])

dLdw = sum(delta[t] * x[t-1] for t in range(1, 4))

print("u:", [round(v, 3) for v in u])
print("s:", s)
print("g:", [None] + [round(v, 4) for v in g[1:]])
print("delta:", [round(v, 4) for v in delta[1:]])
print("dL/dw:", round(dLdw, 4))

# u: [0.0, 0.7, 1.33, 0.897]
# s: [0.0, 0.0, 1.0, 0.0]
# g: [None, 0.7116, 0.6655, 0.9587]
# delta: [-0.0424, -0.2248, -0.9587]
# dL/dw: -1.2259

Checking the derivation: what a finite-difference gradient check actually shows

A useful independent check on any hand-derived gradient is finite differences: nudge w by a small ε, rerun the forward pass, and see how much the loss moves. Do that here with ε = 10⁻⁴ around w = 0.7, using the true Heaviside spiking network (not the surrogate) for both evaluations, and the result is (L(w+ε) - L(w-ε)) / (2ε) = 0 — exactly zero, not approximately zero. Because none of the three membrane potentials (0.700, 1.330, 0.897) sit within 10⁻⁴ of their respective thresholds, nudging w by that little changes no spike decision at all, so the loss doesn't move by even a rounding error. This is not a bug in the check — it is a direct demonstration of the exact problem this chapter opened with: the true gradient of a spiking network is genuinely, numerically zero almost everywhere. The surrogate gradient of -1.226 computed above is not an approximation to some other nonzero "true" gradient that finite differences failed to find; there is no such nonzero true gradient to find at this point. The surrogate is a deliberately different, useful signal, substituted in because the true one carries no information.

What this costs: BPTT memory versus STDP's constant-memory locality

Look again at the backward recursion: δ[2] needed δ[3], which needed g[3], which needed u[3], which needed the entire forward trajectory. To compute a gradient at any timestep, the backward pass has to walk back through every timestep after it — which means the forward pass must store (or be able to recompute) the membrane potential and spike state at every timestep before backpropagation can even start. For a three-step toy example that's nothing; for a real keyword-spotting or ECG-classification SNN unrolled over 200–1,000 timesteps of an audio or biosignal window, that's 200–1,000 stored activation tensors per layer, held in GPU memory simultaneously — this is backpropagation-through-time (BPTT), with exactly the same O(T) memory cost that makes long-sequence RNN training expensive, and for exactly the same structural reason: this recurrence is an RNN cell.

Contrast that with spike-timing-dependent plasticity (STDP), the local, unsupervised learning rule that can run natively on neuromorphic silicon: each synapse updates itself using only the timing of its own pre- and post-synaptic spikes within a short local window, with no dependency on anything beyond that window and no backward pass to unroll. STDP's per-synapse memory cost is O(1), constant regardless of how long the input sequence runs, which is exactly why it can execute on-chip, in real time, with no off-chip memory for gradients. This is the real practical trade-off between the two training paradigms covered elsewhere in this course: STDP buys you online, on-chip, biologically-plausible learning at the cost of accuracy on complex supervised tasks; surrogate-gradient BPTT buys you accuracy competitive with conventional deep networks, but only by paying an RNN-scale memory bill on a GPU, offline, before the trained weights are ever downloaded onto neuromorphic hardware for inference.

Two roads onto silicon, and a long-sequence failure mode

Surrogate-gradient BPTT is not the only way to get weights onto a spiking chip. The alternative is ANN-to-SNN conversion (Rueckauer, Lungu, Hu, Pfeiffer & Liu, 2017): train an ordinary ReLU network with ordinary backpropagation, then map its weights onto spiking neurons whose firing rate over many timesteps approximates the ReLU activation it replaces. Conversion sidesteps the non-differentiability problem entirely — there's no spike anywhere in the training graph — but firing-rate coding needs enough timesteps for a rate to stabilise, typically tens to hundreds, which pushes up inference latency and energy on the deployed chip. Direct surrogate-gradient training, because it optimises the spike timing itself rather than approximating a rate after the fact, routinely reaches comparable accuracy with far fewer timesteps — often single digits to a few dozen — which is why production-oriented SNN toolchains like snnTorch (Eshraghian et al., 2023) default to it for latency-sensitive deployments such as always-on wearables, even though it's the more memory-hungry approach to train.

That memory hunger has a second-order consequence worth naming explicitly, because it's the same failure mode that plagued RNNs for a decade before LSTMs and gating fixed it: look at the multiplier in the backward recursion, (β - θ·g[t]). With β < 1 and the surrogate g[t] typically small whenever a membrane potential sits far from threshold, long products of this term across hundreds of timesteps shrink toward zero — a vanishing-gradient problem, structurally identical to a vanilla RNN's, arriving in SNN training for the same underlying reason. Modern neuromorphic hardware like Intel's Loihi 2 (Orchard et al., 2021) partly answers this on the deployment side by supporting graded, multi-bit spikes rather than pure binary ones, which loosens some of these constraints; on the training side, the standard mitigations are the same ones RNN training adopted — truncated BPTT windows and carefully shaped surrogate functions that stay away from zero over a wider range.

Active recall

Attempt each of these before reading the worked answers below.

  1. Why does plain backpropagation fail on a spiking neuron's threshold function? What does ∂s/∂u equal almost everywhere, and what happens exactly at u = θ?
  2. Using the same setup as the worked example (β=0.9, θ=1.0, k=4, x=[1,1,1], y=[0,1,1]) but with w = 0.65 instead of 0.7, recompute u[1..3], s[1..3], and ∂L/∂w.
  3. Now take the original weight w = 0.7, but raise the threshold to θ = 1.2 (everything else unchanged, including that θ appears in three places in the model). Recompute the full forward pass, all three surrogate values, and ∂L/∂w. Does the spike pattern change? Does the gradient's magnitude?
  4. A classmate argues: "Surrogate-gradient-trained SNNs use backprop, so they must run on the same dense, synchronous, energy-hungry hardware as ANNs — neuromorphic chips can't actually help them." What's wrong with this claim?
  5. Why does BPTT-based SNN training need memory that scales with the number of timesteps T, while STDP-based on-chip learning does not?
  6. Real SNNs for audio or biosignal tasks are often unrolled over hundreds of timesteps, not three. Name two concrete consequences of that for training — one about memory, one about a gradient pathology — and connect each to a specific quantity in this chapter's derivation.

Worked answers

1. s[t] = Θ(u[t]-θ) is the Heaviside step function, which is flat everywhere except at the discontinuity, so ∂s/∂u = 0 for every u ≠ θ. Exactly at u = θ the function jumps instantaneously, and its derivative there is a Dirac delta — informally infinite, but over a set of measure zero, so no floating-point value of u ever lands on it. Chained through backprop, a zero derivative anywhere in the path zeroes the whole gradient, so plain autodiff on a spiking network reports exactly zero gradient for every weight, every time — no learning signal at all.

2. With w=0.65: u[1]=0.9·0+0.65·1-0=0.650, s[1]=0. u[2]=0.9·0.650+0.65-0=1.235, s[2]=Θ(1.235-1.0)=1. u[3]=0.9·1.235+0.65-1.0·1=0.7615, s[3]=Θ(0.7615-1.0)=0. Same spike pattern [0,1,0] as the original. Surrogates: g[1]=0.6347, g[2]=0.8080, g[3]=0.8030 — all shift because every u[t] moved. Backward: δ[3]=(0-1)·0.8030=-0.8030; δ[2]=0+(-0.8030)·(0.9-1.0·0.8080)=(-0.8030)(0.0920)=-0.0739; δ[1]=0+(-0.0739)·(0.9-1.0·0.6347)=(-0.0739)(0.2653)=-0.0196. ∂L/∂w = -0.0196-0.0739-0.8030 = -0.8965. A smaller weight produced a smaller-magnitude gradient — the network is now further from firing at t=3 in absolute terms relative to how close the surrogate perceives it, illustrating that the gradient's size is not simply proportional to "how wrong" the output looks, but to how the whole chain of local sensitivities compounds.

3. Raising θ to 1.2 changes three things simultaneously: the reset subtraction (-θ·s[t-1]), the spike comparison (u-θ), and the surrogate's centre. Forward pass: u[1]=0.700 (unchanged, no reset term active yet), s[1]=Θ(0.700-1.2)=0. u[2]=0.9·0.700+0.7-0=1.330 (also unchanged so far), s[2]=Θ(1.330-1.2)=1 — still spikes, since 1.33>1.2. u[3]=0.9·1.330+0.7-1.2·1=0.697 (this did change, because the reset now subtracts 1.2 instead of 1.0), s[3]=Θ(0.697-1.2)=0. Spike pattern is unchanged, [0,1,0] — but everything downstream is not. Surrogates, now centred at 1.2: g[1]=0.420 (dropped, since u[1]=0.700 is now farther from the higher threshold), g[2]=0.9353 (rose sharply, since u[2]=1.330 is now much closer to 1.2 than it was to 1.0), g[3]=0.4161. Backward: δ[3]=(0-1)(0.4161)=-0.4161; δ[2]=0+(-0.4161)(0.9-1.2·0.9353)=(-0.4161)(-0.2224)=+0.0926 — note the sign flip, because θ·g[2]=1.1224>β=0.9 now, making the local Jacobian negative; δ[1]=0+(0.0926)(0.9-1.2·0.420)=(0.0926)(0.396)=+0.0367. ∂L/∂w = 0.0367+0.0926-0.4161=-0.2868. So: same spikes, but the gradient magnitude collapsed from -1.226 to -0.287, and two of the three per-timestep contributions flipped sign along the way — a change to θ alone, holding the weight fixed, silently reshapes the entire gradient computation, not just the firing threshold. This is exactly the "full ripple effect" a single shared-parameter change can cause.

4. The claim conflates training-time compute with inference-time compute. Training genuinely happens on a GPU, using dense autodiff and BPTT — that part is accurate, and it is expensive. But once training finishes, the surrogate function is discarded entirely; it was never part of the model, only part of the optimiser's bookkeeping. The deployed network computes exactly the same sparse, binary, event-driven Heaviside spikes it computed during the forward pass at every training step — nothing about the forward computation graph changes between training and deployment. Running those fixed, trained weights on Loihi-2-style asynchronous silicon captures the same event-driven energy savings any hand-designed or STDP-trained spiking network would get. The GPU cost is a one-time training expense; the chip's energy advantage is a per-inference, ongoing property of the forward computation, which surrogate-gradient training never touches.

5. The backward recursion δ[t] = (local loss term) + δ[t+1]·(β - θ·g[t]) requires δ[t+1], which requires g[t+1], which requires u[t+1] — so computing any gradient requires having the entire forward trajectory available, all the way to the end of the sequence, before backpropagation can even begin. That means every membrane potential and spike from every one of the T timesteps must be held in memory simultaneously: O(T) memory. STDP updates a synapse using only its own pre- and post-synaptic spike timing in a short local window — nothing from outside that window is ever needed, so no unrolled trajectory is stored and the memory cost per synapse is O(1), independent of how long the sequence runs. That locality is precisely what lets STDP execute directly on-chip in real time, with no external memory for gradients, while BPTT-based training is run offline on a GPU.

6. Memory: as in question 5, BPTT must retain a state per timestep, so a 500-timestep sequence needs roughly 500× the activation memory of this chapter's 3-step example — for a real network with many neurons per layer and many layers, that becomes the binding constraint on batch size and sequence length during training, exactly as it is for long-sequence RNNs. Gradient pathology: the per-step multiplier (β - θ·g[t]) that appeared in every δ[t] computation above has magnitude well below 1 whenever g[t] is small (which is the common case — the surrogate is only large near threshold, as seen in this chapter's own g values ranging from about 0.4 to 0.96, and can be much smaller than that far from threshold). Multiplying hundreds of such factors together, as a 500-step unroll would, drives the product toward zero — a vanishing-gradient problem structurally identical to a vanilla RNN's, arising for the identical reason: repeated multiplication of a Jacobian with magnitude under 1 across many timesteps.

Training a Spiking Neuron: Exact Spikes Forward, Surrogate Gradient Backward FORWARD PASS — exact spikes (what the chip really executes) x1=1 x2=1 x3=1 ×w=0.7 ×w=0.7 ×w=0.7 u0 0 u1 0.700 u2 1.330 u3 0.897 ×β=0.9 ×β=0.9 ×β=0.9 Θ(u-1.0) Θ(u-1.0) Θ(u-1.0) s1 0 s2 1 s3 0 -1.0·s1 -1.0·s2 BACKWARD PASS — surrogate gradient (training-only, never runs on the chip) ∂L/∂s=0, g=0.712 ∂L/∂s=0, g=0.665 ∂L/∂s=-1, g=0.959 δ1 -0.042 δ2 -0.225 δ3 -0.959 ×(β-θ·g2)=×0.235 ×(β-θ·g1)=×0.188 ∂L/∂w = δ1·x1 + δ2·x2 + δ3·x3 = -0.042 - 0.225 - 0.959 = -1.226 forward: exact spikes (executes on real neuromorphic silicon) backward: surrogate gradient (exists only during training, on a GPU)

The diagram makes the asymmetry visible: the top half is what actually happens on a neuromorphic chip, spike by spike, and never involves a sigmoid anywhere. The bottom half exists only inside a training loop, only as a bookkeeping device for computing which direction to move each weight, and is thrown away the moment training stops. Confusing the two — thinking the smooth surrogate is part of what gets deployed — is the single easiest way to misunderstand why surrogate-gradient-trained SNNs still deliver the energy advantages neuromorphic hardware is built for.

Think About It

Think about this: How would you explain neuromorphic computing and spiking neural networks to a friend who has never seen a computer? What real-world analogy would you use? Imagine you had to build a system using these concepts — what would be your first step? Try this: before moving on, write down three things you learned and one question you still have.

Key Takeaways — Summary and Recap

Let us recap what we covered: the core ideas behind neuromorphic computing and spiking neural networks, how they connect to real-world applications, and why they matter for your journey in computer science. Remember these key points as you move forward. For competitive exam preparation (CBSE, JEE, BITSAT), focus on understanding the WHY behind each concept, not just the WHAT.

← AI Chip Design and GPU/TPU ArchitecturesQuantum Machine Learning and Quantum Computing →

Found this useful? Share it!

📱 WhatsApp 🐦 Twitter 💼 LinkedIn