Skip to content

Commit c34d72b

Browse files
committed
neuron pruning changes, initial tests
1 parent 6a37d4b commit c34d72b

File tree

3 files changed

+158
-44
lines changed

3 files changed

+158
-44
lines changed

pallets/subtensor/src/registration.rs

Lines changed: 36 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -423,65 +423,57 @@ impl<T: Config> Pallet<T> {
423423
}
424424

425425
/// Determine which peer to prune from the network by finding the element with the lowest pruning score out of
426-
/// immunity period. If all neurons are in immunity period, return node with lowest prunning score.
427-
/// This function will always return an element to prune.
426+
/// immunity period. If there is a tie for lowest pruning score, the neuron registered earliest is pruned.
427+
/// If all neurons are in immunity period, the neuron with the lowest pruning score is pruned. If there is a tie for
428+
/// the lowest pruning score, the immune neuron registered earliest is pruned.
429+
/// Ties for earliest registration are broken by the neuron with the lowest uid.
428430
pub fn get_neuron_to_prune(netuid: u16) -> u16 {
429431
let mut min_score: u16 = u16::MAX;
430-
let mut min_score_in_immunity_period = u16::MAX;
431-
let mut uid_with_min_score = 0;
432-
let mut uid_with_min_score_in_immunity_period: u16 = 0;
432+
let mut min_score_in_immunity: u16 = u16::MAX;
433+
let mut earliest_registration: u64 = u64::MAX;
434+
let mut earliest_registration_in_immunity: u64 = u64::MAX;
435+
let mut uid_to_prune: u16 = 0;
436+
let mut uid_to_prune_in_immunity: u16 = 0;
437+
let mut found_non_immune = false;
433438

434439
let neurons_n = Self::get_subnetwork_n(netuid);
435440
if neurons_n == 0 {
436441
return 0; // If there are no neurons in this network.
437442
}
438443

439-
let current_block: u64 = Self::get_current_block_as_u64();
440-
let immunity_period: u64 = Self::get_immunity_period(netuid) as u64;
441-
for neuron_uid_i in 0..neurons_n {
442-
let pruning_score: u16 = Self::get_pruning_score_for_uid(netuid, neuron_uid_i);
444+
for neuron_uid in 0..neurons_n {
445+
let pruning_score: u16 = Self::get_pruning_score_for_uid(netuid, neuron_uid);
443446
let block_at_registration: u64 =
444-
Self::get_neuron_block_at_registration(netuid, neuron_uid_i);
445-
#[allow(clippy::comparison_chain)]
446-
if min_score == pruning_score {
447-
if current_block.saturating_sub(block_at_registration) < immunity_period {
448-
//neuron is in immunity period
449-
if min_score_in_immunity_period > pruning_score {
450-
min_score_in_immunity_period = pruning_score;
451-
uid_with_min_score_in_immunity_period = neuron_uid_i;
452-
}
453-
} else {
454-
uid_with_min_score = neuron_uid_i;
447+
Self::get_neuron_block_at_registration(netuid, neuron_uid);
448+
let is_immune = Self::get_neuron_is_immune(netuid, neuron_uid);
449+
450+
if is_immune {
451+
if pruning_score < min_score_in_immunity
452+
|| (pruning_score == min_score_in_immunity
453+
&& block_at_registration < earliest_registration_in_immunity)
454+
{
455+
min_score_in_immunity = pruning_score;
456+
earliest_registration_in_immunity = block_at_registration;
457+
uid_to_prune_in_immunity = neuron_uid;
455458
}
456-
}
457-
// Find min pruning score.
458-
else if min_score > pruning_score {
459-
if current_block.saturating_sub(block_at_registration) < immunity_period {
460-
//neuron is in immunity period
461-
if min_score_in_immunity_period > pruning_score {
462-
min_score_in_immunity_period = pruning_score;
463-
uid_with_min_score_in_immunity_period = neuron_uid_i;
464-
}
465-
} else {
459+
} else {
460+
found_non_immune = true;
461+
if pruning_score < min_score
462+
|| (pruning_score == min_score && block_at_registration < earliest_registration)
463+
{
466464
min_score = pruning_score;
467-
uid_with_min_score = neuron_uid_i;
465+
earliest_registration = block_at_registration;
466+
uid_to_prune = neuron_uid;
468467
}
469468
}
470469
}
471-
if min_score == u16::MAX {
472-
//all neuorns are in immunity period
473-
Self::set_pruning_score_for_uid(
474-
netuid,
475-
uid_with_min_score_in_immunity_period,
476-
u16::MAX,
477-
);
478-
uid_with_min_score_in_immunity_period
470+
471+
if found_non_immune {
472+
Self::set_pruning_score_for_uid(netuid, uid_to_prune, u16::MAX);
473+
uid_to_prune
479474
} else {
480-
// We replace the pruning score here with u16 max to ensure that all peers always have a
481-
// pruning score. In the event that every peer has been pruned this function will prune
482-
// the last element in the network continually.
483-
Self::set_pruning_score_for_uid(netuid, uid_with_min_score, u16::MAX);
484-
uid_with_min_score
475+
Self::set_pruning_score_for_uid(netuid, uid_to_prune_in_immunity, u16::MAX);
476+
uid_to_prune_in_immunity
485477
}
486478
}
487479

pallets/subtensor/src/utils.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,13 @@ impl<T: Config> Pallet<T> {
461461
Self::deposit_event(Event::ImmunityPeriodSet(netuid, immunity_period));
462462
}
463463

464+
pub fn get_neuron_is_immune(netuid: u16, uid: u16) -> bool {
465+
let registered_at = Self::get_neuron_block_at_registration(netuid, uid);
466+
let current_block = Self::get_current_block_as_u64();
467+
let immunity_period = Self::get_immunity_period(netuid);
468+
current_block.saturating_sub(registered_at) < u64::from(immunity_period)
469+
}
470+
464471
pub fn get_min_allowed_weights(netuid: u16) -> u16 {
465472
MinAllowedWeights::<T>::get(netuid)
466473
}

pallets/subtensor/tests/registration.rs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
#![allow(clippy::unwrap_used)]
22

3+
use std::u16;
4+
35
use frame_support::traits::Currency;
6+
use substrate_fixed::types::extra::True;
47

58
use crate::mock::*;
69
use frame_support::dispatch::{DispatchClass, DispatchInfo, GetDispatchInfo, Pays};
@@ -538,6 +541,118 @@ fn test_burn_adjustment() {
538541
});
539542
}
540543

544+
#[test]
545+
fn test_burn_registration_pruning_scenarios() {
546+
new_test_ext(1).execute_with(|| {
547+
let netuid: u16 = 1;
548+
let tempo: u16 = 13;
549+
let burn_cost = 1000;
550+
let coldkey_account_id = U256::from(667);
551+
let max_allowed_uids = 6;
552+
let immunity_period = 5000;
553+
554+
SubtensorModule::set_burn(netuid, burn_cost);
555+
SubtensorModule::set_max_allowed_uids(netuid, max_allowed_uids);
556+
SubtensorModule::set_target_registrations_per_interval(netuid, max_allowed_uids);
557+
SubtensorModule::set_immunity_period(netuid, immunity_period);
558+
559+
// SubtensorModule::set_immunity_period(netuid, immunity_period);
560+
561+
add_network(netuid, tempo, 0);
562+
563+
let mint_balance = burn_cost * u64::from(max_allowed_uids) + 1_000_000_000;
564+
SubtensorModule::add_balance_to_coldkey_account(&coldkey_account_id, mint_balance);
565+
566+
// Register first half of neurons
567+
for i in 0..3 {
568+
assert_ok!(SubtensorModule::burned_register(
569+
<<Test as Config>::RuntimeOrigin>::signed(coldkey_account_id),
570+
netuid,
571+
U256::from(i)
572+
));
573+
step_block(1);
574+
}
575+
576+
// Note: pruning score is set to u16::MAX after getting neuron to prune
577+
578+
// 1. Test all immune neurons
579+
assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 0), true);
580+
assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 1), true);
581+
assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 2), true);
582+
583+
SubtensorModule::set_pruning_score_for_uid(netuid, 0, 100);
584+
SubtensorModule::set_pruning_score_for_uid(netuid, 1, 75);
585+
SubtensorModule::set_pruning_score_for_uid(netuid, 2, 50);
586+
587+
// The immune neuron with the lowest score should be pruned
588+
assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), 2);
589+
590+
// 2. Test tie-breaking for immune neurons
591+
SubtensorModule::set_pruning_score_for_uid(netuid, 1, 50);
592+
SubtensorModule::set_pruning_score_for_uid(netuid, 2, 50);
593+
594+
// Should get the oldest neuron
595+
assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), 1);
596+
597+
// 3. Test no immune neurons
598+
step_block(immunity_period);
599+
600+
assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 0), false);
601+
assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 1), false);
602+
assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 2), false);
603+
604+
SubtensorModule::set_pruning_score_for_uid(netuid, 0, 100);
605+
SubtensorModule::set_pruning_score_for_uid(netuid, 1, 50);
606+
SubtensorModule::set_pruning_score_for_uid(netuid, 2, 75);
607+
608+
// The non-immune neuron with the lowest score should be pruned
609+
assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), 1);
610+
611+
// 4. Test tie-breaking for non-immune neurons
612+
SubtensorModule::set_pruning_score_for_uid(netuid, 1, 50);
613+
SubtensorModule::set_pruning_score_for_uid(netuid, 2, 50);
614+
615+
// Should get the oldest non-immune neuron
616+
assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), 1);
617+
618+
// 5. Test mixed immunity
619+
// Register second batch of neurons (these will be non-immune)
620+
for i in 3..6 {
621+
assert_ok!(SubtensorModule::burned_register(
622+
<<Test as Config>::RuntimeOrigin>::signed(coldkey_account_id),
623+
netuid,
624+
U256::from(i)
625+
));
626+
step_block(1);
627+
}
628+
629+
assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 3), true);
630+
assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 4), true);
631+
assert_eq!(SubtensorModule::get_neuron_is_immune(netuid, 5), true);
632+
633+
// Set pruning scores for all neurons
634+
SubtensorModule::set_pruning_score_for_uid(netuid, 0, 75); // non-immune
635+
SubtensorModule::set_pruning_score_for_uid(netuid, 1, 50); // non-immune
636+
SubtensorModule::set_pruning_score_for_uid(netuid, 2, 60); // non-immune
637+
SubtensorModule::set_pruning_score_for_uid(netuid, 3, 40); // immune
638+
SubtensorModule::set_pruning_score_for_uid(netuid, 4, 55); // immune
639+
SubtensorModule::set_pruning_score_for_uid(netuid, 5, 45); // immune
640+
641+
// The non-immune neuron with the lowest score should be pruned
642+
assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), 1);
643+
644+
// If we remove the lowest non-immune neuron, it should choose the next lowest non-immune
645+
SubtensorModule::set_pruning_score_for_uid(netuid, 1, u16::MAX);
646+
assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), 2);
647+
648+
// If we make all non-immune neurons have high scores, it should choose the oldest non-immune neuron
649+
SubtensorModule::set_pruning_score_for_uid(netuid, 0, u16::MAX);
650+
SubtensorModule::set_pruning_score_for_uid(netuid, 1, u16::MAX);
651+
SubtensorModule::set_pruning_score_for_uid(netuid, 2, u16::MAX);
652+
assert_eq!(SubtensorModule::get_neuron_to_prune(netuid), 0);
653+
});
654+
}
655+
541656
#[test]
542657
fn test_registration_too_many_registrations_per_block() {
543658
new_test_ext(1).execute_with(|| {

0 commit comments

Comments
 (0)