BUG: Severity Low: Non-initial model group cap compares N-1 post-collateral weight against pre-collateral contribution #2217
Summary
The group cap for non-initial models compares values in two different units. The cap budget is cap_factor × the previous epoch's final weight, which is after collateral adjustment, the 30% power cap and the confirmation discount. The value being capped is the group's pre-collateral contribution (ConsensusKoeff × PoC weight). Collateral adjustment then runs after the cap and reduces the group's weight a second time.
When participants are not fully collateralized, a non-initial model's weight share converges to about cap_factor × base_weight_ratio, no matter how much compute it contributes.
Where
ComputeGroupCap:cap = CapFactor × TotalNetworkWeight.TotalNetworkWeightis the sum ofConsensusWeights.ConsensusWeightscome frombuildPreviousConfirmedWeightCaps. They are the N-1 confirmed effective weights, i.e. finalActiveParticipant.Weightafter collateral and power capping, times the confirmation ratio.ComputeConsensusWeightscompares that cap againstrawTotal = Σ ConsensusKoeff × MemberPocWeights, which is before collateral.runWeightPipelineinmodule.gothen callsAdjustWeightsByCollateral(L764) andapplyEpochPowerCapping(L765) on the already-capped weights. The cap is applied at L725.
The calculator uses the name "consensus weight" for both the N-1 post-collateral value and the N pre-collateral value.
Observed on testnet
The testnet runs v0.2.17-testnet-1 with DECODE PoC; the coefficient is 1 for both models.
- Models: Qwen3-4B is the initial model; Qwen2.5-7B is non-initial.
- Params:
cap_factor = 0.75,base_weight_ratio = 0.2, grace period ended. Only one participant has collateral. - Raw PoC weight: 7B ≈ 8,877, 4B ≈ 12,655, so 7B supplies about 41% of raw PoC weight.
- One epoch:
- Cap = 0.75 × N-1 total 1,730 = 1,297, so 7B is scaled to 14.6% of its raw contribution.
- Collateral (×0.2) then reduces 7B to about 259; the chain shows 257.
- The 30% power cap trims the large 4B participants. The final total is 1,704.
- Result: 7B's weight share settles at 0.75 × 0.2 = 15%. Epochs 877–879 show 14.9%, 15.2% and 15.0%.
- With the cap applied in post-collateral units, a rough estimate puts 7B at about 33%.
This also moves the share closer to the eligibility threshold (w_threshold = 0.1). If it drops below that, the model gets weight 0 and cannot become eligible again. See "Related" below.
Mainnet impact today
None at the moment. As of epoch 447: - All 25 active participants are fully collateralized (lowest coverage 1.1×). - No participant hits the 30% power cap. - Neither non-initial model (DeepSeek-V4-Flash and GLM-5.3-Flash, at about 118k–659k and 173k–482k against a cap of about 1.07M) reaches the cap.
The issue becomes live on mainnet if a non-initial model grows enough for the cap to bind and, at the same time, weight is reduced by a slash or collateral withdrawal, or by the confirmation discount.
Why the tests don't catch it
The ComputeGroupCap and ComputeConsensusWeights tests in weight_calculator_test.go set TotalNetworkWeight equal to the PoC weights, with no collateral or power cap, so the two units coincide.
Design doc mismatch
proposals/multi-model-poc/design-2.md and README.md define the cap as cap_factor * sum(member's prior-epoch consensus weight from other eligible groups), i.e. the group's own members. The code uses the whole network's N-1 weight. Under the doc's formula, hosts that only serve a non-initial model would always get a cap of 0. Either the doc or the code should be updated, and the units need to be defined explicitly.
Possible fixes
- Persist each participant's N-1 pre-collateral weight (the
ComputeConsensusWeightsoutput) and compute the cap against that. - Or apply the group cap after
AdjustWeightsByCollateral, so both sides are in final-weight units. - Add a test with
base_weight_ratio < 1and partial collateral, plus one where the power cap binds.
Related
A non-initial group whose members all had zero N-1 weight, for example after every member dropped out for one epoch, fails both MeetsWeightThreshold and the v_min established-member check in IsGroupEligible. It then stays at weight 0 permanently. On testnet the only way out was a governance change to w_threshold/v_min and back. This is worth a separate issue or a recovery path.
💬 Comments (2)
Chain logs from testnet (epoch 881 formation)
These are from v0.2.17-testnet-1, and the same pattern repeats in epochs 879, 880 and 882. Addresses are shortened.
INF Collateral grace period has ended. Adjusting weights by collateral. current_epoch=881
INF Universal power capping applied to epoch powers originalTotalPower=6943 cappedTotalPower=1714 participantCount=7
INF weight_group epoch=881 model=Qwen/Qwen2.5-7B-Instruct coeff=1 raw_total=8858 cap=1280 scale=0.144502144953714157
INF weight_group epoch=881 model=Qwen/Qwen3-4B-Instruct-2507 coeff=1 raw_total=12638 cap=-1 scale=1
INF weight_pipeline epoch=881 addr=…pcgs0y modes=7B:DIRECT,4B:NONE consensus=492 before_collateral=492 final=98
INF weight_pipeline epoch=881 addr=…32vrcn modes=7B:DIRECT,4B:NONE consensus=255 before_collateral=255 final=51
INF weight_pipeline epoch=881 addr=…q60cwt modes=7B:DIRECT,4B:NONE consensus=265 before_collateral=265 final=53
INF weight_pipeline epoch=881 addr=…4sgtd5 modes=7B:DIRECT,4B:NONE consensus=266 before_collateral=266 final=53
INF weight_pipeline epoch=881 addr=…u4txhy modes=7B:DELEGATE,4B:DIRECT consensus=5277 before_collateral=5277 final=514
INF weight_pipeline epoch=881 addr=…kyc325 modes=7B:NONE,4B:DIRECT consensus=2159 before_collateral=2159 final=431
INF weight_pipeline epoch=881 addr=…2f3a2y modes=7B:DELEGATE,4B:DIRECT consensus=5202 before_collateral=5202 final=514
What the logs show:
weight_group:- The 7B cap of 1280 is
0.75 × 1707, and 1707 is epoch 880's final total (cappedTotalPower). - It is compared against
raw_total=8858, which is in pre-collateral units, so 7B is scaled to 14.45%. weight_pipeline, 7B hosts: none of them have collateral. After the cap, collateral cuts each one again to 20%, e.g. pcgs0y 492 → 98.- The 7B final total is 255 of 1714, i.e. 14.9%, which matches
cap_factor × base_weight_ratio = 0.75 × 0.2. - Power capping:
originalTotalPower=6943 → cappedTotalPower=1714combines collateral and the 30% power cap. That 1714 then becomes the next epoch's cap budget. - Code comment: the one on
emitWeightPipelineLogssays the gap betweenbefore_collateralandfinalis "collateral adjustment and power-cap (both rarely active)". Here both are active every epoch.
The per-participant collateral breakdown (Adjusted participant weight by collateral) is logged at debug level, so it is not visible at the default log level. Raising it to info would make this issue easier to spot on mainnet.
Simulated fix
I fed epoch 881's per-node PoC weights through the chain's own ComputeConsensusWeights and ApplyPowerCappingForWeights. The current pipeline reproduces the on-chain finals exactly. With the cap computed in matching units, either by applying it after collateral or by comparing against N-1 pre-collateral weights:
| 7B final | 4B final | 7B share | |
|---|---|---|---|
| current (on chain) | 255 | 1459 | 14.9% |
| fixed, first epoch (cap after collateral) | 1278 | 2670 | 32.4% |
| fixed, steady state (both fixes) | 1770 | 2881 | 38.1% |
At steady state the 7B cap no longer binds (1770 < 0.75 × 4651).
🔄 Auto-synced from Issue #2217 every hour.
@GLiberman, let's fix it in 0.2.18, please.