canado / docs /COMMUNICATION.md
funny790's picture
Upload folder using huggingface_hub (part 3)
ec09965 verified
|
Raw
History Blame Contribute Delete
8.77 kB
# Validator ↔ Trainer Communication Map
Every channel between the subnet's roles, in one place: who writes, who reads,
what carries integrity, and what happens when several validators run at once.
`docs/ARCHITECTURE.md` explains *why* the round works this way; this page is
the wire-level *how*.
Terminology worth pinning down first: the **trainer** publishes the round's
**manifest** (signed training receipts); each **validator** publishes a round
**receipt** (signed scoring/weights record). Validators never post manifests β€”
they gate, score, set weights, and publish receipts.
## The picture
```
Bittensor chain
commitments β–² β”‚ commitments, β–² weights
(gen refs) β”‚ β”‚ incentive, blocks β”‚ (per validator,
β”‚ β–Ό β”‚ independent)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ miners β”‚ β”‚ trainer β”‚ β”‚ validators β”‚ Γ— N
β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ (owner GPU boundary)β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β–² β–² β–² β”‚
β”‚ generator β”‚ checkpoints β”‚ manifest β”‚ β”‚ β”‚ β”‚ receipts (signed,
β”‚ repos β–Ό β–Ό β”‚ β”‚ β”‚ β–Ό per-validator prefix)
β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Hippius β”‚ β”‚ manifest bucket (Hippius S3) β”‚
β”‚ Hippius β”‚ β”‚ Hub (OCI) β”‚ β”‚ manifests/… logs/… β”‚
β”‚ Hub (OCI) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ receipts/<hotkey>/… index.htmlβ”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β–²
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” pool bucket β”‚ snapshots (tar+sha256,
TSBench-Forge ─► β”‚ owner poolβ”‚ β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ selected by
(private raw β”‚ builder β”‚ effective_block)
data bucket) β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
```
## Channels
| # | Channel | Writer β†’ Reader | Medium / key | Integrity | Multi-validator behaviour |
|---|---------|-----------------|--------------|-----------|---------------------------|
| 1 | Generator commitments | miner β†’ trainer, validators | chain reveal commitments, `metro-v1:gen:hippius:<repo>@<digest>` | on-chain, digest-pinned | read-only; identical view per block |
| 2 | Round seed | chain β†’ everyone | epoch-start block hash β†’ `seed_from_block_hash` | consensus | deterministic, shared |
| 3 | Training manifest | trainer β†’ validators | `manifests/round-<id>.json` + `manifests/latest.json` (manifest bucket) | signed by `[manifest] trainer_hotkey`; gated by `check_manifest` | read-only; single writer (the trainer) |
| 4 | Checkpoints / generators | trainer, miners β†’ validators | Hippius Hub OCI, `repo@sha256:…` | content-addressed digest self-verifies | read-only |
| 5 | Eval pool | owner pool builder β†’ validators | `pool/snapshots/block-<N>.tar` + `pool/index.json` (pool bucket) | sha256 pinned in the **signed manifest** (`eval_pool_key`/`eval_pool_sha256`, gated by `check_pool_pin`); index sha256 as legacy fallback for unpinned manifests | read-only; all validators resolve the same snapshot for a round and verify it against the pin |
| 6 | Weights | each validator β†’ chain | `set_weights` | on-chain, per-hotkey | independent by construction β€” no shared state |
| 7 | Round receipts | each validator β†’ auditors, dashboard | `receipts/<hotkey>/round-<id>.json` + `receipts/<hotkey>/latest.json`, shared `receipts/latest.json` pointer | signed per validator hotkey; public-read | **single-writer prefixes** β€” validators cannot clobber each other; only the convenience pointer is last-writer-wins |
| 8 | Receipts index | each validator β†’ dashboard | `receipts/index.json`, entries keyed `(round_id, validator_hotkey)` | unsigned, presentational only | merge-keyed read-modify-write; a simultaneous write can drop one entry until that validator's next round β€” never audit- or weight-bearing |
| 8b | Live chain status | validator (or `scripts/publish_chain_status.py`) β†’ dashboard | `status/chain.json`: block anchor, epoch grid, stage windows, revealed submissions (see `cascade.shared.chain_status`) | unsigned, presentational only | last-writer-wins whole-object overwrite β€” honest publishers write near-identical chain state; never audit- or weight-bearing |
| 9 | Training logs | trainer β†’ observers | `logs/round-<id>/<role>.jsonl` (+ optional wandb) | none (observability) | read-only |
| 10 | Trainer ↔ GPU pods | trainer orchestrator ↔ rented pods | SSH, receipt-sentinel stdout | wallet never leaves the orchestrator | n/a |
| 11 | Validator ↔ eval pod | validator ↔ its own GPU pod | SSH+scp (`--eval-hosts`) | only public checkpoint + report cross | per-validator, private |
The channels a verdict depends on (1–6) are all either on-chain, signed, or
content-addressed *and* deterministic per round β€” N validators reach the same
verdict independently, which is why weights need no coordination. Channels
7–9 are evidence and observability, never inputs to consensus.
Because storage keys aren't prefix-scoped, anyone holding a bucket's write
credentials can overwrite *any* object in it. The design assumes that and
anchors integrity above the storage layer: manifests fail the trainer-hotkey
signature gate if touched, checkpoints are digest-pinned, and the eval pool is
pinned inside the signed manifest. What write access *does* buy an attacker is
vandalism β€” deleted receipts, a defaced dashboard, stalled rounds β€” which is
detectable (signatures, the R2 mirror) but not preventable; the mitigation is
key hygiene: one pair per bucket, pool keys separate from manifest keys, forge
keys never shared beyond the owner.
## What a validator needs access to β€” and only this
| Resource | Access | Credential |
|----------|--------|------------|
| Bittensor chain | read + `set_weights` | hotkey wallet |
| Manifest bucket | read `manifests/…`, write `receipts/<own hotkey>/…` + index | `HIPPIUS_S3_*` |
| Pool bucket | read-only snapshots | `POOL_S3_*` β€” issue a pair separate from the manifest-bucket keys (S3 keys aren't prefix-scoped; a shared pair could write the pool) |
| Hippius Hub | read (pull checkpoints/generators) | `HIPPIUS_HUB_TOKEN` |
| HF benchmark datasets + bench sidecar (opt-in only, see below) | public read | `HF_TOKEN` if gated |
The benchmark sidecar (`benchmarks/`, or an SSH eval pod via `--eval-hosts`) is
**not** part of a stock validator. All three of its call sites default off:
`[eval] run_benchmarks` (log-only numbers on a dethrone), `[scoring]
gift_gate_mode` (the gift-eval gate β€” the only mode in which the sidecar is
load-bearing, since `enforce` turns a sidecar failure into an inconclusive
round), and `[scoring] cascade_enabled` (warm-start promotion, which prefers
the trainer-signed `bench_scores` on the manifest and touches the sidecar only
as a non-consensus-safe fallback for manifests that carry none).
Deliberately **not** on the list: the TSBench-Forge raw-data bucket. Validators
consume forge data only through the built pool snapshots (channel 5). The raw
catalog + parquet relay (`tsbench-forge-sources` on Hippius β€” see
`docs/EVAL_POOL.md`) is an owner-orchestrator-side secret; keeping it private
is the anti-gaming lever that stops miners from fitting generators to the
held-out pool.
## TSBench-Forge β†’ validator data path
```
forge repo cron (scrape-data.yml)
└─ sync_storage.py ─► s3://tsbench-forge-sources [private, owner-only]
owner orchestrator
└─ aws s3 sync … ./tsforge
cascade-pool publish --sources tsbench_forge ─► pool bucket snapshots
validators
└─ select snapshot by effective_block, verify sha256, rotate windows by round seed
```
One bucket contract end to end: `sources.yaml` + `data/<source_id>/<date>.parquet`
at the relay, deterministic tar + `pool/index.json` at the pool. The forge cron
and the pool-publish cron are decoupled; the staleness guard
(`max_stale_days`, fail-loud when every feed is stale) lives in the pool
builder so a dead scraper can't silently erode freshness.