This chapter builds on: Ch 9 Yuma Consensus, Ch 10 Emissions

Epoch Processing

An epoch is a periodic recalculation that runs Yuma Consensus and distributes rewards. Epochs are part of block execution, triggered by each subnet's tempo parameter.

Step 1: Check Tempo Trigger

Each subnet has a Tempo parameter that determines how often its epoch runs. At each block, the runtime checks whether this subnet's epoch is due. Subnets are staggered by their network ID so they don't all run in the same block:

if (block_number + netuid + 1) % (tempo + 1) == 0 → run epoch for this subnet

The netuid offset ensures subnets with the same tempo run in different blocks, spreading the computation load. The + 1 adjustments prevent division-by-zero edge cases.

Common tempo values:

TempoIntervalUse Case
10~2 minutesFast-updating subnets
100~20 minutesStandard subnets
360~72 minutesSlow, stable subnets
7200~24 hoursRoot network (SN0)

Block time is ~12 seconds, so tempo 100 = ~20 minutes between epochs.

Step 2: Read Current State

When an epoch triggers, the runtime reads all relevant state from storage:

Storage ItemPurpose
WeightsValidator weight assignments (sparse matrix)
BondsPrevious epoch's bond matrix
TotalStakeTAO staked to each hotkey
LastUpdateLast block each neuron set weights
ValidatorPermitWhich neurons can validate
BlockAtRegistrationWhen each neuron registered

Step 3: Filter Active Participants

Before computing consensus, several filters are applied to determine which neurons participate:

FilterWhat It DoesStorage Reference
Activity CutoffNeurons who haven't set weights within activity_cutoff blocks are inactiveActivityCutoff
Stake ThresholdValidators below minimum stake don't countNominatorMinRequiredStake
Validator PermitsOnly permitted validators' weights countValidatorPermit
Outdated WeightsWeights to neurons registered after weight-setting are masked
Self-WeightsDiagonal (self-voting) is always masked out
rust
// Simplified filtering logic
let active = last_update.map(|block|
    current_block - block < activity_cutoff
);

let filtered_stake = stake.map(|s|
    if s < min_stake { 0 } else { s }
);

// Mask non-validators and self-weights
weights = mask_rows(&validator_forbids, &weights);
weights = mask_diag(&weights);

Step 4: Run Yuma Consensus

With filtered inputs, the Yuma Consensus algorithm runs:

  1. Compute consensus: Stake-weighted median per miner
  2. Clip weights: Limit all weights to consensus values
  3. Calculate ranks: Stake-weighted sum of clipped weights
  4. Normalize to incentive: Miner emission shares
  5. Update bonds: EMA of weight × stake products
  6. Calculate dividends: Validator shares from bond positions

See Yuma Consensus for the detailed algorithm.

Step 5: Compute Emission Distribution

The epoch determines how the subnet's accumulated emission is distributed:

RecipientSourcePending Storage
MinersIncentive[j] × alpha emissionPendingServerEmission
ValidatorsDividends[i] × alpha emissionPendingValidatorEmission
OwnerOwner cut percentagePendingOwnerCut
Root DivsRoot network validatorsPendingRootAlphaDivs

Emission accumulates in pending storage items and is released to wallets when the epoch completes. See Emission Mechanics for the full flow.

Step 6: Write Results to Storage

Finally, all computed values are written back to on-chain storage:

Storage ItemUpdated Value
ConsensusConsensus weights per miner (u16)
IncentiveNormalized miner incentive scores (u16)
DividendsNormalized validator dividend scores (u16)
BondsUpdated EMA bonds matrix
ValidatorTrustTrust scores per validator (u16)
EmissionCombined emission per neuron
ActiveActivity status per neuron
ValidatorPermitUpdated permits (top-k by stake)

Yuma3 Variant

Yuma3 is an enhanced epoch algorithm enabled per-subnet via the Yuma3On flag.

Key Differences

FeatureStandard YumaYuma3
EMA AlphaFixed per subnetDynamic "Liquid Alpha" per weight
Bond NormalizationColumn normalizationIncentive-weighted normalization
Dividend Formulabonds × incentiveModified formula with stake weighting

Liquid Alpha

In Yuma3, the EMA alpha for bond updates is dynamic. Weights closer to consensus get faster bond accumulation:

rust
// Liquid alpha: consensus-aligned weights bond faster
fn liquid_alpha(weight, consensus, bond, alpha_low, alpha_high) {
    let diff = if weight >= bond {
        weight - consensus  // buying position
    } else {
        bond - weight       // selling position
    };

    // Sigmoid: closer to 0 diff = higher alpha
    let sigmoid = 1 / (1 + exp(-steepness * (diff - 0.5)));
    alpha_low + sigmoid * (alpha_high - alpha_low)
}

The alpha range is configured by AlphaValues (which stores both the low and high bounds).

When to Use Yuma3

Yuma3 is designed for subnets using dTAO where the liquid alpha mechanism smooths bond changes as validators adjust weights. It rewards consensus-aligned validators with faster bond accumulation, providing more nuanced incentive alignment.

Performance Considerations

Epoch processing must complete within block time constraints (~12 seconds for all subnets combined). Optimizations include:

  • Sparse matrices: Weights and bonds are stored as sparse vectors (Vec<(uid, u16)>) to handle subnets with many neurons
  • Fixed-point arithmetic: All computations use I32F32 fixed-point to avoid floating-point non-determinism across nodes
  • Efficient median: Pivot-based search instead of full sorting for the weighted median calculation

Epoch Events

The epoch emits events that can be observed on-chain:

These events allow indexers and explorers to track emission distribution without parsing storage changes.

Related Pages

Source Code

Epoch processing is implemented in:

  • pallets/subtensor/src/epoch/run_epoch.rs
    • epoch_mechanism() at line 559: Main epoch function
    • epoch_dense_mechanism() at line 146: Dense matrix variant (testing)
    • compute_bonds() at line 1258: Bond computation
    • compute_ema_bonds_normal() at line 1222: Standard EMA bonds