Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hotfix/dont max upscale bonds #1439

Closed
wants to merge 13 commits into from
21 changes: 11 additions & 10 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

53 changes: 53 additions & 0 deletions pallets/subtensor/src/epoch/math.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ pub fn fixed_to_u16(x: I32F32) -> u16 {
x.saturating_to_num::<u16>()
}

#[allow(dead_code)]
pub fn fixed_to_u32(x: I32F32) -> u32 {
x.saturating_to_num::<u32>()
}

#[allow(dead_code)]
pub fn fixed_to_u64(x: I32F32) -> u64 {
x.saturating_to_num::<u64>()
Expand Down Expand Up @@ -60,6 +65,11 @@ pub fn fixed_proportion_to_u16(x: I32F32) -> u16 {
fixed_to_u16(x.saturating_mul(I32F32::saturating_from_num(u16::MAX)))
}

#[allow(dead_code)]
pub fn fixed_proportion_to_u32(x: I32F32) -> u32 {
fixed_to_u32(x.saturating_mul(I32F32::saturating_from_num(u32::MAX)))
}

#[allow(dead_code)]
pub fn vec_fixed32_to_u64(vec: Vec<I32F32>) -> Vec<u64> {
vec.into_iter().map(fixed_to_u64).collect()
Expand Down Expand Up @@ -90,6 +100,11 @@ pub fn vec_fixed_proportions_to_u16(vec: Vec<I32F32>) -> Vec<u16> {
vec.into_iter().map(fixed_proportion_to_u16).collect()
}

#[allow(dead_code)]
pub fn vec_fixed_proportions_to_u32(vec: Vec<I32F32>) -> Vec<u32> {
vec.into_iter().map(fixed_proportion_to_u32).collect()
}

#[allow(dead_code)]
// Max-upscale vector and convert to u16 so max_value = u16::MAX. Assumes non-negative normalized input.
pub fn vec_max_upscale_to_u16(vec: &[I32F32]) -> Vec<u16> {
Expand Down Expand Up @@ -549,6 +564,24 @@ pub fn inplace_mask_rows(mask: &[bool], matrix: &mut [Vec<I32F32>]) {
});
}

// Apply column mask to matrix, mask=true will mask out, i.e. set to 0.
// Assumes each column has the same length.
#[allow(dead_code)]
pub fn inplace_mask_cols(mask: &[bool], matrix: &mut [Vec<I32F32>]) {
let Some(first_row) = matrix.first() else {
return;
};
assert_eq!(mask.len(), first_row.len());
let zero: I32F32 = I32F32::saturating_from_num(0);
matrix.iter_mut().for_each(|row_elem| {
row_elem.iter_mut().zip(mask).for_each(|(elem, mask_col)| {
if *mask_col {
*elem = zero;
}
});
});
}

// Mask out the diagonal of the input matrix in-place.
#[allow(dead_code)]
pub fn inplace_mask_diag(matrix: &mut [Vec<I32F32>]) {
Expand Down Expand Up @@ -674,6 +707,26 @@ pub fn vec_mask_sparse_matrix(
result
}

// Remove cells from sparse matrix where the mask function of a scalar and a vector is true.
#[allow(dead_code, clippy::indexing_slicing)]
pub fn scalar_vec_mask_sparse_matrix(
sparse_matrix: &[Vec<(u16, I32F32)>],
scalar: u64,
vector: &[u64],
mask_fn: &dyn Fn(u64, u64) -> bool,
) -> Vec<Vec<(u16, I32F32)>> {
let n: usize = sparse_matrix.len();
let mut result: Vec<Vec<(u16, I32F32)>> = vec![vec![]; n];
for (i, sparse_row) in sparse_matrix.iter().enumerate() {
for (j, value) in sparse_row {
if !mask_fn(scalar, vector[*j as usize]) {
result[i].push((*j, *value));
}
}
}
result
}

// Row-wise matrix-vector hadamard product.
#[allow(dead_code)]
pub fn row_hadamard(matrix: &[Vec<I32F32>], vector: &[I32F32]) -> Vec<Vec<I32F32>> {
Expand Down
82 changes: 44 additions & 38 deletions pallets/subtensor/src/epoch/run_epoch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ impl<T: Config> Pallet<T> {
let current_block: u64 = Self::get_current_block_as_u64();
log::trace!("current_block:\n{:?}\n", current_block);

// Get tempo.
let tempo: u64 = Self::get_tempo(netuid).into();
log::trace!("tempo:\n{:?}\n", tempo);

// Get activity cutoff.
let activity_cutoff: u64 = Self::get_activity_cutoff(netuid) as u64;
log::trace!("activity_cutoff:\n{:?}\n", activity_cutoff);
Expand All @@ -44,7 +48,7 @@ impl<T: Config> Pallet<T> {
let block_at_registration: Vec<u64> = Self::get_block_at_registration(netuid);
log::trace!("Block at registration:\n{:?}\n", &block_at_registration);

// Outdated matrix, updated_ij=True if i has last updated (weights) after j has last registered.
// Outdated matrix, outdated_ij=True if i has last updated (weights) after j has last registered.
let outdated: Vec<Vec<bool>> = last_update
.iter()
.map(|updated| {
Expand All @@ -56,6 +60,16 @@ impl<T: Config> Pallet<T> {
.collect();
log::trace!("Outdated:\n{:?}\n", &outdated);

// Recently registered matrix, recently_ij=True if last_tempo was *before* j was last registered.
// Mask if: the last tempo block happened *before* the registration block
// ==> last_tempo <= registered
let last_tempo: u64 = current_block.saturating_sub(tempo);
let recently_registered: Vec<bool> = block_at_registration
.iter()
.map(|registered| last_tempo <= *registered)
.collect();
log::trace!("Recently registered:\n{:?}\n", &recently_registered);

// ===========
// == Stake ==
// ===========
Expand Down Expand Up @@ -185,17 +199,16 @@ impl<T: Config> Pallet<T> {

// Access network bonds.
let mut bonds: Vec<Vec<I32F32>> = Self::get_bonds(netuid);
inplace_mask_matrix(&outdated, &mut bonds); // mask outdated bonds
inplace_col_normalize(&mut bonds); // sum_i b_ij = 1
log::trace!("B:\n{:?}\n", &bonds);

// Remove bonds referring to neurons that have registered since last tempo.
inplace_mask_cols(&recently_registered, &mut bonds); // mask recently registered bonds

// Compute bonds delta column normalized.
let mut bonds_delta: Vec<Vec<I32F32>> = row_hadamard(&weights_for_bonds, &active_stake); // ΔB = W◦S
inplace_col_normalize(&mut bonds_delta); // sum_i b_ij = 1
let bonds_delta: Vec<Vec<I32F32>> = row_hadamard(&weights_for_bonds, &active_stake); // ΔB = W◦S
log::trace!("ΔB:\n{:?}\n", &bonds_delta);

// Compute the Exponential Moving Average (EMA) of bonds.
let mut ema_bonds = Self::compute_ema_bonds(netuid, consensus.clone(), bonds_delta, bonds);
inplace_col_normalize(&mut ema_bonds); // sum_i b_ij = 1
let ema_bonds = Self::compute_ema_bonds(netuid, consensus.clone(), bonds_delta, bonds);
log::trace!("emaB:\n{:?}\n", &ema_bonds);

// Compute dividends: d_i = SUM(j) b_ij * inc_j
Expand Down Expand Up @@ -326,8 +339,6 @@ impl<T: Config> Pallet<T> {
ValidatorTrust::<T>::insert(netuid, cloned_validator_trust);
ValidatorPermit::<T>::insert(netuid, new_validator_permits.clone());

// Column max-upscale EMA bonds for storage: max_i w_ij = 1.
inplace_col_max_upscale(&mut ema_bonds);
new_validator_permits
.iter()
.zip(validator_permits)
Expand All @@ -336,13 +347,13 @@ impl<T: Config> Pallet<T> {
.for_each(|(i, ((new_permit, validator_permit), ema_bond))| {
// Set bonds only if uid retains validator permit, otherwise clear bonds.
if *new_permit {
let new_bonds_row: Vec<(u16, u16)> = (0..n)
.zip(vec_fixed_proportions_to_u16(ema_bond.clone()))
let new_bonds_row: Vec<(u16, u32)> = (0..n)
.zip(vec_fixed_proportions_to_u32(ema_bond.clone()))
.collect();
Bonds::<T>::insert(netuid, i as u16, new_bonds_row);
} else if validator_permit {
// Only overwrite the intersection.
let new_empty_bonds_row: Vec<(u16, u16)> = vec![];
let new_empty_bonds_row: Vec<(u16, u32)> = vec![];
Bonds::<T>::insert(netuid, i as u16, new_empty_bonds_row);
}
});
Expand Down Expand Up @@ -386,6 +397,10 @@ impl<T: Config> Pallet<T> {
let current_block: u64 = Self::get_current_block_as_u64();
log::trace!("current_block: {:?}", current_block);

// Get tempo.
let tempo: u64 = Self::get_tempo(netuid).into();
log::trace!("tempo: {:?}", tempo);

// Get activity cutoff.
let activity_cutoff: u64 = Self::get_activity_cutoff(netuid) as u64;
log::trace!("activity_cutoff: {:?}", activity_cutoff);
Expand Down Expand Up @@ -548,33 +563,26 @@ impl<T: Config> Pallet<T> {
let mut bonds: Vec<Vec<(u16, I32F32)>> = Self::get_bonds_sparse(netuid);
log::trace!("B: {:?}", &bonds);

// Remove bonds referring to deregistered neurons.
bonds = vec_mask_sparse_matrix(
// Remove bonds referring to neurons that have registered since last tempo.
// Mask if: the last tempo block happened *before* the registration block
// ==> last_tempo <= registered
let last_tempo: u64 = current_block.saturating_sub(tempo);
bonds = scalar_vec_mask_sparse_matrix(
&bonds,
&last_update,
last_tempo,
&block_at_registration,
&|updated, registered| updated <= registered,
&|last_tempo, registered| last_tempo <= registered,
);
log::trace!("B (outdatedmask): {:?}", &bonds);

// Normalize remaining bonds: sum_i b_ij = 1.
inplace_col_normalize_sparse(&mut bonds, n);
log::trace!("B (mask+norm): {:?}", &bonds);

// Compute bonds delta column normalized.
let mut bonds_delta: Vec<Vec<(u16, I32F32)>> =
let bonds_delta: Vec<Vec<(u16, I32F32)>> =
row_hadamard_sparse(&weights_for_bonds, &active_stake); // ΔB = W◦S (outdated W masked)
log::trace!("ΔB: {:?}", &bonds_delta);

// Normalize bonds delta.
inplace_col_normalize_sparse(&mut bonds_delta, n); // sum_i b_ij = 1
log::trace!("ΔB (norm): {:?}", &bonds_delta);

// Compute the Exponential Moving Average (EMA) of bonds.
let mut ema_bonds =
let ema_bonds =
Self::compute_ema_bonds_sparse(netuid, consensus.clone(), bonds_delta, bonds);
// Normalize EMA bonds.
inplace_col_normalize_sparse(&mut ema_bonds, n); // sum_i b_ij = 1
log::trace!("Exponential Moving Average Bonds: {:?}", &ema_bonds);

// Compute dividends: d_i = SUM(j) b_ij * inc_j.
Expand Down Expand Up @@ -712,8 +720,6 @@ impl<T: Config> Pallet<T> {
ValidatorTrust::<T>::insert(netuid, cloned_validator_trust);
ValidatorPermit::<T>::insert(netuid, new_validator_permits.clone());

// Column max-upscale EMA bonds for storage: max_i w_ij = 1.
inplace_col_max_upscale_sparse(&mut ema_bonds, n);
new_validator_permits
.iter()
.zip(validator_permits)
Expand All @@ -722,14 +728,14 @@ impl<T: Config> Pallet<T> {
.for_each(|(i, ((new_permit, validator_permit), ema_bond))| {
// Set bonds only if uid retains validator permit, otherwise clear bonds.
if *new_permit {
let new_bonds_row: Vec<(u16, u16)> = ema_bond
let new_bonds_row: Vec<(u16, u32)> = ema_bond
.iter()
.map(|(j, value)| (*j, fixed_proportion_to_u16(*value)))
.map(|(j, value)| (*j, fixed_proportion_to_u32(*value)))
.collect();
Bonds::<T>::insert(netuid, i as u16, new_bonds_row);
} else if validator_permit {
// Only overwrite the intersection.
let new_empty_bonds_row: Vec<(u16, u16)> = vec![];
let new_empty_bonds_row: Vec<(u16, u32)> = vec![];
Bonds::<T>::insert(netuid, i as u16, new_empty_bonds_row);
}
});
Expand Down Expand Up @@ -814,12 +820,12 @@ impl<T: Config> Pallet<T> {
weights
}

/// Output unnormalized sparse bonds, input bonds are assumed to be column max-upscaled in u16.
/// Output unnormalized sparse bonds.
pub fn get_bonds_sparse(netuid: u16) -> Vec<Vec<(u16, I32F32)>> {
let n: usize = Self::get_subnetwork_n(netuid) as usize;
let mut bonds: Vec<Vec<(u16, I32F32)>> = vec![vec![]; n];
for (uid_i, bonds_vec) in
<Bonds<T> as IterableStorageDoubleMap<u16, u16, Vec<(u16, u16)>>>::iter_prefix(netuid)
<Bonds<T> as IterableStorageDoubleMap<u16, u16, Vec<(u16, u32)>>>::iter_prefix(netuid)
.filter(|(uid_i, _)| *uid_i < n as u16)
{
for (uid_j, bonds_ij) in bonds_vec {
Expand All @@ -832,12 +838,12 @@ impl<T: Config> Pallet<T> {
bonds
}

/// Output unnormalized bonds in [n, n] matrix, input bonds are assumed to be column max-upscaled in u16.
/// Output unnormalized bonds in [n, n] matrix.
pub fn get_bonds(netuid: u16) -> Vec<Vec<I32F32>> {
let n: usize = Self::get_subnetwork_n(netuid) as usize;
let mut bonds: Vec<Vec<I32F32>> = vec![vec![I32F32::saturating_from_num(0.0); n]; n];
for (uid_i, bonds_vec) in
<Bonds<T> as IterableStorageDoubleMap<u16, u16, Vec<(u16, u16)>>>::iter_prefix(netuid)
<Bonds<T> as IterableStorageDoubleMap<u16, u16, Vec<(u16, u32)>>>::iter_prefix(netuid)
.filter(|(uid_i, _)| *uid_i < n as u16)
{
for (uid_j, bonds_ij) in bonds_vec.into_iter().filter(|(uid_j, _)| *uid_j < n as u16) {
Expand Down
6 changes: 3 additions & 3 deletions pallets/subtensor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -666,8 +666,8 @@ pub mod pallet {
vec![]
}
#[pallet::type_value]
/// Value definition for bonds with type vector of (u16, u16).
pub fn DefaultBonds<T: Config>() -> Vec<(u16, u16)> {
/// Value definition for bonds with type vector of (u16, u32).
pub fn DefaultBonds<T: Config>() -> Vec<(u16, u32)> {
vec![]
}
#[pallet::type_value]
Expand Down Expand Up @@ -1416,7 +1416,7 @@ pub mod pallet {
u16,
Identity,
u16,
Vec<(u16, u16)>,
Vec<(u16, u32)>,
ValueQuery,
DefaultBonds<T>,
>;
Expand Down
Loading
Loading