# Single source of truth for active subnet configuration. Miners, the trainer, # and validators all read this file via cascade.shared.config. Schema is versioned. # Rationale for these settings lives in docs/ (ARCHITECTURE.md, VALIDATOR.md). schema_version = 1 [subnet] netuid = 91 # MAINNET netuid name = "cascade" description = "Synthetic time-series training data, KOTH on CRPS + MASE of a Toto2-4M trained from scratch." [generator] # Bounds the trainer enforces on every generator's output; a violation # disqualifies the generator for the round. corpus_n_series = 16384 # legacy exact-count drain; superseded while corpus_target_points # is armed (below), still the probe/off-switch denomination min_length = 64 # per-series length band (timesteps) max_length = 4096 # keep in lockstep with [training] context_length max_total_points = 2_000_000_000 # Points-denominated materialised drain (DEC-CA-0031): the cache_reuse / # `cascade verify` drain stops at this many cumulative values-points instead of # at a fixed series count — 67_108_864 = 16384 × 4096, today's corpus size, but # a miner may now spend it as 65k×1024 (or any shape inside the length band). # GP/kernel draws scale ~cubically in length, so the fixed count pinned those # priors into their worst corner; freeing the count is what makes them # affordable inside max_generate_seconds. Inert for the live stream_cpu feed # (which already stops at the training token budget) and for the dedup probe # (count-denominated by construction). Not in contract_digest — no [generator] # key is — so this is an operational (trainer-side) knob, no coordinated # restart. 0 = legacy exactly-corpus_n_series drain. corpus_target_points = 67_108_864 max_generate_seconds = 7200 # batch-drain budget: RLIMIT_CPU + wall for a materialised # (cache_reuse / verify) generator run. 1800 → 7200 # (DEC-CA-0031): at 1800 every measured GP-prior was 13–116× # over budget; 7200 admits the gp prior at L=4096 with the # relative-jitter Cholesky (docs/MINER.md) and the cheaper # priors at reduced length under corpus_target_points. # RLIMIT_CPU sums across threads (multiprocessing is a # blocked import), so multi-core BLAS burns it faster — # single-thread your linear algebra. # Streaming per-frame stall window (seconds between series before the child is # killed). Split out of max_generate_seconds (DEC-CA-0031) so the batch-budget # raise above does NOT widen how long a stalled generator can idle a heat/final # lane — 1800 preserves the pre-split streaming behaviour exactly. 0 = fall # back to max_generate_seconds. stream_stall_seconds = 1800 max_memory_mb = 4096 # rlimit for the generation sandbox max_repo_mb = 128 # cap on the fetched submission (code-only, no weights) # Per-series variates (DEC-CA-0041). 32 = Toto 2 training-width parity (owner # 2026-09-09). Measured: C=32 fits a 24 GB card at production shape (19.5 GB, # linear in C) and costs no per-token throughput. Eval-side mv_channels stays # ≤ 8 (EVAL_POOL.md); OOM is a challenger fault and never re-queues. max_channels = 32 # The record carrier (DEC-CA-0020). interface_version 1 = record-or-array yield # with `values` only; every reserved field (mask, start, freq, group_id, roles, # labels, quantiles) is hard-rejected until the release that consumes it. Bumps # only when a field is accepted+consumed together (the accepted set then folds # into [training] — one deliberate contract_digest bump). interface_version = 1 # Carrier cap in canonical payload BYTES — the denomination that stays correct # when non-values fields arrive. Originally 16e9 = max_total_points × 8 # (float64), calibrated to the MATERIALISED DRAIN and "numerically inert" by # design. v0.6.0 (d4141d8) armed it on the cumulative live STREAM, where a # full-budget run legitimately serves token_budget × 8 bytes (finals: 40B # tokens ≈ 320 GB) — every r33 run silently stopped at ~2.0B tokens (~5-15% # of budget; OPSLOG 2026-08-21). HOTFIX: raised above the largest stream # budget so the consumer's token-budget stop binds again (pre-v0.6.0 # semantics); drains stay memory-guarded by max_total_points. Restore a # stream-shaped cap (per-corpus, cycling-aware) in v0.6.1. 0 disables. max_payload_bytes = 400_000_000_000 # Per-series cap on the masked fraction, active only once "mask" is accepted # via [training] accepted_fields (DEC-CA-0023). max_missing_frac = 0.5 # Channel-redundancy gate (DEC-CA-0026): "off" | "shadow" | "enforce" against # max_channel_corr (max off-diagonal |Pearson| between standardized channels). # 0.999 targets ONLY the jitter-duplicate exploit. Keep off until the Stage 1 # shadow telemetry (channel_telemetry.frac_over_0999) clears honest # generators; then shadow; enforce last. Meaningless at max_channels = 1. channel_corr_mode = "shadow" # armed with the C=32 raise: labels only, drops # nothing; enforce waits on the shadow telemetry max_channel_corr = 0.999 # Structural data-quality gates (enforced by the trainer and `cascade verify`). max_abs_value = 0.0 # reject series whose peak |value| exceeds this; 0.0 = float32 ceiling reject_constant = false # reject flat (zero-range) series max_dup_fraction = 0.05 # cache_reuse only: max fraction of exact byte-duplicate series; 1.0 disables # Generation sandbox (trainer-side; never folded into contract_digest). # subprocess — rlimited child in a network namespace with a socket guard (default) # container — same child inside docker/podman with hardened flags; requires a # digest-pinned sandbox_image and its sandbox_python sandbox_mode = "subprocess" sandbox_image = "" # e.g. "ghcr.io/tensorlink-ai/cascade-worker@sha256:<64hex>" sandbox_python = "python3" # python inside sandbox_image sandbox_strict = false # true on production: no netns => refuse, don't degrade. # MUST be true (or container mode) before the dedup # probe is armed (dedup_probe_series > 0): the probe runs # generator code on the orchestrator, which holds the # eval pool and wallet (DEC-CA-0008). Left false at merge # because worker pods are UNVERIFIED under strict — flip # is its own change, after verifying pods, testnet first. [training] # The FIXED training contract — identical for king and challenger; folded into # contract_digest. Trains a Toto2 backbone from random init on each corpus. # Arch integers verified against Datadog/Toto-2.0-4m config.json (2026-07-02). base_arch = "toto2" arch_preset = "toto2-4m" base_arch_digest = "953e40d292e037ea07ffea25ae1fdf2e8b7dc8990276c081fa67fb4371396147" # sha256 of frozen arch+init; recompute with `cascade-trainer --offline` # architecture (head_dim fixed at 64 across the Toto2 family; patch_size 32) d_model = 256 num_layers = 4 num_heads = 4 head_dim = 64 patch_size = 32 mlp_expansion = 2 d_ff = 688 # exact FFN hidden width from the released config.json num_quantiles = 9 # 9-level pinball head (0.1..0.9) — matches [eval]/scoring # objective / masking (Toto 2.0 Contiguous Patch Masking + quantile head) masking = "contiguous_patch" cpm_c_max = 16 cpm_p_max = 0.4 input_transform = "arcsinh_causal" # I/O lengths — must match [eval] so the trained model fits the eval windows context_length = 4096 horizon = 64 # From-scratch budget, enforced as a fixed token count (hours × ref throughput) # so king and challenger see identical compute; max_train_seconds is the hard guard. # DEC-CA-0042 (owner 2026-09-16, amended 21:30 the same day): the BUDGET stays the # 3h figure (3h × ref_throughput = 40B series-points — the univariate budget never # grows); only the WALL grew to 5h (max_train_seconds below). The budget is # series-points, so a C-channel corpus trains C× 40B channel tokens in the SAME # step count as a univariate leg — width buys tokens, hours stay fixed. target_train_hours = 3.0 ref_throughput_tokens_per_s = 3700000 # 20x (2026-07-15): L40S is ~20x the old 185k ref, so it was # finishing the budget early; 20x the TOKEN budget, same wall warmup_fraction = 0.05 batch_size = 64 # Toto 2.0 global batch size # Batch geometry at C > 1 (DEC-CA-0042, owner 2026-09-16; revises the # DEC-CA-0041 "sequences" fill armed 2026-09-09): batch_size counts SERIES # whatever the width — a C=32 batch is 64 series × 32 variates (Toto 2's own # geometry). The step-count collapse "sequences" existed to prevent (a C=32 # leg falling from ~152k to ~4.8k updates on a channel-token budget) is # closed by budget_denomination below instead: the budget is series-points, # so every width earns the same step count and a wide batch trains C× the # tokens per step. The wall (max_train_seconds) still binds first for a # width the pinned SKU cannot push through in the leg — deadline_hit as ever. batch_denomination = "series" # What one budget point IS at C > 1 (DEC-CA-0042, digest-bound drop-when- # default; "points" = legacy C×L billing). "series_points": a (C, L) series # costs L, so the channel-token budget scales with width — univariate 1×, # C=32 → 32× — and the stream, the trainer's counter, and cascade-audit all # stop on this rule (the corpus digest covers the consumed prefix). Needs a # worker image built from this release: an older worker parses no such key # and would bill C×L, stopping a wide leg C× early. Bit-identical at C=1. budget_denomination = "series_points" # optimiser (u-muP transfer: tune once on a small proxy width, pin the result) optimizer = "normuon_adamw" base_lr = 4e-3 # u-muP width-independent learning rate weight_decay = 2e-8 # ARMED 2026-08-27: part of the DEC-CA-0035 cut # (see the armed block below; was 0.05) lr_schedule = "wsd" # warmup-stable-decay (DEC-CA-0018): warmup only at # generation start; warm-started rounds re-enter FLAT at base_lr. # Finished form is a separate armable mechanism (fork-anneal or # ema_decay — arm at most ONE). wsd writes optimizer.safetensors # (~3x) so Muon/AdamW moments survive the round boundary. # [training] edit ⇒ contract_digest changed: restart trainer AND # validator together. # anneal_fraction = 0.15 # fork-anneal (DEC-CA-0029, wsd's deferred "D"): cosine decay # base_lr->0 over this fraction; annealed weights scored, # weights_stable rides beside for warm-start. OFF (0.0) = today's # artifacts, digest untouched. Arming is a contract cut (testnet # cycle + coordinated release). NEVER with ema_decay or # [telemetry] bench_anneal_fraction — one finished form only. # Toto2-aligned optimizer constants (DEC-CA-0035, digest-bound drop-when-default; # defaults = previously hardcoded behavior). ARMED 2026-08-27 (owner), one cut # with ema_decay 0.999 (the artifact the measurement scored) + weight_decay 2e-8. # NEVER with anneal_fraction / bench_anneal_fraction (trainer refuses). ema_decay = 0.999 # EMA finished form (DEC-CA-0033; the scored artifact) gen_seed_mix = 3 # DEC-CA-0033 N-seed generation mix (owner-armed # 2026-08-27: measured working at 3 seeds) — corpus # digest = rolling digest of the interleaved stream # (~1/3 budget per derived seed, ~sqrt(3) less # generation-seed noise) muon_momentum = 0.96 # Muon Nesterov momentum (default 0.95) muon_row_beta2 = 0.999 # NorMuon row second-moment EMA (default 0.95) grad_clip = 7.0 # train-loop grad-norm clip (default 1.0) adamw_beta1 = 0.91 # AdamW group betas (defaults 0.9/0.999) adamw_beta2 = 0.972 adamw_lr_scale = 0.018518518518518517 # 1/54 matrix:AdamW split (default 1.0) warm_lr_scale = 0.125 # warm-started runs train at base_lr x this; # from-scratch keeps full base_lr (default 1.0) umup_base_d_model = 256 # proxy width the HPs were tuned at train_seed_salt = 1337 # mixed into the (shared) training seed max_train_seconds = 18000 # hard wall-clock cap (5h, DEC-CA-0042); a time stop is flagged deadline_hit # Corpus feed mode (folded into contract_digest): # stream_cpu — live-stream from a CPU generator; byte-exact reproducible # stream_gpu — live-stream from a GPU generator; tolerance/same-hardware audit # cache_reuse — draw a fixed corpus once, multi-pass under the budget; byte-exact corpus_mode = "stream_cpu" # Accepted record-field set (DEC-CA-0020 layer 3): the payload fields the # carrier accepts AND the trainer consumes — accept-equals-consume is enforced # in code (only fields with a wired consumer load). DIGEST-BOUND via the # drop-when-default convention: [] is omitted from contract_digest (today's # digest is untouched), setting it is a deliberate bump → coordinated # epoch-boundary restart, the routine re-pin protocol. Arming order per the # roadmap: "mask" needs the pool's masked-history eval slice first; "roles" # values 0/1 may arm with multivariate, allow_future_known (roles=2) stays # false until docs/EVAL_POOL.md carries the covariate exogeneity rule. accepted_fields = [] allow_future_known = false # Owner-published shared real corpus (DEC-CA-0028): immutable ref # ("repo@sha256:<64hex>") of the frozen, licensed real-data corpus generators # may READ (opt-in real_corpus_dir constructor kwarg; repos stay code-only). # DIGEST-BOUND via drop-when-default: "" is omitted from contract_digest, so # today's digest is untouched; setting it is the deliberate bump. Arming is # FORBIDDEN until docs/EVAL_POOL.md carries the corpus/eval-pool disjointness # rule (provenance + time wall) and the DEC-CA-0028 pricing experiment has run. real_corpus_ref = "" # Pin the GPU SKU (exact torch device name). Empty = king and challenger must # match each other. Unpinned 2026-09-02 for per-round GPU choice # ([round] funded_pod_skus); folds into contract_digest — coordinated deploy. expected_gpu = "" # was "NVIDIA L40S" (DEC-CA-0002) # Training-runtime image (digest-pinned worker ref), injected as # CASCADE_TRAIN_IMAGE_DIGEST into each pod; final runs refuse a mismatched # runtime. Baked chain.toml inside the image carries an EMPTY pin (enforcement is # launch-env injection + the provisioner health gate; digest computed # orchestrator-side). Folded into contract_digest — re-pin ⇒ trainer AND # validators restart together at an epoch boundary, pods pull the new image. train_image_digest = "sha256:43c07d4207d7693517d74203f6783fdd2c2f91e3d475f38db38e4798ae97785e" # ^ ghcr cascade-worker:worker-v0.9.0 — built 2026-09-16 09:38 UTC from this # branch (publish-worker run 35080201768): budget_denomination (DEC-CA-0042) # parsed on the pod, PLUS check_loss_finite (840e900, 2026-09-14) which the # v0.8.0 image (main@9346a52) never carried — three NaN legs trained to the # wall on it. Re-pinned here (declared gating: no validator restart). # Extra final-stage model sizes. Each [[training.sizes]] is trained alongside # the primary in the final; the throne is decided on the COMBINED score. A size # overrides only shape fields + digest + throughput; all else inherits [training]. # # 20M (Toto2-22M) is DISABLED at launch. Before enabling: pin the arch to the # released Datadog/Toto-2.0-22m config.json, measure throughput, and recompute # base_arch_digest with `cascade-trainer --offline`. # [[training.sizes]] # arch_preset = "toto2-22m" # base_arch_digest = "c7ecca53ed939eabfc34ccdb8a698d69101526c24f299efa21c0c0c288c96792" # d_model = 512 # num_layers = 8 # num_heads = 8 # head_dim stays 64 ⇒ d_model = num_heads × 64 # mlp_expansion = 2 # ref_throughput_tokens_per_s = 90000 # MEASURE on the reference GPU for THIS size # Size-conditional overrides (DEC-CA-0027; unset = inherit [training]): # expected_gpu = "" # per-size duel GPU pin (exact nvidia-smi string; # # 300M+ pins H100-class — mind SXM vs PCIe variants, # # and measure ref throughput on THAT variant) # target_train_hours = 0.0 # per-size budget hours (313M increments ~6h; set # # jointly with the margin re-unitisation from the # # null-LCB noise-floor measurement) [round] # Twice-daily round cadence + two-stage (heat → final). Base seed = the chain # block hash at the epoch boundary; only commitments revealed strictly before it # are eligible. CONSENSUS-RELEVANT: every validator derives the epoch from this # for pool-snapshot selection, so it must move in LOCKSTEP across operators (a # validator on a stale value silently picks a different snapshot). NOT part of # contract_digest ([training] only). The heat fleet auto-sizes from this # (plan_fleet) — don't hand-pin gpus_per_pod/max_pods. epoch_blocks = 3600 # ≈12h at a 12s block time round_hours = 12.0 # informational: wall-clock span of an epoch # Scheduled switch (0/0 = steady state). Last used: 7200 → 3600 at 8726400, # 2026-07-29. To schedule another, the block must be a boundary of BOTH grids # (load-checked); set both back to 0 once it is safely past. epoch_blocks_prev = 0 epoch_activation_block = 0 # Rolling intake + era king (DEC-CA-0043, block-gated). Trainer POLICY: from # this block challengers train the moment they are funded, legs cross # boundaries, every boundary publishes ONE settlement manifest (era king + # every challenger finished since the last settlement), the king's leg is # trained once per era and cached. 0 = off (the boundary-synchronous trainer, # bit-identical to before). ROLLOVER recipe (owner call, one boundary for # everything): rolling_from_block = era_king_from_block = # tenure_blocks_from_block = cohort_maxt_increment_from_block = ROLLOVER # (a multiple of epoch_blocks × era_settlements — the era grid is absolute); # epoch_blocks = 900, epoch_blocks_prev = 3600, epoch_activation_block = # ROLLOVER (3h grid); ROLLOVER % 3600 == 0. Load-checked. Do NOT arm before # the six external validators run a release carrying the era envelope. # With every key at 0 the block is DECIDED ON CHAIN by validator stake # ([activation] below, DEC-CA-0045); typing a block here overrides that. rolling_from_block = 0 # Settlements per era (consecutive boundaries sharing one set of training # seeds, one init and one cached king checkpoint): 4 on the 3h grid = 12h # eras — today's king-leg cadence with 3h verdicts inside it. Inert until a # rollover block is set. era_settlements = 4 heat_train_hours = 1.0 # screening budget per competitor (on screen_size) heat_n_windows = 2000 # eval windows the heat screens on (= [eval] n_windows; # the full set — a smaller screen is noisy near the top) heat_num_samples = 100 # sample forecasts per window (0 = reuse [eval]); matches # the final's count so screen + duel rank on one estimator # Heat wall-clock cap: max(heat_guard_factor × heat_train_hours, floor), never # above max_train_seconds. RAISE the factor if heat pods are a slower SKU than # the reference box, else every heat time-truncates. heat_guard_factor = 1.0 heat_guard_floor_seconds = 900 # In-round re-queues for challengers whose heat dispatch died on an # INFRASTRUCTURE failure (storage layer / ssh transport) — miner-fault # failures never re-queue. 0 restores terminal drops. heat_infra_requeues = 2 finalists = 1 # challengers promoted from the heat to the final (top-N) # Tie-aware finalist cohort (DEC-CA-0012) — ARMED (owner 2026-08-20, # max_finalists=3): a leader the screen separated advances ALONE; a statistically # tied top is re-scored on a larger CPU-only slice (no retrain, no GPU) and # survivors advance too, capped at max_finalists. The validator judges the whole # cohort under a family-wise alpha/k. Bit-identical at max_finalists=1. # CRITICAL: tie_runoff_windows MUST STAY 0 while the jittered mix (DEC-CA-0019) # is active — the run-off assumes the draw is a permutation PREFIX, which the # jitter breaks (a "larger" draw returns the same mix_target_windows list, no # incremental evidence). With none, the run-off is skipped and the tied set # advances capped (the supported path). Re-arming needs a jitter-aware draw. max_finalists = 3 # cap on the advancing tied cohort (armed 2026-08-20; 1 = tie logic OFF) # Init-baseline heat SHADOW row (DEC-CA-0034; armed 2026-08-27, owner): the # warm-start init is scored on the heat slice and published as # HeatResult.init_baseline. Visibility only — never filters the heat # (heat-side has no enforce). Trainer-only key, no consensus surface. init_gate_mode = "shadow" tie_runoff_windows = 0 # MUST stay 0 under the jittered mix — see NOTE above # Wall clock for the WHOLE run-off, mirroring dedup_phase_seconds: on expiry # the pre-run-off tied set advances (capped) — a screen that cannot finish # must not sink the round it protects. Inert while max_finalists = 1. tie_runoff_phase_seconds = 900 # Anti-spam: a challenger hotkey that has entered a heat is burned and must # re-register to resubmit. Set false only on a throwaway testnet. # Go-live gate: only commitments revealed at/after this block ever compete # (0 = off). MAINNET: set to the announced launch block AT LAUNCH so netuid # squatters / rehearsal commits from before the official start never enter a # round (and never burn their one submission). commit_floor_block = 8622922 # OFFICIAL GO-LIVE BLOCK (deliberate): reveals before this never compete # (unit fixtures neutralize it in tests/conftest.py) one_submission_per_hotkey = true # ── Duel-only rounds (no heat), block-gated ────────────────────────────────── # From the first epoch boundary >= duel_from_block the heat screen is skipped: # every screened entrant seats straight into the duel in reveal order and # trains the full [training] budget beside the king on the L40S final fleet. # The provisioner rents no heat fleet and sizes the final to fit the whole # field inside the epoch — each lane runs floor((round_hours − 1.5h) / 3h) = 3 # legs back to back (pods carry a one-epoch TTL) — up to its pod ceiling # ([provisioner.final] max_pods). Seating is one-shot at the round start: # with duel_seat_all = true (default since 2026-09-08) the trainer seats the # WHOLE screened field and a fleet that came up short queues the extra legs # (the mid-final lane pool dispatches them as lanes join; the trainer logs # "FLEET SHORT" — top the lanes up, the round lands late otherwise). With # duel_seat_all = false (the pre-2026-09-08 rule) it seats only what the # lanes on file can finish before the boundary and the overflow waits for # the next round without spending its submission — round 565093567954236803 # seated 2 of 5 that way after the rental lottery delivered 1 of 2 planned # lanes. With 2 pods × 2 lanes a round fits 11 legs; each extra 2-lane pod # adds 6. The validator judges the whole cohort under alpha/k (DEC-CA-0012, # k = seated). Trainer-side ([round] is outside contract_digest). 0 = off; # rollback = 0 + trainer restart. duel_from_block = 8992800 # 2026-09-04 ~08:30 UTC boundary (multiple of epoch_blocks) duel_field_cap = 0 # 0 = no per-round cap; N = explicit cap (wins over duel_seat_all) duel_seat_all = true # seat every screened entrant; false = seats bounded by the lanes on file # ── Miner-funded compute (DEC-CA-0036) — armed, block-gated ────────────────── # Composes with duel-only above: legacy (operator-funded, duel-only) rounds run # until funded_activation_block; from it the funded queue IS the field and the # whole seated cohort still duels with no heat, exactly the duel-only shape. # "off" / "shadow" / "required" (funded queue IS the field; every seated # entrant duels, no heat). Everything below is inert before # funded_activation_block; legacy rounds keep running until it. # Details: docs/MINER_FUNDED_ROUNDS.md. funded_mode = "required" funded_activation_block = 9046800 # first funded round ≈ Fri 2026-09-11 20:30 UTC (evening; # owner 2026-09-10). The WHOLE release flips at THIS block, in # one boundary: the funded queue becomes the field AND cohort # max-T + increment (DEC-CA-0038/0039) + the [training] contract # cut all go live together (their *_from_block below == this). # The prior 08:30 boundary 9043200 runs the LAST old-world round # — a legacy operator-funded duel under the OLD scoring/contract/ # trainer; nothing new fires there. cascade-intake opens for # lium-key funding + private submissions right AFTER that morning # round, so miners have the day to submit and fund before the # evening funded round. MV scoring arms later, 9068400 (Mon 20:30). funded_queue_path = "funded_queue.json" # shared with cascade-intake (work_root-relative) # With funded_mode = "required": an unfunded boundary runs NOTHING (no king # leg, no pods, no manifest — validators just see no manifest and hold). # This is the elastic-cadence floor; the ceiling is the epoch grid itself. skip_unfunded_rounds = true max_rounds_per_day = 2 # cadence sizing/telemetry only (grid = epoch_blocks) funded_entry_ttl_hours = 36.0 # MUST track cascade-intake --ttl-hours (key TTL) # "rent": each funded leg rents ONE pod on its payer's Lium key (king + eval # stay operator-funded). Consensus-inert. funded_pods = "rent" payer_vault_dir = "payer_vault" # cascade-intake --vault-dir (work_root-relative) funded_pod_sku = "" # unused with funded_pod_skus set (per-round pick) # Worker image funded pods boot — keep = the fleet's pinned digest. funded_pod_image = "ghcr.io/tensorlink-ai/cascade-worker:worker-v0.9.0@sha256:43c07d4207d7693517d74203f6783fdd2c2f91e3d475f38db38e4798ae97785e" # sha256 of that image's cascade/**/*.py (scripts/worker_code_fingerprint.py # @). Every rented final/funded pod must hash its files to this # BEFORE a leg is dispatched — the digest env a template injects is echoed # faithfully by a host that started a STALE image under the pinned tag # (2026-09-12: Lium 91.224.44.222/.223 ran worker-v0.7.0 as "v0.8.0"; three # miners' legs stalled and were burned). Re-derive on every image re-pin. worker_code_fingerprint = "dd5236181d9267f5bae26d49e545cf6bfb06afc2d0618e7e25cf101034db763b" # 100 files, worker-v0.9.0 (43c07d42…); registry walk == branch tree 2026-09-16 funded_ready_timeout_seconds = 900.0 # How a funded leg's checkpoint leaves its payer pod. "harvest" (default): # CREDENTIAL-FREE pods — the worker trains --local-only (nothing at all in the # pod's env), the orchestrator pulls the checkpoint over the pinned ssh, # ingest-verifies it, and uploads it under its own identity. Needs a # funded_pod_image built from a --local-only-aware release. "robot": legacy # per-pod push-only Hub robot (revoked at teardown; Harbor expiry below). funded_pod_checkpoint = "harvest" funded_robot_duration_days = 1 # Seats per round (0 = finalist_cap); clamped to live market capacity minus # the king's reserve when the probe is on. Held-back seats wait unburned. # 99 = no cap (owner 2026-09-16: "there shouldn't be any cap on GPUs"): every # paid entry seats the round it queues in — each seat rents its own payer pod, # so wall-clock stays one leg, and legs the market cannot serve poll for # capacity (then the operator lane) instead of waiting a whole round. funded_field_cap = 99 funded_capacity_probe = true funded_capacity_reserve = 1 # Allowed GPU types, preference-ordered; each round runs entirely on the # most-available one (king included). Needs [training] expected_gpu = "". # Blackwell (RTX5090, B200/B300) waits on a torch cu12.8 re-pin. funded_pod_skus = ["RTX4090", "RTX3090", "L40S", "L40", "A6000"] # Never rent executors whose CPU model contains any of these (case-insensitive # substring of Lium's specs.cpu.model). 2026-09-13/14: four legs stalled — # zero series inside the 1800 s stall window — and all four sat on Xeon # E5-26xx v4 hosts, while the same generators ran on every Ryzen host. # Applies to the king rent, every payer leg and the capacity probe. [] = off. funded_cpu_blocklist = ["Xeon(R) CPU E5-2"] # Per-SKU floor for the fixed calibration bench every freshly rented pod runs # over ssh before dispatch (~5 s; host_probe.host_bench composite tokens/s, the # same number the run telemetry reports as host_bench_tokens_per_s). Below the # floor the pod is released, its executor stays excluded for the round and the # leg rents again on the stale-image bad-pod budget — the miner is never # blamed; the JIT king gets the same gate on a fresh rent. 2026-09-15 round # 9068400: the 4090 fleet benched 394M–646M (median ~587M) on one equal wall — # the king's 394M host trained 70 % of its budget, 5900X hosts 100 %. An # unreachable probe logs and lets the pod through. {} / unknown SKU = off. # ARMED 2026-09-16 (owner: "add the guard for 4090") RTX4090 = 500M tokens/s: # 45 Lium 4090 legs Sep 13–16 (wandb) — hosts >= 570M trained 0.76–1.00 of the # budget, 489–516M hosts 0.54–0.84, 376–453M hosts 0.46–0.81; the floor drops # the bottom ~27 % of 4090 rentals and keeps every seated leg on an # equal-compute host. Other SKUs stay unguarded until benched. funded_host_bench_floor = { RTX4090 = 500e6 } # ── Open-market rounds (owner 2026-09-20; ON by default) ───────────────────── # funded_sku_per_leg: choose the GPU type PER LEG — every leg (the JIT king # too) rents the cheapest fitting executor across funded_pod_skus instead of # the whole round locking to one type. Fair because budgets are compute- # denominated and every leg completes its full token budget; needs # [training] expected_gpu = "" and the validators' manifest gate (_check_gpu, # gpu_mismatch on mixed types) lifted — that gate lifts at [scoring] # era_king_from_block (= ROLLOVER), and the per-leg choice takes effect at # the SAME block: until then every round still locks one type (#295), so # arming this key early changes nothing before the rollover. funded_sku_per_leg = true # Price guards on marketplace executors (USD; 0 = off). Cap the PER-LEG cost # (price/h × the SKU's measured wall below): an H100 at $1.30/h finishing in # an hour bills less than a 4090 at $0.50/h for three — a plain $/h cap would # exclude the cheaper pool. funded_max_price_per_hour is only a sanity # ceiling. An unpriced listing is skipped while either cap is armed. # $1.60/leg ≈ a 4090 at $0.50/h for 3.2 h or an H100 at $1.30/h for 1.2 h; # $1.50/h keeps anything exotic out. funded_max_price_per_hour = 1.5 funded_max_leg_cost_usd = 1.6 # Measured wall (seconds) of one full-budget leg per SKU: drives the per-leg # cost cap and the per-SKU latest safe start (a fast type — or a hosts.toml # lane carrying `sku = "…"` — may still start late in the epoch). A type # without an entry is assumed to need [training] max_train_seconds. From run # telemetry at the 150k-step budget: RTX4090 3 h 45 (hosts ≥ 500M tokens/s; # 3 h 40–4 h 34 observed), L40S 2 h 50 (2 h 47 measured), L40 ~3 h 10 (est.), # H100 ~1 h 40 (est., unmeasured — conservative). A6000 / RTX3090 are # wall-bound: no entry ⇒ the 5 h cap. Re-measure when the budget changes. funded_sku_wall_seconds = { RTX4090 = 13500, L40S = 10200, L40 = 11400, H100 = 6000 } # King pod rents JIT at the round's SKU (operator-billed); retire the # provisioner's final stage — the trainer ledgers and sweeps these pods. funded_king_rent = true # Detached dispatch (2026-09-18): every remote leg and pod-side bench runs in # its own session on the pod (setsid+nohup; stdout/stderr/pid/exit_code in a # per-leg run dir under _train_work/_dispatch) and the trainer polls it with # short ssh calls, tolerating an unreachable pod for # dispatch_reattach_grace_seconds before the leg is declared lost. Replaces # the attached form, where one dropped ssh session failed a healthy leg # (09-13 5Co2Te; 09-17 5DoJQ; 09-18 02:52 all seven Oslo lanes → 8 legs # re-queued). Credentials still go over stdin only. false = attached. detached_dispatch = true dispatch_poll_seconds = 30 dispatch_reattach_grace_seconds = 900 # Hybrid fallback: a funded leg (or the JIT king) still WAITING for marketplace # capacity takes an OPERATOR final lane from hosts.toml the moment one is on # file — operator-billed for that leg only; legs already running on their # payer's pod are untouched. false ⇒ funded legs never run on operator lanes. funded_operator_fallback = false # Direct submissions + champion-only publication (DEC-CA-0036, second half; # SHIPPED INERT): code POSTs straight to cascade-intake (same request as the # Lium key), lives in the operator-private store below, and rides the chain as # an ordinary hippius payload under the reserved vault/direct namespace — no # validator change, no digest change. Only a THRONE publishes (champions/ # on the manifest bucket, sn100-top-model style): "crown" immediately, # "delay" after N reign rounds, "dethrone" at hand-off (every policy reveals # a deposed vault king at latest). Losers stay private forever. # ARMED with this release (owner 2026-09-09, reversing the 2026-09-02 # "stay off"): cascade-intake opens for private submissions + lium-key funding # right AFTER the 08:30 morning round (9043200); the first funded round to # consume them is the evening 9046800. Policy "dethrone" = maximum privacy — the LIVE king's code stays # private for its whole reign and publishes only at hand-off, so history stays # re-derivable while a reigning edge is never exposed. Flip to "delay" (+N # reign rounds) or "crown" (immediate) before Friday if less privacy is wanted. submission_vault_dir = "submission_vault" # work_root-relative operator-private store champion_publish = "crown" # a new king's code goes public as soon as it is crowned (owner 2026-09-14: "we want it immediately available") champion_publish_delay_rounds = 1 # Content-level duplicate screen (pre-heat): re-uploads, comment shuffles, # and rename-only copies of the king or an earlier-committed challenger lose # their heat slot; the earliest submission keeps it. Pairwise only — never # transitive clusters. Dropped copies still burn their one submission. # NO similarity threshold: a ratio bar (originally sim >= 0.99, with a sketch # tier for oversize streams) is gameable by spacing edits just under it — # operators were already laddering variants at 0.90-0.986 — and it produced # the one confirmed false positive (a round's eventual finalist). Drops ride # on EXACT identity only: same tree, same normalized tokens, same tokens # modulo renames — and same probe OUTPUT once dedup_probe_mode enforces (a # zero-delta re-roll produces byte-identical probe bytes, so the spam a # threshold would target is caught ungameably). # ENABLED enforce 2026-07-26 by owner decision (DEC-CA-0008). The live-shadow # step was substituted by offline replay of the real screen on rounds # r1206/r1207/r1208 (drops: 2/0/0, all exact-identity or denied refs — zero # similarity-based verdicts possible since the threshold was removed). dedup_mode = "enforce" # off | shadow | enforce (exact tiers) # config_only tier (identical .py, differing configs): SHADOW-logged until # this is true — config sweeps are ticket-spam AND the legitimate fork path. # Block-gated: with dedup_config_only_from_block set the tier drops only in # rounds whose epoch boundary is at/after it (shadow before). Trainer-side. dedup_config_only_enforce = true dedup_config_only_from_block = 8992800 # = [round] duel_from_block # Cost caps: tokenizer input is attacker-chosen up to max_repo_mb (~2s/MB); # the retained-token cap bounds the config_only delta measurement, the only # quadratic pass left. Field repos are ~100KB / 7-11k tokens (~40x headroom). dedup_max_tokens = 50000 # 0 = uncapped; do NOT run 0 in production dedup_max_text_mb = 4 # tokenizer input budget per repo # Wall clock for the WHOLE screen. On expiry the field proceeds unscreened: a # screen that cannot finish must not sink the round it protects. dedup_phase_seconds = 900 # Behavioral probe: sandbox-draw N series per surviving entrant under the # shared round seed, twice. Twice-differs = non-deterministic generator # (contract violation, the entropy re-roll). Identical probe bytes across # entrants/vs-king = same process regardless of code. Probe drops gate on # dedup_probe_mode INDEPENDENTLY of dedup_mode — within-family heat spreads # suggest nondeterminism is widespread, and probe drops burn hotkeys, so the # probe ships SHADOW while the static tiers enforce. dedup_probe_mode = "shadow" # off | shadow | enforce # Probe OFF (owner, 2026-07-26): the probe EXECUTES miner code on the # orchestrator even in shadow (shadow gates the drops, not the execution). # To arm later: verify worker pods + this host under [generator] # sandbox_strict = true (or container mode), flip that first, then set # series = 8 with probe_mode = "shadow"; enforce only after reviewing # shadow verdicts. The static tiers above are independent and stay on. dedup_probe_series = 0 # 0 = probe off # Total probe wall clock; the per-draw budget is derived from it (budget / waves # / 2 draws), so N hostile entrants each burning their clock cannot stretch the # stage past this. The probe REFUSES to run unless [generator] has # sandbox_mode = "container" or sandbox_strict = true — shadow mode gates the # drops, not the execution, and this host holds the eval pool and the wallet. dedup_probe_budget_seconds = 600 # Genesis baseline king (burn-until-dethroned): a PUBLIC Hippius generator ref # (repo@digest). When set, the trainer trains THIS generator as the king # whenever no on-chain champion has a resolvable commitment — a fixed, # un-earnable floor — and the validator BURNS emission to burn_uid until a real # miner dethrones it (the king entry carries a sentinel uid = -1). Empty = off: # promote the lowest-UID miner to interim king (legacy behaviour). genesis_generator_ref = "cascade/genesis-baseline@sha256:56c68bdac31718db7c59708c9678b19347fe7ef2f8a696bdd5f3ad4548e17a65" # submissions_db_path = "trainer_submissions.json" # relative ⇒ under --work-root # Timed-reveal safety margin (blocks): `cascade deploy` targets its timelock # reveal at `epoch boundary − reveal_margin_blocks` — hidden all window, public # only the last ~5 min. Must absorb commit-inclusion + drand reveal jitter (a # reveal at/after the boundary misses the round) while staying under a copier's # automated turnaround. Tighten only after measuring jitter on a live round # (scripts/measure_reveal_jitter.py). # Deliberately UNCHANGED by the 2026-07-28 24h → 12h round switch. This is an # absolute quantity anchored to the boundary: both terms that size it — chain # jitter and copier turnaround — are properties of the chain and the adversary, # not of the round length, so halving it alongside epoch_blocks would cut the # jitter headroom for no reason. The only coupling is the public-exposure # fraction, 25/7200 = 0.35% → 25/3600 = 0.69% of a round: still negligible. # Sole hard constraint is reveal_margin_blocks < epoch_blocks (25 < 3600). reveal_margin_blocks = 25 # Scaling seam: which size the heat screens at, and which size(s) decide the # throne. arch_preset names from the size registry; >1 throne size = combined score. screen_size = "toto2-4m" throne_sizes = ["toto2-4m"] [eval] # Held-out eval windows, identical for king and challenger. The slice is private # and rotates per round (seeded by block hash) — see cascade.validator.windows. eval_dataset = "cascade-private-v1" # identifier the validator matches on eval_source = "private-rotating" # rotating private pool (vs a fixed public set) window_pool = "" # Hippius Hub ref (repo@digest) of the held-out pool; # build/push with `cascade-pool build --out ./pool --upload` # (see docs/EVAL_POOL.md) num_samples = 100 # sample forecasts drawn per window n_windows = 2000 # eval windows drawn per round from the pool context_length = 4096 # must match [training]; pool must supply >=4096+horizon windows horizon = 64 # Jittered round mix (DEC-TB-0003 port, DEC-CA-0019) — ARMED AT RELEASE, # IMMEDIATE activation (owner decision 2026-08-20, fork risk explicitly # accepted): rounds whose epoch boundary is >= mix_from_block draw the # jittered mix; every earlier round keeps the legacy uniform permutation # byte-identically (pinned by test). Block 8895600 = the 2026-08-21 08:30 UTC # boundary — the first boundary not yet scored at deploy time (the block must # NEVER be set in the past: audit replay and resync re-derive earlier rounds # under each round's own rule via the receipt's epoch_start_block, so a past # block would contradict already-published receipts). Moved 8892000 → 8895600 # on 2026-08-20 22:30: the fleet deploys in one coordinated event AFTER the # 8892000 round is already in flight, so 8892000 would have been in the past. # ALL validators must restart on this release before the 8895600 round's # manifest is scored, or verdicts fork from that block until they do. # Expected mix stays equal-weight per domain (capacity-capped); realised mix # is unpredictable. mix_target_windows: the jittered draw size (0 = # n_windows). Calibrated on the 2026-08-19 production snapshot: 1200 of # ~2400 rotates the mix hard (per-domain p1–p99 spans ~6x) with effective # domains ≥ 5.0 and P(<4.0)=0 over 2k simulated rounds; at 2000 the caps # crush the jitter (~±7%). Class rotation (mix_class_keep_frac) is inert # until pool snapshots carry dgp_class labels (builder change in this # release; arm by republishing the pool). mix_from_block = 8895600 # ACTIVATION epoch boundary (2026-08-21 08:30 UTC); 0 = off mix_jitter_alpha = 4.0 # symmetric Dirichlet over domains (expectation uniform) mix_block_slots = 8 # allocation unit: a cell is absent or gets >= 1 block mix_class_keep_frac = 0.7 # per-round dgp_class activation fraction mix_series_bag_frac = 0.7 # per-round series eligibility (salted-hash bag) mix_target_windows = 1200 # DEC-CA-0032 two-tier domain split: domains too small to fill an even share # of the draw always draw AT capacity (the flat jitter could starve them # below even their caps); the remaining domains split the rest evenly under # the tight tier alpha (+/-2-3pp on a ~4-domain residual set). Degenerates to # a tight even split over ALL domains once every domain clears an even share # — i.e. converges to uniform as pool capacity floors (300/domain) land. # CONSENSUS: all validators must flip at the same boundary — set # mix_tier_from_block only in a coordinated release (0 = flat split). mix_tier_from_block = 8942400 # ARMED 2026-08-27 (owner): two-tier even-domain # split live from r47's epoch — same boundary as # the recipe cut, one coordinated validator update mix_tier_jitter_alpha = 75.0 # jittered draw size; inert until mix_from_block is set # Scored horizon ladder: from scored_from_block the verdict draws one # even-by-domain rung per horizon over the round's snapshot (rung sizes # equalised, n_windows total; the 720 rung needs series >= 784 steps) and the # pooled rows go through the unchanged paired bootstrap. CONSENSUS — same # rollout as mix_from_block: every validator must run this release before the # block. [] / 0 = single-horizon rule. scored_horizons = [64, 256, 720] scored_from_block = 8992800 # = [round] duel_from_block — LIVE on main since # 2026-09-04; no longer tied to funded_activation_block # (which moved to 9046800). DO NOT move a passed gate. # Public-benchmark logging (LOG-ONLY; never feeds scoring/weights/KOTH). Runs # out of process via the benchmarks/ sidecar; best-effort. See benchmarks/README.md. run_benchmarks = false # off by default — opt in per validator benchmark_project_dir = "benchmarks" # path to the sidecar project benchmark_suites = [] # subset of ["gift-eval","boom","time"]; [] = all three benchmark_num_samples = 0 # 0 reuses num_samples above benchmark_max_series = 0 # cap datasets/tasks for a fast smoke run; 0 = full benchmark # Cascade post-publish duel bench (used only when [scoring] cascade_enabled). # After each manifest publishes, the trainer benches BOTH duel checkpoints on # GIFT-Eval/BOOM/TIME and publishes a separate signed report # (benchmarks/round-.json). bench_hold_max_hours caps how long the # provisioner holds the round's FINAL pod for that sweep past its normal # teardown signal; the hold releases early when the bench_complete marker # lands. Inert while cascade_enabled = false. bench_hold_max_hours = 9.0 # king+challenger bench SEQUENTIALLY on the one final pod # (same-pod parallel launches preempt each other) — sweep is # ~45-60min WITH data staged; raised 3.0→6.0 (2026-08-07) after # rate-limited HF downloads on token-less pods ate the 3h hold; # raised 6.0→9.0 (2026-08-20) for DEC-CA-0012 cohorts: a k=3 # cohort benches 4 legs, and 9h stays inside the ttl_epochs=1 # pod lifetime measured from the JIT final rent # twice. Data now pre-pushed from the box at final-rent time. # Public-benchmark no-regression gate data (CONSENSUS; used when [scoring] # gift_gate_mode != "off"). Enforcing validators need the data pinned locally. gift_gate_datasets = "" # gift-eval config subset ("" = full 97 configs) gift_gate_num_samples = 0 # 0 reuses num_samples gift_gate_data_dir = "" # pinned benchmark data dir ("" = suite env vars) gift_gate_timeout_s = 3600 # per-checkpoint gift-eval timeout [scoring] # King-of-the-hill — tenure-DECAYING dethrone margin (DEC-CA-0016, armed at # release by owner decision 2026-08-15): the bar a challenger must clear # starts at win_margin_start for a fresh king and decays linearly to # win_margin_end over margin_warmup_rounds of tenure (~1.25% by round 4, # floor from round 8 ≈ 4 days at the 12h cadence, held forever). Tenure # survives warm-start promotion, so a long hold defends the floor. # CONSENSUS-SENSITIVE COORDINATED UPGRADE: margin is judged per-validator and # this config activates the decay AT release — every external validator (6) # and the trainer must upgrade in one coordinated window # (docs/MARGIN_DECAY_ROLLOUT.md). While the fleet is mixed, any round whose # challenger LCB lands between the decayed and flat margin FORKS verdicts. # HARD GUARDRAIL: win_margin_end must stay > 0 — the floor is the safety # property (0.5% sits above the bootstrap noise band, so a statistically # tied challenger never wins by luck; a 0 or sub-noise floor becomes a slow # term-limit lottery). win_margin_start = 0.01 # fresh-king margin (tenure 0) from margin_activation_block # Scheduled change of the fresh-king margin (same shape as epoch_blocks_prev / # epoch_activation_block): rounds whose epoch boundary precedes # margin_activation_block are judged at win_margin_start_prev, later rounds at # win_margin_start. Every validator resolves it from the round's block, so # restart timing never forks a verdict; cascade-audit replays each receipt # under its own value. KEEP the pair after the flip (pre-flip receipts replay # against it). 0.0 / 0 = no scheduled change. win_margin_start_prev = 0.02 # in force for rounds before margin_activation_block margin_activation_block = 8992800 # = [eval] scored_from_block / [round] duel_from_block # Cohort-duel family-wise correction (DEC-CA-0038): Bonferroni alpha/k -> a # shared-resample max-T (Westfall-Young), exact under the k challengers' real # positive correlation instead of the loose union bound. CONSENSUS, block-gated # like margin_activation_block above: every validator resolves the rule from the # round's epoch block, audit replays each round under its own. Bit-identical at # k<=1 (single-challenger rounds never change). 0 = Bonferroni forever. cohort_maxt_from_block = 9046800 # Fri 2026-09-11 20:30 UTC — the evening funded round, one flip # with funded_activation_block. The 08:30 morning round (9043200) # is the last old-world round, still judged under Bonferroni α/k. # Increment-margin activation (DEC-CA-0039): from this block the dethrone bar # is judged in INCREMENT units (a fraction of the typical per-round gain over # the shared init) instead of a fixed % of the king's absolute score, so a # maturing lineage's small-but-real gains stay clearable. Same block as the # cohort max-T above = one coordinated validator-upgrade window for both. # margin_mode below stays "level" (the pre-gate rule); 0 = no scheduled flip. increment_from_block = 9046800 # = cohort_maxt_from_block (both flip at the evening funded round) # Increment margin ON COHORT rounds (DEC-CA-0039 amended 2026-09-19): until this # block a cohort round (k > 1) under the max-T is judged in LEVEL units — the # joint bound only knew the level statistic, so from 9046800 on the increment # margin never actually judged a funded round (every one had k > 1). From a # round whose epoch boundary is >= this block the max-T bound is the # %-of-increment statistic (baseline on the shared resample), stacking the two # corrections as DEC-CA-0039 describes. CONSENSUS — release-then-activate with # the external validators; audit replays each round under its own block. # 0 = cohort rounds stay level-judged (no scheduled flip). cohort_maxt_increment_from_block = 0 # Multivariate scoring activation (DEC-CA-0041): from this block a multivariate # window's channels are averaged into ONE per-window contribution (GIFT-Eval # weighting — the window counts once, not once per channel), so the round point # statistic agrees with the source-cluster bootstrap. Bit-identical on today's # univariate pool. ARMED 2026-09-09 (owner): 9068400 ≈ Mon 2026-09-14 20:30 UTC # (owner 2026-09-10: the Monday-arvo boundary, +12h off the 08:30 grid slot) — # well after the 9046800 Friday evening flip so the consensus flips never share # a boundary; one release covers both. Bit-identical until an MV-packed pool # publishes (pool build --mv-pack). mv_score_from_block = 9068400 win_margin_end = 0.005 # decayed floor (> 0!) — reached at margin_warmup_rounds margin_warmup_rounds = 8 # rounds of tenure to reach the floor (~4d at 12h rounds) # Era king (DEC-CA-0043) — CONSENSUS, release-then-activate with the external # validators; audit replays each settlement under its own block. From a # settlement whose epoch boundary is >= this block the validator verifies the # era envelope (era stamp on the era grid, seeds from the previous era's # start block, init = members_gen(era)[era % k] of the latest promotion with # effective_era <= era, king entry = the champion at the pointer this # validator judged, refs verified as of each entry's train_block, manifests # hash-chained and walked in order, same-GPU fallback lifted when no GPU is # pinned). Must equal [round] rolling_from_block and be >= # cohort_maxt_increment_from_block >= cohort_maxt_from_block (load-checked). # 0 = off (bit-identical to the ungated validator). era_king_from_block = 0 # Tenure / ripeness in BLOCKS from the rollover (CONSENSUS): the margin decay # and the promotion clock keep their wall-time length across the 3600 → 900 # grid switch instead of running 4× faster. Both *_blocks values are inert # until tenure_blocks_from_block is set (= ROLLOVER). tenure_blocks_from_block = 0 margin_warmup_blocks = 28800 # = 8 rounds × 3600 blocks (~4 d) — same decay as today cascade_reign_blocks = 18000 # = 5 rounds × 3600 blocks (~2.5 d) — same ripeness as today king_resync_max_blocks = 18000 # = king_resync_max_rounds (5) × 3600 blocks — same valve as today min_windows = 200 # below this many common windows, no decision min_clusters = 0 # min distinct window clusters (pool `source` labels) # for a decision; raise to ~30 once the pool # carries source labels. 0 = off. bootstrap_B = 10000 # paired-bootstrap resamples bootstrap_alpha = 0.05 # one-sided LCB level dethrone_cp = 1 # consecutive round wins to take the throne # Reward routing: weight split across the current king plus up to # reward_prior_kings previous distinct kings still registered; if none are # registered, all weight burns to burn_uid. 0 = pure winner-take-all. reward_prior_kings = 4 burn_uid = 0 # weight sink (owner burn slot) king_decay = 0.5 # geometric decay across [king, prior kings]: king ∝ 1, # prior ∝ 0.5, 0.25, … — the king is UNAMBIGUOUSLY the # highest-incentive UID (king resolution depends on it). # 1.0 = flat split (ambiguous; do not use in production). # 1.0 = flat split, <1 skews to the current king # Public-benchmark no-regression gate (cascade.eval.gift_gate). AND-ed with the # private-pool decision: can BLOCK a dethrone, never grant one. Start on # "shadow" to calibrate the tolerance before enforcing. gift_gate_mode = "off" # "off" | "shadow" (log-only) | "enforce" gift_gate_tolerance = 0.03 # relative slack before the challenger fails gift_gate_min_configs = 15 # shared configs below which the gate is uncomputable # Margin denomination (DEC-CA-0027): "level" = LCB of (king-chal)/king (the # from-scratch rule, exact current behaviour). "increment" = the warm-start-era # rule: the shared init is scored as a third paired reference and the margin # prices a fraction of a typical per-round improvement — flip it WITH the # warm-start era (E2), across all validators at once ([scoring] is # fleet-consensus). Random-init rounds are judged at "level" automatically. # margin_increment_floor floors the increment normaliser at this fraction of # the baseline level, so near-zero increments can't explode the statistic. margin_mode = "level" margin_increment_floor = 0.01 # Cascade — king-reign promotion / warm-start (cascade.validator.cascade + # cascade.trainer.promotion, DEC-CA-0013 propose-and-verify). After a ripe # reign the TRAINER selects up to cascade_top_k reign checkpoints as the next # warm-start generation (signed PromotionRecord, promotions/gen-.json) and # rotates rounds across the members; validators verify the declaration against # the envelope below instead of re-deriving the selection. The king PERSISTS # on promotion (DEC-CA-0004); the throne is never vacated. cascade_enabled = true # ARMED 2026-08-05: warm-start promotion + post-publish duel bench live # Consecutive undethroned ROUNDS before a Cascade fires — "survived N # challenges". Renamed from cascade_reign_days on 2026-07-28 (the old key is # still accepted): the clock used to divide reign blocks by a fixed 7200-block # day, which at the 24h cadence happened to equal one round. At 12h rounds that # same 7 would have silently meant 14 challenges. The clock is now epoch- # relative (cascade.validator.cascade.reign_rounds), so this number means the # same thing at any cadence and needs no re-tuning when the round length moves. # Lowered 7 → 5 at arming (owner, 2026-08-05): at the 12h cadence, 5 rounds # ≈ 2.5 days per promotion cycle. CONSENSUS-relevant — every validator must # run the same value or cascades fire at different rounds (divergent pins). cascade_reign_rounds = 5 # Promotion ENVELOPE (DEC-CA-0013). CONSENSUS-relevant like the reign clock: # every validator must run the same values or their accept/reject verdicts on # a declared promotion fork. cascade_top_k caps how many member checkpoints # one promotion may declare (the trainer may declare fewer — the selection # policy is trainer-side and free to evolve); cascade_quality_epsilon is the # quality floor: every member's bench geomean must sit within (1 + epsilon) # of the best verifiable score in the reign, so diversity can never be bought # with a materially worse init. cascade_top_k = 3 cascade_quality_epsilon = 0.05 # Block-gated contract transition (2026-08-20, WSD flip — DEC-CA-0018): rounds # whose epoch boundary precedes contract_from_block score against the pinned # PRIOR contract digest (the warmup_cosine contract every round through # boundary 8892000 trained under), so validators restart onto this release at # any time without sacrificing the in-flight round; rounds from block 8895600 # (2026-08-21 08:30 UTC) accept only the digest recomputed from [training] # above. Same activation block as mix_from_block — one flip boundary for the # whole release. Fleet-consensus values: keep identical across validators. # Retire (""/0) in the release after the transition round ages out. # ARMED 2026-09-10 (owner): this release's [training] cut (expected_gpu ""→ # unpinned, train_image_digest → worker-v0.8.0 sha256:9d5bc70d…, # batch_denomination = "sequences") takes effect for rounds from block 9046800 # (Fri 2026-09-11 20:30 UTC — the evening funded round; one flip with # funded/max-T/increment). Every earlier round — INCLUDING the 08:30 morning # round at 9043200 — verifies against the pinned PRIOR digest below: the exact # contract_digest the LIVE mainnet manifests carry right now (read off # manifests/latest.json 2026-09-10), so the morning round runs on the OLD # trainer/contract and validators restart onto this release ANY TIME before # 9046800 without sacrificing an in-flight round. New digest recomputed from # [training] above = 22081e46…; verify after any edit. # (Retires the aged-out c6d24621…/8942400 DEC-CA-0035+0033 transition pair.) # 2026-09-16 (DEC-CA-0042, amended): max_train_seconds 18000 (target_train_hours # stays 3.0) / batch_denomination "series" / budget_denomination "series_points" # move the digest again (recomputed = f88909e9…). None of them is a LOCKED term, and # declared_contract_from_block (8942400) is armed below, so this cut ships on # the trainer + worker image alone — validators keep accepting the declared # body; the strict path (and this prior pin) only matters for a body-less # manifest, which the trainer no longer publishes. prior_contract_digest = "81d28346acb55892290ef3a6970caed2c3e8a5f569627b76cdefc29426f6f045" contract_from_block = 9046800 # Declared-contract gating (DEC-CA-0036). From this block a manifest is gated on # the LOCKED terms only — arch_preset, base_arch_digest, expected_gpu, # train_seed_salt, and each extra size's preset/GPU pin — verified against the # full training contract the trainer now publishes and signs inside the manifest # (`contract_body`). Every other [training] term is recorded and audited but # never rejects a round, so a trainer-side fix (runtime image re-pin, recipe # change, budget change) ships the same day instead of waiting on six external # validators to restart. # # 0 = OFF, the deployed value: every round takes the legacy strict path (full # digest equality against the local [training]). Arming this is the LAST # [training]-driven change that needs a coordinated fleet deploy — after it, # [training] edits never again reject a round on an un-upgraded validator, and # validator restarts are reserved for actual scoring-rule changes ([scoring], # [eval], the cascade envelope). Ship the release first, let the fleet upgrade at # its own pace, THEN set the block: rounds before it replay strict, rounds after # replay declared, and cascade-audit judges each round under its own rule. # Fleet-consensus value like every [scoring] key: keep identical across validators. # ARMED 2026-08-27 (owner): live for rounds from r47's epoch, same boundary as # the recipe cut — one validator update before block 8942400 covers both. declared_contract_from_block = 8942400 # Init-baseline duel gate (DEC-CA-0034) in SHADOW (armed 2026-08-27, owner): # the validator scores the warm-start init on the verdict windows and records # init_baseline_geomean / init_floor_passed in the receipt (drop-when-None — # archived signatures survive) but the verdict is UNCHANGED. Log-only, so # validators may enable it at their own pace; enforce is a later, separate # coordinated decision once the shadow verdicts calibrate init_gate_tolerance. init_gate_mode = "shadow" [dependencies] # Allowlist for a generator's requirements.txt. Generators are purely # algorithmic — no weight files accepted (see cascade.interface.validation); # torch is allowed as a compute library for GP/kernel priors only. max_packages = 16 allowed = [ "numpy", "scipy", "pandas", "statsmodels", "numba", "torch", # GP/kernel/graph primitives for GP-prior generators (CPU-only generate path) "scikit-learn", "gpytorch", "networkx", ] [static_guard] # Blocked imports in a submitted generator.py. blocked = [ "socket", "urllib", "urllib2", "urllib3", "requests", "httpx", "http.client", "http.server", "subprocess", "os.system", "ctypes", "cffi", "pickle", "marshal", "shelve", "multiprocessing", "asyncio.subprocess", "cascade.trainer", "cascade.validator", "cascade.shared.chain", ] [storage] # Hippius storage layer: Hub OCI registry for models/checkpoints/generators # (pinned by repo@digest), S3 for manifests + logs. Credentials come from the # environment, never this file: HIPPIUS_HUB_TOKEN (or _USERNAME/_PASSWORD) for # the registry, HIPPIUS_S3_ACCESS_KEY / HIPPIUS_S3_SECRET_KEY for S3. hub_registry_url = "https://registry.hippius.com" # informational; client uses its own default hub_namespace = "cascade" # repo namespace for trainer/owner pushes s3_endpoint = "https://s3.hippius.com" s3_region = "decentralized" manifest_bucket = "cascade-manifests" # round-.json + latest.json logs_bucket = "cascade-logs" # logs/round-/.jsonl # Eval-pool bucket (daily snapshots + pool/index.json, published with # `cascade-pool publish`). Empty = use the static [eval] window_pool ref instead. pool_bucket = "cascade-eval-pool" # daily snapshots from publish-pool.yml # Pool endpoint override (e.g. Cloudflare R2 + POOL_S3_ACCESS_KEY / # POOL_S3_SECRET_KEY); empty = reuse the Hippius S3 endpoint above. pool_s3_endpoint = "" pool_s3_region = "" # HuggingFace-Hub dataset used as a manifest/receipt fallback ONLY during a # Hippius S3 outage (cascade.shared.hippius.HFFallbackStore). Make it a public # dataset; auth via HF_TOKEN. Empty = no fallback. hf_backup_repo = "" # e.g. "yourorg/cascade-manifests-mirror" # S3-compatible BACKUP of the manifest/receipt bucket: dual-writes every object # and serves reads when Hippius S3 is down. Credentials via # BACKUP_S3_ACCESS_KEY / BACKUP_S3_SECRET_KEY. backup_s3_endpoint = "https://04128c0d5ba104eb6a6b77c898aed78e.r2.cloudflarestorage.com" backup_s3_region = "" # empty = "auto" (R2's region) backup_bucket = "" # empty = reuse manifest_bucket name # Live R2 BACKUP of the eval-pool bucket: dual-writes every snapshot tar + # pool/index.json and serves validator reads when Hippius S3 5xx's, so an outage # no longer strands the pool-pin gate (pool_pin_unverifiable). Endpoint/region/ # creds are REUSED from backup_s3_* above (BACKUP_S3_ACCESS_KEY/_SECRET_KEY); # only the bucket differs. Empty = no pool backup. Hippius stays primary. pool_backup_bucket = "cascade-eval-pool" # R2 bucket; "" disables # Private KING ARCHIVE (Cloudflare R2): `cascade-scrape-kings` saves every # generator that has ever held the throne (the code, fetched from the Hub and # packed to a tar) under kings/ plus a kings/index.json "db" — and EVERY # eligible participant generator (from each round's full receipt) under # generators// (grouped by committing miner) with its own # generators/index.json. A permanent private record, # independent of the public Hub repos a miner could delete. Endpoint/region # default to the backup_* R2 above; credentials via KING_ARCHIVE_S3_ACCESS_KEY # / KING_ARCHIVE_S3_SECRET_KEY (falling back to BACKUP_S3_* when unset). Keep # the bucket PRIVATE. king_archive_bucket = "cascade-king-archive" king_archive_s3_endpoint = "" # empty = reuse backup_s3_endpoint king_archive_s3_region = "" # empty = reuse backup_s3_region ("auto") [manifest] # Signed training receipts in [storage] manifest_bucket. trainer_hotkey is the # only signer a validator trusts. trainer_hotkey = "5FcCsoB2R5M55RdrNrvPQv7xmLJRFzs7R7C73UUSxgUCyfsw" # owner trainer hotkey (mainnet) poll_seconds = 120 # Trust anchor for the public round receipts: the validator hotkey whose # signature `cascade-audit` requires. Empty = verify against the receipt's # self-declared signer (audit warns that the signer is unpinned). validator_hotkey = "5FcCsoB2R5M55RdrNrvPQv7xmLJRFzs7R7C73UUSxgUCyfsw" # LOCAL OVERRIDE (audit trust anchor) — re-apply after every pull; see OPSLOG 2026-07-26 [validator] weight_set_interval_blocks = 180 poll_seconds = 120 hf_cache_seconds = 1800 state_db_path = "validator_state.db" # Cascade persistence (see [scoring] cascade_reign_days): reign clock + reign # checkpoint log, and the promoted warm-start pointer. The CO-HOSTED TRAINER # reads warm_start_init_path too (its rounds train from the pinned init once a # promotion fires) — point both processes at the same file. cascade_state_db_path = "cascade_state.json" warm_start_init_path = "warm_start_init.json" # Operator kill-switch (or `cascade-validator --force-burn`): burn to # [scoring] burn_uid on EVERY weight-set instead of voting the champion, while # still refreshing last_update (stays Yuma-active). Champion state is untouched # and keeps evolving; unset + restart to resume voting the persisted king. # For "something looks wrong, stop endorsing while I investigate". # force_burn = false # First boot with no local state: inherit the current champion from the signed # public receipt trail (verified against the pinned [manifest] hotkey) instead # of judging the next manifest blind. Makes validator join-order irrelevant to # who holds the throne. Existing state always wins; flip false to opt out. bootstrap_from_receipts = true [wandb] # Optional live training dashboards (Weights & Biases). Observability only — # never feeds scoring or weights; held-out eval scores are not sent. One run per # (round, competitor, size), tagged with the miner hotkey. Requires the [wandb] # extra; WANDB_API_KEY comes from the environment, never this file. enabled = true # live wandb logging ON (owner 2026-07-18); observability only project = "cascade" # wandb project; make it PUBLIC for miners to view entity = "" # wandb team/user (empty = the API key's default) mode = "online" # online | offline | disabled # A SEPARATE, project-scoped wandb key for ISOLATED (miner-paid) pods, named by # env var; forwarded to those pods as WANDB_API_KEY so funded legs stream live # too. The payer can read it from their pod — never the operator's key (the name # "WANDB_API_KEY" is refused). Empty ⇒ funded legs log no wandb (pre-2026-09-13). funded_key_env = "WANDB_API_KEY_FUNDED" [telemetry] # Host-side run telemetry (cascade.trainer.host_probe). Observability only — # these fields ride the training-log channel next to the per-step metrics, never # the manifest, the receipt, or contract_digest, and nothing in the scoring path # reads them. They exist because the heat prices generator speed (DEC-CA-0001) on # a fleet rented per round: data_wait_frac can rule the CORPUS out as the cause of # a slow run, but only a host-side measurement can rule the POD in. host_bench # times a fixed calibration workload before each run's corpus stream opens (a few # GPU-seconds; it cannot eat the compute budget — max_train_seconds anchors at the # first training batch), and is the one field comparable across pods. host_probe = true # lane geometry, CPU/GPU capability, opaque pod + machine ids host_bench = true # + the fixed calibration bench (host_bench_tokens_per_s) # Payer-pod bench (DEC-CA-0036): each funded challenger benches on its own pod # after the duel (miner-billed, ~1h; the pod is torn down when the sweep ends). # Payer numbers are a FILTER: the top-N re-bench on the operator's king pod and # only the operator's numbers are signed/published; a payer number better than # the re-bench by more than the tolerance is a forged sweep (entry dropped). funded_bench = true funded_bench_verify_top = 1 funded_bench_verify_tolerance = 0.02 # Shadow scratch control (DEC-CA-0014 Stage 1): every M-th warm-started round # the trainer ALSO trains the king's generator from scratch (random init, # identical contract + seeds) strictly post-publish on the idle final pod, # benches it, and publishes benchmarks/scratch/round-.json — labeled # telemetry that never feeds scoring, selection, or promotion. [telemetry] # tier on purpose: must never touch contract_digest. 0 = off. MAINNET stays 0 # until the owner arms it (DEC-CA-0014 says start M=4): arming costs a full # training leg every M rounds and needs [eval] bench_hold_max_hours sized for # duel bench + train leg + one more bench (~6-7h total at the 3h budget) — or # a dedicated pod. Testnet first, one full cycle. scratch_shadow_every_rounds = 0 # bench_anneal_fraction = 0.15 # bench-anneal (DEC-CA-0030): NEVER together with [training] # ema_decay (the worker's anneal_recipe refuses an EMA-armed # contract) or anneal_fraction — one finished-form mechanism # at a time. Before the post-round bench sweep, # resume each duel checkpoint (weights + optimizer, fresh salted # corpus) under a pure cosine decay base_lr -> 0 for this fraction # of the full budget, and bench the ANNEALED copy — the signed # BenchScores (public bench stream, promotion picks, DEC-CA-0017 # guard) then read finished form instead of wsd's mid-stable # artifacts. Canonical checkpoints, manifest, bench-report wire # format, and every validator-verified byte untouched — TRAINER # RESTART ONLY, no validator involvement. Anneal-leg failure falls # back to benching the raw checkpoint. Costs ~fraction x one # training leg per benched checkpoint on the same pod; size # [eval] bench_hold_max_hours accordingly. 0.0/absent = off. [activation] # Stake-weighted activation of the DEC-CA-0043 rollover (DEC-CA-0045). Each # validator on a release carrying this feature posts a plain on-chain # commitment ("cascade-ready:1::0:0") from its hotkey at startup. # At every boundary of the grid in force, every node sums the stake of the # permit-holding validators that have posted it; the first boundary where # that share reaches `threshold` LOCKS IN (one-way), and the rollover is the # NEXT boundary — the round in which the count crossed finishes on the old # rules, the one after starts on the new (rolling intake, era king, tenure # in blocks, the grid switch below, and the increment-unit max-T all flip # there). A validator that has locked in rewrites its note with the block, # so a restarted or late node adopts the block validators holding # `threshold` of stake all name — no archive node needed. The trainer and # the provisioner READ the same decision every tick and arm themselves (a # trainer still on the OLD release must be restarted on the new one at the # lock-in boundary); the validator stamps the block on every receipt # (`activation_block`) so the audit replays each round under it. The # typed-in DEC-CA-0043 keys ([round] rolling_from_block etc.) always win # over the resolved block — the owner override. "" = off. feature = "rolling-era-king" # Fraction of ELIGIBLE validator stake (permit holders) that must have # signalled. A bare majority: check the owner validator's own share before # relying on this — over half and it decides alone. threshold = 0.51 # The grid from the rollover ([round] epoch_blocks after it; the loaded 3600 # becomes epoch_blocks_prev). Must divide epoch_blocks. 0 = keep the grid. epoch_blocks_after = 900 # Leave OUT permit holders whose last weight-set is older than this many # blocks at the tally, so a dead validator's stake cannot hold the number # down. 0 = count every permit holder. dormant_after_blocks = 0