← Home

Gradient Stabilisation

ar015 · 12 June 2026 · pdf

Conductance-based spiking networks (COBA, PING) need one extra ingredient to train at all: a single flag, --v-grad-dampen. This article is the deep dive on why it is needed and what it does, derived from the discrete equations the code runs. For the surrounding recipe — BPTT, the spike surrogate, the loss, optimiser, readout, and regulariser — see the Training article; this one assumes that background.

In plain English. The E and I cells form a loop that makes the network oscillate at gamma (≈25 Hz). Training sends the gradient backward around that same loop, and each time a neuron crosses its spike threshold the surrogate hands the gradient a large multiplier. The loop is traversed once per gamma cycle — only about five times in a 200 ms trial — but each pass multiplies by a factor well above one (the surrogate slope enters squared), so a handful of passes is enough to overflow to NaN. The forward simulation stays bounded because the spike reset clamps each cell every cycle; the gradient sidesteps that reset, so forward stability buys nothing backward. The fix, --v-grad-dampen, divides the voltage gradient by a constant 𝛾 at every step, shrinking the loop’s multiplier below one — and thanks to a straight-through trick it changes only the gradient, never the simulation. The rest of this article proves each of those claims from the discrete equations.

Naive BPTT through a 2000-step COBA/PING trial reaches NaN within a few batches. This article establishes the failure and its remedy from the discrete equations the code runs — no step omitted. The plan is: write the one-step map, differentiate it exactly (Lemma 1), propagate the gradient backward (the recursion), show the recurrent loop forces that gradient to diverge geometrically (Proposition 1), and show that scaling the per-step voltage gradient by 1/𝛾 makes the loop a contraction while leaving the forward trajectory untouched (Proposition 2).

Setup: the discrete update

Index the timesteps 𝑡=0,,𝑇 at Δ𝑡=0.1 ms. Each cell holds a state (𝑉𝑡,𝑔𝑒𝑡,𝑔𝑖𝑡). With reversal potentials 𝐸𝐿,𝐸𝑒,𝐸𝑖, capacitance 𝐶𝑚, leak 𝑔𝐿, synaptic decays 𝛽𝑒=𝑒Δ𝑡/𝜏𝑒 and 𝛽𝑖=𝑒Δ𝑡/𝜏𝑖, threshold 𝜗 and reset 𝑉𝑟, the step is exactly (lif_step_expeuler, exp_synapse):

𝑠𝑡=Θ(𝑉𝑡𝜗)𝜒𝑡,(S)𝑔𝑒𝑡+1=𝛽𝑒(𝑔𝑒𝑡+𝑊𝑒𝑠pre𝑡),𝑔𝑖𝑡+1=𝛽𝑖(𝑔𝑖𝑡+𝑊𝑖𝑠pre𝑡),(C)𝑉̃𝑡+1=𝑉𝑡+1+(𝑉𝑡𝑉𝑡+1)𝛼𝑡+1,𝑉𝑡+1={𝑉𝑟if 𝑠𝑡=1 or refractory𝑉̃𝑡+1otherwise,(V)

where 𝜒𝑡{0,1} is the not-refractory gate, and the membrane decay and rest point are built from the post-update conductances 𝑔𝑡+1:

𝑔tot=𝑔𝐿+𝑔𝑒𝑡+1+𝑔𝑖𝑡+1,𝛼𝑡+1=𝑒Δ𝑡𝑔tot/𝐶𝑚,𝑉𝑡+1=𝑔𝐿𝐸𝐿+𝑔𝑒𝑡+1𝐸𝑒+𝑔𝑖𝑡+1𝐸𝑖𝑔tot.(P)

The timing matters and is taken from the code: the spike 𝑠𝑡 in (S) is read from the current voltage 𝑉𝑡, drives the conductance one step later in (C), and that conductance sets the voltage in (V). So one synaptic edge — presynaptic voltage to postsynaptic voltage — spans one timestep.

Lemma 1 — the one-step Jacobian

Differentiate (S), (C), (V) entry by entry. The spike surrogate gives the spike derivative

𝜕𝑠𝑡𝜕𝑉𝑡=𝜎(𝑉𝑡𝜗)𝜒𝑡,𝜎(𝑢)=𝑘(1+𝑘|𝑢|)2,𝜎(0)=𝑘.(1)

From (C), the conductance partials are a self-decay and a presynaptic-voltage coupling obtained by chaining (1):

𝜕𝑔𝑒𝑡+1𝜕𝑔𝑒𝑡=𝛽𝑒,𝜕𝑔𝑒𝑡+1𝜕𝑉pre𝑡=𝛽𝑒𝑊𝑒𝜎(𝑉pre𝑡𝜗)𝜒pre𝑡.(2)

From (V), with 𝑔 held, the membrane self-term is the decay; and differentiating (P) gives the conductance-to-voltage term (𝜕𝑔tot/𝜕𝑔𝑒=1, 𝜕𝑉/𝜕𝑔𝑒=(𝐸𝑒𝑉)/𝑔tot, 𝜕𝛼/𝜕𝑔𝑒=(Δ𝑡/𝐶𝑚)𝛼):

𝜕𝑉̃𝑡+1𝜕𝑉𝑡=𝛼𝑡+1,𝜕𝑉̃𝑡+1𝜕𝑔𝑒𝑡+1=(1𝛼)𝐸𝑒𝑉𝑔totrest shiftsΔ𝑡𝐶𝑚𝛼(𝑉𝑡𝑉)decay shifts𝜅𝑒Δ𝑡𝐶𝑚(𝐸𝑒𝑉),(3)

the last step being the leading order in Δ𝑡 (using 1𝛼Δ𝑡𝑔tot/𝐶𝑚). Define 𝜅𝑖 identically with 𝐸𝑖. Collecting (1)–(3), the one-step Jacobian on (𝑉,𝑔𝑒,𝑔𝑖), with the cross-cell coupling in the lower-left block, is

𝐽𝑡=𝜕(𝑉,𝑔𝑒,𝑔𝑖)𝑡+1𝜕(𝑉,𝑔𝑒,𝑔𝑖)𝑡=(𝜌𝛼𝜅𝑒𝜅𝑖𝛽𝑒𝑊𝑒𝜎(𝑉pre𝜗)𝜒pre𝛽𝑒0𝛽𝑖𝑊𝑖𝜎(𝑉pre𝜗)𝜒pre0𝛽𝑖).(J)

The single subtlety, and it is decisive below: the reset in (V) is a torch.where, so on any cell that spikes or is refractory the output is the constant 𝑉𝑟 and the membrane self-term is gated to zero. We write this as the factor 𝜌{0,1} on the 𝜕𝑉𝑡+1/𝜕𝑉𝑡 entry: 𝜌=0 at a spike (gradient through the cell’s own membrane is cut), 𝜌=1 otherwise. Crucially 𝜌 multiplies only the membrane self-term — it does not touch the spike output 𝑠𝑡 in (1)–(2), which is evaluated at 𝑉𝑡 before the reset.

Backpropagation: the gradient recursion

Let 𝜆𝑡𝜕ℒ︀/𝜕(𝑉,𝑔𝑒,𝑔𝑖)𝑡. Reverse-mode autodiff is exactly the linear recursion

𝜆𝑡=(𝐽𝑡)𝜆𝑡+1,𝜆𝑇=𝑥𝑇ℒ︀𝜆𝑡=(𝑠=𝑡𝑇1𝐽𝑠)𝜆𝑇.(4)

So the gradient that reaches step 𝑡 is governed by the product of one-step Jacobians, and 𝜆𝑡(𝑠𝐽𝑠)𝜆𝑇. Whether this is benign or catastrophic is decided by the voltage component of (J) chained through the recurrent wiring.

Proposition 1 — the backpropagated gradient diverges (the problem)

Claim. In a network with a recurrent E→I→E loop, the voltage gradient grows geometrically in the number of gamma cycles traversed — not in the number of timesteps. A 200 ms trial holds only 𝑁5 cycles against 𝑇=2000 steps; the danger is that each cycle multiplies by a large factor, not that there are many cycles.

Proof. Compose (2) and (3): the gradient carried from a postsynaptic voltage at 𝑡{+}1 to a presynaptic voltage at 𝑡 across one synapse is the product of the conductance-coupling and the membrane term,

𝑎𝜕𝑉post𝑡+1𝜕𝑉pre𝑡=𝜅Δ𝑡𝐶𝑚(𝐸syn𝑉)𝛽𝑊synapse𝜎(𝑉pre𝜗)spike.(5)

Traverse the loop once: 𝑉𝐸𝑉𝐼 across 𝑊𝑒𝑖 (edge gain 𝑎𝑒𝑖), then 𝑉𝐼𝑉𝐸 across 𝑊𝑖𝑒 (edge gain 𝑎𝑖𝑒). Over one round trip the 𝐸-voltage gradient maps to itself with the loop gain

𝜚=𝑎𝑒𝑖𝑎𝑖𝑒=𝑘2two 𝜎 at volley𝛽𝑒𝛽𝑖𝑊𝑒𝑖𝑊𝑖𝑒𝜅𝐼𝜅𝐸.(6)

The factor 𝑘2 appears once per gamma cycle. Each population fires a single synchronous volley per cycle, so the two large kicks 𝜎𝜎(0)=𝑘 (one for 𝐸, one for 𝐼) occur together once per loop traversal and nowhere else; between volleys the per-step Jacobians are the mild sub-unit decays 𝛼,𝛽<1, which merely carry the gradient along without amplifying it. So (4) collapses, across cycles, to the scalar recursion 𝜆𝑉,𝐸(𝑛)𝜚𝜆𝑉,𝐸(𝑛+1), giving after 𝑁 cycles

|𝜆𝑉,𝐸||𝜚|𝑁|𝜆𝑉,𝐸𝑇|.(7)

Worked example (default PING init). Take the released constants: Δ𝑡=0.1 ms, 𝑘=5, 𝐶𝑚𝐸=1.0 nF, 𝐶𝑚𝐼=0.5 nF, 𝐸𝑒=0, 𝐸𝑖=80 mV, 𝑉𝜗=50 mV at the crossing, 𝜏AMPA=2 ms (𝛽𝑒=𝑒0.050.95), 𝜏GABA=9 ms (𝛽𝑖0.99), and order-µS coupling 𝑊𝑒𝑖1, 𝑊𝑖𝑒2 µS. The two edge gains (5) are

𝑎𝑒𝑖=0.10.5(0(50))𝜅𝐼=100.951𝛽𝑒𝑊𝑒𝑖5𝑘48,𝑎𝑖𝑒=0.11.0(80(50))𝜅𝐸=30.992𝛽𝑖𝑊𝑖𝑒5𝑘30,

so the loop gain is 𝜚=𝑎𝑒𝑖𝑎𝑖𝑒1.4×103. Over the 𝑁5 cycles of a 200 ms trial — not the 𝑇=2000 steps — the voltage gradient is amplified by

|𝜚|𝑁(1.4×103)56×1015.

A unit gradient seeded at the readout thus returns to 𝑡=0 scaled by 1016; summed over the batch and compounded across successive optimiser steps it crosses the fp32 ceiling (3.4×1038) and the loss becomes NaN within a few batches. The blow-up is robust: even if threshold spread cuts the effective 𝜎 tenfold (each edge ÷10, so 𝜚÷100), |𝜚|14 and |𝜚|56×105 — still divergent. The compounding is per cycle, not per step. ∎

Why the forward pass does not blow up the same way. The forward orbit is bounded because the reset (V) slams each spiking cell to 𝑉𝑟 every cycle — that is the 𝜌=0 gate in (J), a strong per-cycle contraction. But 𝜚 in (6) is built only from the spike-output edges (5), i.e. from 𝜎 evaluated before the reset; the gate 𝜌 sits on the membrane self-term and never enters the loop product. So the backward loop bypasses precisely the contraction that bounds the forward orbit. Forward stability and backward divergence are not in contradiction: they travel different paths through (J), and the straight-through reset is what separates them.

Proposition 2 — per-step voltage-gradient damping bounds it (the solution)

The flag --v-grad-dampen inserts, before the reset, the operation dv = _scale_grad(dv, 1/γ) where 𝑑𝑣=(𝑉𝑉)(1𝛼) is the membrane increment in (V). The primitive

_scale_grad(𝑥,𝑐)=𝑐𝑥+detach(𝑥)(1𝑐)(8)

is the identity in the forward pass (𝑐𝑥+(1{}𝑐)𝑥=𝑥) but multiplies the backward gradient through 𝑥 by 𝑐=1/𝛾. Re-differentiating (V) with 𝑑𝑣 so scaled changes two partials of (J):

𝜕𝑉𝑡+1𝜕𝑉𝑡=11𝛼𝛾[𝛼,1],𝜕𝑉𝑡+1𝜕𝑔𝑡+1=𝜅𝛾.(9)

The membrane self-term stays bounded by 1 (for 𝛾 it tends to 1 — the slow integration pathway is preserved, not crushed), while every voltage←conductance edge — and therefore every loop edge (5) — is divided by 𝛾. The loop gain (6) becomes

𝜚𝛾=𝑎𝑒𝑖𝛾𝑎𝑖𝑒𝛾=𝜚𝛾2,(10)

so the backward loop is a contraction, |𝜚𝛾|1, as soon as

𝛾|𝜚|.(11)

By (8) the forward trajectory 𝑥𝑡 is bitwise identical with or without the flag, so this is a pure modification of the gradient, not of the dynamics. ∎

In code this is the single line _scale_grad(dv, 1.0 / v_grad_dampen) in the LIF step. Since |𝜚|𝑘2𝑐 for an order-unity loop constant 𝑐, the threshold (11) scales like |𝜚|𝑘𝑐, consistent with the recipes: 𝛾80 for the unitless standard SNN and 𝛾1000 for COBA/PING (used by exp025 and downstream), whose larger conductance-scale loop constant demands the larger 𝛾.

Corollary — the cost, and choosing γ

The 1/𝛾 in (9) lands on every voltage←conductance gradient, not only the recurrent loop. The feedforward input also enters through 𝑔𝑒 (the term 𝑊in𝑠in in (C)), so the input-weight gradient is suppressed by the same factor: damping trades a slice of the legitimate learning signal for stability. Hence the operating rule — take the smallest 𝛾 satisfying (11), i.e. the smallest value that prevents overflow. Too large a 𝛾 can therefore quietly cap achievable accuracy by starving the input layer of gradient. Distinct from gradient clipping, which rescales the assembled parameter gradient after the fact: damping reshapes the recursion (4) term by term, before any parameter gradient is formed. Tightening (11) per-layer on long-trial tasks is open work.

Prediction. The mechanism implies the flag is load-bearing only when the loop exists: the same network should train with damping fully off when run as COBA (𝑊𝑒𝑖=0, loop open), but fail as PING (𝑊𝑒𝑖>0) — every optimiser step’s gradient going non-finite and being skipped, leaving the network frozen at chance.