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.
Files changed (53) hide show
  1. spikeforge_server-0.1.0/LICENSE +30 -0
  2. spikeforge_server-0.1.0/PKG-INFO +130 -0
  3. spikeforge_server-0.1.0/README.md +94 -0
  4. spikeforge_server-0.1.0/pyproject.toml +86 -0
  5. spikeforge_server-0.1.0/server/__init__.py +1 -0
  6. spikeforge_server-0.1.0/server/__main__.py +55 -0
  7. spikeforge_server-0.1.0/server/animation.py +76 -0
  8. spikeforge_server-0.1.0/server/app.py +154 -0
  9. spikeforge_server-0.1.0/server/backend_handlers.py +59 -0
  10. spikeforge_server-0.1.0/server/backend_payloads.py +53 -0
  11. spikeforge_server-0.1.0/server/download_errors.py +5 -0
  12. spikeforge_server-0.1.0/server/downloads.py +129 -0
  13. spikeforge_server-0.1.0/server/encoder.py +106 -0
  14. spikeforge_server-0.1.0/server/energy_handlers.py +62 -0
  15. spikeforge_server-0.1.0/server/energy_messages.py +22 -0
  16. spikeforge_server-0.1.0/server/energy_payloads.py +60 -0
  17. spikeforge_server-0.1.0/server/engine_factory.py +79 -0
  18. spikeforge_server-0.1.0/server/event_engine.py +145 -0
  19. spikeforge_server-0.1.0/server/handlers.py +247 -0
  20. spikeforge_server-0.1.0/server/hub_downloads.py +39 -0
  21. spikeforge_server-0.1.0/server/hub_handlers.py +117 -0
  22. spikeforge_server-0.1.0/server/hub_messages.py +51 -0
  23. spikeforge_server-0.1.0/server/hub_payloads.py +59 -0
  24. spikeforge_server-0.1.0/server/introspection_handlers.py +173 -0
  25. spikeforge_server-0.1.0/server/introspection_payloads.py +117 -0
  26. spikeforge_server-0.1.0/server/messages.py +232 -0
  27. spikeforge_server-0.1.0/server/model_handlers.py +56 -0
  28. spikeforge_server-0.1.0/server/model_payloads.py +26 -0
  29. spikeforge_server-0.1.0/server/nir_handlers.py +67 -0
  30. spikeforge_server-0.1.0/server/payloads.py +96 -0
  31. spikeforge_server-0.1.0/server/protocol_handlers.py +51 -0
  32. spikeforge_server-0.1.0/server/protocol_version.py +34 -0
  33. spikeforge_server-0.1.0/server/schemas/__init__.py +18 -0
  34. spikeforge_server-0.1.0/server/schemas/client_message.py +37 -0
  35. spikeforge_server-0.1.0/server/schemas/encode_config.py +34 -0
  36. spikeforge_server-0.1.0/server/schemas/hub_query.py +20 -0
  37. spikeforge_server-0.1.0/server/schemas/model_query.py +17 -0
  38. spikeforge_server-0.1.0/server/schemas/server_message.py +31 -0
  39. spikeforge_server-0.1.0/server/schemas/train_config.py +50 -0
  40. spikeforge_server-0.1.0/server/session.py +62 -0
  41. spikeforge_server-0.1.0/server/stats.py +28 -0
  42. spikeforge_server-0.1.0/server/target_handlers.py +117 -0
  43. spikeforge_server-0.1.0/server/target_payloads.py +52 -0
  44. spikeforge_server-0.1.0/server/training.py +223 -0
  45. spikeforge_server-0.1.0/server/web.py +63 -0
  46. spikeforge_server-0.1.0/setup.cfg +4 -0
  47. spikeforge_server-0.1.0/spikeforge_server.egg-info/PKG-INFO +130 -0
  48. spikeforge_server-0.1.0/spikeforge_server.egg-info/SOURCES.txt +51 -0
  49. spikeforge_server-0.1.0/spikeforge_server.egg-info/dependency_links.txt +1 -0
  50. spikeforge_server-0.1.0/spikeforge_server.egg-info/entry_points.txt +2 -0
  51. spikeforge_server-0.1.0/spikeforge_server.egg-info/not-zip-safe +1 -0
  52. spikeforge_server-0.1.0/spikeforge_server.egg-info/requires.txt +12 -0
  53. 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
+ [![CI](https://github.com/capsize-games/spikeforge/actions/workflows/ci.yml/badge.svg)](https://github.com/capsize-games/spikeforge/actions/workflows/ci.yml)
40
+ [![Status: pre-1.0](https://img.shields.io/badge/status-pre--1.0-orange.svg)](OPEN_SOURCE_CHECKLIST.md)
41
+ [![License: BSD-3-Clause](https://img.shields.io/badge/License-BSD_3--Clause-blue.svg)](LICENSE)
42
+ [![Python 3.10–3.13](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13-3776AB.svg)](https://www.python.org/downloads/)
43
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
44
+ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/capsize-games/spikeforge/blob/main/CONTRIBUTING.md)
45
+ [![Docs](https://img.shields.io/badge/docs-long--form%20reference-blue.svg)](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
+ [![CI](https://github.com/capsize-games/spikeforge/actions/workflows/ci.yml/badge.svg)](https://github.com/capsize-games/spikeforge/actions/workflows/ci.yml)
4
+ [![Status: pre-1.0](https://img.shields.io/badge/status-pre--1.0-orange.svg)](OPEN_SOURCE_CHECKLIST.md)
5
+ [![License: BSD-3-Clause](https://img.shields.io/badge/License-BSD_3--Clause-blue.svg)](LICENSE)
6
+ [![Python 3.10–3.13](https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12%20%7C%203.13-3776AB.svg)](https://www.python.org/downloads/)
7
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
8
+ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/capsize-games/spikeforge/blob/main/CONTRIBUTING.md)
9
+ [![Docs](https://img.shields.io/badge/docs-long--form%20reference-blue.svg)](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"}