Fellow in a container¶
The one-line curl ... | bash join builds a fellow in a virtualenv on the
machine itself. The fellow image is the other way to run one: everything
the fellow needs (Python, PyTorch with its CUDA libraries, the pinned runtime
libraries, the mesh tunnel's binaries) is in the image, and the machine needs
only Docker, an NVIDIA driver and the
NVIDIA Container Toolkit.
Run¶
docker run -d --name fellow --gpus all --restart unless-stopped \
--stop-timeout 120 \
-e SWARM_LEADER_URL=https://swarm.example.org \
-e SWARM_JOIN_TOKEN=<from the pool's join command> \
-e SWARM_FELLOW_ID=7001 \
-e SWARM_HOST=<an address other fellows can reach> \
-e SWARM_FELLOW_DRAIN_S=100 \
-p 5557:5557 -p 5558:5558 -p 5559:5559 \
-v fellow-state:/state \
ghcr.io/franzelfx/silentswarm-fellow:<version>
SWARM_FELLOW_IDis the node's identity. Keep it the same across restarts and recreates: a new ID is a new node, and enough of them fill the pool's node limit. The bootstrap derives one from the host name; the image takes what you give it, orSWARM_FELLOW_ID_BASEplusSWARM_FELLOW_INDEX(a StatefulSet's pod ordinal, which is how the Helm chart numbers its in-cluster fellows).SWARM_JOIN_TOKENbinds the node to its pool. Use an enrollment token (the pool page's Add a node, "An enrollment token for containers and Kubernetes"): it does not expire and admits every container started with it, up to an optional number of nodes, until you revoke it. A one-hour join command's token works too, for the first start; after that the fellow comes back with the node credential it keeps in/state/node_token.- Ports. The fellow advertises the ports it binds, so publish them
unchanged. A taken port is an error in the image (the bootstrap moves to the
next free pair instead): another port would be one nothing routes to. Without
open ports the leader's relay carries the traffic, and with the mesh on the
fellow joins it on its own (
SWARM_FELLOW_MESH=0declines it). - GPUs.
--gpus all, or--gpus '"device=0,1"'for some. The image setsCUDA_DEVICE_ORDER=PCI_BUS_IDso GPU indices matchnvidia-smi, andNVIDIA_DRIVER_CAPABILITIES=compute,utilityso the fellow can read the cards' memory and temperature.
State¶
The fellow runs as uid 10001 and writes only to /state
(SWARM_FELLOW_STATE_DIR):
Under /state |
What |
|---|---|
models/ |
servable model cache |
hf/ |
Hugging Face cache (HF_HOME, unless you set it) |
servable_exports/ |
a training run's exported shards - possibly the only copy until the upload to the leader finishes |
snapshots/, offload/ |
stage snapshots, disk offload for a stage too big for its cards |
control/ |
fellow checkout requests, the liveness file |
mesh/ |
the mesh tunnel's node state |
node_token |
the node credential, so a restart re-registers as the same node of the same pool |
logs/fellow.out |
the log the leader's log view shows |
Use a named volume (as above), or a host directory owned by uid 10001. Each
path still takes its own variable (SWARM_FELLOW_MODEL_CACHE,
SWARM_FELLOW_SERVABLE_EXPORT_ROOT, ...) when you want it elsewhere.
Stopping¶
docker stop (and a Kubernetes pod deletion) sends SIGTERM. A fellow holding a
training stage then checks itself out: the leader moves the stage to
another node at its next snapshot boundary, and the fellow waits up to
SWARM_FELLOW_DRAIN_S seconds (default 20) for that before it stops its worker
and exits 0. A stage not moved in time is the job's recovery to handle, from
its last snapshot. Give the container longer than the drain:
--stop-timeout (Docker's default is 10 s) or terminationGracePeriodSeconds
(30 s on Kubernetes). A serving stage needs no drain: the leader's serving
failover replaces it.
The checkout keeps the node out of new work; the next start checks it back in.
fellow checkout works the same in a container:
docker exec fellow fellow checkout --in 15m
docker exec fellow fellow checkin
Health and logs¶
The image's health check, and the probe to use as a Kubernetes livenessProbe,
is python -m silent_swarm.fellow.health_check. It is local: the runner
touches /state/control/alive while it runs, and the check fails when that is
older than SWARM_FELLOW_ALIVE_MAX_S (60 s). It does not ask the leader, so a
leader restart never restarts the fellows and kills their work.
health_check --leader checks leader reachability instead, for diagnosis.
Logs go to stdout and stderr (docker logs fellow) and to
/state/logs/fellow.out, which the fellow ships to the leader's log view.
Upgrades¶
The image is the code. swarm update and the leader's update skip
image-managed fellows (image_managed in the response, "(image)" beside the
version on the Training page), and a fellow refuses an UPDATE that reaches it
anyway - the bootstrap would start a second fellow beside it. Upgrade by
pulling the next version and recreating the container with the same volume and
SWARM_FELLOW_ID.
The image pins its runtime libraries (torch, transformers, accelerate, peft,
bitsandbytes, ...) in docker/fellow-constraints.txt; a bootstrap fellow takes
the newest its requirements allow. A pool can mix both: the leader only uses a
feature every fellow in a job declares, which keeps the wire format compatible,
but two transformers versions need not compute identical numbers.