docs: keep the fleet's shape out of prose

Site names, host assignments and hardware inventories were scattered
through readme.md, CLAUDE.md and a unit-file comment. They belong in the
deploy matrices — the single source of infra truth — and nowhere else.

readme.md now describes roles and measured hardware classes rather than
which box is where; the GPU-contention section is written as a check to
perform rather than a named host. CLAUDE.md gains a standing rule so this
does not creep back.
This commit is contained in:
Rob Thijssen
2026-08-31 17:56:43 +03:00
parent 57a3705de4
commit 942bbb2417
4 changed files with 55 additions and 41 deletions
+11 -12
View File
@@ -2,11 +2,12 @@ name: deploy
# Deploy — or validate — Quantus Planck nodes and their external GPU miners.
#
# Topology (the workflow is the source of infra truth,
# ~/git/architecture/deployment-gitea-actions.md):
# ~/git/architecture/deployment-gitea-actions.md — hosts belong here, and only
# here; see readme.md "Scope"):
#
# bob.hanzalova.internal quantus-node consensus, P2P, rewards
# ▲ QUIC/9833 (mesh only, rich-rule scoped to the miner host)
# benjy.hanzalova.internal quantus-miner 1× RTX 4090
# quantus-node consensus, P2P, rewards
# ▲ QUIC/9833 (mesh only, rich-rule scoped to that node's miners)
# quantus-miner GPU search
#
# The node and the miner are separate processes on separate hosts by design: the
# node is disk+network bound and belongs on an always-on box, the miner is pure
@@ -121,7 +122,6 @@ jobs:
# (doc/wormhole-rewards.md §6):
# quantus-node key quantus --scheme wormhole --words < mnemonic.txt
- host: bob.hanzalova.internal
site: plovdiv
inner_hash: "0x0000000000000000000000000000000000000000000000000000000000000000"
miners: benjy.hanzalova.internal # space-separated if more than one
# Second site, for decentralisation. Uncomment when provisioned, and
@@ -130,12 +130,11 @@ jobs:
# the point of running them separately (doc/wormhole-rewards.md §5).
#
# NOTE: a node with no miners authors nothing, because
# --miner-listen-port disables built-in mining. If Kyustendil is meant
# to be a plain relaying full node (the readme's suggestion), it needs
# a unit WITHOUT --validator/--miner-listen-port, not this one — and
# then it needs no inner_hash at all.
# - host: gramathea.kosherinata.internal
# site: kyustendil
# --miner-listen-port disables built-in mining. A node intended purely
# to relay and validate needs a unit WITHOUT
# --validator/--miner-listen-port, not this one — and then it needs no
# inner_hash at all.
# - host: <second-node>.internal
# inner_hash: "0x..."
# miners: ""
steps:
@@ -268,7 +267,7 @@ jobs:
run() { ssh $SSHOPTS gitea_ci@"${{ matrix.host }}" "$@"; }
fail=0
echo "--- unit (${{ matrix.site }}) ---"
echo "--- unit (${{ matrix.host }}) ---"
run systemctl is-active quantus-node.service
echo "--- version ---"
+13 -4
View File
@@ -83,8 +83,17 @@ node takes a preimage rather than an address, what publishing it does and does
not expose, and why the seed-phrase derivation step is a design flaw rather than
an operator error. Read it before changing anything about reward configuration.
## Before enabling the miner on benjy
## Before enabling a miner on any host
`benjy` already runs helexa `neuron` on `:13131` against the same RTX 4090. The
miner will saturate that GPU. `readme.md` documents the options; the deploy
deliberately does not arbitrate. This is an open decision, not an oversight.
A miner host may already be serving helexa `neuron` from the same GPU. The miner
will saturate it. `readme.md` documents the options; the deploy deliberately
does not arbitrate. This is an open decision, not an oversight.
## Keep the fleet's shape in the workflow
Host assignments, hardware inventories, site or geographic names, ISPs and
addressing belong in the `deploy-node` / `deploy-miner` matrices in
`.gitea/workflows/deploy.yaml` — the single source of infra truth — and nowhere
else. Do not restate them in `readme.md`, `CLAUDE.md`, `doc/`, or unit-file
comments, even as helpful context. Prose should describe roles and measured
hardware classes, never which box is where.
+2 -2
View File
@@ -12,8 +12,8 @@
# there is no tuning knob here worth more than the GPU power limit — which is a
# host-level concern (nvidia-smi -pl), not a unit-file one.
#
# --cpu-workers 0 is deliberate: benjy's i9-13900KF contributes ~3.9 MH/s next
# to the 4090's ~183 MH/s, for a couple hundred watts. Measured, not assumed.
# --cpu-workers 0 is deliberate: a current-gen desktop CPU contributes ~4 MH/s
# next to a 4090's ~183 MH/s, for a couple hundred watts. Measured, not assumed.
[Unit]
Description=Quantus external miner (GPU)
+29 -23
View File
@@ -3,12 +3,18 @@
Deployment for a [Quantus Network](https://quantus.com) Planck node and its
external GPU miner.
Two components, two hosts:
Two components:
| Component | Host | Role |
| --- | --- | --- |
| `quantus-node` | `bob.hanzalova.internal` | consensus, P2P, reward address. Disk + network bound. |
| `quantus-miner` | `benjy.hanzalova.internal` | Poseidon2-over-Goldilocks search on 1× RTX 4090. Pure GPU compute. |
| Component | Role |
| --- | --- |
| `quantus-node` | consensus, P2P, reward address. Disk + network bound. |
| `quantus-miner` | Poseidon2-over-Goldilocks search. Pure GPU compute. |
**Scope:** which hosts run what lives in the `deploy-node` / `deploy-miner`
matrices in `.gitea/workflows/deploy.yaml` and nowhere else — that is the
convention (`deployment-gitea-actions.md`) and it keeps the fleet's shape in one
place instead of scattered through prose. Don't restate host assignments,
hardware inventories, sites or addressing in this file.
They are split deliberately. Setting `--miner-listen-port` on the node
**disables its built-in CPU mining**, so the node authors nothing unless an
@@ -29,16 +35,17 @@ in registers, and there is no memory hardness at all. Measured on this fleet:
| Hardware | Hashrate | Power | Efficiency |
| --- | --- | --- | --- |
| RTX 5090 (`beast`, ×2) | 368 MH/s each | 600 W | 0.60 MH/s/W |
| RTX 5090 | 368 MH/s | 600 W | 0.60 MH/s/W |
| RTX 5090 @ 400 W floor | 281 MH/s | 400 W | 0.70 MH/s/W |
| RTX 4090 (`benjy`) | 183 MH/s | 450 W | 0.41 MH/s/W |
| Ryzen 9 9950X3D, 32t | 4.15 MH/s | — | — |
| Xeon E5-2620 v3, 12t (`bob`) | 0.50 MH/s | — | — |
| RTX 4090 | 183 MH/s | 450 W | 0.41 MH/s/W |
| current-gen desktop CPU, 32t | ~4 MH/s | — | — |
| older server CPU, 12t | ~0.5 MH/s | — | — |
A single 5090 at its minimum power limit out-hashes every CPU in the fleet
combined by roughly 24×. CPU mining is therefore disabled everywhere
(`--cpu-workers 0`), including on the GPU hosts, where it would add ~1% hashrate
for a couple hundred watts. The old Xeons are node-capable and mining-worthless.
Measured on representative hardware, not estimated. A single 5090 at its minimum
power limit out-hashes a couple of dozen server CPUs combined. CPU mining is
therefore disabled everywhere (`--cpu-workers 0`), including on GPU hosts, where
it would add ~1% hashrate for a couple hundred watts. Older server CPUs are
node-capable and mining-worthless.
GPU/CPU parity is verified upstream (`engine-gpu`'s `gpu_cpu_parity` example,
25/25 jobs) — the GPU is doing real work, not advancing a lying counter.
@@ -145,24 +152,23 @@ attached, both look perfectly healthy to systemd. Hence the counter check.
Upgrades are deliberate — bump `NODE_VERSION` / `MINER_VERSION` in the workflow.
There is no auto-update on a validator on purpose.
## Known conflict: helexa on `benjy` ⚠️
## Known conflict: GPU contention with helexa ⚠️
`benjy` already runs **helexa `neuron`** on `:13131` (`architecture/generic.md`
§11, `inference.md`), serving inference from the same RTX 4090 this miner will
saturate. They will contend directly: the miner drives the GPU to ~95%
utilisation and ~450 W continuously, which will make inference latency on benjy
by turns awful and unpredictable.
A miner host may already be running helexa `neuron` against the same GPU
(`architecture/generic.md` §11, `inference.md`). They contend directly: the miner
drives the GPU to ~95% utilisation and near its power limit continuously, which
makes inference latency on that host by turns awful and unpredictable.
Nothing here resolves that, and the deploy does not try to arbitrate. Options,
in rough order of preference:
1. Move mining to `beast` (2× 5090, and 4× the hashrate) and leave benjy to
inference. Requires checking what beast's neuron is doing first.
1. Put mining on a host that is not serving inference.
2. Make the miner yield — `--gpu-throttle-ms`, or an `ExecCondition` that
refuses to start while a model is loaded.
3. Accept the contention on benjy and treat inference there as best-effort.
3. Accept the contention and treat inference on that host as best-effort.
Decide before enabling `quantus-miner.service` on benjy, not after.
Check what a candidate miner host is already serving before enabling
`quantus-miner.service` on it, not after.
## Deviations from house convention