diff --git a/pallets/admin-utils/src/benchmarking.rs b/pallets/admin-utils/src/benchmarking.rs index abbcd0f16..e872b1fe6 100644 --- a/pallets/admin-utils/src/benchmarking.rs +++ b/pallets/admin-utils/src/benchmarking.rs @@ -100,6 +100,14 @@ mod benchmarks { _(RawOrigin::Root, 1u16/*netuid*/, 100u64/*bonds_moving_average*/)/*sudo_set_bonds_moving_average*/; } + #[benchmark] + fn sudo_set_bonds_penalty() { + pallet_subtensor::Pallet::::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); + + #[extrinsic_call] + _(RawOrigin::Root, 1u16/*netuid*/, 100u64/*bonds_penalty*/)/*sudo_set_bonds_penalty*/; + } + #[benchmark] fn sudo_set_max_allowed_validators() { pallet_subtensor::Pallet::::init_new_network(1u16 /*netuid*/, 1u16 /*tempo*/); diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index f6b132148..4ae3bb13a 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -306,6 +306,22 @@ pub mod pallet { Ok(()) } + #[pallet::call_index(59)] + #[pallet::weight(( + Weight::from_ref_time(14_000_000) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)), + DispatchClass::Operational, + Pays::No + ))] + pub fn sudo_set_bonds_penalty( + origin: OriginFor, + netuid: u16, + bonds_penalty: u16, + ) -> DispatchResult { + Self::do_sudo_set_bonds_penalty(origin, netuid, bonds_penalty) + } + /// The extrinsic sets the adjustment beta for a subnet. /// It is only callable by the root account or subnet owner. /// The extrinsic will call the Subtensor pallet to set the adjustment beta. @@ -685,6 +701,31 @@ pub mod pallet { Ok(()) } + /// The extrinsic sets the bonds penalty for a subnet. + /// It is only callable by the root account or subnet owner. + /// The extrinsic will call the Subtensor pallet to set the bonds penalty. + #[pallet::call_index(60)] + #[pallet::weight(::WeightInfo::sudo_set_bonds_penalty())] + pub fn sudo_set_bonds_penalty( + origin: OriginFor, + netuid: u16, + bonds_penalty: u16, + ) -> DispatchResult { + pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; + + ensure!( + pallet_subtensor::Pallet::::if_subnet_exist(netuid), + Error::::SubnetDoesNotExist + ); + pallet_subtensor::Pallet::::set_bonds_penalty(netuid, bonds_penaltyverage); + log::debug!( + "Bondspenaltyet( netuid: {:?} bonds_penalty: {:?} ) ", + netuid, + bonds_penalty + ); + Ok(()) + } + /// The extrinsic sets the maximum registrations per block for a subnet. /// It is only callable by the root account. /// The extrinsic will call the Subtensor pallet to set the maximum registrations per block. diff --git a/pallets/admin-utils/src/tests/mock.rs b/pallets/admin-utils/src/tests/mock.rs index b2dbb66c0..5517196f1 100644 --- a/pallets/admin-utils/src/tests/mock.rs +++ b/pallets/admin-utils/src/tests/mock.rs @@ -86,6 +86,7 @@ parameter_types! { pub const InitialImmunityPeriod: u16 = 2; pub const InitialMaxAllowedUids: u16 = 2; pub const InitialBondsMovingAverage: u64 = 900_000; + pub const InitialBondsPenalty: u16 = 0; pub const InitialStakePruningMin: u16 = 0; pub const InitialFoundationDistribution: u64 = 0; pub const InitialDefaultDelegateTake: u16 = 11_796; // 18% honest number. @@ -163,6 +164,7 @@ impl pallet_subtensor::Config for Test { type InitialMaxRegistrationsPerBlock = InitialMaxRegistrationsPerBlock; type InitialPruningScore = InitialPruningScore; type InitialBondsMovingAverage = InitialBondsMovingAverage; + type InitialBondsPenalty = InitialBondsPenalty; type InitialMaxAllowedValidators = InitialMaxAllowedValidators; type InitialDefaultDelegateTake = InitialDefaultDelegateTake; type InitialMinDelegateTake = InitialMinDelegateTake; diff --git a/pallets/admin-utils/src/tests/mod.rs b/pallets/admin-utils/src/tests/mod.rs index f791e5471..115683464 100644 --- a/pallets/admin-utils/src/tests/mod.rs +++ b/pallets/admin-utils/src/tests/mod.rs @@ -741,6 +741,39 @@ fn test_sudo_set_bonds_moving_average() { }); } +#[test] +fn test_sudo_set_bonds_penalty() { + new_test_ext().execute_with(|| { + let netuid: u16 = 1; + let to_be_set: u16 = 10; + let init_value: u16 = SubtensorModule::get_bonds_penalty(netuid); + add_network(netuid, 10, 0); + assert_eq!( + SubtensorModule::sudo_set_bonds_penalty( + <::RuntimeOrigin>::signed(U256::from(0)), + netuid, + to_be_set + ), + Err(DispatchError::BadOrigin.into()) + ); + assert_eq!( + SubtensorModule::sudo_set_bonds_penalty( + <::RuntimeOrigin>::root(), + netuid + 1, + to_be_set + ), + Err(Error::::NetworkDoesNotExist.into()) + ); + assert_eq!(SubtensorModule::get_bonds_penalty(netuid), init_value); + assert_ok!(SubtensorModule::sudo_set_bonds_penalty( + <::RuntimeOrigin>::root(), + netuid, + to_be_set + )); + assert_eq!(SubtensorModule::get_bonds_penalty(netuid), to_be_set); + }); +} + #[test] fn test_sudo_set_rao_recycled() { new_test_ext().execute_with(|| { diff --git a/pallets/subtensor/src/epoch/math.rs b/pallets/subtensor/src/epoch/math.rs index 57d0f6b6f..9d63ac1c5 100644 --- a/pallets/subtensor/src/epoch/math.rs +++ b/pallets/subtensor/src/epoch/math.rs @@ -1022,6 +1022,79 @@ pub fn weighted_median_col_sparse( median } +// Element-wise interpolation of two matrices: Result = A + ratio * (B - A). +// ratio is has intended range [0, 1] +// ratio=0: Result = A +// ratio=1: Result = B +#[allow(dead_code)] +pub fn interpolate( + mat1: &Vec>, + mat2: &Vec>, + ratio: I32F32, +) -> Vec> { + if ratio == I32F32::from_num(0) { + return mat1.clone(); + } + if ratio == I32F32::from_num(1) { + return mat2.clone(); + } + assert!(mat1.len() == mat2.len()); + if mat1.len() == 0 { + return vec![vec![]; 1]; + } + if mat1[0].len() == 0 { + return vec![vec![]; 1]; + } + let mut result: Vec> = vec![vec![I32F32::from_num(0); mat1[0].len()]; mat1.len()]; + for i in 0..mat1.len() { + assert!(mat1[i].len() == mat2[i].len()); + for j in 0..mat1[i].len() { + result[i][j] = mat1[i][j] + ratio * (mat2[i][j] - mat1[i][j]); + } + } + result +} + +// Element-wise interpolation of two sparse matrices: Result = A + ratio * (B - A). +// ratio is has intended range [0, 1] +// ratio=0: Result = A +// ratio=1: Result = B +#[allow(dead_code)] +pub fn interpolate_sparse( + mat1: &Vec>, + mat2: &Vec>, + columns: u16, + ratio: I32F32, +) -> Vec> { + if ratio == I32F32::from_num(0) { + return mat1.clone(); + } + if ratio == I32F32::from_num(1) { + return mat2.clone(); + } + assert!(mat1.len() == mat2.len()); + let rows = mat1.len(); + let zero: I32F32 = I32F32::from_num(0); + let mut result: Vec> = vec![vec![]; rows]; + for i in 0..rows { + let mut row1: Vec = vec![zero; columns as usize]; + for (j, value) in mat1[i].iter() { + row1[*j as usize] = *value; + } + let mut row2: Vec = vec![zero; columns as usize]; + for (j, value) in mat2[i].iter() { + row2[*j as usize] = *value; + } + for j in 0..columns as usize { + let interp: I32F32 = row1[j] + ratio * (row2[j] - row1[j]); + if zero < interp { + result[i].push((j as u16, interp)) + } + } + } + result +} + // Element-wise product of two matrices. #[allow(dead_code)] pub fn hadamard(mat1: &[Vec], mat2: &[Vec]) -> Vec> { diff --git a/pallets/subtensor/src/epoch/run_epoch.rs b/pallets/subtensor/src/epoch/run_epoch.rs index be06ed59f..1b070f7b4 100644 --- a/pallets/subtensor/src/epoch/run_epoch.rs +++ b/pallets/subtensor/src/epoch/run_epoch.rs @@ -137,18 +137,22 @@ impl Pallet { // Compute preranks: r_j = SUM(i) w_ij * s_i let preranks: Vec = matmul(&weights, &active_stake); - // Clip weights at majority consensus - let kappa: I32F32 = Self::get_float_kappa(netuid); // consensus majority ratio, e.g. 51%. + // Consensus majority ratio, e.g. 51%. + let kappa: I32F32 = Self::get_float_kappa(netuid); + // Calculate consensus as stake-weighted median of weights. let consensus: Vec = weighted_median_col(&active_stake, &weights, kappa); - inplace_col_clip(&mut weights, &consensus); - let validator_trust: Vec = row_sum(&weights); + // Clip weights at majority consensus. + let mut clipped_weights: Vec> = weights.clone(); + inplace_col_clip(&mut clipped_weights, &consensus); + // Calculate validator trust as sum of clipped weights set by validator. + let validator_trust: Vec = row_sum(&clipped_weights); // ==================================== // == Ranks, Server Trust, Incentive == // ==================================== // Compute ranks: r_j = SUM(i) w_ij * s_i - let mut ranks: Vec = matmul(&weights, &active_stake); + let mut ranks: Vec = matmul(&clipped_weights, &active_stake); // Compute server trust: ratio of rank after vs. rank before. let trust: Vec = vecdiv(&ranks, &preranks); @@ -161,6 +165,14 @@ impl Pallet { // == Bonds and Dividends == // ========================= + // Get validator bonds penalty in [0, 1]. + let bonds_penalty: I32F32 = Self::get_float_bonds_penalty(netuid); + // Calculate weights for bonds, apply bonds penalty to weights. + // bonds_penalty = 0: weights_for_bonds = weights.clone() + // bonds_penalty = 1: weights_for_bonds = clipped_weights.clone() + let weights_for_bonds: Vec> = + interpolate(&weights, &clipped_weights, bonds_penalty); + // Access network bonds. let mut bonds: Vec> = Self::get_bonds(netuid); inplace_mask_matrix(&outdated, &mut bonds); // mask outdated bonds @@ -168,7 +180,7 @@ impl Pallet { log::trace!("B:\n{:?}\n", &bonds); // Compute bonds delta column normalized. - let mut bonds_delta: Vec> = row_hadamard(&weights, &active_stake); // ΔB = W◦S + let mut bonds_delta: Vec> = row_hadamard(&weights_for_bonds, &active_stake); // ΔB = W◦S inplace_col_normalize(&mut bonds_delta); // sum_i b_ij = 1 log::trace!("ΔB:\n{:?}\n", &bonds_delta); // Compute the Exponential Moving Average (EMA) of bonds. @@ -474,15 +486,18 @@ impl Pallet { let preranks: Vec = matmul_sparse(&weights, &active_stake, n); log::trace!("Ranks (before): {:?}", &preranks); - // Clip weights at majority consensus - let kappa: I32F32 = Self::get_float_kappa(netuid); // consensus majority ratio, e.g. 51%. + // Consensus majority ratio, e.g. 51%. + let kappa: I32F32 = Self::get_float_kappa(netuid); + // Calculate consensus as stake-weighted median of weights. let consensus: Vec = weighted_median_col_sparse(&active_stake, &weights, n, kappa); log::trace!("Consensus: {:?}", &consensus); - weights = col_clip_sparse(&weights, &consensus); - log::trace!("Weights: {:?}", &weights); + // Clip weights at majority consensus. + let clipped_weights: Vec> = col_clip_sparse(&weights, &consensus); + log::trace!("Clipped Weights: {:?}", &clipped_weights); - let validator_trust: Vec = row_sum_sparse(&weights); + // Calculate validator trust as sum of clipped weights set by validator. + let validator_trust: Vec = row_sum_sparse(&clipped_weights); log::trace!("Validator Trust: {:?}", &validator_trust); // ============================= @@ -490,7 +505,7 @@ impl Pallet { // ============================= // Compute ranks: r_j = SUM(i) w_ij * s_i. - let mut ranks: Vec = matmul_sparse(&weights, &active_stake, n); + let mut ranks: Vec = matmul_sparse(&clipped_weights, &active_stake, n); log::trace!("Ranks (after): {:?}", &ranks); // Compute server trust: ratio of rank after vs. rank before. @@ -505,6 +520,14 @@ impl Pallet { // == Bonds and Dividends == // ========================= + // Get validator bonds penalty in [0, 1]. + let bonds_penalty: I32F32 = Self::get_float_bonds_penalty(netuid); + // Calculate weights for bonds, apply bonds penalty to weights. + // bonds_penalty = 0: weights_for_bonds = weights.clone() + // bonds_penalty = 1: weights_for_bonds = clipped_weights.clone() + let weights_for_bonds: Vec> = + interpolate_sparse(&weights, &clipped_weights, n, bonds_penalty); + // Access network bonds. let mut bonds: Vec> = Self::get_bonds_sparse(netuid); log::trace!("B: {:?}", &bonds); @@ -523,7 +546,8 @@ impl Pallet { log::trace!("B (mask+norm): {:?}", &bonds); // Compute bonds delta column normalized. - let mut bonds_delta: Vec> = row_hadamard_sparse(&weights, &active_stake); // ΔB = W◦S (outdated W masked) + let mut bonds_delta: Vec> = + row_hadamard_sparse(&weights_for_bonds, &active_stake); // ΔB = W◦S (outdated W masked) log::trace!("ΔB: {:?}", &bonds_delta); // Normalize bonds delta. @@ -713,6 +737,9 @@ impl Pallet { pub fn get_float_kappa(netuid: u16) -> I32F32 { I32F32::from_num(Self::get_kappa(netuid)).saturating_div(I32F32::from_num(u16::MAX)) } + pub fn get_float_bonds_penalty(netuid: u16) -> I32F32 { + I32F32::from_num(Self::get_bonds_penalty(netuid)) / I32F32::from_num(u16::MAX) + } pub fn get_block_at_registration(netuid: u16) -> Vec { let n = Self::get_subnetwork_n(netuid); diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index c895a7e1d..42381966b 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -568,6 +568,11 @@ pub mod pallet { pub fn DefaultBondsMovingAverage() -> u64 { T::InitialBondsMovingAverage::get() } + /// Default bonds penalty. + #[pallet::type_value] + pub fn DefaultBondsPenalty() -> u16 { + T::InitialBondsPenalty::get() + } #[pallet::type_value] /// Default validator prune length. pub fn DefaultValidatorPruneLen() -> u64 { @@ -1171,6 +1176,10 @@ pub mod pallet { /// --- MAP ( netuid ) --> bonds_moving_average pub type BondsMovingAverage = StorageMap<_, Identity, u16, u64, ValueQuery, DefaultBondsMovingAverage>; + #[pallet::storage] + /// --- MAP ( netuid ) --> bonds_penalty + pub type BondsPenalty = + StorageMap<_, Identity, u16, u16, ValueQuery, DefaultBondsPenalty>; /// --- MAP ( netuid ) --> weights_set_rate_limit #[pallet::storage] pub type WeightsSetRateLimit = diff --git a/pallets/subtensor/src/macros/config.rs b/pallets/subtensor/src/macros/config.rs index 6595c8a43..49bb44dc1 100644 --- a/pallets/subtensor/src/macros/config.rs +++ b/pallets/subtensor/src/macros/config.rs @@ -93,6 +93,9 @@ mod config { /// Initial bonds moving average. #[pallet::constant] type InitialBondsMovingAverage: Get; + /// Initial bonds penalty. + #[pallet::constant] + type InitialBondsPenalty: Get; /// Initial target registrations per interval. #[pallet::constant] type InitialTargetRegistrationsPerInterval: Get; diff --git a/pallets/subtensor/src/macros/events.rs b/pallets/subtensor/src/macros/events.rs index 7ef00b1e3..a7d2b99b9 100644 --- a/pallets/subtensor/src/macros/events.rs +++ b/pallets/subtensor/src/macros/events.rs @@ -55,6 +55,8 @@ mod events { ImmunityPeriodSet(u16, u16), /// bonds moving average is set for a subnet. BondsMovingAverageSet(u16, u64), + /// bonds penalty is set for a subnet. + BondsPenaltySet(u16, u16), /// setting the max number of allowed validators on a subnet. MaxAllowedValidatorsSet(u16, u16), /// the axon server information is added to the network. diff --git a/pallets/subtensor/src/tests/epoch.rs b/pallets/subtensor/src/tests/epoch.rs index 0bd5a18cb..2b0f7719f 100644 --- a/pallets/subtensor/src/tests/epoch.rs +++ b/pallets/subtensor/src/tests/epoch.rs @@ -154,10 +154,14 @@ fn init_run_epochs( random_weights: bool, random_seed: u64, sparse: bool, + bonds_penalty: u16, ) { // === Create the network add_network(netuid, u16::MAX - 1, 0); // set higher tempo to avoid built-in epoch, then manual epoch instead + // === Set bonds penalty + SubtensorModule::set_bonds_penalty(netuid, bonds_penalty); + // === Register uids SubtensorModule::set_max_allowed_uids(netuid, n); for key in 0..n { @@ -403,21 +407,25 @@ fn init_run_epochs( // let epochs: u16 = 1; // let interleave = 2; // log::info!("test_consensus_guarantees ({network_n:?}, {validators_n:?} validators)"); -// for (major_stake, major_weight, minor_weight, weight_stddev) in vec![ -// (0.51, 1., 1., 0.001), -// (0.51, 0.03, 0., 0.001), -// (0.51, 0.51, 0.49, 0.001), -// (0.51, 0.51, 1., 0.001), -// (0.51, 0.61, 0.8, 0.1), -// (0.6, 0.67, 0.65, 0.2), -// (0.6, 0.74, 0.77, 0.4), -// (0.6, 0.76, 0.8, 0.4), -// (0.6, 0.76, 1., 0.4), -// (0.6, 0.92, 1., 0.4), -// (0.6, 0.94, 1., 0.4), -// (0.65, 0.78, 0.85, 0.6), -// (0.7, 0.81, 0.85, 0.8), -// (0.7, 0.83, 0.85, 1.), +// for (major_stake, major_weight, minor_weight, weight_stddev, bonds_penalty) in vec![ +// (0.51, 1., 1., 0.001, u16::MAX), +// (0.51, 0.03, 0., 0.001, u16::MAX), +// (0.51, 0.51, 0.49, 0.001, u16::MAX), +// (0.51, 0.51, 1., 0.001, u16::MAX), +// (0.51, 0.61, 0.8, 0.1, u16::MAX), +// (0.6, 0.67, 0.65, 0.2, u16::MAX), +// (0.6, 0.74, 0.77, 0.4, u16::MAX), +// (0.6, 0.76, 0.8, 0.4, u16::MAX), +// (0.6, 0.73, 1., 0.4, u16::MAX), // bonds_penalty = 100% +// (0.6, 0.74, 1., 0.4, 55800), // bonds_penalty = 85% +// (0.6, 0.76, 1., 0.4, 43690), // bonds_penalty = 66% +// (0.6, 0.78, 1., 0.4, 21845), // bonds_penalty = 33% +// (0.6, 0.79, 1., 0.4, 0), // bonds_penalty = 0% +// (0.6, 0.92, 1., 0.4, u16::MAX), +// (0.6, 0.94, 1., 0.4, u16::MAX), +// (0.65, 0.78, 0.85, 0.6, u16::MAX), +// (0.7, 0.81, 0.85, 0.8, u16::MAX), +// (0.7, 0.83, 0.85, 1., u16::MAX), // ] { // let ( // validators, @@ -455,6 +463,7 @@ fn init_run_epochs( // false, // 0, // false, +// bonds_penalty // ); // let mut major_emission: I64F64 = I64F64::from_num(0); @@ -698,6 +707,7 @@ fn test_512_graph() { false, 0, false, + u16::MAX, ); let bonds = SubtensorModule::get_bonds(netuid); for uid in validators { @@ -744,96 +754,99 @@ fn test_512_graph_random_weights() { let epochs: u16 = 1; log::info!("test_{network_n:?}_graph_random_weights ({validators_n:?} validators)"); for interleave in 0..3 { + // server-self weight off/on for server_self in [false, true] { - // server-self weight off/on - let (validators, servers) = distribute_nodes( - validators_n as usize, - network_n as usize, - interleave as usize, - ); - let server: usize = servers[0] as usize; - let validator: usize = validators[0] as usize; - #[allow(clippy::type_complexity)] - let (mut rank, mut incentive, mut dividend, mut emission, mut bondv, mut bonds): ( - Vec, - Vec, - Vec, - Vec, - Vec, - Vec, - ) = (vec![], vec![], vec![], vec![], vec![], vec![]); - - // Dense epoch - new_test_ext(1).execute_with(|| { - init_run_epochs( - netuid, - network_n, - &validators, - &servers, - epochs, - 1, - server_self, - &[], - false, - &[], - false, - true, - interleave as u64, - false, + for bonds_penalty in vec![0, u16::MAX / 2, u16::MAX] { + let (validators, servers) = distribute_nodes( + validators_n as usize, + network_n as usize, + interleave as usize, ); + let server: usize = servers[0] as usize; + let validator: usize = validators[0] as usize; + let (mut rank, mut incentive, mut dividend, mut emission, mut bondv, mut bonds): ( + Vec, + Vec, + Vec, + Vec, + Vec, + Vec, + ) = (vec![], vec![], vec![], vec![], vec![], vec![]); + + // Dense epoch + new_test_ext().execute_with(|| { + init_run_epochs( + netuid, + network_n, + &validators, + &servers, + epochs, + 1, + server_self, + &vec![], + false, + &vec![], + false, + true, + interleave as u64, + false, + bonds_penalty, + ); - let bond = SubtensorModule::get_bonds(netuid); - for uid in 0..network_n { - rank.push(SubtensorModule::get_rank_for_uid(netuid, uid)); - incentive.push(SubtensorModule::get_incentive_for_uid(netuid, uid)); - dividend.push(SubtensorModule::get_dividends_for_uid(netuid, uid)); - emission.push(SubtensorModule::get_emission_for_uid(netuid, uid)); - bondv.push(bond[uid as usize][validator]); - bonds.push(bond[uid as usize][server]); - } - }); + let bond = SubtensorModule::get_bonds(netuid); + for uid in 0..network_n { + rank.push(SubtensorModule::get_rank_for_uid(netuid, uid)); + incentive.push(SubtensorModule::get_incentive_for_uid(netuid, uid)); + dividend.push(SubtensorModule::get_dividends_for_uid(netuid, uid)); + emission.push(SubtensorModule::get_emission_for_uid(netuid, uid)); + bondv.push(bond[uid as usize][validator]); + bonds.push(bond[uid as usize][server]); + } + }); - // Sparse epoch (same random seed as dense) - new_test_ext(1).execute_with(|| { - init_run_epochs( - netuid, - network_n, - &validators, - &servers, - epochs, - 1, - server_self, - &[], - false, - &[], - false, - true, - interleave as u64, - true, - ); - // Assert that dense and sparse epoch results are equal - let bond = SubtensorModule::get_bonds(netuid); - for uid in 0..network_n { - assert_eq!( - SubtensorModule::get_rank_for_uid(netuid, uid), - rank[uid as usize] - ); - assert_eq!( - SubtensorModule::get_incentive_for_uid(netuid, uid), - incentive[uid as usize] - ); - assert_eq!( - SubtensorModule::get_dividends_for_uid(netuid, uid), - dividend[uid as usize] - ); - assert_eq!( - SubtensorModule::get_emission_for_uid(netuid, uid), - emission[uid as usize] + // Sparse epoch (same random seed as dense) + new_test_ext().execute_with(|| { + init_run_epochs( + netuid, + network_n, + &validators, + &servers, + epochs, + 1, + server_self, + &vec![], + false, + &vec![], + false, + true, + interleave as u64, + true, + bonds_penalty, ); - assert_eq!(bond[uid as usize][validator], bondv[uid as usize]); - assert_eq!(bond[uid as usize][server], bonds[uid as usize]); - } - }); + // Assert that dense and sparse epoch results are equal + let bond = SubtensorModule::get_bonds(netuid); + for uid in 0..network_n { + assert_eq!( + SubtensorModule::get_rank_for_uid(netuid, uid), + rank[uid as usize] + ); + assert_eq!( + SubtensorModule::get_incentive_for_uid(netuid, uid), + incentive[uid as usize] + ); + assert_eq!( + SubtensorModule::get_dividends_for_uid(netuid, uid), + dividend[uid as usize] + ); + assert_eq!( + SubtensorModule::get_emission_for_uid(netuid, uid), + emission[uid as usize] + ); + assert_eq!(bond[uid as usize][validator], bondv[uid as usize]); + assert_eq!(bond[uid as usize][server], bonds[uid as usize]); + } + }); + } } } } @@ -873,6 +886,7 @@ fn test_512_graph_random_weights() { // false, // 0, // true, +// u16::MAX, // ); // let (total_stake, _, _) = SubtensorModule::get_stake_weights_for_network(netuid); // assert_eq!(total_stake.iter().map(|s| s.to_num::()).sum::(), 21_000_000_000_000_000); @@ -940,6 +954,7 @@ fn test_512_graph_random_weights() { // false, // 0, // true, +// u16::MAX, // ); // let bonds = SubtensorModule::get_bonds(netuid); // for uid in validators { @@ -995,6 +1010,8 @@ fn test_bonds() { SubtensorModule::set_weights_set_rate_limit( netuid, 0 ); SubtensorModule::set_min_allowed_weights( netuid, 1 ); SubtensorModule::set_max_weight_limit( netuid, u16::MAX ); + SubtensorModule::set_bonds_penalty(netuid, u16::MAX); + // === Register [validator1, validator2, validator3, validator4, server1, server2, server3, server4] for key in 0..n as u64 { @@ -1752,6 +1769,7 @@ fn test_outdated_weights() { SubtensorModule::set_target_registrations_per_interval(netuid, n); SubtensorModule::set_min_allowed_weights(netuid, 0); SubtensorModule::set_max_weight_limit(netuid, u16::MAX); + SubtensorModule::set_bonds_penalty(netuid, u16::MAX); assert_eq!(SubtensorModule::get_registrations_this_block(netuid), 0); // === Register [validator1, validator2, server1, server2] @@ -2817,6 +2835,7 @@ fn test_blocks_since_last_step() { // let epochs: u16 = 1; // let interleave = 0; // let weight_stddev: I32F32 = fixed(0.4); +// let bonds_penalty: u16 = u16::MAX; // println!("["); // for _major_stake in vec![0.51, 0.55, 0.6, 0.65, 0.7, 0.75, 0.8, 0.85, 0.9, 0.95, 0.99] { // let major_stake: I32F32 = I32F32::from_num(_major_stake); @@ -2846,7 +2865,7 @@ fn test_blocks_since_last_step() { // ); // // new_test_ext(1).execute_with(|| { -// init_run_epochs(netuid, network_n, &validators, &servers, epochs, 1, true, &stake, true, &weights, true, false, 0, true); +// init_run_epochs(netuid, network_n, &validators, &servers, epochs, 1, true, &stake, true, &weights, true, false, 0, true, bonds_penalty); // // let mut major_emission: I64F64 = I64F64::from_num(0); // let mut minor_emission: I64F64 = I64F64::from_num(0); diff --git a/pallets/subtensor/src/tests/math.rs b/pallets/subtensor/src/tests/math.rs index c5aaca84c..6eb51bc29 100644 --- a/pallets/subtensor/src/tests/math.rs +++ b/pallets/subtensor/src/tests/math.rs @@ -1716,6 +1716,240 @@ fn test_math_weighted_median_col_sparse() { ); } +#[test] +fn test_math_interpolate() { + let mat1: Vec> = vec![vec![]]; + let mat2: Vec> = vec![vec![]]; + let target: Vec> = vec![vec![]]; + let ratio = I32F32::from_num(0); + let result = interpolate(&mat1, &mat2, ratio); + assert_mat_compare(&result, &target, I32F32::from_num(0)); + + let mat1: Vec> = vec![vec![I32F32::from_num(0)]]; + let mat2: Vec> = vec![vec![I32F32::from_num(1)]]; + let target: Vec> = vec![vec![I32F32::from_num(0)]]; + let ratio = I32F32::from_num(0); + let result = interpolate(&mat1, &mat2, ratio); + assert_mat_compare(&result, &target, I32F32::from_num(0)); + + let target: Vec> = vec![vec![I32F32::from_num(1)]]; + let ratio = I32F32::from_num(1); + let result = interpolate(&mat1, &mat2, ratio); + assert_mat_compare(&result, &target, I32F32::from_num(0)); + + let mat1: Vec = vec![0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]; + let mat2: Vec = vec![0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]; + let target: Vec = vec![0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]; + let mat1 = vec_to_mat_fixed(&mat1, 4, false); + let mat2 = vec_to_mat_fixed(&mat2, 4, false); + let ratio = I32F32::from_num(0); + let target = vec_to_mat_fixed(&target, 4, false); + let result = interpolate(&mat1, &mat2, ratio); + assert_mat_compare(&result, &target, I32F32::from_num(0)); + + let ratio = I32F32::from_num(1); + let result = interpolate(&mat1, &mat2, ratio); + assert_mat_compare(&result, &target, I32F32::from_num(0)); + + let mat1: Vec = vec![1., 10., 100., 1000., 10000., 100000.]; + let mat2: Vec = vec![10., 100., 1000., 10000., 100000., 1000000.]; + let target: Vec = vec![1., 10., 100., 1000., 10000., 100000.]; + let mat1 = vec_to_mat_fixed(&mat1, 3, false); + let mat2 = vec_to_mat_fixed(&mat2, 3, false); + let ratio = I32F32::from_num(0); + let target = vec_to_mat_fixed(&target, 3, false); + let result = interpolate(&mat1, &mat2, ratio); + assert_mat_compare(&result, &target, I32F32::from_num(0)); + + let target: Vec = vec![9.1, 91., 910., 9100., 91000., 910000.]; + let ratio = I32F32::from_num(0.9); + let target = vec_to_mat_fixed(&target, 3, false); + let result = interpolate(&mat1, &mat2, ratio); + assert_mat_compare(&result, &target, I32F32::from_num(0.0001)); + + let mat1: Vec = vec![0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]; + let mat2: Vec = vec![1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]; + let target: Vec = vec![0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]; + let mat1 = vec_to_mat_fixed(&mat1, 4, false); + let mat2 = vec_to_mat_fixed(&mat2, 4, false); + let ratio = I32F32::from_num(0); + let target = vec_to_mat_fixed(&target, 4, false); + let result = interpolate(&mat1, &mat2, ratio); + assert_mat_compare(&result, &target, I32F32::from_num(0)); + + let target: Vec = vec![ + 0.000000001, + 0.000000001, + 0.000000001, + 0.000000001, + 0.000000001, + 0.000000001, + 0.000000001, + 0.000000001, + 0.000000001, + 0.000000001, + 0.000000001, + 0.000000001, + ]; + let ratio = I32F32::from_num(0.000000001); + let target = vec_to_mat_fixed(&target, 4, false); + let result = interpolate(&mat1, &mat2, ratio); + assert_mat_compare(&result, &target, I32F32::from_num(0)); + + let target: Vec = vec![0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5]; + let ratio = I32F32::from_num(0.5); + let target = vec_to_mat_fixed(&target, 4, false); + let result = interpolate(&mat1, &mat2, ratio); + assert_mat_compare(&result, &target, I32F32::from_num(0)); + + let target: Vec = vec![ + 0.9999998808, + 0.9999998808, + 0.9999998808, + 0.9999998808, + 0.9999998808, + 0.9999998808, + 0.9999998808, + 0.9999998808, + 0.9999998808, + 0.9999998808, + 0.9999998808, + 0.9999998808, + ]; + let ratio = I32F32::from_num(0.9999998808); + let target = vec_to_mat_fixed(&target, 4, false); + let result = interpolate(&mat1, &mat2, ratio); + assert_mat_compare(&result, &target, I32F32::from_num(0)); + + let target: Vec = vec![1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]; + let ratio = I32F32::from_num(1); + let target = vec_to_mat_fixed(&target, 4, false); + let result = interpolate(&mat1, &mat2, ratio); + assert_mat_compare(&result, &target, I32F32::from_num(0)); +} + +#[test] +fn test_math_interpolate_sparse() { + let mat1: Vec> = vec![vec![]]; + let mat2: Vec> = vec![vec![]]; + let target: Vec> = vec![vec![]]; + let ratio = I32F32::from_num(0); + let result = interpolate_sparse(&mat1, &mat2, 0, ratio); + assert_sparse_mat_compare(&result, &target, I32F32::from_num(0)); + + let mat1: Vec = vec![0.]; + let mat2: Vec = vec![1.]; + let target: Vec = vec![0.]; + let mat1 = vec_to_sparse_mat_fixed(&mat1, 1, false); + let mat2 = vec_to_sparse_mat_fixed(&mat2, 1, false); + let ratio = I32F32::from_num(0); + let target = vec_to_sparse_mat_fixed(&target, 1, false); + let result = interpolate_sparse(&mat1, &mat2, 1, ratio); + assert_sparse_mat_compare(&result, &target, I32F32::from_num(0)); + + let target: Vec = vec![0.5]; + let ratio = I32F32::from_num(0.5); + let target = vec_to_sparse_mat_fixed(&target, 1, false); + let result = interpolate_sparse(&mat1, &mat2, 1, ratio); + assert_sparse_mat_compare(&result, &target, I32F32::from_num(0)); + + let target: Vec = vec![1.]; + let ratio = I32F32::from_num(1); + let target = vec_to_sparse_mat_fixed(&target, 1, false); + let result = interpolate_sparse(&mat1, &mat2, 1, ratio); + assert_sparse_mat_compare(&result, &target, I32F32::from_num(0)); + + let mat1: Vec = vec![0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]; + let mat2: Vec = vec![0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]; + let target: Vec = vec![0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]; + let mat1 = vec_to_sparse_mat_fixed(&mat1, 4, false); + let mat2 = vec_to_sparse_mat_fixed(&mat2, 4, false); + let ratio = I32F32::from_num(0); + let target = vec_to_sparse_mat_fixed(&target, 4, false); + let result = interpolate_sparse(&mat1, &mat2, 3, ratio); + assert_sparse_mat_compare(&result, &target, I32F32::from_num(0)); + + let ratio = I32F32::from_num(1); + let result = interpolate_sparse(&mat1, &mat2, 3, ratio); + assert_sparse_mat_compare(&result, &target, I32F32::from_num(0)); + + let mat1: Vec = vec![1., 0., 100., 1000., 10000., 100000.]; + let mat2: Vec = vec![10., 100., 1000., 10000., 100000., 0.]; + let target: Vec = vec![1., 0., 100., 1000., 10000., 100000.]; + let mat1 = vec_to_sparse_mat_fixed(&mat1, 3, false); + let mat2 = vec_to_sparse_mat_fixed(&mat2, 3, false); + let ratio = I32F32::from_num(0); + let target = vec_to_sparse_mat_fixed(&target, 3, false); + let result = interpolate_sparse(&mat1, &mat2, 2, ratio); + assert_sparse_mat_compare(&result, &target, I32F32::from_num(0)); + + let target: Vec = vec![9.1, 90., 910., 9100., 91000., 10000.]; + let ratio = I32F32::from_num(0.9); + let target = vec_to_sparse_mat_fixed(&target, 3, false); + let result = interpolate_sparse(&mat1, &mat2, 2, ratio); + assert_sparse_mat_compare(&result, &target, I32F32::from_num(0.0001)); + + let mat1: Vec = vec![0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]; + let mat2: Vec = vec![1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]; + let target: Vec = vec![0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]; + let mat1 = vec_to_sparse_mat_fixed(&mat1, 4, false); + let mat2 = vec_to_sparse_mat_fixed(&mat2, 4, false); + let ratio = I32F32::from_num(0); + let target = vec_to_sparse_mat_fixed(&target, 4, false); + let result = interpolate_sparse(&mat1, &mat2, 3, ratio); + assert_sparse_mat_compare(&result, &target, I32F32::from_num(0)); + + let target: Vec = vec![ + 0.000000001, + 0.000000001, + 0.000000001, + 0.000000001, + 0.000000001, + 0.000000001, + 0.000000001, + 0.000000001, + 0.000000001, + 0.000000001, + 0.000000001, + 0.000000001, + ]; + let ratio = I32F32::from_num(0.000000001); + let target = vec_to_sparse_mat_fixed(&target, 4, false); + let result = interpolate_sparse(&mat1, &mat2, 3, ratio); + assert_sparse_mat_compare(&result, &target, I32F32::from_num(0)); + + let target: Vec = vec![0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5]; + let ratio = I32F32::from_num(0.5); + let target = vec_to_sparse_mat_fixed(&target, 4, false); + let result = interpolate_sparse(&mat1, &mat2, 3, ratio); + assert_sparse_mat_compare(&result, &target, I32F32::from_num(0)); + + let target: Vec = vec![ + 0.9999998808, + 0.9999998808, + 0.9999998808, + 0.9999998808, + 0.9999998808, + 0.9999998808, + 0.9999998808, + 0.9999998808, + 0.9999998808, + 0.9999998808, + 0.9999998808, + 0.9999998808, + ]; + let ratio = I32F32::from_num(0.9999998808); + let target = vec_to_sparse_mat_fixed(&target, 4, false); + let result = interpolate_sparse(&mat1, &mat2, 3, ratio); + assert_sparse_mat_compare(&result, &target, I32F32::from_num(0)); + + let target: Vec = vec![1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]; + let ratio = I32F32::from_num(1); + let target = vec_to_sparse_mat_fixed(&target, 4, false); + let result = interpolate_sparse(&mat1, &mat2, 3, ratio); + assert_sparse_mat_compare(&result, &target, I32F32::from_num(0)); +} + #[test] fn test_math_hadamard() { let mat2: Vec = vec![1., 2., 3., 4., 5., 6., 7., 8., 9., 10., 11., 12.]; diff --git a/pallets/subtensor/src/tests/mock.rs b/pallets/subtensor/src/tests/mock.rs index a6f9caed7..facab56ca 100644 --- a/pallets/subtensor/src/tests/mock.rs +++ b/pallets/subtensor/src/tests/mock.rs @@ -138,6 +138,7 @@ parameter_types! { pub const InitialImmunityPeriod: u16 = 2; pub const InitialMaxAllowedUids: u16 = 2; pub const InitialBondsMovingAverage: u64 = 900_000; + pub const InitialBondsPenalty:u16 = 0; pub const InitialStakePruningMin: u16 = 0; pub const InitialFoundationDistribution: u64 = 0; pub const InitialDefaultDelegateTake: u16 = 11_796; // 18%, same as in production @@ -373,6 +374,7 @@ impl crate::Config for Test { type InitialMaxRegistrationsPerBlock = InitialMaxRegistrationsPerBlock; type InitialPruningScore = InitialPruningScore; type InitialBondsMovingAverage = InitialBondsMovingAverage; + type InitialBondsPenalty = InitialBondsPenalty; type InitialMaxAllowedValidators = InitialMaxAllowedValidators; type InitialDefaultDelegateTake = InitialDefaultDelegateTake; type InitialMinDelegateTake = InitialMinDelegateTake; diff --git a/pallets/subtensor/src/utils/misc.rs b/pallets/subtensor/src/utils/misc.rs index 25f547c5d..f1e0c7eb4 100644 --- a/pallets/subtensor/src/utils/misc.rs +++ b/pallets/subtensor/src/utils/misc.rs @@ -566,6 +566,14 @@ impl Pallet { Self::deposit_event(Event::BondsMovingAverageSet(netuid, bonds_moving_average)); } + pub fn get_bonds_penalty(netuid: u16) -> u16 { + BondsPenalty::::get(netuid) + } + pub fn set_bonds_penalty(netuid: u16, bonds_penalty: u16) { + BondsPenalty::::insert(netuid, bonds_penalty); + Self::deposit_event(Event::BondsPenaltySet(netuid, bonds_penalty)); + } + pub fn get_max_registrations_per_block(netuid: u16) -> u16 { MaxRegistrationsPerBlock::::get(netuid) } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 72221b9ff..0782390ae 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -1011,6 +1011,7 @@ parameter_types! { pub const SubtensorInitialMaxRegistrationsPerBlock: u16 = 1; pub const SubtensorInitialPruningScore : u16 = u16::MAX; pub const SubtensorInitialBondsMovingAverage: u64 = 900_000; + pub const SuntensorInitialBondsPenalty: u16 = 0; pub const SubtensorInitialDefaultTake: u16 = 11_796; // 18% honest number. pub const SubtensorInitialMinDelegateTake: u16 = 0; // Allow 0% delegate take pub const SubtensorInitialDefaultChildKeyTake: u16 = 0; // Allow 0% childkey take @@ -1058,6 +1059,7 @@ impl pallet_subtensor::Config for Runtime { type InitialKappa = SubtensorInitialKappa; type InitialMaxAllowedUids = SubtensorInitialMaxAllowedUids; type InitialBondsMovingAverage = SubtensorInitialBondsMovingAverage; + type InitialBondsPenalty = SubtensorInitialBondsPenalty; type InitialIssuance = SubtensorInitialIssuance; type InitialMinAllowedWeights = SubtensorInitialMinAllowedWeights; type InitialEmissionValue = SubtensorInitialEmissionValue;