Repository navigation
interpreter spine plan
This is the detailed design for Phase 1 of the
ecosystem_roadmap.md. Read the roadmap first for phase dependencies and the full-picture context.
Design/spec only. No production code is changed by this document. Every claim about the current code is grounded in a file/line reference so a Code-mode agent can execute this file-by-file.
Scope of this phase: interpreter spine first — a configurable topology
builder (FC/conv/recurrent), adoption of the official nir + nirtorch
packages, NIR export, and snnTorch-vs-NIR numerical validation. Broad UI work
is deferred to Phase 3 of the roadmap.
The end goal is an ecosystem where neuromorphic programmers bridge libraries through a common interpreter. The interpreter's three primary functions are:
- Universal graph translation — ingest snnTorch models, map to standard graph primitives, handle skip/recurrent/multi-branch edges.
-
Dual-mode introspection + execution — educational per-timestep state
(
U[t],S[t],I[t]) vs. fast production execution. - Validation and numerical accuracy — compare snnTorch trajectories against the translated NIR graph and prove near-zero drift.
Of these, functions 1 and 3 are completely absent today; function 2 exists only for spike outputs. This plan builds all three around a single spine: one declarative model graph that emits both the snnTorch module and the NIR graph, plus an independent reference NIR executor used to validate the translation.
Only the parts of the snnTorch series that feed the interpreter are in scope.
| # | Tutorial | Verdict | Take | Skip |
|---|---|---|---|---|
| 1 | Spike Encoding | Done | Existing rate/latency/delta/random | n/a |
| 2 | Neuromorphic Datasets | Defer | Event-tensor ingestion (future) | Tonic/DVS pipelines now |
| 3 | Spiking Neural Networks | Partial | LIF + Synaptic + Lapicque neuron params and state semantics, reset mechanisms | Neuron cross-section derivations |
| 4 | Training SNNs | Partial | Surrogate-gradient config, regression/spike-count readouts | Dataset-specific tuning tables |
| 5 | Spiking CNNs | Core |
Conv2d, pooling, flatten stages and their NIR mapping |
Data-augmentation specifics |
| 6 | Recurrent SNNs | Core | Recurrent LIF (RLeaky), explicit delayed feedback edges |
Long BPTT training recipes |
| 7 | NIR | Core | Graph extraction, node mapping, reference simulation, drift validation | Hardware deployment runtimes now |
Tutorial 2 (event datasets) is the only tutorial deliberately deferred: it adds a data-ingestion axis, not a translation/validation axis, and can land after the spine is proven.
| Capability the goal requires | Current reality | Anchor |
|---|---|---|
| Configurable topology | One hardcoded 2-layer FC LIF, hand-written loop |
SpikingNet, forward_spikes()
|
| Conv / recurrent support | None | n/a |
| Neuron model library | Only snn.Leaky with beta
|
SpikingNet.__init__() |
| Graph representation / tracing | None; no fx, no IR | n/a |
| NIR dependency | Not present |
requirements.txt, setup.py
|
| NIR export | None | n/a |
| NIR execution / cross-check | None | n/a |
| Numerical validation | None | n/a |
| State introspection | Spikes only, no U[t]/I[t]
|
infer_spikes() |
| Educational vs production mode | Implicit via track flag only |
_pack() |
| Topology in checkpoints |
meta stores dataset/hidden/beta only |
save() |
| Tests | No tests/ directory exists |
repo root listing |
-
SpikingNet.forward(x, num_steps)andSpikingNet.forward_spikes(spikes, track)return contracts are consumed byTrainingEngine._train_batch(),predict(),inference.infer_spikes(), anddevice.warmup(). -
SpikingNetparameter names_fc1,_lif1,_fc2,_lif2are baked into savedstate_dicts; existing checkpoints inMODEL_DIRmust keep loading. - Properties
hidden,beta,num_classes,input_sizeare read bymodel_loaded_payload()and the device bench. -
main.py/main_encodings.pyexporters must keep running unchanged.
The spine has three movements: lift a snnTorch model to NIR, interpret the NIR graph independently, and validate the two against each other.
flowchart TD
S[TopologySpec graph] --> B[build_module]
S --> N[to_nir]
B --> T[snnTorch module]
T --> R[Generic temporal simulator]
R --> TR[Trajectory U S I per layer]
N --> G[NIRGraph]
G --> I[Reference NIR interpreter]
I --> NR[NIR trajectory]
TR --> V[Validator drift metrics]
NR --> V
V --> REP[ValidationReport]
B --> E[TrainingEngine]
S --> E
Core idea: the TopologySpec is the single source of truth. The snnTorch
module and the NIR graph are two renderings of the same spec, so their
structure cannot silently diverge. The independent NIR interpreter is what
makes the drift check meaningful rather than tautological.
-
One spec, two renderings.
TopologySpecemits bothbuild_module()(snnTorch) andto_nir()(NIR). No hand-maintained duplication. - Spec is a graph, not a list. Stages plus explicit edges, so residual skips, multi-branch inputs, and delayed recurrent feedback are first-class.
-
Per-step, stateless stages. Each stage is a pure function of
(input, state)returning(output, state). The temporal loop lives in one generic simulator, never in a model'sforward. This keeps the graph fx-traceable and NIR-mappable. - Independent interpreter. The NIR executor re-implements the emitted primitives from NIR parameters; it never calls back into the snnTorch stages. This is the only way drift detection is honest.
-
API isolation. All
nir/nirtorchimports live behindnir_bridge/api.py, so upstream API churn is contained to one module. -
Declarative-first export. Export builds NIR nodes directly from the
spec.
nirtorch.extract_nir_graphis the secondary path for ingesting arbitrary user modules, with unsupported nodes surfaced as errors. -
Legacy first, always.
SpikingNetbecomes a thin wrapper over thefc_legacypreset and keeps its parameter names, method contracts, and properties so old checkpoints and callers are unaffected. -
Modes are a flag, not a fork.
ExecutionModeselects trajectory capture; the code path is shared. -
Small modules. One class per file, files under 250 lines, functions
under 20 lines, 79-column lines, full type hints — per
rules.md.
New packages (each new class in its own file, keeping files small):
spikeforge/topology/
__init__.py
stage.py Stage: name, kind, params
spec.py TopologySpec: stages + edges; chain/residual/recurrent helpers
builder.py build_module(spec) -> torch.nn.Module
presets.py fc_legacy, fc_small, conv_net, recurrent_net
spikeforge/neurons/
__init__.py
registry.py NEURONS: name -> factory; build(name, **params)
leaky.py snn.Leaky factory + NIR parameter conversion
lapicque.py snn.Lapicque factory + conversion
synaptic.py snn.Synaptic factory + conversion
recurrent.py snn.RLeaky factory + conversion
spikeforge/simulator/
__init__.py
state.py NeuronState / StateMap containers
runner.py run(stages, edges, spikes, track) -> outputs or trajectories
trajectory.py Trajectory: per-layer S[t], U[t], I[t]
spikeforge/nir_bridge/
__init__.py
api.py isolated nir/nirtorch imports + capability check
mapper.py Stage kind <-> NIR node conversion table
exporter.py to_nir(spec) -> NIRGraph; graph_summary() -> JSON-able dict
interpreter.py execute(NIRGraph, spikes) -> Trajectory
drift.py error metrics between two trajectories
validator.py validate(model, spec, sample) -> ValidationReport
targets.py hardware target registry (metadata only)
Modified files:
| File | Change |
|---|---|
spiking_net.py |
Re-implement as a wrapper over the fc_legacy preset; keep _fc1/_lif1/_fc2/_lif2, forward, forward_spikes, properties |
inference.py |
Consume the simulator; add optional membrane trajectory without changing existing payload keys |
training_engine.py |
Build the configured topology; expose the spec |
checkpoint_mixin.py |
Persist the TopologySpec in meta
|
encoding_mixin.py |
Unchanged (encoder contract is stable) |
train_config.py |
Add topology: str and topology_params: dict
|
handlers.py |
Add nir_export / nir_validate dispatch and handlers |
server_message.py |
Add nir_graph / nir_validation types |
requirements.txt, setup.py
|
Add nir + nirtorch (an optional nir extra, on by default in Docker) |
New CLI module spikeforge/cli/verify.py exposes
python -m spikeforge.cli.verify export --topology conv_net and
... validate --topology conv_net --dataset mnist for headless use.
The mapper is the heart of translation. Exact nir constructor signatures are
isolated in api.py; the conversion contract is documented here and enforced
by tests.
| Stage kind | snnTorch source | NIR node | Conversion contract |
|---|---|---|---|
linear |
nn.Linear |
nir.Affine |
weight/bias copied directly |
conv2d |
nn.Conv2d |
nir.Conv2d |
weight/bias/stride/padding/dilation/groups copied |
flatten |
nn.Flatten |
nir.Flatten |
start/end dims copied |
pool |
nn.AvgPool2d / nn.SumPool2d
|
nir.AvgPool2d / nir.SumPool2d
|
kernel/stride/padding copied |
lif (subtract) |
snn.Leaky |
nir.LI + nir.Threshold + nir.Delay + nir.Scale
|
tau_mem = -dt/ln(beta); R = 1/(1-beta); the Threshold fires at v_threshold; the delayed spike is scaled by -threshold and summed back into the LI, reproducing snnTorch's one-step-delayed subtract reset exactly |
lif (zero) |
snn.Leaky |
nir.LIF |
hard reset to v_reset = 0 with the same tau/R relation; exact because a zero reset is a hard reset |
li |
snn.Lapicque |
nir.LI + nir.Threshold (+ reset chain) |
same tau/R relation; snnTorch's Lapicque uses a first-order Euler update, not zero-order hold, so this carries a small quantified residual |
synaptic |
snn.Synaptic |
nir.CubaLIF |
second-order decay chain; alpha/beta -> tau_syn/tau_mem; w_in = 1/(1-alpha), R = 1/(1-beta); the subtract reset carries a residual |
recurrent |
snn.RLeaky |
nir.LIF + nir.Delay
|
feedback edge with a one-step delay |
skip |
residual add | graph edge with weight 1 into a merge node | explicit edges in the spec |
The dt used for the beta <-> tau_mem relation is one timestep per step
(dt = 1). The reference interpreter solves the continuous NIR neuron with
the exact zero-order-hold (exponential) form, whose input gain is
R * (1 - beta); the contract therefore sets R = 1 / (1 - beta) so that
gain is exactly one and the discrete recurrences coincide. A naive R = 1
would introduce a (1 - beta) mismatch.
Correction (Phase 1d, empirical). The installed snntorch 1.0.0 defaults
Leaky(reset_delay=True): the reset at step t is triggered by the previous
step's spike, so mem[t] = beta*mem[t-1] + I[t] - threshold*spk[t-1]. A NIR
LIF hard reset stores a constant v_reset and cannot retain that residual,
so subtract is modelled faithfully as the LI/Threshold/Delay/Scale
loop above instead of a LIF. zero and none resets remain single-node
(nir.LIF and nir.LI + nir.Threshold). The validator confirms exact spike
trains and readouts, with only float32 membrane rounding (~1e-6) for the four
presets.
nir_bridge/validator.py runs the same input spikes through both the
snnTorch module and the reference NIR interpreter and compares trajectories.
-
Inputs compared per layer: spike train
S[t], membraneU[t], and the final readout (accumulated output spikes and logits). - Metrics per layer and quantity: max absolute error, mean absolute error, relative error, and spike-event agreement fraction.
- Tolerances are per-quantity constants (spikes exact-match expected; membrane and readout within a small epsilon).
-
Report:
ValidationReportwith per-layer results, an overallwithin_toleranceboolean, and a human-readable summary of the worst offender — this is the artifact a professional gates hardware deployment on.
Validation is deliberately forward-only (no gradients), because surrogate gradients are training artifacts and are validated separately by the training tutorial's parity checks.
Add topology/ and neurons/; define Stage, TopologySpec, build_module,
and presets fc_legacy/fc_small/conv_net/recurrent_net.
Acceptance: each preset builds a module; fc_legacy reproduces the current
SpikingNet parameter names and a forward pass matches the current net
numerically on the same weights.
Add simulator/; make SpikingNet a wrapper over fc_legacy; route training,
inference, and device warmup through the simulator.
Acceptance: main.py and main_encodings.py still run; existing
checkpoints load and produce identical outputs; inference.infer_spikes
payload keys are unchanged.
Add nir/nirtorch; implement api.py, mapper.py, exporter.py.
Acceptance: to_nir emits a NIRGraph for FC, conv, recurrent, and
skip topologies with the expected node kinds and edges; graph_summary
returns a JSON-able node/edge listing.
Implement interpreter.py for the emitted primitives only.
Acceptance: a hand-built single LIF step matches an analytic step; an
identity Affine + LIF graph matches the snnTorch module to within 1e-6.
Add drift.py and validator.py.
Acceptance: validate reports within_tolerance=True for the shipped
presets on a fixed seed; deliberately perturbing a mapped parameter flips it
to False and names the offending layer.
Thread topology through TrainConfig, TrainingEngine, and checkpoint meta;
add nir_export / nir_validate WS actions and payload schemas.
Acceptance: a topology-trained checkpoint records its spec in meta and
reloads into the matching build; the two WS actions return graph and report
payloads over a live connection.
Add a tests/ suite; run ruff and pytest.
Acceptance: tests cover mapping, export, interpreter, drift, and legacy
checkpoint compatibility; ruff clean; all Python files under 250 lines and
functions under 20 lines.
-
NIR API churn:
nir/nirtorchsignatures may differ from the contract above;api.pyisolates this and M3 begins with a capability probe. -
Neuron semantics:
snn.Leakyreset/threshold conventions vs. NIR LIF must be pinned down empirically; the validator is the safety net. -
fx tracing of loops: avoided by design — per-step stages plus a generic
loop mean no model
forwardcontains aforloop. - Deferred: event-dataset ingestion (Tutorial 2), hardware target compilation beyond metadata, broad UI panels for graph/validation views, and long-sequence BPTT optimizations.
Because broad UI is deferred, the spine ships as a Python API plus a CLI and
two server actions. The follow-up milestone (not in this plan) adds a graph
view, a membrane-voltage trajectory panel, and a validation/drift panel to the
React dashboard, reusing the existing raster/inference wiring.
- Home
- Architecture
- Backend Execution
- Benchmarks
- Dashboard
- Development
- Event Datasets
- Event Runtime And Energy
- Features
- Implications And Boundaries
- Interop Foldins
- Interpreter Spine
- Introspection
- Model Deployment
- Model Hub
- Notes
- Operational Maturity
- Production Workflows
- Project Layout
- Quickstart
- Requirements
- Sequence Primitives
- Streaming Timeseries
- Targets And Interop
- Usage
- Arch 0001 Adr Repo Topology
- Arch 0001 Core Boundary
- Arch 0001 Decision Metrics
- Arch 0001 Migration Plan
- Arch 0001 Packaging Versioning
- Arch 0001 Protocol Contract
- Arch 0001 Risk Register
- Arch 0001 Target Topology
- Backend Execution Plan
- Ecosystem Listings
- Ecosystem Roadmap
- Event Runtime Plan
- Hub Expansion Plan
- Plans
- Interop Foldins Plan
- Interpreter Spine Plan
- Memory System Research
- Model Hub Plan
- Operations Plan
- Production Toolkit Plan
- Production Use Cases
- Professional Roadmap
- Repo Topology Plan
- Sequence Primitives Plan
- Use Case Audio Keyword Spotting
- Use Case Biosignal Medical Monitoring
- Use Case Computational Neuroscience
- Use Case Edge Power Budgets
- Use Case Event Camera Vision
- Use Case Intrusion Anomaly Detection
- Use Case Low Latency Sensor Stream
- Use Case Rl Control Robotics
- Use Case Spiking Transformers
- Use Case Streaming Timeseries