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

Normalization Update #909

Merged
merged 26 commits into from
Sep 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
aeb2d2b
local train bug fix
Eugene-hu Sep 6, 2022
15775e0
normalization update
Eugene-hu Sep 7, 2022
9a640d4
fix tests
Eugene-hu Sep 7, 2022
25559fd
Merge branch 'nobunaga' into normalization_fixes
Eugene-hu Sep 7, 2022
f3ffece
remove test
Eugene-hu Sep 7, 2022
183b153
updated normalization
Eugene-hu Sep 8, 2022
6ad417d
Naming changes, bug fixes
Eugene-hu Sep 10, 2022
23d5b3d
Merge branch 'nobunaga' into normalization_fixes
Eugene-hu Sep 12, 2022
4849e55
subtensor update for max clip
Eugene-hu Sep 10, 2022
a5a02e0
max weight to a million
Eugene-hu Sep 14, 2022
25ec064
Merge branch 'nobunaga' into normalization_fixes
Eugene-hu Sep 14, 2022
986fdd3
Merge branch 'nobunaga' into normalization_fixes
Eugene-hu Sep 14, 2022
f09c328
Fixes for ordering and comments
Eugene-hu Sep 14, 2022
e69f4da
additional tests
Eugene-hu Sep 14, 2022
f9700ee
string fix
Eugene-hu Sep 15, 2022
846d6fb
numerical stability and testing updates
Eugene-hu Sep 15, 2022
671df54
minor update for division by zero
Eugene-hu Sep 15, 2022
1ed517a
Naming and spacing fixes
Eugene-hu Sep 15, 2022
013385a
Merge branch 'nobunaga' into normalization_fixes
Eugene-hu Sep 15, 2022
c50a1b0
Merge branch 'normalization_fixes' of https://github.com/opentensor/b…
Eugene-hu Sep 15, 2022
cff539b
epsilon update
Eugene-hu Sep 19, 2022
499b24c
Merge branch 'nobunaga' into normalization_fixes
Eugene-hu Sep 20, 2022
07dce9b
Merge branch 'nobunaga' into normalization_fixes
Eugene-hu Sep 21, 2022
4fdd30c
Merge branch 'nobunaga' into normalization_fixes
Eugene-hu Sep 23, 2022
ad9fe5d
small fix
Eugene-hu Sep 23, 2022
411538d
Merge branch 'nobunaga' into normalization_fixes
Eugene-hu Sep 23, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 16 additions & 14 deletions bittensor/_neuron/text/core_validator/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ def run_epoch( self ):
sequence_length = self.subtensor.validator_sequence_length
validation_len = self.config.neuron.validation_len # Number of tokens to holdout for phrase validation beyond sequence context
min_allowed_weights = self.subtensor.min_allowed_weights
max_allowed_ratio = self.subtensor.max_allowed_min_max_ratio
max_weight_limit = self.subtensor.max_weight_limit
blocks_per_epoch = self.subtensor.validator_epoch_length if self.config.neuron.blocks_per_epoch == -1 else self.config.neuron.blocks_per_epoch
epochs_until_reset = self.subtensor.validator_epochs_per_reset if self.config.neuron.epochs_until_reset == -1 else self.config.neuron.epochs_until_reset

Expand All @@ -358,7 +358,7 @@ def run_epoch( self ):
if self.config.using_wandb:
wandb.log({'era/batch_size': batch_size, 'era/sequence_length': sequence_length,
'era/validation_len': validation_len,
'era/min_allowed_weights': min_allowed_weights, 'era/max_allowed_ratio': max_allowed_ratio,
'era/min_allowed_weights': min_allowed_weights, 'era/max_weight_limit': max_weight_limit,
'era/blocks_per_epoch': blocks_per_epoch, 'era/epochs_until_reset': epochs_until_reset},
step=current_block)

Expand Down Expand Up @@ -507,8 +507,8 @@ def run_epoch( self ):
f'[dim]weights[/dim] sum:{sample_weights.sum().item():.2g} '
f'[white] max:[bold]{sample_weights.max().item():.4g}[/bold] / '
f'min:[bold]{sample_weights.min().item():.4g}[/bold] [/white] '
f'\[{sample_weights.max().item() / sample_weights.min().item():.1f}:1] '
f'({max_allowed_ratio} allowed)')
f'\[{sample_weights.max().item()}:1] '
f'({max_weight_limit} allowed)')

self.subtensor.set_weights(
uids=sample_uids.detach().to('cpu'),
Expand Down Expand Up @@ -603,7 +603,7 @@ def calculate_weights(self, responsive_uids: Set, queried_uids: Set):

# === Randomize UIDs in preferred order (responsive -> queried -> rest) ===
min_allowed_weights = self.subtensor.min_allowed_weights
max_allowed_ratio = self.subtensor.max_allowed_min_max_ratio
max_weight_limit = self.subtensor.max_weight_limit

non_responsive_uids = queried_uids - responsive_uids
non_queried_uids = set(range(self.metagraph.n)) - queried_uids
Expand Down Expand Up @@ -633,7 +633,9 @@ def calculate_weights(self, responsive_uids: Set, queried_uids: Set):
sample_uids = preferred_uids[:weights_to_set] # slice to weights_to_set
sample_weights = neuron_weights[:weights_to_set] # slice to weights_to_set

logger.info(f'{len(sample_weights)} Shapley values | min:{sample_weights.min()} max:{sample_weights.max()}')
# === If no uids responds, return ===
if len(sample_uids) == 0:
return sample_uids, sample_weights

# === Exclude lowest quantile from weight setting ===
max_exclude = (len(sample_weights) - min_allowed_weights) / len(sample_weights) # max excludable weight quantile
Expand All @@ -646,19 +648,19 @@ def calculate_weights(self, responsive_uids: Set, queried_uids: Set):
logger.info(f'Exclude {exclude_quantile} quantile ({lowest_quantile}) | '
f'{len(sample_weights)} Shapley values | min:{sample_weights.min()} max:{sample_weights.max()}')

# === Normalize and apply max_allowed_ratio ===
sample_weights = bittensor.utils.weight_utils.normalize_max_multiple(x=sample_weights,
multiple=max_allowed_ratio)
logger.info(f'{len(sample_weights)} normalize_max_multiple | '
f'min:{sample_weights.min()} max:{sample_weights.max()}')
# === Normalize and apply max_weight_limit ===
sample_weights = bittensor.utils.weight_utils.normalize_max_weight(x=sample_weights,
limit=max_weight_limit)
logger.info(f'{len(sample_weights)} normalize_max_weight | '
f'max:{sample_weights.max()}')

return sample_uids, sample_weights

def weights_table(self, sample_uids, sample_weights, include_uids=None, num_rows: int = None):
r""" Prints weights table given sample_uids and sample_weights.
"""
min_allowed_weights = self.subtensor.min_allowed_weights
max_allowed_ratio = self.subtensor.max_allowed_min_max_ratio
max_weight_limit = self.subtensor.max_weight_limit

# === Weight table ===
# Prints exponential moving average statistics of valid neurons and latest weights
Expand Down Expand Up @@ -688,8 +690,8 @@ def weights_table(self, sample_uids, sample_weights, include_uids=None, num_rows
f'sum:{sample_weights.sum().item():.2g} '
f'[white] max:[bold]{sample_weights.max().item():.4g}[/bold] / '
f'min:[bold]{sample_weights.min().item():.4g}[/bold] [/white] '
f'\[{sample_weights.max().item() / sample_weights.min().item():.1f}:1] '
f'({max_allowed_ratio} allowed)', # caption
f'\[{sample_weights.max().item()}:1] '
f'({max_weight_limit} allowed)', # caption
mark_uids=avail_include_uids)


Expand Down
44 changes: 29 additions & 15 deletions bittensor/_subtensor/subtensor_impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ def rho (self) -> int:
@retry(delay=2, tries=3, backoff=2, max_delay=4)
def make_substrate_call_with_retry():
with self.substrate as substrate:
return substrate.query( module='SubtensorModule', storage_function = 'Rho').value
return substrate.query( module='SubtensorModule', storage_function = 'Rho' ).value
return make_substrate_call_with_retry()

@property
Expand All @@ -157,7 +157,7 @@ def kappa (self) -> int:
@retry(delay=2, tries=3, backoff=2, max_delay=4)
def make_substrate_call_with_retry():
with self.substrate as substrate:
return substrate.query( module='SubtensorModule', storage_function = 'Kappa').value
return substrate.query( module='SubtensorModule', storage_function = 'Kappa' ).value
return make_substrate_call_with_retry()

@property
Expand All @@ -170,7 +170,7 @@ def difficulty (self) -> int:
@retry(delay=2, tries=3, backoff=2, max_delay=4)
def make_substrate_call_with_retry():
with self.substrate as substrate:
return substrate.query( module='SubtensorModule', storage_function = 'Difficulty').value
return substrate.query( module='SubtensorModule', storage_function = 'Difficulty' ).value
return make_substrate_call_with_retry()

@property
Expand All @@ -196,7 +196,7 @@ def immunity_period (self) -> int:
@retry(delay=2, tries=3, backoff=2, max_delay=4)
def make_substrate_call_with_retry():
with self.substrate as substrate:
return substrate.query( module='SubtensorModule', storage_function = 'ImmunityPeriod').value
return substrate.query( module='SubtensorModule', storage_function = 'ImmunityPeriod' ).value
return make_substrate_call_with_retry()

@property
Expand All @@ -209,7 +209,7 @@ def validator_batch_size (self) -> int:
@retry(delay=2, tries=3, backoff=2, max_delay=4)
def make_substrate_call_with_retry():
with self.substrate as substrate:
return substrate.query( module='SubtensorModule', storage_function = 'ValidatorBatchSize').value
return substrate.query( module='SubtensorModule', storage_function = 'ValidatorBatchSize' ).value
return make_substrate_call_with_retry()


Expand All @@ -223,7 +223,7 @@ def validator_sequence_length (self) -> int:
@retry(delay=2, tries=3, backoff=2, max_delay=4)
def make_substrate_call_with_retry():
with self.substrate as substrate:
return substrate.query( module='SubtensorModule', storage_function = 'ValidatorSequenceLength').value
return substrate.query( module='SubtensorModule', storage_function = 'ValidatorSequenceLength' ).value
return make_substrate_call_with_retry()

@property
Expand All @@ -236,7 +236,7 @@ def validator_epochs_per_reset (self) -> int:
@retry(delay=2, tries=3, backoff=2, max_delay=4)
def make_substrate_call_with_retry():
with self.substrate as substrate:
return substrate.query( module='SubtensorModule', storage_function = 'ValidatorEpochsPerReset').value
return substrate.query( module='SubtensorModule', storage_function = 'ValidatorEpochsPerReset' ).value
return make_substrate_call_with_retry()

@property
Expand All @@ -249,7 +249,7 @@ def validator_epoch_length (self) -> int:
@retry(delay=2, tries=3, backoff=2, max_delay=4)
def make_substrate_call_with_retry():
with self.substrate as substrate:
return substrate.query( module='SubtensorModule', storage_function = 'ValidatorEpochLen').value
return substrate.query( module='SubtensorModule', storage_function = 'ValidatorEpochLen' ).value
return make_substrate_call_with_retry()

@property
Expand All @@ -262,7 +262,7 @@ def total_stake (self) -> 'bittensor.Balance':
@retry(delay=2, tries=3, backoff=2, max_delay=4)
def make_substrate_call_with_retry():
with self.substrate as substrate:
return bittensor.Balance.from_rao( substrate.query( module='SubtensorModule', storage_function = 'TotalStake').value )
return bittensor.Balance.from_rao( substrate.query( module='SubtensorModule', storage_function = 'TotalStake' ).value )
return make_substrate_call_with_retry()

@property
Expand All @@ -275,7 +275,21 @@ def min_allowed_weights (self) -> int:
@retry(delay=2, tries=3, backoff=2, max_delay=4)
def make_substrate_call_with_retry():
with self.substrate as substrate:
return substrate.query( module='SubtensorModule', storage_function = 'MinAllowedWeights').value
return substrate.query( module='SubtensorModule', storage_function = 'MinAllowedWeights' ).value
return make_substrate_call_with_retry()

@property
def max_weight_limit (self) -> int:
r""" Returns MaxWeightLimit
Returns:
max_weight (int):
the max value for weights after normalizaiton
"""
@retry(delay=2, tries=3, backoff=2, max_delay=4)
def make_substrate_call_with_retry():
with self.substrate as substrate:
U32_MAX = 4294967295
return substrate.query( module='SubtensorModule', storage_function = 'MaxWeightLimit' ).value/U32_MAX
return make_substrate_call_with_retry()

@property
Expand All @@ -288,7 +302,7 @@ def max_allowed_min_max_ratio(self) -> int:
@retry(delay=2, tries=3, backoff=2, max_delay=4)
def make_substrate_call_with_retry():
with self.substrate as substrate:
return substrate.query( module='SubtensorModule', storage_function = 'MaxAllowedMaxMinRatio').value
return substrate.query( module='SubtensorModule', storage_function = 'MaxAllowedMaxMinRatio' ).value
return make_substrate_call_with_retry()

@property
Expand All @@ -301,7 +315,7 @@ def n (self) -> int:
@retry(delay=2, tries=3, backoff=2, max_delay=4)
def make_substrate_call_with_retry():
with self.substrate as substrate:
return substrate.query( module='SubtensorModule', storage_function = 'N').value
return substrate.query( module='SubtensorModule', storage_function = 'N' ).value
return make_substrate_call_with_retry()

@property
Expand All @@ -314,7 +328,7 @@ def max_n (self) -> int:
@retry(delay=2, tries=3, backoff=2, max_delay=4)
def make_substrate_call_with_retry():
with self.substrate as substrate:
return substrate.query( module='SubtensorModule', storage_function = 'MaxAllowedUids').value
return substrate.query( module='SubtensorModule', storage_function = 'MaxAllowedUids' ).value
return make_substrate_call_with_retry()

@property
Expand All @@ -336,7 +350,7 @@ def blocks_since_epoch (self) -> int:
@retry(delay=2, tries=3, backoff=2, max_delay=4)
def make_substrate_call_with_retry():
with self.substrate as substrate:
return substrate.query( module='SubtensorModule', storage_function = 'BlocksSinceLastStep').value
return substrate.query( module='SubtensorModule', storage_function = 'BlocksSinceLastStep' ).value
return make_substrate_call_with_retry()

@property
Expand All @@ -349,7 +363,7 @@ def blocks_per_epoch (self) -> int:
@retry(delay=2, tries=3, backoff=2, max_delay=4)
def make_substrate_call_with_retry():
with self.substrate as substrate:
return substrate.query( module='SubtensorModule', storage_function = 'BlocksPerStep').value
return substrate.query( module='SubtensorModule', storage_function = 'BlocksPerStep' ).value
return make_substrate_call_with_retry()

def get_n (self, block: int = None) -> int:
Expand Down
42 changes: 31 additions & 11 deletions bittensor/utils/weight_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,26 +22,46 @@

U32_MAX = 4294967295

def normalize_max_multiple( x: torch.FloatTensor, multiple:int = 3 ) -> 'torch.FloatTensor':
r""" Normalizes the tensor x so that sum(x) = 1 and the max value is at most multiple times larger than the min value.
def normalize_max_weight( x: torch.FloatTensor, limit:float = 0.1 ) -> 'torch.FloatTensor':
r""" Normalizes the tensor x so that sum(x) = 1 and the max value is not greater than the limit.
Args:
x (:obj:`torch.FloatTensor`):
Tensor to be max_value normalized.
multiple: float:
Max value is multiple times larger than the min after normalization.
limit: float:
Max value after normalization.
Returns:
x (:obj:`torch.FloatTensor`):
y (:obj:`torch.FloatTensor`):
Normalized x tensor.
"""
x = x
shift = 1 / ( multiple - 1 )
x = x - x.min()
epsilon = 1e-7 #For numerical stability after normalization

weights = x.clone()
values, _ = torch.sort(weights)

if x.sum() == 0:
if x.sum() == 0 or len(x)*limit <= 1:
return torch.ones_like(x)/x.size(0)
else:
x = x / x.sum()
y = (torch.tanh(x * len(x)) + shift)/(torch.tanh( x * len(x) ) + shift).sum()
estimation = values/values.sum()

if estimation.max() <= limit:
return weights/weights.sum()

# Find the cumlative sum and sorted tensor
cumsum = torch.cumsum(estimation,0)

# Determine the index of cutoff
estimation_sum = torch.tensor([(len(values)-i-1)*estimation[i] for i in range(len(values))])
n_values = (estimation/(estimation_sum+cumsum+epsilon)<limit).sum()

# Determine the cutoff based on the index
cutoff_scale = (limit*cumsum[n_values-1]-epsilon)/(1-(limit*(len(estimation)-n_values)))
cutoff= cutoff_scale*values.sum()

# Applying the cutoff
weights[weights > cutoff] = cutoff

y = weights/weights.sum()

return y

def convert_weight_uids_and_vals_to_tensor( n: int, uids: List[int], weights: List[int] ) -> 'torch.FloatTensor':
Expand Down
75 changes: 44 additions & 31 deletions tests/unit_tests/bittensor_tests/utils/test_weight_utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import torch
import bittensor.utils.weight_utils as weight_utils
import pytest
import random

def test_convert_weight_and_uids():
uids = torch.tensor(list(range(10)))
Expand Down Expand Up @@ -33,43 +34,55 @@ def test_convert_weight_and_uids():
weights = torch.rand(10)
weight_utils.convert_weights_and_uids_for_emit( uids, weights )

def test_normalize_with_min_max():
weights = torch.rand(10)
wn = weight_utils.normalize_max_multiple( weights, multiple = 10 )
assert wn.max() / wn.min() <= 11
def test_normalize_with_max_weight():
weights = torch.rand(1000)
wn = weight_utils.normalize_max_weight( weights, limit = 0.01 )
assert wn.max() <= 0.01

weights = torch.rand(2)
wn = weight_utils.normalize_max_multiple( weights, multiple = 10 )
assert wn.max() / wn.min() <= 11
weights = torch.zeros(1000)
wn = weight_utils.normalize_max_weight( weights, limit = 0.01 )
assert wn.max() <= 0.01

weights = torch.randn(10)
wn = weight_utils.normalize_max_multiple( weights, multiple = 10 )
assert wn.max() / wn.min() <= 11
weights = torch.rand(1000)
wn = weight_utils.normalize_max_weight( weights, limit = 0.02 )
assert wn.max() <= 0.02

weights = torch.eye(10)[0]
wn = weight_utils.normalize_max_multiple( weights, multiple = 10 )
assert wn.max() / wn.min() <= 11
weights = torch.zeros(1000)
wn = weight_utils.normalize_max_weight( weights, limit = 0.02 )
assert wn.max() <= 0.02

weights = torch.zeros(10)
wn = weight_utils.normalize_max_multiple( weights, multiple = 10 )
assert wn.max() / wn.min() <= 11
weights = torch.rand(1000)
wn = weight_utils.normalize_max_weight( weights, limit = 0.03 )
assert wn.max() <= 0.03

weights = torch.rand(10)
wn = weight_utils.normalize_max_multiple( weights, multiple = 2 )
assert wn.max() / wn.min() <= 3
weights = torch.zeros(1000)
wn = weight_utils.normalize_max_weight( weights, limit = 0.03 )
assert wn.max() <= 0.03

weights = torch.rand(2)
wn = weight_utils.normalize_max_multiple( weights, multiple = 2 )
assert wn.max() / wn.min() <= 3
# Check for Limit
limit = 0.001
weights = torch.rand(2000)
w = weights / weights.sum()
wn = weight_utils.normalize_max_weight( weights, limit = limit )
assert (w.max() >= limit and (limit - wn.max()).abs() < 0.001) or (w.max() < limit and wn.max() < limit)

weights = torch.randn(10)
wn = weight_utils.normalize_max_multiple( weights, multiple = 2 )
assert wn.max() / wn.min() <= 3
# Check for Zeros
limit = 0.01
weights = torch.zeros(2000)
wn = weight_utils.normalize_max_weight( weights, limit = limit )
assert wn.max() == 1/2000

weights = torch.eye(10)[0]
wn = weight_utils.normalize_max_multiple( weights, multiple = 2 )
assert wn.max() / wn.min() <= 3
# Check for Ordering after normalization
weights = torch.rand(100)
wn = weight_utils.normalize_max_weight( weights, limit = 1 )
assert torch.equal(wn,weights/weights.sum())

weights = torch.zeros(10)
wn = weight_utils.normalize_max_multiple( weights, multiple = 2 )
assert wn.max() / wn.min() <= 3
# Check for eplison changes
eplison = 0.01
weights,_ = torch.sort(torch.rand(100))
x = weights/weights.sum()
limit = x[-10]
change = eplison*limit
y = weight_utils.normalize_max_weight(x, limit=limit-change)
z = weight_utils.normalize_max_weight(x, limit=limit+change)
assert (y-z).abs().sum() < eplison