diff --git a/Dockerfile b/Dockerfile index 15081dbe70..5a355b5cf6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # syntax=docker/dockerfile:1 -FROM pytorch/pytorch:2.0.1-cuda11.7-cudnn8-devel +FROM python:3.11.8-bookworm LABEL bittensor.image.authors="bittensor.com" \ bittensor.image.vendor="Bittensor" \ @@ -9,19 +9,13 @@ LABEL bittensor.image.authors="bittensor.com" \ bittensor.image.revision="${VCS_REF}" \ bittensor.image.created="${BUILD_DATE}" \ bittensor.image.documentation="https://app.gitbook.com/@opentensor/s/bittensor/" -LABEL bittensor.dependencies.versions.torch="2.0.1" -LABEL bittensor.dependencies.versions.cuda="11.7" ARG DEBIAN_FRONTEND=noninteractive -#nvidia key migration -RUN apt-key del 7fa2af80 -RUN apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/3bf863cc.pub -RUN apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/machine-learning/repos/ubuntu2004/x86_64/7fa2af80.pub # Update the base image RUN apt update && apt upgrade -y # Install bittensor ## Install dependencies -RUN apt install -y curl sudo nano git htop netcat wget unzip tmux apt-utils cmake build-essential +RUN apt install -y curl sudo nano git htop netcat-openbsd wget unzip tmux apt-utils cmake build-essential ## Upgrade pip RUN pip3 install --upgrade pip diff --git a/README.md b/README.md index a431f5e8f5..0d3a8e6fd3 100644 --- a/README.md +++ b/README.md @@ -329,8 +329,8 @@ my_axon.attach( ).start() ``` -Dendrite: Inheriting from PyTorch's Module class, represents the abstracted implementation of a network client module designed -to send requests to those endpoints to receive inputs. +Dendrite: Represents the abstracted implementation of a network client module +designed to send requests to those endpoints to receive inputs. Example: ```python diff --git a/VERSION b/VERSION index 6020dfc232..66ce77b7ea 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -6.9.3 \ No newline at end of file +7.0.0 diff --git a/bittensor/__init__.py b/bittensor/__init__.py index fdc89c293a..151faa70be 100644 --- a/bittensor/__init__.py +++ b/bittensor/__init__.py @@ -27,7 +27,7 @@ nest_asyncio.apply() # Bittensor code and protocol version. -__version__ = "6.9.3" +__version__ = "7.0.0" version_split = __version__.split(".") __version_as_int__: int = ( diff --git a/bittensor/chain_data.py b/bittensor/chain_data.py index c9ed37714a..2b1839d1ad 100644 --- a/bittensor/chain_data.py +++ b/bittensor/chain_data.py @@ -14,7 +14,6 @@ # THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. -import torch import bittensor import json @@ -265,16 +264,14 @@ def from_neuron_info(cls, neuron_info: dict) -> "AxonInfo": coldkey=neuron_info["coldkey"], ) - def to_parameter_dict(self) -> "torch.nn.ParameterDict": - r"""Returns a torch tensor of the subnet info.""" - return torch.nn.ParameterDict(self.__dict__) + def to_parameter_dict(self) -> dict[str, Union[int, str]]: + r"""Returns a dict of the subnet info.""" + return self.__dict__ @classmethod - def from_parameter_dict( - cls, parameter_dict: "torch.nn.ParameterDict" - ) -> "AxonInfo": - r"""Returns an axon_info object from a torch parameter_dict.""" - return cls(**dict(parameter_dict)) + def from_parameter_dict(cls, parameter_dict: dict[str, Any]) -> "AxonInfo": + r"""Returns an axon_info object from a parameter_dict.""" + return cls(**parameter_dict) class ChainDataType(Enum): @@ -958,16 +955,14 @@ def fix_decoded_values(cls, decoded: Dict) -> "SubnetInfo": owner_ss58=ss58_encode(decoded["owner"], bittensor.__ss58_format__), ) - def to_parameter_dict(self) -> "torch.nn.ParameterDict": - r"""Returns a torch tensor of the subnet info.""" - return torch.nn.ParameterDict(self.__dict__) + def to_parameter_dict(self) -> dict[str, Any]: + r"""Returns a dict of the subnet info.""" + return self.__dict__ @classmethod - def from_parameter_dict( - cls, parameter_dict: "torch.nn.ParameterDict" - ) -> "SubnetInfo": - r"""Returns a SubnetInfo object from a torch parameter_dict.""" - return cls(**dict(parameter_dict)) + def from_parameter_dict(cls, parameter_dict: dict[str, Any]) -> "SubnetInfo": + r"""Returns a SubnetInfo object from a parameter_dict.""" + return cls(**parameter_dict) @dataclass @@ -1054,16 +1049,14 @@ def fix_decoded_values(cls, decoded: Dict) -> "SubnetHyperparameters": difficulty=decoded["difficulty"], ) - def to_parameter_dict(self) -> "torch.nn.ParameterDict": - r"""Returns a torch tensor of the subnet hyperparameters.""" - return torch.nn.ParameterDict(self.__dict__) + def to_parameter_dict(self) -> dict[str, Union[int, float, bool]]: + r"""Returns a dict of the subnet hyperparameters.""" + return self.__dict__ @classmethod - def from_parameter_dict( - cls, parameter_dict: "torch.nn.ParameterDict" - ) -> "SubnetInfo": - r"""Returns a SubnetHyperparameters object from a torch parameter_dict.""" - return cls(**dict(parameter_dict)) + def from_parameter_dict(cls, parameter_dict: dict[str, Any]) -> "SubnetInfo": + r"""Returns a SubnetHyperparameters object from a parameter_dict.""" + return cls(**parameter_dict) @dataclass @@ -1119,14 +1112,14 @@ def fix_decoded_values(cls, decoded: Dict) -> "IPInfo": protocol=decoded["ip_type_and_protocol"] & 0xF, ) - def to_parameter_dict(self) -> "torch.nn.ParameterDict": - r"""Returns a torch tensor of the subnet info.""" - return torch.nn.ParameterDict(self.__dict__) + def to_parameter_dict(self) -> dict[str, Union[str, int]]: + r"""Returns a dict of the subnet ip info.""" + return self.__dict__ @classmethod - def from_parameter_dict(cls, parameter_dict: "torch.nn.ParameterDict") -> "IPInfo": - r"""Returns a IPInfo object from a torch parameter_dict.""" - return cls(**dict(parameter_dict)) + def from_parameter_dict(cls, parameter_dict: dict[str, Any]) -> "IPInfo": + r"""Returns a IPInfo object from a parameter_dict.""" + return cls(**parameter_dict) # Senate / Proposal data diff --git a/bittensor/commands/register.py b/bittensor/commands/register.py index 72fdb6853a..8b21a33304 100644 --- a/bittensor/commands/register.py +++ b/bittensor/commands/register.py @@ -216,7 +216,7 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): ) sys.exit(1) - subtensor.register( + registered = subtensor.register( wallet=wallet, netuid=cli.config.netuid, prompt=not cli.config.no_prompt, @@ -234,6 +234,8 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): "verbose", defaults.pow_register.verbose ), ) + if not registered: + sys.exit(1) @staticmethod def add_args(parser: argparse.ArgumentParser): @@ -408,7 +410,7 @@ def run(cli: "bittensor.cli"): def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): r"""Register neuron.""" wallet = bittensor.wallet(config=cli.config) - subtensor.run_faucet( + success = subtensor.run_faucet( wallet=wallet, prompt=not cli.config.no_prompt, tpb=cli.config.pow_register.cuda.get("tpb", None), @@ -425,6 +427,9 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): "verbose", defaults.pow_register.verbose ), ) + if not success: + bittensor.logging.error("Faucet run failed.") + sys.exit(1) @staticmethod def add_args(parser: argparse.ArgumentParser): diff --git a/bittensor/commands/root.py b/bittensor/commands/root.py index 7cc8cbce70..912390cafc 100644 --- a/bittensor/commands/root.py +++ b/bittensor/commands/root.py @@ -16,7 +16,7 @@ # DEALINGS IN THE SOFTWARE. import re -import torch +import numpy as np import typing import argparse import numpy as np @@ -301,7 +301,7 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): f"Boosting weight for netuid {cli.config.netuid} from {prev_weight} -> {new_weight}" ) my_weights[cli.config.netuid] = new_weight - all_netuids = torch.tensor(list(range(len(my_weights)))) + all_netuids = np.arange(len(my_weights)) bittensor.__console__.print("Setting root weights...") subtensor.root_set_weights( @@ -419,7 +419,7 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): my_weights = root.weights[my_uid] my_weights[cli.config.netuid] -= cli.config.amount my_weights[my_weights < 0] = 0 # Ensure weights don't go negative - all_netuids = torch.tensor(list(range(len(my_weights)))) + all_netuids = np.arange(len(my_weights)) subtensor.root_set_weights( wallet=wallet, @@ -520,12 +520,12 @@ def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): cli.config.weights = Prompt.ask(f"Enter weights (e.g. {example})") # Parse from string - netuids = torch.tensor( - list(map(int, re.split(r"[ ,]+", cli.config.netuids))), dtype=torch.long + netuids = np.array( + list(map(int, re.split(r"[ ,]+", cli.config.netuids))), dtype=np.int64 ) - weights = torch.tensor( + weights = np.array( list(map(float, re.split(r"[ ,]+", cli.config.weights))), - dtype=torch.float32, + dtype=np.float32, ) # Run the set weights operation. diff --git a/bittensor/commands/utils.py b/bittensor/commands/utils.py index bcf9826626..3fe4db52ef 100644 --- a/bittensor/commands/utils.py +++ b/bittensor/commands/utils.py @@ -17,11 +17,11 @@ import sys import os -import torch import bittensor +import requests +from bittensor.utils.registration import maybe_get_torch from typing import List, Dict, Any, Optional from rich.prompt import Confirm, PromptBase -import requests from dataclasses import dataclass from . import defaults @@ -78,7 +78,9 @@ def check_netuid_set( def check_for_cuda_reg_config(config: "bittensor.config") -> None: """Checks, when CUDA is available, if the user would like to register with their CUDA device.""" - if torch.cuda.is_available(): + + torch = maybe_get_torch() + if torch is not None and torch.cuda.is_available(): if not config.no_prompt: if config.pow_register.cuda.get("use_cuda") == None: # flag not set # Ask about cuda registration only if a CUDA device is available. diff --git a/bittensor/dendrite.py b/bittensor/dendrite.py index b5e8df9a28..9a9202ab31 100644 --- a/bittensor/dendrite.py +++ b/bittensor/dendrite.py @@ -22,15 +22,14 @@ import asyncio import uuid import time -import torch import aiohttp import bittensor from typing import Union, Optional, List, Union, AsyncGenerator, Any -class dendrite(torch.nn.Module): +class dendrite: """ - The Dendrite class, inheriting from PyTorch's Module class, represents the abstracted implementation of a network client module. + The Dendrite class represents the abstracted implementation of a network client module. In the brain analogy, dendrites receive signals from other neurons (in this case, network servers or axons), and the Dendrite class here is designed @@ -122,6 +121,9 @@ def __init__( self._session: Optional[aiohttp.ClientSession] = None + async def __call__(self, *args, **kwargs): + return await self.forward(*args, **kwargs) + @property async def session(self) -> aiohttp.ClientSession: """ diff --git a/bittensor/extrinsics/registration.py b/bittensor/extrinsics/registration.py index c601ebe6a8..367d987b5e 100644 --- a/bittensor/extrinsics/registration.py +++ b/bittensor/extrinsics/registration.py @@ -18,11 +18,10 @@ import bittensor -import torch import time from rich.prompt import Confirm from typing import List, Union, Optional, Tuple -from bittensor.utils.registration import POWSolution, create_pow +from bittensor.utils.registration import POWSolution, create_pow, maybe_get_torch def register_extrinsic( @@ -102,6 +101,10 @@ def register_extrinsic( ): return False + torch = maybe_get_torch() + if torch is None: + return False + # Attempt rolling registration. attempts = 1 while True: @@ -379,6 +382,10 @@ def run_faucet_extrinsic( ): return False + torch = maybe_get_torch() + if torch is None: + return False + # Unlock coldkey wallet.coldkey @@ -391,7 +398,7 @@ def run_faucet_extrinsic( while True: try: pow_result = None - while pow_result == None or pow_result.is_stale(subtensor=subtensor): + while pow_result is None or pow_result.is_stale(subtensor=subtensor): # Solve latest POW. if cuda: if not torch.cuda.is_available(): diff --git a/bittensor/extrinsics/root.py b/bittensor/extrinsics/root.py index ab8b314870..826bdf7973 100644 --- a/bittensor/extrinsics/root.py +++ b/bittensor/extrinsics/root.py @@ -19,8 +19,9 @@ import bittensor import time -import torch import logging +import numpy as np +from numpy.typing import NDArray from rich.prompt import Confirm from typing import Union import bittensor.utils.weight_utils as weight_utils @@ -101,8 +102,8 @@ def root_register_extrinsic( def set_root_weights_extrinsic( subtensor: "bittensor.subtensor", wallet: "bittensor.wallet", - netuids: Union[torch.LongTensor, list], - weights: Union[torch.FloatTensor, list], + netuids: Union[NDArray[np.int64], list], + weights: Union[NDArray[np.float32], list], version_key: int = 0, wait_for_inclusion: bool = False, wait_for_finalization: bool = False, @@ -115,7 +116,7 @@ def set_root_weights_extrinsic( Bittensor wallet object. netuids (List[int]): The ``netuid`` of the subnet to set weights for. - weights ( Union[torch.FloatTensor, list]): + weights (Union[NDArray[np.float32], list]): Weights to set. These must be ``float`` s and must correspond to the passed ``netuid`` s. version_key (int): The version key of the validator. @@ -131,22 +132,22 @@ def set_root_weights_extrinsic( """ # First convert types. if isinstance(netuids, list): - netuids = torch.tensor(netuids, dtype=torch.int64) + netuids = np.array(netuids, dtype=np.int64) if isinstance(weights, list): - weights = torch.tensor(weights, dtype=torch.float32) + weights = np.array(weights, dtype=np.float32) # Get weight restrictions. min_allowed_weights = subtensor.min_allowed_weights(netuid=0) max_weight_limit = subtensor.max_weight_limit(netuid=0) # Get non zero values. - non_zero_weight_idx = torch.argwhere(weights > 0).squeeze(dim=1) + non_zero_weight_idx = np.argwhere(weights > 0).squeeze(axis=1) non_zero_weight_uids = netuids[non_zero_weight_idx] non_zero_weights = weights[non_zero_weight_idx] - if non_zero_weights.numel() < min_allowed_weights: + if non_zero_weights.size < min_allowed_weights: raise ValueError( "The minimum number of weights required to set weights is {}, got {}".format( - min_allowed_weights, non_zero_weights.numel() + min_allowed_weights, non_zero_weights.size ) ) diff --git a/bittensor/extrinsics/set_weights.py b/bittensor/extrinsics/set_weights.py index 5e0c7cc6af..61960ceb78 100644 --- a/bittensor/extrinsics/set_weights.py +++ b/bittensor/extrinsics/set_weights.py @@ -18,8 +18,9 @@ import bittensor -import torch import logging +import numpy as np +from numpy.typing import NDArray from rich.prompt import Confirm from typing import Union, Tuple import bittensor.utils.weight_utils as weight_utils @@ -32,8 +33,8 @@ def set_weights_extrinsic( subtensor: "bittensor.subtensor", wallet: "bittensor.wallet", netuid: int, - uids: Union[torch.LongTensor, list], - weights: Union[torch.FloatTensor, list], + uids: Union[NDArray[np.int64], list], + weights: Union[NDArray[np.float32], list], version_key: int = 0, wait_for_inclusion: bool = False, wait_for_finalization: bool = False, @@ -48,9 +49,9 @@ def set_weights_extrinsic( Bittensor wallet object. netuid (int): The ``netuid`` of the subnet to set weights for. - uids (Union[torch.LongTensor, list]): + uids (Union[NDArray[np.int64], list]): The ``uint64`` uids of destination neurons. - weights ( Union[torch.FloatTensor, list]): + weights (Union[NDArray[np.float32], list]): The weights to set. These must be ``float`` s and correspond to the passed ``uid`` s. version_key (int): The version key of the validator. @@ -67,9 +68,9 @@ def set_weights_extrinsic( # First convert types. if isinstance(uids, list): - uids = torch.tensor(uids, dtype=torch.int64) + uids = np.array(uids, dtype=np.int64) if isinstance(weights, list): - weights = torch.tensor(weights, dtype=torch.float32) + weights = np.array(weights, dtype=np.float32) # Reformat and normalize. weight_uids, weight_vals = weight_utils.convert_weights_and_uids_for_emit( diff --git a/bittensor/metagraph.py b/bittensor/metagraph.py index 6159b84c24..e4a7ff21e3 100644 --- a/bittensor/metagraph.py +++ b/bittensor/metagraph.py @@ -18,13 +18,35 @@ # DEALINGS IN THE SOFTWARE. import os -import torch +import pickle +import numpy as np +from numpy.typing import NDArray import bittensor from os import listdir from os.path import join from typing import List, Optional from bittensor.chain_data import AxonInfo +from bittensor.utils.registration import maybe_get_torch + +METAGRAPH_STATE_DICT_NDARRAY_KEYS = [ + "version", + "n", + "block", + "stake", + "total_stake", + "ranks", + "trust", + "consensus", + "validator_trust", + "incentive", + "emission", + "dividends", + "active", + "last_update", + "validator_permit", + "uids", +] def get_save_dir(network: str, netuid: int) -> str: @@ -70,7 +92,7 @@ def latest_block_path(dir_path: str) -> str: return latest_file_full_path -class metagraph(torch.nn.Module): +class Metagraph: """ The metagraph class is a core component of the Bittensor network, representing the neural graph that forms the backbone of the decentralized machine learning system. @@ -83,9 +105,9 @@ class metagraph(torch.nn.Module): Args: netuid (int): A unique identifier that distinguishes between different instances or versions of the Bittensor network. network (str): The name of the network, signifying specific configurations or iterations within the Bittensor ecosystem. - version (torch.nn.parameter.Parameter): The version number of the network, formatted for compatibility with PyTorch models, integral for tracking network updates. - n (torch.nn.Parameter): The total number of neurons in the network, reflecting its size and complexity. - block (torch.nn.Parameter): The current block number in the blockchain, crucial for synchronizing with the network's latest state. + version (NDArray): The version number of the network, integral for tracking network updates. + n (NDArray): The total number of neurons in the network, reflecting its size and complexity. + block (NDArray): The current block number in the blockchain, crucial for synchronizing with the network's latest state. stake: Represents the cryptocurrency staked by neurons, impacting their influence and earnings within the network. total_stake: The cumulative stake across all neurons. ranks: Neuron rankings as per the Yuma Consensus algorithm, influencing their incentive distribution and network authority. @@ -128,7 +150,7 @@ class metagraph(torch.nn.Module): """ @property - def S(self) -> torch.nn.Parameter: + def S(self) -> NDArray: """ Represents the stake of each neuron in the Bittensor network. Stake is an important concept in the Bittensor ecosystem, signifying the amount of network weight (or “stake”) each neuron holds, @@ -136,12 +158,12 @@ def S(self) -> torch.nn.Parameter: from the network, playing a crucial role in the distribution of incentives and decision-making processes. Returns: - torch.nn.Parameter: A tensor representing the stake of each neuron in the network. Higher values signify a greater stake held by the respective neuron. + NDArray: A tensor representing the stake of each neuron in the network. Higher values signify a greater stake held by the respective neuron. """ return self.total_stake @property - def R(self) -> torch.nn.Parameter: + def R(self) -> NDArray: """ Contains the ranks of neurons in the Bittensor network. Ranks are determined by the network based on each neuron's performance and contributions. Higher ranks typically indicate a greater level of @@ -149,12 +171,12 @@ def R(self) -> torch.nn.Parameter: incentives within the network, with higher-ranked neurons receiving more incentive. Returns: - torch.nn.Parameter: A tensor where each element represents the rank of a neuron. Higher values indicate higher ranks within the network. + NDArray: A tensor where each element represents the rank of a neuron. Higher values indicate higher ranks within the network. """ return self.ranks @property - def I(self) -> torch.nn.Parameter: + def I(self) -> NDArray: """ Incentive values of neurons represent the rewards they receive for their contributions to the network. The Bittensor network employs an incentive mechanism that rewards neurons based on their @@ -162,12 +184,12 @@ def I(self) -> torch.nn.Parameter: trusted contributions are incentivized. Returns: - torch.nn.Parameter: A tensor of incentive values, indicating the rewards or benefits accrued by each neuron based on their contributions and network consensus. + NDArray: A tensor of incentive values, indicating the rewards or benefits accrued by each neuron based on their contributions and network consensus. """ return self.incentive @property - def E(self) -> torch.nn.Parameter: + def E(self) -> NDArray: """ Denotes the emission values of neurons in the Bittensor network. Emissions refer to the distribution or release of rewards (often in the form of cryptocurrency) to neurons, typically based on their stake and @@ -175,12 +197,12 @@ def E(self) -> torch.nn.Parameter: contributing neurons are appropriately rewarded. Returns: - torch.nn.Parameter: A tensor where each element represents the emission value for a neuron, indicating the amount of reward distributed to that neuron. + NDArray: A tensor where each element represents the emission value for a neuron, indicating the amount of reward distributed to that neuron. """ return self.emission @property - def C(self) -> torch.nn.Parameter: + def C(self) -> NDArray: """ Represents the consensus values of neurons in the Bittensor network. Consensus is a measure of how much a neuron's contributions are trusted and agreed upon by the majority of the network. It is @@ -189,13 +211,13 @@ def C(self) -> torch.nn.Parameter: are more widely trusted and valued across the network. Returns: - torch.nn.Parameter: A tensor of consensus values, where each element reflects the level of trust and agreement a neuron has achieved within the network. + NDArray: A tensor of consensus values, where each element reflects the level of trust and agreement a neuron has achieved within the network. """ return self.consensus @property - def T(self) -> torch.nn.Parameter: + def T(self) -> NDArray: """ Represents the trust values assigned to each neuron in the Bittensor network. Trust is a key metric that reflects the reliability and reputation of a neuron based on its past behavior and contributions. It is @@ -206,12 +228,12 @@ def T(self) -> torch.nn.Parameter: has in others. A higher value in the trust matrix suggests a stronger trust relationship between neurons. Returns: - torch.nn.Parameter: A tensor of trust values, where each element represents the trust level of a neuron. Higher values denote a higher level of trust within the network. + NDArray: A tensor of trust values, where each element represents the trust level of a neuron. Higher values denote a higher level of trust within the network. """ return self.trust @property - def Tv(self) -> torch.nn.Parameter: + def Tv(self) -> NDArray: """ Contains the validator trust values of neurons in the Bittensor network. Validator trust is specifically associated with neurons that act as validators within the network. This specialized form of trust reflects @@ -222,24 +244,24 @@ def Tv(self) -> torch.nn.Parameter: determining the validators' influence and responsibilities in these critical functions. Returns: - torch.nn.Parameter: A tensor of validator trust values, specifically applicable to neurons serving as validators, where higher values denote greater trustworthiness in their validation roles. + NDArray: A tensor of validator trust values, specifically applicable to neurons serving as validators, where higher values denote greater trustworthiness in their validation roles. """ return self.validator_trust @property - def D(self) -> torch.nn.Parameter: + def D(self) -> NDArray: """ Represents the dividends received by neurons in the Bittensor network. Dividends are a form of reward or distribution, typically given to neurons based on their stake, performance, and contribution to the network. They are an integral part of the network's incentive structure, encouraging active and beneficial participation. Returns: - torch.nn.Parameter: A tensor of dividend values, where each element indicates the dividends received by a neuron, reflecting their share of network rewards. + NDArray: A tensor of dividend values, where each element indicates the dividends received by a neuron, reflecting their share of network rewards. """ return self.dividends @property - def B(self) -> torch.nn.Parameter: + def B(self) -> NDArray: """ Bonds in the Bittensor network represent a speculative reward mechanism where neurons can accumulate bonds in other neurons. Bonds are akin to investments or stakes in other neurons, reflecting a belief in @@ -247,12 +269,12 @@ def B(self) -> torch.nn.Parameter: among neurons while providing an additional layer of incentive. Returns: - torch.nn.Parameter: A tensor representing the bonds held by each neuron, where each value signifies the proportion of bonds owned by one neuron in another. + NDArray: A tensor representing the bonds held by each neuron, where each value signifies the proportion of bonds owned by one neuron in another. """ return self.bonds @property - def W(self) -> torch.nn.Parameter: + def W(self) -> NDArray: """ Represents the weights assigned to each neuron in the Bittensor network. In the context of Bittensor, weights are crucial for determining the influence and interaction between neurons. Each neuron is responsible @@ -265,7 +287,7 @@ def W(self) -> torch.nn.Parameter: placed on that neuron's contributions. Returns: - torch.nn.Parameter: A tensor of inter-peer weights, where each element :math:`w_{ij}` represents the weight assigned by neuron :math:`i` to neuron :math:`j`. This matrix is fundamental to the network's functioning, influencing the distribution of incentives and the inter-neuronal dynamics. + NDArray: A tensor of inter-peer weights, where each element :math:`w_{ij}` represents the weight assigned by neuron :math:`i` to neuron :math:`j`. This matrix is fundamental to the network's functioning, influencing the distribution of incentives and the inter-neuronal dynamics. """ return self.weights @@ -401,67 +423,56 @@ def __init__( metagraph = metagraph(netuid=123, network="finney", lite=True, sync=True) """ super(metagraph, self).__init__() + self.netuid = netuid self.network = network - self.version = torch.nn.Parameter( - torch.tensor([bittensor.__version_as_int__], dtype=torch.int64), - requires_grad=False, - ) - self.n: torch.nn.Parameter = torch.nn.Parameter( - torch.tensor([0], dtype=torch.int64), requires_grad=False - ) - self.block: torch.nn.Parameter = torch.nn.Parameter( - torch.tensor([0], dtype=torch.int64), requires_grad=False - ) - self.stake = torch.nn.Parameter( - torch.tensor([], dtype=torch.float32), requires_grad=False - ) - self.total_stake: torch.nn.Parameter = torch.nn.Parameter( - torch.tensor([], dtype=torch.float32), requires_grad=False - ) - self.ranks: torch.nn.Parameter = torch.nn.Parameter( - torch.tensor([], dtype=torch.float32), requires_grad=False - ) - self.trust: torch.nn.Parameter = torch.nn.Parameter( - torch.tensor([], dtype=torch.float32), requires_grad=False - ) - self.consensus: torch.nn.Parameter = torch.nn.Parameter( - torch.tensor([], dtype=torch.float32), requires_grad=False - ) - self.validator_trust: torch.nn.Parameter = torch.nn.Parameter( - torch.tensor([], dtype=torch.float32), requires_grad=False - ) - self.incentive: torch.nn.Parameter = torch.nn.Parameter( - torch.tensor([], dtype=torch.float32), requires_grad=False - ) - self.emission: torch.nn.Parameter = torch.nn.Parameter( - torch.tensor([], dtype=torch.float32), requires_grad=False - ) - self.dividends: torch.nn.Parameter = torch.nn.Parameter( - torch.tensor([], dtype=torch.float32), requires_grad=False - ) - self.active = torch.nn.Parameter( - torch.tensor([], dtype=torch.int64), requires_grad=False - ) - self.last_update = torch.nn.Parameter( - torch.tensor([], dtype=torch.int64), requires_grad=False - ) - self.validator_permit = torch.nn.Parameter( - torch.tensor([], dtype=torch.bool), requires_grad=False - ) - self.weights: torch.nn.Parameter = torch.nn.Parameter( - torch.tensor([], dtype=torch.float32), requires_grad=False - ) - self.bonds: torch.nn.Parameter = torch.nn.Parameter( - torch.tensor([], dtype=torch.int64), requires_grad=False - ) - self.uids = torch.nn.Parameter( - torch.tensor([], dtype=torch.int64), requires_grad=False - ) + self.version = (np.array([bittensor.__version_as_int__], dtype=np.int64),) + self.n = np.array([0], dtype=np.int64) + self.block = np.array([0], dtype=np.int64) + self.stake = np.array([], dtype=np.float32) + self.total_stake = np.array([], dtype=np.float32) + self.ranks = np.array([], dtype=np.float32) + self.trust = np.array([], dtype=np.float32) + self.consensus = np.array([], dtype=np.float32) + self.validator_trust = np.array([], dtype=np.float32) + self.incentive = np.array([], dtype=np.float32) + self.emission = np.array([], dtype=np.float32) + self.dividends = np.array([], dtype=np.float32) + self.active = np.array([], dtype=np.int64) + self.last_update = np.array([], dtype=np.int64) + self.validator_permit = np.array([], dtype=bool) + self.weights = np.array([], dtype=np.float32) + self.bonds = np.array([], dtype=np.int64) + self.uids = np.array([], dtype=np.int64) self.axons: List[AxonInfo] = [] if sync: self.sync(block=None, lite=lite) + def state_dict(self): + return { + "netuid": self.netuid, + "network": self.network, + "version": self.version, + "n": self.n, + "block": self.block, + "stake": self.stake, + "total_stake": self.total_stake, + "ranks": self.ranks, + "trust": self.trust, + "consensus": self.consensus, + "validator_trust": self.validator_trust, + "incentive": self.incentive, + "emission": self.emission, + "dividends": self.dividends, + "active": self.active, + "last_update": self.last_update, + "validator_permit": self.validator_permit, + "weights": self.weights, + "bonds": self.bonds, + "uids": self.uids, + "axons": self.axons, + } + def sync( self, block: Optional[int] = None, @@ -588,62 +599,61 @@ def _set_metagraph_attributes(self, block, subtensor): self._set_metagraph_attributes(block, subtensor) """ # TODO: Check and test the setting of each attribute - self.n = self._create_tensor(len(self.neurons), dtype=torch.int64) + self.n = self._create_tensor(len(self.neurons), dtype=np.int64) self.version = self._create_tensor( - [bittensor.__version_as_int__], dtype=torch.int64 + [bittensor.__version_as_int__], dtype=np.int64 ) self.block = self._create_tensor( - block if block else subtensor.block, dtype=torch.int64 + block if block else subtensor.block, dtype=np.int64 ) self.uids = self._create_tensor( - [neuron.uid for neuron in self.neurons], dtype=torch.int64 + [neuron.uid for neuron in self.neurons], dtype=np.int64 ) self.trust = self._create_tensor( - [neuron.trust for neuron in self.neurons], dtype=torch.float32 + [neuron.trust for neuron in self.neurons], dtype=np.float32 ) self.consensus = self._create_tensor( - [neuron.consensus for neuron in self.neurons], dtype=torch.float32 + [neuron.consensus for neuron in self.neurons], dtype=np.float32 ) self.incentive = self._create_tensor( - [neuron.incentive for neuron in self.neurons], dtype=torch.float32 + [neuron.incentive for neuron in self.neurons], dtype=np.float32 ) self.dividends = self._create_tensor( - [neuron.dividends for neuron in self.neurons], dtype=torch.float32 + [neuron.dividends for neuron in self.neurons], dtype=np.float32 ) self.ranks = self._create_tensor( - [neuron.rank for neuron in self.neurons], dtype=torch.float32 + [neuron.rank for neuron in self.neurons], dtype=np.float32 ) self.emission = self._create_tensor( - [neuron.emission for neuron in self.neurons], dtype=torch.float32 + [neuron.emission for neuron in self.neurons], dtype=np.float32 ) self.active = self._create_tensor( - [neuron.active for neuron in self.neurons], dtype=torch.int64 + [neuron.active for neuron in self.neurons], dtype=np.int64 ) self.last_update = self._create_tensor( - [neuron.last_update for neuron in self.neurons], dtype=torch.int64 + [neuron.last_update for neuron in self.neurons], dtype=np.int64 ) self.validator_permit = self._create_tensor( - [neuron.validator_permit for neuron in self.neurons], dtype=torch.bool + [neuron.validator_permit for neuron in self.neurons], dtype=bool ) self.validator_trust = self._create_tensor( - [neuron.validator_trust for neuron in self.neurons], dtype=torch.float32 + [neuron.validator_trust for neuron in self.neurons], dtype=np.float32 ) self.total_stake = self._create_tensor( - [neuron.total_stake.tao for neuron in self.neurons], dtype=torch.float32 + [neuron.total_stake.tao for neuron in self.neurons], dtype=np.float32 ) self.stake = self._create_tensor( - [neuron.stake for neuron in self.neurons], dtype=torch.float32 + [neuron.stake for neuron in self.neurons], dtype=np.float32 ) self.axons = [n.axon_info for n in self.neurons] - def _create_tensor(self, data, dtype) -> torch.nn.Parameter: + def _create_tensor(self, data, dtype) -> NDArray: """ - Creates a tensor parameter with the given data and data type. This method is a utility function used internally to encapsulate data into a PyTorch tensor, making it compatible with the metagraph's PyTorch - model structure. + Creates a numpy array with the given data and data type. This method is a utility function used internally to encapsulate data into a np.array, making it compatible with the metagraph's numpy model structure. Args: data: The data to be included in the tensor. This could be any numeric data, like stakes, ranks, etc. - dtype: The data type for the tensor, typically a PyTorch data type like ``torch.float32`` or ``torch.int64``. + dtype: The data type for the tensor, typically a numpy data type like ``np.float32`` or ``np.int64``. Returns: A tensor parameter encapsulating the provided data. @@ -651,10 +661,10 @@ def _create_tensor(self, data, dtype) -> torch.nn.Parameter: Internal Usage: Used internally to create tensor parameters for various metagraph attributes:: - self.stake = self._create_tensor(neuron_stakes, dtype=torch.float32) + self.stake = self._create_tensor(neuron_stakes, dtype=np.float32) """ # TODO: Check and test the creation of tensor - return torch.nn.Parameter(torch.tensor(data, dtype=dtype), requires_grad=False) + return np.array(data, dtype=dtype) def _set_weights_and_bonds(self, subtensor: Optional[bittensor.subtensor] = None): """ @@ -681,7 +691,7 @@ def _set_weights_and_bonds(self, subtensor: Optional[bittensor.subtensor] = None [neuron.bonds for neuron in self.neurons], "bonds" ) - def _process_weights_or_bonds(self, data, attribute: str) -> torch.nn.Parameter: + def _process_weights_or_bonds(self, data, attribute: str) -> NDArray: """ Processes the raw weights or bonds data and converts it into a structured tensor format. This method handles the transformation of neuron connection data (``weights`` or ``bonds``) from a list or other unstructured format into a tensor that can be utilized within the metagraph model. @@ -700,7 +710,7 @@ def _process_weights_or_bonds(self, data, attribute: str) -> torch.nn.Parameter: data_array = [] for item in data: if len(item) == 0: - data_array.append(torch.zeros(len(self.neurons))) + data_array.append(np.zeros(len(self.neurons), dtype=np.float32)) else: uids, values = zip(*item) # TODO: Validate and test the conversion of uids and values to tensor @@ -714,12 +724,12 @@ def _process_weights_or_bonds(self, data, attribute: str) -> torch.nn.Parameter: data_array.append( bittensor.utils.weight_utils.convert_bond_uids_and_vals_to_tensor( len(self.neurons), list(uids), list(values) + ).astype( + np.float32 ) ) tensor_param = ( - torch.nn.Parameter(torch.stack(data_array), requires_grad=False) - if len(data_array) - else torch.nn.Parameter() + np.stack(data_array) if len(data_array) else np.array([], dtype=np.float32) ) if len(data_array) == 0: bittensor.logging.warning( @@ -729,7 +739,7 @@ def _process_weights_or_bonds(self, data, attribute: str) -> torch.nn.Parameter: def _process_root_weights( self, data, attribute: str, subtensor: bittensor.subtensor - ) -> torch.nn.Parameter: + ) -> NDArray: """ Specifically processes the root weights data for the metagraph. This method is similar to :func:`_process_weights_or_bonds` but is tailored for processing root weights, which have a different structure and significance in the network. @@ -754,7 +764,7 @@ def _process_root_weights( subnets = subtensor.get_subnets() for item in data: if len(item) == 0: - data_array.append(torch.zeros(n_subnets)) + data_array.append(np.zeros(n_subnets, dtype=np.float32)) else: uids, values = zip(*item) # TODO: Validate and test the conversion of uids and values to tensor @@ -765,9 +775,7 @@ def _process_root_weights( ) tensor_param = ( - torch.nn.Parameter(torch.stack(data_array), requires_grad=False) - if len(data_array) - else torch.nn.Parameter() + np.stack(data_array) if len(data_array) else np.array([], dtype=np.float32) ) if len(data_array) == 0: bittensor.logging.warning( @@ -799,11 +807,10 @@ def save(self) -> "metagraph": """ save_directory = get_save_dir(self.network, self.netuid) os.makedirs(save_directory, exist_ok=True) - graph_file = save_directory + f"/block-{self.block.item()}.pt" + graph_filename = save_directory + f"/block-{self.block.item()}.pt" state_dict = self.state_dict() - state_dict["axons"] = self.axons - torch.save(state_dict, graph_file) - state_dict = torch.load(graph_file) + with open(graph_filename, "wb") as graph_file: + pickle.dump(state_dict, graph_file) return self def load(self): @@ -858,43 +865,52 @@ def load_from_path(self, dir_path: str) -> "metagraph": contain valid data for the metagraph. It is essential to ensure that the directory path and the state files within it are accurate and consistent with the expected metagraph structure. """ - graph_file = latest_block_path(dir_path) - state_dict = torch.load(graph_file) - self.n = torch.nn.Parameter(state_dict["n"], requires_grad=False) - self.block = torch.nn.Parameter(state_dict["block"], requires_grad=False) - self.uids = torch.nn.Parameter(state_dict["uids"], requires_grad=False) - self.stake = torch.nn.Parameter(state_dict["stake"], requires_grad=False) - self.total_stake = torch.nn.Parameter( - state_dict["total_stake"], requires_grad=False - ) - self.ranks = torch.nn.Parameter(state_dict["ranks"], requires_grad=False) - self.trust = torch.nn.Parameter(state_dict["trust"], requires_grad=False) - self.consensus = torch.nn.Parameter( - state_dict["consensus"], requires_grad=False - ) - self.validator_trust = torch.nn.Parameter( - state_dict["validator_trust"], requires_grad=False - ) - self.incentive = torch.nn.Parameter( - state_dict["incentive"], requires_grad=False - ) - self.emission = torch.nn.Parameter(state_dict["emission"], requires_grad=False) - self.dividends = torch.nn.Parameter( - state_dict["dividends"], requires_grad=False - ) - self.active = torch.nn.Parameter(state_dict["active"], requires_grad=False) - self.last_update = torch.nn.Parameter( - state_dict["last_update"], requires_grad=False - ) - self.validator_permit = torch.nn.Parameter( - state_dict["validator_permit"], requires_grad=False - ) - self.uids = torch.nn.Parameter(state_dict["uids"], requires_grad=False) + graph_filename = latest_block_path(dir_path) + try: + with open(graph_filename, "rb") as graph_file: + state_dict = pickle.load(graph_file) + except pickle.UnpicklingError: + bittensor.__console__.print( + "Unable to load file. Attempting to restore metagraph using torch." + ) + if not (torch := maybe_get_torch()): + raise ImportError + else: + bittensor.__console__.print( + ":warning:[yellow]Warning:[/yellow] This functionality exists to load " + "metagraph state from legacy saves, but will not be supported in the future." + ) + try: + state_dict = torch.load(graph_filename) + for key in METAGRAPH_STATE_DICT_NDARRAY_KEYS: + state_dict[key] = state_dict[key].detach().numpy() + except RuntimeError: + bittensor.__console__.print( + "Unable to load file. It may be corrupted." + ) + raise + + self.n = state_dict["n"] + self.block = state_dict["block"] + self.uids = state_dict["uids"] + self.stake = state_dict["stake"] + self.total_stake = state_dict["total_stake"] + self.ranks = state_dict["ranks"] + self.trust = state_dict["trust"] + self.consensus = state_dict["consensus"] + self.validator_trust = state_dict["validator_trust"] + self.incentive = state_dict["incentive"] + self.emission = state_dict["emission"] + self.dividends = state_dict["dividends"] + self.active = state_dict["active"] + self.last_update = state_dict["last_update"] + self.validator_permit = state_dict["validator_permit"] self.axons = state_dict["axons"] if "weights" in state_dict: - self.weights = torch.nn.Parameter( - state_dict["weights"], requires_grad=False - ) + self.weights = state_dict["weights"] if "bonds" in state_dict: - self.bonds = torch.nn.Parameter(state_dict["bonds"], requires_grad=False) + self.bonds = state_dict["bonds"] return self + + +metagraph = Metagraph diff --git a/bittensor/subtensor.py b/bittensor/subtensor.py index 86304f2bc9..a4bc4b20f3 100644 --- a/bittensor/subtensor.py +++ b/bittensor/subtensor.py @@ -20,8 +20,9 @@ import os import copy import time -import torch import logging +import numpy as np +from numpy.typing import NDArray import argparse import bittensor import scalecodec @@ -666,8 +667,8 @@ def set_weights( self, wallet: "bittensor.wallet", netuid: int, - uids: Union[torch.LongTensor, list], - weights: Union[torch.FloatTensor, list], + uids: Union[NDArray[np.int64], list], + weights: Union[NDArray[np.float32], list], version_key: int = bittensor.__version_as_int__, uid: Optional[int] = None, wait_for_inclusion: bool = False, @@ -684,8 +685,8 @@ def set_weights( wallet (bittensor.wallet): The wallet associated with the neuron setting the weights. netuid (int): The unique identifier of the subnet. uid (int): Unique identifier for the caller on the subnet specified by `netuid`. - uids (Union[torch.LongTensor, list]): The list of neuron UIDs that the weights are being set for. - weights (Union[torch.FloatTensor, list]): The corresponding weights to be set for each UID. + uids (Union[NDArray[np.int64], list]): The list of neuron UIDs that the weights are being set for. + weights (Union[NDArray[np.float32], list]): The corresponding weights to be set for each UID. version_key (int, optional): Version key for compatibility with the network. wait_for_inclusion (bool, optional): Waits for the transaction to be included in a block. wait_for_finalization (bool, optional): Waits for the transaction to be finalized on the blockchain. @@ -2146,8 +2147,8 @@ def make_substrate_call_with_retry(): def root_set_weights( self, wallet: "bittensor.wallet", - netuids: Union[torch.LongTensor, list], - weights: Union[torch.FloatTensor, list], + netuids: Union[NDArray[np.int64], list], + weights: Union[NDArray[np.float32], list], version_key: int = 0, wait_for_inclusion: bool = False, wait_for_finalization: bool = False, @@ -2159,8 +2160,8 @@ def root_set_weights( Args: wallet (bittensor.wallet): The wallet associated with the neuron setting the weights. - netuids (Union[torch.LongTensor, list]): The list of neuron UIDs for which weights are being set. - weights (Union[torch.FloatTensor, list]): The corresponding weights to be set for each UID. + netuids (Union[NDArray[np.int64], list]): The list of neuron UIDs for which weights are being set. + weights (Union[NDArray[np.float32], list]): The corresponding weights to be set for each UID. version_key (int, optional): Version key for compatibility with the network. wait_for_inclusion (bool, optional): Waits for the transaction to be included in a block. wait_for_finalization (bool, optional): Waits for the transaction to be finalized on the blockchain. diff --git a/bittensor/tensor.py b/bittensor/tensor.py index 46b4e30c83..5949554041 100644 --- a/bittensor/tensor.py +++ b/bittensor/tensor.py @@ -16,52 +16,51 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. -import numpy -import torch +import numpy as np import base64 import msgpack import pydantic import msgpack_numpy from typing import Optional, Union, List -TORCH_DTYPES = { - "torch.float16": torch.float16, - "torch.float32": torch.float32, - "torch.float64": torch.float64, - "torch.uint8": torch.uint8, - "torch.int16": torch.int16, - "torch.int8": torch.int8, - "torch.int32": torch.int32, - "torch.int64": torch.int64, - "torch.bool": torch.bool, +NUMPY_DTYPES = { + "float16": np.float16, + "float32": np.float32, + "float64": np.float64, + "uint8": np.uint8, + "int16": np.int16, + "int8": np.int8, + "int32": np.int32, + "int64": np.int64, + "bool": bool, } -def cast_dtype(raw: Union[None, torch.dtype, str]) -> str: +def cast_dtype(raw: Union[None, np.dtype, str]) -> str: """ - Casts the raw value to a string representing the `torch data type `_. + Casts the raw value to a string representing the `numpy data type `_. Args: - raw (Union[None, torch.dtype, str]): The raw value to cast. + raw (Union[None, numpy.dtype, str]): The raw value to cast. Returns: - str: The string representing the torch data type. + str: The string representing the numpy data type. Raises: Exception: If the raw value is of an invalid type. """ if not raw: return None - if isinstance(raw, torch.dtype): - return TORCH_DTYPES[raw] + if isinstance(raw, np.dtype): + return NUMPY_DTYPES[raw] elif isinstance(raw, str): assert ( - raw in TORCH_DTYPES - ), f"{str} not a valid torch type in dict {TORCH_DTYPES}" + raw in NUMPY_DTYPES + ), f"{str} not a valid numpy type in dict {NUMPY_DTYPES}" return raw else: raise Exception( - f"{raw} of type {type(raw)} does not have a valid type in Union[None, torch.dtype, str]" + f"{raw} of type {type(raw)} does not have a valid type in Union[None, numpy.dtype, str]" ) @@ -97,11 +96,11 @@ def cast_shape(raw: Union[None, List[int], str]) -> str: class tensor: - def __new__(cls, tensor: Union[list, numpy.ndarray, torch.Tensor]): + def __new__(cls, tensor: Union[list, np.ndarray, np.ndarray]): if isinstance(tensor, list): - tensor = torch.tensor(tensor) - elif isinstance(tensor, numpy.ndarray): - tensor = torch.tensor(tensor) + tensor = np.array(tensor) + elif isinstance(tensor, np.ndarray): + tensor = np.array(tensor) return Tensor.serialize(tensor=tensor) @@ -118,21 +117,21 @@ class Tensor(pydantic.BaseModel): class Config: validate_assignment = True - def tensor(self) -> torch.Tensor: + def tensor(self) -> np.ndarray: return self.deserialize() def tolist(self) -> List[object]: return self.deserialize().tolist() def numpy(self) -> "numpy.ndarray": - return self.deserialize().detach().numpy() + return self.deserialize() - def deserialize(self) -> "torch.Tensor": + def deserialize(self) -> "np.ndarray": """ Deserializes the Tensor object. Returns: - torch.Tensor: The deserialized tensor object. + np.array: The deserialized tensor object. Raises: Exception: If the deserialization process encounters an error. @@ -142,19 +141,19 @@ def deserialize(self) -> "torch.Tensor": numpy_object = msgpack.unpackb( buffer_bytes, object_hook=msgpack_numpy.decode ).copy() - torch_object = torch.as_tensor(numpy_object) + numpy = numpy_object # Reshape does not work for (0) or [0] if not (len(shape) == 1 and shape[0] == 0): - torch_object = torch_object.reshape(shape) - return torch_object.type(TORCH_DTYPES[self.dtype]) + numpy = numpy.reshape(shape) + return numpy.astype(NUMPY_DTYPES[self.dtype]) @staticmethod - def serialize(tensor: "torch.Tensor") -> "Tensor": + def serialize(tensor: "np.ndarray") -> "Tensor": """ Serializes the given tensor. Args: - tensor (torch.Tensor): The tensor to serialize. + tensor (np.array): The tensor to serialize. Returns: Tensor: The serialized tensor. @@ -166,9 +165,8 @@ def serialize(tensor: "torch.Tensor") -> "Tensor": shape = list(tensor.shape) if len(shape) == 0: shape = [0] - torch_numpy = tensor.cpu().detach().numpy().copy() data_buffer = base64.b64encode( - msgpack.packb(torch_numpy, default=msgpack_numpy.encode) + msgpack.packb(tensor, default=msgpack_numpy.encode) ).decode("utf-8") return Tensor(buffer=data_buffer, shape=shape, dtype=dtype) @@ -182,8 +180,8 @@ def serialize(tensor: "torch.Tensor") -> "Tensor": dtype: str = pydantic.Field( title="dtype", - description="Tensor data type. This field specifies the data type of the tensor, such as torch.float32 or torch.int64.", - examples="torch.float32", + description="Tensor data type. This field specifies the data type of the tensor, such as numpy.float32 or numpy.int64.", + examples="np.float32", allow_mutation=False, repr=True, ) # Represents the data type of the tensor. diff --git a/bittensor/threadpool.py b/bittensor/threadpool.py index c20d64a6b3..3e49786ff6 100644 --- a/bittensor/threadpool.py +++ b/bittensor/threadpool.py @@ -225,14 +225,14 @@ def submit(self, fn: Callable, *args, **kwargs) -> _base.Future: priority = kwargs.get("priority", random.randint(0, 1000000)) if priority == 0: priority = random.randint(1, 100) - eplison = random.uniform(0, 0.01) * priority + epsilon = random.uniform(0, 0.01) * priority start_time = time.time() if "priority" in kwargs: del kwargs["priority"] f = _base.Future() w = _WorkItem(f, fn, start_time, args, kwargs) - self._work_queue.put((-float(priority + eplison), w), block=False) + self._work_queue.put((-float(priority + epsilon), w), block=False) self._adjust_thread_count() return f diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index fbc3aa7d47..4282738a02 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -16,15 +16,17 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. -from typing import Callable, Union, List, Optional, Dict, Literal +from typing import Callable, List, Dict, Literal, Tuple import bittensor import hashlib import requests -import torch import scalecodec +import numpy as np from .wallet_utils import * # noqa F401 +from .registration import maybe_get_torch + RAOPERTAO = 1e9 U16_MAX = 65535 @@ -37,25 +39,34 @@ def ss58_to_vec_u8(ss58_address: str) -> List[int]: return encoded_address -def unbiased_topk(values, k, dim=0, sorted=True, largest=True): +def unbiased_topk( + values: np.ndarray, k: int, dim=0, sorted=True, largest=True, axis=0 +) -> Tuple[np.ndarray, np.ndarray]: r"""Selects topk as in torch.topk but does not bias lower indices when values are equal. Args: - values: (torch.Tensor) + values: (np.ndarray) Values to index into. k: (int): Number to take. Return: - topk: (torch.Tensor): + topk: (np.ndarray): topk k values. - indices: (torch.LongTensor) + indices: (np.ndarray) indices of the topk values. """ - permutation = torch.randperm(values.shape[dim]) - permuted_values = values[permutation] - topk, indices = torch.topk( - permuted_values, k, dim=dim, sorted=sorted, largest=largest - ) + if dim != 0 and axis == 0: + # Ensures a seamless transition for calls made to this function that specified args by keyword + axis = dim + + permutation = np.random.permutation(values.shape[axis]) + permuted_values = np.take(values, permutation, axis=axis) + indices = np.argpartition(permuted_values, -k, axis=axis)[-k:] + if not sorted: + indices = np.sort(indices, axis=axis) + if not largest: + indices = indices[::-1] + topk = np.take(permuted_values, indices, axis=axis) return topk, permutation[indices] diff --git a/bittensor/utils/registration.py b/bittensor/utils/registration.py index b389d8176b..3fe4b03a89 100644 --- a/bittensor/utils/registration.py +++ b/bittensor/utils/registration.py @@ -12,7 +12,6 @@ import backoff import bittensor -import torch from Crypto.Hash import keccak from rich import console as rich_console from rich import status as rich_status @@ -20,6 +19,19 @@ from .formatting import get_human_readable, millify from ._register_cuda import solve_cuda +try: + import torch +except ImportError: + torch = None + + +def maybe_get_torch(): + if torch is None: + bittensor.logging.warning( + "This command requires torch. Please install torch package." + ) + return torch + class CUDAException(Exception): """An exception raised when an error occurs in the CUDA environment.""" diff --git a/bittensor/utils/weight_utils.py b/bittensor/utils/weight_utils.py index 0c5c944a0e..12c2b903f1 100644 --- a/bittensor/utils/weight_utils.py +++ b/bittensor/utils/weight_utils.py @@ -1,4 +1,4 @@ -""" Conversion for weight between chain representation and torch tensor +""" Conversion for weight between chain representation and np.array """ # The MIT License (MIT) @@ -18,8 +18,9 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. -import torch +import numpy as np import bittensor +from numpy.typing import NDArray from typing import Tuple, List U32_MAX = 4294967295 @@ -27,25 +28,25 @@ def normalize_max_weight( - x: torch.FloatTensor, limit: float = 0.1 -) -> "torch.FloatTensor": + x: NDArray[np.float32], limit: float = 0.1 +) -> NDArray[np.float32]: 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`): + x (:obj:`np.float32`): Tensor to be max_value normalized. limit: float: Max value after normalization. Returns: - y (:obj:`torch.FloatTensor`): + y (:obj:`np.float32`): Normalized x tensor. """ epsilon = 1e-7 # For numerical stability after normalization - weights = x.clone() - values, _ = torch.sort(weights) + weights = x.copy() + values = np.sort(weights) - if x.sum() == 0 or len(x) * limit <= 1: - return torch.ones_like(x) / x.size(0) + if x.sum() == 0 or x.shape[0] * limit <= 1: + return np.ones_like(x) / x.shape[0] else: estimation = values / values.sum() @@ -53,10 +54,10 @@ def normalize_max_weight( return weights / weights.sum() # Find the cumlative sum and sorted tensor - cumsum = torch.cumsum(estimation, 0) + cumsum = np.cumsum(estimation, 0) # Determine the index of cutoff - estimation_sum = torch.tensor( + estimation_sum = np.array( [(len(values) - i - 1) * estimation[i] for i in range(len(values))] ) n_values = (estimation / (estimation_sum + cumsum + epsilon) < limit).sum() @@ -77,8 +78,8 @@ def normalize_max_weight( def convert_weight_uids_and_vals_to_tensor( n: int, uids: List[int], weights: List[int] -) -> "torch.FloatTensor": - r"""Converts weights and uids from chain representation into a torch tensor (inverse operation from convert_weights_and_uids_for_emit) +) -> NDArray[np.float32]: + r"""Converts weights and uids from chain representation into a np.array (inverse operation from convert_weights_and_uids_for_emit) Args: n: int: number of neurons on network. @@ -87,10 +88,10 @@ def convert_weight_uids_and_vals_to_tensor( weights (:obj:`List[int],`): Tensor of weights. Returns: - row_weights ( torch.FloatTensor ): + row_weights ( np.float32 ): Converted row weights. """ - row_weights = torch.zeros([n], dtype=torch.float32) + row_weights = np.zeros([n], dtype=np.float32) for uid_j, wij in list(zip(uids, weights)): row_weights[uid_j] = float( wij @@ -103,8 +104,8 @@ def convert_weight_uids_and_vals_to_tensor( def convert_root_weight_uids_and_vals_to_tensor( n: int, uids: List[int], weights: List[int], subnets: List[int] -) -> "torch.FloatTensor": - r"""Converts root weights and uids from chain representation into a torch tensor (inverse operation from convert_weights_and_uids_for_emit) +) -> NDArray[np.float32]: + r"""Converts root weights and uids from chain representation into a np.array (inverse operation from convert_weights_and_uids_for_emit) Args: n: int: number of neurons on network. @@ -115,11 +116,11 @@ def convert_root_weight_uids_and_vals_to_tensor( subnets (:obj:`List[int],`): list of subnets on the network Returns: - row_weights ( torch.FloatTensor ): + row_weights ( np.float32 ): Converted row weights. """ - row_weights = torch.zeros([n], dtype=torch.float32) + row_weights = np.zeros([n], dtype=np.float32) for uid_j, wij in list(zip(uids, weights)): if uid_j in subnets: index_s = subnets.index(uid_j) @@ -136,8 +137,8 @@ def convert_root_weight_uids_and_vals_to_tensor( def convert_bond_uids_and_vals_to_tensor( n: int, uids: List[int], bonds: List[int] -) -> "torch.LongTensor": - r"""Converts bond and uids from chain representation into a torch tensor. +) -> NDArray[np.int64]: + r"""Converts bond and uids from chain representation into a np.array. Args: n: int: number of neurons on network. @@ -146,23 +147,23 @@ def convert_bond_uids_and_vals_to_tensor( bonds (:obj:`List[int],`): Tensor of bonds. Returns: - row_bonds ( torch.FloatTensor ): + row_bonds ( np.float32 ): Converted row bonds. """ - row_bonds = torch.zeros([n], dtype=torch.int64) + row_bonds = np.zeros([n], dtype=np.int64) for uid_j, bij in list(zip(uids, bonds)): row_bonds[uid_j] = int(bij) return row_bonds def convert_weights_and_uids_for_emit( - uids: torch.LongTensor, weights: torch.FloatTensor + uids: NDArray[np.int64], weights: NDArray[np.float32] ) -> Tuple[List[int], List[int]]: r"""Converts weights into integer u32 representation that sum to MAX_INT_WEIGHT. Args: - uids (:obj:`torch.LongTensor,`): + uids (:obj:`np.int64,`): Tensor of uids as destinations for passed weights. - weights (:obj:`torch.FloatTensor,`): + weights (:obj:`np.float32,`): Tensor of weights. Returns: weight_uids (List[int]): @@ -209,13 +210,13 @@ def convert_weights_and_uids_for_emit( def process_weights_for_netuid( - uids, - weights: torch.Tensor, + uids: NDArray[np.int64], + weights: NDArray[np.float32], netuid: int, subtensor: "bittensor.subtensor", metagraph: "bittensor.metagraph" = None, exclude_quantile: int = 0, -) -> torch.FloatTensor: +) -> Tuple[NDArray[np.int64], NDArray[np.float32]]: bittensor.logging.debug("process_weights_for_netuid()") bittensor.logging.debug("weights", weights) bittensor.logging.debug("netuid", netuid) @@ -223,12 +224,12 @@ def process_weights_for_netuid( bittensor.logging.debug("metagraph", metagraph) # Get latest metagraph from chain if metagraph is None. - if metagraph == None: + if metagraph is None: metagraph = subtensor.metagraph(netuid) # Cast weights to floats. - if not isinstance(weights, torch.FloatTensor): - weights = weights.type(torch.float32) + if not isinstance(weights, np.float32): + weights = weights.astype(np.float32) # Network configuration parameters from an subtensor. # These parameters determine the range of acceptable weights for each neuron. @@ -240,29 +241,29 @@ def process_weights_for_netuid( bittensor.logging.debug("max_weight_limit", max_weight_limit) # Find all non zero weights. - non_zero_weight_idx = torch.argwhere(weights > 0).squeeze(dim=1) + non_zero_weight_idx = np.argwhere(weights > 0).squeeze(axis=1) non_zero_weight_uids = uids[non_zero_weight_idx] non_zero_weights = weights[non_zero_weight_idx] - if non_zero_weights.numel() == 0 or metagraph.n < min_allowed_weights: + if non_zero_weights.size == 0 or metagraph.n < min_allowed_weights: bittensor.logging.warning("No non-zero weights returning all ones.") - final_weights = torch.ones((metagraph.n)).to(metagraph.n) / metagraph.n + final_weights = np.ones((metagraph.n), dtype=np.int64) / metagraph.n bittensor.logging.debug("final_weights", final_weights) - return torch.tensor(list(range(len(final_weights)))), final_weights + return np.arange(len(final_weights)), final_weights - elif non_zero_weights.numel() < min_allowed_weights: + elif non_zero_weights.size < min_allowed_weights: bittensor.logging.warning( "No non-zero weights less then min allowed weight, returning all ones." ) - # ( const ): Should this be torch.zeros( ( metagraph.n ) ) to reset everyone to build up weight? + # ( const ): Should this be np.zeros( ( metagraph.n ) ) to reset everyone to build up weight? weights = ( - torch.ones((metagraph.n)).to(metagraph.n) * 1e-5 + np.ones((metagraph.n), dtype=np.int64) * 1e-5 ) # creating minimum even non-zero weights weights[non_zero_weight_idx] += non_zero_weights bittensor.logging.debug("final_weights", weights) normalized_weights = bittensor.utils.weight_utils.normalize_max_weight( x=weights, limit=max_weight_limit ) - return torch.tensor(list(range(len(normalized_weights)))), normalized_weights + return np.arange(len(normalized_weights)), normalized_weights bittensor.logging.debug("non_zero_weights", non_zero_weights) @@ -271,7 +272,7 @@ def process_weights_for_netuid( non_zero_weights ) exclude_quantile = min([quantile, max_exclude]) - lowest_quantile = non_zero_weights.quantile(exclude_quantile) + lowest_quantile = np.quantile(non_zero_weights, exclude_quantile) bittensor.logging.debug("max_exclude", max_exclude) bittensor.logging.debug("exclude_quantile", exclude_quantile) bittensor.logging.debug("lowest_quantile", lowest_quantile) diff --git a/requirements/cubit.txt b/requirements/cubit.txt index c4e73590eb..5af1316836 100644 --- a/requirements/cubit.txt +++ b/requirements/cubit.txt @@ -1,2 +1,3 @@ +torch>=1.13.1 cubit>=1.1.0 cubit @ git+https://github.com/opentensor/cubit.git diff --git a/requirements/dev.txt b/requirements/dev.txt index 71e70ae766..25fd8561ef 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -10,3 +10,4 @@ hypothesis==6.81.1 flake8==7.0.0 mypy==1.8.0 types-retry==0.9.9.4 +torch>=1.13.1 diff --git a/requirements/prod.txt b/requirements/prod.txt index 5a1e01bd1e..debbfd2c71 100644 --- a/requirements/prod.txt +++ b/requirements/prod.txt @@ -30,7 +30,6 @@ scalecodec==1.2.7 # scalecodec should not be changed unless first verifying com shtab==1.6.5 substrate-interface==1.7.5 termcolor -torch>=1.13.1 tqdm uvicorn==0.22.0 wheel diff --git a/requirements/torch.txt b/requirements/torch.txt new file mode 100644 index 0000000000..028dec0810 --- /dev/null +++ b/requirements/torch.txt @@ -0,0 +1 @@ +torch>=1.13.1 diff --git a/scripts/environments/README.md b/scripts/environments/README.md index 23c14e1d5a..0caa0d2ae4 100644 --- a/scripts/environments/README.md +++ b/scripts/environments/README.md @@ -18,4 +18,4 @@ There are quite a few Python libraries that are not yet compatible with Apple M ```bash conda activate bittensor # activate the env pip install --no-deps bittensor # install bittensor - ``` \ No newline at end of file + ``` diff --git a/setup.py b/setup.py index 8660101c04..376a9fc069 100644 --- a/setup.py +++ b/setup.py @@ -42,6 +42,7 @@ def read_requirements(path): requirements = read_requirements("requirements/prod.txt") extra_requirements_dev = read_requirements("requirements/dev.txt") extra_requirements_cubit = read_requirements("requirements/cubit.txt") +extra_requirements_torch = read_requirements("requirements/torch.txt") here = path.abspath(path.dirname(__file__)) @@ -74,6 +75,7 @@ def read_requirements(path): install_requires=requirements, extras_require={ "dev": extra_requirements_dev, + "torch": extra_requirements_torch, }, scripts=["bin/btcli"], classifiers=[ diff --git a/tests/integration_tests/test_metagraph_integration.py b/tests/integration_tests/test_metagraph_integration.py index 47ab8abe55..5dbb9ddfc1 100644 --- a/tests/integration_tests/test_metagraph_integration.py +++ b/tests/integration_tests/test_metagraph_integration.py @@ -17,9 +17,10 @@ # DEALINGS IN THE SOFTWARE. import bittensor -from bittensor.mock import MockSubtensor import torch -import pytest +import os +from bittensor.mock import MockSubtensor +from bittensor.metagraph import METAGRAPH_STATE_DICT_NDARRAY_KEYS, get_save_dir _subtensor_mock: MockSubtensor = MockSubtensor() @@ -56,6 +57,23 @@ def test_load_sync_save(self): self.metagraph.load() self.metagraph.save() + def test_load_sync_save_from_torch(self): + self.metagraph.sync(lite=True, subtensor=self.sub) + + def deprecated_save_torch(metagraph): + save_directory = get_save_dir(metagraph.network, metagraph.netuid) + os.makedirs(save_directory, exist_ok=True) + graph_filename = save_directory + f"/block-{metagraph.block.item()}.pt" + state_dict = metagraph.state_dict() + for key in METAGRAPH_STATE_DICT_NDARRAY_KEYS: + state_dict[key] = torch.nn.Parameter( + torch.tensor(state_dict[key]), requires_grad=False + ) + torch.save(state_dict, graph_filename) + + deprecated_save_torch(self.metagraph) + self.metagraph.load() + def test_state_dict(self): self.metagraph.load() state = self.metagraph.state_dict() @@ -94,8 +112,3 @@ def test_properties(self): metagraph.D metagraph.B metagraph.W - - def test_parameters(self): - params = list(self.metagraph.parameters()) - assert len(params) > 0 - assert isinstance(params[0], torch.nn.parameter.Parameter) diff --git a/tests/unit_tests/test_chain_data.py b/tests/unit_tests/test_chain_data.py index d8e1c1eb0c..2cc842d9a2 100644 --- a/tests/unit_tests/test_chain_data.py +++ b/tests/unit_tests/test_chain_data.py @@ -1,5 +1,4 @@ import pytest -import torch import bittensor from bittensor.chain_data import AxonInfo, ChainDataType, DelegateInfo, NeuronInfo @@ -199,7 +198,7 @@ def test_to_parameter_dict(axon_info, test_case): result = axon_info.to_parameter_dict() # Assert - assert isinstance(result, torch.nn.ParameterDict) + assert isinstance(result, dict) for key, value in axon_info.__dict__.items(): assert key in result assert result[key] == value, f"Test case: {test_case}" @@ -209,16 +208,14 @@ def test_to_parameter_dict(axon_info, test_case): "parameter_dict, expected, test_case", [ ( - torch.nn.ParameterDict( - { - "version": 1, - "ip": "127.0.0.1", - "port": 8080, - "ip_type": 4, - "hotkey": "hot", - "coldkey": "cold", - } - ), + { + "version": 1, + "ip": "127.0.0.1", + "port": 8080, + "ip_type": 4, + "hotkey": "hot", + "coldkey": "cold", + }, AxonInfo( version=1, ip="127.0.0.1", diff --git a/tests/unit_tests/test_metagraph.py b/tests/unit_tests/test_metagraph.py index 0c1aac2637..38d2cf14cb 100644 --- a/tests/unit_tests/test_metagraph.py +++ b/tests/unit_tests/test_metagraph.py @@ -17,12 +17,11 @@ from unittest.mock import Mock import pytest -import torch +import numpy as np import bittensor from bittensor.metagraph import metagraph as Metagraph from unittest.mock import MagicMock -from loguru import logger @pytest.fixture @@ -66,25 +65,25 @@ def test_set_metagraph_attributes(mock_environment): assert metagraph.n.item() == len(neurons) assert metagraph.block.item() == 5 assert ( - torch.equal( + np.array_equal( metagraph.uids, - torch.tensor([neuron.uid for neuron in neurons], dtype=torch.int64), + np.array([neuron.uid for neuron in neurons], dtype=np.int64), ) - == True + is True ) assert ( - torch.equal( + np.array_equal( metagraph.trust, - torch.tensor([neuron.trust for neuron in neurons], dtype=torch.float32), + np.array([neuron.trust for neuron in neurons], dtype=np.float32), ) - == True + is True ) assert ( - torch.equal( + np.array_equal( metagraph.consensus, - torch.tensor([neuron.consensus for neuron in neurons], dtype=torch.float32), + np.array([neuron.consensus for neuron in neurons], dtype=np.float32), ) == True ) diff --git a/tests/unit_tests/test_synapse.py b/tests/unit_tests/test_synapse.py index 70dd88db76..9c15f67ce9 100644 --- a/tests/unit_tests/test_synapse.py +++ b/tests/unit_tests/test_synapse.py @@ -15,7 +15,6 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. import json -import torch import base64 import typing import pytest diff --git a/tests/unit_tests/test_tensor.py b/tests/unit_tests/test_tensor.py index c09d1d64e0..94d8f7cd52 100644 --- a/tests/unit_tests/test_tensor.py +++ b/tests/unit_tests/test_tensor.py @@ -15,7 +15,7 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. import pytest -import torch +import numpy as np import bittensor import numpy @@ -24,7 +24,7 @@ @pytest.fixture def example_tensor(): # Create a tensor from a list using PyTorch - data = torch.tensor([1, 2, 3, 4]) + data = np.array([1, 2, 3, 4]) # Serialize the tensor into a Tensor instance and return it return bittensor.tensor(data) @@ -34,8 +34,8 @@ def test_deserialize(example_tensor): # Deserialize the tensor from the Tensor instance tensor = example_tensor.deserialize() - # Check that the result is a PyTorch tensor with the correct values - assert isinstance(tensor, torch.Tensor) + # Check that the result is a np.array with the correct values + assert isinstance(tensor, np.ndarray) assert tensor.tolist() == [1, 2, 3, 4] @@ -62,7 +62,7 @@ def test_serialize(example_tensor): assert example_tensor.dtype == example_tensor.dtype assert example_tensor.shape == example_tensor.shape - assert isinstance(example_tensor.tensor(), torch.Tensor) + assert isinstance(example_tensor.tensor(), np.ndarray) # Check that the Tensor instance has the correct buffer, dtype, and shape assert example_tensor.buffer == example_tensor.buffer @@ -73,7 +73,7 @@ def test_serialize(example_tensor): def test_buffer_field(): # Create a Tensor instance with a specified buffer, dtype, and shape tensor = bittensor.Tensor( - buffer="0x321e13edqwds231231231232131", dtype="torch.float32", shape=[3, 3] + buffer="0x321e13edqwds231231231232131", dtype="float32", shape=[3, 3] ) # Check that the buffer field matches the provided value @@ -83,17 +83,17 @@ def test_buffer_field(): def test_dtype_field(): # Create a Tensor instance with a specified buffer, dtype, and shape tensor = bittensor.Tensor( - buffer="0x321e13edqwds231231231232131", dtype="torch.float32", shape=[3, 3] + buffer="0x321e13edqwds231231231232131", dtype="float32", shape=[3, 3] ) # Check that the dtype field matches the provided value - assert tensor.dtype == "torch.float32" + assert tensor.dtype == "float32" def test_shape_field(): # Create a Tensor instance with a specified buffer, dtype, and shape tensor = bittensor.Tensor( - buffer="0x321e13edqwds231231231232131", dtype="torch.float32", shape=[3, 3] + buffer="0x321e13edqwds231231231232131", dtype="float32", shape=[3, 3] ) # Check that the shape field matches the provided value @@ -101,37 +101,34 @@ def test_shape_field(): def test_serialize_all_types(): - bittensor.tensor(torch.tensor([1], dtype=torch.float16)) - bittensor.tensor(torch.tensor([1], dtype=torch.float32)) - bittensor.tensor(torch.tensor([1], dtype=torch.float64)) - bittensor.tensor(torch.tensor([1], dtype=torch.uint8)) - bittensor.tensor(torch.tensor([1], dtype=torch.int32)) - bittensor.tensor(torch.tensor([1], dtype=torch.int64)) - bittensor.tensor(torch.tensor([1], dtype=torch.bool)) + bittensor.tensor(np.array([1], dtype=np.float16)) + bittensor.tensor(np.array([1], dtype=np.float32)) + bittensor.tensor(np.array([1], dtype=np.float64)) + bittensor.tensor(np.array([1], dtype=np.uint8)) + bittensor.tensor(np.array([1], dtype=np.int32)) + bittensor.tensor(np.array([1], dtype=np.int64)) + bittensor.tensor(np.array([1], dtype=bool)) def test_serialize_all_types_equality(): - torchtensor = torch.randn([100], dtype=torch.float16) - assert torch.all(bittensor.tensor(torchtensor).tensor() == torchtensor) + rng = np.random.default_rng() - torchtensor = torch.randn([100], dtype=torch.float32) - assert torch.all(bittensor.tensor(torchtensor).tensor() == torchtensor) + tensor = rng.standard_normal((100,), dtype=np.float32) + assert np.all(bittensor.tensor(tensor).tensor() == tensor) - torchtensor = torch.randn([100], dtype=torch.float64) - assert torch.all(bittensor.tensor(torchtensor).tensor() == torchtensor) + tensor = rng.standard_normal((100,), dtype=np.float64) + assert np.all(bittensor.tensor(tensor).tensor() == tensor) - torchtensor = torch.randint(255, 256, (1000,), dtype=torch.uint8) - assert torch.all(bittensor.tensor(torchtensor).tensor() == torchtensor) + tensor = np.random.randint(255, 256, (1000,), dtype=np.uint8) + assert np.all(bittensor.tensor(tensor).tensor() == tensor) - torchtensor = torch.randint( - 2_147_483_646, 2_147_483_647, (1000,), dtype=torch.int32 - ) - assert torch.all(bittensor.tensor(torchtensor).tensor() == torchtensor) + tensor = np.random.randint(2_147_483_646, 2_147_483_647, (1000,), dtype=np.int32) + assert np.all(bittensor.tensor(tensor).tensor() == tensor) - torchtensor = torch.randint( - 9_223_372_036_854_775_806, 9_223_372_036_854_775_807, (1000,), dtype=torch.int64 + tensor = np.random.randint( + 9_223_372_036_854_775_806, 9_223_372_036_854_775_807, (1000,), dtype=np.int64 ) - assert torch.all(bittensor.tensor(torchtensor).tensor() == torchtensor) + assert np.all(bittensor.tensor(tensor).tensor() == tensor) - torchtensor = torch.randn([100], dtype=torch.float32) < 0.5 - assert torch.all(bittensor.tensor(torchtensor).tensor() == torchtensor) + tensor = rng.standard_normal((100,), dtype=np.float32) < 0.5 + assert np.all(bittensor.tensor(tensor).tensor() == tensor) diff --git a/tests/unit_tests/utils/test_utils.py b/tests/unit_tests/utils/test_utils.py index 0875d921e0..88938ed8f4 100644 --- a/tests/unit_tests/utils/test_utils.py +++ b/tests/unit_tests/utils/test_utils.py @@ -17,14 +17,15 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. -import torch +import numpy as np import bittensor.utils.weight_utils as weight_utils +import bittensor.utils as unbiased_topk import pytest def test_convert_weight_and_uids(): - uids = torch.tensor(list(range(10))) - weights = torch.rand(10) + uids = np.arange(10) + weights = np.random.rand(10) weight_utils.convert_weights_and_uids_for_emit(uids, weights) # min weight < 0 @@ -44,77 +45,77 @@ def test_convert_weight_and_uids(): weight_utils.convert_weights_and_uids_for_emit(uids, weights[1:]) # sum(weights) == 0 - weights = torch.zeros(10) + weights = np.zeros(10) weight_utils.convert_weights_and_uids_for_emit(uids, weights) # test for overflow and underflow for _ in range(5): - uids = torch.tensor(list(range(10))) - weights = torch.rand(10) + uids = np.arange(10) + weights = np.random.rand(10) weight_utils.convert_weights_and_uids_for_emit(uids, weights) def test_normalize_with_max_weight(): - weights = torch.rand(1000) + weights = np.random.rand(1000) wn = weight_utils.normalize_max_weight(weights, limit=0.01) assert wn.max() <= 0.01 - weights = torch.zeros(1000) + weights = np.zeros(1000) wn = weight_utils.normalize_max_weight(weights, limit=0.01) assert wn.max() <= 0.01 - weights = torch.rand(1000) + weights = np.random.rand(1000) wn = weight_utils.normalize_max_weight(weights, limit=0.02) assert wn.max() <= 0.02 - weights = torch.zeros(1000) + weights = np.zeros(1000) wn = weight_utils.normalize_max_weight(weights, limit=0.02) assert wn.max() <= 0.02 - weights = torch.rand(1000) + weights = np.random.rand(1000) wn = weight_utils.normalize_max_weight(weights, limit=0.03) assert wn.max() <= 0.03 - weights = torch.zeros(1000) + weights = np.zeros(1000) wn = weight_utils.normalize_max_weight(weights, limit=0.03) assert wn.max() <= 0.03 # Check for Limit limit = 0.001 - weights = torch.rand(2000) + weights = np.random.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 ( + assert (w.max() >= limit and np.abs(limit - wn.max()) < 0.001) or ( w.max() < limit and wn.max() < limit ) # Check for Zeros limit = 0.01 - weights = torch.zeros(2000) + weights = np.zeros(2000) wn = weight_utils.normalize_max_weight(weights, limit=limit) assert wn.max() == 1 / 2000 # Check for Ordering after normalization - weights = torch.rand(100) + weights = np.random.rand(100) wn = weight_utils.normalize_max_weight(weights, limit=1) - assert torch.equal(wn, weights / weights.sum()) + assert np.array_equal(wn, weights / weights.sum()) - # Check for eplison changes - eplison = 0.01 - weights, _ = torch.sort(torch.rand(100)) + # Check for epsilon changes + epsilon = 0.01 + weights = np.sort(np.random.rand(100)) x = weights / weights.sum() limit = x[-10] - change = eplison * limit + change = epsilon * 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 + assert np.abs(y - z).sum() < epsilon @pytest.mark.parametrize( "test_id, n, uids, weights, expected", [ - ("happy-path-1", 3, [0, 1, 2], [15, 5, 80], torch.tensor([0.15, 0.05, 0.8])), - ("happy-path-2", 4, [1, 3], [50, 50], torch.tensor([0.0, 0.5, 0.0, 0.5])), + ("happy-path-1", 3, [0, 1, 2], [15, 5, 80], np.array([0.15, 0.05, 0.8])), + ("happy-path-2", 4, [1, 3], [50, 50], np.array([0.0, 0.5, 0.0, 0.5])), ], ) def test_convert_weight_uids_and_vals_to_tensor_happy_path( @@ -124,15 +125,15 @@ def test_convert_weight_uids_and_vals_to_tensor_happy_path( result = weight_utils.convert_weight_uids_and_vals_to_tensor(n, uids, weights) # Assert - assert torch.allclose(result, expected), f"Failed {test_id}" + assert np.allclose(result, expected), f"Failed {test_id}" @pytest.mark.parametrize( "test_id, n, uids, weights, expected", [ - ("edge_case_empty", 5, [], [], torch.zeros(5)), - ("edge_case_single", 1, [0], [100], torch.tensor([1.0])), - ("edge_case_all_zeros", 4, [0, 1, 2, 3], [0, 0, 0, 0], torch.zeros(4)), + ("edge_case_empty", 5, [], [], np.zeros(5)), + ("edge_case_single", 1, [0], [100], np.array([1.0])), + ("edge_case_all_zeros", 4, [0, 1, 2, 3], [0, 0, 0, 0], np.zeros(4)), ], ) def test_convert_weight_uids_and_vals_to_tensor_edge_cases( @@ -142,14 +143,14 @@ def test_convert_weight_uids_and_vals_to_tensor_edge_cases( result = weight_utils.convert_weight_uids_and_vals_to_tensor(n, uids, weights) # Assert - assert torch.allclose(result, expected), f"Failed {test_id}" + assert np.allclose(result, expected), f"Failed {test_id}" @pytest.mark.parametrize( "test_id, n, uids, weights, exception", [ ("error-case-mismatched-lengths", 3, [0, 1, 3, 4, 5], [10, 20, 30], IndexError), - ("error-case-negative-n", -1, [0, 1], [10, 20], RuntimeError), + ("error-case-negative-n", -1, [0, 1], [10, 20], ValueError), ("error-case-invalid-uids", 3, [0, 3], [10, 20], IndexError), ], ) @@ -170,7 +171,7 @@ def test_convert_weight_uids_and_vals_to_tensor_error_cases( [0, 1, 2], [15, 5, 80], [0, 1, 2], - torch.tensor([0.15, 0.05, 0.8]), + np.array([0.15, 0.05, 0.8]), ), ( "happy-path-2", @@ -178,7 +179,7 @@ def test_convert_weight_uids_and_vals_to_tensor_error_cases( [0, 2], [300, 300], [0, 1, 2], - torch.tensor([0.5, 0.0, 0.5]), + np.array([0.5, 0.0, 0.5]), ), ], ) @@ -191,7 +192,7 @@ def test_convert_root_weight_uids_and_vals_to_tensor_happy_paths( ) # Assert - assert torch.allclose(result, expected, atol=1e-4), f"Failed {test_id}" + assert np.allclose(result, expected, atol=1e-4), f"Failed {test_id}" @pytest.mark.parametrize( @@ -203,7 +204,7 @@ def test_convert_root_weight_uids_and_vals_to_tensor_happy_paths( [0], [0], [0], - torch.tensor([0.0]), + np.array([0.0]), ), # Single neuron with zero weight ( "edge-2", @@ -211,7 +212,7 @@ def test_convert_root_weight_uids_and_vals_to_tensor_happy_paths( [0, 1], [0, 0], [0, 1], - torch.tensor([0.0, 0.0]), + np.array([0.0, 0.0]), ), # All zero weights ], ) @@ -224,7 +225,7 @@ def test_convert_root_weight_uids_and_vals_to_tensor_edge_cases( ) # Assert - assert torch.allclose(result, expected, atol=1e-4), f"Failed {test_id}" + assert np.allclose(result, expected, atol=1e-4), f"Failed {test_id}" @pytest.mark.parametrize( @@ -253,16 +254,16 @@ def test_convert_root_weight_uids_and_vals_to_tensor_error_cases( 5, [1, 3, 4], [10, 20, 30], - torch.tensor([0, 10, 0, 20, 30], dtype=torch.int64), + np.array([0, 10, 0, 20, 30], dtype=np.int64), ), ( "happy-path-2", 3, [0, 1, 2], [7, 8, 9], - torch.tensor([7, 8, 9], dtype=torch.int64), + np.array([7, 8, 9], dtype=np.int64), ), - ("happy-path-3", 4, [2], [15], torch.tensor([0, 0, 15, 0], dtype=torch.int64)), + ("happy-path-3", 4, [2], [15], np.array([0, 0, 15, 0], dtype=np.int64)), ], ) def test_happy_path(test_id, n, uids, bonds, expected_output): @@ -270,19 +271,19 @@ def test_happy_path(test_id, n, uids, bonds, expected_output): result = weight_utils.convert_bond_uids_and_vals_to_tensor(n, uids, bonds) # Assert - assert torch.equal(result, expected_output), f"Failed {test_id}" + assert np.array_equal(result, expected_output), f"Failed {test_id}" @pytest.mark.parametrize( "test_id, n, uids, bonds, expected_output", [ - ("edge-1", 1, [0], [0], torch.tensor([0], dtype=torch.int64)), # Single element + ("edge-1", 1, [0], [0], np.array([0], dtype=np.int64)), # Single element ( "edge-2", 10, [], [], - torch.zeros(10, dtype=torch.int64), + np.zeros(10, dtype=np.int64), ), # Empty uids and bonds ], ) @@ -291,14 +292,14 @@ def test_edge_cases(test_id, n, uids, bonds, expected_output): result = weight_utils.convert_bond_uids_and_vals_to_tensor(n, uids, bonds) # Assert - assert torch.equal(result, expected_output), f"Failed {test_id}" + assert np.array_equal(result, expected_output), f"Failed {test_id}" @pytest.mark.parametrize( "test_id, n, uids, bonds, exception", [ ("error-1", 5, [1, 3, 6], [10, 20, 30], IndexError), # uid out of bounds - ("error-2", -1, [0], [10], RuntimeError), # Negative number of neurons + ("error-2", -1, [0], [10], ValueError), # Negative number of neurons ], ) def test_error_cases(test_id, n, uids, bonds, exception): diff --git a/tests/unit_tests/utils/test_weight_utils.py b/tests/unit_tests/utils/test_weight_utils.py index 0875d921e0..f315edcdce 100644 --- a/tests/unit_tests/utils/test_weight_utils.py +++ b/tests/unit_tests/utils/test_weight_utils.py @@ -17,14 +17,14 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. -import torch +import numpy as np import bittensor.utils.weight_utils as weight_utils import pytest def test_convert_weight_and_uids(): - uids = torch.tensor(list(range(10))) - weights = torch.rand(10) + uids = np.arange(10) + weights = np.random.rand(10) weight_utils.convert_weights_and_uids_for_emit(uids, weights) # min weight < 0 @@ -44,77 +44,77 @@ def test_convert_weight_and_uids(): weight_utils.convert_weights_and_uids_for_emit(uids, weights[1:]) # sum(weights) == 0 - weights = torch.zeros(10) + weights = np.zeros(10) weight_utils.convert_weights_and_uids_for_emit(uids, weights) # test for overflow and underflow for _ in range(5): - uids = torch.tensor(list(range(10))) - weights = torch.rand(10) + uids = np.arange(10) + weights = np.random.rand(10) weight_utils.convert_weights_and_uids_for_emit(uids, weights) def test_normalize_with_max_weight(): - weights = torch.rand(1000) + weights = np.random.rand(1000) wn = weight_utils.normalize_max_weight(weights, limit=0.01) assert wn.max() <= 0.01 - weights = torch.zeros(1000) + weights = np.zeros(1000) wn = weight_utils.normalize_max_weight(weights, limit=0.01) assert wn.max() <= 0.01 - weights = torch.rand(1000) + weights = np.random.rand(1000) wn = weight_utils.normalize_max_weight(weights, limit=0.02) assert wn.max() <= 0.02 - weights = torch.zeros(1000) + weights = np.zeros(1000) wn = weight_utils.normalize_max_weight(weights, limit=0.02) assert wn.max() <= 0.02 - weights = torch.rand(1000) + weights = np.random.rand(1000) wn = weight_utils.normalize_max_weight(weights, limit=0.03) assert wn.max() <= 0.03 - weights = torch.zeros(1000) + weights = np.zeros(1000) wn = weight_utils.normalize_max_weight(weights, limit=0.03) assert wn.max() <= 0.03 # Check for Limit limit = 0.001 - weights = torch.rand(2000) + weights = np.random.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 ( + assert abs((w.max() >= limit and (limit - wn.max())) < 0.001) or ( w.max() < limit and wn.max() < limit ) # Check for Zeros limit = 0.01 - weights = torch.zeros(2000) + weights = np.zeros(2000) wn = weight_utils.normalize_max_weight(weights, limit=limit) assert wn.max() == 1 / 2000 # Check for Ordering after normalization - weights = torch.rand(100) + weights = np.random.rand(100) wn = weight_utils.normalize_max_weight(weights, limit=1) - assert torch.equal(wn, weights / weights.sum()) + assert np.array_equal(wn, weights / weights.sum()) - # Check for eplison changes - eplison = 0.01 - weights, _ = torch.sort(torch.rand(100)) + # Check for epsilon changes + epsilon = 0.01 + weights = np.sort(np.random.rand(100)) x = weights / weights.sum() limit = x[-10] - change = eplison * limit + change = epsilon * 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 + assert np.abs(y - z).sum() < epsilon @pytest.mark.parametrize( "test_id, n, uids, weights, expected", [ - ("happy-path-1", 3, [0, 1, 2], [15, 5, 80], torch.tensor([0.15, 0.05, 0.8])), - ("happy-path-2", 4, [1, 3], [50, 50], torch.tensor([0.0, 0.5, 0.0, 0.5])), + ("happy-path-1", 3, [0, 1, 2], [15, 5, 80], np.array([0.15, 0.05, 0.8])), + ("happy-path-2", 4, [1, 3], [50, 50], np.array([0.0, 0.5, 0.0, 0.5])), ], ) def test_convert_weight_uids_and_vals_to_tensor_happy_path( @@ -124,15 +124,15 @@ def test_convert_weight_uids_and_vals_to_tensor_happy_path( result = weight_utils.convert_weight_uids_and_vals_to_tensor(n, uids, weights) # Assert - assert torch.allclose(result, expected), f"Failed {test_id}" + assert np.allclose(result, expected), f"Failed {test_id}" @pytest.mark.parametrize( "test_id, n, uids, weights, expected", [ - ("edge_case_empty", 5, [], [], torch.zeros(5)), - ("edge_case_single", 1, [0], [100], torch.tensor([1.0])), - ("edge_case_all_zeros", 4, [0, 1, 2, 3], [0, 0, 0, 0], torch.zeros(4)), + ("edge_case_empty", 5, [], [], np.zeros(5)), + ("edge_case_single", 1, [0], [100], np.array([1.0])), + ("edge_case_all_zeros", 4, [0, 1, 2, 3], [0, 0, 0, 0], np.zeros(4)), ], ) def test_convert_weight_uids_and_vals_to_tensor_edge_cases( @@ -142,14 +142,14 @@ def test_convert_weight_uids_and_vals_to_tensor_edge_cases( result = weight_utils.convert_weight_uids_and_vals_to_tensor(n, uids, weights) # Assert - assert torch.allclose(result, expected), f"Failed {test_id}" + assert np.allclose(result, expected), f"Failed {test_id}" @pytest.mark.parametrize( "test_id, n, uids, weights, exception", [ ("error-case-mismatched-lengths", 3, [0, 1, 3, 4, 5], [10, 20, 30], IndexError), - ("error-case-negative-n", -1, [0, 1], [10, 20], RuntimeError), + ("error-case-negative-n", -1, [0, 1], [10, 20], ValueError), ("error-case-invalid-uids", 3, [0, 3], [10, 20], IndexError), ], ) @@ -170,7 +170,7 @@ def test_convert_weight_uids_and_vals_to_tensor_error_cases( [0, 1, 2], [15, 5, 80], [0, 1, 2], - torch.tensor([0.15, 0.05, 0.8]), + np.array([0.15, 0.05, 0.8]), ), ( "happy-path-2", @@ -178,7 +178,7 @@ def test_convert_weight_uids_and_vals_to_tensor_error_cases( [0, 2], [300, 300], [0, 1, 2], - torch.tensor([0.5, 0.0, 0.5]), + np.array([0.5, 0.0, 0.5]), ), ], ) @@ -191,7 +191,7 @@ def test_convert_root_weight_uids_and_vals_to_tensor_happy_paths( ) # Assert - assert torch.allclose(result, expected, atol=1e-4), f"Failed {test_id}" + assert np.allclose(result, expected, atol=1e-4), f"Failed {test_id}" @pytest.mark.parametrize( @@ -203,7 +203,7 @@ def test_convert_root_weight_uids_and_vals_to_tensor_happy_paths( [0], [0], [0], - torch.tensor([0.0]), + np.array([0.0]), ), # Single neuron with zero weight ( "edge-2", @@ -211,7 +211,7 @@ def test_convert_root_weight_uids_and_vals_to_tensor_happy_paths( [0, 1], [0, 0], [0, 1], - torch.tensor([0.0, 0.0]), + np.array([0.0, 0.0]), ), # All zero weights ], ) @@ -224,7 +224,7 @@ def test_convert_root_weight_uids_and_vals_to_tensor_edge_cases( ) # Assert - assert torch.allclose(result, expected, atol=1e-4), f"Failed {test_id}" + assert np.allclose(result, expected, atol=1e-4), f"Failed {test_id}" @pytest.mark.parametrize( @@ -253,16 +253,16 @@ def test_convert_root_weight_uids_and_vals_to_tensor_error_cases( 5, [1, 3, 4], [10, 20, 30], - torch.tensor([0, 10, 0, 20, 30], dtype=torch.int64), + np.array([0, 10, 0, 20, 30], dtype=np.int64), ), ( "happy-path-2", 3, [0, 1, 2], [7, 8, 9], - torch.tensor([7, 8, 9], dtype=torch.int64), + np.array([7, 8, 9], dtype=np.int64), ), - ("happy-path-3", 4, [2], [15], torch.tensor([0, 0, 15, 0], dtype=torch.int64)), + ("happy-path-3", 4, [2], [15], np.array([0, 0, 15, 0], dtype=np.int64)), ], ) def test_happy_path(test_id, n, uids, bonds, expected_output): @@ -270,19 +270,19 @@ def test_happy_path(test_id, n, uids, bonds, expected_output): result = weight_utils.convert_bond_uids_and_vals_to_tensor(n, uids, bonds) # Assert - assert torch.equal(result, expected_output), f"Failed {test_id}" + assert np.array_equal(result, expected_output), f"Failed {test_id}" @pytest.mark.parametrize( "test_id, n, uids, bonds, expected_output", [ - ("edge-1", 1, [0], [0], torch.tensor([0], dtype=torch.int64)), # Single element + ("edge-1", 1, [0], [0], np.array([0], dtype=np.int64)), # Single element ( "edge-2", 10, [], [], - torch.zeros(10, dtype=torch.int64), + np.zeros(10, dtype=np.int64), ), # Empty uids and bonds ], ) @@ -291,14 +291,14 @@ def test_edge_cases(test_id, n, uids, bonds, expected_output): result = weight_utils.convert_bond_uids_and_vals_to_tensor(n, uids, bonds) # Assert - assert torch.equal(result, expected_output), f"Failed {test_id}" + assert np.array_equal(result, expected_output), f"Failed {test_id}" @pytest.mark.parametrize( "test_id, n, uids, bonds, exception", [ ("error-1", 5, [1, 3, 6], [10, 20, 30], IndexError), # uid out of bounds - ("error-2", -1, [0], [10], RuntimeError), # Negative number of neurons + ("error-2", -1, [0], [10], ValueError), # Negative number of neurons ], ) def test_error_cases(test_id, n, uids, bonds, exception):