Inference & model serving¶
SilentSwarm can serve the models it trains for interactive use: pick a trained model, load it onto a GPU node, chat with streaming tokens, and call it from an OpenAI-compatible API. This document describes the runtime; the staged rollout is tracked in the project's internal planning notes.
Design principle: the leader stays torch-free¶
The leader is the control plane and never imports torch (keeps its image
small and the public tunnel healthy). All inference compute runs on a fellow
(GPU node). The leader is a gateway: it owns the model catalog, decides which
fellow serves a model, and proxies/streams requests — exactly like training
dispatch (signal_hub WS for control + outbound-only HTTP for data, so it works
behind NAT/Cloudflare).
Components¶
| Layer | Module | Role |
|---|---|---|
| Serving core | runtime/torch/serving.py |
reconstruct a servable model → .generate() + streaming |
| Servable layout | (same) | <model>/servable.json + shards/stage-*/weights.pt + tokenizer/ |
| Registry | leader/serving/model_registry.py |
discover servable models (torch-free) |
| Serving subprocess | runtime/torch/inference_server.py |
one torch process per loaded model (JSONL protocol) |
| Fellow manager | fellow/inference_manager.py |
load/unload, capacity cap, shard fetch, idle auto-unload |
| Fellow dispatch | fellow/inference_dispatch.py |
drive the subprocess, per-request metrics |
| Broker | leader/serving/inference_broker.py |
NAT-safe request/response fan-out |
| Tokens | leader/accounts/api_tokens.py |
hashed bearer API tokens |
| Pipeline (E7) | runtime/torch/pipeline_serving.py |
distributed generation with a compression boundary |
| UI | leader/templates/inference.* |
/ops/inference chat + dropdown + API panel |
Request flow (chat)¶
browser / curl ──POST /v1/chat/completions──► leader gateway
│ pick serving fellow (heartbeat loaded_models)
▼
broker.submit(fellow_id, request) ── fellow long-polls
GET /api/v1/infer/requests/{fellow_id}
fellow runs generate(stream) ──► POST /api/v1/infer/responses/{id} (token/done)
browser ◄── SSE deltas ◄── broker.responses(id) ◄────────────┘
Client disconnect drops the response queue; the fellow's next frame POST returns
410, which aborts forwarding (soft-cancel; hard subprocess interrupt is future
work).
HTTP API¶
GET /api/v1/models·GET /api/v1/models/{id}·GET /api/v1/models/{id}/archivePOST /api/v1/models/{id}/load·POST /api/v1/models/{id}/unload·GET /api/v1/models/{id}/statusPOST /api/v1/chat/completionsandPOST /v1/chat/completions(OpenAI shape,streamsupported)POST/GET/DELETE /api/v1/api-tokens- Fellow transport:
GET /api/v1/infer/requests/{fellow_id},POST /api/v1/infer/responses/{request_id}
CLI¶
swarm models # list servable models
swarm model-load <id> --wait # load onto a fellow, wait until ready
swarm infer <id> "your prompt" # one-shot completion
swarm model-unload <id> # free VRAM
Authentication¶
Bearer tokens (Authorization: Bearer sk-swarm-…) authorize the chat endpoints.
Tokens are minted server-side, shown once, and stored only as SHA-256 hashes.
Auth is enforced when any token exists or SWARM_REQUIRE_API_TOKEN=1.
Distributed (pipelined) inference — E7¶
pipeline_serving.PipelineGenerator runs autoregressive generation across
pipeline stages on different devices, with the trained compression module on the
boundary, so the activation crossing the boundary is the narrow code. It reports
per-token wire bytes; no-compression sends hidden_size × 2 bytes/token, a
learned bottleneck materially less. Greedy pipeline output matches single-node
output. Cross-node transport reuses the training ZMQ/relay channels.
Hardening & observability — E8¶
- Capacity: one model per fellow by default (
SWARM_MAX_LOADED_MODELS), oldest evicted on overflow; generation is serialized per model. - Idle auto-unload: a loaded model is stopped after
SWARM_MODEL_IDLE_UNLOAD_Sseconds without a request (default900), and its VRAM goes back to the driver. The next request pays the reload. A generation in flight is never reaped, however long it streams. Set the variable to0on a dedicated inference node where reload latency matters more than the memory. - Metrics: per-model
tokens_per_s,last_ttft_s,requests,idle_s,vram_bytessurface in the heartbeatloaded_modelsandGET .../status.
VRAM lifecycle¶
VRAM is only ever borrowed, and every path that ends a fellow's involvement with a model gives it back:
| Event | What releases the memory |
|---|---|
UNLOAD_MODEL signal, or swarm model-unload |
the serving subprocess is asked to exit, then killed after 10 s |
No request for SWARM_MODEL_IDLE_UNLOAD_S |
the fellow's idle reaper stops the subprocess |
A second model loaded past SWARM_MAX_LOADED_MODELS |
the oldest model is evicted before the new one starts |
STOP / PURGE signal, fellow shutdown |
every serving subprocess is stopped |
UPDATE signal |
subprocesses are stopped before the execv re-exec |
The last row is the one that is easy to get wrong: a serving subprocess is a child of the fellow but not in its process group, so an exit that skips the shutdown leaves it running and holding a GPU that the leader now believes is free. Nothing short of killing it by hand gets that memory back.
Training is not on this list because it never needed to be: a torch job runs in its own subprocess that exits when the job ends, which releases the CUDA context with it.
Note that a loaded model's reported vram_free_gb is a high-water mark, not a
live reading - torch's caching allocator holds freed KV-cache blocks rather than
returning them to the driver. That is why the leader budgets the cache
arithmetically (telemetry/vram_budget.py) instead of trusting free VRAM.
Environment variables¶
| Var | Default | Meaning |
|---|---|---|
SWARM_SERVABLE_MODELS_ROOT |
.swarm/servable_models |
leader: where servable models live |
SWARM_FELLOW_MODEL_CACHE |
~/.silent-swarm/models |
fellow: downloaded model cache |
SWARM_MAX_LOADED_MODELS |
1 |
fellow: concurrent loaded models |
SWARM_MODEL_IDLE_UNLOAD_S |
900 |
fellow: idle TTL auto-unload (0 disables) |
SWARM_REQUIRE_API_TOKEN |
unset | leader: force bearer auth |
SWARM_API_TOKENS_FILE |
next to state DB | leader: token store path |