<p align="center">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/neuro-kitchen/dsp-kitchen/main/assets/logo/neuro-kitchen-lockup-on-dark.svg">
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/neuro-kitchen/dsp-kitchen/main/assets/logo/neuro-kitchen-lockup-on-light.svg">
<img alt="neuro-kitchen" width="360" src="https://raw.githubusercontent.com/neuro-kitchen/dsp-kitchen/main/assets/logo/neuro-kitchen-lockup-on-light.svg">
</picture>
</p>
# dspk
> [!WARNING]
> **Early development (0.0.x).** APIs change between releases.
**[Documentation](https://neuro-kitchen.github.io/dsp-kitchen/)** · [Python API](https://neuro-kitchen.github.io/dsp-kitchen/reference/python/) · [Rust API](https://docs.rs/dspk) · [Changelog](https://neuro-kitchen.github.io/dsp-kitchen/changelog/)
Multi-channel signal processing written once in [CubeCL](https://github.com/tracel-ai/cubecl) and
run on any GPU (wgpu: Vulkan, Metal, DirectX 12; CUDA; ROCm / HIP) or the CPU, with the device
chosen at run time. Processing stages keep data on the device between them, so a recording
crosses disk → RAM → device memory as few times as possible.
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/neuro-kitchen/dsp-kitchen/main/assets/img/dsp-app_main-darkmode.png">
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/neuro-kitchen/dsp-kitchen/main/assets/img/dsp-app_main-lightmode.png">
<img alt="dspk-app: 385 channels of a Neuropixels recording as traces and a heatmap, with the channel list, view settings and timeline" src="https://raw.githubusercontent.com/neuro-kitchen/dsp-kitchen/main/assets/img/dsp-app_main-darkmode.png">
</picture>
*`dspk-app` (desktop workbench, `cargo install dspk-app`): a Neuropixels recording (385 channels, 30 kHz)
drawn from min/max pyramids built by `dspk-view`.*
## Crates and folders
| `crates/` | `dspk` | Entry crate: re-exports the crates below, features choose what is included | crates.io |
| | `dspk-core` | Recordings, buffers, exact time and sample rates, windows, compute targets | crates.io |
| | `dspk-io` | Raw binary and Zarr recordings, format detection, chunk cache | crates.io |
| | `dspk-ops` | Filters, resampling, spatial operators, linear algebra, statistics, peaks, device pipelines | crates.io |
| `engine/` | `dspk-pipeline` | Pipelines as data (JSON / TOML specs): plan, many inputs, merges, provenance | crates.io |
| `serve/` | `dspk-view` | Min/max envelopes and pyramids for viewing, local or remote | crates.io |
| | `dspk-stream` | Network sessions for continuous signals (QUIC + TLS, protobuf) | crates.io |
| `synapse/` | `dspk-synapse` | Electrophysiology: NWB, SpikeGLX, mtscomp, Phy formats, probes, detection, clustering, matching, metrics, sorter building blocks, provenance | crates.io |
| | `dspk-synapse-ml` | Pretrained models (DARTsort, SpikeNet2, UnitRefine) and their runtime | crates.io |
| `synapse/sorters/` | `dspk-sorter-kilosort` | Kilosort4 and EMUsort (features `ks4`, `emusort`; GPL-3.0) | crates.io |
| | `dspk-sorter-ms5` | MountainSort 5, with isosplit6 (Apache-2.0) | crates.io |
| | `dspk-sorter-si` | SpikeInterface's SpyKING CIRCUS 2 and Tridesclous 2 (features `sc2`, `tdc2`) | crates.io |
| `hub/` | `dspk-hub` | Verified downloads of published artifacts, with data catalogs per domain (feature `synapse`) | crates.io |
| `app/` | `dspk-cli` | The `dspk` command line: `dspk run`, `dspk sort`, `dspk doctor`, … | built from source |
| | `dspk-cli-sorter` | Every sorter in one program (GPL-3.0), run by `dspk sort` | GitHub release |
| | `dspk-app` | Desktop workbench: Explore, Pipeline, Sorting, Curation ([README](app/dspk-app/README.md)) | crates.io |
| | `dspk-app-core` | The app's core: any multichannel signal, the session, the plugin interface | crates.io |
| `plugins/app/` | `dspk-app-synapse` | The app's electrophysiology plugin: probes, sortings, Sorting and Curation | crates.io |
| `bindings/python/` | `dspk` | The Python package | PyPI |
| `playground/` | | Worked examples as `# %%` cells and pipeline specs ([README](playground/README.md)) | |
| `benchmarks/` | | Timings and validations against scipy and other sorters ([README](benchmarks/README.md)) | |
Every crate builds with wgpu, CUDA and the CPU runtime by default. Development and tests use one
profile:
```bash
cargo build --workspace --profile validate
cargo test --workspace --exclude dspk-app --exclude dspk-py --profile validate
```
Each kernel is tested on every runtime compiled in (`DSPK_TEST_RUNTIMES=cuda` limits a run to one).
Timings against scipy and kernel benchmarks: [benchmarks](benchmarks/README.md).
## Runtimes
Runtimes are crate features; the device is picked at run time with `dspk::core::ComputeTarget`
(or the `DSPK_RUNTIME` environment variable) among those compiled in.
```toml
[dependencies]
dspk = "0.0.9" # wgpu, cuda and cpu (default)
# dspk = { version = "0.0.9", default-features = false, features = ["wgpu", "zarr"] } # wgpu only
```
| `wgpu` (default) | WebGPU: Vulkan, Metal, DirectX 12 | NVIDIA RTX 2070 Max-Q, Intel UHD 630 (Vulkan) |
| `cuda` (default) | NVIDIA CUDA | NVIDIA RTX 2070 Max-Q |
| `cpu` (default) | CubeCL CPU runtime (MLIR / LLVM) | x86-64 |
| `hip` | AMD ROCm / HIP | compiles; **untested** (no AMD GPU available) |
CUDA and HIP are loaded at run time, so building with them needs no toolkit installed. The CPU
runtime downloads a prebuilt LLVM on its first build (cached in `~/.local/share/tracel`); leave it
out with `default-features = false` for a lighter build. All three are the default because the
library, its tests, the CLI and the Python package are built and tested together that way.
### Performance
Device time of one recording-sized block (384 channels × 60 000 samples, float32: 2 s of
Neuropixels at 30 kHz), data already on the device, autotuned kernel; RTX 2070 Max-Q and an
i7-9750H (6 cores, 12 threads) through CubeCL's CPU runtime:
| IIR band-pass, 5th order, forward | 5.3 ms | 12.3 ms | 39 ms |
| FIR, 63 taps, causal | 3.1 ms | 6.4 ms | 33 ms |
| Running median, width 9 | 0.59 ms | 0.69 ms | 20 ms |
| Common median reference | 10.3 ms | 16.5 ms | 239 ms |
| Common average reference | 1.0 ms | 1.2 ms | 11 ms |
| `resample_poly` 1 / 3 | 1.4 ms | 1.7 ms | 16 ms |
Where a kernel has several exact implementations (direct, shared-memory tiles, sorting networks,
vectors of 2–16 values per unit that the CPU runtime turns into SIMD), the autotuner times them
on the device and keeps the fastest. The vector versions are bit-identical to the scalar ones;
other alternatives (FIR tiles, IIR time blocks) agree to rounding;
`dspk_core::compute::pin_tuned_choices` fixes the IIR block split and the matrix-multiply choice for
runs that must reproduce bit for bit. Reproduce with
`cargo test -p dspk-benchmarks --profile validate --test bench_kernels -- --ignored --nocapture` (and
`bench_vector` for every choice); timings against scipy from Python are in
[benchmarks](benchmarks/README.md).
### Kernel and autotune cache
Autotune decisions and compiled kernels are cached across processes: the first run on a device
tunes and compiles, later runs start in about a second. The cache is one SQLite database,
`default.db` (with its `-wal` / `-tshm` journal files), in CubeCL's folder of the user cache
directory:
| Linux, macOS | `$XDG_CACHE_HOME/cubecl/`, by default `~/.cache/cubecl/` |
| Windows | `%LOCALAPPDATA%\cubecl\` |
| No user directory (no `HOME`, minimal containers) | the temporary directory, `cubecl/` |
- `DSPK_CACHE_DIR=/some/dir` puts it in that directory instead (shared machines, CI caches,
read-only home directories).
- `DSPK_KERNEL_CACHE=0` turns the compiled-kernel cache off; autotune decisions still persist.
- Deleting the folder is safe: the next run tunes and compiles again. It grows with every runtime,
device and kernel variant used (about 130 MB after the full test suite on wgpu, CUDA and CPU).
- A `cubecl.toml` in the working directory, or CubeCL's own environment variables, still apply.
- Code that opens a device through CubeCL directly instead of dspk gets CubeCL's default:
`target/environment/` of the nearest Cargo project, or the user cache directory outside one.
> [!NOTE]
> **Abort at exit (upstream, worked around).** CubeCL drops its kernel-cache database (turso) while
> a device thread ends, which can abort a process after its work: `fatal runtime error: thread
> local panicked on drop, aborting` (exit code 134;
> [tracel-ai/cubecl#1981](https://github.com/tracel-ai/cubecl/issues/1981)). dspk avoids it by ending
> only wgpu devices at exit (their Vulkan drivers need it) and leaving CUDA and CPU device threads to
> the process exit. Calling `dspk_core::compute::shutdown_devices` yourself can still hit it; so can
> code that opens devices through CubeCL directly. `DSPK_KERNEL_CACHE=0` avoids it at the cost of
> compiling kernels in every process.
## Python
```bash
pip install dspk
```
```python
import dspk
from dspk.ops import Pipeline, stage
dspk.runtime.available() # e.g. ['wgpu', 'cuda', 'cpu']
pipe = Pipeline([stage.car(), stage.iir("bandpass", (300, 6000), mode="forward")],
sample_rate=30_000, stream=True)
out = pipe.process(chunk) # (channels, samples) in, same shape out, on the GPU
```
Wheels for Linux x86_64 and Windows x64 (wgpu, CUDA, CPU runtime) and macOS arm64 (wgpu, CPU);
CPython ≥ 3.10. See [bindings/python](bindings/python/README.md).
**Device size limit.** Kernels index with 32-bit integers (WebGPU has no 64-bit integers, and one
kernel source serves every runtime): one window or batch on the device holds at most 2³² − 1
values (e.g. 384 channels × 11.1 million samples). Larger requests are refused with an error.
## Spike sorting
Kilosort4, EMUsort, MountainSort 5, SpyKING CIRCUS 2 and Tridesclous 2 run on the GPU (or the CPU)
in one separate program, `dspk-cli-sorter`, released with each version. The `dspk` command line runs
it as its own process:
```bash
dspk sorters install # this version's build, SHA-256 checked
dspk sort kilosort4 recording.bin -o sorted --format phy
dspk sort kilosort4 --help # every setting and its default
dspk-cli-sorter licenses # each sorter's licence and source
```
It is installed in the user data folder (`~/.local/share/dspk/sorters/<version>/` on Linux,
`~/Library/Application Support/dspk/…` on macOS, `%LOCALAPPDATA%\dspk\…` on Windows;
`DSPK_DATA_DIR` moves it, `DSPK_CLI_SORTER_BIN` names another build). A source build finds the
`dspk-cli-sorter` built next to `dspk` (`cargo build -p dspk-cli-sorter`).
## License
MIT OR Apache-2.0, at your option ([LICENSE-MIT](LICENSE-MIT), [LICENSE-APACHE](LICENSE-APACHE)):
the published crates, the `dspk` command line and the Python package.
The sorters follow their upstreams' licences:
| `dspk-sorter-kilosort` (Kilosort4, EMUsort) | GPL-3.0-only | Kilosort4 and EMUsort are GPL-3.0 |
| `dspk-sorter-ms5` (MountainSort 5) | Apache-2.0 | MountainSort 5 and isosplit6 are Apache-2.0 |
| `dspk-sorter-si` (SpyKING CIRCUS 2, Tridesclous 2) | MIT OR Apache-2.0 (+ SpikeInterface's MIT notice) | ported from SpikeInterface (MIT) |
| `dspk-synapse` | MIT OR Apache-2.0 (+ SpikeInterface's MIT notice) | SpikeInterface components |
| `dspk-cli-sorter` (the program) | GPL-3.0-only | contains `dspk-sorter-kilosort` |
Each sorter crate carries its upstream licence and a `NOTICE.md`. Nothing MIT / Apache-licensed
links `dspk-sorter-kilosort`: `dspk sort` runs `dspk-cli-sorter` as a separate program, whose source is this
repository at the version's tag.