spikeforge-server 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- spikeforge_server-0.1.0/LICENSE +30 -0
- spikeforge_server-0.1.0/PKG-INFO +130 -0
- spikeforge_server-0.1.0/README.md +94 -0
- spikeforge_server-0.1.0/pyproject.toml +86 -0
- spikeforge_server-0.1.0/server/__init__.py +1 -0
- spikeforge_server-0.1.0/server/__main__.py +55 -0
- spikeforge_server-0.1.0/server/animation.py +76 -0
- spikeforge_server-0.1.0/server/app.py +154 -0
- spikeforge_server-0.1.0/server/backend_handlers.py +59 -0
- spikeforge_server-0.1.0/server/backend_payloads.py +53 -0
- spikeforge_server-0.1.0/server/download_errors.py +5 -0
- spikeforge_server-0.1.0/server/downloads.py +129 -0
- spikeforge_server-0.1.0/server/encoder.py +106 -0
- spikeforge_server-0.1.0/server/energy_handlers.py +62 -0
- spikeforge_server-0.1.0/server/energy_messages.py +22 -0
- spikeforge_server-0.1.0/server/energy_payloads.py +60 -0
- spikeforge_server-0.1.0/server/engine_factory.py +79 -0
- spikeforge_server-0.1.0/server/event_engine.py +145 -0
- spikeforge_server-0.1.0/server/handlers.py +247 -0
- spikeforge_server-0.1.0/server/hub_downloads.py +39 -0
- spikeforge_server-0.1.0/server/hub_handlers.py +117 -0
- spikeforge_server-0.1.0/server/hub_messages.py +51 -0
- spikeforge_server-0.1.0/server/hub_payloads.py +59 -0
- spikeforge_server-0.1.0/server/introspection_handlers.py +173 -0
- spikeforge_server-0.1.0/server/introspection_payloads.py +117 -0
- spikeforge_server-0.1.0/server/messages.py +232 -0
- spikeforge_server-0.1.0/server/model_handlers.py +56 -0
- spikeforge_server-0.1.0/server/model_payloads.py +26 -0
- spikeforge_server-0.1.0/server/nir_handlers.py +67 -0
- spikeforge_server-0.1.0/server/payloads.py +96 -0
- spikeforge_server-0.1.0/server/protocol_handlers.py +51 -0
- spikeforge_server-0.1.0/server/protocol_version.py +34 -0
- spikeforge_server-0.1.0/server/schemas/__init__.py +18 -0
- spikeforge_server-0.1.0/server/schemas/client_message.py +37 -0
- spikeforge_server-0.1.0/server/schemas/encode_config.py +34 -0
- spikeforge_server-0.1.0/server/schemas/hub_query.py +20 -0
- spikeforge_server-0.1.0/server/schemas/model_query.py +17 -0
- spikeforge_server-0.1.0/server/schemas/server_message.py +31 -0
- spikeforge_server-0.1.0/server/schemas/train_config.py +50 -0
- spikeforge_server-0.1.0/server/session.py +62 -0
- spikeforge_server-0.1.0/server/stats.py +28 -0
- spikeforge_server-0.1.0/server/target_handlers.py +117 -0
- spikeforge_server-0.1.0/server/target_payloads.py +52 -0
- spikeforge_server-0.1.0/server/training.py +223 -0
- spikeforge_server-0.1.0/server/web.py +63 -0
- spikeforge_server-0.1.0/setup.cfg +4 -0
- spikeforge_server-0.1.0/spikeforge_server.egg-info/PKG-INFO +130 -0
- spikeforge_server-0.1.0/spikeforge_server.egg-info/SOURCES.txt +51 -0
- spikeforge_server-0.1.0/spikeforge_server.egg-info/dependency_links.txt +1 -0
- spikeforge_server-0.1.0/spikeforge_server.egg-info/entry_points.txt +2 -0
- spikeforge_server-0.1.0/spikeforge_server.egg-info/not-zip-safe +1 -0
- spikeforge_server-0.1.0/spikeforge_server.egg-info/requires.txt +12 -0
- spikeforge_server-0.1.0/spikeforge_server.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2018 Capsize LLC
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
* Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
* Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
30
|
+
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: spikeforge-server
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: FastAPI + WebSocket server adapter for the spikeforge dashboard.
|
|
5
|
+
Author-email: Capsize LLC <contact@capsizegames.com>
|
|
6
|
+
Maintainer-email: Capsize LLC <contact@capsizegames.com>
|
|
7
|
+
License-Expression: BSD-3-Clause
|
|
8
|
+
Project-URL: Homepage, https://github.com/capsize-games/spikeforge
|
|
9
|
+
Project-URL: Repository, https://github.com/capsize-games/spikeforge
|
|
10
|
+
Keywords: spiking neural networks,fastapi,websockets,dashboard
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: fastapi>=0.110
|
|
25
|
+
Requires-Dist: uvicorn[standard]>=0.27
|
|
26
|
+
Requires-Dist: websockets>=12.0
|
|
27
|
+
Requires-Dist: pydantic>=2.5
|
|
28
|
+
Requires-Dist: spikeforge~=0.3.0
|
|
29
|
+
Requires-Dist: spikeforge-targets~=0.1.0
|
|
30
|
+
Requires-Dist: spikeforge-hub~=0.1.0
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
33
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
34
|
+
Requires-Dist: ruff>=0.0.280; extra == "dev"
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
|
|
37
|
+
# spikeforge
|
|
38
|
+
|
|
39
|
+
[](https://github.com/capsize-games/spikeforge/actions/workflows/ci.yml)
|
|
40
|
+
[](OPEN_SOURCE_CHECKLIST.md)
|
|
41
|
+
[](LICENSE)
|
|
42
|
+
[](https://www.python.org/downloads/)
|
|
43
|
+
[](https://github.com/astral-sh/ruff)
|
|
44
|
+
[](https://github.com/capsize-games/spikeforge/blob/main/CONTRIBUTING.md)
|
|
45
|
+
[](documentation/README.md)
|
|
46
|
+
|
|
47
|
+
A spiking-neural-network (SNN) toolkit built on
|
|
48
|
+
[snnTorch](https://snntorch.readthedocs.io/) and PyTorch. Loads MNIST-style
|
|
49
|
+
and neuromorphic event datasets, encodes them into rate, latency, delta, and
|
|
50
|
+
random spikes, and trains, validates, exports, and deploys LIF networks —
|
|
51
|
+
with a live browser dashboard served over WebSockets.
|
|
52
|
+
|
|
53
|
+
> **Pre-1.0 and unpublished.** `install.sh` is the supported path today;
|
|
54
|
+
> PyPI ships once the distributions are published. Before trusting any
|
|
55
|
+
> number this produces, read
|
|
56
|
+
> [Implications and boundaries](documentation/implications-and-boundaries.md).
|
|
57
|
+
|
|
58
|
+
## Quickstart
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
git clone https://github.com/capsize-games/spikeforge.git
|
|
62
|
+
cd spikeforge
|
|
63
|
+
docker compose up --build
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Open <http://localhost:8877> — the dashboard connects to the WebSocket on
|
|
67
|
+
the same host and port. No separate backend or proxy to run.
|
|
68
|
+
|
|
69
|
+
Prefer a local install, a headless example, or the CLI tools instead? See
|
|
70
|
+
[Usage](documentation/usage.md) and [Quickstart](documentation/quickstart.md)
|
|
71
|
+
for every path (`./install.sh`, local dev with Vite, `examples/`, and the
|
|
72
|
+
eight `spikeforge-*` console scripts).
|
|
73
|
+
|
|
74
|
+
## Features
|
|
75
|
+
|
|
76
|
+
- **Encoding** — rate, latency, delta, and random spike coders.
|
|
77
|
+
- **Training** — fully-connected and convolutional LIF networks with
|
|
78
|
+
surrogate-gradient cross-entropy, checkpointing, and opt-in AMP / gradient
|
|
79
|
+
checkpointing / truncated BPTT / multi-GPU.
|
|
80
|
+
- **Topologies** — `fc_legacy`, `fc_small`, `conv_net`, `recurrent_net`, plus
|
|
81
|
+
the sequence presets `sequence_mlp` and `sequence_attn`.
|
|
82
|
+
- **Datasets** — MNIST, Fashion-MNIST, KMNIST, QMNIST, USPS, EMNIST,
|
|
83
|
+
CIFAR-10, and (via the `events` extra) N-MNIST, DVS128 Gesture,
|
|
84
|
+
CIFAR10-DVS, and Spiking Speech Commands.
|
|
85
|
+
- **Interpreter spine** — NIR export, an independent NIR interpreter, and
|
|
86
|
+
numerical drift validation.
|
|
87
|
+
- **Introspection** — educational-mode `U[t]`/`I[t]`/`S[t]` traces,
|
|
88
|
+
trajectory metrics, and surrogate-derivative curves.
|
|
89
|
+
- **Deployment** — a capability matrix, weight quantization, energy
|
|
90
|
+
accounting, and executable `reference`, `norse`, and `lava_loihi2`
|
|
91
|
+
backends.
|
|
92
|
+
- **Model hub** — a curated, offline-first catalog plus optional live
|
|
93
|
+
Hugging Face search.
|
|
94
|
+
- **Dashboard** — a React + TypeScript UI with training, introspection,
|
|
95
|
+
analysis, targets, energy, and hub panels, and seven guided walkthroughs.
|
|
96
|
+
|
|
97
|
+
## Packages
|
|
98
|
+
|
|
99
|
+
This repository is a single workspace that publishes four distributions:
|
|
100
|
+
|
|
101
|
+
| Distribution | Import root | Purpose |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| `spikeforge` | `spikeforge` | Core package: encoders, topologies, training, simulator, NIR bridge, tracking |
|
|
104
|
+
| `spikeforge-targets` | `spikeforge_targets` | Deployment targets, quantization, energy accounting, sparse event runtime |
|
|
105
|
+
| `spikeforge-hub` | `spikeforge_hub` | Curated model hub and optional Hugging Face access |
|
|
106
|
+
| `spikeforge-server` | `server` | FastAPI + WebSocket server and dashboard hosting |
|
|
107
|
+
|
|
108
|
+
## Documentation
|
|
109
|
+
|
|
110
|
+
This README stays short on purpose.
|
|
111
|
+
[`documentation/`](documentation/README.md) is the full reference — install
|
|
112
|
+
paths, the CLI tools, architecture, module layout, and the dev workflow —
|
|
113
|
+
written for contributors and coding agents alike. Also see
|
|
114
|
+
[COOKBOOK.md](COOKBOOK.md) for copy-pasteable recipes,
|
|
115
|
+
[examples/](examples/) for runnable end-to-end scripts, and
|
|
116
|
+
[plans/](plans/) for design documents and the roadmap.
|
|
117
|
+
|
|
118
|
+
See [CONTRIBUTING.md](https://github.com/capsize-games/spikeforge/blob/main/CONTRIBUTING.md)
|
|
119
|
+
and [rules.md](rules.md) before opening a pull request.
|
|
120
|
+
|
|
121
|
+
## Citing
|
|
122
|
+
|
|
123
|
+
If spikeforge is useful in your research, please cite it — see
|
|
124
|
+
[CITATION.cff](CITATION.cff) (GitHub renders a "Cite this repository"
|
|
125
|
+
button from it automatically).
|
|
126
|
+
|
|
127
|
+
## License
|
|
128
|
+
|
|
129
|
+
Released under the **BSD 3-Clause License** — see [LICENSE](LICENSE) and
|
|
130
|
+
[AUTHORS](AUTHORS).
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# spikeforge
|
|
2
|
+
|
|
3
|
+
[](https://github.com/capsize-games/spikeforge/actions/workflows/ci.yml)
|
|
4
|
+
[](OPEN_SOURCE_CHECKLIST.md)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://www.python.org/downloads/)
|
|
7
|
+
[](https://github.com/astral-sh/ruff)
|
|
8
|
+
[](https://github.com/capsize-games/spikeforge/blob/main/CONTRIBUTING.md)
|
|
9
|
+
[](documentation/README.md)
|
|
10
|
+
|
|
11
|
+
A spiking-neural-network (SNN) toolkit built on
|
|
12
|
+
[snnTorch](https://snntorch.readthedocs.io/) and PyTorch. Loads MNIST-style
|
|
13
|
+
and neuromorphic event datasets, encodes them into rate, latency, delta, and
|
|
14
|
+
random spikes, and trains, validates, exports, and deploys LIF networks —
|
|
15
|
+
with a live browser dashboard served over WebSockets.
|
|
16
|
+
|
|
17
|
+
> **Pre-1.0 and unpublished.** `install.sh` is the supported path today;
|
|
18
|
+
> PyPI ships once the distributions are published. Before trusting any
|
|
19
|
+
> number this produces, read
|
|
20
|
+
> [Implications and boundaries](documentation/implications-and-boundaries.md).
|
|
21
|
+
|
|
22
|
+
## Quickstart
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
git clone https://github.com/capsize-games/spikeforge.git
|
|
26
|
+
cd spikeforge
|
|
27
|
+
docker compose up --build
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Open <http://localhost:8877> — the dashboard connects to the WebSocket on
|
|
31
|
+
the same host and port. No separate backend or proxy to run.
|
|
32
|
+
|
|
33
|
+
Prefer a local install, a headless example, or the CLI tools instead? See
|
|
34
|
+
[Usage](documentation/usage.md) and [Quickstart](documentation/quickstart.md)
|
|
35
|
+
for every path (`./install.sh`, local dev with Vite, `examples/`, and the
|
|
36
|
+
eight `spikeforge-*` console scripts).
|
|
37
|
+
|
|
38
|
+
## Features
|
|
39
|
+
|
|
40
|
+
- **Encoding** — rate, latency, delta, and random spike coders.
|
|
41
|
+
- **Training** — fully-connected and convolutional LIF networks with
|
|
42
|
+
surrogate-gradient cross-entropy, checkpointing, and opt-in AMP / gradient
|
|
43
|
+
checkpointing / truncated BPTT / multi-GPU.
|
|
44
|
+
- **Topologies** — `fc_legacy`, `fc_small`, `conv_net`, `recurrent_net`, plus
|
|
45
|
+
the sequence presets `sequence_mlp` and `sequence_attn`.
|
|
46
|
+
- **Datasets** — MNIST, Fashion-MNIST, KMNIST, QMNIST, USPS, EMNIST,
|
|
47
|
+
CIFAR-10, and (via the `events` extra) N-MNIST, DVS128 Gesture,
|
|
48
|
+
CIFAR10-DVS, and Spiking Speech Commands.
|
|
49
|
+
- **Interpreter spine** — NIR export, an independent NIR interpreter, and
|
|
50
|
+
numerical drift validation.
|
|
51
|
+
- **Introspection** — educational-mode `U[t]`/`I[t]`/`S[t]` traces,
|
|
52
|
+
trajectory metrics, and surrogate-derivative curves.
|
|
53
|
+
- **Deployment** — a capability matrix, weight quantization, energy
|
|
54
|
+
accounting, and executable `reference`, `norse`, and `lava_loihi2`
|
|
55
|
+
backends.
|
|
56
|
+
- **Model hub** — a curated, offline-first catalog plus optional live
|
|
57
|
+
Hugging Face search.
|
|
58
|
+
- **Dashboard** — a React + TypeScript UI with training, introspection,
|
|
59
|
+
analysis, targets, energy, and hub panels, and seven guided walkthroughs.
|
|
60
|
+
|
|
61
|
+
## Packages
|
|
62
|
+
|
|
63
|
+
This repository is a single workspace that publishes four distributions:
|
|
64
|
+
|
|
65
|
+
| Distribution | Import root | Purpose |
|
|
66
|
+
|---|---|---|
|
|
67
|
+
| `spikeforge` | `spikeforge` | Core package: encoders, topologies, training, simulator, NIR bridge, tracking |
|
|
68
|
+
| `spikeforge-targets` | `spikeforge_targets` | Deployment targets, quantization, energy accounting, sparse event runtime |
|
|
69
|
+
| `spikeforge-hub` | `spikeforge_hub` | Curated model hub and optional Hugging Face access |
|
|
70
|
+
| `spikeforge-server` | `server` | FastAPI + WebSocket server and dashboard hosting |
|
|
71
|
+
|
|
72
|
+
## Documentation
|
|
73
|
+
|
|
74
|
+
This README stays short on purpose.
|
|
75
|
+
[`documentation/`](documentation/README.md) is the full reference — install
|
|
76
|
+
paths, the CLI tools, architecture, module layout, and the dev workflow —
|
|
77
|
+
written for contributors and coding agents alike. Also see
|
|
78
|
+
[COOKBOOK.md](COOKBOOK.md) for copy-pasteable recipes,
|
|
79
|
+
[examples/](examples/) for runnable end-to-end scripts, and
|
|
80
|
+
[plans/](plans/) for design documents and the roadmap.
|
|
81
|
+
|
|
82
|
+
See [CONTRIBUTING.md](https://github.com/capsize-games/spikeforge/blob/main/CONTRIBUTING.md)
|
|
83
|
+
and [rules.md](rules.md) before opening a pull request.
|
|
84
|
+
|
|
85
|
+
## Citing
|
|
86
|
+
|
|
87
|
+
If spikeforge is useful in your research, please cite it — see
|
|
88
|
+
[CITATION.cff](CITATION.cff) (GitHub renders a "Cite this repository"
|
|
89
|
+
button from it automatically).
|
|
90
|
+
|
|
91
|
+
## License
|
|
92
|
+
|
|
93
|
+
Released under the **BSD 3-Clause License** — see [LICENSE](LICENSE) and
|
|
94
|
+
[AUTHORS](AUTHORS).
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# PEP 621 packaging authority for the server distribution (ARCH-0001 Phase
|
|
2
|
+
# 1b).
|
|
3
|
+
#
|
|
4
|
+
# The server distribution owns the top-level ``server`` package. It promotes
|
|
5
|
+
# the dependencies that used to be the core ``web`` extra to its own base
|
|
6
|
+
# dependencies, so core stays headless and the server is the only thing that
|
|
7
|
+
# pulls FastAPI/uvicorn/websockets/pydantic.
|
|
8
|
+
#
|
|
9
|
+
# ``server/`` stays at its current repository path and is exposed to the build
|
|
10
|
+
# via the ``server`` symlink next to this file (see the ``packages.find`` table
|
|
11
|
+
# below). The package discovery here depends only on the top-level ``server``
|
|
12
|
+
# package, never on core.
|
|
13
|
+
|
|
14
|
+
[build-system]
|
|
15
|
+
requires = ["setuptools>=68"]
|
|
16
|
+
build-backend = "setuptools.build_meta"
|
|
17
|
+
|
|
18
|
+
[project]
|
|
19
|
+
name = "spikeforge-server"
|
|
20
|
+
version = "0.1.0"
|
|
21
|
+
description = "FastAPI + WebSocket server adapter for the spikeforge dashboard."
|
|
22
|
+
# ``README.md`` and ``LICENSE`` are committed symlinks to the repository-root
|
|
23
|
+
# files, mirroring the core distribution's metadata approach.
|
|
24
|
+
readme = "README.md"
|
|
25
|
+
requires-python = ">=3.10"
|
|
26
|
+
license = "BSD-3-Clause"
|
|
27
|
+
license-files = ["LICENSE"]
|
|
28
|
+
authors = [
|
|
29
|
+
{ name = "Capsize LLC", email = "contact@capsizegames.com" },
|
|
30
|
+
]
|
|
31
|
+
maintainers = [
|
|
32
|
+
{ name = "Capsize LLC", email = "contact@capsizegames.com" },
|
|
33
|
+
]
|
|
34
|
+
keywords = [
|
|
35
|
+
"spiking neural networks",
|
|
36
|
+
"fastapi",
|
|
37
|
+
"websockets",
|
|
38
|
+
"dashboard",
|
|
39
|
+
]
|
|
40
|
+
classifiers = [
|
|
41
|
+
"Development Status :: 4 - Beta",
|
|
42
|
+
"Intended Audience :: Developers",
|
|
43
|
+
"Intended Audience :: Science/Research",
|
|
44
|
+
"Operating System :: OS Independent",
|
|
45
|
+
"Programming Language :: Python :: 3",
|
|
46
|
+
"Programming Language :: Python :: 3.10",
|
|
47
|
+
"Programming Language :: Python :: 3.11",
|
|
48
|
+
"Programming Language :: Python :: 3.12",
|
|
49
|
+
"Programming Language :: Python :: 3.13",
|
|
50
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
51
|
+
]
|
|
52
|
+
dependencies = [
|
|
53
|
+
"fastapi>=0.110",
|
|
54
|
+
"uvicorn[standard]>=0.27",
|
|
55
|
+
"websockets>=12.0",
|
|
56
|
+
"pydantic>=2.5",
|
|
57
|
+
"spikeforge~=0.3.0",
|
|
58
|
+
"spikeforge-targets~=0.1.0",
|
|
59
|
+
"spikeforge-hub~=0.1.0",
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
[project.optional-dependencies]
|
|
63
|
+
dev = [
|
|
64
|
+
"pytest>=7.0",
|
|
65
|
+
"pytest-cov>=4.0",
|
|
66
|
+
"ruff>=0.0.280",
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
[project.scripts]
|
|
70
|
+
spikeforge-server = "server.__main__:main"
|
|
71
|
+
|
|
72
|
+
[project.urls]
|
|
73
|
+
Homepage = "https://github.com/capsize-games/spikeforge"
|
|
74
|
+
Repository = "https://github.com/capsize-games/spikeforge"
|
|
75
|
+
|
|
76
|
+
[tool.setuptools]
|
|
77
|
+
zip-safe = false
|
|
78
|
+
|
|
79
|
+
[tool.setuptools.packages.find]
|
|
80
|
+
# Mirror the core distribution: the `server` import root is exposed next to
|
|
81
|
+
# this file as a symlink to `../../server` so the default via-sdist
|
|
82
|
+
# `python -m build` produces a complete wheel. Discovery depends only on this
|
|
83
|
+
# top-level package, never on core.
|
|
84
|
+
where = ["."]
|
|
85
|
+
include = ["server*"]
|
|
86
|
+
exclude = ["spikeforge*", "spikeforge_targets*", "spikeforge_hub*", "tests*"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""FastAPI server exposing SNN encoding experiments over WebSocket."""
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Run the FastAPI server with uvicorn: ``python -m server``."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
from typing import Optional, Sequence
|
|
5
|
+
|
|
6
|
+
import uvicorn
|
|
7
|
+
|
|
8
|
+
#: Default bind address and port (the single-port dashboard contract on :8877).
|
|
9
|
+
DEFAULT_HOST = "127.0.0.1"
|
|
10
|
+
DEFAULT_PORT = 8877
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _parse_args(argv: Optional[Sequence[str]]) -> argparse.Namespace:
|
|
14
|
+
"""Parse the optional bind arguments, keeping the historical defaults."""
|
|
15
|
+
parser = argparse.ArgumentParser(
|
|
16
|
+
prog="spikeforge-server",
|
|
17
|
+
description="Run the spikeforge dashboard/WebSocket server.",
|
|
18
|
+
)
|
|
19
|
+
parser.add_argument(
|
|
20
|
+
"--host",
|
|
21
|
+
default=DEFAULT_HOST,
|
|
22
|
+
help=f"bind address (default: {DEFAULT_HOST})",
|
|
23
|
+
)
|
|
24
|
+
parser.add_argument(
|
|
25
|
+
"--port",
|
|
26
|
+
type=int,
|
|
27
|
+
default=DEFAULT_PORT,
|
|
28
|
+
help=f"bind port (default: {DEFAULT_PORT})",
|
|
29
|
+
)
|
|
30
|
+
parser.add_argument(
|
|
31
|
+
"--no-reload",
|
|
32
|
+
action="store_true",
|
|
33
|
+
help="disable uvicorn's auto-reload watcher",
|
|
34
|
+
)
|
|
35
|
+
return parser.parse_args(argv)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def main(argv: Optional[Sequence[str]] = None) -> None:
|
|
39
|
+
"""Start the dashboard server with uvicorn.
|
|
40
|
+
|
|
41
|
+
``python -m server`` and the ``spikeforge-server`` console script both
|
|
42
|
+
land here; the defaults are the historical ``127.0.0.1:8877`` with
|
|
43
|
+
auto-reload, matching the project's single-port contract.
|
|
44
|
+
"""
|
|
45
|
+
args = _parse_args(argv)
|
|
46
|
+
uvicorn.run(
|
|
47
|
+
"server.app:app",
|
|
48
|
+
host=args.host,
|
|
49
|
+
port=args.port,
|
|
50
|
+
reload=not args.no_reload,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
if __name__ == "__main__":
|
|
55
|
+
main()
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Per-step hidden-layer animation frames and their availability message.
|
|
2
|
+
|
|
3
|
+
The frames come from the loaded model's hidden stage. Without a model there
|
|
4
|
+
is nothing honest to animate, so availability and its named reason are
|
|
5
|
+
reported to the client rather than a fabricated frame; with no model the
|
|
6
|
+
input-frame stream stays exactly as it was.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Any, List, Optional, Tuple
|
|
10
|
+
|
|
11
|
+
import torch
|
|
12
|
+
from fastapi import WebSocket
|
|
13
|
+
|
|
14
|
+
from server.messages import send_locked
|
|
15
|
+
from server.session import Session
|
|
16
|
+
from spikeforge.network.hidden_frames import hidden_frame_series
|
|
17
|
+
|
|
18
|
+
#: Named reasons the hidden animation cannot run.
|
|
19
|
+
NO_MODEL = "no model loaded; train or load one"
|
|
20
|
+
NO_SAMPLE = "no sample configured"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _on_device(net: Any, spikes: torch.Tensor) -> torch.Tensor:
|
|
24
|
+
"""Move ``spikes`` onto the network's parameter device."""
|
|
25
|
+
param = next(net.parameters(), None)
|
|
26
|
+
return spikes if param is None else spikes.to(param.device)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def hidden_series(session: Session) -> Tuple[Optional[List[Any]], str]:
|
|
30
|
+
"""Return the per-step hidden frames and a reason when unavailable."""
|
|
31
|
+
engine = session.training.engine
|
|
32
|
+
if engine is None:
|
|
33
|
+
return None, NO_MODEL
|
|
34
|
+
sample = session.engine
|
|
35
|
+
if sample is None:
|
|
36
|
+
return None, NO_SAMPLE
|
|
37
|
+
try:
|
|
38
|
+
spikes = _on_device(engine.net, sample.spike_input())
|
|
39
|
+
return hidden_frame_series(engine.net, spikes), ""
|
|
40
|
+
except Exception as exc:
|
|
41
|
+
return None, f"{type(exc).__name__}: {exc}"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
async def send_animation_state(
|
|
45
|
+
ws: WebSocket, session: Session, available: bool, reason: str
|
|
46
|
+
) -> None:
|
|
47
|
+
"""Report whether the hidden animation is available, and why not."""
|
|
48
|
+
await send_locked(ws, session, {
|
|
49
|
+
"type": "animation_state",
|
|
50
|
+
"payload": {"available": available, "reason": reason,
|
|
51
|
+
"source": "hidden"},
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
async def emit_hidden_frame(
|
|
56
|
+
ws: WebSocket, session: Session, frames: List[Any], step: int
|
|
57
|
+
) -> None:
|
|
58
|
+
"""Send one step's hidden-layer frame as a ``spike_frame``."""
|
|
59
|
+
index = min(max(int(step), 0), len(frames) - 1)
|
|
60
|
+
await send_locked(ws, session, {
|
|
61
|
+
"type": "spike_frame",
|
|
62
|
+
"payload": frames[index],
|
|
63
|
+
"step": index,
|
|
64
|
+
"source": "hidden",
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
async def prepare(
|
|
69
|
+
ws: WebSocket, session: Session, animate: bool
|
|
70
|
+
) -> List[Any]:
|
|
71
|
+
"""Return the hidden frame series, reporting availability once."""
|
|
72
|
+
if not animate:
|
|
73
|
+
return []
|
|
74
|
+
frames, reason = hidden_series(session)
|
|
75
|
+
await send_animation_state(ws, session, frames is not None, reason)
|
|
76
|
+
return frames or []
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"""FastAPI application exposing the encoding engine over WebSocket."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from typing import Any, Dict, Optional
|
|
5
|
+
|
|
6
|
+
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
|
|
7
|
+
|
|
8
|
+
from server.downloads import manager
|
|
9
|
+
from server.handlers import dispatch
|
|
10
|
+
from server.hub_downloads import manager as hub_manager
|
|
11
|
+
from server.messages import send_locked
|
|
12
|
+
from server.protocol_version import PROTOCOL_MAJOR, PROTOCOL_VERSION
|
|
13
|
+
from server.schemas import ClientMessage
|
|
14
|
+
from server.session import Session
|
|
15
|
+
from server.web import mount_client
|
|
16
|
+
from spikeforge.runtime import device as device_mod
|
|
17
|
+
|
|
18
|
+
app = FastAPI(title="spikeforge server")
|
|
19
|
+
|
|
20
|
+
# Warm the CUDA context once at startup so training/benchmarks don't stall.
|
|
21
|
+
device_mod.prime()
|
|
22
|
+
|
|
23
|
+
# Serve the built React app (no-op when client/dist is absent).
|
|
24
|
+
mount_client(app)
|
|
25
|
+
|
|
26
|
+
# One session per connected client.
|
|
27
|
+
_sessions: Dict[int, Session] = {}
|
|
28
|
+
|
|
29
|
+
#: Payload code returned when an inbound version is missing or mismatched.
|
|
30
|
+
VERSION_MISMATCH = "protocol_version_mismatch"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _version_error(raw: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
34
|
+
"""Return a mismatch payload, or None when the inbound version fits.
|
|
35
|
+
|
|
36
|
+
From Phase 2 onward ``protocol_version`` is required: a message that
|
|
37
|
+
omits it -- or carries one whose MAJOR component differs from ours -- is
|
|
38
|
+
rejected with ``payload.code = "protocol_version_mismatch"``.
|
|
39
|
+
"""
|
|
40
|
+
incoming = raw.get("protocol_version")
|
|
41
|
+
if incoming is None:
|
|
42
|
+
return {
|
|
43
|
+
"code": VERSION_MISMATCH,
|
|
44
|
+
"message": (
|
|
45
|
+
"client message is missing protocol_version; server "
|
|
46
|
+
f"protocol is {PROTOCOL_VERSION}"
|
|
47
|
+
),
|
|
48
|
+
"client": None,
|
|
49
|
+
"server": PROTOCOL_VERSION,
|
|
50
|
+
}
|
|
51
|
+
major = str(incoming).split(".", 1)[0]
|
|
52
|
+
if major == PROTOCOL_MAJOR:
|
|
53
|
+
return None
|
|
54
|
+
return {
|
|
55
|
+
"code": VERSION_MISMATCH,
|
|
56
|
+
"message": (
|
|
57
|
+
f"client protocol {incoming!r} is incompatible with server "
|
|
58
|
+
f"protocol {PROTOCOL_VERSION}"
|
|
59
|
+
),
|
|
60
|
+
"client": incoming,
|
|
61
|
+
"server": PROTOCOL_VERSION,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
async def _drain_training(ws: WebSocket, session: Session) -> None:
|
|
66
|
+
"""Forward worker-produced training messages to the socket."""
|
|
67
|
+
while True:
|
|
68
|
+
message = await session.training.queue.get()
|
|
69
|
+
await send_locked(ws, session, message)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
async def _dispatch_inbox(ws: WebSocket, session: Session) -> None:
|
|
73
|
+
"""Process queued client messages one at a time."""
|
|
74
|
+
while True:
|
|
75
|
+
message = await session.inbox.get()
|
|
76
|
+
try:
|
|
77
|
+
await dispatch(ws, session, message)
|
|
78
|
+
except Exception as exc: # keep the connection alive on bad input
|
|
79
|
+
await send_locked(ws, session, {
|
|
80
|
+
"type": "error", "payload": str(exc),
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
async def _cleanup(
|
|
85
|
+
session: Session,
|
|
86
|
+
drain: asyncio.Task,
|
|
87
|
+
worker: asyncio.Task,
|
|
88
|
+
session_id: int,
|
|
89
|
+
) -> None:
|
|
90
|
+
"""Stop background work and forget the session."""
|
|
91
|
+
session.training.stop()
|
|
92
|
+
drain.cancel()
|
|
93
|
+
worker.cancel()
|
|
94
|
+
await session.cancel()
|
|
95
|
+
_sessions.pop(session_id, None)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
async def _serve(ws: WebSocket, session: Session) -> None:
|
|
99
|
+
"""Read client messages, giving downloads their own cancel path."""
|
|
100
|
+
while True:
|
|
101
|
+
raw = await ws.receive_json()
|
|
102
|
+
version_error = _version_error(raw)
|
|
103
|
+
if version_error is not None:
|
|
104
|
+
await send_locked(ws, session, {
|
|
105
|
+
"type": "error", "payload": version_error,
|
|
106
|
+
})
|
|
107
|
+
continue
|
|
108
|
+
try:
|
|
109
|
+
message = ClientMessage.model_validate(raw)
|
|
110
|
+
except Exception as exc: # keep the connection alive on bad input
|
|
111
|
+
await send_locked(ws, session, {
|
|
112
|
+
"type": "error", "payload": str(exc),
|
|
113
|
+
})
|
|
114
|
+
continue
|
|
115
|
+
# Cancels must bypass the queue so they land while a download blocks
|
|
116
|
+
# the dispatcher on its progress stream.
|
|
117
|
+
if message.type == "cancel_download":
|
|
118
|
+
manager.cancel()
|
|
119
|
+
continue
|
|
120
|
+
if message.type == "hub_cancel":
|
|
121
|
+
hub_manager.cancel()
|
|
122
|
+
await send_locked(ws, session, {
|
|
123
|
+
"type": "hub_download_state",
|
|
124
|
+
"payload": hub_manager.snapshot(),
|
|
125
|
+
})
|
|
126
|
+
continue
|
|
127
|
+
await session.inbox.put(message)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@app.websocket("/ws")
|
|
131
|
+
async def websocket_endpoint(ws: WebSocket) -> None:
|
|
132
|
+
"""Accept a client, run its session, and always clean up."""
|
|
133
|
+
await ws.accept()
|
|
134
|
+
session_id = id(ws)
|
|
135
|
+
session = Session(asyncio.get_running_loop())
|
|
136
|
+
_sessions[session_id] = session
|
|
137
|
+
drain = asyncio.create_task(_drain_training(ws, session))
|
|
138
|
+
worker = asyncio.create_task(_dispatch_inbox(ws, session))
|
|
139
|
+
try:
|
|
140
|
+
await _serve(ws, session)
|
|
141
|
+
except WebSocketDisconnect:
|
|
142
|
+
pass
|
|
143
|
+
except Exception as exc: # surface unexpected errors to the client
|
|
144
|
+
await send_locked(ws, session, {
|
|
145
|
+
"type": "error", "payload": str(exc),
|
|
146
|
+
})
|
|
147
|
+
finally:
|
|
148
|
+
await _cleanup(session, drain, worker, session_id)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@app.get("/health")
|
|
152
|
+
async def health() -> Dict[str, str]:
|
|
153
|
+
"""Liveness probe for container/orchestrator health checks."""
|
|
154
|
+
return {"status": "ok"}
|