-
Notifications
You must be signed in to change notification settings - Fork 0
Cookbook
Practical, copy-pasteable recipes for every shipped capability, organised by goal. Each recipe says what it is for, gives the exact command(s) (or a short Python snippet), shows the expected output/shape, and lists the caveats.
Every command below was executed against the repository at version 0.1.0
with the project virtualenv (venv/). Outputs are trimmed to the fields that
matter; … means "more keys follow". Where an optional SDK is not installed
here, the recipe shows the honest output the tool prints and the install line
that would change it.
The reasoning behind the deliberate boundaries (estimated energy, single-step ONNX, simulation-only attention, and so on) is in the README's Implications and boundaries section.
-
Interpreter. All Python commands use the project venv:
venv/bin/python. If you activated the venv, plainpythonworks too. -
Console scripts. Packaging installs one script per surface
(
venv/bin/spikeforge-verify,spikeforge-records,spikeforge-targets,spikeforge-hub,spikeforge-energy,spikeforge-benchmark). Several of these are only reachable as console scripts or as subcommands ofspikeforge-verify— see the note in §12.1. - JSON everywhere. Every CLI command prints indented JSON.
-
Exit codes are CI gates.
validate,deploy,roundtrip,run,onnx-roundtrip,hub import, andbenchmark --fail-on-regressionexit non-zero when their result is negative, so they can gate a pipeline. -
Offline-safe. The recipes use cached datasets (
build/) and synthetic fixtures; none requires network access except the optional hub download and the (optional) live Hugging Face search.
What it's for. The core install is deliberately lean; every capability beyond it is an opt-in extra with an isolated probe, so an absent package is reported, never raised at import.
Command.
# Core (torch, torchvision, snntorch, matplotlib, Pillow, numpy)
pip install -e ./packages/spikeforge
# The dashboard / WebSocket server (pulls core + the FastAPI stack)
pip install -e ./packages/spikeforge-server
# A representative "everything" install
pip install -e "./packages/spikeforge[dev,nir,events,onnx,norse,tracking,docs]"
pip install -e ./packages/spikeforge-hub
pip install -e ./packages/spikeforge-server
# Development (pytest, pytest-cov, ruff, jsonschema)
pip install -e "./packages/spikeforge[dev]"Extras matrix (declared in
packages/spikeforge/pyproject.toml):
The dashboard/WebSocket server is not a core extra; it is the
packages/spikeforge-server distribution. The model hub is its own
distribution too — packages/spikeforge-hub (import root spikeforge_hub) — and its
huggingface_hub dependency is a base dependency of that distribution rather
than a core hub extra.
| Extra | Packages | Enables | If absent |
|---|---|---|---|
nir |
nir, nirtorch
|
NIR export, interpretation, nirtorch extraction |
typed unavailable error |
events |
tonic |
Tonic event datasets (N-MNIST, DVS128 Gesture, CIFAR10-DVS, SSC) + event training | datasets reported unavailable; EventsExtraMissingError
|
onnx |
onnx, onnxruntime
|
ONNX export/import bridge | typed unavailable error |
norse |
norse |
real Norse simulator backend |
norse target available: false
|
lava |
lava-nc |
Lava/Loihi 2 backend path |
lava_loihi2 target available: false
|
tracking |
tensorboard |
TensorBoard sink | local manifest remains the default |
tracking-wandb |
wandb |
Weights & Biases sink | local manifest remains the default |
docs |
mkdocs-material |
the generated docs site |
build_docs.sh reports the gap |
Not executed here. The installs above need network access and are shown for reference;
nir,events,onnx, anddocswere already present in this environment, whilenorse,lava, andtrackingwere intentionally absent so the recipes below can show the honest degradation. Live hub search reports unavailable wheneverhuggingface_hubcannot be imported.
What it's for. Confirm which optional capabilities resolved, without importing a single optional package yourself.
Command.
venv/bin/python -c "import sys; print(sys.version)"
venv/bin/python -m spikeforge.cli.verify targets # backend SDKs
venv/bin/python -m spikeforge_hub.cli search fc # huggingface_hub
venv/bin/python -c "from spikeforge.tracking import sinks; print(sinks.describe('tensorboard'))"Expected output (this environment).
3.12.x …
# targets: reference available:true; norse/lava_loihi2/spinnaker2/speck/xylo available:false
# hub search: "available": false, "reason": "requires the `hub` extra (huggingface_hub)"
{'requested': 'tensorboard', 'active': False, 'reason': "sink 'tensorboard' backend is not installed"}
Caveats. A missing extra is always a reason, never an exception — that is the point of the probe pattern.
What it's for. Train the fully-connected LIF network on an image dataset without the dashboard.
Command.
from spikeforge import TrainingEngine
eng = TrainingEngine(
dataset="mnist", hidden=32, epochs=1, num_steps=5,
subset=128, batch_size=64, device="cpu",
)
last = None
for metrics in eng.train():
last = metrics
print(sorted(last.keys()))
print("loss=%.4f train_acc=%.3f test_acc=%s" % (
last["loss"], last["train_accuracy"], last["test_accuracy"]))Expected output.
['epoch', 'loss', 'step', 'test_accuracy', 'total', 'train_accuracy']
loss=1.8707 train_acc=-1.000 test_acc=52.775
Caveats.
- The accuracy key is
train_accuracy(notaccuracy);test_accuracyis a percentage and isnulluntil an evaluation step runs. -
train_acc=-1.000is the sentinel when the metric is not yet computed for that step, not a real score. - Downloading MNIST happens on first use; it is cached under
SPIKEFORGE_DATA_DIR(defaultbuild/). - Dataset normalisation to 28×28 is shared across all image datasets, so one architecture fits MNIST, Fashion-MNIST, KMNIST, QMNIST, USPS, EMNIST, and (grayscaled) CIFAR-10.
What it's for. Interactive training with live loss/accuracy charts, model management, and predictions.
Command.
# terminal 1 — FastAPI + WebSocket server on :8877
venv/bin/python -m server
# terminal 2 — Vite dev server (proxies /ws to :8877)
cd client && npm install && npm run dev # opens http://localhost:5173Or run both together:
scripts/dev.sh devHealth check (from another terminal):
curl -fsS http://127.0.0.1:8877/healthExpected output.
{"status":"ok"}
Caveats. The server can also serve a prebuilt client on a single port
(:8877) — see the README's Docker section. The Vite proxy target must match the
server port (client/vite.config.ts).
What it's for. Train on an event (neuromorphic) dataset. N-MNIST and the other event datasets load through Tonic; when Tonic is absent (or you ask for it explicitly) the loader serves a labelled synthetic stream.
Command (synthetic fallback — offline, no downloads).
from spikeforge.events.event_source import EventSampleSource
from spikeforge.events.event_bridge import EventSpikeBridge
from spikeforge.topology.registry import build_topology
from spikeforge.training.event_engine import EventTrainingEngine
src = EventSampleSource("n_mnist", synthetic_only=True)
print(src.origin, "|", src.description)
sample, label = src.load(0)
print("sensor:", sample.shape, "label:", label)
spec, module = build_topology("fc_legacy", {"num_classes": 10})
spikes, meta = EventSpikeBridge().encode(sample, spec)
print("bridged:", tuple(spikes.shape))
eng = EventTrainingEngine(dataset="n_mnist", synthetic_only=True, hidden=32,
epochs=1, num_steps=10, subset=32, batch_size=16,
device="cpu")
for metrics in eng.train():
pass
print("loss=%.4f test_acc=%s" % (metrics["loss"], metrics["test_accuracy"]))Expected output.
synthetic | synthetic n_mnist events on a 28x28 sensor (offline; no real recording)
sensor: (28, 28) label: 0
bridged: (10, 1, 784)
loss=2.3026 test_acc=12.5
Caveats.
- On the synthetic stream the accuracy is near chance (≈10–12% for ten classes); it is a wiring demo, not a benchmark.
- Without
synthetic_only=True, a missing Tonic raises the typedEventsExtraMissingErrorinstead of passing a synthetic stream off as a recording. Install the extra withpip install -e "./packages/spikeforge[events]". -
conv_netneeds a square single/dual-channel sensor matching its declared side; a feature-input topology (fc_legacy,fc_small,recurrent_net) accepts the flattened sensor. A mismatch raisesEventGeometryErrornaming both sides.
What it's for. See each spike coding, its firing rate/sparsity, and the (explicitly approximate) reconstruction.
Command.
from spikeforge.data.sample_source import SampleSource
from spikeforge.encoding.spike_encoder import SpikeEncoder
from spikeforge.introspection.encoding import encoding_report
img = SampleSource(dataset="mnist").image(0)
for coding in ("rate", "latency", "delta", "random"):
rep = encoding_report(img, SpikeEncoder(coding=coding, num_steps=10))
print(coding, "firing_rate=%.4f" % rep["firing_rate"],
"sparsity=%.4f" % rep["sparsity"],
"reconstruction_supported:", rep["reconstruction_supported"])Expected output.
rate firing_rate=0.1366 sparsity=0.8634 reconstruction_supported: True
latency firing_rate=0.1000 sparsity=0.9000 reconstruction_supported: True
delta firing_rate=0.1378 sparsity=0.8622 reconstruction_supported: True
random firing_rate=0.2532 sparsity=0.7468 reconstruction_supported: False
Caveats. The rate encoder is a stochastic Bernoulli sampler, so its
numbers vary run to run. Reconstructions are approximate and say so in the
report's approximation field: latency is quantised to integer steps and
saturates for sub-threshold pixels; delta is a lower bound exact only when each
step rises by exactly the threshold; random carries no image signal, so
reconstruction_supported is False and reconstruction is null.
What it's for. Execute a topology over a spike train in educational
mode (which records U[t]/I[t]/S[t]) and gather per-stage metrics.
Command.
from spikeforge.encoding.spike_encoder import SpikeEncoder
from spikeforge.data.sample_source import SampleSource
from spikeforge.introspection.metrics import trajectory_metrics
from spikeforge.runtime.execution_mode import ExecutionMode
from spikeforge.simulator import input_shape
from spikeforge.simulator.runner import run
from spikeforge.topology.registry import build_topology
img = SampleSource(dataset="mnist").image(0)
spikes = SpikeEncoder(coding="rate", num_steps=10).encode_image(img)
spec, module = build_topology("fc_legacy", {"num_classes": 10})
traj = run(module, input_shape.to_input_shape(spikes, spec),
mode=ExecutionMode.EDUCATIONAL)
print("logits:", tuple(traj.logits.shape), "spike stages:", list(traj.spikes))
print("metric stages:", list(trajectory_metrics(traj).keys()))Expected output.
logits: (1, 10) spike stages: ['_lif1', '_lif2']
metric stages: ['steps', 'stages']
Caveats. Production mode records nothing (the difference is recording
overhead, not behaviour); in production the trace dicts are empty. Metrics
include firing rate, sparsity, ISI stats, and a per-neuron histogram — all
plain JSON types (use trajectory_metrics(...)["stages"][stage] to reach them).
What it's for. Render a TopologySpec into a nir.NIRGraph and prove the
independent interpreter agrees with snnTorch. Both are CI gates.
Command.
venv/bin/python -m spikeforge.cli.verify export --topology conv_net
venv/bin/python -m spikeforge.cli.verify validate --topology conv_net
venv/bin/python -m spikeforge.cli.verify validate --topology sequence_mlpExpected output (validate, trimmed).
{ "…": "…",
"within_tolerance": true,
"readout": { "max_abs": 0.0, "agreement": 1.0 },
"worst": { "layer": "lif1", "quantity": "membrane",
"metric": "mean_abs", "value": 1.6e-07 } }validate exits 0 when within_tolerance is true. The membrane residual you
see is the documented Euler-vs-zero-order-hold difference, reported rather than
hidden.
Caveats. export --out FILE writes the graph summary (node/edge
inventory), not a re-loadable graph — see §5.3 for the ingestable form.
What it's for. sequence_mlp is built only from NIR-mappable kinds and
validates end to end; sequence_attn uses embedding/attention/normalisation
kinds the installed nir cannot represent.
Command.
venv/bin/python -m spikeforge.cli.verify validate --topology sequence_mlp
venv/bin/python -m spikeforge.cli.verify export --topology sequence_attnExpected output.
# sequence_mlp: exit 0, "within_tolerance": true
# sequence_attn: exit 1
{
"error": "stage kind 'embedding' cannot be mapped to NIR: the installed nir has no Embedding primitive, so a token lookup table cannot be represented; the stage is simulation-only",
"kind": "embedding"
}
Caveats. sequence_attn stays available for simulation and introspection
only; the typed UnsupportedStageError names the first unexportable stage.
What it's for. roundtrip persists a graph in a version-stamped JSON
envelope, reloads it, and compares — identical: true only on zero-error
structural equality. ingest runs an external envelope on the NIR interpreter.
Command.
# persist + reload + compare (also writes a loadable envelope with --out)
venv/bin/python -m spikeforge.cli.verify roundtrip --topology conv_net \
--out build/graph_envelope.json
# ingest the very file roundtrip wrote
venv/bin/python -m spikeforge.cli.verify ingest --file build/graph_envelope.jsonExpected output.
// roundtrip
{ "identical": true, "steps": 8, "readout": { "max_abs": 0.0, "agreement": 1.0 } }
// ingest
{ "path": "build/graph_envelope.json", "topology": "conv_net",
"steps": 8, "readout": [ 0.0, 0.0, "…" ],
"spike_nodes": [ "lif1", "lif2", "out" ] }Caveats. ingest expects the version-stamped envelope ({"format": "spikeforge-nir-graph", …}), not the export --out summary; feeding
it the summary fails with malformed graph file. To build an envelope in
Python:
from spikeforge.nir_bridge import to_nir, save_graph
from spikeforge.topology.registry import build_topology
spec, module = build_topology("conv_net")
save_graph(to_nir(spec, module), "build/api_graph.json")What it's for. Lift an arbitrary PyTorch module into NIR through nirtorch
and run it on the independent interpreter.
Command.
import torch
from torch import nn
torch.save(nn.Sequential(nn.Flatten(), nn.Linear(784, 10)), "build/lin.pt")venv/bin/python -m spikeforge.cli.verify extract --module build/lin.ptExpected output (trimmed).
{ "nodes": [ {"name": "input_1", "kind": "Input"},
{"name": "_0", "kind": "Flatten"},
{"name": "_1", "kind": "Affine"},
{"name": "output", "kind": "Output"} ],
"features": 784, "steps": 8, "runnable": true, "readout": [ "…" ] }Caveats. Only nn.Linear and nn.Flatten are mapped; any other module
raises the typed UnsupportedNodeError naming the class — no silent
truncation. Requires the nir extra.
What it's for. The bundled catalog renders fully offline and ships only
verified entries (a real source plus a concrete license); live Hugging Face
search is provided by the spikeforge-hub distribution (spikeforge_hub). Adding an entry
requires a real repository or reference and a verified license — see
spikeforge_hub/CURATION.md. The
on-demand downloader stays fully available for any vetted repository you choose
to add.
Command.
venv/bin/spikeforge-hub list --framework nir
venv/bin/spikeforge-hub list --available
venv/bin/spikeforge-hub search fc --limit 5Expected output (search, trimmed).
{ "query": "fc", "limit": 5,
"available": false,
"reason": "requires the `hub` extra (huggingface_hub)",
"results": [ { "id": "nir/fc_legacy", "framework": "nir",
"kind": "nir_graph", "license": "BSD-3-Clause",
"available": true, "cached": true }, "…" ] }Caveats. available: false here is about live HF search, not the curated
catalog (which is always browsable). A catalog entry whose license is the
"unverified-candidate" marker is also reported available: false with a named
reason, so an unverified candidate is never presented as ready.
What it's for. Materialize an artifact into the offline cache
(SPIKEFORGE_HUB_DIR, default build/hub).
Command.
venv/bin/spikeforge-hub download nir/conv_netExpected output.
{ "id": "nir/conv_net", "source": "bundled",
"status": "unverified", "verified": false,
"reason": "bundled artifact; materialization is deferred to import",
"sha256": null, "size_bytes": 0 }Caveats. A bundled graph publishes no checksum, so it verifies as
unverified rather than trusted — the honest label, not a failure. download
exits non-zero only when status == "failed" (unless --no-verify).
What it's for. Run the three-gate import funnel: structural inspect,
compatibility verdict, then weight load + drift check + promote into
MODEL_DIR.
Command.
venv/bin/spikeforge-hub inspect nir/fc_legacy
venv/bin/spikeforge-hub import nir/fc_legacy --topology fc_legacyExpected output (import, trimmed).
{ "id": "nir/fc_legacy", "kind": "nir_graph",
"verdict": { "verdict": "exact", "topology": "fc_legacy", "mismatches": [] },
"promoted": true,
"destination": "build/models/hub_nir_fc_legacy.pt",
"weights": { "loaded": true, "missing": [], "unexpected": [] },
"validation": { "within_tolerance": true } }Caveats. import exits non-zero on an incompatible verdict (printing the
named mismatches). A NIR-only artifact with no preset match is still runnable
through the reference interpreter, so import is useful without a weight mapping.
Use the HubPanel in the browser (venv/bin/python -m server). It drives
six WebSocket actions — hub_list, hub_search, hub_download, hub_cancel,
hub_inspect, hub_import — and renders entry cards, a compat badge, a
progress/cancel row, and an inline verdict that names every mismatch.
What it's for. Classify every node of a topology against a target into
supported / substituted / unsupported, with constraints and an optional
drift section. deployable is true only when the target is available and has
zero unsupported nodes.
Command.
venv/bin/spikeforge-verify deploy --topology conv_net --target reference # exit 0
venv/bin/spikeforge-verify deploy --topology conv_net --target xylo # exit 1Expected output (xylo, trimmed).
{ "…": "…",
"deployable": false,
"notes": [ "target SDK is not installed; run the enabling extra",
"validation compares snnTorch against the exported graph" ] }What it's for. Apply a target's declared substitutions and report what
changed (applied / skipped / unfixable) plus a post-rewrite drift check.
An unfixable primitive is named, never dropped.
Command.
venv/bin/spikeforge-verify rewrite --topology conv_net --target norseExpected output (trimmed).
{ "target": "norse", "applied": [], "skipped": [],
"unfixable": [ {"node": "lif1__reset_delay", "primitive": "Delay",
"reason": "no substitution declared"}, "…" ],
"ready": false, "counts": { "applied": 0, "skipped": 0, "unfixable": 3 },
"drift": { "within_tolerance": true } }Caveats. Two rules ship — IF→beta=0 LIF for norse and
AvgPool2d→SumPool2d+Scale for lava_loihi2. conv_net's neurons are
already LIF, so the IF rule does not fire here; the reset Delay nodes have
no declared substitution and are reported unfixable, leaving ready: false.
rewrite itself always exits 0 (its report is informational).
What it's for. Rewrite → (optionally) quantize → gate on availability → compile → run → compare to the reference interpreter.
Command.
venv/bin/spikeforge-verify run --topology conv_net --target reference # exit 0
venv/bin/spikeforge-verify run --topology conv_net --target norse # exit 1
venv/bin/spikeforge-verify run --topology conv_net --target lava_loihi2 # exit 1Expected output (norse, trimmed).
{ "target": "norse", "status": "unavailable", "steps": 0,
"notes": [ "install the 'norse' extra to run the 'norse' target" ],
"compare": null }Caveats. run exits non-zero unless status == "ok" and the comparison
is within tolerance. norse and lava_loihi2 execute once their extras are
installed; spinnaker2, speck, and xylo remain declarative placeholders.
No physical device is attached, so no hardware timing is measured.
What it's for. Restrict a graph's weight tensors to a target's declared scheme and read the per-layer before/after ranges plus the induced drift — and, on request, simulate what a fixed-point device does to the activations and membranes flowing through the network, so the drift figure covers more than the weights.
Command.
venv/bin/spikeforge-verify run --topology conv_net --target lava_loihi2 | \
python -c "import json,sys; d=json.load(sys.stdin); print(json.dumps(d['quantization'], indent=2))"Expected output (trimmed).
{ "target": "lava_loihi2", "scheme": "weight_int8", "applied": true,
"layers": [ { "node": "conv1", "primitive": "Conv2d",
"before": [ -0.3184, 0.3314 ], "after": [ -0.3183, "…" ] }, "…" ],
"counts": { "layers": 3 },
"drift": { "…": "…", "membranes": { "…": "…" }, "includes": [ "weights" ] },
"activation": { "scheme": "none", "applied": false,
"reason": "activation quantization is disabled" } }These figures reproduce: the fixture builds its module under its own seed, so running the command again prints the same numbers. They do come from torch's RNG stream, so a torch build whose stream differs will produce its own consistent set rather than these.
The activation block reports the target's declared activation_quantization,
which is none on every shipped target: the fixed-point widths a vendor's
neuron state actually uses are not verified in this repository, so no target
asserts one. Opt into a simulated scheme explicitly with deploy; the drift
check then runs the quantized graph through the reference interpreter under a
hook that snaps every computed node output, every carried membrane and the
membrane each step records onto an 8-bit grid calibrated on the drift fixture
itself:
venv/bin/spikeforge-verify deploy --topology conv_net --target lava_loihi2 \
--activation-quantization activation_membrane_int8 | \
python -c "import json,sys; q=json.load(sys.stdin)['quantization']; print(json.dumps(q['activation'], indent=2)); print(json.dumps(q['drift'], indent=2))"Expected output (trimmed). deploy exits 1 here because the Lava SDK
is not installed, as it always does for an undeployable target; the block is
produced either way. Each layer records the range before and after the snap
and the error the grid introduced, calibration.source says where the grid's
bounds came from, and drift.includes says which roundings the drift figure
now covers:
{ "scheme": "activation_membrane_int8", "bits": 8, "target": "both", "applied": true,
"layers": [ { "name": "conv1", "kind": "activation",
"before": [ -1.0968, 1.0960 ], "after": [ -1.0968, 1.0968 ],
"max_abs": 0.0043, "mean_abs": 0.0022 }, "…" ],
"counts": { "layers": 15, "steps": 8 },
"calibration": { "bits": 8, "target": "both", "samples": 144,
"source": "drift fixture", "ranges": { "…": "…" } } }
{ "steps": 8,
"readout": { "max_abs": 0.0000, "…": "…" },
"spikes": { "nodes": ["lif1", "lif2", "out"], "agreement": 0.9958, "…": "…" },
"membranes": { "nodes": ["lif1__mem", "lif2__mem", "out__mem"], "max_abs": 1.0080, "…": "…" },
"within_tolerance": false,
"includes": ["weights", "activation", "membrane"] }membranes is reported, not gated: a membrane can move without any spike
moving, so it is where a rounding shows first. Only the carried membrane
propagates into the next step — the recorded one is read by no node — so
snapping the recorded value moves this figure and no dynamics, and both are
the same register, reported under one <node>.membrane key. Pass a
Calibration folded from your own data
(activation_quant_graph.calibrate_graph) through
quantize(..., calibration=...) to fix the grid from a calibration dataset
instead of the fixture; bounds below the observed peak then clip, which the
report shows as after ranges pinned at the bound.
With --target reference (scheme none) the weight block is an honest
no-op:
{ "target": "reference", "scheme": "none", "applied": false,
"reason": "target declares no quantization", "layers": [] }Caveats. Simulated, not device-exact. Weights are restricted in the graph; activations and membranes are snapped after each node's floating update, so the threshold comparison still sees a float membrane and only what is carried is on the grid. No integer accumulation, accumulator overflow, per-channel schemes, or vendor kernel are modelled; the grid applies to the values a step produces, never to the arithmetic that produced them; without a supplied calibration the grid is fitted to the fixture, so it never clips on that fixture; a scheme requested without a spike fixture is reported unapplied, as is an unknown scheme. The source graph is never mutated, and executing a quantized graph still needs the target SDK. See Implications and boundaries §3.
What it's for. Count SOP/MAC/AC and timesteps, then map them onto a
per-target declared cost table. Without --sparse the report is the dense
baseline (sop == mac); with it, the event-driven reduction.
Command.
venv/bin/spikeforge-energy account --topology conv_net --target reference --sparse
venv/bin/spikeforge-energy account --topology conv_net --target reference # dense
venv/bin/spikeforge-energy report --topology conv_net --target reference --sparse # + parityExpected output (sparse, trimmed).
{ "target": "reference", "estimate": true, "basis": "declared cost table",
"timesteps": 8,
"ops": { "sop": 725112, "mac": 4641280, "ac": 351552 },
"efficiency": { "sop_over_mac": 0.1562 },
"energy": { "total_pj": 3977112.0, "dense_pj": 23557952.0 },
"latency": { "step_ns": 2000.0, "total_ns": 16000.0 },
"measured": null,
"notes": [ "estimate only; no device measured",
"declared cost source: Declared in-repo order-of-magnitude estimate …" ] }Dense run: "sop": 4641280 and "sop_over_mac": 1.0. report adds a
comparison block proving the sparse and dense readouts agree within tolerance.
Caveats. Every number is an estimate from a "measured": false table,
valid for comparing models/targets and sparse-vs-dense trade-offs, not for
power budgets. A target with no table reports basis: "unavailable" and null
numbers rather than a fabricated figure.
venv/bin/python -m spikeforge.benchmark --topology fc_small --steps 4 \
--repeats 1 --energy --energy-target referenceThe report's per-mode energy block carries the same report payload, so the
timing and the estimate travel together.
-
estimate: true,basis: "declared cost table"— the normal case. -
estimate: true,basis: "unavailable"— no table for that target. -
estimate: false,basis: "device measurement"— only when a device probe or an explicitmeasurement=reports one. There is a single integration point (account(source, target, measurement=...)) that flips this label, so adding a real device changes nothing else.
What it's for. Export a topology's single forward step with spec metadata, re-import it exactly, and (for foreign files) map a limited op set or fail by name.
Command.
venv/bin/spikeforge-verify onnx-export --topology conv_net --out build/model.onnx
venv/bin/spikeforge-verify onnx-import --file build/model.onnx
venv/bin/spikeforge-verify onnx-roundtrip --topology conv_netExpected output (export / roundtrip, trimmed).
{ "path": "build/model.onnx", "topology": "conv_net", "opset": 17,
"ops": [ "Add", "AveragePool", "Cast", "Clip", "Constant", "Conv",
"Flatten", "Gemm", "Greater", "Identity", "Mul", "Sub" ],
"nodes": 51, "input_shape": [ 2, 1, 28, 28 ],
"temporal": "single-step; the time loop stays in the simulator" }
// roundtrip: { "source": "metadata", "identical": true, … }Importing a foreign file (no metadata) maps only
Gemm/MatMul, Conv, Flatten, AveragePool, Dropout, and Identity:
# strip the bridge's metadata to simulate a third-party graph
venv/bin/python -c "import onnx; m=onnx.load('build/model.onnx'); [m.metadata_props.remove(p) for p in list(m.metadata_props)]; onnx.save(m,'build/model_nometa.onnx')"
venv/bin/spikeforge-verify onnx-import --file build/model_nometa.onnx
# exit 1: ONNX op 'Constant' has no faithful SNN stage mapping: no stage kind represents itCaveats. An exported ONNX file is not a complete temporal SNN — the
time loop stays in the simulator, so another runtime will not reproduce
multi-timestep dynamics. Neuron-internal ops (Greater/Sub/Clip) have no
SNN stage mapping and are rejected by name. Requires the onnx extra.
from spikeforge.network import model_store
from spikeforge.topology.registry import build_topology
spec, module = build_topology("fc_legacy", {"num_classes": 10})
model_store.save("cookbook_demo", module,
{"dataset": "mnist", "topology": "fc_legacy", "coding": "rate",
"input_mode": "rate", "device": "cpu"})venv/bin/spikeforge-records list --dataset mnist --topology fc_legacy
venv/bin/spikeforge-records manifest cookbook_demoExpected output (manifest, trimmed).
{ "name": "cookbook_demo", "available": false,
"manifest": { "available": false,
"reason": "no manifest stored (legacy checkpoint)",
"meta": { "dataset": "mnist", "topology": "fc_legacy" } } }Caveats. manifest reports available: false for a checkpoint saved
without a reproducibility manifest (a "legacy" card) rather than erroring. A
trained checkpoint written by the training engine does carry a manifest.
from spikeforge.tracking.config_hash import config_hash
from spikeforge.tracking.determinism import enable_deterministic
print(config_hash({"a": 1, "b": [2, 3]}) == config_hash({"b": [2, 3], "a": 1})) # True
print(enable_deterministic(seed=0).to_dict())Expected output.
True
{'enabled': True, 'seed': 0, 'applied': {'python': True, 'numpy': True,
'torch': True, 'cublas_workspace': True, 'cudnn_deterministic': True, …},
'notes': ['torch.use_deterministic_algorithms(warn_only=True) is set',
'hardware thread scheduling is reported, not enforced']}
Caveats. The hash is order-independent by construction (canonical JSON). Determinism narrows the bit-exactness gap; it does not close it.
from spikeforge.tracking import sinks
print(sinks.describe("tensorboard"))
print(sinks.describe("wandb"))Expected output (with the extras absent).
{'requested': 'tensorboard', 'active': False, 'reason': "sink 'tensorboard' backend is not installed"}
{'requested': 'wandb', 'active': False, 'reason': "sink 'wandb' backend is not installed"}
Caveats. The local manifest is always written first; an absent tracker
is a recorded reason, not an error. Install with pip install -e ".[tracking]" (TensorBoard) or ".[tracking-wandb]" (Weights & Biases).
Select a sink via TrainConfig.tracking (tensorboard / wandb, default
null).
SPIKEFORGE_METRICS_PERSIST=1 SPIKEFORGE_METRICS_DIR=build/cookbook_metrics venv/bin/python - <<'PY'
from spikeforge.observability import metrics, persistence
metrics.counter("train.steps", 3)
print(metrics.snapshot())
print(persistence.flush("cookbook").run_id)
print(persistence.status())
PYExpected output (trimmed).
{'counters': {'train.steps': 3.0}, 'gauges': {}, 'timers': {}}
cookbook
{'enabled': True, 'root': 'build/cookbook_metrics', 'run_id': 'default', 'last_flush': …}
Caveats. With SPIKEFORGE_METRICS_PERSIST unset, flush() is a no-op and
behaviour is unchanged. The registry is per-process and in-memory; persistence
does not aggregate across workers.
Two entry points exist and they are not always interchangeable:
| Need | Works | Does not work |
|---|---|---|
| verify family (export/validate/targets/deploy/rewrite/run/roundtrip/ingest/extract/onnx-*) |
venv/bin/spikeforge-verify … or venv/bin/python -m spikeforge.cli.verify …
|
— |
| records |
venv/bin/spikeforge-records … or venv/bin/spikeforge-verify records …
|
python -m spikeforge.cli.records_cli (no __main__; prints nothing) |
| targets/deploy/rewrite/run/extract |
venv/bin/spikeforge-targets … or venv/bin/spikeforge-verify …
|
bare python -m spikeforge_targets.cli.target_cli (needs a subcommand) |
| hub |
venv/bin/spikeforge-hub … or venv/bin/python -m spikeforge_hub.cli …
|
— |
| energy |
venv/bin/spikeforge-energy … or venv/bin/python -m spikeforge_targets.energy.cli …
|
— |
| benchmark |
venv/bin/spikeforge-benchmark … or venv/bin/python -m spikeforge.benchmark …
|
— |
What it's for. Store one JSON record per run and catch regressions over
time. --compare takes a stored run id (from --list), not a label.
python -m spikeforge.benchmark --topology fc_small --steps 8 --repeats 2 \
--save --label cookbook
python -m spikeforge.benchmark --list
python -m spikeforge.benchmark --compare <run-id> --against <run-id> --threshold 0.5Expected output (compare, trimmed).
{ "threshold": 0.5, "compared": 2,
"cases": [ { "topology": "fc_small", "mode": "production",
"metrics": [ { "metric": "ms_per_step", "baseline": 0.43,
"candidate": 0.78, "change_fraction": 0.81,
"regressed": true }, "…" ] } ] }Caveats. Wall-time noise on a shared runner moves metrics a few percent, so
use a threshold (CI default 10%). --fail-on-regression turns a regression
into a non-zero exit. Add --energy to attach the energy estimate (§9.2).
scripts/build_docs.sh # build into build/docs
scripts/build_docs.sh --check # fail on broken documentation linksExpected output (tail).
INFO - Documentation built in 0.52 seconds
==> checking documentation links
Caveats. Requires the docs extra. docs/ is generated from plans/ plus
the root README (and this cookbook and the open-source checklist) and is never
edited by hand.
venv/bin/python main.py # rate pipeline -> MP4/GIF/PNG/rasters in build/
venv/bin/python main_encodings.py # latency / delta / random demosBoth write into build/ (gitignored). ffmpeg is only needed for MP4 output.
scripts/dev.sh help # list every command
scripts/dev.sh check # ruff + pytest + client type-check + client build
scripts/dev.sh bench # benchmark suite (spikeforge-benchmark)
scripts/dev.sh dev # run the API and Vite dev server together
scripts/dev.sh data # show the dataset cache and sizesdocker compose up --build # CUDA image, dashboard on :8877
docker compose --profile cpu up --build # CPU-only torch (smaller image)Only one profile can own port 8877 at a time.
- 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